@atproto/repo 0.0.1 → 0.2.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 (104) 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 +27 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.js +22870 -12456
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +73 -31
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +23 -0
  14. package/dist/repo.d.ts +19 -31
  15. package/dist/src/block-map.d.ts +23 -0
  16. package/dist/src/blockstore/persistent-blockstore.d.ts +12 -0
  17. package/dist/src/cid-set.d.ts +14 -0
  18. package/dist/src/collection.d.ts +22 -0
  19. package/dist/src/data-diff.d.ts +34 -0
  20. package/dist/src/error.d.ts +21 -0
  21. package/dist/src/index.d.ts +7 -0
  22. package/dist/src/logger.d.ts +2 -0
  23. package/dist/src/mst/diff.d.ts +33 -0
  24. package/dist/src/mst/index.d.ts +4 -0
  25. package/dist/src/mst/mst.d.ts +106 -0
  26. package/dist/src/mst/util.d.ts +9 -0
  27. package/dist/src/mst/walker.d.ts +22 -0
  28. package/dist/src/parse.d.ts +11 -0
  29. package/dist/src/readable-repo.d.ts +25 -0
  30. package/dist/src/repo.d.ts +39 -0
  31. package/dist/src/storage/error.d.ts +22 -0
  32. package/dist/src/storage/index.d.ts +1 -0
  33. package/dist/src/storage/memory-blobstore.d.ts +1 -0
  34. package/dist/src/storage/memory-blockstore.d.ts +28 -0
  35. package/dist/src/storage/readable-blockstore.d.ts +21 -0
  36. package/dist/src/storage/repo-storage.d.ts +18 -0
  37. package/dist/src/storage/sync-storage.d.ts +15 -0
  38. package/dist/src/storage/types.d.ts +12 -0
  39. package/dist/src/storage/util.d.ts +17 -0
  40. package/dist/src/structure.d.ts +39 -0
  41. package/dist/src/sync/consumer.d.ts +19 -0
  42. package/dist/src/sync/index.d.ts +2 -0
  43. package/dist/src/sync/producer.d.ts +13 -0
  44. package/dist/src/sync/provider.d.ts +11 -0
  45. package/dist/src/types.d.ts +368 -0
  46. package/dist/src/util.d.ts +13 -0
  47. package/dist/src/verify.d.ts +5 -0
  48. package/dist/storage/index.d.ts +4 -0
  49. package/dist/storage/memory-blockstore.d.ts +29 -0
  50. package/dist/storage/readable-blockstore.d.ts +24 -0
  51. package/dist/storage/repo-storage.d.ts +19 -0
  52. package/dist/storage/sync-storage.d.ts +15 -0
  53. package/dist/storage/types.d.ts +4 -0
  54. package/dist/sync/consumer.d.ts +19 -0
  55. package/dist/sync/index.d.ts +2 -0
  56. package/dist/sync/provider.d.ts +9 -0
  57. package/dist/tsconfig.build.tsbuildinfo +1 -0
  58. package/dist/types.d.ts +137 -331
  59. package/dist/util.d.ts +35 -12
  60. package/dist/verify.d.ts +31 -4
  61. package/jest.bench.config.js +2 -1
  62. package/package.json +13 -6
  63. package/src/block-map.ts +103 -0
  64. package/src/cid-set.ts +1 -2
  65. package/src/data-diff.ts +117 -0
  66. package/src/error.ts +31 -0
  67. package/src/index.ts +4 -1
  68. package/src/mst/diff.ts +120 -90
  69. package/src/mst/mst.ts +179 -187
  70. package/src/mst/util.ts +54 -31
  71. package/src/parse.ts +44 -0
  72. package/src/readable-repo.ts +75 -0
  73. package/src/repo.ts +145 -244
  74. package/src/storage/index.ts +4 -0
  75. package/src/storage/memory-blockstore.ts +133 -0
  76. package/src/storage/readable-blockstore.ts +56 -0
  77. package/src/storage/repo-storage.ts +43 -0
  78. package/src/storage/sync-storage.ts +35 -0
  79. package/src/storage/types.ts +4 -0
  80. package/src/sync/consumer.ts +140 -0
  81. package/src/sync/index.ts +2 -0
  82. package/src/sync/provider.ts +91 -0
  83. package/src/types.ts +110 -73
  84. package/src/util.ts +258 -56
  85. package/src/verify.ts +248 -42
  86. package/tests/_util.ts +132 -97
  87. package/tests/mst.test.ts +269 -122
  88. package/tests/rebase.test.ts +37 -0
  89. package/tests/repo.test.ts +48 -50
  90. package/tests/sync/checkout.test.ts +75 -0
  91. package/tests/sync/diff.test.ts +92 -0
  92. package/tests/sync/narrow.test.ts +149 -0
  93. package/tests/util.test.ts +21 -0
  94. package/tsconfig.build.tsbuildinfo +1 -1
  95. package/tsconfig.json +2 -1
  96. package/src/blockstore/index.ts +0 -2
  97. package/src/blockstore/ipld-store.ts +0 -103
  98. package/src/blockstore/memory-blockstore.ts +0 -49
  99. package/src/sync.ts +0 -38
  100. package/tests/sync.test.ts +0 -129
  101. /package/dist/{blockstore → src/blockstore}/index.d.ts +0 -0
  102. /package/dist/{blockstore → src/blockstore}/ipld-store.d.ts +0 -0
  103. /package/dist/{blockstore → src/blockstore}/memory-blockstore.d.ts +0 -0
  104. /package/dist/{sync.d.ts → src/sync.d.ts} +0 -0
@@ -0,0 +1,75 @@
1
+ import * as crypto from '@atproto/crypto'
2
+ import { CidSet, 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
+ import { streamToBuffer } from '@atproto/common'
8
+ import { CarReader } from '@ipld/car/reader'
9
+
10
+ describe('Checkout Sync', () => {
11
+ let storage: MemoryBlockstore
12
+ let syncStorage: MemoryBlockstore
13
+ let repo: Repo
14
+ let keypair: crypto.Keypair
15
+ let repoData: RepoContents
16
+
17
+ const repoDid = 'did:example:test'
18
+
19
+ beforeAll(async () => {
20
+ storage = new MemoryBlockstore()
21
+ keypair = await crypto.Secp256k1Keypair.create()
22
+ repo = await Repo.create(storage, repoDid, keypair)
23
+ syncStorage = new MemoryBlockstore()
24
+ const filled = await util.fillRepo(repo, keypair, 20)
25
+ repo = filled.repo
26
+ repoData = filled.data
27
+ })
28
+
29
+ it('sync a non-historical repo checkout', async () => {
30
+ const checkoutCar = await streamToBuffer(
31
+ sync.getCheckout(storage, repo.cid),
32
+ )
33
+ const checkout = await sync.loadCheckout(
34
+ syncStorage,
35
+ checkoutCar,
36
+ repoDid,
37
+ keypair.did(),
38
+ )
39
+ const checkoutRepo = await Repo.load(syncStorage, checkout.root)
40
+ const contents = await checkoutRepo.getContents()
41
+ expect(contents).toEqual(repoData)
42
+ expect(checkout.contents).toEqual(repoData)
43
+ })
44
+
45
+ it('does not sync unneeded blocks during checkout', async () => {
46
+ const commitPath = await storage.getCommitPath(repo.cid, null)
47
+ if (!commitPath) {
48
+ throw new Error('Could not get commitPath')
49
+ }
50
+ const hasGenesisCommit = await syncStorage.has(commitPath[0])
51
+ expect(hasGenesisCommit).toBeFalsy()
52
+ })
53
+
54
+ it('does not sync duplicate blocks', async () => {
55
+ const carBytes = await streamToBuffer(sync.getCheckout(storage, repo.cid))
56
+ const car = await CarReader.fromBytes(carBytes)
57
+ const cids = new CidSet()
58
+ for await (const block of car.blocks()) {
59
+ if (cids.has(block.cid)) {
60
+ throw new Error(`duplicate block: :${block.cid.toString()}`)
61
+ }
62
+ cids.add(block.cid)
63
+ }
64
+ })
65
+
66
+ it('throws on a bad signature', async () => {
67
+ const badRepo = await util.addBadCommit(repo, keypair)
68
+ const checkoutCar = await streamToBuffer(
69
+ sync.getCheckout(storage, badRepo.cid),
70
+ )
71
+ await expect(
72
+ sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()),
73
+ ).rejects.toThrow(RepoVerificationError)
74
+ })
75
+ })
@@ -0,0 +1,92 @@
1
+ import * as crypto from '@atproto/crypto'
2
+ import { Repo, RepoContents } from '../../src'
3
+ import { MemoryBlockstore } from '../../src/storage'
4
+ import * as sync from '../../src/sync'
5
+
6
+ import * as util from '../_util'
7
+ import { streamToBuffer } from '@atproto/common'
8
+
9
+ describe('Diff Sync', () => {
10
+ let storage: MemoryBlockstore
11
+ let syncStorage: MemoryBlockstore
12
+ let repo: Repo
13
+ let keypair: crypto.Keypair
14
+ let repoData: RepoContents
15
+
16
+ const repoDid = 'did:example:test'
17
+
18
+ beforeAll(async () => {
19
+ storage = new MemoryBlockstore()
20
+ keypair = await crypto.Secp256k1Keypair.create()
21
+ repo = await Repo.create(storage, repoDid, keypair)
22
+ syncStorage = new MemoryBlockstore()
23
+ })
24
+
25
+ let syncRepo: Repo
26
+
27
+ it('syncs an empty repo', async () => {
28
+ const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
29
+ const loaded = await sync.loadFullRepo(
30
+ syncStorage,
31
+ car,
32
+ repoDid,
33
+ keypair.did(),
34
+ )
35
+ syncRepo = await Repo.load(syncStorage, loaded.root)
36
+ const data = await syncRepo.data.list(10)
37
+ expect(data.length).toBe(0)
38
+ })
39
+
40
+ it('syncs a repo that is starting from scratch', async () => {
41
+ const filled = await util.fillRepo(repo, keypair, 100)
42
+ repo = filled.repo
43
+ repoData = filled.data
44
+
45
+ const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
46
+ const loaded = await sync.loadFullRepo(
47
+ syncStorage,
48
+ car,
49
+ repoDid,
50
+ keypair.did(),
51
+ )
52
+ syncRepo = await Repo.load(syncStorage, loaded.root)
53
+ const contents = await syncRepo.getContents()
54
+ expect(contents).toEqual(repoData)
55
+ await util.verifyRepoDiff(loaded.writeLog, {}, repoData)
56
+ })
57
+
58
+ it('syncs a repo that is behind', async () => {
59
+ // add more to providers's repo & have consumer catch up
60
+ const beforeData = structuredClone(repoData)
61
+ const edited = await util.editRepo(repo, repoData, keypair, {
62
+ adds: 20,
63
+ updates: 20,
64
+ deletes: 20,
65
+ })
66
+ repo = edited.repo
67
+ repoData = edited.data
68
+ const diffCar = await streamToBuffer(
69
+ sync.getCommits(storage, repo.cid, syncRepo.cid),
70
+ )
71
+ const loaded = await sync.loadDiff(
72
+ syncRepo,
73
+ diffCar,
74
+ repoDid,
75
+ keypair.did(),
76
+ )
77
+ syncRepo = await Repo.load(syncStorage, loaded.root)
78
+ const contents = await syncRepo.getContents()
79
+ expect(contents).toEqual(repoData)
80
+ await util.verifyRepoDiff(loaded.writeLog, beforeData, repoData)
81
+ })
82
+
83
+ it('throws on a bad signature', async () => {
84
+ const badRepo = await util.addBadCommit(repo, keypair)
85
+ const diffCar = await streamToBuffer(
86
+ sync.getCommits(storage, badRepo.cid, syncRepo.cid),
87
+ )
88
+ await expect(
89
+ sync.loadDiff(syncRepo, diffCar, repoDid, keypair.did()),
90
+ ).rejects.toThrow('Invalid signature on commit')
91
+ })
92
+ })
@@ -0,0 +1,149 @@
1
+ import { TID, streamToBuffer } from '@atproto/common'
2
+ import * as crypto from '@atproto/crypto'
3
+ import { RecordClaim, Repo, RepoContents } from '../../src'
4
+ import { MemoryBlockstore } from '../../src/storage'
5
+ import * as verify from '../../src/verify'
6
+ import * as sync from '../../src/sync'
7
+
8
+ import * as util from '../_util'
9
+
10
+ describe('Narrow Sync', () => {
11
+ let storage: MemoryBlockstore
12
+ let repo: Repo
13
+ let keypair: crypto.Keypair
14
+ let repoData: RepoContents
15
+
16
+ const repoDid = 'did:example:test'
17
+
18
+ beforeAll(async () => {
19
+ storage = new MemoryBlockstore()
20
+ keypair = await crypto.Secp256k1Keypair.create()
21
+ repo = await Repo.create(storage, repoDid, keypair)
22
+ const filled = await util.fillRepo(repo, keypair, 5)
23
+ repo = filled.repo
24
+ repoData = filled.data
25
+ })
26
+
27
+ const getProofs = async (claims: RecordClaim[]) => {
28
+ return streamToBuffer(sync.getRecords(storage, repo.cid, claims))
29
+ }
30
+
31
+ const doVerify = (proofs: Uint8Array, claims: RecordClaim[]) => {
32
+ return verify.verifyProofs(proofs, claims, repoDid, keypair.did())
33
+ }
34
+
35
+ it('verifies valid records', async () => {
36
+ const claims = util.contentsToClaims(repoData)
37
+ const proofs = await getProofs(claims)
38
+ const results = await doVerify(proofs, claims)
39
+ expect(results.verified.length).toBeGreaterThan(0)
40
+ expect(results.verified).toEqual(claims)
41
+ expect(results.unverified.length).toBe(0)
42
+ })
43
+
44
+ it('verifies record nonexistence', async () => {
45
+ const claims: RecordClaim[] = [
46
+ {
47
+ collection: util.testCollections[0],
48
+ rkey: TID.nextStr(), // does not exist
49
+ record: null,
50
+ },
51
+ ]
52
+ const proofs = await getProofs(claims)
53
+ const results = await doVerify(proofs, claims)
54
+ expect(results.verified.length).toBeGreaterThan(0)
55
+ expect(results.verified).toEqual(claims)
56
+ expect(results.unverified.length).toBe(0)
57
+ })
58
+
59
+ it('does not verify a record that doesnt exist', async () => {
60
+ const realClaims = util.contentsToClaims(repoData)
61
+ const claims: RecordClaim[] = [
62
+ {
63
+ ...realClaims[0],
64
+ rkey: TID.nextStr(),
65
+ },
66
+ ]
67
+ const proofs = await getProofs(claims)
68
+ const results = await doVerify(proofs, claims)
69
+ expect(results.verified.length).toBe(0)
70
+ expect(results.unverified.length).toBeGreaterThan(0)
71
+ expect(results.unverified).toEqual(claims)
72
+ })
73
+
74
+ it('does not verify an invalid record at a real path', async () => {
75
+ const realClaims = util.contentsToClaims(repoData)
76
+ const claims: RecordClaim[] = [
77
+ {
78
+ ...realClaims[0],
79
+ record: util.generateObject(),
80
+ },
81
+ ]
82
+ const proofs = await getProofs(claims)
83
+ const results = await doVerify(proofs, claims)
84
+ expect(results.verified.length).toBe(0)
85
+ expect(results.unverified.length).toBeGreaterThan(0)
86
+ expect(results.unverified).toEqual(claims)
87
+ })
88
+
89
+ it('does not verify a delete where the record does exist', async () => {
90
+ const realClaims = util.contentsToClaims(repoData)
91
+ const claims: RecordClaim[] = [
92
+ {
93
+ collection: realClaims[0].collection,
94
+ rkey: realClaims[0].rkey,
95
+ record: null,
96
+ },
97
+ ]
98
+ const proofs = await getProofs(claims)
99
+ const results = await doVerify(proofs, claims)
100
+ expect(results.verified.length).toBe(0)
101
+ expect(results.unverified.length).toBeGreaterThan(0)
102
+ expect(results.unverified).toEqual(claims)
103
+ })
104
+
105
+ it('can determine record proofs from car file', async () => {
106
+ const possible = util.contentsToClaims(repoData)
107
+ const claims = [
108
+ //random sampling of records
109
+ possible[0],
110
+ possible[4],
111
+ possible[5],
112
+ possible[8],
113
+ ]
114
+ const proofs = await getProofs(claims)
115
+ const records = await verify.verifyRecords(proofs, repoDid, keypair.did())
116
+ for (const record of records) {
117
+ const foundClaim = claims.find(
118
+ (claim) =>
119
+ claim.collection === record.collection && claim.rkey === record.rkey,
120
+ )
121
+ if (!foundClaim) {
122
+ throw new Error('Could not find record for claim')
123
+ }
124
+ expect(foundClaim.record).toEqual(
125
+ repoData[record.collection][record.rkey],
126
+ )
127
+ }
128
+ })
129
+
130
+ it('verifyRecords throws on a bad signature', async () => {
131
+ const badRepo = await util.addBadCommit(repo, keypair)
132
+ const claims = util.contentsToClaims(repoData)
133
+ const proofs = await streamToBuffer(
134
+ sync.getRecords(storage, badRepo.cid, claims),
135
+ )
136
+ const fn = verify.verifyRecords(proofs, repoDid, keypair.did())
137
+ await expect(fn).rejects.toThrow(verify.RepoVerificationError)
138
+ })
139
+
140
+ it('verifyProofs throws on a bad signature', async () => {
141
+ const badRepo = await util.addBadCommit(repo, keypair)
142
+ const claims = util.contentsToClaims(repoData)
143
+ const proofs = await streamToBuffer(
144
+ sync.getRecords(storage, badRepo.cid, claims),
145
+ )
146
+ const fn = verify.verifyProofs(proofs, claims, repoDid, keypair.did())
147
+ await expect(fn).rejects.toThrow(verify.RepoVerificationError)
148
+ })
149
+ })
@@ -0,0 +1,21 @@
1
+ import { dataToCborBlock, wait } from '@atproto/common'
2
+ import { writeCar } from '../src'
3
+
4
+ describe('Utils', () => {
5
+ describe('writeCar()', () => {
6
+ it('propagates errors', async () => {
7
+ const iterate = async () => {
8
+ const iter = writeCar(null, async (car) => {
9
+ await wait(1)
10
+ const block = await dataToCborBlock({ test: 1 })
11
+ await car.put(block)
12
+ throw new Error('Oops!')
13
+ })
14
+ for await (const bytes of iter) {
15
+ // no-op
16
+ }
17
+ }
18
+ await expect(iterate).rejects.toThrow('Oops!')
19
+ })
20
+ })
21
+ })