@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,133 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { CommitData, def, RebaseData } 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
+ import CidSet from '../cid-set'
9
+
10
+ export class MemoryBlockstore extends RepoStorage {
11
+ blocks: BlockMap
12
+ head: CID | null = null
13
+
14
+ constructor(blocks?: BlockMap) {
15
+ super()
16
+ this.blocks = new BlockMap()
17
+ if (blocks) {
18
+ this.blocks.addMap(blocks)
19
+ }
20
+ }
21
+
22
+ async getHead(): Promise<CID | null> {
23
+ return this.head
24
+ }
25
+
26
+ async getBytes(cid: CID): Promise<Uint8Array | null> {
27
+ return this.blocks.get(cid) || null
28
+ }
29
+
30
+ async has(cid: CID): Promise<boolean> {
31
+ return this.blocks.has(cid)
32
+ }
33
+
34
+ async getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {
35
+ return this.blocks.getMany(cids)
36
+ }
37
+
38
+ async putBlock(cid: CID, block: Uint8Array): Promise<void> {
39
+ this.blocks.set(cid, block)
40
+ }
41
+
42
+ async putMany(blocks: BlockMap): Promise<void> {
43
+ this.blocks.addMap(blocks)
44
+ }
45
+
46
+ async indexCommits(commits: CommitData[]): Promise<void> {
47
+ commits.forEach((commit) => {
48
+ this.blocks.addMap(commit.blocks)
49
+ })
50
+ }
51
+
52
+ async updateHead(cid: CID, _prev: CID | null): Promise<void> {
53
+ this.head = cid
54
+ }
55
+
56
+ async applyCommit(commit: CommitData): Promise<void> {
57
+ this.blocks.addMap(commit.blocks)
58
+ this.head = commit.commit
59
+ }
60
+
61
+ async getCommitPath(
62
+ latest: CID,
63
+ earliest: CID | null,
64
+ ): Promise<CID[] | null> {
65
+ let curr: CID | null = latest
66
+ const path: CID[] = []
67
+ while (curr !== null) {
68
+ path.push(curr)
69
+ const commit = await this.readObj(curr, def.commit)
70
+ if (!earliest && commit.prev === null) {
71
+ return path.reverse()
72
+ } else if (earliest && commit.prev.equals(earliest)) {
73
+ return path.reverse()
74
+ }
75
+ curr = commit.prev
76
+ }
77
+ return null
78
+ }
79
+
80
+ async getBlocksForCommits(
81
+ commits: CID[],
82
+ ): Promise<{ [commit: string]: BlockMap }> {
83
+ const commitData: { [commit: string]: BlockMap } = {}
84
+ let prevData: MST | null = null
85
+ for (const commitCid of commits) {
86
+ const commit = await this.readObj(commitCid, def.commit)
87
+ const data = await MST.load(this, commit.data)
88
+ const diff = await DataDiff.of(data, prevData)
89
+ const { blocks, missing } = await this.getBlocks([
90
+ commitCid,
91
+ ...diff.newCidList(),
92
+ ])
93
+ if (missing.length > 0) {
94
+ throw new MissingCommitBlocksError(commitCid, missing)
95
+ }
96
+ commitData[commitCid.toString()] = blocks
97
+ prevData = data
98
+ }
99
+ return commitData
100
+ }
101
+
102
+ async applyRebase(rebase: RebaseData) {
103
+ this.putMany(rebase.blocks)
104
+ const allCids = new CidSet([
105
+ ...rebase.preservedCids,
106
+ ...rebase.blocks.cids(),
107
+ ])
108
+ const toDelete: CID[] = []
109
+ this.blocks.forEach((_bytes, cid) => {
110
+ if (!allCids.has(cid)) {
111
+ toDelete.push(cid)
112
+ }
113
+ })
114
+ for (const cid of toDelete) {
115
+ this.blocks.delete(cid)
116
+ }
117
+ await this.updateHead(rebase.commit, null)
118
+ }
119
+
120
+ async sizeInBytes(): Promise<number> {
121
+ let total = 0
122
+ this.blocks.forEach((bytes) => {
123
+ total += bytes.byteLength
124
+ })
125
+ return total
126
+ }
127
+
128
+ async destroy(): Promise<void> {
129
+ this.blocks.clear()
130
+ }
131
+ }
132
+
133
+ export default MemoryBlockstore
@@ -0,0 +1,56 @@
1
+ import { check } from '@atproto/common'
2
+ import { RepoRecord } from '@atproto/lexicon'
3
+ import { CID } from 'multiformats/cid'
4
+ import BlockMap from '../block-map'
5
+ import { MissingBlockError } from '../error'
6
+ import * as parse from '../parse'
7
+ import { cborToLexRecord } from '../util'
8
+
9
+ export abstract class ReadableBlockstore {
10
+ abstract getBytes(cid: CID): Promise<Uint8Array | null>
11
+ abstract has(cid: CID): Promise<boolean>
12
+ abstract getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }>
13
+
14
+ async attemptRead<T>(
15
+ cid: CID,
16
+ def: check.Def<T>,
17
+ ): Promise<{ obj: T; bytes: Uint8Array } | null> {
18
+ const bytes = await this.getBytes(cid)
19
+ if (!bytes) return null
20
+ return parse.parseObjByDef(bytes, cid, def)
21
+ }
22
+
23
+ async readObjAndBytes<T>(
24
+ cid: CID,
25
+ def: check.Def<T>,
26
+ ): Promise<{ obj: T; bytes: Uint8Array }> {
27
+ const read = await this.attemptRead(cid, def)
28
+ if (!read) {
29
+ throw new MissingBlockError(cid, def.name)
30
+ }
31
+ return read
32
+ }
33
+
34
+ async readObj<T>(cid: CID, def: check.Def<T>): Promise<T> {
35
+ const obj = await this.readObjAndBytes(cid, def)
36
+ return obj.obj
37
+ }
38
+
39
+ async attemptReadRecord(cid: CID): Promise<RepoRecord | null> {
40
+ try {
41
+ return await this.readRecord(cid)
42
+ } catch {
43
+ return null
44
+ }
45
+ }
46
+
47
+ async readRecord(cid: CID): Promise<RepoRecord> {
48
+ const bytes = await this.getBytes(cid)
49
+ if (!bytes) {
50
+ throw new MissingBlockError(cid)
51
+ }
52
+ return cborToLexRecord(bytes)
53
+ }
54
+ }
55
+
56
+ export default ReadableBlockstore
@@ -0,0 +1,43 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import BlockMap from '../block-map'
3
+ import { CommitBlockData, CommitData, RebaseData } from '../types'
4
+ import ReadableBlockstore from './readable-blockstore'
5
+
6
+ export abstract class RepoStorage extends ReadableBlockstore {
7
+ abstract getHead(forUpdate?: boolean): Promise<CID | null>
8
+ abstract getCommitPath(
9
+ latest: CID,
10
+ earliest: CID | null,
11
+ ): Promise<CID[] | null>
12
+ abstract getBlocksForCommits(
13
+ commits: CID[],
14
+ ): Promise<{ [commit: string]: BlockMap }>
15
+
16
+ abstract putBlock(cid: CID, block: Uint8Array): Promise<void>
17
+ abstract putMany(blocks: BlockMap): Promise<void>
18
+ abstract updateHead(cid: CID, prev: CID | null): Promise<void>
19
+ abstract indexCommits(commit: CommitData[]): Promise<void>
20
+ abstract applyRebase(rebase: RebaseData): Promise<void>
21
+
22
+ async applyCommit(commit: CommitData): Promise<void> {
23
+ await Promise.all([
24
+ this.indexCommits([commit]),
25
+ this.updateHead(commit.commit, commit.prev),
26
+ ])
27
+ }
28
+
29
+ async getCommits(
30
+ latest: CID,
31
+ earliest: CID | null,
32
+ ): Promise<CommitBlockData[] | null> {
33
+ const commitPath = await this.getCommitPath(latest, earliest)
34
+ if (!commitPath) return null
35
+ const blocksByCommit = await this.getBlocksForCommits(commitPath)
36
+ return commitPath.map((commit) => ({
37
+ commit,
38
+ blocks: blocksByCommit[commit.toString()] || new BlockMap(),
39
+ }))
40
+ }
41
+ }
42
+
43
+ export default RepoStorage
@@ -0,0 +1,35 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import BlockMap from '../block-map'
3
+ import ReadableBlockstore from './readable-blockstore'
4
+
5
+ export class SyncStorage extends ReadableBlockstore {
6
+ constructor(
7
+ public staged: ReadableBlockstore,
8
+ public saved: ReadableBlockstore,
9
+ ) {
10
+ super()
11
+ }
12
+
13
+ async getBytes(cid: CID): Promise<Uint8Array | null> {
14
+ const got = await this.staged.getBytes(cid)
15
+ if (got) return got
16
+ return this.saved.getBytes(cid)
17
+ }
18
+
19
+ async getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {
20
+ const fromStaged = await this.staged.getBlocks(cids)
21
+ const fromSaved = await this.saved.getBlocks(fromStaged.missing)
22
+ const blocks = fromStaged.blocks
23
+ blocks.addMap(fromSaved.blocks)
24
+ return {
25
+ blocks,
26
+ missing: fromSaved.missing,
27
+ }
28
+ }
29
+
30
+ async has(cid: CID): Promise<boolean> {
31
+ return (await this.staged.has(cid)) || (await this.saved.has(cid))
32
+ }
33
+ }
34
+
35
+ export default SyncStorage
@@ -5,8 +5,12 @@ export interface BlobStore {
5
5
  putTemp(bytes: Uint8Array | stream.Readable): Promise<string>
6
6
  makePermanent(key: string, cid: CID): Promise<void>
7
7
  putPermanent(cid: CID, bytes: Uint8Array | stream.Readable): Promise<void>
8
+ quarantine(cid: CID): Promise<void>
9
+ unquarantine(cid: CID): Promise<void>
8
10
  getBytes(cid: CID): Promise<Uint8Array>
9
11
  getStream(cid: CID): Promise<stream.Readable>
12
+ hasStored(cid: CID): Promise<boolean>
13
+ delete(cid: CID): Promise<void>
10
14
  }
11
15
 
12
16
  export class BlobNotFoundError extends Error {}
@@ -0,0 +1,140 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { MemoryBlockstore, RepoStorage } from '../storage'
3
+ import Repo from '../repo'
4
+ import * as verify from '../verify'
5
+ import * as util from '../util'
6
+ import { CommitData, RepoContents, WriteLog } from '../types'
7
+ import CidSet from '../cid-set'
8
+ import { MissingBlocksError } from '../error'
9
+
10
+ // Checkouts
11
+ // -------------
12
+
13
+ export const loadCheckout = async (
14
+ storage: RepoStorage,
15
+ repoCar: Uint8Array,
16
+ did: string,
17
+ signingKey: string,
18
+ ): Promise<{ root: CID; contents: RepoContents }> => {
19
+ const { root, blocks } = await util.readCarWithRoot(repoCar)
20
+ const updateStorage = new MemoryBlockstore(blocks)
21
+ const checkout = await verify.verifyCheckout(
22
+ updateStorage,
23
+ root,
24
+ did,
25
+ signingKey,
26
+ )
27
+
28
+ const checkoutBlocks = await updateStorage.getBlocks(
29
+ checkout.newCids.toList(),
30
+ )
31
+ if (checkoutBlocks.missing.length > 0) {
32
+ throw new MissingBlocksError('sync', checkoutBlocks.missing)
33
+ }
34
+ await Promise.all([
35
+ storage.putMany(checkoutBlocks.blocks),
36
+ storage.updateHead(root, null),
37
+ ])
38
+
39
+ return {
40
+ root,
41
+ contents: checkout.contents,
42
+ }
43
+ }
44
+
45
+ // Diffs
46
+ // -------------
47
+
48
+ export const loadFullRepo = async (
49
+ storage: RepoStorage,
50
+ repoCar: Uint8Array,
51
+ did: string,
52
+ signingKey: string,
53
+ ): Promise<{ root: CID; writeLog: WriteLog; repo: Repo }> => {
54
+ const { root, blocks } = await util.readCarWithRoot(repoCar)
55
+ const updateStorage = new MemoryBlockstore(blocks)
56
+ const updates = await verify.verifyFullHistory(
57
+ updateStorage,
58
+ root,
59
+ did,
60
+ signingKey,
61
+ )
62
+
63
+ const [writeLog] = await Promise.all([
64
+ persistUpdates(storage, updateStorage, updates),
65
+ storage.updateHead(root, null),
66
+ ])
67
+
68
+ const repo = await Repo.load(storage, root)
69
+
70
+ return {
71
+ root,
72
+ writeLog,
73
+ repo,
74
+ }
75
+ }
76
+
77
+ export const loadDiff = async (
78
+ repo: Repo,
79
+ diffCar: Uint8Array,
80
+ did: string,
81
+ signingKey: string,
82
+ ): Promise<{ root: CID; writeLog: WriteLog }> => {
83
+ const { root, blocks } = await util.readCarWithRoot(diffCar)
84
+ const updateStorage = new MemoryBlockstore(blocks)
85
+ const updates = await verify.verifyUpdates(
86
+ repo,
87
+ updateStorage,
88
+ root,
89
+ did,
90
+ signingKey,
91
+ )
92
+
93
+ const [writeLog] = await Promise.all([
94
+ persistUpdates(repo.storage, updateStorage, updates),
95
+ repo.storage.updateHead(root, repo.cid),
96
+ ])
97
+
98
+ return {
99
+ root,
100
+ writeLog,
101
+ }
102
+ }
103
+
104
+ // Helpers
105
+ // -------------
106
+
107
+ export const persistUpdates = async (
108
+ storage: RepoStorage,
109
+ updateStorage: RepoStorage,
110
+ updates: verify.VerifiedUpdate[],
111
+ ): Promise<WriteLog> => {
112
+ const newCids = new CidSet()
113
+ for (const update of updates) {
114
+ newCids.addSet(update.newCids)
115
+ }
116
+
117
+ const diffBlocks = await updateStorage.getBlocks(newCids.toList())
118
+ if (diffBlocks.missing.length > 0) {
119
+ throw new MissingBlocksError('sync', diffBlocks.missing)
120
+ }
121
+ const commits: CommitData[] = updates.map((update) => {
122
+ const forCommit = diffBlocks.blocks.getMany(update.newCids.toList())
123
+ if (forCommit.missing.length > 0) {
124
+ throw new MissingBlocksError('sync', forCommit.missing)
125
+ }
126
+ return {
127
+ commit: update.commit,
128
+ prev: update.prev,
129
+ blocks: forCommit.blocks,
130
+ }
131
+ })
132
+
133
+ await storage.indexCommits(commits)
134
+
135
+ return Promise.all(
136
+ updates.map((upd) =>
137
+ util.diffToWriteDescripts(upd.diff, diffBlocks.blocks),
138
+ ),
139
+ )
140
+ }
@@ -0,0 +1,2 @@
1
+ export * from './consumer'
2
+ export * from './provider'
@@ -0,0 +1,91 @@
1
+ import { def, RecordPath } from '../types'
2
+ import { BlockWriter } from '@ipld/car/writer'
3
+ import { CID } from 'multiformats/cid'
4
+ import CidSet from '../cid-set'
5
+ import { MissingBlocksError } from '../error'
6
+ import { RepoStorage } from '../storage'
7
+ import * as util from '../util'
8
+ import { MST } from '../mst'
9
+
10
+ // Checkouts
11
+ // -------------
12
+
13
+ export const getCheckout = (
14
+ storage: RepoStorage,
15
+ commitCid: CID,
16
+ ): AsyncIterable<Uint8Array> => {
17
+ return util.writeCar(commitCid, async (car: BlockWriter) => {
18
+ const commit = await storage.readObjAndBytes(commitCid, def.commit)
19
+ await car.put({ cid: commitCid, bytes: commit.bytes })
20
+ const mst = MST.load(storage, commit.obj.data)
21
+ await mst.writeToCarStream(car)
22
+ })
23
+ }
24
+
25
+ // Commits
26
+ // -------------
27
+
28
+ export const getCommits = (
29
+ storage: RepoStorage,
30
+ latest: CID,
31
+ earliest: CID | null,
32
+ ): AsyncIterable<Uint8Array> => {
33
+ return util.writeCar(latest, (car: BlockWriter) => {
34
+ return writeCommitsToCarStream(storage, car, latest, earliest)
35
+ })
36
+ }
37
+
38
+ export const getFullRepo = (
39
+ storage: RepoStorage,
40
+ cid: CID,
41
+ ): AsyncIterable<Uint8Array> => {
42
+ return getCommits(storage, cid, null)
43
+ }
44
+
45
+ export const writeCommitsToCarStream = async (
46
+ storage: RepoStorage,
47
+ car: BlockWriter,
48
+ latest: CID,
49
+ earliest: CID | null,
50
+ ): Promise<void> => {
51
+ const commits = await storage.getCommits(latest, earliest)
52
+ if (commits === null) {
53
+ throw new Error('Could not find shared history')
54
+ }
55
+ if (commits.length === 0) return
56
+ for (const commit of commits) {
57
+ for (const entry of commit.blocks.entries()) {
58
+ await car.put(entry)
59
+ }
60
+ }
61
+ }
62
+
63
+ // Narrow slices
64
+ // -------------
65
+
66
+ export const getRecords = (
67
+ storage: RepoStorage,
68
+ commitCid: CID,
69
+ paths: RecordPath[],
70
+ ): AsyncIterable<Uint8Array> => {
71
+ return util.writeCar(commitCid, async (car: BlockWriter) => {
72
+ const commit = await storage.readObjAndBytes(commitCid, def.commit)
73
+ await car.put({ cid: commitCid, bytes: commit.bytes })
74
+ const mst = MST.load(storage, commit.obj.data)
75
+ const cidsForPaths = await Promise.all(
76
+ paths.map((p) =>
77
+ mst.cidsForPath(util.formatDataKey(p.collection, p.rkey)),
78
+ ),
79
+ )
80
+ const allCids = cidsForPaths.reduce((acc, cur) => {
81
+ return acc.addSet(new CidSet(cur))
82
+ }, new CidSet())
83
+ const found = await storage.getBlocks(allCids.toList())
84
+ if (found.missing.length > 0) {
85
+ throw new MissingBlocksError('writeRecordsToCarStream', found.missing)
86
+ }
87
+ for (const block of found.blocks.entries()) {
88
+ await car.put(block)
89
+ }
90
+ })
91
+ }