@automerge/automerge-repo 2.0.0-alpha.6 → 2.0.0-collectionsync-alpha.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 (164) hide show
  1. package/dist/CollectionHandle.d.ts +14 -0
  2. package/dist/CollectionHandle.d.ts.map +1 -0
  3. package/dist/CollectionHandle.js +37 -0
  4. package/dist/DocHandle.d.ts +67 -2
  5. package/dist/DocHandle.d.ts.map +1 -1
  6. package/dist/DocHandle.js +113 -2
  7. package/dist/DocUrl.d.ts +47 -0
  8. package/dist/DocUrl.d.ts.map +1 -0
  9. package/dist/DocUrl.js +72 -0
  10. package/dist/EphemeralData.d.ts +20 -0
  11. package/dist/EphemeralData.d.ts.map +1 -0
  12. package/dist/EphemeralData.js +1 -0
  13. package/dist/Repo.d.ts +28 -7
  14. package/dist/Repo.d.ts.map +1 -1
  15. package/dist/Repo.js +142 -143
  16. package/dist/ferigan.d.ts +51 -0
  17. package/dist/ferigan.d.ts.map +1 -0
  18. package/dist/ferigan.js +98 -0
  19. package/dist/helpers/tests/storage-adapter-tests.d.ts +2 -2
  20. package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -1
  21. package/dist/helpers/tests/storage-adapter-tests.js +19 -39
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/network/NetworkSubsystem.d.ts +1 -0
  26. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  27. package/dist/network/NetworkSubsystem.js +3 -0
  28. package/dist/network/messages.d.ts +7 -1
  29. package/dist/network/messages.d.ts.map +1 -1
  30. package/dist/network/messages.js +2 -1
  31. package/dist/src/DocHandle.d.ts +182 -0
  32. package/dist/src/DocHandle.d.ts.map +1 -0
  33. package/dist/src/DocHandle.js +405 -0
  34. package/dist/src/DocUrl.d.ts +49 -0
  35. package/dist/src/DocUrl.d.ts.map +1 -0
  36. package/dist/src/DocUrl.js +72 -0
  37. package/dist/src/EphemeralData.d.ts +19 -0
  38. package/dist/src/EphemeralData.d.ts.map +1 -0
  39. package/dist/src/EphemeralData.js +1 -0
  40. package/dist/src/Repo.d.ts +74 -0
  41. package/dist/src/Repo.d.ts.map +1 -0
  42. package/dist/src/Repo.js +208 -0
  43. package/dist/src/helpers/arraysAreEqual.d.ts +2 -0
  44. package/dist/src/helpers/arraysAreEqual.d.ts.map +1 -0
  45. package/dist/src/helpers/arraysAreEqual.js +2 -0
  46. package/dist/src/helpers/cbor.d.ts +4 -0
  47. package/dist/src/helpers/cbor.d.ts.map +1 -0
  48. package/dist/src/helpers/cbor.js +8 -0
  49. package/dist/src/helpers/eventPromise.d.ts +11 -0
  50. package/dist/src/helpers/eventPromise.d.ts.map +1 -0
  51. package/dist/src/helpers/eventPromise.js +7 -0
  52. package/dist/src/helpers/headsAreSame.d.ts +2 -0
  53. package/dist/src/helpers/headsAreSame.d.ts.map +1 -0
  54. package/dist/src/helpers/headsAreSame.js +4 -0
  55. package/dist/src/helpers/mergeArrays.d.ts +2 -0
  56. package/dist/src/helpers/mergeArrays.d.ts.map +1 -0
  57. package/dist/src/helpers/mergeArrays.js +15 -0
  58. package/dist/src/helpers/pause.d.ts +6 -0
  59. package/dist/src/helpers/pause.d.ts.map +1 -0
  60. package/dist/src/helpers/pause.js +10 -0
  61. package/dist/src/helpers/tests/network-adapter-tests.d.ts +21 -0
  62. package/dist/src/helpers/tests/network-adapter-tests.d.ts.map +1 -0
  63. package/dist/src/helpers/tests/network-adapter-tests.js +122 -0
  64. package/dist/src/helpers/withTimeout.d.ts +12 -0
  65. package/dist/src/helpers/withTimeout.d.ts.map +1 -0
  66. package/dist/src/helpers/withTimeout.js +24 -0
  67. package/dist/src/index.d.ts +53 -0
  68. package/dist/src/index.d.ts.map +1 -0
  69. package/dist/src/index.js +40 -0
  70. package/dist/src/network/NetworkAdapter.d.ts +26 -0
  71. package/dist/src/network/NetworkAdapter.d.ts.map +1 -0
  72. package/dist/src/network/NetworkAdapter.js +4 -0
  73. package/dist/src/network/NetworkSubsystem.d.ts +23 -0
  74. package/dist/src/network/NetworkSubsystem.d.ts.map +1 -0
  75. package/dist/src/network/NetworkSubsystem.js +120 -0
  76. package/dist/src/network/messages.d.ts +85 -0
  77. package/dist/src/network/messages.d.ts.map +1 -0
  78. package/dist/src/network/messages.js +23 -0
  79. package/dist/src/storage/StorageAdapter.d.ts +14 -0
  80. package/dist/src/storage/StorageAdapter.d.ts.map +1 -0
  81. package/dist/src/storage/StorageAdapter.js +1 -0
  82. package/dist/src/storage/StorageSubsystem.d.ts +12 -0
  83. package/dist/src/storage/StorageSubsystem.d.ts.map +1 -0
  84. package/dist/src/storage/StorageSubsystem.js +145 -0
  85. package/dist/src/synchronizer/CollectionSynchronizer.d.ts +25 -0
  86. package/dist/src/synchronizer/CollectionSynchronizer.d.ts.map +1 -0
  87. package/dist/src/synchronizer/CollectionSynchronizer.js +106 -0
  88. package/dist/src/synchronizer/DocSynchronizer.d.ts +29 -0
  89. package/dist/src/synchronizer/DocSynchronizer.d.ts.map +1 -0
  90. package/dist/src/synchronizer/DocSynchronizer.js +263 -0
  91. package/dist/src/synchronizer/Synchronizer.d.ts +9 -0
  92. package/dist/src/synchronizer/Synchronizer.d.ts.map +1 -0
  93. package/dist/src/synchronizer/Synchronizer.js +2 -0
  94. package/dist/src/types.d.ts +16 -0
  95. package/dist/src/types.d.ts.map +1 -0
  96. package/dist/src/types.js +1 -0
  97. package/dist/storage/StorageAdapter.d.ts +9 -0
  98. package/dist/storage/StorageAdapter.d.ts.map +1 -1
  99. package/dist/storage/StorageAdapter.js +33 -0
  100. package/dist/storage/StorageSubsystem.d.ts +12 -2
  101. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  102. package/dist/storage/StorageSubsystem.js +42 -100
  103. package/dist/synchronizer/CollectionSynchronizer.d.ts +4 -2
  104. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  105. package/dist/synchronizer/CollectionSynchronizer.js +28 -15
  106. package/dist/synchronizer/DocSynchronizer.d.ts +6 -5
  107. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  108. package/dist/synchronizer/DocSynchronizer.js +76 -178
  109. package/dist/synchronizer/Synchronizer.d.ts +11 -0
  110. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  111. package/dist/test/CollectionSynchronizer.test.d.ts +2 -0
  112. package/dist/test/CollectionSynchronizer.test.d.ts.map +1 -0
  113. package/dist/test/CollectionSynchronizer.test.js +57 -0
  114. package/dist/test/DocHandle.test.d.ts +2 -0
  115. package/dist/test/DocHandle.test.d.ts.map +1 -0
  116. package/dist/test/DocHandle.test.js +238 -0
  117. package/dist/test/DocSynchronizer.test.d.ts +2 -0
  118. package/dist/test/DocSynchronizer.test.d.ts.map +1 -0
  119. package/dist/test/DocSynchronizer.test.js +111 -0
  120. package/dist/test/Network.test.d.ts +2 -0
  121. package/dist/test/Network.test.d.ts.map +1 -0
  122. package/dist/test/Network.test.js +11 -0
  123. package/dist/test/Repo.test.d.ts +2 -0
  124. package/dist/test/Repo.test.d.ts.map +1 -0
  125. package/dist/test/Repo.test.js +568 -0
  126. package/dist/test/StorageSubsystem.test.d.ts +2 -0
  127. package/dist/test/StorageSubsystem.test.d.ts.map +1 -0
  128. package/dist/test/StorageSubsystem.test.js +56 -0
  129. package/dist/test/helpers/DummyNetworkAdapter.d.ts +9 -0
  130. package/dist/test/helpers/DummyNetworkAdapter.d.ts.map +1 -0
  131. package/dist/test/helpers/DummyNetworkAdapter.js +15 -0
  132. package/dist/test/helpers/DummyStorageAdapter.d.ts +16 -0
  133. package/dist/test/helpers/DummyStorageAdapter.d.ts.map +1 -0
  134. package/dist/test/helpers/DummyStorageAdapter.js +33 -0
  135. package/dist/test/helpers/generate-large-object.d.ts +5 -0
  136. package/dist/test/helpers/generate-large-object.d.ts.map +1 -0
  137. package/dist/test/helpers/generate-large-object.js +9 -0
  138. package/dist/test/helpers/getRandomItem.d.ts +2 -0
  139. package/dist/test/helpers/getRandomItem.d.ts.map +1 -0
  140. package/dist/test/helpers/getRandomItem.js +4 -0
  141. package/dist/test/types.d.ts +4 -0
  142. package/dist/test/types.d.ts.map +1 -0
  143. package/dist/test/types.js +1 -0
  144. package/package.json +3 -3
  145. package/src/CollectionHandle.ts +54 -0
  146. package/src/DocHandle.ts +133 -4
  147. package/src/Repo.ts +192 -183
  148. package/src/ferigan.ts +184 -0
  149. package/src/helpers/tests/storage-adapter-tests.ts +31 -62
  150. package/src/index.ts +2 -0
  151. package/src/network/NetworkSubsystem.ts +4 -0
  152. package/src/network/messages.ts +11 -2
  153. package/src/storage/StorageAdapter.ts +42 -0
  154. package/src/storage/StorageSubsystem.ts +59 -119
  155. package/src/synchronizer/CollectionSynchronizer.ts +34 -26
  156. package/src/synchronizer/DocSynchronizer.ts +84 -231
  157. package/src/synchronizer/Synchronizer.ts +14 -0
  158. package/test/CollectionSynchronizer.test.ts +4 -2
  159. package/test/DocHandle.test.ts +141 -0
  160. package/test/DocSynchronizer.test.ts +6 -1
  161. package/test/RemoteHeadsSubscriptions.test.ts +1 -1
  162. package/test/Repo.test.ts +225 -117
  163. package/test/StorageSubsystem.test.ts +20 -16
  164. package/test/remoteHeads.test.ts +1 -1
package/src/ferigan.ts ADDED
@@ -0,0 +1,184 @@
1
+ import { EventEmitter } from "eventemitter3"
2
+ import { MessageContents, RepoMessage } from "./network/messages.js"
3
+ import { AutomergeUrl } from "./types.js"
4
+ import { Repo } from "./Repo.js"
5
+
6
+ const URL_RE = /automerge:([\w\d+/=]+)(?:\?([\w\d+/=&]*))*/
7
+
8
+ export interface Ferigan extends EventEmitter<FeriganEvents> {
9
+ receiveMessage(message: RepoMessage): Promise<void>
10
+ load(
11
+ doc: ChangeLogId,
12
+ since: ChangeHash[]
13
+ ): AsyncIterableIterator<Progress<ChangeLog | undefined>>
14
+ loadCollection(
15
+ doc: ChangeLogId
16
+ ): AsyncIterableIterator<Progress<Index | undefined>>
17
+ append(
18
+ doc: ChangeLogId,
19
+ parents: ChangeHash[],
20
+ changes: Uint8Array
21
+ ): Promise<void>
22
+ replace(
23
+ doc: ChangeLogId,
24
+ start: ChangeHash,
25
+ end: ChangeHash,
26
+ changes: Uint8Array
27
+ ): Promise<void>
28
+ }
29
+
30
+ export function makeFerigan(repo: Repo): Ferigan {
31
+ function fakeProgress<T>(): AsyncIterableIterator<Progress<T>> {
32
+ return (async function*() {
33
+ yield { type: "synchronizing_index" }
34
+ })()
35
+ }
36
+ class FakeFerigan extends EventEmitter<FeriganEvents> {
37
+ #repo: Repo
38
+
39
+ constructor(repo: Repo) {
40
+ super()
41
+ this.#repo = repo
42
+ }
43
+
44
+ async receiveMessage(message: RepoMessage): Promise<void> { }
45
+ load(
46
+ doc: ChangeLogId
47
+ ): AsyncIterableIterator<Progress<ChangeLog | undefined>> {
48
+ return fakeProgress()
49
+ }
50
+ async *loadCollection(
51
+ doc: ChangeLogId
52
+ ): AsyncIterableIterator<Progress<Index | undefined>> {
53
+ yield { type: "synchronizing_index" }
54
+
55
+ const index: Index = { rootUrl: doc as AutomergeUrl, entries: [] }
56
+
57
+ function findLinks(
58
+ obj: unknown
59
+ ): { url: string }[] {
60
+ const links: { url: string; }[] = []
61
+
62
+ const traverse = (value: unknown): void => {
63
+ if (typeof value === "string") {
64
+ const url = parseUrl(value)
65
+ if (url != null) {
66
+ links.push(url)
67
+ }
68
+ } else if (Array.isArray(value)) {
69
+ value.forEach(traverse)
70
+ } else if (typeof value === "object" && value !== null) {
71
+ Object.values(value).forEach(traverse)
72
+ }
73
+ }
74
+
75
+ traverse(obj)
76
+ return links
77
+ }
78
+
79
+ const indexDoc = this.#repo.find(doc as AutomergeUrl)
80
+ const handlesToProcess = [indexDoc]
81
+
82
+ while (handlesToProcess.length > 0) {
83
+ const handle = handlesToProcess.pop()
84
+ if (!handle) {
85
+ continue
86
+ }
87
+
88
+ const doc = await handle.doc()
89
+ if (!doc) {
90
+ continue
91
+ }
92
+
93
+ handle.on("change", change => {
94
+ for (const patch of change.patches) {
95
+ if (patch.action === "splice") {
96
+ const possibleUrl = parseUrl(patch.value)
97
+ if (possibleUrl != null) {
98
+ this.emit("indexChanged", {
99
+ indexUrl: indexDoc.url,
100
+ change: {
101
+ type: "add",
102
+ url: possibleUrl.url,
103
+ }
104
+ })
105
+ }
106
+ }
107
+ }
108
+ })
109
+
110
+ const links = findLinks(doc)
111
+ for (const { url: urlStr } of links) {
112
+ const url = urlStr as AutomergeUrl
113
+ if (index.entries.some(entry => entry === url)) {
114
+ continue
115
+ }
116
+ index.entries.push(url)
117
+
118
+ const childHandle = this.#repo.find(url as AutomergeUrl)
119
+ handlesToProcess.push(childHandle)
120
+ }
121
+ }
122
+ yield { type: "done", value: index }
123
+ }
124
+ append(
125
+ doc: ChangeLogId,
126
+ parents: ChangeHash[],
127
+ changes: Uint8Array
128
+ ): Promise<void> {
129
+ return Promise.resolve()
130
+ }
131
+ replace(
132
+ doc: ChangeLogId,
133
+ start: ChangeHash,
134
+ end: ChangeHash,
135
+ changes: Uint8Array
136
+ ): Promise<void> {
137
+ return Promise.resolve()
138
+ }
139
+ }
140
+ return new FakeFerigan(repo)
141
+ }
142
+
143
+ interface FeriganEvents {
144
+ message: (event: {message: MessageContents}) => void
145
+ changed: (event: {changedLog: ChangeLogId}) => void
146
+ indexChanged: (event: {indexUrl: ChangeLogId, change: IndexChange}) => void
147
+ }
148
+
149
+ type IndexChange = {
150
+ type: "add"
151
+ url: AutomergeUrl
152
+ }
153
+
154
+ type ChangeLogId = string
155
+
156
+ type ChangeHash = string
157
+
158
+ type ChangeLog = { start: ChangeHash; end: ChangeHash; changes: Uint8Array }
159
+
160
+ export type Progress<T> =
161
+ | { type: "synchronizing_index" }
162
+ | {
163
+ type: "synchronizing_docs"
164
+ progress: number
165
+ total: number
166
+ }
167
+ | { type: "done"; value: T }
168
+
169
+ export type Index = {
170
+ rootUrl: AutomergeUrl,
171
+ entries: AutomergeUrl[],
172
+ }
173
+
174
+ function parseUrl(
175
+ urlStr: string
176
+ ): { url: AutomergeUrl } | undefined {
177
+ const match = urlStr.match(URL_RE)
178
+ if (!match) {
179
+ return undefined
180
+ }
181
+ const url = new URL(match[0])
182
+ const normalisedUrl = `automerge:${url.pathname}`
183
+ return { url: normalisedUrl as AutomergeUrl }
184
+ }
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest"
1
+ import { describe, expect, beforeEach, it as _it } from "vitest"
2
2
 
3
3
  import type { StorageAdapterInterface } from "../../storage/StorageAdapterInterface.js"
4
4
 
@@ -8,72 +8,61 @@ const PAYLOAD_C = () => new Uint8Array([2, 111, 74, 131, 236, 96, 142, 193])
8
8
 
9
9
  const LARGE_PAYLOAD = new Uint8Array(100000).map(() => Math.random() * 256)
10
10
 
11
- export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
12
- const setup = async () => {
13
- const { adapter, teardown = NO_OP } = await _setup()
14
- return { adapter, teardown }
15
- }
11
+ type AdapterTestContext = {
12
+ adapter: StorageAdapterInterface
13
+ }
14
+
15
+ const it = _it<AdapterTestContext>
16
+
17
+ export function runStorageAdapterTests(setup: SetupFn, title?: string): void {
18
+ beforeEach<AdapterTestContext>(async ctx => {
19
+ const { adapter, teardown = NO_OP } = await setup()
20
+ ctx.adapter = adapter
21
+ return teardown
22
+ })
16
23
 
17
24
  describe(`Storage adapter acceptance tests ${
18
25
  title ? `(${title})` : ""
19
26
  }`, () => {
20
27
  describe("load", () => {
21
- it("should return undefined if there is no data", async () => {
22
- const { adapter, teardown } = await setup()
23
-
28
+ it("should return undefined if there is no data", async ({ adapter }) => {
24
29
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
25
30
  expect(actual).toBeUndefined()
26
-
27
- teardown()
28
31
  })
29
32
  })
30
33
 
31
34
  describe("save and load", () => {
32
- it("should return data that was saved", async () => {
33
- const { adapter, teardown } = await setup()
34
-
35
+ it("should return data that was saved", async ({ adapter }) => {
35
36
  await adapter.save(["storage-adapter-id"], PAYLOAD_A())
36
37
  const actual = await adapter.load(["storage-adapter-id"])
37
38
  expect(actual).toStrictEqual(PAYLOAD_A())
38
-
39
- teardown()
40
39
  })
41
40
 
42
- it("should work with composite keys", async () => {
43
- const { adapter, teardown } = await setup()
44
-
41
+ it("should work with composite keys", async ({ adapter }) => {
45
42
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
46
43
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
47
44
  expect(actual).toStrictEqual(PAYLOAD_A())
48
-
49
- teardown()
50
45
  })
51
46
 
52
- it("should work with a large payload", async () => {
53
- const { adapter, teardown } = await setup()
54
-
47
+ it("should work with a large payload", async ({ adapter }) => {
55
48
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], LARGE_PAYLOAD)
56
49
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
57
50
  expect(actual).toStrictEqual(LARGE_PAYLOAD)
58
-
59
- teardown()
60
51
  })
61
52
  })
62
53
 
63
54
  describe("loadRange", () => {
64
- it("should return an empty array if there is no data", async () => {
65
- const { adapter, teardown } = await setup()
66
-
55
+ it("should return an empty array if there is no data", async ({
56
+ adapter,
57
+ }) => {
67
58
  expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([])
68
-
69
- teardown()
70
59
  })
71
60
  })
72
61
 
73
62
  describe("save and loadRange", () => {
74
- it("should return all the data that matches the key", async () => {
75
- const { adapter, teardown } = await setup()
76
-
63
+ it("should return all the data that matches the key", async ({
64
+ adapter,
65
+ }) => {
77
66
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
78
67
  await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B())
79
68
  await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C())
@@ -92,13 +81,9 @@ export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
92
81
  { key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
93
82
  ])
94
83
  )
95
-
96
- teardown()
97
84
  })
98
85
 
99
- it("should only load values that match they key", async () => {
100
- const { adapter, teardown } = await setup()
101
-
86
+ it("should only load values that match they key", async ({ adapter }) => {
102
87
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
103
88
  await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_C())
104
89
 
@@ -113,15 +98,11 @@ export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
113
98
  { key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_C() },
114
99
  ])
115
100
  )
116
-
117
- teardown()
118
101
  })
119
102
  })
120
103
 
121
104
  describe("save and remove", () => {
122
- it("after removing, should be empty", async () => {
123
- const { adapter, teardown } = await setup()
124
-
105
+ it("after removing, should be empty", async ({ adapter }) => {
125
106
  await adapter.save(["AAAAA", "snapshot", "xxxxx"], PAYLOAD_A())
126
107
  await adapter.remove(["AAAAA", "snapshot", "xxxxx"])
127
108
 
@@ -129,30 +110,24 @@ export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
129
110
  expect(
130
111
  await adapter.load(["AAAAA", "snapshot", "xxxxx"])
131
112
  ).toBeUndefined()
132
-
133
- teardown()
134
113
  })
135
114
  })
136
115
 
137
116
  describe("save and save", () => {
138
- it("should overwrite data saved with the same key", async () => {
139
- const { adapter, teardown } = await setup()
140
-
117
+ it("should overwrite data saved with the same key", async ({
118
+ adapter,
119
+ }) => {
141
120
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
142
121
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_B())
143
122
 
144
123
  expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual([
145
124
  { key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_B() },
146
125
  ])
147
-
148
- teardown()
149
126
  })
150
127
  })
151
128
 
152
129
  describe("removeRange", () => {
153
- it("should remove a range of records", async () => {
154
- const { adapter, teardown } = await setup()
155
-
130
+ it("should remove a range of records", async ({ adapter }) => {
156
131
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
157
132
  await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B())
158
133
  await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C())
@@ -162,13 +137,9 @@ export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
162
137
  expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([
163
138
  { key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
164
139
  ])
165
-
166
- teardown()
167
140
  })
168
141
 
169
- it("should not remove records that don't match", async () => {
170
- const { adapter, teardown } = await setup()
171
-
142
+ it("should not remove records that don't match", async ({ adapter }) => {
172
143
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
173
144
  await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_B())
174
145
 
@@ -178,8 +149,6 @@ export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
178
149
  expect(actual).toStrictEqual([
179
150
  { key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_B() },
180
151
  ])
181
-
182
- teardown()
183
152
  })
184
153
  })
185
154
  })
@@ -189,5 +158,5 @@ const NO_OP = () => {}
189
158
 
190
159
  export type SetupFn = () => Promise<{
191
160
  adapter: StorageAdapterInterface
192
- teardown?: () => void
161
+ teardown?: () => void | Promise<void>
193
162
  }>
package/src/index.ts CHANGED
@@ -41,6 +41,8 @@ export type { NetworkAdapterInterface } from "./network/NetworkAdapterInterface.
41
41
  export { isRepoMessage } from "./network/messages.js"
42
42
  export { StorageAdapter } from "./storage/StorageAdapter.js"
43
43
  export type { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js"
44
+ export type { Index, Progress } from "./ferigan.js"
45
+ export { CollectionHandle } from "./CollectionHandle.js"
44
46
 
45
47
  /** @hidden **/
46
48
  export * as cbor from "./helpers/cbor.js"
@@ -38,6 +38,10 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
38
38
  adapters.forEach(a => this.addNetworkAdapter(a))
39
39
  }
40
40
 
41
+ get peers(): PeerId[] {
42
+ return Array.from(Object.keys(this.#adaptersByPeer)) as PeerId[]
43
+ }
44
+
41
45
  disconnect() {
42
46
  this.adapters.forEach(a => a.disconnect())
43
47
  }
@@ -130,6 +130,14 @@ export type RepoMessage =
130
130
  | DocumentUnavailableMessage
131
131
  | RemoteSubscriptionControlMessage
132
132
  | RemoteHeadsChanged
133
+ | BeelayMessage
134
+
135
+ export type BeelayMessage = {
136
+ type: "beelay"
137
+ senderId: PeerId
138
+ targetId: PeerId
139
+ message: Uint8Array
140
+ }
133
141
 
134
142
  /** These are message types that are handled by the {@link CollectionSynchronizer}.*/
135
143
  export type DocMessage =
@@ -167,10 +175,11 @@ export const isRepoMessage = (message: Message): message is RepoMessage =>
167
175
  isRequestMessage(message) ||
168
176
  isDocumentUnavailableMessage(message) ||
169
177
  isRemoteSubscriptionControlMessage(message) ||
170
- isRemoteHeadsChanged(message)
178
+ isRemoteHeadsChanged(message) ||
179
+ true
171
180
 
172
181
  // prettier-ignore
173
- export const isDocumentUnavailableMessage = (msg: Message): msg is DocumentUnavailableMessage =>
182
+ export const isDocumentUnavailableMessage = (msg: Message): msg is DocumentUnavailableMessage =>
174
183
  msg.type === "doc-unavailable"
175
184
 
176
185
  export const isRequestMessage = (msg: Message): msg is RequestMessage =>
@@ -34,3 +34,45 @@ export abstract class StorageAdapter implements StorageAdapterInterface {
34
34
  /** Remove all values with keys that start with `keyPrefix` */
35
35
  abstract removeRange(keyPrefix: StorageKey): Promise<void>
36
36
  }
37
+
38
+ export class InMemoryStorageAdapter extends StorageAdapter {
39
+ #data: Record<string, Uint8Array> = {}
40
+
41
+ #keyToString(key: string[]): string {
42
+ return key.join(".")
43
+ }
44
+
45
+ #stringToKey(key: string): string[] {
46
+ return key.split(".")
47
+ }
48
+
49
+ async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
50
+ const range = Object.entries(this.#data)
51
+ .filter(([key, _]) => key.startsWith(this.#keyToString(keyPrefix)))
52
+ .map(([key, data]) => ({ key: this.#stringToKey(key), data }))
53
+ return Promise.resolve(range)
54
+ }
55
+
56
+ async removeRange(keyPrefix: string[]): Promise<void> {
57
+ Object.entries(this.#data)
58
+ .filter(([key, _]) => key.startsWith(this.#keyToString(keyPrefix)))
59
+ .forEach(([key, _]) => delete this.#data[key])
60
+ }
61
+
62
+ async load(key: string[]): Promise<Uint8Array | undefined> {
63
+ return new Promise(resolve => resolve(this.#data[this.#keyToString(key)]))
64
+ }
65
+
66
+ async save(key: string[], binary: Uint8Array) {
67
+ this.#data[this.#keyToString(key)] = binary
68
+ return Promise.resolve()
69
+ }
70
+
71
+ async remove(key: string[]) {
72
+ delete this.#data[this.#keyToString(key)]
73
+ }
74
+
75
+ keys() {
76
+ return Object.keys(this.#data)
77
+ }
78
+ }