@weaveprotocol/core 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +322 -45
  2. package/dist/doors/doors.d.ts +139 -0
  3. package/dist/doors/doors.d.ts.map +1 -0
  4. package/dist/doors/doors.js +210 -0
  5. package/dist/doors/doors.js.map +1 -0
  6. package/dist/doors/index.d.ts +9 -0
  7. package/dist/doors/index.d.ts.map +1 -0
  8. package/dist/doors/index.js +7 -0
  9. package/dist/doors/index.js.map +1 -0
  10. package/dist/identity/contact-key.d.ts +68 -0
  11. package/dist/identity/contact-key.d.ts.map +1 -0
  12. package/dist/identity/contact-key.js +185 -0
  13. package/dist/identity/contact-key.js.map +1 -0
  14. package/dist/index.d.ts +16 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +10 -5
  17. package/dist/index.js.map +1 -1
  18. package/dist/network/index.d.ts +2 -0
  19. package/dist/network/index.d.ts.map +1 -1
  20. package/dist/network/index.js +1 -0
  21. package/dist/network/index.js.map +1 -1
  22. package/dist/network/mailbox.d.ts +49 -0
  23. package/dist/network/mailbox.d.ts.map +1 -0
  24. package/dist/network/mailbox.js +125 -0
  25. package/dist/network/mailbox.js.map +1 -0
  26. package/dist/network/mesh.d.ts +6 -0
  27. package/dist/network/mesh.d.ts.map +1 -1
  28. package/dist/network/mesh.js +11 -1
  29. package/dist/network/mesh.js.map +1 -1
  30. package/dist/network/multi-signaling.d.ts +12 -2
  31. package/dist/network/multi-signaling.d.ts.map +1 -1
  32. package/dist/network/multi-signaling.js +86 -14
  33. package/dist/network/multi-signaling.js.map +1 -1
  34. package/dist/network/peer-auth.d.ts +50 -16
  35. package/dist/network/peer-auth.d.ts.map +1 -1
  36. package/dist/network/peer-auth.js +52 -15
  37. package/dist/network/peer-auth.js.map +1 -1
  38. package/dist/node/carrier.d.ts +87 -0
  39. package/dist/node/carrier.d.ts.map +1 -1
  40. package/dist/node/carrier.js +180 -59
  41. package/dist/node/carrier.js.map +1 -1
  42. package/dist/node/copy.d.ts.map +1 -1
  43. package/dist/node/copy.js +6 -3
  44. package/dist/node/copy.js.map +1 -1
  45. package/dist/node/host.d.ts +117 -0
  46. package/dist/node/host.d.ts.map +1 -0
  47. package/dist/node/host.js +192 -0
  48. package/dist/node/host.js.map +1 -0
  49. package/dist/node/index.d.ts +6 -2
  50. package/dist/node/index.d.ts.map +1 -1
  51. package/dist/node/index.js +1 -0
  52. package/dist/node/index.js.map +1 -1
  53. package/dist/node/node.d.ts.map +1 -1
  54. package/dist/node/node.js +958 -25
  55. package/dist/node/node.js.map +1 -1
  56. package/dist/node/space-runtime.d.ts +42 -25
  57. package/dist/node/space-runtime.d.ts.map +1 -1
  58. package/dist/node/space-runtime.js +734 -35
  59. package/dist/node/space-runtime.js.map +1 -1
  60. package/dist/node/types.d.ts +375 -3
  61. package/dist/node/types.d.ts.map +1 -1
  62. package/dist/privacy/space-encryption.d.ts +16 -0
  63. package/dist/privacy/space-encryption.d.ts.map +1 -1
  64. package/dist/privacy/space-encryption.js +40 -0
  65. package/dist/privacy/space-encryption.js.map +1 -1
  66. package/dist/query/engine.js +1 -1
  67. package/dist/query/engine.js.map +1 -1
  68. package/dist/query/types.d.ts +7 -0
  69. package/dist/query/types.d.ts.map +1 -1
  70. package/dist/query/types.js.map +1 -1
  71. package/dist/react/use-query.d.ts +1 -1
  72. package/dist/react/use-query.d.ts.map +1 -1
  73. package/dist/records/topics.d.ts +21 -0
  74. package/dist/records/topics.d.ts.map +1 -0
  75. package/dist/records/topics.js +86 -0
  76. package/dist/records/topics.js.map +1 -0
  77. package/dist/schema/collection-def.d.ts +7 -0
  78. package/dist/schema/collection-def.d.ts.map +1 -1
  79. package/dist/schema/collection-def.js +4 -0
  80. package/dist/schema/collection-def.js.map +1 -1
  81. package/dist/schema/expression.d.ts +2 -0
  82. package/dist/schema/expression.d.ts.map +1 -1
  83. package/dist/schema/expression.js +1 -0
  84. package/dist/schema/expression.js.map +1 -1
  85. package/dist/schemas/contacts.d.ts +214 -0
  86. package/dist/schemas/contacts.d.ts.map +1 -0
  87. package/dist/schemas/contacts.js +98 -0
  88. package/dist/schemas/contacts.js.map +1 -0
  89. package/dist/schemas/index.d.ts +2 -0
  90. package/dist/schemas/index.d.ts.map +1 -1
  91. package/dist/schemas/index.js +1 -0
  92. package/dist/schemas/index.js.map +1 -1
  93. package/dist/session/auth.d.ts.map +1 -1
  94. package/dist/session/auth.js +15 -3
  95. package/dist/session/auth.js.map +1 -1
  96. package/dist/session/connect.d.ts +25 -1
  97. package/dist/session/connect.d.ts.map +1 -1
  98. package/dist/session/connect.js +9 -2
  99. package/dist/session/connect.js.map +1 -1
  100. package/dist/session/hosting.d.ts +155 -0
  101. package/dist/session/hosting.d.ts.map +1 -0
  102. package/dist/session/hosting.js +160 -0
  103. package/dist/session/hosting.js.map +1 -0
  104. package/dist/space/account-registry.d.ts +6 -0
  105. package/dist/space/account-registry.d.ts.map +1 -1
  106. package/dist/space/account-registry.js +16 -4
  107. package/dist/space/account-registry.js.map +1 -1
  108. package/dist/space/notify.d.ts +88 -0
  109. package/dist/space/notify.d.ts.map +1 -0
  110. package/dist/space/notify.js +127 -0
  111. package/dist/space/notify.js.map +1 -0
  112. package/dist/space/pass.d.ts +6 -0
  113. package/dist/space/pass.d.ts.map +1 -1
  114. package/dist/space/pass.js +5 -2
  115. package/dist/space/pass.js.map +1 -1
  116. package/dist/space/roles.d.ts +71 -0
  117. package/dist/space/roles.d.ts.map +1 -1
  118. package/dist/space/roles.js +114 -0
  119. package/dist/space/roles.js.map +1 -1
  120. package/dist/space/space-access.d.ts +24 -0
  121. package/dist/space/space-access.d.ts.map +1 -1
  122. package/dist/space/space-access.js +24 -0
  123. package/dist/space/space-access.js.map +1 -1
  124. package/dist/space/space-manager.d.ts +35 -1
  125. package/dist/space/space-manager.d.ts.map +1 -1
  126. package/dist/space/space-manager.js +72 -25
  127. package/dist/space/space-manager.js.map +1 -1
  128. package/dist/storage/blob/memory.d.ts +9 -0
  129. package/dist/storage/blob/memory.d.ts.map +1 -0
  130. package/dist/storage/blob/memory.js +20 -0
  131. package/dist/storage/blob/memory.js.map +1 -0
  132. package/dist/storage/blob/s3.d.ts +16 -0
  133. package/dist/storage/blob/s3.d.ts.map +1 -0
  134. package/dist/storage/blob/s3.js +86 -0
  135. package/dist/storage/blob/s3.js.map +1 -0
  136. package/dist/storage/blob-store.d.ts +26 -0
  137. package/dist/storage/blob-store.d.ts.map +1 -0
  138. package/dist/storage/blob-store.js +2 -0
  139. package/dist/storage/blob-store.js.map +1 -0
  140. package/dist/storage/encrypted-adapter.d.ts +3 -3
  141. package/dist/storage/encrypted-adapter.js +3 -3
  142. package/dist/storage/folder-adapter.d.ts +8 -8
  143. package/dist/storage/folder-adapter.d.ts.map +1 -1
  144. package/dist/storage/folder-adapter.js +7 -7
  145. package/dist/storage/folder-reconcile.d.ts +12 -14
  146. package/dist/storage/folder-reconcile.d.ts.map +1 -1
  147. package/dist/storage/folder-reconcile.js +20 -14
  148. package/dist/storage/folder-reconcile.js.map +1 -1
  149. package/dist/storage/index.d.ts +0 -1
  150. package/dist/storage/index.d.ts.map +1 -1
  151. package/dist/storage/index.js +0 -1
  152. package/dist/storage/index.js.map +1 -1
  153. package/dist/storage/indexeddb-adapter.d.ts.map +1 -1
  154. package/dist/storage/indexeddb-adapter.js +8 -9
  155. package/dist/storage/indexeddb-adapter.js.map +1 -1
  156. package/dist/storage/mirror.d.ts +68 -0
  157. package/dist/storage/mirror.d.ts.map +1 -0
  158. package/dist/storage/mirror.js +174 -0
  159. package/dist/storage/mirror.js.map +1 -0
  160. package/dist/storage/segment.d.ts +20 -0
  161. package/dist/storage/segment.d.ts.map +1 -0
  162. package/dist/storage/segment.js +27 -0
  163. package/dist/storage/segment.js.map +1 -0
  164. package/dist/storage/storage-provider.d.ts +36 -40
  165. package/dist/storage/storage-provider.d.ts.map +1 -1
  166. package/dist/storage/storage-provider.js +208 -117
  167. package/dist/storage/storage-provider.js.map +1 -1
  168. package/dist/sync/index.d.ts +1 -1
  169. package/dist/sync/index.d.ts.map +1 -1
  170. package/dist/sync/index.js +1 -1
  171. package/dist/sync/index.js.map +1 -1
  172. package/dist/sync/negentropy.d.ts +68 -0
  173. package/dist/sync/negentropy.d.ts.map +1 -0
  174. package/dist/sync/negentropy.js +379 -0
  175. package/dist/sync/negentropy.js.map +1 -0
  176. package/dist/sync/sync-engine.d.ts +45 -13
  177. package/dist/sync/sync-engine.d.ts.map +1 -1
  178. package/dist/sync/sync-engine.js +290 -207
  179. package/dist/sync/sync-engine.js.map +1 -1
  180. package/dist/sync/sync-messages.d.ts +42 -32
  181. package/dist/sync/sync-messages.d.ts.map +1 -1
  182. package/dist/sync/sync-messages.js +1 -1
  183. package/dist/sync/sync-messages.js.map +1 -1
  184. package/dist/types.d.ts +9 -0
  185. package/dist/types.d.ts.map +1 -1
  186. package/dist/types.js.map +1 -1
  187. package/dist/utils/hash.d.ts +9 -0
  188. package/dist/utils/hash.d.ts.map +1 -1
  189. package/dist/utils/hash.js +33 -0
  190. package/dist/utils/hash.js.map +1 -1
  191. package/package.json +8 -3
  192. package/dist/storage/mst.d.ts +0 -121
  193. package/dist/storage/mst.d.ts.map +0 -1
  194. package/dist/storage/mst.js +0 -402
  195. package/dist/storage/mst.js.map +0 -1
  196. package/dist/sync/anti-entropy.d.ts +0 -49
  197. package/dist/sync/anti-entropy.d.ts.map +0 -1
  198. package/dist/sync/anti-entropy.js +0 -62
  199. package/dist/sync/anti-entropy.js.map +0 -1
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module storage/segment
3
+ * A batch of record versions, as a mirror keeps them: exactly as they travel
4
+ * on the wire — private bodies still sealed — packed into one immutable file.
5
+ *
6
+ * A segment's name sorts (a counter, for people reading a listing) and ends
7
+ * in the hash of its bytes, so an upload retried after a timeout lands on the
8
+ * same name, and two writers never write the same file differently.
9
+ */
10
+ import type { Expression } from '../types.js';
11
+ export declare const SEGMENT_SUFFIX = ".seg";
12
+ /** Packs versions into a segment's bytes */
13
+ export declare function packSegment(versions: ReadonlyArray<Expression>): Uint8Array;
14
+ /** The versions in a segment; none for bytes that are not one */
15
+ export declare function unpackSegment(bytes: Uint8Array): Expression[];
16
+ /** A segment's file name: `000042-<first 16 hex of its hash>.seg` */
17
+ export declare function segmentName(counter: number, bytes: Uint8Array): Promise<string>;
18
+ /** Roughly how many bytes a version takes in a segment */
19
+ export declare const versionSize: (version: Expression) => number;
20
+ //# sourceMappingURL=segment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../src/storage/segment.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,eAAO,MAAM,cAAc,SAAS,CAAC;AAErC,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,UAAU,CAE3E;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAQ7D;AAED,qEAAqE;AACrE,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrF;AAED,0DAA0D;AAC1D,eAAO,MAAM,WAAW,GAAI,SAAS,UAAU,WAA+C,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { sha256 } from '../utils/hash.js';
2
+ import { utf8Decode, utf8Encode } from '../utils/encoding.js';
3
+ export const SEGMENT_SUFFIX = '.seg';
4
+ /** Packs versions into a segment's bytes */
5
+ export function packSegment(versions) {
6
+ return utf8Encode(JSON.stringify({ v: 1, versions }));
7
+ }
8
+ /** The versions in a segment; none for bytes that are not one */
9
+ export function unpackSegment(bytes) {
10
+ try {
11
+ const parsed = JSON.parse(utf8Decode(bytes));
12
+ if (parsed.v !== 1 || !Array.isArray(parsed.versions))
13
+ return [];
14
+ return parsed.versions.filter((version) => typeof version === 'object' && version !== null && typeof version.id === 'string');
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ }
20
+ /** A segment's file name: `000042-<first 16 hex of its hash>.seg` */
21
+ export async function segmentName(counter, bytes) {
22
+ const hash = Array.from((await sha256(bytes)).subarray(0, 8), (b) => b.toString(16).padStart(2, '0')).join('');
23
+ return `${String(counter).padStart(6, '0')}-${hash}${SEGMENT_SUFFIX}`;
24
+ }
25
+ /** Roughly how many bytes a version takes in a segment */
26
+ export const versionSize = (version) => utf8Encode(JSON.stringify(version)).length;
27
+ //# sourceMappingURL=segment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.js","sourceRoot":"","sources":["../../src/storage/segment.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAErC,4CAA4C;AAC5C,MAAM,UAAU,WAAW,CAAC,QAAmC;IAC7D,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAwC,CAAC;QACpF,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACjE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAyB,EAAE,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAQ,OAAsB,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IACvK,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,KAAiB;IAClE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/G,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;AACxE,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAmB,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC"}
@@ -1,32 +1,37 @@
1
1
  /**
2
2
  * @module storage-provider
3
- * A space's store: record versions, and the Merkle Search Tree that indexes them.
4
- *
5
- * The tree maps **record keys to version ids**:
3
+ * A space's store: record versions, and plain entries saying which is which.
6
4
  *
7
5
  * ```
8
- * r/<key> → the current version (every record)
9
- * g/<key> → the record's first version (once it has been edited)
10
- * h/<key>/<seq>/<id> → a superseded version (only if marked `retain`)
6
+ * r/<key> → the current version (every record)
7
+ * g/<key> → the record's first version (once it has been edited)
8
+ * h/<key>/<seq>/<id> → a superseded version (only if marked `retain`)
9
+ * i/<collection>/<time>/<id> → every version kept, by collection — what sync compares
11
10
  * ```
12
11
  *
13
12
  * A version that arrives is compared with the one at `r/<key>` by the ordering
14
13
  * rule (`records/version.ts`). The winner takes `r/<key>`; the loser's body is
15
14
  * dropped — unless it is the record's first version, kept as proof of who
16
15
  * created it, or its writer marked it `retain`. The same set of versions gives
17
- * the same tree whatever order they arrive in, which is what lets two peers
18
- * converge.
16
+ * the same entries whatever order they arrive in, which is what lets two
17
+ * peers converge.
18
+ *
19
+ * Sync compares the `i/` entries: the set of versions kept, one collection at
20
+ * a time (`sync/negentropy.ts`). Nothing else is stored for it. The sets are
21
+ * read once and kept in memory, updated by every change made here; a change
22
+ * made by another writer of the same store (a tab, a folder's other device)
23
+ * reaches them through {@link StorageProvider.invalidate}.
19
24
  */
20
25
  import type { StorageAdapter, Expression } from '../types.js';
26
+ import { ItemSet, type Sum } from '../sync/negentropy.js';
21
27
  export interface StorageProvider {
22
28
  /**
23
29
  * Takes in a version of a record: it becomes current if it supersedes the
24
30
  * current one, and is kept or dropped otherwise. Idempotent.
25
- * @returns The new tree root
26
31
  */
27
- addExpression(expression: Expression): Promise<string | null>;
28
- /** Drops a version from the tree and the store, wherever it is indexed. */
29
- removeExpression(id: string): Promise<string | null>;
32
+ addExpression(expression: Expression): Promise<void>;
33
+ /** Drops a version from the store and wherever it is indexed. */
34
+ removeExpression(id: string): Promise<void>;
30
35
  /** A version by its id, current or not, if this store holds it. */
31
36
  getExpression(id: string): Promise<Expression | null>;
32
37
  /** The current version of a record — possibly a delete. */
@@ -35,51 +40,42 @@ export interface StorageProvider {
35
40
  getGenesis(key: string): Promise<Expression | null>;
36
41
  /** Every version of a record this store keeps: current, first, and retained. Newest first. */
37
42
  history(key: string): Promise<Expression[]>;
38
- /** {@link history} for every record whose key starts with `prefix`, reading only that part of the tree. */
43
+ /** {@link history} for every record whose key starts with `prefix`, reading only those entries. */
39
44
  histories(prefix: string): Promise<Map<string, Expression[]>>;
40
45
  /** Current versions of every record, deletes included. */
41
46
  listCurrent(): Promise<Expression[]>;
42
47
  /** Current versions in one collection, deletes included — found through the store's collection index. */
43
48
  queryExpressions(collection: string): Promise<Expression[]>;
44
- /** Every tree entry, for sync and copying: `r/…`, `g/…`, `h/…` keys and the version ids under them. */
45
- entries(): Promise<Array<{
46
- key: string;
47
- value: string;
48
- }>>;
49
- /** Get the current MST root CID. */
50
- getRootCid(): Promise<string | null>;
49
+ /** The id of every version this store keeps — current, first and retained. */
50
+ versionIds(): Promise<string[]>;
51
+ /** The versions kept in one collection, as sync compares them: sorted, with sums. */
52
+ items(collection: string): Promise<ItemSet>;
53
+ /** Every collection this store keeps anything in, and the sum of its version ids. */
54
+ sums(): Promise<ReadonlyMap<string, Sum>>;
51
55
  /**
52
- * Deletes the tree nodes the root no longer reaches. Every change rewrites
53
- * the path to one entry and leaves the old path behind, so without this a
54
- * store grows with every edit ever made rather than with its records.
55
- *
56
- * Tabs share a browser's store, and a change another tab has in flight may
57
- * build on nodes this one's root has left. So a node is deleted only once it
58
- * was already unreachable at a compaction at least `graceMs` earlier —
59
- * remembered in the store, so it holds across sessions. A store whose other
60
- * writers can lag by more than that — a folder behind a sync service — must
61
- * not be compacted at all.
62
- * @returns How many nodes were deleted
56
+ * One fingerprint of everything kept, hex. Equal fingerprints mean the
57
+ * same versions kept — for status, and for tests.
63
58
  */
64
- compact(): Promise<number>;
59
+ fingerprint(): Promise<string>;
60
+ /** Forgets what was read into memory: another writer changed the store underneath. */
61
+ invalidate(): void;
65
62
  /** Get the underlying storage adapter. */
66
63
  getAdapter(): StorageAdapter;
67
64
  /** Close the storage adapter. */
68
65
  close(): Promise<void>;
69
66
  }
70
- export interface StorageProviderOptions {
71
- /** Compact on its own after this many changes (see `compact`). Off by default. */
72
- readonly compactEvery?: number;
73
- /** How long a node stays unreachable before `compact` deletes it. Default a minute. */
74
- readonly graceMs?: number;
75
- }
76
67
  export declare const CURRENT_PREFIX = "r/";
77
68
  export declare const GENESIS_PREFIX = "g/";
78
69
  export declare const HISTORY_PREFIX = "h/";
70
+ export declare const ITEM_PREFIX = "i/";
71
+ /**
72
+ * Where a version sorts for sync: its own time, in whole seconds. Only an
73
+ * order — a wrong clock makes a version slower to find, never lost.
74
+ */
75
+ export declare function syncTime(version: Pick<Expression, 'createdAt'>): number;
79
76
  /**
80
77
  * Creates a StorageProvider wrapping an adapter.
81
78
  * @param adapter The initialized storage adapter.
82
- * @returns The StorageProvider orchestrator.
83
79
  */
84
- export declare function createStorageProvider(adapter: StorageAdapter, options?: StorageProviderOptions): StorageProvider;
80
+ export declare function createStorageProvider(adapter: StorageAdapter): StorageProvider;
85
81
  //# sourceMappingURL=storage-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-provider.d.ts","sourceRoot":"","sources":["../../src/storage/storage-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAW,MAAM,aAAa,CAAC;AAKvE,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrD,mEAAmE;IACnE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACtD,2DAA2D;IAC3D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,kFAAkF;IAClF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,8FAA8F;IAC9F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5C,2GAA2G;IAC3G,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IAC9D,0DAA0D;IAC1D,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACrC,yGAAyG;IACzG,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5D,uGAAuG;IACvG,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1D,oCAAoC;IACpC,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,0CAA0C;IAC1C,UAAU,IAAI,cAAc,CAAC;IAC7B,iCAAiC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAQD,MAAM,WAAW,sBAAsB;IACrC,kFAAkF;IAClF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,cAAc,OAAO,CAAC;AAOnC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAsMpH"}
1
+ {"version":3,"file":"storage-provider.d.ts","sourceRoot":"","sources":["../../src/storage/storage-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAW,MAAM,aAAa,CAAC;AAIvE,OAAO,EAAmD,OAAO,EAA4B,KAAK,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAErI,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,mEAAmE;IACnE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACtD,2DAA2D;IAC3D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,kFAAkF;IAClF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,8FAA8F;IAC9F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5C,mGAAmG;IACnG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IAC9D,0DAA0D;IAC1D,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACrC,yGAAyG;IACzG,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5D,8EAA8E;IAC9E,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChC,qFAAqF;IACrF,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,qFAAqF;IACrF,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1C;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,sFAAsF;IACtF,UAAU,IAAI,IAAI,CAAC;IACnB,0CAA0C;IAC1C,UAAU,IAAI,cAAc,CAAC;IAC7B,iCAAiC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,WAAW,OAAO,CAAC;AAOhC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,MAAM,CAGvE;AA8BD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,eAAe,CA8P9E"}
@@ -1,191 +1,273 @@
1
1
  /**
2
2
  * @module storage-provider
3
- * A space's store: record versions, and the Merkle Search Tree that indexes them.
4
- *
5
- * The tree maps **record keys to version ids**:
3
+ * A space's store: record versions, and plain entries saying which is which.
6
4
  *
7
5
  * ```
8
- * r/<key> → the current version (every record)
9
- * g/<key> → the record's first version (once it has been edited)
10
- * h/<key>/<seq>/<id> → a superseded version (only if marked `retain`)
6
+ * r/<key> → the current version (every record)
7
+ * g/<key> → the record's first version (once it has been edited)
8
+ * h/<key>/<seq>/<id> → a superseded version (only if marked `retain`)
9
+ * i/<collection>/<time>/<id> → every version kept, by collection — what sync compares
11
10
  * ```
12
11
  *
13
12
  * A version that arrives is compared with the one at `r/<key>` by the ordering
14
13
  * rule (`records/version.ts`). The winner takes `r/<key>`; the loser's body is
15
14
  * dropped — unless it is the record's first version, kept as proof of who
16
15
  * created it, or its writer marked it `retain`. The same set of versions gives
17
- * the same tree whatever order they arrive in, which is what lets two peers
18
- * converge.
16
+ * the same entries whatever order they arrive in, which is what lets two
17
+ * peers converge.
18
+ *
19
+ * Sync compares the `i/` entries: the set of versions kept, one collection at
20
+ * a time (`sync/negentropy.ts`). Nothing else is stored for it. The sets are
21
+ * read once and kept in memory, updated by every change made here; a change
22
+ * made by another writer of the same store (a tab, a folder's other device)
23
+ * reaches them through {@link StorageProvider.invalidate}.
19
24
  */
20
- import { insertIntoMST, deleteFromMST, lookupInMST, listMSTEntries, collectReachableCids } from './mst.js';
21
25
  import { byVersion, supersedes } from '../records/version.js';
22
- import { utf8Encode, utf8Decode } from '../utils/encoding.js';
23
- const ROOT_KEY = '__mst_root';
24
- /** Unreachable nodes found by the last compaction, and when */
25
- const CONDEMNED_KEY = '__condemned';
26
- /** A tree node's key in the store: its CID, `b` and 52 base32 characters */
27
- const NODE_KEY = /^b[a-z2-7]{52}$/;
26
+ import { utf8Encode, utf8Decode, bytesToHex } from '../utils/encoding.js';
27
+ import { cidDigest } from '../utils/hash.js';
28
+ import { addToSum, combineSums, EMPTY_SUM, fingerprintOf, ItemSet, removeFromSum } from '../sync/negentropy.js';
28
29
  export const CURRENT_PREFIX = 'r/';
29
30
  export const GENESIS_PREFIX = 'g/';
30
31
  export const HISTORY_PREFIX = 'h/';
32
+ export const ITEM_PREFIX = 'i/';
31
33
  const currentKey = (key) => `${CURRENT_PREFIX}${key}`;
32
34
  const genesisKey = (key) => `${GENESIS_PREFIX}${key}`;
33
35
  /** `seq` padded so a key's history lists in order */
34
36
  const historyKey = (e) => `${HISTORY_PREFIX}${e.key}/${String(e.seq).padStart(15, '0')}/${e.id}`;
37
+ /**
38
+ * Where a version sorts for sync: its own time, in whole seconds. Only an
39
+ * order — a wrong clock makes a version slower to find, never lost.
40
+ */
41
+ export function syncTime(version) {
42
+ const ms = Date.parse(version.createdAt);
43
+ return Number.isFinite(ms) && ms > 0 ? Math.floor(ms / 1000) : 0;
44
+ }
45
+ /** Collection names may hold anything; the entry needs them free of `/`. */
46
+ const itemKey = (e) => `${ITEM_PREFIX}${encodeURIComponent(e.collection)}/${syncTime(e)}/${e.id}`;
47
+ /** Reads an `i/` entry back: null for anything malformed, or an id that is not a content id */
48
+ function parseItemKey(key) {
49
+ const [, collection, time, id, extra] = key.split('/');
50
+ if (collection === undefined || time === undefined || id === undefined || extra !== undefined)
51
+ return null;
52
+ const digest = cidDigest(id);
53
+ const timestamp = Number(time);
54
+ if (!digest || !Number.isSafeInteger(timestamp) || timestamp < 0)
55
+ return null;
56
+ try {
57
+ return { collection: decodeURIComponent(collection), id, item: { timestamp, id: digest } };
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
35
63
  /**
36
64
  * Creates a StorageProvider wrapping an adapter.
37
65
  * @param adapter The initialized storage adapter.
38
- * @returns The StorageProvider orchestrator.
39
66
  */
40
- export function createStorageProvider(adapter, options = {}) {
41
- async function getRootCid(store = adapter) {
42
- const bytes = await store.get(ROOT_KEY);
43
- return bytes ? utf8Decode(bytes) : null;
44
- }
45
- // Every change reads the root, rewrites the path to one entry, and writes a
46
- // new root. Two of those interleaved would each start from the same root and
47
- // the second would silently drop the first's entry. So changes take turns.
67
+ export function createStorageProvider(adapter) {
68
+ // Every change reads the current version, then writes. Two of those
69
+ // interleaved would each decide against the same current version, and one
70
+ // would lose its place. So changes take turns.
48
71
  let tail = Promise.resolve();
49
72
  const exclusively = (change) => {
50
73
  const run = tail.then(change, change);
51
74
  tail = run.catch(() => { });
52
75
  return run;
53
76
  };
77
+ // ─── What sync compares, in memory ─────────────────────────────────
78
+ /** Versions kept, by collection, then by id. Null until first needed. */
79
+ let kept = null;
80
+ /** Sorted sets, built when a reconciliation asks for one and dropped when their collection changes */
81
+ const sets = new Map();
82
+ /** The sum of each collection's ids, kept up to date on every change: a hello needs no sorting */
83
+ let totals = null;
84
+ /** Read in turn with changes, so none lands between the listing and the sets being kept up to date */
85
+ const loadKept = async () => kept ??
86
+ exclusively(async () => {
87
+ if (kept)
88
+ return kept;
89
+ const loaded = new Map();
90
+ for (const key of await adapter.list(ITEM_PREFIX)) {
91
+ const parsed = parseItemKey(key);
92
+ if (!parsed)
93
+ continue;
94
+ (loaded.get(parsed.collection) ?? loaded.set(parsed.collection, new Map()).get(parsed.collection)).set(parsed.id, parsed.item);
95
+ }
96
+ kept = loaded;
97
+ totals = new Map();
98
+ for (const [collection, byId] of loaded) {
99
+ let sum = EMPTY_SUM;
100
+ for (const item of byId.values())
101
+ sum = addToSum(sum, item.id);
102
+ totals.set(collection, sum);
103
+ }
104
+ return loaded;
105
+ });
106
+ /** Keeps the in-memory sets in step with a change just written */
107
+ const track = (added, removed) => {
108
+ for (const e of [...added, ...removed])
109
+ sets.delete(e.collection);
110
+ if (!kept || !totals)
111
+ return;
112
+ for (const e of removed) {
113
+ const item = kept.get(e.collection)?.get(e.id);
114
+ if (!item)
115
+ continue;
116
+ kept.get(e.collection).delete(e.id);
117
+ totals.set(e.collection, removeFromSum(totals.get(e.collection) ?? EMPTY_SUM, item.id));
118
+ }
119
+ for (const e of added) {
120
+ const digest = cidDigest(e.id);
121
+ const byId = kept.get(e.collection) ?? kept.set(e.collection, new Map()).get(e.collection);
122
+ if (!digest || byId.has(e.id))
123
+ continue;
124
+ byId.set(e.id, { timestamp: syncTime(e), id: digest });
125
+ totals.set(e.collection, addToSum(totals.get(e.collection) ?? EMPTY_SUM, digest));
126
+ }
127
+ };
128
+ // ─── Changes ───────────────────────────────────────────────────────
54
129
  /**
55
- * Runs one change of the tree. Its node writes are held and made in one
56
- * batch with the new root, so a change lands whole or not at all, in one
57
- * transaction rather than one per node. Versions it drops are deleted only
58
- * after, so the tree never names a record that is gone.
130
+ * Runs one change. Its entry writes are held and made in one batch, so a
131
+ * change lands whole or not at all. Versions it drops are deleted only
132
+ * after, so no entry ever names a record that is gone.
59
133
  */
60
134
  const change = (run) => exclusively(async () => {
61
135
  const writes = new Map();
62
- const tree = {
63
- ...adapter,
64
- get: async (key) => (writes.has(key) ? writes.get(key) : adapter.get(key)),
65
- has: async (key) => (writes.has(key) ? writes.get(key) !== null : adapter.has(key)),
66
- put: async (key, value) => void writes.set(key, value),
67
- delete: async (key) => void writes.set(key, null),
136
+ const added = new Map();
137
+ const dropped = new Map();
138
+ const entries = {
139
+ get: async (key) => {
140
+ const bytes = writes.has(key) ? writes.get(key) : await adapter.get(key);
141
+ return bytes ? utf8Decode(bytes) : null;
142
+ },
143
+ set: (key, id) => void writes.set(key, utf8Encode(id)),
144
+ unset: (key) => void writes.set(key, null),
145
+ keep: async (version) => {
146
+ await adapter.putExpression(version);
147
+ writes.set(itemKey(version), utf8Encode(version.id));
148
+ added.set(version.id, version);
149
+ dropped.delete(version.id);
150
+ },
151
+ drop: (version) => {
152
+ writes.set(itemKey(version), null);
153
+ dropped.set(version.id, version);
154
+ added.delete(version.id);
155
+ },
68
156
  };
69
- const dropped = [];
70
- const before = await getRootCid();
71
- const root = await run(tree, before, (id) => dropped.push(id));
72
- if (root === before && writes.size === 0 && dropped.length === 0)
73
- return root;
74
- writes.set(ROOT_KEY, root ? utf8Encode(root) : null);
157
+ await run(entries);
158
+ if (writes.size === 0 && dropped.size === 0)
159
+ return;
75
160
  await adapter.batch([...writes].map(([key, value]) => (value ? { type: 'put', key, value } : { type: 'delete', key })));
76
- for (const id of dropped)
161
+ for (const id of dropped.keys())
77
162
  await adapter.deleteExpression(id);
78
- changed();
79
- return root;
80
- });
81
- const graceMs = options.graceMs ?? 60_000;
82
- const compact = () => exclusively(async () => {
83
- const reachable = await collectReachableCids(adapter, await getRootCid());
84
- const dead = (await adapter.list('b')).filter((key) => NODE_KEY.test(key) && !reachable.has(key));
85
- const saved = await adapter.get(CONDEMNED_KEY);
86
- const earlier = saved ? JSON.parse(utf8Decode(saved)) : null;
87
- const ripe = graceMs === 0 ? dead : earlier && Date.now() - earlier.at >= graceMs ? earlier.cids : null;
88
- const condemn = (cids) => ({ type: 'put', key: CONDEMNED_KEY, value: utf8Encode(JSON.stringify({ at: Date.now(), cids })) });
89
- // Too soon to delete anything: remember what is dead now, unless an earlier list is already waiting.
90
- if (ripe === null) {
91
- if (!earlier)
92
- await adapter.batch([condemn(dead)]);
93
- return 0;
94
- }
95
- const doomed = new Set(ripe);
96
- const deleting = dead.filter((key) => doomed.has(key));
97
- await adapter.batch([...deleting.map((key) => ({ type: 'delete', key })), condemn(dead.filter((key) => !doomed.has(key)))]);
98
- return deleting.length;
163
+ track([...added.values()], [...dropped.values()]);
99
164
  });
100
- let changes = 0;
101
- /** Counts a change, and compacts once enough have piled up */
102
- const changed = () => {
103
- if (options.compactEvery && ++changes >= options.compactEvery) {
104
- changes = 0;
105
- void compact().catch(() => { });
106
- }
107
- };
108
165
  /**
109
166
  * Places a version that is not current: the record's first version if it is
110
167
  * the lowest-id one seen, history if retained, otherwise gone.
111
168
  */
112
- async function demote(tree, root, version, current, drop) {
169
+ async function demote(entries, version, current) {
113
170
  // A first version is kept as proof of who created the record — the one
114
171
  // with the lowest id, if several devices each created the same chosen key.
115
172
  // Not while the record is still at seq 0: then the current version is it.
116
173
  if (version.seq === 0 && current.seq > 0) {
117
- const heldId = await lookupInMST(tree, root, genesisKey(version.key));
174
+ const heldId = await entries.get(genesisKey(version.key));
118
175
  if (heldId === version.id)
119
- return root;
176
+ return;
120
177
  if (heldId === null || version.id < heldId) {
121
- await adapter.putExpression(version);
122
- root = await insertIntoMST(tree, root, genesisKey(version.key), version.id);
178
+ await entries.keep(version);
179
+ entries.set(genesisKey(version.key), version.id);
123
180
  const displaced = heldId ? await adapter.getExpression(heldId) : null;
124
- return displaced ? keepOrDrop(tree, root, displaced, drop) : root;
181
+ if (displaced)
182
+ await keepOrDrop(entries, displaced);
183
+ return;
125
184
  }
126
185
  }
127
- return keepOrDrop(tree, root, version, drop);
186
+ await keepOrDrop(entries, version);
128
187
  }
129
- async function keepOrDrop(tree, root, version, drop) {
188
+ async function keepOrDrop(entries, version) {
130
189
  if (version.retain) {
131
- await adapter.putExpression(version);
132
- return insertIntoMST(tree, root, historyKey(version), version.id);
190
+ await entries.keep(version);
191
+ entries.set(historyKey(version), version.id);
192
+ return;
133
193
  }
134
- drop(version.id);
135
- return root;
194
+ entries.drop(version);
136
195
  }
137
196
  const load = async (ids) => (await Promise.all([...ids].map((id) => adapter.getExpression(id)))).filter((v) => v !== null);
197
+ const readId = async (key) => {
198
+ const bytes = await adapter.get(key);
199
+ return bytes ? utf8Decode(bytes) : null;
200
+ };
138
201
  async function histories(prefix) {
139
- const root = await getRootCid();
140
202
  const ids = new Map();
141
- for (const tree of [CURRENT_PREFIX, GENESIS_PREFIX, HISTORY_PREFIX]) {
142
- for (const entry of await listMSTEntries(adapter, root, `${tree}${prefix}`)) {
143
- // `r/<key>`, `g/<key>`, `h/<key>/<seq>/<id>` — a record key holds no `/`.
144
- const key = entry.key.split('/')[1];
145
- (ids.get(key) ?? ids.set(key, new Set()).get(key)).add(entry.value);
203
+ const add = (key, id) => (ids.get(key) ?? ids.set(key, new Set()).get(key)).add(id);
204
+ for (const entry of [CURRENT_PREFIX, GENESIS_PREFIX]) {
205
+ for (const name of await adapter.list(`${entry}${prefix}`)) {
206
+ const id = await readId(name);
207
+ if (id)
208
+ add(name.slice(entry.length), id);
146
209
  }
147
210
  }
211
+ // `h/<key>/<seq>/<id>` — a record key holds no `/`, and the id is in the entry's name.
212
+ for (const name of await adapter.list(`${HISTORY_PREFIX}${prefix}`)) {
213
+ const [, key, , id] = name.split('/');
214
+ if (key && id)
215
+ add(key, id);
216
+ }
148
217
  const result = new Map();
149
218
  for (const [key, held] of ids)
150
219
  result.set(key, (await load(held)).sort(byVersion));
151
220
  return result;
152
221
  }
153
- async function currentOf(tree, root, key) {
154
- const id = await lookupInMST(tree, root, currentKey(key));
155
- return id ? adapter.getExpression(id) : null;
222
+ async function items(collection) {
223
+ const cached = sets.get(collection);
224
+ if (cached)
225
+ return cached;
226
+ const set = new ItemSet((await loadKept()).get(collection)?.values() ?? []);
227
+ sets.set(collection, set);
228
+ return set;
229
+ }
230
+ async function sums() {
231
+ await loadKept();
232
+ return new Map([...(totals ?? [])].filter(([, sum]) => sum.count > 0));
156
233
  }
157
234
  return Object.freeze({
158
235
  addExpression(incoming) {
159
- return change(async (tree, root, drop) => {
160
- const current = await currentOf(tree, root, incoming.key);
161
- if (current?.id === incoming.id)
162
- return root;
236
+ return change(async (entries) => {
237
+ const currentId = await entries.get(currentKey(incoming.key));
238
+ if (currentId === incoming.id)
239
+ return;
240
+ const current = currentId ? await adapter.getExpression(currentId) : null;
163
241
  if (current && !supersedes(incoming, current))
164
- return demote(tree, root, incoming, current, drop);
165
- await adapter.putExpression(incoming);
166
- root = await insertIntoMST(tree, root, currentKey(incoming.key), incoming.id);
167
- return current ? demote(tree, root, current, incoming, drop) : root;
242
+ return demote(entries, incoming, current);
243
+ await entries.keep(incoming);
244
+ entries.set(currentKey(incoming.key), incoming.id);
245
+ if (current)
246
+ await demote(entries, current, incoming);
168
247
  });
169
248
  },
170
249
  removeExpression(id) {
171
- return change(async (tree, root, drop) => {
250
+ return change(async (entries) => {
172
251
  const version = await adapter.getExpression(id);
173
- for (const key of version ? [currentKey(version.key), genesisKey(version.key), historyKey(version)] : []) {
174
- if ((await lookupInMST(tree, root, key)) === id)
175
- root = await deleteFromMST(tree, root, key);
252
+ if (!version)
253
+ return;
254
+ for (const key of [currentKey(version.key), genesisKey(version.key)]) {
255
+ if ((await entries.get(key)) === id)
256
+ entries.unset(key);
176
257
  }
177
- drop(id);
178
- return root;
258
+ entries.unset(historyKey(version));
259
+ entries.drop(version);
179
260
  });
180
261
  },
181
262
  async getExpression(id) {
182
263
  return adapter.getExpression(id);
183
264
  },
184
265
  async getCurrent(key) {
185
- return currentOf(adapter, await getRootCid(), key);
266
+ const id = await readId(currentKey(key));
267
+ return id ? adapter.getExpression(id) : null;
186
268
  },
187
269
  async getGenesis(key) {
188
- const id = await lookupInMST(adapter, await getRootCid(), genesisKey(key));
270
+ const id = await readId(genesisKey(key));
189
271
  return id ? adapter.getExpression(id) : null;
190
272
  },
191
273
  async history(key) {
@@ -193,28 +275,37 @@ export function createStorageProvider(adapter, options = {}) {
193
275
  },
194
276
  histories,
195
277
  async listCurrent() {
196
- return load((await listMSTEntries(adapter, await getRootCid(), CURRENT_PREFIX)).map((e) => e.value));
278
+ const ids = await Promise.all((await adapter.list(CURRENT_PREFIX)).map(readId));
279
+ return load(ids.filter((id) => id !== null));
197
280
  },
198
281
  async queryExpressions(collection) {
199
- // The index holds every version kept, not only current ones; the tree says which is current.
200
- const root = await getRootCid();
282
+ // The index holds every version kept, not only current ones; the entries say which is current.
201
283
  const held = await adapter.queryExpressions(collection, Infinity);
202
- const current = await Promise.all(held.map(async (v) => (await lookupInMST(adapter, root, currentKey(v.key))) === v.id));
284
+ const current = await Promise.all(held.map(async (v) => (await readId(currentKey(v.key))) === v.id));
203
285
  return held.filter((_, i) => current[i]);
204
286
  },
205
- async entries() {
206
- return listMSTEntries(adapter, await getRootCid());
287
+ async versionIds() {
288
+ return [...(await loadKept()).values()].flatMap((byId) => [...byId.keys()]);
207
289
  },
208
- async getRootCid() {
209
- return getRootCid();
290
+ items,
291
+ sums,
292
+ async fingerprint() {
293
+ let total = EMPTY_SUM;
294
+ for (const sum of (await sums()).values())
295
+ total = combineSums(total, sum);
296
+ return bytesToHex(await fingerprintOf(total));
297
+ },
298
+ invalidate() {
299
+ kept = null;
300
+ totals = null;
301
+ sets.clear();
210
302
  },
211
- compact,
212
303
  getAdapter() {
213
304
  return adapter;
214
305
  },
215
306
  async close() {
216
307
  await adapter.close();
217
- }
308
+ },
218
309
  });
219
310
  }
220
311
  //# sourceMappingURL=storage-provider.js.map