@atproto/repo 0.8.12 → 0.9.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 (105) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/block-map.d.ts +15 -16
  4. package/dist/block-map.d.ts.map +1 -1
  5. package/dist/block-map.js +9 -44
  6. package/dist/block-map.js.map +1 -1
  7. package/dist/car.d.ts +8 -8
  8. package/dist/car.d.ts.map +1 -1
  9. package/dist/car.js +107 -37
  10. package/dist/car.js.map +1 -1
  11. package/dist/cid-set.d.ts +8 -7
  12. package/dist/cid-set.d.ts.map +1 -1
  13. package/dist/cid-set.js +11 -4
  14. package/dist/cid-set.js.map +1 -1
  15. package/dist/data-diff.d.ts +10 -10
  16. package/dist/data-diff.d.ts.map +1 -1
  17. package/dist/data-diff.js.map +1 -1
  18. package/dist/error.d.ts +10 -10
  19. package/dist/error.d.ts.map +1 -1
  20. package/dist/error.js.map +1 -1
  21. package/dist/logger.d.ts.map +1 -1
  22. package/dist/logger.js +1 -0
  23. package/dist/logger.js.map +1 -1
  24. package/dist/mst/mst.d.ts +29 -29
  25. package/dist/mst/mst.d.ts.map +1 -1
  26. package/dist/mst/mst.js +12 -13
  27. package/dist/mst/mst.js.map +1 -1
  28. package/dist/mst/util.d.ts +2 -2
  29. package/dist/mst/util.d.ts.map +1 -1
  30. package/dist/mst/util.js +19 -40
  31. package/dist/mst/util.js.map +1 -1
  32. package/dist/parse.d.ts +6 -7
  33. package/dist/parse.d.ts.map +1 -1
  34. package/dist/parse.js +2 -3
  35. package/dist/parse.js.map +1 -1
  36. package/dist/readable-repo.d.ts +6 -7
  37. package/dist/readable-repo.d.ts.map +1 -1
  38. package/dist/readable-repo.js +0 -1
  39. package/dist/readable-repo.js.map +1 -1
  40. package/dist/repo.d.ts +4 -4
  41. package/dist/repo.d.ts.map +1 -1
  42. package/dist/repo.js +11 -10
  43. package/dist/repo.js.map +1 -1
  44. package/dist/storage/memory-blockstore.d.ts +9 -9
  45. package/dist/storage/memory-blockstore.d.ts.map +1 -1
  46. package/dist/storage/memory-blockstore.js.map +1 -1
  47. package/dist/storage/readable-blockstore.d.ts +11 -12
  48. package/dist/storage/readable-blockstore.d.ts.map +1 -1
  49. package/dist/storage/readable-blockstore.js +2 -36
  50. package/dist/storage/readable-blockstore.js.map +1 -1
  51. package/dist/storage/sync-storage.d.ts +5 -5
  52. package/dist/storage/sync-storage.d.ts.map +1 -1
  53. package/dist/storage/sync-storage.js.map +1 -1
  54. package/dist/storage/types.d.ts +25 -26
  55. package/dist/storage/types.d.ts.map +1 -1
  56. package/dist/storage/types.js +0 -1
  57. package/dist/storage/types.js.map +1 -1
  58. package/dist/sync/consumer.d.ts +3 -3
  59. package/dist/sync/consumer.d.ts.map +1 -1
  60. package/dist/sync/consumer.js +1 -1
  61. package/dist/sync/consumer.js.map +1 -1
  62. package/dist/sync/provider.d.ts +3 -3
  63. package/dist/sync/provider.d.ts.map +1 -1
  64. package/dist/sync/provider.js.map +1 -1
  65. package/dist/types.d.ts +127 -100
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js +53 -13
  68. package/dist/types.js.map +1 -1
  69. package/dist/util.d.ts +8 -3
  70. package/dist/util.d.ts.map +1 -1
  71. package/dist/util.js +33 -17
  72. package/dist/util.js.map +1 -1
  73. package/package.json +6 -7
  74. package/src/block-map.ts +31 -28
  75. package/src/car.ts +124 -46
  76. package/src/cid-set.ts +16 -10
  77. package/src/data-diff.ts +10 -10
  78. package/src/error.ts +6 -6
  79. package/src/logger.ts +1 -0
  80. package/src/mst/mst.ts +24 -28
  81. package/src/mst/util.ts +24 -11
  82. package/src/parse.ts +8 -10
  83. package/src/readable-repo.ts +6 -9
  84. package/src/repo.ts +13 -12
  85. package/src/storage/memory-blockstore.ts +8 -8
  86. package/src/storage/readable-blockstore.ts +12 -15
  87. package/src/storage/sync-storage.ts +4 -4
  88. package/src/storage/types.ts +25 -27
  89. package/src/sync/consumer.ts +5 -5
  90. package/src/sync/provider.ts +10 -7
  91. package/src/types.ts +82 -43
  92. package/src/util.ts +31 -16
  93. package/tests/_keys.ts +156 -156
  94. package/tests/_util.ts +28 -14
  95. package/tests/car.test.ts +31 -13
  96. package/tests/commit-proof-fixtures.json +79 -57
  97. package/tests/commit-proofs.test.ts +3 -3
  98. package/tests/covering-proofs.test.ts +6 -6
  99. package/tests/mst.test.ts +18 -19
  100. package/tests/proofs.test.ts +8 -8
  101. package/tests/repo.test.ts +1 -1
  102. package/tests/sync.test.ts +3 -6
  103. package/bench/mst.bench.ts +0 -165
  104. package/bench/repo.bench.ts +0 -48
  105. package/tsconfig.tests.tsbuildinfo +0 -1
@@ -1,49 +1,47 @@
1
- /* eslint-disable import/no-deprecated */
2
-
3
- import stream from 'node:stream'
4
- import { CID } from 'multiformats/cid'
5
- import { check } from '@atproto/common'
6
- import { RepoRecord } from '@atproto/lexicon'
1
+ import type { Readable } from 'node:stream'
2
+ import { check } from '@atproto/common-web'
3
+ import { Cid, LexMap } from '@atproto/lex-data'
7
4
  import { BlockMap } from '../block-map'
8
5
  import { CommitData } from '../types'
9
6
 
10
7
  export interface RepoStorage {
11
8
  // Writable
12
- getRoot(): Promise<CID | null>
13
- putBlock(cid: CID, block: Uint8Array, rev: string): Promise<void>
9
+ getRoot(): Promise<Cid | null>
10
+ putBlock(cid: Cid, block: Uint8Array, rev: string): Promise<void>
14
11
  putMany(blocks: BlockMap, rev: string): Promise<void>
15
- updateRoot(cid: CID, rev: string): Promise<void>
12
+ updateRoot(cid: Cid, rev: string): Promise<void>
16
13
  applyCommit(commit: CommitData)
17
14
 
18
15
  // Readable
19
- getBytes(cid: CID): Promise<Uint8Array | null>
20
- has(cid: CID): Promise<boolean>
21
- getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }>
16
+ getBytes(cid: Cid): Promise<Uint8Array | null>
17
+ has(cid: Cid): Promise<boolean>
18
+ getBlocks(cids: Cid[]): Promise<{ blocks: BlockMap; missing: Cid[] }>
22
19
  attemptRead<T>(
23
- cid: CID,
20
+ cid: Cid,
24
21
  def: check.Def<T>,
25
22
  ): Promise<{ obj: T; bytes: Uint8Array } | null>
26
23
  readObjAndBytes<T>(
27
- cid: CID,
24
+ cid: Cid,
28
25
  def: check.Def<T>,
29
26
  ): Promise<{ obj: T; bytes: Uint8Array }>
30
- readObj<T>(cid: CID, def: check.Def<T>): Promise<T>
31
- attemptReadRecord(cid: CID): Promise<RepoRecord | null>
32
- readRecord(cid: CID): Promise<RepoRecord>
27
+ readObj<T>(cid: Cid, def: check.Def<T>): Promise<T>
28
+ attemptReadRecord(cid: Cid): Promise<LexMap | null>
29
+ readRecord(cid: Cid): Promise<LexMap>
33
30
  }
34
31
 
32
+ // @TODO make this less node-js specific by using AsyncIterable<Uint8Array> instead of Readable
35
33
  export interface BlobStore {
36
- putTemp(bytes: Uint8Array | stream.Readable): Promise<string>
37
- makePermanent(key: string, cid: CID): Promise<void>
38
- putPermanent(cid: CID, bytes: Uint8Array | stream.Readable): Promise<void>
39
- quarantine(cid: CID): Promise<void>
40
- unquarantine(cid: CID): Promise<void>
41
- getBytes(cid: CID): Promise<Uint8Array>
42
- getStream(cid: CID): Promise<stream.Readable>
34
+ putTemp(bytes: Uint8Array | Readable): Promise<string>
35
+ makePermanent(key: string, cid: Cid): Promise<void>
36
+ putPermanent(cid: Cid, bytes: Uint8Array | Readable): Promise<void>
37
+ quarantine(cid: Cid): Promise<void>
38
+ unquarantine(cid: Cid): Promise<void>
39
+ getBytes(cid: Cid): Promise<Uint8Array>
40
+ getStream(cid: Cid): Promise<Readable>
43
41
  hasTemp(key: string): Promise<boolean>
44
- hasStored(cid: CID): Promise<boolean>
45
- delete(cid: CID): Promise<void>
46
- deleteMany(cid: CID[]): Promise<void>
42
+ hasStored(cid: Cid): Promise<boolean>
43
+ delete(cid: Cid): Promise<void>
44
+ deleteMany(cid: Cid[]): Promise<void>
47
45
  }
48
46
 
49
47
  export class BlobNotFoundError extends Error {}
@@ -1,4 +1,4 @@
1
- import { CID } from 'multiformats/cid'
1
+ import { Cid } from '@atproto/lex-data'
2
2
  import { BlockMap } from '../block-map'
3
3
  import { readCarWithRoot } from '../car'
4
4
  import { DataDiff } from '../data-diff'
@@ -25,7 +25,7 @@ export const verifyRepoCar = async (
25
25
 
26
26
  export const verifyRepo = async (
27
27
  blocks: BlockMap,
28
- head: CID,
28
+ head: Cid,
29
29
  did?: string,
30
30
  signingKey?: string,
31
31
  opts?: { ensureLeaves?: boolean },
@@ -52,7 +52,7 @@ export const verifyDiffCar = async (
52
52
  export const verifyDiff = async (
53
53
  repo: ReadableRepo | null,
54
54
  updateBlocks: BlockMap,
55
- updateRoot: CID,
55
+ updateRoot: Cid,
56
56
  did?: string,
57
57
  signingKey?: string,
58
58
  opts?: { ensureLeaves?: boolean },
@@ -103,7 +103,7 @@ export const verifyDiff = async (
103
103
  // @NOTE only verifies the root, not the repo contents
104
104
  const verifyRepoRoot = async (
105
105
  storage: ReadableBlockstore,
106
- head: CID,
106
+ head: Cid,
107
107
  did?: string,
108
108
  signingKey?: string,
109
109
  ): Promise<ReadableRepo> => {
@@ -155,7 +155,7 @@ export const verifyProofs = async (
155
155
  unverified.push(claim)
156
156
  }
157
157
  } else {
158
- if (claim.cid.equals(found)) {
158
+ if (found?.equals(claim.cid)) {
159
159
  verified.push(claim)
160
160
  } else {
161
161
  unverified.push(claim)
@@ -1,10 +1,10 @@
1
- import { CID } from 'multiformats/cid'
1
+ import { Cid } from '@atproto/lex-data'
2
2
  import { writeCarStream } from '../car'
3
3
  import { CidSet } from '../cid-set'
4
4
  import { MissingBlocksError } from '../error'
5
5
  import { MST } from '../mst'
6
6
  import { ReadableBlockstore, RepoStorage } from '../storage'
7
- import { RecordPath, def } from '../types'
7
+ import { CarBlock, RecordPath, def } from '../types'
8
8
  import * as util from '../util'
9
9
 
10
10
  // Full Repo
@@ -12,12 +12,15 @@ import * as util from '../util'
12
12
 
13
13
  export const getFullRepo = (
14
14
  storage: RepoStorage,
15
- commitCid: CID,
15
+ commitCid: Cid,
16
16
  ): AsyncIterable<Uint8Array> => {
17
17
  return writeCarStream(commitCid, iterateFullRepo(storage, commitCid))
18
18
  }
19
19
 
20
- async function* iterateFullRepo(storage: RepoStorage, commitCid: CID) {
20
+ async function* iterateFullRepo(
21
+ storage: RepoStorage,
22
+ commitCid: Cid,
23
+ ): AsyncGenerator<CarBlock> {
21
24
  const commit = await storage.readObjAndBytes(commitCid, def.commit)
22
25
  yield { cid: commitCid, bytes: commit.bytes }
23
26
  const mst = MST.load(storage, commit.obj.data)
@@ -31,7 +34,7 @@ async function* iterateFullRepo(storage: RepoStorage, commitCid: CID) {
31
34
 
32
35
  export const getRecords = (
33
36
  storage: ReadableBlockstore,
34
- commitCid: CID,
37
+ commitCid: Cid,
35
38
  paths: RecordPath[],
36
39
  ): AsyncIterable<Uint8Array> => {
37
40
  return writeCarStream(
@@ -42,9 +45,9 @@ export const getRecords = (
42
45
 
43
46
  async function* iterateRecordBlocks(
44
47
  storage: ReadableBlockstore,
45
- commitCid: CID,
48
+ commitCid: Cid,
46
49
  paths: RecordPath[],
47
- ) {
50
+ ): AsyncGenerator<CarBlock> {
48
51
  const commit = await storage.readObjAndBytes(commitCid, def.commit)
49
52
  yield { cid: commitCid, bytes: commit.bytes }
50
53
  const mst = MST.load(storage, commit.obj.data)
package/src/types.ts CHANGED
@@ -1,43 +1,50 @@
1
- /* eslint-disable import/no-deprecated */
2
-
3
- import { CID } from 'multiformats'
4
1
  import { z } from 'zod'
5
- import { schema as common } from '@atproto/common'
6
- import { def as commonDef } from '@atproto/common-web'
7
- import { RepoRecord } from '@atproto/lexicon'
2
+ import { Cid, LexMap, ifCid } from '@atproto/lex-data'
3
+ import { NsidString, RecordKeyString } from '@atproto/syntax'
8
4
  import { BlockMap } from './block-map'
9
5
  import { CidSet } from './cid-set'
10
6
 
11
7
  // Repo nodes
12
8
  // ---------------
13
9
 
10
+ const cidSchema = z.unknown().transform((input, ctx): Cid => {
11
+ const cid = ifCid(input)
12
+ if (cid) return cid
13
+
14
+ ctx.addIssue({
15
+ code: z.ZodIssueCode.custom,
16
+ message: 'Not a valid CID',
17
+ })
18
+ return z.NEVER
19
+ })
20
+
14
21
  const unsignedCommit = z.object({
15
22
  did: z.string(),
16
23
  version: z.literal(3),
17
- data: common.cid,
24
+ data: cidSchema,
18
25
  rev: z.string(),
19
26
  // `prev` added for backwards compatibility with v2, no requirement of keeping around history
20
- prev: common.cid.nullable(),
27
+ prev: cidSchema.nullable(),
21
28
  })
22
29
  export type UnsignedCommit = z.infer<typeof unsignedCommit> & { sig?: never }
23
30
 
24
31
  const commit = z.object({
25
32
  did: z.string(),
26
33
  version: z.literal(3),
27
- data: common.cid,
34
+ data: cidSchema,
28
35
  rev: z.string(),
29
- prev: common.cid.nullable(),
30
- sig: common.bytes,
36
+ prev: cidSchema.nullable(),
37
+ sig: z.instanceof(Uint8Array),
31
38
  })
32
39
  export type Commit = z.infer<typeof commit>
33
40
 
34
41
  const legacyV2Commit = z.object({
35
42
  did: z.string(),
36
43
  version: z.literal(2),
37
- data: common.cid,
44
+ data: cidSchema,
38
45
  rev: z.string().optional(),
39
- prev: common.cid.nullable(),
40
- sig: common.bytes,
46
+ prev: cidSchema.nullable(),
47
+ sig: z.instanceof(Uint8Array),
41
48
  })
42
49
  export type LegacyV2Commit = z.infer<typeof legacyV2Commit>
43
50
 
@@ -48,14 +55,46 @@ const versionedCommit = z.discriminatedUnion('version', [
48
55
  export type VersionedCommit = z.infer<typeof versionedCommit>
49
56
 
50
57
  export const schema = {
51
- ...common,
58
+ cid: cidSchema,
59
+ carHeader: z.object({
60
+ version: z.literal(1),
61
+ roots: z.array(cidSchema),
62
+ }),
63
+ bytes: z.instanceof(Uint8Array),
64
+ string: z.string(),
65
+ array: z.array(z.unknown()),
66
+ map: z.record(z.string(), z.unknown()),
67
+ unknown: z.unknown(),
52
68
  commit,
53
69
  legacyV2Commit,
54
70
  versionedCommit,
55
71
  }
56
72
 
57
73
  export const def = {
58
- ...commonDef,
74
+ cid: {
75
+ name: 'cid',
76
+ schema: schema.cid,
77
+ },
78
+ carHeader: {
79
+ name: 'CAR header',
80
+ schema: schema.carHeader,
81
+ },
82
+ bytes: {
83
+ name: 'bytes',
84
+ schema: schema.bytes,
85
+ },
86
+ string: {
87
+ name: 'string',
88
+ schema: schema.string,
89
+ },
90
+ map: {
91
+ name: 'map',
92
+ schema: schema.map,
93
+ },
94
+ unknown: {
95
+ name: 'unknown',
96
+ schema: schema.unknown,
97
+ },
59
98
  commit: {
60
99
  name: 'commit',
61
100
  schema: schema.commit,
@@ -77,46 +116,46 @@ export enum WriteOpAction {
77
116
 
78
117
  export type RecordCreateOp = {
79
118
  action: WriteOpAction.Create
80
- collection: string
81
- rkey: string
82
- record: RepoRecord
119
+ collection: NsidString
120
+ rkey: RecordKeyString
121
+ record: LexMap
83
122
  }
84
123
 
85
124
  export type RecordUpdateOp = {
86
125
  action: WriteOpAction.Update
87
- collection: string
88
- rkey: string
89
- record: RepoRecord
126
+ collection: NsidString
127
+ rkey: RecordKeyString
128
+ record: LexMap
90
129
  }
91
130
 
92
131
  export type RecordDeleteOp = {
93
132
  action: WriteOpAction.Delete
94
- collection: string
95
- rkey: string
133
+ collection: NsidString
134
+ rkey: RecordKeyString
96
135
  }
97
136
 
98
137
  export type RecordWriteOp = RecordCreateOp | RecordUpdateOp | RecordDeleteOp
99
138
 
100
139
  export type RecordCreateDescript = {
101
140
  action: WriteOpAction.Create
102
- collection: string
103
- rkey: string
104
- cid: CID
141
+ collection: NsidString
142
+ rkey: RecordKeyString
143
+ cid: Cid
105
144
  }
106
145
 
107
146
  export type RecordUpdateDescript = {
108
147
  action: WriteOpAction.Update
109
- collection: string
110
- rkey: string
111
- prev: CID
112
- cid: CID
148
+ collection: NsidString
149
+ rkey: RecordKeyString
150
+ prev: Cid
151
+ cid: Cid
113
152
  }
114
153
 
115
154
  export type RecordDeleteDescript = {
116
155
  action: WriteOpAction.Delete
117
- collection: string
118
- rkey: string
119
- cid: CID
156
+ collection: NsidString
157
+ rkey: RecordKeyString
158
+ cid: Cid
120
159
  }
121
160
 
122
161
  export type RecordWriteDescript =
@@ -130,10 +169,10 @@ export type WriteLog = RecordWriteDescript[][]
130
169
  // ---------------
131
170
 
132
171
  export type CommitData = {
133
- cid: CID
172
+ cid: Cid
134
173
  rev: string
135
174
  since: string | null
136
- prev: CID | null
175
+ prev: Cid | null
137
176
  newBlocks: BlockMap
138
177
  relevantBlocks: BlockMap
139
178
  removedCids: CidSet
@@ -143,14 +182,14 @@ export type RepoUpdate = CommitData & {
143
182
  ops: RecordWriteOp[]
144
183
  }
145
184
 
146
- export type CollectionContents = Record<string, RepoRecord>
147
- export type RepoContents = Record<string, CollectionContents>
185
+ export type CollectionContents = Record<string, LexMap>
186
+ export type RepoContents = Record<NsidString, CollectionContents>
148
187
 
149
- export type RepoRecordWithCid = { cid: CID; value: RepoRecord }
188
+ export type RepoRecordWithCid = { cid: Cid; value: LexMap }
150
189
  export type CollectionContentsWithCids = Record<string, RepoRecordWithCid>
151
190
  export type RepoContentsWithCids = Record<string, CollectionContentsWithCids>
152
191
 
153
- export type DatastoreContents = Record<string, CID>
192
+ export type DatastoreContents = Record<string, Cid>
154
193
 
155
194
  export type RecordPath = {
156
195
  collection: string
@@ -160,13 +199,13 @@ export type RecordPath = {
160
199
  export type RecordCidClaim = {
161
200
  collection: string
162
201
  rkey: string
163
- cid: CID | null
202
+ cid: Cid | null
164
203
  }
165
204
 
166
205
  export type RecordClaim = {
167
206
  collection: string
168
207
  rkey: string
169
- record: RepoRecord | null
208
+ record: LexMap | null
170
209
  }
171
210
 
172
211
  // Sync
@@ -183,6 +222,6 @@ export type VerifiedRepo = {
183
222
  }
184
223
 
185
224
  export type CarBlock = {
186
- cid: CID
225
+ cid: Cid
187
226
  bytes: Uint8Array
188
227
  }
package/src/util.ts CHANGED
@@ -1,10 +1,8 @@
1
- /* eslint-disable import/no-deprecated */
2
-
3
- import * as cbor from '@ipld/dag-cbor'
4
- import { TID, cborDecode, check, cidForCbor, schema } from '@atproto/common'
1
+ import { TID } from '@atproto/common-web'
5
2
  import * as crypto from '@atproto/crypto'
6
3
  import { Keypair } from '@atproto/crypto'
7
- import { LexValue, RepoRecord, ipldToLex, lexToIpld } from '@atproto/lexicon'
4
+ import * as cbor from '@atproto/lex-cbor'
5
+ import { Cid, LexMap, LexValue, isPlainObject } from '@atproto/lex-data'
8
6
  import { DataDiff } from './data-diff'
9
7
  import {
10
8
  Commit,
@@ -68,9 +66,9 @@ export const ensureCreates = (
68
66
  }
69
67
 
70
68
  export const parseDataKey = (key: string): RecordPath => {
71
- const parts = key.split('/')
72
- if (parts.length !== 2) throw new Error(`Invalid record key: ${key}`)
73
- return { collection: parts[0], rkey: parts[1] }
69
+ const { length, 0: collection, 1: rkey } = key.split('/')
70
+ if (length !== 2) throw new Error(`Invalid record key: ${key}`)
71
+ return { collection, rkey }
74
72
  }
75
73
 
76
74
  export const formatDataKey = (collection: string, rkey: string): string => {
@@ -102,21 +100,17 @@ export const verifyCommitSig = async (
102
100
  return crypto.verifySignature(didKey, encoded, sig)
103
101
  }
104
102
 
105
- export const cborToLex = (val: Uint8Array): LexValue => {
106
- return ipldToLex(cborDecode(val))
107
- }
103
+ export const cborToLex: (val: Uint8Array) => LexValue = cbor.decode
108
104
 
109
- export const cborToLexRecord = (val: Uint8Array): RepoRecord => {
105
+ export const cborToLexRecord = (val: Uint8Array): LexMap => {
110
106
  const parsed = cborToLex(val)
111
- if (!check.is(parsed, schema.map)) {
107
+ if (!isPlainObject(parsed)) {
112
108
  throw new Error('lexicon records be a json object')
113
109
  }
114
110
  return parsed
115
111
  }
116
112
 
117
- export const cidForRecord = async (val: LexValue) => {
118
- return cidForCbor(lexToIpld(val))
119
- }
113
+ export const cidForRecord: (val: LexValue) => Promise<Cid> = cbor.cidForLex
120
114
 
121
115
  export const ensureV3Commit = (commit: LegacyV2Commit | Commit): Commit => {
122
116
  if (commit.version === 3) {
@@ -129,3 +123,24 @@ export const ensureV3Commit = (commit: LegacyV2Commit | Commit): Commit => {
129
123
  }
130
124
  }
131
125
  }
126
+
127
+ export async function concatBytesAsync(iterable: AsyncIterable<Uint8Array>) {
128
+ const chunks: Uint8Array[] = []
129
+ for await (const chunk of iterable) chunks.push(chunk)
130
+ return concatBytes(chunks)
131
+ }
132
+
133
+ /**
134
+ * This is the same as {@link Buffer.concat}, without the `totalLength` argument.
135
+ */
136
+ export function concatBytes(chunks: readonly Uint8Array[]): Uint8Array {
137
+ let totalLength = 0
138
+ for (const chunk of chunks) totalLength += chunk.byteLength
139
+ const result = new Uint8Array(totalLength)
140
+ let offset = 0
141
+ for (const chunk of chunks) {
142
+ result.set(chunk, offset)
143
+ offset += chunk.byteLength
144
+ }
145
+ return result
146
+ }