@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/src/parse.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { check, cborDecode } from '@atproto/common'
2
+ import { RepoRecord } from '@atproto/lexicon'
3
+ import { CID } from 'multiformats/cid'
4
+ import BlockMap from './block-map'
5
+ import { MissingBlockError, UnexpectedObjectError } from './error'
6
+ import { cborToLexRecord } from './util'
7
+
8
+ export const getAndParseRecord = async (
9
+ blocks: BlockMap,
10
+ cid: CID,
11
+ ): Promise<{ record: RepoRecord; bytes: Uint8Array }> => {
12
+ const bytes = blocks.get(cid)
13
+ if (!bytes) {
14
+ throw new MissingBlockError(cid, 'record')
15
+ }
16
+ const record = await cborToLexRecord(bytes)
17
+ return { record, bytes }
18
+ }
19
+
20
+ export const getAndParseByDef = async <T>(
21
+ blocks: BlockMap,
22
+ cid: CID,
23
+ def: check.Def<T>,
24
+ ): Promise<{ obj: T; bytes: Uint8Array }> => {
25
+ const bytes = blocks.get(cid)
26
+ if (!bytes) {
27
+ throw new MissingBlockError(cid, def.name)
28
+ }
29
+ return parseObjByDef(bytes, cid, def)
30
+ }
31
+
32
+ export const parseObjByDef = <T>(
33
+ bytes: Uint8Array,
34
+ cid: CID,
35
+ def: check.Def<T>,
36
+ ): { obj: T; bytes: Uint8Array } => {
37
+ const obj = cborDecode(bytes)
38
+ const res = def.schema.safeParse(obj)
39
+ if (res.success) {
40
+ return { obj: res.data, bytes }
41
+ } else {
42
+ throw new UnexpectedObjectError(cid, def.name)
43
+ }
44
+ }
@@ -0,0 +1,75 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { Commit, def, DataStore, RepoContents } from './types'
3
+ import { ReadableBlockstore } from './storage'
4
+ import { MST } from './mst'
5
+ import log from './logger'
6
+ import * as util from './util'
7
+ import * as parse from './parse'
8
+ import { MissingBlocksError } from './error'
9
+
10
+ type Params = {
11
+ storage: ReadableBlockstore
12
+ data: DataStore
13
+ commit: Commit
14
+ cid: CID
15
+ }
16
+
17
+ export class ReadableRepo {
18
+ storage: ReadableBlockstore
19
+ data: DataStore
20
+ commit: Commit
21
+ cid: CID
22
+
23
+ constructor(params: Params) {
24
+ this.storage = params.storage
25
+ this.data = params.data
26
+ this.commit = params.commit
27
+ this.cid = params.cid
28
+ }
29
+
30
+ static async load(storage: ReadableBlockstore, commitCid: CID) {
31
+ const commit = await storage.readObj(commitCid, def.commit)
32
+ const data = await MST.load(storage, commit.data)
33
+ log.info({ did: commit.did }, 'loaded repo for')
34
+ return new ReadableRepo({
35
+ storage,
36
+ data,
37
+ commit,
38
+ cid: commitCid,
39
+ })
40
+ }
41
+
42
+ get did(): string {
43
+ return this.commit.did
44
+ }
45
+
46
+ get version(): number {
47
+ return this.commit.version
48
+ }
49
+
50
+ async getRecord(collection: string, rkey: string): Promise<unknown | null> {
51
+ const dataKey = collection + '/' + rkey
52
+ const cid = await this.data.get(dataKey)
53
+ if (!cid) return null
54
+ return this.storage.readObj(cid, def.unknown)
55
+ }
56
+
57
+ async getContents(): Promise<RepoContents> {
58
+ const entries = await this.data.list()
59
+ const cids = entries.map((e) => e.value)
60
+ const { blocks, missing } = await this.storage.getBlocks(cids)
61
+ if (missing.length > 0) {
62
+ throw new MissingBlocksError('getContents record', missing)
63
+ }
64
+ const contents: RepoContents = {}
65
+ for (const entry of entries) {
66
+ const { collection, rkey } = util.parseDataKey(entry.key)
67
+ contents[collection] ??= {}
68
+ const parsed = await parse.getAndParseRecord(blocks, entry.value)
69
+ contents[collection][rkey] = parsed.record
70
+ }
71
+ return contents
72
+ }
73
+ }
74
+
75
+ export default ReadableRepo
package/src/repo.ts CHANGED
@@ -1,311 +1,181 @@
1
1
  import { CID } from 'multiformats/cid'
2
- import { CarWriter } from '@ipld/car'
3
- import { BlockWriter } from '@ipld/car/writer'
2
+ import * as crypto from '@atproto/crypto'
4
3
  import {
5
- RepoRoot,
6
4
  Commit,
7
5
  def,
8
6
  DataStore,
9
- RepoMeta,
10
7
  RecordCreateOp,
11
8
  RecordWriteOp,
9
+ CommitData,
10
+ WriteOpAction,
12
11
  } from './types'
13
- import { streamToArray } from '@atproto/common'
14
- import IpldStore from './blockstore/ipld-store'
15
- import * as auth from '@atproto/auth'
12
+ import { RepoStorage } from './storage'
16
13
  import { MST } from './mst'
14
+ import DataDiff from './data-diff'
17
15
  import log from './logger'
16
+ import BlockMap from './block-map'
17
+ import { ReadableRepo } from './readable-repo'
18
18
  import * as util from './util'
19
19
 
20
20
  type Params = {
21
- blockstore: IpldStore
21
+ storage: RepoStorage
22
22
  data: DataStore
23
23
  commit: Commit
24
- root: RepoRoot
25
- meta: RepoMeta
26
24
  cid: CID
27
- stagedWrites: RecordWriteOp[]
28
25
  }
29
26
 
30
- export class Repo {
31
- blockstore: IpldStore
32
- data: DataStore
33
- commit: Commit
34
- root: RepoRoot
35
- meta: RepoMeta
36
- cid: CID
37
- stagedWrites: RecordWriteOp[]
27
+ export class Repo extends ReadableRepo {
28
+ storage: RepoStorage
38
29
 
39
30
  constructor(params: Params) {
40
- this.blockstore = params.blockstore
41
- this.data = params.data
42
- this.commit = params.commit
43
- this.root = params.root
44
- this.meta = params.meta
45
- this.cid = params.cid
46
- this.stagedWrites = params.stagedWrites
31
+ super(params)
32
+ this.storage = params.storage
47
33
  }
48
34
 
49
- static async create(
50
- blockstore: IpldStore,
35
+ static async formatInitCommit(
36
+ storage: RepoStorage,
51
37
  did: string,
52
- authStore: auth.AuthStore,
53
- initialRecords: RecordCreateOp[] = [],
54
- ): Promise<Repo> {
55
- let tokenCid: CID | null = null
56
- if (!(await authStore.canSignForDid(did))) {
57
- const foundUcan = await authStore.findUcan(auth.maintenanceCap(did))
58
- if (foundUcan === null) {
59
- throw new Error(`No valid Ucan for creating repo`)
60
- }
61
- tokenCid = await blockstore.stage(auth.encodeUcan(foundUcan))
62
- }
63
-
64
- let data = await MST.create(blockstore)
65
- for (const write of initialRecords) {
66
- const cid = await blockstore.stage(write.value)
67
- const dataKey = write.collection + '/' + write.rkey
38
+ keypair: crypto.Keypair,
39
+ initialWrites: RecordCreateOp[] = [],
40
+ ): Promise<CommitData> {
41
+ const newBlocks = new BlockMap()
42
+
43
+ let data = await MST.create(storage)
44
+ for (const record of initialWrites) {
45
+ const cid = await newBlocks.add(record.record)
46
+ const dataKey = util.formatDataKey(record.collection, record.rkey)
68
47
  data = await data.add(dataKey, cid)
69
48
  }
70
- const dataCid = await data.stage()
71
49
 
72
- const meta: RepoMeta = {
73
- did,
74
- version: 1,
75
- datastore: 'mst',
76
- }
77
- const metaCid = await blockstore.stage(meta)
50
+ const unstoredData = await data.getUnstoredBlocks()
51
+ newBlocks.addMap(unstoredData.blocks)
52
+
53
+ const commit = await util.signCommit(
54
+ {
55
+ did,
56
+ version: 2,
57
+ prev: null,
58
+ data: unstoredData.root,
59
+ },
60
+ keypair,
61
+ )
62
+ const commitCid = await newBlocks.add(commit)
78
63
 
79
- const root: RepoRoot = {
80
- meta: metaCid,
64
+ return {
65
+ commit: commitCid,
81
66
  prev: null,
82
- auth_token: tokenCid,
83
- data: dataCid,
67
+ blocks: newBlocks,
84
68
  }
69
+ }
85
70
 
86
- const rootCid = await blockstore.stage(root)
87
- const commit: Commit = {
88
- root: rootCid,
89
- sig: await authStore.sign(rootCid.bytes),
90
- }
91
-
92
- const cid = await blockstore.stage(commit)
93
-
94
- await blockstore.saveStaged()
71
+ static async createFromCommit(
72
+ storage: RepoStorage,
73
+ commit: CommitData,
74
+ ): Promise<Repo> {
75
+ await storage.applyCommit(commit)
76
+ return Repo.load(storage, commit.commit)
77
+ }
95
78
 
96
- log.info({ did }, `created repo`)
97
- return new Repo({
98
- blockstore,
99
- data,
100
- commit,
101
- root,
102
- meta,
103
- cid,
104
- stagedWrites: [],
105
- })
79
+ static async create(
80
+ storage: RepoStorage,
81
+ did: string,
82
+ keypair: crypto.Keypair,
83
+ initialWrites: RecordCreateOp[] = [],
84
+ ): Promise<Repo> {
85
+ const commit = await Repo.formatInitCommit(
86
+ storage,
87
+ did,
88
+ keypair,
89
+ initialWrites,
90
+ )
91
+ return Repo.createFromCommit(storage, commit)
106
92
  }
107
93
 
108
- static async load(blockstore: IpldStore, cid: CID) {
109
- const commit = await blockstore.get(cid, def.commit)
110
- const root = await blockstore.get(commit.root, def.repoRoot)
111
- const meta = await blockstore.get(root.meta, def.repoMeta)
112
- const data = await MST.load(blockstore, root.data)
113
- log.info({ did: meta.did }, 'loaded repo for')
94
+ static async load(storage: RepoStorage, cid?: CID) {
95
+ const commitCid = cid || (await storage.getHead())
96
+ if (!commitCid) {
97
+ throw new Error('No cid provided and none in storage')
98
+ }
99
+ const commit = await storage.readObj(commitCid, def.commit)
100
+ const data = await MST.load(storage, commit.data)
101
+ log.info({ did: commit.did }, 'loaded repo for')
114
102
  return new Repo({
115
- blockstore,
103
+ storage,
116
104
  data,
117
105
  commit,
118
- root,
119
- meta,
120
- cid,
121
- stagedWrites: [],
122
- })
123
- }
124
-
125
- private updateRepo(params: Partial<Params>): Repo {
126
- return new Repo({
127
- blockstore: params.blockstore || this.blockstore,
128
- data: params.data || this.data,
129
- commit: params.commit || this.commit,
130
- root: params.root || this.root,
131
- meta: params.meta || this.meta,
132
- cid: params.cid || this.cid,
133
- stagedWrites: params.stagedWrites || this.stagedWrites,
106
+ cid: commitCid,
134
107
  })
135
108
  }
136
109
 
137
- get did(): string {
138
- return this.meta.did
139
- }
140
-
141
- async getRecord(collection: string, rkey: string): Promise<unknown | null> {
142
- const dataKey = collection + '/' + rkey
143
- const cid = await this.data.get(dataKey)
144
- if (!cid) return null
145
- return this.blockstore.getUnchecked(cid)
146
- }
147
-
148
- stageUpdate(write: RecordWriteOp | RecordWriteOp[]): Repo {
149
- const writeArr = Array.isArray(write) ? write : [write]
150
- return this.updateRepo({
151
- stagedWrites: [...this.stagedWrites, ...writeArr],
152
- })
153
- }
110
+ async formatCommit(
111
+ toWrite: RecordWriteOp | RecordWriteOp[],
112
+ keypair: crypto.Keypair,
113
+ ): Promise<CommitData> {
114
+ const writes = Array.isArray(toWrite) ? toWrite : [toWrite]
115
+ const commitBlocks = new BlockMap()
154
116
 
155
- async createCommit(
156
- authStore: auth.AuthStore,
157
- performUpdate?: (prev: CID, curr: CID) => Promise<CID | null>,
158
- ): Promise<Repo> {
159
117
  let data = this.data
160
- for (const write of this.stagedWrites) {
161
- if (write.action === 'create') {
162
- const cid = await this.blockstore.stage(write.value)
118
+ for (const write of writes) {
119
+ if (write.action === WriteOpAction.Create) {
120
+ const cid = await commitBlocks.add(write.record)
163
121
  const dataKey = write.collection + '/' + write.rkey
164
122
  data = await data.add(dataKey, cid)
165
- } else if (write.action === 'update') {
166
- const cid = await this.blockstore.stage(write.value)
123
+ } else if (write.action === WriteOpAction.Update) {
124
+ const cid = await commitBlocks.add(write.record)
167
125
  const dataKey = write.collection + '/' + write.rkey
168
126
  data = await data.update(dataKey, cid)
169
- } else if (write.action === 'delete') {
127
+ } else if (write.action === WriteOpAction.Delete) {
170
128
  const dataKey = write.collection + '/' + write.rkey
171
129
  data = await data.delete(dataKey)
172
130
  }
173
131
  }
174
- const token = (await authStore.canSignForDid(this.did))
175
- ? null
176
- : await util.ucanForOperation(this.data, data, this.did, authStore)
177
- const tokenCid = token ? await this.blockstore.stage(token) : null
178
-
179
- const dataCid = await data.stage()
180
- const root: RepoRoot = {
181
- meta: this.root.meta,
182
- prev: this.cid,
183
- auth_token: tokenCid,
184
- data: dataCid,
185
- }
186
- const rootCid = await this.blockstore.stage(root)
187
- const commit: Commit = {
188
- root: rootCid,
189
- sig: await authStore.sign(rootCid.bytes),
190
- }
191
- const commitCid = await this.blockstore.stage(commit)
192
-
193
- if (performUpdate) {
194
- const rebaseOn = await performUpdate(this.cid, commitCid)
195
- if (rebaseOn) {
196
- await this.blockstore.clearStaged()
197
- const rebaseRepo = await Repo.load(this.blockstore, rebaseOn)
198
- return rebaseRepo.createCommit(authStore, performUpdate)
199
- } else {
200
- await this.blockstore.saveStaged()
201
- }
202
- } else {
203
- await this.blockstore.saveStaged()
204
- }
205
132
 
206
- return this.updateRepo({
207
- cid: commitCid,
208
- root,
209
- commit,
210
- data,
211
- stagedWrites: [],
212
- })
213
- }
214
-
215
- async revert(count: number): Promise<Repo> {
216
- let revertTo = this.cid
217
- for (let i = 0; i < count; i++) {
218
- const commit = await this.blockstore.get(revertTo, def.commit)
219
- const root = await this.blockstore.get(commit.root, def.repoRoot)
220
- if (root.prev === null) {
221
- throw new Error(`Could not revert ${count} commits`)
133
+ const unstoredData = await data.getUnstoredBlocks()
134
+ commitBlocks.addMap(unstoredData.blocks)
135
+
136
+ // ensure we're not missing any blocks that were removed and then readded in this commit
137
+ const diff = await DataDiff.of(data, this.data)
138
+ const found = commitBlocks.getMany(diff.newCidList())
139
+ if (found.missing.length > 0) {
140
+ const fromStorage = await this.storage.getBlocks(found.missing)
141
+ if (fromStorage.missing.length > 0) {
142
+ // this shouldn't ever happen
143
+ throw new Error(
144
+ 'Could not find block for commit in Datastore or storage',
145
+ )
222
146
  }
223
- revertTo = root.prev
147
+ commitBlocks.addMap(fromStorage.blocks)
224
148
  }
225
- return Repo.load(this.blockstore, revertTo)
226
- }
227
149
 
228
- // CAR FILES
229
- // -----------
230
-
231
- async getCarNoHistory(): Promise<Uint8Array> {
232
- return this.openCar((car: BlockWriter) => {
233
- return this.writeCheckoutToCarStream(car)
234
- })
235
- }
236
-
237
- async getDiffCar(to: CID | null): Promise<Uint8Array> {
238
- return this.openCar((car: BlockWriter) => {
239
- return this.writeCommitsToCarStream(car, to, this.cid)
240
- })
241
- }
242
-
243
- async getFullHistory(): Promise<Uint8Array> {
244
- return this.getDiffCar(null)
245
- }
150
+ const commit = await util.signCommit(
151
+ {
152
+ did: this.did,
153
+ version: 2,
154
+ prev: this.cid,
155
+ data: unstoredData.root,
156
+ },
157
+ keypair,
158
+ )
159
+ const commitCid = await commitBlocks.add(commit)
246
160
 
247
- private async openCar(
248
- fn: (car: BlockWriter) => Promise<void>,
249
- ): Promise<Uint8Array> {
250
- const { writer, out } = CarWriter.create([this.cid])
251
- try {
252
- await fn(writer)
253
- } finally {
254
- writer.close()
161
+ return {
162
+ commit: commitCid,
163
+ prev: this.cid,
164
+ blocks: commitBlocks,
255
165
  }
256
- return streamToArray(out)
257
166
  }
258
167
 
259
- async writeCheckoutToCarStream(car: BlockWriter): Promise<void> {
260
- const commit = await this.blockstore.get(this.cid, def.commit)
261
- const root = await this.blockstore.get(commit.root, def.repoRoot)
262
- await this.blockstore.addToCar(car, this.cid)
263
- await this.blockstore.addToCar(car, commit.root)
264
- await this.blockstore.addToCar(car, root.meta)
265
- if (root.auth_token) {
266
- await this.blockstore.addToCar(car, root.auth_token)
267
- }
268
- await this.data.writeToCarStream(car)
168
+ async applyCommit(commitData: CommitData): Promise<Repo> {
169
+ await this.storage.applyCommit(commitData)
170
+ return Repo.load(this.storage, commitData.commit)
269
171
  }
270
172
 
271
- async writeCommitsToCarStream(
272
- car: BlockWriter,
273
- oldestCommit: CID | null,
274
- recentCommit: CID,
275
- ): Promise<void> {
276
- const commitPath = await util.getCommitPath(
277
- this.blockstore,
278
- oldestCommit,
279
- recentCommit,
280
- )
281
- if (commitPath === null) {
282
- throw new Error('Could not find shared history')
283
- }
284
- if (commitPath.length === 0) return
285
- const firstHeadInPath = await Repo.load(this.blockstore, commitPath[0])
286
- // handle the first commit
287
- let prevHead: Repo | null =
288
- firstHeadInPath.root.prev !== null
289
- ? await Repo.load(this.blockstore, firstHeadInPath.root.prev)
290
- : null
291
- for (const commit of commitPath) {
292
- const nextHead = await Repo.load(this.blockstore, commit)
293
- await this.blockstore.addToCar(car, nextHead.cid)
294
- await this.blockstore.addToCar(car, nextHead.commit.root)
295
- await this.blockstore.addToCar(car, nextHead.root.meta)
296
- if (nextHead.root.auth_token) {
297
- await this.blockstore.addToCar(car, nextHead.root.auth_token)
298
- }
299
- if (prevHead === null) {
300
- await nextHead.data.writeToCarStream(car)
301
- } else {
302
- const diff = await prevHead.data.diff(nextHead.data)
303
- await Promise.all(
304
- diff.newCidList().map((cid) => this.blockstore.addToCar(car, cid)),
305
- )
306
- }
307
- prevHead = nextHead
308
- }
173
+ async applyWrites(
174
+ toWrite: RecordWriteOp | RecordWriteOp[],
175
+ keypair: crypto.Keypair,
176
+ ): Promise<Repo> {
177
+ const commit = await this.formatCommit(toWrite, keypair)
178
+ return this.applyCommit(commit)
309
179
  }
310
180
  }
311
181
 
@@ -1 +1,5 @@
1
+ export * from './readable-blockstore'
2
+ export * from './repo-storage'
3
+ export * from './memory-blockstore'
4
+ export * from './sync-storage'
1
5
  export * from './types'
@@ -0,0 +1,114 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { CommitData, def } from '../types'
3
+ import BlockMap from '../block-map'
4
+ import { MST } from '../mst'
5
+ import DataDiff from '../data-diff'
6
+ import { MissingCommitBlocksError } from '../error'
7
+ import RepoStorage from './repo-storage'
8
+
9
+ export class MemoryBlockstore extends RepoStorage {
10
+ blocks: BlockMap
11
+ head: CID | null = null
12
+
13
+ constructor(blocks?: BlockMap) {
14
+ super()
15
+ this.blocks = new BlockMap()
16
+ if (blocks) {
17
+ this.blocks.addMap(blocks)
18
+ }
19
+ }
20
+
21
+ async getHead(): Promise<CID | null> {
22
+ return this.head
23
+ }
24
+
25
+ async getBytes(cid: CID): Promise<Uint8Array | null> {
26
+ return this.blocks.get(cid) || null
27
+ }
28
+
29
+ async has(cid: CID): Promise<boolean> {
30
+ return this.blocks.has(cid)
31
+ }
32
+
33
+ async getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {
34
+ return this.blocks.getMany(cids)
35
+ }
36
+
37
+ async putBlock(cid: CID, block: Uint8Array): Promise<void> {
38
+ this.blocks.set(cid, block)
39
+ }
40
+
41
+ async putMany(blocks: BlockMap): Promise<void> {
42
+ this.blocks.addMap(blocks)
43
+ }
44
+
45
+ async indexCommits(commits: CommitData[]): Promise<void> {
46
+ commits.forEach((commit) => {
47
+ this.blocks.addMap(commit.blocks)
48
+ })
49
+ }
50
+
51
+ async updateHead(cid: CID, _prev: CID | null): Promise<void> {
52
+ this.head = cid
53
+ }
54
+
55
+ async applyCommit(commit: CommitData): Promise<void> {
56
+ this.blocks.addMap(commit.blocks)
57
+ this.head = commit.commit
58
+ }
59
+
60
+ async getCommitPath(
61
+ latest: CID,
62
+ earliest: CID | null,
63
+ ): Promise<CID[] | null> {
64
+ let curr: CID | null = latest
65
+ const path: CID[] = []
66
+ while (curr !== null) {
67
+ path.push(curr)
68
+ const commit = await this.readObj(curr, def.commit)
69
+ if (!earliest && commit.prev === null) {
70
+ return path.reverse()
71
+ } else if (earliest && commit.prev.equals(earliest)) {
72
+ return path.reverse()
73
+ }
74
+ curr = commit.prev
75
+ }
76
+ return null
77
+ }
78
+
79
+ async getBlocksForCommits(
80
+ commits: CID[],
81
+ ): Promise<{ [commit: string]: BlockMap }> {
82
+ const commitData: { [commit: string]: BlockMap } = {}
83
+ let prevData: MST | null = null
84
+ for (const commitCid of commits) {
85
+ const commit = await this.readObj(commitCid, def.commit)
86
+ const data = await MST.load(this, commit.data)
87
+ const diff = await DataDiff.of(data, prevData)
88
+ const { blocks, missing } = await this.getBlocks([
89
+ commitCid,
90
+ ...diff.newCidList(),
91
+ ])
92
+ if (missing.length > 0) {
93
+ throw new MissingCommitBlocksError(commitCid, missing)
94
+ }
95
+ commitData[commitCid.toString()] = blocks
96
+ prevData = data
97
+ }
98
+ return commitData
99
+ }
100
+
101
+ async sizeInBytes(): Promise<number> {
102
+ let total = 0
103
+ this.blocks.forEach((bytes) => {
104
+ total += bytes.byteLength
105
+ })
106
+ return total
107
+ }
108
+
109
+ async destroy(): Promise<void> {
110
+ this.blocks.clear()
111
+ }
112
+ }
113
+
114
+ export default MemoryBlockstore