@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.
Files changed (63) hide show
  1. package/bench/mst.bench.ts +7 -4
  2. package/bench/repo.bench.ts +25 -16
  3. package/dist/block-map.d.ts +25 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +11605 -10399
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +68 -25
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +22 -0
  14. package/dist/repo.d.ts +14 -30
  15. package/dist/storage/index.d.ts +4 -0
  16. package/dist/storage/memory-blockstore.d.ts +28 -0
  17. package/dist/storage/readable-blockstore.d.ts +24 -0
  18. package/dist/storage/repo-storage.d.ts +18 -0
  19. package/dist/storage/sync-storage.d.ts +15 -0
  20. package/dist/storage/types.d.ts +3 -0
  21. package/dist/sync/consumer.d.ts +18 -0
  22. package/dist/sync/index.d.ts +2 -0
  23. package/dist/sync/provider.d.ts +9 -0
  24. package/dist/types.d.ts +124 -317
  25. package/dist/util.d.ts +31 -12
  26. package/dist/verify.d.ts +26 -4
  27. package/package.json +4 -2
  28. package/src/block-map.ts +95 -0
  29. package/src/cid-set.ts +1 -2
  30. package/src/data-diff.ts +121 -0
  31. package/src/error.ts +31 -0
  32. package/src/index.ts +3 -1
  33. package/src/mst/diff.ts +120 -90
  34. package/src/mst/mst.ts +185 -184
  35. package/src/mst/util.ts +54 -31
  36. package/src/parse.ts +44 -0
  37. package/src/readable-repo.ts +75 -0
  38. package/src/repo.ts +119 -249
  39. package/src/storage/index.ts +4 -0
  40. package/src/storage/memory-blockstore.ts +114 -0
  41. package/src/storage/readable-blockstore.ts +56 -0
  42. package/src/storage/repo-storage.ts +42 -0
  43. package/src/storage/sync-storage.ts +35 -0
  44. package/src/storage/types.ts +3 -0
  45. package/src/sync/consumer.ts +137 -0
  46. package/src/sync/index.ts +2 -0
  47. package/src/sync/provider.ts +91 -0
  48. package/src/types.ts +101 -62
  49. package/src/util.ts +237 -56
  50. package/src/verify.ts +207 -42
  51. package/tests/_util.ts +132 -97
  52. package/tests/mst.test.ts +269 -122
  53. package/tests/repo.test.ts +48 -50
  54. package/tests/sync/checkout.test.ts +57 -0
  55. package/tests/sync/diff.test.ts +87 -0
  56. package/tests/sync/narrow.test.ts +145 -0
  57. package/tsconfig.build.tsbuildinfo +1 -1
  58. package/tsconfig.json +2 -1
  59. package/src/blockstore/index.ts +0 -2
  60. package/src/blockstore/ipld-store.ts +0 -103
  61. package/src/blockstore/memory-blockstore.ts +0 -49
  62. package/src/sync.ts +0 -38
  63. package/tests/sync.test.ts +0 -129
package/tests/mst.test.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { MST, DataAdd, DataUpdate, DataDelete } from '../src/mst'
2
- import { countPrefixLen } from '../src/mst/util'
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/blockstore'
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.generateBulkTidMapping(1000, blockstore)
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 cid = await mst.stage()
94
- const loaded = await MST.load(blockstore, cid)
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.generateBulkTidMapping(100, blockstore),
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 mst.diff(toDiff)
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
- // Special Cases (these are made for fanout 32)
158
- // ------------
159
-
160
- it('trims the top of an MST on stage', async () => {
161
- const layer0 = [
162
- '3j6hnk65jis2t',
163
- '3j6hnk65jit2t',
164
- '3j6hnk65jiu2t',
165
- '3j6hnk65jne2t',
166
- '3j6hnk65jnm2t',
167
- ]
168
- const layer1 = '3j6hnk65jju2t'
169
- mst = await MST.create(blockstore, [], { fanout: 32 })
170
- const cid = await util.randomCid()
171
- const tids = [...layer0, layer1]
172
- for (const tid of tids) {
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
- // These are some tricky things that can come up that may not be included in a randomized tree
185
-
186
- /**
187
- * `f` gets added & it does two node splits (e is no longer grouped with g/h)
188
- *
189
- * * *
190
- * _________|________ ____|_____
191
- * | | | | | | | |
192
- * * d * i * -> * f *
193
- * __|__ __|__ __|__ __|__ __|___
194
- * | | | | | | | | | | | | | | |
195
- * a b c e g h j k l * d * * i *
196
- * __|__ | _|_ __|__
197
- * | | | | | | | | |
198
- * a b c e g h j k l
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
- const root = await mst.stage()
228
- mst = MST.load(blockstore, root, { fanout: 32 })
229
-
230
- const allTids = [...layer0, ...layer1, layer2]
231
- for (const tid of allTids) {
232
- const got = await mst.get(tid)
233
- expect(cid.equals(got)).toBeTruthy()
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
- const layer = await mst.getLayer()
263
- expect(layer).toBe(2)
264
- const allTids = [...layer0, layer2]
265
- for (const tid of allTids) {
266
- const got = await mst.get(tid)
267
- expect(cid.equals(got)).toBeTruthy()
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('utils', () => {
273
- it('counts prefix length', () => {
274
- expect(countPrefixLen('abc', 'abc')).toBe(3)
275
- expect(countPrefixLen('', 'abc')).toBe(0)
276
- expect(countPrefixLen('abc', '')).toBe(0)
277
- expect(countPrefixLen('ab', 'abc')).toBe(2)
278
- expect(countPrefixLen('abc', 'ab')).toBe(2)
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
  })
@@ -1,107 +1,105 @@
1
- import * as auth from '@atproto/auth'
2
-
1
+ import * as crypto from '@atproto/crypto'
3
2
  import { Repo } from '../src/repo'
4
- import { MemoryBlockstore } from '../src/blockstore'
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 blockstore: MemoryBlockstore
13
- let authStore: auth.AuthStore
12
+ let storage: MemoryBlockstore
13
+ let keypair: crypto.Keypair
14
14
  let repo: Repo
15
- let repoData: util.RepoData
15
+ let repoData: RepoContents
16
16
 
17
17
  it('creates repo', async () => {
18
- blockstore = new MemoryBlockstore()
19
- authStore = await verifier.createTempAuthStore()
20
- await authStore.claimFull()
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.meta.did).toEqual(await authStore.did())
26
- expect(repo.meta.version).toBe(1)
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
- .stageUpdate({
35
- action: 'create',
31
+ repo = await repo.applyWrites(
32
+ {
33
+ action: WriteOpAction.Create,
36
34
  collection: collName,
37
- rkey: rkey,
38
- value: record,
39
- })
40
- .createCommit(authStore)
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
- .stageUpdate({
48
- action: 'update',
45
+ repo = await repo.applyWrites(
46
+ {
47
+ action: WriteOpAction.Update,
49
48
  collection: collName,
50
- rkey: rkey,
51
- value: updatedRecord,
52
- })
53
- .createCommit(authStore)
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
- .stageUpdate({
59
- action: 'delete',
57
+ repo = await repo.applyWrites(
58
+ {
59
+ action: WriteOpAction.Delete,
60
60
  collection: collName,
61
61
  rkey: rkey,
62
- })
63
- .createCommit(authStore)
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, authStore, 100)
70
+ const filled = await util.fillRepo(repo, keypair, 100)
70
71
  repo = filled.repo
71
72
  repoData = filled.data
72
- await util.checkRepo(repo, repoData)
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, authStore, {
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 util.checkRepo(repo, repoData)
84
+ const contents = await repo.getContents()
85
+ expect(contents).toEqual(repoData)
83
86
  })
84
87
 
85
- it('adds a valid signature to commit', async () => {
86
- const commit = await repo.commit
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(await authStore.did())
94
+ expect(repo.did).toEqual(keypair.did())
97
95
  })
98
96
 
99
97
  it('loads from blockstore', async () => {
100
- const reloadedRepo = await Repo.load(blockstore, repo.cid)
98
+ const reloadedRepo = await Repo.load(storage, repo.cid)
101
99
 
102
- await util.checkRepo(reloadedRepo, repoData)
103
- expect(repo.meta.did).toEqual(await authStore.did())
104
- expect(repo.meta.version).toBe(1)
105
- expect(repo.meta.datastore).toBe('mst')
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
+ })