@automerge/automerge-repo 1.0.13 → 1.0.14
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.
- package/dist/Repo.js +1 -1
- package/dist/helpers/cbor.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.d.ts +3 -3
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/messages.d.ts +7 -18
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/storage/StorageAdapter.d.ts +19 -22
- package/dist/storage/StorageAdapter.d.ts.map +1 -1
- package/dist/storage/StorageAdapter.js +2 -2
- package/dist/storage/StorageSubsystem.d.ts +39 -3
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +128 -75
- package/dist/storage/chunkTypeFromKey.d.ts +13 -0
- package/dist/storage/chunkTypeFromKey.d.ts.map +1 -0
- package/dist/storage/chunkTypeFromKey.js +18 -0
- package/dist/storage/keyHash.d.ts +4 -0
- package/dist/storage/keyHash.d.ts.map +1 -0
- package/dist/storage/keyHash.js +15 -0
- package/dist/storage/types.d.ts +37 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +1 -0
- package/package.json +2 -2
- package/src/Repo.ts +1 -1
- package/src/helpers/cbor.ts +1 -1
- package/src/index.ts +11 -3
- package/src/network/NetworkAdapter.ts +3 -3
- package/src/network/messages.ts +8 -21
- package/src/storage/StorageAdapter.ts +23 -30
- package/src/storage/StorageSubsystem.ts +159 -93
- package/src/storage/chunkTypeFromKey.ts +22 -0
- package/src/storage/keyHash.ts +17 -0
- package/src/storage/types.ts +39 -0
- package/test/StorageSubsystem.test.ts +143 -35
- 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
|
-
|
|
23
|
-
|
|
24
|
+
describe("Automerge document storage", () => {
|
|
25
|
+
it("stores and retrieves an Automerge document", async () => {
|
|
26
|
+
const storage = new StorageSubsystem(adapter)
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
106
|
+
// reload it from storage
|
|
107
|
+
const reloadedDoc = await storage.loadDoc(key)
|
|
35
108
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
123
|
+
describe("Arbitrary key/value storage", () => {
|
|
124
|
+
it("stores and retrieves a blob", async () => {
|
|
125
|
+
const storage = new StorageSubsystem(adapter)
|
|
45
126
|
|
|
46
|
-
|
|
47
|
-
d.foo = "bar"
|
|
48
|
-
})
|
|
127
|
+
const value = cbor.encode({ foo: "bar" })
|
|
49
128
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
129
|
+
const namespace = "MyCoolAdapter"
|
|
130
|
+
const key = "ABC123"
|
|
131
|
+
await storage.save(namespace, key, value)
|
|
53
132
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
138
|
+
it("keeps namespaces separate", async () => {
|
|
139
|
+
const storage = new StorageSubsystem(adapter)
|
|
59
140
|
|
|
60
|
-
|
|
141
|
+
const key = "ABC123"
|
|
61
142
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
151
|
+
const reloadedValue1 = await storage.load(namespace1, key)
|
|
152
|
+
assert.notEqual(reloadedValue1, undefined)
|
|
153
|
+
assert.deepEqual(cbor.decode(reloadedValue1)["foo"], "bar")
|
|
69
154
|
|
|
70
|
-
|
|
71
|
-
|
|
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 }))
|