@automerge/automerge-repo 1.0.13 → 1.0.15

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 (36) hide show
  1. package/dist/Repo.js +1 -1
  2. package/dist/helpers/cbor.js +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/network/NetworkAdapter.d.ts +3 -3
  6. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  7. package/dist/network/messages.d.ts +7 -18
  8. package/dist/network/messages.d.ts.map +1 -1
  9. package/dist/storage/StorageAdapter.d.ts +19 -22
  10. package/dist/storage/StorageAdapter.d.ts.map +1 -1
  11. package/dist/storage/StorageAdapter.js +2 -2
  12. package/dist/storage/StorageSubsystem.d.ts +39 -3
  13. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  14. package/dist/storage/StorageSubsystem.js +128 -75
  15. package/dist/storage/chunkTypeFromKey.d.ts +13 -0
  16. package/dist/storage/chunkTypeFromKey.d.ts.map +1 -0
  17. package/dist/storage/chunkTypeFromKey.js +18 -0
  18. package/dist/storage/keyHash.d.ts +4 -0
  19. package/dist/storage/keyHash.d.ts.map +1 -0
  20. package/dist/storage/keyHash.js +15 -0
  21. package/dist/storage/types.d.ts +37 -0
  22. package/dist/storage/types.d.ts.map +1 -0
  23. package/dist/storage/types.js +1 -0
  24. package/package.json +2 -2
  25. package/src/Repo.ts +1 -1
  26. package/src/helpers/cbor.ts +1 -1
  27. package/src/index.ts +11 -3
  28. package/src/network/NetworkAdapter.ts +3 -3
  29. package/src/network/messages.ts +8 -21
  30. package/src/storage/StorageAdapter.ts +23 -30
  31. package/src/storage/StorageSubsystem.ts +159 -93
  32. package/src/storage/chunkTypeFromKey.ts +22 -0
  33. package/src/storage/keyHash.ts +17 -0
  34. package/src/storage/types.ts +39 -0
  35. package/test/StorageSubsystem.test.ts +143 -35
  36. package/test/helpers/DummyStorageAdapter.ts +2 -4
@@ -8,6 +8,8 @@ import { describe, it } from "vitest"
8
8
  import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
9
9
  import { StorageSubsystem } from "../src/storage/StorageSubsystem.js"
10
10
  import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
11
+ import { cbor } from "../src/index.js"
12
+ import { pause } from "../src/helpers/pause.js"
11
13
 
12
14
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "automerge-repo-tests"))
13
15
 
@@ -19,55 +21,161 @@ describe("StorageSubsystem", () => {
19
21
 
20
22
  Object.entries(adaptersToTest).forEach(([adapterName, adapter]) => {
21
23
  describe(adapterName, () => {
22
- it("can store and retrieve an Automerge document", async () => {
23
- const storage = new StorageSubsystem(adapter)
24
+ describe("Automerge document storage", () => {
25
+ it("stores and retrieves an Automerge document", async () => {
26
+ const storage = new StorageSubsystem(adapter)
24
27
 
25
- const doc = A.change(A.init<any>(), "test", d => {
26
- d.foo = "bar"
28
+ const doc = A.change(A.init<any>(), "test", d => {
29
+ d.foo = "bar"
30
+ })
31
+
32
+ // save it to storage
33
+ const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
34
+ await storage.saveDoc(key, doc)
35
+
36
+ // reload it from storage
37
+ const reloadedDoc = await storage.loadDoc(key)
38
+
39
+ // check that it's the same doc
40
+ assert.deepStrictEqual(reloadedDoc, doc)
41
+ })
42
+
43
+ it("retrieves an Automerge document following lots of changes", async () => {
44
+ const storage = new StorageSubsystem(adapter)
45
+
46
+ type TestDoc = { foo: number }
47
+
48
+ const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
49
+
50
+ let doc = A.init<TestDoc>()
51
+
52
+ const N = 100
53
+ for (let i = 0; i < N; i++) {
54
+ doc = A.change(doc, "test", d => {
55
+ d.foo = i
56
+ })
57
+ // save it to storage
58
+ await storage.saveDoc(key, doc)
59
+ }
60
+
61
+ // reload it from storage, simulating a new process
62
+ const storage2 = new StorageSubsystem(adapter)
63
+ const reloadedDoc = await storage2.loadDoc<TestDoc>(key)
64
+
65
+ // check that the doc has the right value
66
+ assert.equal(reloadedDoc?.foo, N - 1)
27
67
  })
28
68
 
29
- // save it to storage
30
- const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
31
- await storage.saveDoc(key, doc)
69
+ it("stores incremental changes following a load", async () => {
70
+ const storage = new StorageSubsystem(adapter)
71
+
72
+ const doc = A.change(A.init<any>(), "test", d => {
73
+ d.foo = "bar"
74
+ })
75
+
76
+ // save it to storage
77
+ const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
78
+ storage.saveDoc(key, doc)
79
+
80
+ // reload it from storage, simulating a new process
81
+ const storage2 = new StorageSubsystem(adapter)
82
+ const reloadedDoc = await storage2.loadDoc(key)
83
+
84
+ assert(reloadedDoc, "doc should be loaded")
85
+
86
+ // make a change
87
+ const changedDoc = A.change<any>(reloadedDoc, "test 2", d => {
88
+ d.foo = "baz"
89
+ })
90
+
91
+ // save it to storage
92
+ storage2.saveDoc(key, changedDoc)
93
+ })
94
+
95
+ it("removes an Automerge document", async () => {
96
+ const storage = new StorageSubsystem(adapter)
97
+
98
+ const doc = A.change(A.init<any>(), "test", d => {
99
+ d.foo = "bar"
100
+ })
101
+
102
+ // save it to storage
103
+ const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
104
+ await storage.saveDoc(key, doc)
32
105
 
33
- // reload it from storage
34
- const reloadedDoc = await storage.loadDoc(key)
106
+ // reload it from storage
107
+ const reloadedDoc = await storage.loadDoc(key)
35
108
 
36
- // check that it's the same doc
37
- assert.deepStrictEqual(reloadedDoc, doc)
109
+ // check that it's the same doc
110
+ assert.deepStrictEqual(reloadedDoc, doc)
111
+
112
+ // remove it
113
+ await storage.removeDoc(key)
114
+
115
+ // reload it from storage
116
+ const reloadedDoc2 = await storage.loadDoc(key)
117
+
118
+ // check that it's undefined
119
+ assert.equal(reloadedDoc2, undefined)
120
+ })
38
121
  })
39
- })
40
- })
41
122
 
42
- it("correctly stores incremental changes following a load", async () => {
43
- const adapter = new DummyStorageAdapter()
44
- const storage = new StorageSubsystem(adapter)
123
+ describe("Arbitrary key/value storage", () => {
124
+ it("stores and retrieves a blob", async () => {
125
+ const storage = new StorageSubsystem(adapter)
45
126
 
46
- const doc = A.change(A.init<any>(), "test", d => {
47
- d.foo = "bar"
48
- })
127
+ const value = cbor.encode({ foo: "bar" })
49
128
 
50
- // save it to storage
51
- const key = parseAutomergeUrl(generateAutomergeUrl()).documentId
52
- storage.saveDoc(key, doc)
129
+ const namespace = "MyCoolAdapter"
130
+ const key = "ABC123"
131
+ await storage.save(namespace, key, value)
53
132
 
54
- // create new storage subsystem to simulate a new process
55
- const storage2 = new StorageSubsystem(adapter)
133
+ const reloadedValue = await storage.load(namespace, key)
134
+ assert.notEqual(reloadedValue, undefined)
135
+ assert.deepEqual(cbor.decode(reloadedValue)["foo"], "bar")
136
+ })
56
137
 
57
- // reload it from storage
58
- const reloadedDoc = await storage2.loadDoc(key)
138
+ it("keeps namespaces separate", async () => {
139
+ const storage = new StorageSubsystem(adapter)
59
140
 
60
- assert(reloadedDoc, "doc should be loaded")
141
+ const key = "ABC123"
61
142
 
62
- // make a change
63
- const changedDoc = A.change<any>(reloadedDoc, "test 2", d => {
64
- d.foo = "baz"
65
- })
143
+ const namespace1 = "MyCoolAdapter"
144
+ const value1 = cbor.encode({ foo: "bar" })
145
+ await storage.save(namespace1, key, value1)
146
+
147
+ const namespace2 = "SomeDumbAdapter"
148
+ const value2 = cbor.encode({ baz: "pizza" })
149
+ await storage.save(namespace2, key, value2)
66
150
 
67
- // save it to storage
68
- storage2.saveDoc(key, changedDoc)
151
+ const reloadedValue1 = await storage.load(namespace1, key)
152
+ assert.notEqual(reloadedValue1, undefined)
153
+ assert.deepEqual(cbor.decode(reloadedValue1)["foo"], "bar")
69
154
 
70
- // check that the storage adapter contains the correct keys
71
- assert(adapter.keys().some(k => k.startsWith(`${key}.incremental.`)))
155
+ const reloadedValue2 = await storage.load(namespace2, key)
156
+ assert.notEqual(reloadedValue2, undefined)
157
+ assert.deepEqual(cbor.decode(reloadedValue2)["baz"], "pizza")
158
+ })
159
+
160
+ it("removes a blob", async () => {
161
+ const storage = new StorageSubsystem(adapter)
162
+
163
+ const value = cbor.encode({ foo: "bar" })
164
+
165
+ const namespace = "MyCoolAdapter"
166
+ const key = "ABC123"
167
+ await storage.save(namespace, key, value)
168
+
169
+ const reloadedValue = await storage.load(namespace, key)
170
+ assert.notEqual(reloadedValue, undefined)
171
+ assert.deepEqual(cbor.decode(reloadedValue)["foo"], "bar")
172
+
173
+ await storage.remove(namespace, key)
174
+
175
+ const reloadedValue2 = await storage.load(namespace, key)
176
+ assert.equal(reloadedValue2, undefined)
177
+ })
178
+ })
179
+ })
72
180
  })
73
181
  })
@@ -1,4 +1,4 @@
1
- import { StorageAdapter, type StorageKey } from "../../src/index.js"
1
+ import { Chunk, StorageAdapter, type StorageKey } from "../../src/index.js"
2
2
 
3
3
  export class DummyStorageAdapter implements StorageAdapter {
4
4
  #data: Record<string, Uint8Array> = {}
@@ -11,9 +11,7 @@ export class DummyStorageAdapter implements StorageAdapter {
11
11
  return key.split(".")
12
12
  }
13
13
 
14
- async loadRange(
15
- keyPrefix: StorageKey
16
- ): Promise<{ data: Uint8Array; key: StorageKey }[]> {
14
+ async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
17
15
  const range = Object.entries(this.#data)
18
16
  .filter(([key, _]) => key.startsWith(this.#keyToString(keyPrefix)))
19
17
  .map(([key, data]) => ({ key: this.#stringToKey(key), data }))