A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: automerge sync

+76 -20
+76 -20
src/components/transformer/output/bytes/automerge/element.js
··· 4 4 5 5 import "@components/output/polymorphic/indexed-db/element.js"; 6 6 7 - import { computed, signal, untracked } from "@common/signal.js"; 7 + import { computed, signal } from "@common/signal.js"; 8 8 import { 9 9 recursivelyCloneRecords, 10 10 removeUndefinedValuesFromRecord, ··· 38 38 * @param {SignalReader<Uint8Array | undefined>} localCollection 39 39 * @param {SignalReader<Uint8Array | undefined>} remoteCollection 40 40 * @param {Automerge.Doc<T>} initial 41 - * @returns {SignalReader<Automerge.Doc<T>>} 41 + * @returns {SignalReader<{ doc: Automerge.Doc<T>; diverged: boolean; local: boolean; remote: boolean; }>} 42 42 */ 43 - const mergedDoc = (localCollection, remoteCollection, initial) => 43 + const state = (localCollection, remoteCollection, initial) => 44 44 computed(() => { 45 45 const l = loadDocument(localCollection); 46 46 const r = remote.ready() ? loadDocument(remoteCollection) : undefined; 47 47 48 - console.log("Local:", l); 49 - console.log("Remote:", r); 48 + if (!r) { 49 + return l 50 + ? { doc: l, diverged: true, local: false, remote: true } 51 + : { doc: initial, diverged: false, local: false, remote: false }; 52 + } else if (!l) { 53 + return { doc: r, diverged: true, local: true, remote: false }; 54 + } 50 55 51 - if (!r) return l ?? initial; 52 - if (!l) return r; 56 + const lh = Automerge.getHeads(l)[0]; 57 + const rh = Automerge.getHeads(r)[0]; 58 + const diverged = lh !== rh; 53 59 54 - console.log("Merging"); 55 - return Automerge.merge(Automerge.clone(l), Automerge.clone(r)); 60 + return { 61 + doc: diverged 62 + ? Automerge.merge(Automerge.clone(l), Automerge.clone(r)) 63 + : r, 64 + diverged, 65 + local: Automerge.hasHeads(r, [lh]), 66 + remote: Automerge.hasHeads(l, [rh]), 67 + }; 56 68 }); 57 69 58 - const facetsDoc = mergedDoc( 70 + const facets = state( 59 71 computed(() => local()?.facets?.collection()), 60 72 remote.facets.collection, 61 73 INITIAL_FACETS_DOCUMENT, 62 74 ); 63 75 64 - const playlistItemsDoc = mergedDoc( 76 + const playlistItems = state( 65 77 computed(() => local()?.playlistItems?.collection()), 66 78 remote.playlistItems.collection, 67 79 INITIAL_PLAYLIST_ITEMS_DOCUMENT, 68 80 ); 69 81 70 - const themesDoc = mergedDoc( 82 + const themes = state( 71 83 computed(() => local()?.themes?.collection()), 72 84 remote.themes.collection, 73 85 INITIAL_THEMES_DOCUMENT, 74 86 ); 75 87 76 - const tracksDoc = mergedDoc( 88 + const tracks = state( 77 89 computed(() => local()?.tracks?.collection()), 78 90 remote.tracks.collection, 79 91 INITIAL_TRACKS_DOCUMENT, ··· 82 94 this.facets = automergeEntry( 83 95 computed(() => local()?.facets), 84 96 remote.facets, 85 - facetsDoc, 97 + computed(() => facets().doc), 86 98 { 87 99 stripUndefined: true, 88 100 }, ··· 91 103 this.playlistItems = automergeEntry( 92 104 computed(() => local()?.playlistItems), 93 105 remote.playlistItems, 94 - playlistItemsDoc, 106 + computed(() => playlistItems().doc), 95 107 ); 96 108 97 109 this.themes = automergeEntry( 98 110 computed(() => local()?.themes), 99 111 remote.themes, 100 - themesDoc, 112 + computed(() => themes().doc), 101 113 { 102 114 stripUndefined: true, 103 115 }, ··· 106 118 this.tracks = automergeEntry( 107 119 computed(() => local()?.tracks), 108 120 remote.tracks, 109 - tracksDoc, 121 + computed(() => tracks().doc), 110 122 ); 111 123 112 124 this.ready = () => true; 125 + 126 + // Effects 127 + this.effect(() => { 128 + const l = local(); 129 + if (!l) return; 130 + 131 + this.effect(() => { 132 + if (remote.facets.state() !== "loaded") return; 133 + const s = facets(); 134 + if (s.diverged) { 135 + const bytes = Automerge.save(s.doc); 136 + if (l && s.local) l.facets.save(bytes); 137 + if (s.remote) remote.facets.save(bytes); 138 + } 139 + }); 140 + 141 + this.effect(() => { 142 + if (remote.playlistItems.state() !== "loaded") return; 143 + const s = playlistItems(); 144 + if (s.diverged) { 145 + const bytes = Automerge.save(s.doc); 146 + if (l && s.local) l.playlistItems.save(bytes); 147 + if (s.remote) remote.playlistItems.save(bytes); 148 + } 149 + }); 150 + 151 + this.effect(() => { 152 + if (remote.themes.state() !== "loaded") return; 153 + const s = themes(); 154 + if (s.diverged) { 155 + const bytes = Automerge.save(s.doc); 156 + if (l && s.local) l.themes.save(bytes); 157 + if (s.remote) remote.themes.save(bytes); 158 + } 159 + }); 160 + 161 + this.effect(() => { 162 + if (remote.tracks.state() !== "loaded") return; 163 + const s = tracks(); 164 + if (s.diverged) { 165 + const bytes = Automerge.save(s.doc); 166 + if (l && s.local) l.tracks.save(bytes); 167 + if (s.remote) remote.tracks.save(bytes); 168 + } 169 + }); 170 + }); 113 171 } 114 172 115 173 // SIGNALS ··· 196 254 }); 197 255 198 256 const bytes = Automerge.save(doc); 199 - 200 - await untracked(local)?.save(bytes); 201 - await remote.save(bytes); 257 + await local()?.save(bytes); 202 258 }, 203 259 state: computed(() => local()?.state() ?? "sleeping"), 204 260 };