@atproto/repo 0.2.0 → 0.3.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.
- package/dist/data-diff.d.ts +11 -9
- package/dist/index.d.ts +0 -1
- package/dist/index.js +404 -1655
- package/dist/index.js.map +4 -4
- package/dist/mst/mst.d.ts +10 -6
- package/dist/readable-repo.d.ts +1 -1
- package/dist/repo.d.ts +1 -4
- package/dist/storage/index.d.ts +0 -1
- package/dist/storage/memory-blockstore.d.ts +7 -12
- package/dist/storage/types.d.ts +28 -0
- package/dist/sync/consumer.d.ts +13 -17
- package/dist/sync/provider.d.ts +1 -5
- package/dist/types.d.ts +228 -39
- package/dist/util.d.ts +3 -5
- package/package.json +2 -2
- package/src/data-diff.ts +46 -44
- package/src/index.ts +0 -1
- package/src/mst/diff.ts +14 -36
- package/src/mst/mst.ts +14 -4
- package/src/readable-repo.ts +3 -3
- package/src/repo.ts +49 -70
- package/src/storage/index.ts +0 -1
- package/src/storage/memory-blockstore.ts +18 -77
- package/src/storage/types.ts +29 -0
- package/src/sync/consumer.ts +170 -116
- package/src/sync/provider.ts +2 -40
- package/src/types.ts +49 -23
- package/src/util.ts +24 -79
- package/tests/_util.ts +38 -67
- package/tests/mst.test.ts +4 -1
- package/tests/{sync/narrow.test.ts → proofs.test.ts} +9 -20
- package/tests/repo.test.ts +5 -4
- package/tests/sync.test.ts +97 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/src/block-map.d.ts +0 -23
- package/dist/src/blockstore/index.d.ts +0 -2
- package/dist/src/blockstore/ipld-store.d.ts +0 -27
- package/dist/src/blockstore/memory-blockstore.d.ts +0 -13
- package/dist/src/blockstore/persistent-blockstore.d.ts +0 -12
- package/dist/src/cid-set.d.ts +0 -14
- package/dist/src/collection.d.ts +0 -22
- package/dist/src/data-diff.d.ts +0 -34
- package/dist/src/error.d.ts +0 -21
- package/dist/src/index.d.ts +0 -7
- package/dist/src/logger.d.ts +0 -2
- package/dist/src/mst/diff.d.ts +0 -33
- package/dist/src/mst/index.d.ts +0 -4
- package/dist/src/mst/mst.d.ts +0 -106
- package/dist/src/mst/util.d.ts +0 -9
- package/dist/src/mst/walker.d.ts +0 -22
- package/dist/src/parse.d.ts +0 -11
- package/dist/src/readable-repo.d.ts +0 -25
- package/dist/src/repo.d.ts +0 -39
- package/dist/src/storage/error.d.ts +0 -22
- package/dist/src/storage/index.d.ts +0 -1
- package/dist/src/storage/memory-blobstore.d.ts +0 -1
- package/dist/src/storage/memory-blockstore.d.ts +0 -28
- package/dist/src/storage/readable-blockstore.d.ts +0 -21
- package/dist/src/storage/repo-storage.d.ts +0 -18
- package/dist/src/storage/sync-storage.d.ts +0 -15
- package/dist/src/storage/types.d.ts +0 -12
- package/dist/src/storage/util.d.ts +0 -17
- package/dist/src/structure.d.ts +0 -39
- package/dist/src/sync/consumer.d.ts +0 -19
- package/dist/src/sync/index.d.ts +0 -2
- package/dist/src/sync/producer.d.ts +0 -13
- package/dist/src/sync/provider.d.ts +0 -11
- package/dist/src/sync.d.ts +0 -9
- package/dist/src/types.d.ts +0 -368
- package/dist/src/util.d.ts +0 -13
- package/dist/src/verify.d.ts +0 -5
- package/dist/storage/repo-storage.d.ts +0 -19
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/dist/verify.d.ts +0 -32
- package/src/storage/repo-storage.ts +0 -43
- package/src/verify.ts +0 -268
- package/tests/rebase.test.ts +0 -37
- package/tests/sync/checkout.test.ts +0 -75
- package/tests/sync/diff.test.ts +0 -92
package/src/sync/consumer.ts
CHANGED
|
@@ -1,140 +1,194 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import { MemoryBlockstore,
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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 {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
41
|
-
|
|
30
|
+
creates,
|
|
31
|
+
commit: diff.commit,
|
|
42
32
|
}
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
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
|
-
|
|
58
|
+
updateRoot,
|
|
59
59
|
did,
|
|
60
60
|
signingKey,
|
|
61
61
|
)
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
155
|
+
}
|
|
156
|
+
return { verified, unverified }
|
|
157
|
+
}
|
|
134
158
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 {}
|
package/src/sync/provider.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { RepoStorage } from '../storage'
|
|
|
7
7
|
import * as util from '../util'
|
|
8
8
|
import { MST } from '../mst'
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// Full Repo
|
|
11
11
|
// -------------
|
|
12
12
|
|
|
13
|
-
export const
|
|
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
|
@@ -3,30 +3,52 @@ import { schema as common, def as commonDef } from '@atproto/common'
|
|
|
3
3
|
import { CID } from 'multiformats'
|
|
4
4
|
import BlockMap from './block-map'
|
|
5
5
|
import { RepoRecord } from '@atproto/lexicon'
|
|
6
|
+
import CidSet from './cid-set'
|
|
6
7
|
|
|
7
8
|
// Repo nodes
|
|
8
9
|
// ---------------
|
|
9
10
|
|
|
10
11
|
const unsignedCommit = z.object({
|
|
11
12
|
did: z.string(),
|
|
12
|
-
version: z.
|
|
13
|
-
prev: common.cid.nullable(),
|
|
13
|
+
version: z.literal(3),
|
|
14
14
|
data: common.cid,
|
|
15
|
+
rev: z.string(),
|
|
16
|
+
// `prev` added for backwards compatibility with v2, no requirement of keeping around history
|
|
17
|
+
prev: common.cid.nullable().optional(),
|
|
15
18
|
})
|
|
16
19
|
export type UnsignedCommit = z.infer<typeof unsignedCommit> & { sig?: never }
|
|
17
20
|
|
|
18
21
|
const commit = z.object({
|
|
19
22
|
did: z.string(),
|
|
20
|
-
version: z.
|
|
21
|
-
prev: common.cid.nullable(),
|
|
23
|
+
version: z.literal(3),
|
|
22
24
|
data: common.cid,
|
|
25
|
+
rev: z.string(),
|
|
26
|
+
prev: common.cid.nullable().optional(),
|
|
23
27
|
sig: common.bytes,
|
|
24
28
|
})
|
|
25
29
|
export type Commit = z.infer<typeof commit>
|
|
26
30
|
|
|
31
|
+
const legacyV2Commit = z.object({
|
|
32
|
+
did: z.string(),
|
|
33
|
+
version: z.literal(2),
|
|
34
|
+
data: common.cid,
|
|
35
|
+
rev: z.string().optional(),
|
|
36
|
+
prev: common.cid.nullable(),
|
|
37
|
+
sig: common.bytes,
|
|
38
|
+
})
|
|
39
|
+
export type LegacyV2Commit = z.infer<typeof legacyV2Commit>
|
|
40
|
+
|
|
41
|
+
const versionedCommit = z.discriminatedUnion('version', [
|
|
42
|
+
commit,
|
|
43
|
+
legacyV2Commit,
|
|
44
|
+
])
|
|
45
|
+
export type VersionedCommit = z.infer<typeof versionedCommit>
|
|
46
|
+
|
|
27
47
|
export const schema = {
|
|
28
48
|
...common,
|
|
29
49
|
commit,
|
|
50
|
+
legacyV2Commit,
|
|
51
|
+
versionedCommit,
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
export const def = {
|
|
@@ -35,6 +57,10 @@ export const def = {
|
|
|
35
57
|
name: 'commit',
|
|
36
58
|
schema: schema.commit,
|
|
37
59
|
},
|
|
60
|
+
versionedCommit: {
|
|
61
|
+
name: 'versioned_commit',
|
|
62
|
+
schema: schema.versionedCommit,
|
|
63
|
+
},
|
|
38
64
|
}
|
|
39
65
|
|
|
40
66
|
// Repo Operations
|
|
@@ -91,26 +117,13 @@ export type WriteLog = RecordWriteDescript[][]
|
|
|
91
117
|
// Updates/Commits
|
|
92
118
|
// ---------------
|
|
93
119
|
|
|
94
|
-
export type
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
120
|
+
export type CommitData = {
|
|
121
|
+
cid: CID
|
|
122
|
+
rev: string
|
|
123
|
+
since: string | null
|
|
112
124
|
prev: CID | null
|
|
113
|
-
|
|
125
|
+
newBlocks: BlockMap
|
|
126
|
+
removedCids: CidSet
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
export type RepoUpdate = CommitData & {
|
|
@@ -136,3 +149,16 @@ export type RecordClaim = {
|
|
|
136
149
|
rkey: string
|
|
137
150
|
record: RepoRecord | null
|
|
138
151
|
}
|
|
152
|
+
|
|
153
|
+
// Sync
|
|
154
|
+
// ---------------
|
|
155
|
+
|
|
156
|
+
export type VerifiedDiff = {
|
|
157
|
+
writes: RecordWriteDescript[]
|
|
158
|
+
commit: CommitData
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type VerifiedRepo = {
|
|
162
|
+
creates: RecordCreateDescript[]
|
|
163
|
+
commit: CommitData
|
|
164
|
+
}
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
for (const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
}
|