@atproto/repo 0.0.1 → 0.1.0
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/bench/mst.bench.ts +7 -4
- package/bench/repo.bench.ts +25 -16
- package/dist/block-map.d.ts +25 -0
- package/dist/data-diff.d.ts +36 -0
- package/dist/error.d.ts +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11605 -10399
- package/dist/index.js.map +4 -4
- package/dist/mst/diff.d.ts +4 -33
- package/dist/mst/mst.d.ts +68 -25
- package/dist/mst/util.d.ts +13 -5
- package/dist/parse.d.ts +16 -0
- package/dist/readable-repo.d.ts +22 -0
- package/dist/repo.d.ts +14 -30
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/memory-blockstore.d.ts +28 -0
- package/dist/storage/readable-blockstore.d.ts +24 -0
- package/dist/storage/repo-storage.d.ts +18 -0
- package/dist/storage/sync-storage.d.ts +15 -0
- package/dist/storage/types.d.ts +3 -0
- package/dist/sync/consumer.d.ts +18 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/provider.d.ts +9 -0
- package/dist/types.d.ts +124 -317
- package/dist/util.d.ts +31 -12
- package/dist/verify.d.ts +26 -4
- package/package.json +4 -2
- package/src/block-map.ts +95 -0
- package/src/cid-set.ts +1 -2
- package/src/data-diff.ts +121 -0
- package/src/error.ts +31 -0
- package/src/index.ts +3 -1
- package/src/mst/diff.ts +120 -90
- package/src/mst/mst.ts +185 -184
- package/src/mst/util.ts +54 -31
- package/src/parse.ts +44 -0
- package/src/readable-repo.ts +75 -0
- package/src/repo.ts +119 -249
- package/src/storage/index.ts +4 -0
- package/src/storage/memory-blockstore.ts +114 -0
- package/src/storage/readable-blockstore.ts +56 -0
- package/src/storage/repo-storage.ts +42 -0
- package/src/storage/sync-storage.ts +35 -0
- package/src/storage/types.ts +3 -0
- package/src/sync/consumer.ts +137 -0
- package/src/sync/index.ts +2 -0
- package/src/sync/provider.ts +91 -0
- package/src/types.ts +101 -62
- package/src/util.ts +237 -56
- package/src/verify.ts +207 -42
- package/tests/_util.ts +132 -97
- package/tests/mst.test.ts +269 -122
- package/tests/repo.test.ts +48 -50
- package/tests/sync/checkout.test.ts +57 -0
- package/tests/sync/diff.test.ts +87 -0
- package/tests/sync/narrow.test.ts +145 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +2 -1
- package/src/blockstore/index.ts +0 -2
- package/src/blockstore/ipld-store.ts +0 -103
- package/src/blockstore/memory-blockstore.ts +0 -49
- package/src/sync.ts +0 -38
- package/tests/sync.test.ts +0 -129
package/tests/mst.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { MST
|
|
2
|
-
import {
|
|
1
|
+
import { MST } from '../src/mst'
|
|
2
|
+
import DataDiff, { DataAdd, DataUpdate, DataDelete } from '../src/data-diff'
|
|
3
|
+
import { countPrefixLen, InvalidMstKeyError } from '../src/mst/util'
|
|
3
4
|
|
|
4
|
-
import { MemoryBlockstore } from '../src/
|
|
5
|
+
import { MemoryBlockstore } from '../src/storage'
|
|
5
6
|
import * as util from './_util'
|
|
6
7
|
|
|
7
8
|
import { CID } from 'multiformats'
|
|
@@ -15,7 +16,7 @@ describe('Merkle Search Tree', () => {
|
|
|
15
16
|
beforeAll(async () => {
|
|
16
17
|
blockstore = new MemoryBlockstore()
|
|
17
18
|
mst = await MST.create(blockstore)
|
|
18
|
-
mapping = await util.
|
|
19
|
+
mapping = await util.generateBulkDataKeys(1000, blockstore)
|
|
19
20
|
shuffled = util.shuffle(Object.entries(mapping))
|
|
20
21
|
})
|
|
21
22
|
|
|
@@ -90,8 +91,8 @@ describe('Merkle Search Tree', () => {
|
|
|
90
91
|
})
|
|
91
92
|
|
|
92
93
|
it('saves and loads from blockstore', async () => {
|
|
93
|
-
const
|
|
94
|
-
const loaded = await MST.load(blockstore,
|
|
94
|
+
const root = await util.saveMst(blockstore, mst)
|
|
95
|
+
const loaded = await MST.load(blockstore, root)
|
|
95
96
|
const origNodes = await mst.allNodes()
|
|
96
97
|
const loadedNodes = await loaded.allNodes()
|
|
97
98
|
expect(origNodes.length).toBe(loadedNodes.length)
|
|
@@ -104,7 +105,7 @@ describe('Merkle Search Tree', () => {
|
|
|
104
105
|
let toDiff = mst
|
|
105
106
|
|
|
106
107
|
const toAdd = Object.entries(
|
|
107
|
-
await util.
|
|
108
|
+
await util.generateBulkDataKeys(100, blockstore),
|
|
108
109
|
)
|
|
109
110
|
const toEdit = shuffled.slice(500, 600)
|
|
110
111
|
const toDel = shuffled.slice(400, 500)
|
|
@@ -131,7 +132,7 @@ describe('Merkle Search Tree', () => {
|
|
|
131
132
|
expectedDels[entry[0]] = { key: entry[0], cid: entry[1] }
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
const diff = await
|
|
135
|
+
const diff = await DataDiff.of(toDiff, mst)
|
|
135
136
|
|
|
136
137
|
expect(diff.addList().length).toBe(100)
|
|
137
138
|
expect(diff.updateList().length).toBe(100)
|
|
@@ -154,127 +155,273 @@ describe('Merkle Search Tree', () => {
|
|
|
154
155
|
}
|
|
155
156
|
})
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
'
|
|
163
|
-
'
|
|
164
|
-
'
|
|
165
|
-
'
|
|
166
|
-
'
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
mst = await mst.add(tid, cid)
|
|
174
|
-
}
|
|
175
|
-
const layer = await mst.getLayer()
|
|
176
|
-
expect(layer).toBe(1)
|
|
177
|
-
mst = await mst.delete(layer1)
|
|
178
|
-
const root = await mst.stage()
|
|
179
|
-
const loaded = MST.load(blockstore, root)
|
|
180
|
-
const loadedLayer = await loaded.getLayer()
|
|
181
|
-
expect(loadedLayer).toBe(0)
|
|
158
|
+
describe('utils', () => {
|
|
159
|
+
it('counts prefix length', () => {
|
|
160
|
+
expect(countPrefixLen('abc', 'abc')).toBe(3)
|
|
161
|
+
expect(countPrefixLen('', 'abc')).toBe(0)
|
|
162
|
+
expect(countPrefixLen('abc', '')).toBe(0)
|
|
163
|
+
expect(countPrefixLen('ab', 'abc')).toBe(2)
|
|
164
|
+
expect(countPrefixLen('abc', 'ab')).toBe(2)
|
|
165
|
+
expect(countPrefixLen('abcde', 'abc')).toBe(3)
|
|
166
|
+
expect(countPrefixLen('abc', 'abcde')).toBe(3)
|
|
167
|
+
expect(countPrefixLen('abcde', 'abc1')).toBe(3)
|
|
168
|
+
expect(countPrefixLen('abcde', 'abb')).toBe(2)
|
|
169
|
+
expect(countPrefixLen('abcde', 'qbb')).toBe(0)
|
|
170
|
+
expect(countPrefixLen('', 'asdf')).toBe(0)
|
|
171
|
+
expect(countPrefixLen('abc', 'abc\x00')).toBe(3)
|
|
172
|
+
expect(countPrefixLen('abc\x00', 'abc')).toBe(3)
|
|
173
|
+
})
|
|
182
174
|
})
|
|
183
175
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
*/
|
|
201
|
-
it('handles splits that must go 2 deep', async () => {
|
|
202
|
-
const layer0 = [
|
|
203
|
-
'3j6hnk65jis2t',
|
|
204
|
-
'3j6hnk65jit2t',
|
|
205
|
-
'3j6hnk65jiu2t',
|
|
206
|
-
'3j6hnk65jne2t',
|
|
207
|
-
'3j6hnk65jnm2t',
|
|
208
|
-
'3j6hnk65jnn2t',
|
|
209
|
-
'3j6hnk65kvx2t',
|
|
210
|
-
'3j6hnk65kvy2t',
|
|
211
|
-
'3j6hnk65kvz2t',
|
|
212
|
-
]
|
|
213
|
-
const layer1 = ['3j6hnk65jju2t', '3j6hnk65kve2t']
|
|
214
|
-
const layer2 = '3j6hnk65jng2t'
|
|
215
|
-
mst = await MST.create(blockstore, [], { fanout: 32 })
|
|
216
|
-
const cid = await util.randomCid()
|
|
217
|
-
for (const tid of layer0) {
|
|
218
|
-
mst = await mst.add(tid, cid)
|
|
219
|
-
}
|
|
220
|
-
for (const tid of layer1) {
|
|
221
|
-
mst = await mst.add(tid, cid)
|
|
176
|
+
describe('MST Interop Allowable Keys', () => {
|
|
177
|
+
let blockstore: MemoryBlockstore
|
|
178
|
+
let mst: MST
|
|
179
|
+
let cid1: CID
|
|
180
|
+
|
|
181
|
+
beforeAll(async () => {
|
|
182
|
+
blockstore = new MemoryBlockstore()
|
|
183
|
+
mst = await MST.create(blockstore)
|
|
184
|
+
cid1 = CID.parse(
|
|
185
|
+
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const expectReject = async (key: string) => {
|
|
190
|
+
const promise = mst.add(key, cid1)
|
|
191
|
+
await expect(promise).rejects.toThrow(InvalidMstKeyError)
|
|
222
192
|
}
|
|
223
|
-
mst = await mst.add(layer2, cid)
|
|
224
|
-
const layer = await mst.getLayer()
|
|
225
|
-
expect(layer).toBe(2)
|
|
226
193
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
194
|
+
it('rejects the empty key', async () => {
|
|
195
|
+
await expectReject('')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('rejects a key with no collection', async () => {
|
|
199
|
+
await expectReject('asdf')
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('rejects a key with a nested collection', async () => {
|
|
203
|
+
await expectReject('nested/collection/asdf')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('rejects on empty coll or rkey', async () => {
|
|
207
|
+
await expectReject('coll/')
|
|
208
|
+
await expectReject('/rkey')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('rejects non-ascii chars', async () => {
|
|
212
|
+
await expectReject('coll/jalapeñoA')
|
|
213
|
+
await expectReject('coll/coöperative')
|
|
214
|
+
await expectReject('coll/abc💩')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('rejects ascii that we dont support', async () => {
|
|
218
|
+
await expectReject('coll/key$')
|
|
219
|
+
await expectReject('coll/key%')
|
|
220
|
+
await expectReject('coll/key(')
|
|
221
|
+
await expectReject('coll/key)')
|
|
222
|
+
await expectReject('coll/key+')
|
|
223
|
+
await expectReject('coll/key=')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('rejects keys over 256 chars', async () => {
|
|
227
|
+
await expectReject(
|
|
228
|
+
'coll/asdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpao',
|
|
229
|
+
)
|
|
230
|
+
})
|
|
235
231
|
})
|
|
236
|
-
/**
|
|
237
|
-
* `b` gets added & it hashes to 2 levels above any existing leaves
|
|
238
|
-
*
|
|
239
|
-
* * -> *
|
|
240
|
-
* __|__ __|__
|
|
241
|
-
* | | | | |
|
|
242
|
-
* a c * b *
|
|
243
|
-
* | |
|
|
244
|
-
* * *
|
|
245
|
-
* | |
|
|
246
|
-
* a c
|
|
247
|
-
*
|
|
248
|
-
*/
|
|
249
|
-
it('handles new layers that are two higher than existing', async () => {
|
|
250
|
-
const layer0 = ['3j6hnk65jis2t', '3j6hnk65kvz2t']
|
|
251
|
-
const layer2 = '3j6hnk65jng2t'
|
|
252
|
-
mst = await MST.create(blockstore, [], { fanout: 32 })
|
|
253
|
-
const cid = await util.randomCid()
|
|
254
|
-
for (const tid of layer0) {
|
|
255
|
-
mst = await mst.add(tid, cid)
|
|
256
|
-
}
|
|
257
|
-
mst = await mst.add(layer2, cid)
|
|
258
|
-
|
|
259
|
-
const root = await mst.stage()
|
|
260
|
-
mst = MST.load(blockstore, root, { fanout: 32 })
|
|
261
232
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
233
|
+
describe('MST Interop Known Maps', () => {
|
|
234
|
+
let blockstore: MemoryBlockstore
|
|
235
|
+
let mst: MST
|
|
236
|
+
let cid1: CID
|
|
237
|
+
|
|
238
|
+
beforeAll(async () => {
|
|
239
|
+
blockstore = new MemoryBlockstore()
|
|
240
|
+
cid1 = CID.parse(
|
|
241
|
+
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
beforeEach(async () => {
|
|
246
|
+
mst = await MST.create(blockstore)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('computes "empty" tree root CID', async () => {
|
|
250
|
+
expect(await mst.leafCount()).toBe(0)
|
|
251
|
+
expect((await mst.getPointer()).toString()).toBe(
|
|
252
|
+
'bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm',
|
|
253
|
+
)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('computes "trivial" tree root CID', async () => {
|
|
257
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1)
|
|
258
|
+
expect(await mst.leafCount()).toBe(1)
|
|
259
|
+
expect((await mst.getPointer()).toString()).toBe(
|
|
260
|
+
'bafyreibj4lsc3aqnrvphp5xmrnfoorvru4wynt6lwidqbm2623a6tatzdu',
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('computes "singlelayer2" tree root CID', async () => {
|
|
265
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1)
|
|
266
|
+
expect(await mst.leafCount()).toBe(1)
|
|
267
|
+
expect(await mst.layer).toBe(2)
|
|
268
|
+
expect((await mst.getPointer()).toString()).toBe(
|
|
269
|
+
'bafyreih7wfei65pxzhauoibu3ls7jgmkju4bspy4t2ha2qdjnzqvoy33ai',
|
|
270
|
+
)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('computes "simple" tree root CID', async () => {
|
|
274
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // level 0
|
|
275
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fr2j', cid1) // level 0
|
|
276
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // level 1
|
|
277
|
+
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // level 0
|
|
278
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fc2j', cid1) // level 0
|
|
279
|
+
expect(await mst.leafCount()).toBe(5)
|
|
280
|
+
expect((await mst.getPointer()).toString()).toBe(
|
|
281
|
+
'bafyreicmahysq4n6wfuxo522m6dpiy7z7qzym3dzs756t5n7nfdgccwq7m',
|
|
282
|
+
)
|
|
283
|
+
})
|
|
269
284
|
})
|
|
270
|
-
})
|
|
271
285
|
|
|
272
|
-
describe('
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
286
|
+
describe('MST Interop Edge Cases', () => {
|
|
287
|
+
let blockstore: MemoryBlockstore
|
|
288
|
+
let mst: MST
|
|
289
|
+
let cid1: CID
|
|
290
|
+
|
|
291
|
+
beforeAll(async () => {
|
|
292
|
+
blockstore = new MemoryBlockstore()
|
|
293
|
+
cid1 = CID.parse(
|
|
294
|
+
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
beforeEach(async () => {
|
|
299
|
+
mst = await MST.create(blockstore)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('trims top of tree on delete', async () => {
|
|
303
|
+
const l1root =
|
|
304
|
+
'bafyreifnqrwbk6ffmyaz5qtujqrzf5qmxf7cbxvgzktl4e3gabuxbtatv4'
|
|
305
|
+
const l0root =
|
|
306
|
+
'bafyreie4kjuxbwkhzg2i5dljaswcroeih4dgiqq6pazcmunwt2byd725vi'
|
|
307
|
+
|
|
308
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fn2j', cid1) // level 0
|
|
309
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1) // level 0
|
|
310
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // level 0
|
|
311
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // level 1
|
|
312
|
+
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // level 0
|
|
313
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fu2j', cid1) // level 0
|
|
314
|
+
|
|
315
|
+
expect(await mst.leafCount()).toBe(6)
|
|
316
|
+
expect(await mst.layer).toBe(1)
|
|
317
|
+
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
318
|
+
|
|
319
|
+
mst = await mst.delete('com.example.record/3jqfcqzm3fs2j') // level 1
|
|
320
|
+
expect(await mst.leafCount()).toBe(5)
|
|
321
|
+
expect(await mst.layer).toBe(0)
|
|
322
|
+
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
*
|
|
327
|
+
* * *
|
|
328
|
+
* _________|________ ____|_____
|
|
329
|
+
* | | | | | | | |
|
|
330
|
+
* * d * i * -> * f *
|
|
331
|
+
* __|__ __|__ __|__ __|__ __|___
|
|
332
|
+
* | | | | | | | | | | | | | | |
|
|
333
|
+
* a b c e g h j k l * d * * i *
|
|
334
|
+
* __|__ | _|_ __|__
|
|
335
|
+
* | | | | | | | | |
|
|
336
|
+
* a b c e g h j k l
|
|
337
|
+
*
|
|
338
|
+
*/
|
|
339
|
+
it('handles insertion that splits two layers down', async () => {
|
|
340
|
+
const l1root =
|
|
341
|
+
'bafyreiettyludka6fpgp33stwxfuwhkzlur6chs4d2v4nkmq2j3ogpdjem'
|
|
342
|
+
const l2root =
|
|
343
|
+
'bafyreid2x5eqs4w4qxvc5jiwda4cien3gw2q6cshofxwnvv7iucrmfohpm'
|
|
344
|
+
|
|
345
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1) // A; level 0
|
|
346
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // B; level 0
|
|
347
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fr2j', cid1) // C; level 0
|
|
348
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // D; level 1
|
|
349
|
+
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // E; level 0
|
|
350
|
+
// GAP for F
|
|
351
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fz2j', cid1) // G; level 0
|
|
352
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fc2j', cid1) // H; level 0
|
|
353
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fd2j', cid1) // I; level 1
|
|
354
|
+
mst = await mst.add('com.example.record/3jqfcqzm4ff2j', cid1) // J; level 0
|
|
355
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fg2j', cid1) // K; level 0
|
|
356
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fh2j', cid1) // L; level 0
|
|
357
|
+
|
|
358
|
+
expect(await mst.leafCount()).toBe(11)
|
|
359
|
+
expect(await mst.layer).toBe(1)
|
|
360
|
+
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
361
|
+
|
|
362
|
+
// insert F, which will push E out of the node with G+H to a new node under D
|
|
363
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // F; level 2
|
|
364
|
+
expect(await mst.leafCount()).toBe(12)
|
|
365
|
+
expect(await mst.layer).toBe(2)
|
|
366
|
+
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
367
|
+
|
|
368
|
+
// remove F, which should push E back over with G+H
|
|
369
|
+
mst = await mst.delete('com.example.record/3jqfcqzm3fx2j') // F; level 2
|
|
370
|
+
expect(await mst.leafCount()).toBe(11)
|
|
371
|
+
expect(await mst.layer).toBe(1)
|
|
372
|
+
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
*
|
|
377
|
+
* * -> *
|
|
378
|
+
* __|__ __|__
|
|
379
|
+
* | | | | |
|
|
380
|
+
* a c * b *
|
|
381
|
+
* | |
|
|
382
|
+
* * *
|
|
383
|
+
* | |
|
|
384
|
+
* a c
|
|
385
|
+
*
|
|
386
|
+
*/
|
|
387
|
+
it('handles new layers that are two higher than existing', async () => {
|
|
388
|
+
const l0root =
|
|
389
|
+
'bafyreidfcktqnfmykz2ps3dbul35pepleq7kvv526g47xahuz3rqtptmky'
|
|
390
|
+
const l2root =
|
|
391
|
+
'bafyreiavxaxdz7o7rbvr3zg2liox2yww46t7g6hkehx4i4h3lwudly7dhy'
|
|
392
|
+
const l2root2 =
|
|
393
|
+
'bafyreig4jv3vuajbsybhyvb7gggvpwh2zszwfyttjrj6qwvcsp24h6popu'
|
|
394
|
+
|
|
395
|
+
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // A; level 0
|
|
396
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fz2j', cid1) // C; level 0
|
|
397
|
+
expect(await mst.leafCount()).toBe(2)
|
|
398
|
+
expect(await mst.layer).toBe(0)
|
|
399
|
+
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
400
|
+
|
|
401
|
+
// insert B, which is two levels above
|
|
402
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // B; level 2
|
|
403
|
+
expect(await mst.leafCount()).toBe(3)
|
|
404
|
+
expect(await mst.layer).toBe(2)
|
|
405
|
+
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
406
|
+
|
|
407
|
+
// remove B
|
|
408
|
+
mst = await mst.delete('com.example.record/3jqfcqzm3fx2j') // B; level 2
|
|
409
|
+
expect(await mst.leafCount()).toBe(2)
|
|
410
|
+
expect(await mst.layer).toBe(0)
|
|
411
|
+
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
412
|
+
|
|
413
|
+
// insert B (level=2) and D (level=1)
|
|
414
|
+
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // B; level 2
|
|
415
|
+
mst = await mst.add('com.example.record/3jqfcqzm4fd2j', cid1) // D; level 1
|
|
416
|
+
expect(await mst.leafCount()).toBe(4)
|
|
417
|
+
expect(await mst.layer).toBe(2)
|
|
418
|
+
expect((await mst.getPointer()).toString()).toBe(l2root2)
|
|
419
|
+
|
|
420
|
+
// remove D
|
|
421
|
+
mst = await mst.delete('com.example.record/3jqfcqzm4fd2j') // D; level 1
|
|
422
|
+
expect(await mst.leafCount()).toBe(3)
|
|
423
|
+
expect(await mst.layer).toBe(2)
|
|
424
|
+
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
425
|
+
})
|
|
279
426
|
})
|
|
280
427
|
})
|
package/tests/repo.test.ts
CHANGED
|
@@ -1,107 +1,105 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
1
|
+
import * as crypto from '@atproto/crypto'
|
|
3
2
|
import { Repo } from '../src/repo'
|
|
4
|
-
import { MemoryBlockstore } from '../src/
|
|
3
|
+
import { MemoryBlockstore } from '../src/storage'
|
|
5
4
|
import * as util from './_util'
|
|
6
5
|
import { TID } from '@atproto/common'
|
|
6
|
+
import { RepoContents, verifyCommitSig, WriteOpAction } from '../src'
|
|
7
|
+
import { Secp256k1Keypair } from '@atproto/crypto'
|
|
7
8
|
|
|
8
9
|
describe('Repo', () => {
|
|
9
|
-
const verifier = new auth.Verifier()
|
|
10
10
|
const collName = 'com.example.posts'
|
|
11
11
|
|
|
12
|
-
let
|
|
13
|
-
let
|
|
12
|
+
let storage: MemoryBlockstore
|
|
13
|
+
let keypair: crypto.Keypair
|
|
14
14
|
let repo: Repo
|
|
15
|
-
let repoData:
|
|
15
|
+
let repoData: RepoContents
|
|
16
16
|
|
|
17
17
|
it('creates repo', async () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
await
|
|
21
|
-
repo = await Repo.create(blockstore, await authStore.did(), authStore)
|
|
18
|
+
storage = new MemoryBlockstore()
|
|
19
|
+
keypair = await Secp256k1Keypair.create()
|
|
20
|
+
repo = await Repo.create(storage, keypair.did(), keypair)
|
|
22
21
|
})
|
|
23
22
|
|
|
24
23
|
it('has proper metadata', async () => {
|
|
25
|
-
expect(repo.
|
|
26
|
-
expect(repo.
|
|
27
|
-
expect(repo.meta.datastore).toBe('mst')
|
|
24
|
+
expect(repo.did).toEqual(keypair.did())
|
|
25
|
+
expect(repo.version).toBe(2)
|
|
28
26
|
})
|
|
29
27
|
|
|
30
28
|
it('does basic operations', async () => {
|
|
31
29
|
const rkey = TID.nextStr()
|
|
32
30
|
const record = util.generateObject()
|
|
33
|
-
repo = await repo
|
|
34
|
-
|
|
35
|
-
action:
|
|
31
|
+
repo = await repo.applyWrites(
|
|
32
|
+
{
|
|
33
|
+
action: WriteOpAction.Create,
|
|
36
34
|
collection: collName,
|
|
37
|
-
rkey
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
35
|
+
rkey,
|
|
36
|
+
record,
|
|
37
|
+
},
|
|
38
|
+
keypair,
|
|
39
|
+
)
|
|
41
40
|
|
|
42
41
|
let got = await repo.getRecord(collName, rkey)
|
|
43
42
|
expect(got).toEqual(record)
|
|
44
43
|
|
|
45
44
|
const updatedRecord = util.generateObject()
|
|
46
|
-
repo = await repo
|
|
47
|
-
|
|
48
|
-
action:
|
|
45
|
+
repo = await repo.applyWrites(
|
|
46
|
+
{
|
|
47
|
+
action: WriteOpAction.Update,
|
|
49
48
|
collection: collName,
|
|
50
|
-
rkey
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
49
|
+
rkey,
|
|
50
|
+
record: updatedRecord,
|
|
51
|
+
},
|
|
52
|
+
keypair,
|
|
53
|
+
)
|
|
54
54
|
got = await repo.getRecord(collName, rkey)
|
|
55
55
|
expect(got).toEqual(updatedRecord)
|
|
56
56
|
|
|
57
|
-
repo = await repo
|
|
58
|
-
|
|
59
|
-
action:
|
|
57
|
+
repo = await repo.applyWrites(
|
|
58
|
+
{
|
|
59
|
+
action: WriteOpAction.Delete,
|
|
60
60
|
collection: collName,
|
|
61
61
|
rkey: rkey,
|
|
62
|
-
}
|
|
63
|
-
|
|
62
|
+
},
|
|
63
|
+
keypair,
|
|
64
|
+
)
|
|
64
65
|
got = await repo.getRecord(collName, rkey)
|
|
65
66
|
expect(got).toBeNull()
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
it('adds content collections', async () => {
|
|
69
|
-
const filled = await util.fillRepo(repo,
|
|
70
|
+
const filled = await util.fillRepo(repo, keypair, 100)
|
|
70
71
|
repo = filled.repo
|
|
71
72
|
repoData = filled.data
|
|
72
|
-
await
|
|
73
|
+
const contents = await repo.getContents()
|
|
74
|
+
expect(contents).toEqual(repoData)
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
it('edits and deletes content', async () => {
|
|
76
|
-
const edited = await util.editRepo(repo, repoData,
|
|
78
|
+
const edited = await util.editRepo(repo, repoData, keypair, {
|
|
77
79
|
adds: 20,
|
|
78
80
|
updates: 20,
|
|
79
81
|
deletes: 20,
|
|
80
82
|
})
|
|
81
83
|
repo = edited.repo
|
|
82
|
-
await
|
|
84
|
+
const contents = await repo.getContents()
|
|
85
|
+
expect(contents).toEqual(repoData)
|
|
83
86
|
})
|
|
84
87
|
|
|
85
|
-
it('
|
|
86
|
-
const
|
|
87
|
-
const verified = await verifier.verifySignature(
|
|
88
|
-
repo.did,
|
|
89
|
-
commit.root.bytes,
|
|
90
|
-
commit.sig,
|
|
91
|
-
)
|
|
88
|
+
it('has a valid signature to commit', async () => {
|
|
89
|
+
const verified = await verifyCommitSig(repo.commit, keypair.did())
|
|
92
90
|
expect(verified).toBeTruthy()
|
|
93
91
|
})
|
|
94
92
|
|
|
95
93
|
it('sets correct DID', async () => {
|
|
96
|
-
expect(repo.did).toEqual(
|
|
94
|
+
expect(repo.did).toEqual(keypair.did())
|
|
97
95
|
})
|
|
98
96
|
|
|
99
97
|
it('loads from blockstore', async () => {
|
|
100
|
-
const reloadedRepo = await Repo.load(
|
|
98
|
+
const reloadedRepo = await Repo.load(storage, repo.cid)
|
|
101
99
|
|
|
102
|
-
await
|
|
103
|
-
expect(
|
|
104
|
-
expect(repo.
|
|
105
|
-
expect(repo.
|
|
100
|
+
const contents = await reloadedRepo.getContents()
|
|
101
|
+
expect(contents).toEqual(repoData)
|
|
102
|
+
expect(repo.did).toEqual(keypair.did())
|
|
103
|
+
expect(repo.version).toBe(2)
|
|
106
104
|
})
|
|
107
105
|
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as crypto from '@atproto/crypto'
|
|
2
|
+
import { Repo, RepoContents, RepoVerificationError } from '../../src'
|
|
3
|
+
import { MemoryBlockstore } from '../../src/storage'
|
|
4
|
+
import * as sync from '../../src/sync'
|
|
5
|
+
|
|
6
|
+
import * as util from '../_util'
|
|
7
|
+
|
|
8
|
+
describe('Checkout Sync', () => {
|
|
9
|
+
let storage: MemoryBlockstore
|
|
10
|
+
let syncStorage: MemoryBlockstore
|
|
11
|
+
let repo: Repo
|
|
12
|
+
let keypair: crypto.Keypair
|
|
13
|
+
let repoData: RepoContents
|
|
14
|
+
|
|
15
|
+
const repoDid = 'did:example:test'
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
storage = new MemoryBlockstore()
|
|
19
|
+
keypair = await crypto.Secp256k1Keypair.create()
|
|
20
|
+
repo = await Repo.create(storage, repoDid, keypair)
|
|
21
|
+
syncStorage = new MemoryBlockstore()
|
|
22
|
+
const filled = await util.fillRepo(repo, keypair, 20)
|
|
23
|
+
repo = filled.repo
|
|
24
|
+
repoData = filled.data
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('sync a non-historical repo checkout', async () => {
|
|
28
|
+
const checkoutCar = await sync.getCheckout(storage, repo.cid)
|
|
29
|
+
const checkout = await sync.loadCheckout(
|
|
30
|
+
syncStorage,
|
|
31
|
+
checkoutCar,
|
|
32
|
+
repoDid,
|
|
33
|
+
keypair.did(),
|
|
34
|
+
)
|
|
35
|
+
const checkoutRepo = await Repo.load(syncStorage, checkout.root)
|
|
36
|
+
const contents = await checkoutRepo.getContents()
|
|
37
|
+
expect(contents).toEqual(repoData)
|
|
38
|
+
expect(checkout.contents).toEqual(repoData)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('does not sync unneeded blocks during checkout', async () => {
|
|
42
|
+
const commitPath = await storage.getCommitPath(repo.cid, null)
|
|
43
|
+
if (!commitPath) {
|
|
44
|
+
throw new Error('Could not get commitPath')
|
|
45
|
+
}
|
|
46
|
+
const hasGenesisCommit = await syncStorage.has(commitPath[0])
|
|
47
|
+
expect(hasGenesisCommit).toBeFalsy()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('throws on a bad signature', async () => {
|
|
51
|
+
const badRepo = await util.addBadCommit(repo, keypair)
|
|
52
|
+
const checkoutCar = await sync.getCheckout(storage, badRepo.cid)
|
|
53
|
+
await expect(
|
|
54
|
+
sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()),
|
|
55
|
+
).rejects.toThrow(RepoVerificationError)
|
|
56
|
+
})
|
|
57
|
+
})
|