@atproto/repo 0.1.0 → 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 (77) hide show
  1. package/dist/block-map.d.ts +2 -0
  2. package/dist/data-diff.d.ts +2 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +12090 -2882
  5. package/dist/index.js.map +4 -4
  6. package/dist/mst/mst.d.ts +13 -14
  7. package/dist/readable-repo.d.ts +4 -3
  8. package/dist/repo.d.ts +6 -2
  9. package/dist/src/block-map.d.ts +23 -0
  10. package/dist/src/blockstore/persistent-blockstore.d.ts +12 -0
  11. package/dist/src/cid-set.d.ts +14 -0
  12. package/dist/src/collection.d.ts +22 -0
  13. package/dist/src/data-diff.d.ts +34 -0
  14. package/dist/src/error.d.ts +21 -0
  15. package/dist/src/index.d.ts +7 -0
  16. package/dist/src/logger.d.ts +2 -0
  17. package/dist/src/mst/diff.d.ts +33 -0
  18. package/dist/src/mst/index.d.ts +4 -0
  19. package/dist/src/mst/mst.d.ts +106 -0
  20. package/dist/src/mst/util.d.ts +9 -0
  21. package/dist/src/mst/walker.d.ts +22 -0
  22. package/dist/src/parse.d.ts +11 -0
  23. package/dist/src/readable-repo.d.ts +25 -0
  24. package/dist/src/repo.d.ts +39 -0
  25. package/dist/src/storage/error.d.ts +22 -0
  26. package/dist/src/storage/index.d.ts +1 -0
  27. package/dist/src/storage/memory-blobstore.d.ts +1 -0
  28. package/dist/src/storage/memory-blockstore.d.ts +28 -0
  29. package/dist/src/storage/readable-blockstore.d.ts +21 -0
  30. package/dist/src/storage/repo-storage.d.ts +18 -0
  31. package/dist/src/storage/sync-storage.d.ts +15 -0
  32. package/dist/src/storage/types.d.ts +12 -0
  33. package/dist/src/storage/util.d.ts +17 -0
  34. package/dist/src/structure.d.ts +39 -0
  35. package/dist/src/sync/consumer.d.ts +19 -0
  36. package/dist/src/sync/index.d.ts +2 -0
  37. package/dist/src/sync/producer.d.ts +13 -0
  38. package/dist/src/sync/provider.d.ts +11 -0
  39. package/dist/src/types.d.ts +368 -0
  40. package/dist/src/util.d.ts +13 -0
  41. package/dist/src/verify.d.ts +5 -0
  42. package/dist/storage/memory-blockstore.d.ts +2 -1
  43. package/dist/storage/repo-storage.d.ts +2 -1
  44. package/dist/storage/types.d.ts +1 -0
  45. package/dist/sync/consumer.d.ts +1 -0
  46. package/dist/sync/provider.d.ts +4 -4
  47. package/dist/tsconfig.build.tsbuildinfo +1 -0
  48. package/dist/types.d.ts +30 -31
  49. package/dist/util.d.ts +6 -2
  50. package/dist/verify.d.ts +6 -1
  51. package/jest.bench.config.js +2 -1
  52. package/package.json +11 -6
  53. package/src/block-map.ts +8 -0
  54. package/src/data-diff.ts +2 -6
  55. package/src/index.ts +1 -0
  56. package/src/mst/mst.ts +1 -10
  57. package/src/readable-repo.ts +3 -3
  58. package/src/repo.ts +33 -2
  59. package/src/storage/memory-blockstore.ts +20 -1
  60. package/src/storage/repo-storage.ts +2 -1
  61. package/src/storage/types.ts +1 -0
  62. package/src/sync/consumer.ts +4 -1
  63. package/src/sync/provider.ts +11 -11
  64. package/src/types.ts +19 -21
  65. package/src/util.ts +34 -13
  66. package/src/verify.ts +52 -11
  67. package/tests/rebase.test.ts +37 -0
  68. package/tests/sync/checkout.test.ts +21 -3
  69. package/tests/sync/diff.test.ts +9 -4
  70. package/tests/sync/narrow.test.ts +8 -4
  71. package/tests/util.test.ts +21 -0
  72. package/tsconfig.build.tsbuildinfo +1 -1
  73. package/tsconfig.json +1 -1
  74. /package/dist/{blockstore → src/blockstore}/index.d.ts +0 -0
  75. /package/dist/{blockstore → src/blockstore}/ipld-store.d.ts +0 -0
  76. /package/dist/{blockstore → src/blockstore}/memory-blockstore.d.ts +0 -0
  77. /package/dist/{sync.d.ts → src/sync.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { Commit, def, RecordPath } from '../types'
1
+ import { def, RecordPath } from '../types'
2
2
  import { BlockWriter } from '@ipld/car/writer'
3
3
  import { CID } from 'multiformats/cid'
4
4
  import CidSet from '../cid-set'
@@ -10,10 +10,10 @@ import { MST } from '../mst'
10
10
  // Checkouts
11
11
  // -------------
12
12
 
13
- export const getCheckout = async (
13
+ export const getCheckout = (
14
14
  storage: RepoStorage,
15
15
  commitCid: CID,
16
- ): Promise<Uint8Array> => {
16
+ ): AsyncIterable<Uint8Array> => {
17
17
  return util.writeCar(commitCid, async (car: BlockWriter) => {
18
18
  const commit = await storage.readObjAndBytes(commitCid, def.commit)
19
19
  await car.put({ cid: commitCid, bytes: commit.bytes })
@@ -22,24 +22,24 @@ export const getCheckout = async (
22
22
  })
23
23
  }
24
24
 
25
- // Diffs
25
+ // Commits
26
26
  // -------------
27
27
 
28
- export const getDiff = async (
28
+ export const getCommits = (
29
29
  storage: RepoStorage,
30
30
  latest: CID,
31
31
  earliest: CID | null,
32
- ): Promise<Uint8Array> => {
32
+ ): AsyncIterable<Uint8Array> => {
33
33
  return util.writeCar(latest, (car: BlockWriter) => {
34
34
  return writeCommitsToCarStream(storage, car, latest, earliest)
35
35
  })
36
36
  }
37
37
 
38
- export const getFullRepo = async (
38
+ export const getFullRepo = (
39
39
  storage: RepoStorage,
40
40
  cid: CID,
41
- ): Promise<Uint8Array> => {
42
- return getDiff(storage, cid, null)
41
+ ): AsyncIterable<Uint8Array> => {
42
+ return getCommits(storage, cid, null)
43
43
  }
44
44
 
45
45
  export const writeCommitsToCarStream = async (
@@ -63,11 +63,11 @@ export const writeCommitsToCarStream = async (
63
63
  // Narrow slices
64
64
  // -------------
65
65
 
66
- export const getRecords = async (
66
+ export const getRecords = (
67
67
  storage: RepoStorage,
68
68
  commitCid: CID,
69
69
  paths: RecordPath[],
70
- ): Promise<Uint8Array> => {
70
+ ): AsyncIterable<Uint8Array> => {
71
71
  return util.writeCar(commitCid, async (car: BlockWriter) => {
72
72
  const commit = await storage.readObjAndBytes(commitCid, def.commit)
73
73
  await car.put({ cid: commitCid, bytes: commit.bytes })
package/src/types.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod'
2
- import { BlockWriter } from '@ipld/car/writer'
3
2
  import { schema as common, def as commonDef } from '@atproto/common'
4
3
  import { CID } from 'multiformats'
5
4
  import BlockMap from './block-map'
@@ -101,6 +100,19 @@ export type CommitData = CommitBlockData & {
101
100
  prev: CID | null
102
101
  }
103
102
 
103
+ export type RebaseData = {
104
+ commit: CID
105
+ rebased: CID
106
+ blocks: BlockMap
107
+ preservedCids: CID[]
108
+ }
109
+
110
+ export type CommitCidData = {
111
+ commit: CID
112
+ prev: CID | null
113
+ cids: CID[]
114
+ }
115
+
104
116
  export type RepoUpdate = CommitData & {
105
117
  ops: RecordWriteOp[]
106
118
  }
@@ -108,6 +120,12 @@ export type RepoUpdate = CommitData & {
108
120
  export type CollectionContents = Record<string, RepoRecord>
109
121
  export type RepoContents = Record<string, CollectionContents>
110
122
 
123
+ export type RepoRecordWithCid = { cid: CID; value: RepoRecord }
124
+ export type CollectionContentsWithCids = Record<string, RepoRecordWithCid>
125
+ export type RepoContentsWithCids = Record<string, CollectionContentsWithCids>
126
+
127
+ export type DatastoreContents = Record<string, CID>
128
+
111
129
  export type RecordPath = {
112
130
  collection: string
113
131
  rkey: string
@@ -118,23 +136,3 @@ export type RecordClaim = {
118
136
  rkey: string
119
137
  record: RepoRecord | null
120
138
  }
121
-
122
- // DataStores
123
- // ---------------
124
-
125
- export type DataValue = {
126
- key: string
127
- value: CID
128
- }
129
-
130
- export interface DataStore {
131
- add(key: string, value: CID): Promise<DataStore>
132
- update(key: string, value: CID): Promise<DataStore>
133
- delete(key: string): Promise<DataStore>
134
- get(key: string): Promise<CID | null>
135
- list(count?: number, after?: string, before?: string): Promise<DataValue[]>
136
- listWithPrefix(prefix: string, count?: number): Promise<DataValue[]>
137
- getUnstoredBlocks(): Promise<{ root: CID; blocks: BlockMap }>
138
- writeToCarStream(car: BlockWriter): Promise<void>
139
- cidsForPath(key: string): Promise<CID[]>
140
- }
package/src/util.ts CHANGED
@@ -4,12 +4,13 @@ import { CarReader } from '@ipld/car/reader'
4
4
  import { BlockWriter, CarWriter } from '@ipld/car/writer'
5
5
  import { Block as CarBlock } from '@ipld/car/api'
6
6
  import {
7
- streamToArray,
7
+ streamToBuffer,
8
8
  verifyCidForBytes,
9
9
  cborDecode,
10
10
  check,
11
11
  schema,
12
12
  cidForCbor,
13
+ byteIterableToStream,
13
14
  } from '@atproto/common'
14
15
  import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon'
15
16
 
@@ -20,7 +21,6 @@ import DataDiff from './data-diff'
20
21
  import { RepoStorage } from './storage'
21
22
  import {
22
23
  Commit,
23
- DataStore,
24
24
  RecordCreateDescript,
25
25
  RecordDeleteDescript,
26
26
  RecordPath,
@@ -34,6 +34,7 @@ import BlockMap from './block-map'
34
34
  import { MissingBlocksError } from './error'
35
35
  import * as parse from './parse'
36
36
  import { Keypair } from '@atproto/crypto'
37
+ import { Readable } from 'stream'
37
38
 
38
39
  export async function* verifyIncomingCarBlocks(
39
40
  car: AsyncIterable<CarBlock>,
@@ -44,25 +45,37 @@ export async function* verifyIncomingCarBlocks(
44
45
  }
45
46
  }
46
47
 
47
- export const writeCar = async (
48
+ // we have to turn the car writer output into a stream in order to properly handle errors
49
+ export function writeCarStream(
48
50
  root: CID | null,
49
51
  fn: (car: BlockWriter) => Promise<void>,
50
- ): Promise<Uint8Array> => {
52
+ ): Readable {
51
53
  const { writer, out } =
52
54
  root !== null ? CarWriter.create(root) : CarWriter.create()
53
- const bytes = streamToArray(out)
54
- try {
55
- await fn(writer)
56
- } finally {
57
- writer.close()
55
+
56
+ const stream = byteIterableToStream(out)
57
+ fn(writer)
58
+ .catch((err) => {
59
+ stream.destroy(err)
60
+ })
61
+ .finally(() => writer.close())
62
+ return stream
63
+ }
64
+
65
+ export async function* writeCar(
66
+ root: CID | null,
67
+ fn: (car: BlockWriter) => Promise<void>,
68
+ ): AsyncIterable<Uint8Array> {
69
+ const stream = writeCarStream(root, fn)
70
+ for await (const chunk of stream) {
71
+ yield chunk
58
72
  }
59
- return bytes
60
73
  }
61
74
 
62
- export const blocksToCar = async (
75
+ export const blocksToCarStream = (
63
76
  root: CID | null,
64
77
  blocks: BlockMap,
65
- ): Promise<Uint8Array> => {
78
+ ): AsyncIterable<Uint8Array> => {
66
79
  return writeCar(root, async (writer) => {
67
80
  for (const entry of blocks.entries()) {
68
81
  await writer.put(entry)
@@ -70,6 +83,14 @@ export const blocksToCar = async (
70
83
  })
71
84
  }
72
85
 
86
+ export const blocksToCarFile = (
87
+ root: CID | null,
88
+ blocks: BlockMap,
89
+ ): Promise<Uint8Array> => {
90
+ const carStream = blocksToCarStream(root, blocks)
91
+ return streamToBuffer(carStream)
92
+ }
93
+
73
94
  export const readCar = async (
74
95
  bytes: Uint8Array,
75
96
  ): Promise<{ roots: CID[]; blocks: BlockMap }> => {
@@ -108,7 +129,7 @@ export const getWriteLog = async (
108
129
  if (!commits) throw new Error('Could not find shared history')
109
130
  const heads = await Promise.all(commits.map((c) => Repo.load(storage, c)))
110
131
  // Turn commit path into list of diffs
111
- let prev: DataStore = await MST.create(storage) // Empty
132
+ let prev = await MST.create(storage) // Empty
112
133
  const msts = heads.map((h) => h.data)
113
134
  const diffs: DataDiff[] = []
114
135
  for (const mst of msts) {
package/src/verify.ts CHANGED
@@ -6,7 +6,7 @@ import ReadableRepo from './readable-repo'
6
6
  import Repo from './repo'
7
7
  import CidSet from './cid-set'
8
8
  import * as util from './util'
9
- import { RecordClaim, RepoContents } from './types'
9
+ import { RecordClaim, RepoContents, RepoContentsWithCids } from './types'
10
10
  import { def } from './types'
11
11
  import { MST } from './mst'
12
12
  import { cidForCbor } from '@atproto/common'
@@ -16,22 +16,18 @@ export type VerifiedCheckout = {
16
16
  newCids: CidSet
17
17
  }
18
18
 
19
+ export type VerifiedCheckoutWithCids = {
20
+ commit: CID
21
+ contents: RepoContentsWithCids
22
+ }
23
+
19
24
  export const verifyCheckout = async (
20
25
  storage: ReadableBlockstore,
21
26
  head: CID,
22
27
  did: string,
23
28
  signingKey: string,
24
29
  ): Promise<VerifiedCheckout> => {
25
- const repo = await ReadableRepo.load(storage, head)
26
- if (repo.did !== did) {
27
- throw new RepoVerificationError(`Invalid repo did: ${repo.did}`)
28
- }
29
- const validSig = await util.verifyCommitSig(repo.commit, signingKey)
30
- if (!validSig) {
31
- throw new RepoVerificationError(
32
- `Invalid signature on commit: ${repo.cid.toString()}`,
33
- )
34
- }
30
+ const repo = await verifyRepoRoot(storage, head, did, signingKey)
35
31
  const diff = await DataDiff.of(repo.data, null)
36
32
  const newCids = new CidSet([repo.cid]).addSet(diff.newCids)
37
33
 
@@ -51,6 +47,51 @@ export const verifyCheckout = async (
51
47
  }
52
48
  }
53
49
 
50
+ export const verifyCheckoutWithCids = async (
51
+ storage: ReadableBlockstore,
52
+ head: CID,
53
+ did: string,
54
+ signingKey: string,
55
+ ): Promise<VerifiedCheckoutWithCids> => {
56
+ const repo = await verifyRepoRoot(storage, head, did, signingKey)
57
+ const diff = await DataDiff.of(repo.data, null)
58
+
59
+ const contents: RepoContentsWithCids = {}
60
+ for (const add of diff.addList()) {
61
+ const { collection, rkey } = util.parseDataKey(add.key)
62
+ contents[collection] ??= {}
63
+ contents[collection][rkey] = {
64
+ cid: add.cid,
65
+ value: await storage.readRecord(add.cid),
66
+ }
67
+ }
68
+
69
+ return {
70
+ commit: repo.cid,
71
+ contents,
72
+ }
73
+ }
74
+
75
+ // @NOTE only verifies the root, not the repo contents
76
+ const verifyRepoRoot = async (
77
+ storage: ReadableBlockstore,
78
+ head: CID,
79
+ did: string,
80
+ signingKey: string,
81
+ ): Promise<ReadableRepo> => {
82
+ const repo = await ReadableRepo.load(storage, head)
83
+ if (repo.did !== did) {
84
+ throw new RepoVerificationError(`Invalid repo did: ${repo.did}`)
85
+ }
86
+ const validSig = await util.verifyCommitSig(repo.commit, signingKey)
87
+ if (!validSig) {
88
+ throw new RepoVerificationError(
89
+ `Invalid signature on commit: ${repo.cid.toString()}`,
90
+ )
91
+ }
92
+ return repo
93
+ }
94
+
54
95
  export type VerifiedUpdate = {
55
96
  commit: CID
56
97
  prev: CID | null
@@ -0,0 +1,37 @@
1
+ import * as crypto from '@atproto/crypto'
2
+ import { Repo } from '../src/repo'
3
+ import { MemoryBlockstore } from '../src/storage'
4
+ import * as util from './_util'
5
+ import { Secp256k1Keypair } from '@atproto/crypto'
6
+
7
+ describe('Rebases', () => {
8
+ let storage: MemoryBlockstore
9
+ let keypair: crypto.Keypair
10
+ let repo: Repo
11
+
12
+ it('fills a repo with data', async () => {
13
+ storage = new MemoryBlockstore()
14
+ keypair = await Secp256k1Keypair.create()
15
+ repo = await Repo.create(storage, keypair.did(), keypair)
16
+ const filled = await util.fillRepo(repo, keypair, 100)
17
+ repo = filled.repo
18
+ })
19
+
20
+ it('rebases the repo & preserves contents', async () => {
21
+ const dataCidBefore = await repo.data.getPointer()
22
+ const contents = await repo.getContents()
23
+ repo = await repo.rebase(keypair)
24
+ const rebasedContents = await repo.getContents()
25
+ expect(rebasedContents).toEqual(contents)
26
+ const dataCidAfter = await repo.data.getPointer()
27
+ expect(dataCidAfter.equals(dataCidBefore)).toBeTruthy()
28
+ })
29
+
30
+ it('only keeps around relevant cids', async () => {
31
+ const allCids = await repo.data.allCids()
32
+ allCids.add(repo.cid)
33
+ for (const cid of storage.blocks.cids()) {
34
+ expect(allCids.has(cid)).toBeTruthy()
35
+ }
36
+ })
37
+ })
@@ -1,9 +1,11 @@
1
1
  import * as crypto from '@atproto/crypto'
2
- import { Repo, RepoContents, RepoVerificationError } from '../../src'
2
+ import { CidSet, Repo, RepoContents, RepoVerificationError } from '../../src'
3
3
  import { MemoryBlockstore } from '../../src/storage'
4
4
  import * as sync from '../../src/sync'
5
5
 
6
6
  import * as util from '../_util'
7
+ import { streamToBuffer } from '@atproto/common'
8
+ import { CarReader } from '@ipld/car/reader'
7
9
 
8
10
  describe('Checkout Sync', () => {
9
11
  let storage: MemoryBlockstore
@@ -25,7 +27,9 @@ describe('Checkout Sync', () => {
25
27
  })
26
28
 
27
29
  it('sync a non-historical repo checkout', async () => {
28
- const checkoutCar = await sync.getCheckout(storage, repo.cid)
30
+ const checkoutCar = await streamToBuffer(
31
+ sync.getCheckout(storage, repo.cid),
32
+ )
29
33
  const checkout = await sync.loadCheckout(
30
34
  syncStorage,
31
35
  checkoutCar,
@@ -47,9 +51,23 @@ describe('Checkout Sync', () => {
47
51
  expect(hasGenesisCommit).toBeFalsy()
48
52
  })
49
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
+
50
66
  it('throws on a bad signature', async () => {
51
67
  const badRepo = await util.addBadCommit(repo, keypair)
52
- const checkoutCar = await sync.getCheckout(storage, badRepo.cid)
68
+ const checkoutCar = await streamToBuffer(
69
+ sync.getCheckout(storage, badRepo.cid),
70
+ )
53
71
  await expect(
54
72
  sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()),
55
73
  ).rejects.toThrow(RepoVerificationError)
@@ -4,6 +4,7 @@ import { MemoryBlockstore } from '../../src/storage'
4
4
  import * as sync from '../../src/sync'
5
5
 
6
6
  import * as util from '../_util'
7
+ import { streamToBuffer } from '@atproto/common'
7
8
 
8
9
  describe('Diff Sync', () => {
9
10
  let storage: MemoryBlockstore
@@ -24,7 +25,7 @@ describe('Diff Sync', () => {
24
25
  let syncRepo: Repo
25
26
 
26
27
  it('syncs an empty repo', async () => {
27
- const car = await sync.getFullRepo(storage, repo.cid)
28
+ const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
28
29
  const loaded = await sync.loadFullRepo(
29
30
  syncStorage,
30
31
  car,
@@ -41,7 +42,7 @@ describe('Diff Sync', () => {
41
42
  repo = filled.repo
42
43
  repoData = filled.data
43
44
 
44
- const car = await sync.getFullRepo(storage, repo.cid)
45
+ const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
45
46
  const loaded = await sync.loadFullRepo(
46
47
  syncStorage,
47
48
  car,
@@ -64,7 +65,9 @@ describe('Diff Sync', () => {
64
65
  })
65
66
  repo = edited.repo
66
67
  repoData = edited.data
67
- const diffCar = await sync.getDiff(storage, repo.cid, syncRepo.cid)
68
+ const diffCar = await streamToBuffer(
69
+ sync.getCommits(storage, repo.cid, syncRepo.cid),
70
+ )
68
71
  const loaded = await sync.loadDiff(
69
72
  syncRepo,
70
73
  diffCar,
@@ -79,7 +82,9 @@ describe('Diff Sync', () => {
79
82
 
80
83
  it('throws on a bad signature', async () => {
81
84
  const badRepo = await util.addBadCommit(repo, keypair)
82
- const diffCar = await sync.getDiff(storage, badRepo.cid, syncRepo.cid)
85
+ const diffCar = await streamToBuffer(
86
+ sync.getCommits(storage, badRepo.cid, syncRepo.cid),
87
+ )
83
88
  await expect(
84
89
  sync.loadDiff(syncRepo, diffCar, repoDid, keypair.did()),
85
90
  ).rejects.toThrow('Invalid signature on commit')
@@ -1,4 +1,4 @@
1
- import { TID } from '@atproto/common'
1
+ import { TID, streamToBuffer } from '@atproto/common'
2
2
  import * as crypto from '@atproto/crypto'
3
3
  import { RecordClaim, Repo, RepoContents } from '../../src'
4
4
  import { MemoryBlockstore } from '../../src/storage'
@@ -25,7 +25,7 @@ describe('Narrow Sync', () => {
25
25
  })
26
26
 
27
27
  const getProofs = async (claims: RecordClaim[]) => {
28
- return sync.getRecords(storage, repo.cid, claims)
28
+ return streamToBuffer(sync.getRecords(storage, repo.cid, claims))
29
29
  }
30
30
 
31
31
  const doVerify = (proofs: Uint8Array, claims: RecordClaim[]) => {
@@ -130,7 +130,9 @@ describe('Narrow Sync', () => {
130
130
  it('verifyRecords throws on a bad signature', async () => {
131
131
  const badRepo = await util.addBadCommit(repo, keypair)
132
132
  const claims = util.contentsToClaims(repoData)
133
- const proofs = await sync.getRecords(storage, badRepo.cid, claims)
133
+ const proofs = await streamToBuffer(
134
+ sync.getRecords(storage, badRepo.cid, claims),
135
+ )
134
136
  const fn = verify.verifyRecords(proofs, repoDid, keypair.did())
135
137
  await expect(fn).rejects.toThrow(verify.RepoVerificationError)
136
138
  })
@@ -138,7 +140,9 @@ describe('Narrow Sync', () => {
138
140
  it('verifyProofs throws on a bad signature', async () => {
139
141
  const badRepo = await util.addBadCommit(repo, keypair)
140
142
  const claims = util.contentsToClaims(repoData)
141
- const proofs = await sync.getRecords(storage, badRepo.cid, claims)
143
+ const proofs = await streamToBuffer(
144
+ sync.getRecords(storage, badRepo.cid, claims),
145
+ )
142
146
  const fn = verify.verifyProofs(proofs, claims, repoDid, keypair.did())
143
147
  await expect(fn).rejects.toThrow(verify.RepoVerificationError)
144
148
  })
@@ -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
+ })