@atproto/repo 0.2.0 → 0.3.1

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 (88) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1 -1
  4. package/build.js +0 -8
  5. package/dist/data-diff.d.ts +11 -9
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.js +1620 -2953
  8. package/dist/index.js.map +4 -4
  9. package/dist/logger.d.ts +2 -1
  10. package/dist/mst/mst.d.ts +10 -6
  11. package/dist/readable-repo.d.ts +1 -1
  12. package/dist/repo.d.ts +1 -4
  13. package/dist/storage/index.d.ts +0 -1
  14. package/dist/storage/memory-blockstore.d.ts +7 -12
  15. package/dist/storage/types.d.ts +28 -0
  16. package/dist/sync/consumer.d.ts +13 -17
  17. package/dist/sync/provider.d.ts +1 -5
  18. package/dist/types.d.ts +228 -39
  19. package/dist/util.d.ts +3 -5
  20. package/package.json +17 -24
  21. package/src/data-diff.ts +46 -44
  22. package/src/index.ts +0 -1
  23. package/src/logger.ts +2 -1
  24. package/src/mst/diff.ts +14 -36
  25. package/src/mst/mst.ts +14 -4
  26. package/src/readable-repo.ts +3 -3
  27. package/src/repo.ts +49 -70
  28. package/src/storage/index.ts +0 -1
  29. package/src/storage/memory-blockstore.ts +18 -77
  30. package/src/storage/types.ts +29 -0
  31. package/src/sync/consumer.ts +170 -116
  32. package/src/sync/provider.ts +2 -40
  33. package/src/types.ts +51 -24
  34. package/src/util.ts +24 -79
  35. package/tests/_util.ts +38 -67
  36. package/tests/mst.test.ts +4 -1
  37. package/tests/{sync/narrow.test.ts → proofs.test.ts} +9 -20
  38. package/tests/repo.test.ts +5 -4
  39. package/tests/sync.test.ts +97 -0
  40. package/tsconfig.build.json +1 -1
  41. package/tsconfig.json +3 -3
  42. package/dist/src/block-map.d.ts +0 -23
  43. package/dist/src/blockstore/index.d.ts +0 -2
  44. package/dist/src/blockstore/ipld-store.d.ts +0 -27
  45. package/dist/src/blockstore/memory-blockstore.d.ts +0 -13
  46. package/dist/src/blockstore/persistent-blockstore.d.ts +0 -12
  47. package/dist/src/cid-set.d.ts +0 -14
  48. package/dist/src/collection.d.ts +0 -22
  49. package/dist/src/data-diff.d.ts +0 -34
  50. package/dist/src/error.d.ts +0 -21
  51. package/dist/src/index.d.ts +0 -7
  52. package/dist/src/logger.d.ts +0 -2
  53. package/dist/src/mst/diff.d.ts +0 -33
  54. package/dist/src/mst/index.d.ts +0 -4
  55. package/dist/src/mst/mst.d.ts +0 -106
  56. package/dist/src/mst/util.d.ts +0 -9
  57. package/dist/src/mst/walker.d.ts +0 -22
  58. package/dist/src/parse.d.ts +0 -11
  59. package/dist/src/readable-repo.d.ts +0 -25
  60. package/dist/src/repo.d.ts +0 -39
  61. package/dist/src/storage/error.d.ts +0 -22
  62. package/dist/src/storage/index.d.ts +0 -1
  63. package/dist/src/storage/memory-blobstore.d.ts +0 -1
  64. package/dist/src/storage/memory-blockstore.d.ts +0 -28
  65. package/dist/src/storage/readable-blockstore.d.ts +0 -21
  66. package/dist/src/storage/repo-storage.d.ts +0 -18
  67. package/dist/src/storage/sync-storage.d.ts +0 -15
  68. package/dist/src/storage/types.d.ts +0 -12
  69. package/dist/src/storage/util.d.ts +0 -17
  70. package/dist/src/structure.d.ts +0 -39
  71. package/dist/src/sync/consumer.d.ts +0 -19
  72. package/dist/src/sync/index.d.ts +0 -2
  73. package/dist/src/sync/producer.d.ts +0 -13
  74. package/dist/src/sync/provider.d.ts +0 -11
  75. package/dist/src/sync.d.ts +0 -9
  76. package/dist/src/types.d.ts +0 -368
  77. package/dist/src/util.d.ts +0 -13
  78. package/dist/src/verify.d.ts +0 -5
  79. package/dist/storage/repo-storage.d.ts +0 -19
  80. package/dist/tsconfig.build.tsbuildinfo +0 -1
  81. package/dist/verify.d.ts +0 -32
  82. package/src/storage/repo-storage.ts +0 -43
  83. package/src/verify.ts +0 -268
  84. package/tests/rebase.test.ts +0 -37
  85. package/tests/sync/checkout.test.ts +0 -75
  86. package/tests/sync/diff.test.ts +0 -92
  87. package/tsconfig.build.tsbuildinfo +0 -1
  88. package/update-pkg.js +0 -14
@@ -1,5 +1,34 @@
1
1
  import stream from 'stream'
2
2
  import { CID } from 'multiformats/cid'
3
+ import { RepoRecord } from '@atproto/lexicon'
4
+ import { check } from '@atproto/common'
5
+ import BlockMap from '../block-map'
6
+ import { CommitData } from '../types'
7
+
8
+ export interface RepoStorage {
9
+ // Writable
10
+ getRoot(): Promise<CID | null>
11
+ putBlock(cid: CID, block: Uint8Array, rev: string): Promise<void>
12
+ putMany(blocks: BlockMap, rev: string): Promise<void>
13
+ updateRoot(cid: CID): Promise<void>
14
+ applyCommit(commit: CommitData)
15
+
16
+ // Readable
17
+ getBytes(cid: CID): Promise<Uint8Array | null>
18
+ has(cid: CID): Promise<boolean>
19
+ getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }>
20
+ attemptRead<T>(
21
+ cid: CID,
22
+ def: check.Def<T>,
23
+ ): Promise<{ obj: T; bytes: Uint8Array } | null>
24
+ readObjAndBytes<T>(
25
+ cid: CID,
26
+ def: check.Def<T>,
27
+ ): Promise<{ obj: T; bytes: Uint8Array }>
28
+ readObj<T>(cid: CID, def: check.Def<T>): Promise<T>
29
+ attemptReadRecord(cid: CID): Promise<RepoRecord | null>
30
+ readRecord(cid: CID): Promise<RepoRecord>
31
+ }
3
32
 
4
33
  export interface BlobStore {
5
34
  putTemp(bytes: Uint8Array | stream.Readable): Promise<string>
@@ -1,140 +1,194 @@
1
1
  import { CID } from 'multiformats/cid'
2
- import { MemoryBlockstore, RepoStorage } from '../storage'
3
- import Repo from '../repo'
4
- import * as verify from '../verify'
2
+ import { MemoryBlockstore, ReadableBlockstore, SyncStorage } from '../storage'
3
+ import DataDiff from '../data-diff'
4
+ import ReadableRepo from '../readable-repo'
5
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
- ])
6
+ import { RecordClaim, VerifiedDiff, VerifiedRepo } from '../types'
7
+ import { def } from '../types'
8
+ import { MST } from '../mst'
9
+ import { cidForCbor } from '@atproto/common'
10
+ import BlockMap from '../block-map'
11
+
12
+ export const verifyRepoCar = async (
13
+ carBytes: Uint8Array,
14
+ did?: string,
15
+ signingKey?: string,
16
+ ): Promise<VerifiedRepo> => {
17
+ const car = await util.readCarWithRoot(carBytes)
18
+ return verifyRepo(car.blocks, car.root, did, signingKey)
19
+ }
38
20
 
21
+ export const verifyRepo = async (
22
+ blocks: BlockMap,
23
+ head: CID,
24
+ did?: string,
25
+ signingKey?: string,
26
+ ): Promise<VerifiedRepo> => {
27
+ const diff = await verifyDiff(null, blocks, head, did, signingKey)
28
+ const creates = util.ensureCreates(diff.writes)
39
29
  return {
40
- root,
41
- contents: checkout.contents,
30
+ creates,
31
+ commit: diff.commit,
42
32
  }
43
33
  }
44
34
 
45
- // Diffs
46
- // -------------
35
+ export const verifyDiffCar = async (
36
+ repo: ReadableRepo | null,
37
+ carBytes: Uint8Array,
38
+ did?: string,
39
+ signingKey?: string,
40
+ ): Promise<VerifiedDiff> => {
41
+ const car = await util.readCarWithRoot(carBytes)
42
+ return verifyDiff(repo, car.blocks, car.root, did, signingKey)
43
+ }
47
44
 
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(
45
+ export const verifyDiff = async (
46
+ repo: ReadableRepo | null,
47
+ updateBlocks: BlockMap,
48
+ updateRoot: CID,
49
+ did?: string,
50
+ signingKey?: string,
51
+ ): Promise<VerifiedDiff> => {
52
+ const stagedStorage = new MemoryBlockstore(updateBlocks)
53
+ const updateStorage = repo
54
+ ? new SyncStorage(stagedStorage, repo.storage)
55
+ : stagedStorage
56
+ const updated = await verifyRepoRoot(
57
57
  updateStorage,
58
- root,
58
+ updateRoot,
59
59
  did,
60
60
  signingKey,
61
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
-
62
+ const diff = await DataDiff.of(updated.data, repo?.data ?? null)
63
+ const writes = await util.diffToWriteDescripts(diff, updateBlocks)
64
+ const newBlocks = diff.newMstBlocks
65
+ const leaves = updateBlocks.getMany(diff.newLeafCids.toList())
66
+ if (leaves.missing.length > 0) {
67
+ throw new Error(`missing leaf blocks: ${leaves.missing}`)
68
+ }
69
+ newBlocks.addMap(leaves.blocks)
70
+ const removedCids = diff.removedCids
71
+ const commitCid = await newBlocks.add(updated.commit)
72
+ // ensure the commit cid actually changed
73
+ if (repo) {
74
+ if (commitCid.equals(repo.cid)) {
75
+ newBlocks.delete(commitCid)
76
+ } else {
77
+ removedCids.add(repo.cid)
78
+ }
79
+ }
70
80
  return {
71
- root,
72
- writeLog,
73
- repo,
81
+ writes,
82
+ commit: {
83
+ cid: updated.cid,
84
+ rev: updated.commit.rev,
85
+ prev: repo?.cid ?? null,
86
+ since: repo?.commit.rev ?? null,
87
+ newBlocks,
88
+ removedCids,
89
+ },
74
90
  }
75
91
  }
76
92
 
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,
93
+ // @NOTE only verifies the root, not the repo contents
94
+ const verifyRepoRoot = async (
95
+ storage: ReadableBlockstore,
96
+ head: CID,
97
+ did?: string,
98
+ signingKey?: string,
99
+ ): Promise<ReadableRepo> => {
100
+ const repo = await ReadableRepo.load(storage, head)
101
+ if (did !== undefined && repo.did !== did) {
102
+ throw new RepoVerificationError(`Invalid repo did: ${repo.did}`)
101
103
  }
104
+ if (signingKey !== undefined) {
105
+ const validSig = await util.verifyCommitSig(repo.commit, signingKey)
106
+ if (!validSig) {
107
+ throw new RepoVerificationError(
108
+ `Invalid signature on commit: ${repo.cid.toString()}`,
109
+ )
110
+ }
111
+ }
112
+ return repo
102
113
  }
103
114
 
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
+ export const verifyProofs = async (
116
+ proofs: Uint8Array,
117
+ claims: RecordClaim[],
118
+ did: string,
119
+ didKey: string,
120
+ ): Promise<{ verified: RecordClaim[]; unverified: RecordClaim[] }> => {
121
+ const car = await util.readCarWithRoot(proofs)
122
+ const blockstore = new MemoryBlockstore(car.blocks)
123
+ const commit = await blockstore.readObj(car.root, def.commit)
124
+ if (commit.did !== did) {
125
+ throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
115
126
  }
116
-
117
- const diffBlocks = await updateStorage.getBlocks(newCids.toList())
118
- if (diffBlocks.missing.length > 0) {
119
- throw new MissingBlocksError('sync', diffBlocks.missing)
127
+ const validSig = await util.verifyCommitSig(commit, didKey)
128
+ if (!validSig) {
129
+ throw new RepoVerificationError(
130
+ `Invalid signature on commit: ${car.root.toString()}`,
131
+ )
120
132
  }
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,
133
+ const mst = MST.load(blockstore, commit.data)
134
+ const verified: RecordClaim[] = []
135
+ const unverified: RecordClaim[] = []
136
+ for (const claim of claims) {
137
+ const found = await mst.get(
138
+ util.formatDataKey(claim.collection, claim.rkey),
139
+ )
140
+ const record = found ? await blockstore.readObj(found, def.map) : null
141
+ if (claim.record === null) {
142
+ if (record === null) {
143
+ verified.push(claim)
144
+ } else {
145
+ unverified.push(claim)
146
+ }
147
+ } else {
148
+ const expected = await cidForCbor(claim.record)
149
+ if (expected.equals(found)) {
150
+ verified.push(claim)
151
+ } else {
152
+ unverified.push(claim)
153
+ }
130
154
  }
131
- })
132
-
133
- await storage.indexCommits(commits)
155
+ }
156
+ return { verified, unverified }
157
+ }
134
158
 
135
- return Promise.all(
136
- updates.map((upd) =>
137
- util.diffToWriteDescripts(upd.diff, diffBlocks.blocks),
138
- ),
139
- )
159
+ export const verifyRecords = async (
160
+ proofs: Uint8Array,
161
+ did: string,
162
+ signingKey: string,
163
+ ): Promise<RecordClaim[]> => {
164
+ const car = await util.readCarWithRoot(proofs)
165
+ const blockstore = new MemoryBlockstore(car.blocks)
166
+ const commit = await blockstore.readObj(car.root, def.commit)
167
+ if (commit.did !== did) {
168
+ throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
169
+ }
170
+ const validSig = await util.verifyCommitSig(commit, signingKey)
171
+ if (!validSig) {
172
+ throw new RepoVerificationError(
173
+ `Invalid signature on commit: ${car.root.toString()}`,
174
+ )
175
+ }
176
+ const mst = MST.load(blockstore, commit.data)
177
+
178
+ const records: RecordClaim[] = []
179
+ const leaves = await mst.reachableLeaves()
180
+ for (const leaf of leaves) {
181
+ const { collection, rkey } = util.parseDataKey(leaf.key)
182
+ const record = await blockstore.attemptReadRecord(leaf.value)
183
+ if (record) {
184
+ records.push({
185
+ collection,
186
+ rkey,
187
+ record,
188
+ })
189
+ }
190
+ }
191
+ return records
140
192
  }
193
+
194
+ export class RepoVerificationError extends Error {}
@@ -7,10 +7,10 @@ import { RepoStorage } from '../storage'
7
7
  import * as util from '../util'
8
8
  import { MST } from '../mst'
9
9
 
10
- // Checkouts
10
+ // Full Repo
11
11
  // -------------
12
12
 
13
- export const getCheckout = (
13
+ export const getFullRepo = (
14
14
  storage: RepoStorage,
15
15
  commitCid: CID,
16
16
  ): AsyncIterable<Uint8Array> => {
@@ -22,44 +22,6 @@ export const getCheckout = (
22
22
  })
23
23
  }
24
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
25
  // Narrow slices
64
26
  // -------------
65
27
 
package/src/types.ts CHANGED
@@ -1,32 +1,55 @@
1
1
  import { z } from 'zod'
2
- import { schema as common, def as commonDef } from '@atproto/common'
2
+ import { def as commonDef } from '@atproto/common-web'
3
+ import { schema as common } from '@atproto/common'
3
4
  import { CID } from 'multiformats'
4
5
  import BlockMap from './block-map'
5
6
  import { RepoRecord } from '@atproto/lexicon'
7
+ import CidSet from './cid-set'
6
8
 
7
9
  // Repo nodes
8
10
  // ---------------
9
11
 
10
12
  const unsignedCommit = z.object({
11
13
  did: z.string(),
12
- version: z.number(),
13
- prev: common.cid.nullable(),
14
+ version: z.literal(3),
14
15
  data: common.cid,
16
+ rev: z.string(),
17
+ // `prev` added for backwards compatibility with v2, no requirement of keeping around history
18
+ prev: common.cid.nullable().optional(),
15
19
  })
16
20
  export type UnsignedCommit = z.infer<typeof unsignedCommit> & { sig?: never }
17
21
 
18
22
  const commit = z.object({
19
23
  did: z.string(),
20
- version: z.number(),
21
- prev: common.cid.nullable(),
24
+ version: z.literal(3),
22
25
  data: common.cid,
26
+ rev: z.string(),
27
+ prev: common.cid.nullable().optional(),
23
28
  sig: common.bytes,
24
29
  })
25
30
  export type Commit = z.infer<typeof commit>
26
31
 
32
+ const legacyV2Commit = z.object({
33
+ did: z.string(),
34
+ version: z.literal(2),
35
+ data: common.cid,
36
+ rev: z.string().optional(),
37
+ prev: common.cid.nullable(),
38
+ sig: common.bytes,
39
+ })
40
+ export type LegacyV2Commit = z.infer<typeof legacyV2Commit>
41
+
42
+ const versionedCommit = z.discriminatedUnion('version', [
43
+ commit,
44
+ legacyV2Commit,
45
+ ])
46
+ export type VersionedCommit = z.infer<typeof versionedCommit>
47
+
27
48
  export const schema = {
28
49
  ...common,
29
50
  commit,
51
+ legacyV2Commit,
52
+ versionedCommit,
30
53
  }
31
54
 
32
55
  export const def = {
@@ -35,6 +58,10 @@ export const def = {
35
58
  name: 'commit',
36
59
  schema: schema.commit,
37
60
  },
61
+ versionedCommit: {
62
+ name: 'versioned_commit',
63
+ schema: schema.versionedCommit,
64
+ },
38
65
  }
39
66
 
40
67
  // Repo Operations
@@ -91,26 +118,13 @@ export type WriteLog = RecordWriteDescript[][]
91
118
  // Updates/Commits
92
119
  // ---------------
93
120
 
94
- export type CommitBlockData = {
95
- commit: CID
96
- blocks: BlockMap
97
- }
98
-
99
- export type CommitData = CommitBlockData & {
100
- prev: CID | null
101
- }
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
121
+ export type CommitData = {
122
+ cid: CID
123
+ rev: string
124
+ since: string | null
112
125
  prev: CID | null
113
- cids: CID[]
126
+ newBlocks: BlockMap
127
+ removedCids: CidSet
114
128
  }
115
129
 
116
130
  export type RepoUpdate = CommitData & {
@@ -136,3 +150,16 @@ export type RecordClaim = {
136
150
  rkey: string
137
151
  record: RepoRecord | null
138
152
  }
153
+
154
+ // Sync
155
+ // ---------------
156
+
157
+ export type VerifiedDiff = {
158
+ writes: RecordWriteDescript[]
159
+ commit: CommitData
160
+ }
161
+
162
+ export type VerifiedRepo = {
163
+ creates: RecordCreateDescript[]
164
+ commit: CommitData
165
+ }
package/src/util.ts CHANGED
@@ -11,27 +11,24 @@ import {
11
11
  schema,
12
12
  cidForCbor,
13
13
  byteIterableToStream,
14
+ TID,
14
15
  } from '@atproto/common'
15
16
  import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon'
16
17
 
17
18
  import * as crypto from '@atproto/crypto'
18
- import Repo from './repo'
19
- import { MST } from './mst'
20
19
  import DataDiff from './data-diff'
21
- import { RepoStorage } from './storage'
22
20
  import {
23
21
  Commit,
22
+ LegacyV2Commit,
24
23
  RecordCreateDescript,
25
24
  RecordDeleteDescript,
26
25
  RecordPath,
27
26
  RecordUpdateDescript,
28
27
  RecordWriteDescript,
29
28
  UnsignedCommit,
30
- WriteLog,
31
29
  WriteOpAction,
32
30
  } from './types'
33
31
  import BlockMap from './block-map'
34
- import { MissingBlocksError } from './error'
35
32
  import * as parse from './parse'
36
33
  import { Keypair } from '@atproto/crypto'
37
34
  import { Readable } from 'stream'
@@ -120,33 +117,6 @@ export const readCarWithRoot = async (
120
117
  }
121
118
  }
122
119
 
123
- export const getWriteLog = async (
124
- storage: RepoStorage,
125
- latest: CID,
126
- earliest: CID | null,
127
- ): Promise<WriteLog> => {
128
- const commits = await storage.getCommitPath(latest, earliest)
129
- if (!commits) throw new Error('Could not find shared history')
130
- const heads = await Promise.all(commits.map((c) => Repo.load(storage, c)))
131
- // Turn commit path into list of diffs
132
- let prev = await MST.create(storage) // Empty
133
- const msts = heads.map((h) => h.data)
134
- const diffs: DataDiff[] = []
135
- for (const mst of msts) {
136
- diffs.push(await DataDiff.of(mst, prev))
137
- prev = mst
138
- }
139
- const fullDiff = collapseDiffs(diffs)
140
- const diffBlocks = await storage.getBlocks(fullDiff.newCidList())
141
- if (diffBlocks.missing.length > 0) {
142
- throw new MissingBlocksError('write op log', diffBlocks.missing)
143
- }
144
- // Map MST diffs to write ops
145
- return Promise.all(
146
- diffs.map((diff) => diffToWriteDescripts(diff, diffBlocks.blocks)),
147
- )
148
- }
149
-
150
120
  export const diffToWriteDescripts = (
151
121
  diff: DataDiff,
152
122
  blocks: BlockMap,
@@ -187,55 +157,18 @@ export const diffToWriteDescripts = (
187
157
  ])
188
158
  }
189
159
 
190
- export const collapseWriteLog = (log: WriteLog): RecordWriteDescript[] => {
191
- const creates: Record<string, RecordCreateDescript> = {}
192
- const updates: Record<string, RecordUpdateDescript> = {}
193
- const deletes: Record<string, RecordDeleteDescript> = {}
194
- for (const commit of log) {
195
- for (const op of commit) {
196
- const key = op.collection + '/' + op.rkey
197
- if (op.action === WriteOpAction.Create) {
198
- const del = deletes[key]
199
- if (del) {
200
- if (del.cid !== op.cid) {
201
- updates[key] = {
202
- ...op,
203
- action: WriteOpAction.Update,
204
- prev: del.cid,
205
- }
206
- }
207
- delete deletes[key]
208
- } else {
209
- creates[key] = op
210
- }
211
- } else if (op.action === WriteOpAction.Update) {
212
- updates[key] = op
213
- delete creates[key]
214
- delete deletes[key]
215
- } else if (op.action === WriteOpAction.Delete) {
216
- if (creates[key]) {
217
- delete creates[key]
218
- } else {
219
- delete updates[key]
220
- deletes[key] = op
221
- }
222
- } else {
223
- throw new Error(`unknown action: ${op}`)
224
- }
160
+ export const ensureCreates = (
161
+ descripts: RecordWriteDescript[],
162
+ ): RecordCreateDescript[] => {
163
+ const creates: RecordCreateDescript[] = []
164
+ for (const descript of descripts) {
165
+ if (descript.action !== WriteOpAction.Create) {
166
+ throw new Error(`Unexpected action: ${descript.action}`)
167
+ } else {
168
+ creates.push(descript)
225
169
  }
226
170
  }
227
- return [
228
- ...Object.values(creates),
229
- ...Object.values(updates),
230
- ...Object.values(deletes),
231
- ]
232
- }
233
-
234
- export const collapseDiffs = (diffs: DataDiff[]): DataDiff => {
235
- return diffs.reduce((acc, cur) => {
236
- acc.addDiff(cur)
237
- return acc
238
- }, new DataDiff())
171
+ return creates
239
172
  }
240
173
 
241
174
  export const parseDataKey = (key: string): RecordPath => {
@@ -288,3 +221,15 @@ export const cborToLexRecord = (val: Uint8Array): RepoRecord => {
288
221
  export const cidForRecord = async (val: LexValue) => {
289
222
  return cidForCbor(lexToIpld(val))
290
223
  }
224
+
225
+ export const ensureV3Commit = (commit: LegacyV2Commit | Commit): Commit => {
226
+ if (commit.version === 3) {
227
+ return commit
228
+ } else {
229
+ return {
230
+ ...commit,
231
+ version: 3,
232
+ rev: commit.rev ?? TID.nextStr(),
233
+ }
234
+ }
235
+ }