@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/_util.ts CHANGED
@@ -1,38 +1,42 @@
1
+ import fs from 'fs'
1
2
  import { CID } from 'multiformats'
2
- import { TID } from '@atproto/common'
3
- import * as auth from '@atproto/auth'
4
- import IpldStore from '../src/blockstore/ipld-store'
3
+ import { TID, dataToCborBlock } from '@atproto/common'
4
+ import * as crypto from '@atproto/crypto'
5
5
  import { Repo } from '../src/repo'
6
- import { MemoryBlockstore } from '../src/blockstore'
7
- import { DataDiff, MST } from '../src/mst'
8
- import fs from 'fs'
9
- import { RecordWriteOp } from '../src'
6
+ import { RepoStorage } from '../src/storage'
7
+ import { MST } from '../src/mst'
8
+ import {
9
+ BlockMap,
10
+ collapseWriteLog,
11
+ CollectionContents,
12
+ RecordWriteOp,
13
+ RepoContents,
14
+ RecordPath,
15
+ WriteLog,
16
+ WriteOpAction,
17
+ RecordClaim,
18
+ Commit,
19
+ } from '../src'
20
+ import { Keypair, randomBytes } from '@atproto/crypto'
10
21
 
11
22
  type IdMapping = Record<string, CID>
12
23
 
13
- const fakeStore = new MemoryBlockstore()
14
-
15
- export const randomCid = async (store: IpldStore = fakeStore): Promise<CID> => {
16
- const str = randomStr(50)
17
- return store.stage({ test: str })
18
- }
19
-
20
- export const generateBulkTids = (count: number): TID[] => {
21
- const ids: TID[] = []
22
- for (let i = 0; i < count; i++) {
23
- ids.push(TID.next())
24
+ export const randomCid = async (storage?: RepoStorage): Promise<CID> => {
25
+ const block = await dataToCborBlock({ test: randomStr(50) })
26
+ if (storage) {
27
+ await storage.putBlock(block.cid, block.bytes)
24
28
  }
25
- return ids
29
+ return block.cid
26
30
  }
27
31
 
28
- export const generateBulkTidMapping = async (
32
+ export const generateBulkDataKeys = async (
29
33
  count: number,
30
- blockstore: IpldStore = fakeStore,
34
+ blockstore?: RepoStorage,
31
35
  ): Promise<IdMapping> => {
32
- const ids = generateBulkTids(count)
33
36
  const obj: IdMapping = {}
34
- for (const id of ids) {
35
- obj[id.toString()] = await randomCid(blockstore)
37
+ for (let i = 0; i < count; i++) {
38
+ const key = `com.example.record/${TID.nextStr()}`
39
+ obj[key] = await randomCid(blockstore)
36
40
  }
37
41
  return obj
38
42
  }
@@ -76,32 +80,29 @@ export const generateObject = (): Record<string, string> => {
76
80
 
77
81
  export const testCollections = ['com.example.posts', 'com.example.likes']
78
82
 
79
- export type CollectionData = Record<string, unknown>
80
- export type RepoData = Record<string, CollectionData>
81
-
82
83
  export const fillRepo = async (
83
84
  repo: Repo,
84
- authStore: auth.AuthStore,
85
+ keypair: crypto.Keypair,
85
86
  itemsPerCollection: number,
86
- ): Promise<{ repo: Repo; data: RepoData }> => {
87
- const repoData: RepoData = {}
87
+ ): Promise<{ repo: Repo; data: RepoContents }> => {
88
+ const repoData: RepoContents = {}
88
89
  const writes: RecordWriteOp[] = []
89
90
  for (const collName of testCollections) {
90
- const collData: CollectionData = {}
91
+ const collData: CollectionContents = {}
91
92
  for (let i = 0; i < itemsPerCollection; i++) {
92
93
  const object = generateObject()
93
94
  const rkey = TID.nextStr()
94
95
  collData[rkey] = object
95
96
  writes.push({
96
- action: 'create',
97
+ action: WriteOpAction.Create,
97
98
  collection: collName,
98
99
  rkey,
99
- value: object,
100
+ record: object,
100
101
  })
101
102
  }
102
103
  repoData[collName] = collData
103
104
  }
104
- const updated = await repo.stageUpdate(writes).createCommit(authStore)
105
+ const updated = await repo.applyWrites(writes, keypair)
105
106
  return {
106
107
  repo: updated,
107
108
  data: repoData,
@@ -110,17 +111,16 @@ export const fillRepo = async (
110
111
 
111
112
  export const editRepo = async (
112
113
  repo: Repo,
113
- prevData: RepoData,
114
- authStore: auth.AuthStore,
114
+ prevData: RepoContents,
115
+ keypair: crypto.Keypair,
115
116
  params: {
116
117
  adds?: number
117
118
  updates?: number
118
119
  deletes?: number
119
120
  },
120
- ): Promise<{ repo: Repo; data: RepoData }> => {
121
+ ): Promise<{ repo: Repo; data: RepoContents }> => {
121
122
  const { adds = 0, updates = 0, deletes = 0 } = params
122
- const repoData: RepoData = {}
123
- const writes: RecordWriteOp[] = []
123
+ const repoData: RepoContents = {}
124
124
  for (const collName of testCollections) {
125
125
  const collData = prevData[collName]
126
126
  const shuffled = shuffle(Object.entries(collData))
@@ -129,94 +129,129 @@ export const editRepo = async (
129
129
  const object = generateObject()
130
130
  const rkey = TID.nextStr()
131
131
  collData[rkey] = object
132
- writes.push({
133
- action: 'create',
134
- collection: collName,
135
- rkey,
136
- value: object,
137
- })
132
+ repo = await repo.applyWrites(
133
+ {
134
+ action: WriteOpAction.Create,
135
+ collection: collName,
136
+ rkey,
137
+ record: object,
138
+ },
139
+ keypair,
140
+ )
138
141
  }
139
142
 
140
143
  const toUpdate = shuffled.slice(0, updates)
141
144
  for (let i = 0; i < toUpdate.length; i++) {
142
145
  const object = generateObject()
143
146
  const rkey = toUpdate[i][0]
144
- writes.push({
145
- action: 'update',
146
- collection: collName,
147
- rkey,
148
- value: object,
149
- })
147
+ repo = await repo.applyWrites(
148
+ {
149
+ action: WriteOpAction.Update,
150
+ collection: collName,
151
+ rkey,
152
+ record: object,
153
+ },
154
+ keypair,
155
+ )
150
156
  collData[rkey] = object
151
157
  }
152
158
 
153
159
  const toDelete = shuffled.slice(updates, deletes)
154
160
  for (let i = 0; i < toDelete.length; i++) {
155
161
  const rkey = toDelete[i][0]
156
- writes.push({
157
- action: 'delete',
158
- collection: collName,
159
- rkey,
160
- })
162
+ repo = await repo.applyWrites(
163
+ {
164
+ action: WriteOpAction.Delete,
165
+ collection: collName,
166
+ rkey,
167
+ },
168
+ keypair,
169
+ )
161
170
  delete collData[rkey]
162
171
  }
163
172
  repoData[collName] = collData
164
173
  }
165
- const updated = await repo.stageUpdate(writes).createCommit(authStore)
166
174
  return {
167
- repo: updated,
175
+ repo,
168
176
  data: repoData,
169
177
  }
170
178
  }
171
179
 
172
- export const checkRepo = async (repo: Repo, data: RepoData): Promise<void> => {
173
- for (const collName of Object.keys(data)) {
174
- const collData = data[collName]
175
- for (const rkey of Object.keys(collData)) {
176
- const record = await repo.getRecord(collName, rkey)
177
- expect(record).toEqual(collData[rkey])
178
- }
179
- }
180
- }
181
-
182
- export const checkRepoDiff = async (
183
- diff: DataDiff,
184
- before: RepoData,
185
- after: RepoData,
180
+ export const verifyRepoDiff = async (
181
+ writeLog: WriteLog,
182
+ before: RepoContents,
183
+ after: RepoContents,
186
184
  ): Promise<void> => {
187
- const getObjectCid = async (
188
- key: string,
189
- data: RepoData,
190
- ): Promise<CID | undefined> => {
191
- const parts = key.split('/')
192
- const collection = parts[0]
193
- const obj = (data[collection] || {})[parts[1]]
194
- return obj === undefined ? undefined : fakeStore.stage(obj)
185
+ const getVal = (op: RecordWriteOp, data: RepoContents) => {
186
+ return (data[op.collection] || {})[op.rkey]
195
187
  }
188
+ const ops = await collapseWriteLog(writeLog)
196
189
 
197
- for (const add of diff.addList()) {
198
- const beforeCid = await getObjectCid(add.key, before)
199
- const afterCid = await getObjectCid(add.key, after)
200
-
201
- expect(beforeCid).toBeUndefined()
202
- expect(afterCid).toEqual(add.cid)
190
+ for (const op of ops) {
191
+ if (op.action === WriteOpAction.Create) {
192
+ expect(getVal(op, before)).toBeUndefined()
193
+ expect(getVal(op, after)).toEqual(op.record)
194
+ } else if (op.action === WriteOpAction.Update) {
195
+ expect(getVal(op, before)).toBeDefined()
196
+ expect(getVal(op, after)).toEqual(op.record)
197
+ } else if (op.action === WriteOpAction.Delete) {
198
+ expect(getVal(op, before)).toBeDefined()
199
+ expect(getVal(op, after)).toBeUndefined()
200
+ } else {
201
+ throw new Error('unexpected op type')
202
+ }
203
203
  }
204
+ }
204
205
 
205
- for (const update of diff.updateList()) {
206
- const beforeCid = await getObjectCid(update.key, before)
207
- const afterCid = await getObjectCid(update.key, after)
208
-
209
- expect(beforeCid).toEqual(update.prev)
210
- expect(afterCid).toEqual(update.cid)
206
+ export const contentsToClaims = (contents: RepoContents): RecordClaim[] => {
207
+ const claims: RecordClaim[] = []
208
+ for (const coll of Object.keys(contents)) {
209
+ for (const rkey of Object.keys(contents[coll])) {
210
+ claims.push({
211
+ collection: coll,
212
+ rkey: rkey,
213
+ record: contents[coll][rkey],
214
+ })
215
+ }
211
216
  }
217
+ return claims
218
+ }
219
+
220
+ export const pathsForOps = (ops: RecordWriteOp[]): RecordPath[] =>
221
+ ops.map((op) => ({ collection: op.collection, rkey: op.rkey }))
212
222
 
213
- for (const del of diff.deleteList()) {
214
- const beforeCid = await getObjectCid(del.key, before)
215
- const afterCid = await getObjectCid(del.key, after)
223
+ export const saveMst = async (storage: RepoStorage, mst: MST): Promise<CID> => {
224
+ const diff = await mst.getUnstoredBlocks()
225
+ await storage.putMany(diff.blocks)
226
+ return diff.root
227
+ }
216
228
 
217
- expect(beforeCid).toEqual(del.cid)
218
- expect(afterCid).toBeUndefined()
229
+ // Creating repo
230
+ // -------------------
231
+ export const addBadCommit = async (
232
+ repo: Repo,
233
+ keypair: Keypair,
234
+ ): Promise<Repo> => {
235
+ const obj = generateObject()
236
+ const blocks = new BlockMap()
237
+ const cid = await blocks.add(obj)
238
+ const updatedData = await repo.data.add(`com.example.test/${TID.next()}`, cid)
239
+ const unstoredData = await updatedData.getUnstoredBlocks()
240
+ blocks.addMap(unstoredData.blocks)
241
+ // we generate a bad sig by signing some other data
242
+ const commit: Commit = {
243
+ ...repo.commit,
244
+ prev: repo.cid,
245
+ data: unstoredData.root,
246
+ sig: await keypair.sign(randomBytes(256)),
219
247
  }
248
+ const commitCid = await blocks.add(commit)
249
+ await repo.storage.applyCommit({
250
+ commit: commitCid,
251
+ prev: repo.cid,
252
+ blocks: blocks,
253
+ })
254
+ return await Repo.load(repo.storage, commitCid)
220
255
  }
221
256
 
222
257
  // Logging