@atproto/repo 0.1.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/block-map.d.ts +2 -0
- package/dist/data-diff.d.ts +12 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12388 -4431
- package/dist/index.js.map +4 -4
- package/dist/mst/mst.d.ts +19 -16
- package/dist/readable-repo.d.ts +4 -3
- package/dist/repo.d.ts +3 -2
- package/dist/storage/index.d.ts +0 -1
- package/dist/storage/memory-blockstore.d.ts +6 -10
- package/dist/storage/types.d.ts +29 -0
- package/dist/sync/consumer.d.ts +13 -16
- package/dist/sync/provider.d.ts +2 -6
- package/dist/types.d.ts +236 -48
- package/dist/util.d.ts +9 -7
- package/jest.bench.config.js +2 -1
- package/package.json +12 -7
- package/src/block-map.ts +8 -0
- package/src/data-diff.ts +47 -49
- package/src/index.ts +1 -1
- package/src/mst/diff.ts +14 -36
- package/src/mst/mst.ts +15 -14
- package/src/readable-repo.ts +5 -5
- package/src/repo.ts +50 -40
- package/src/storage/index.ts +0 -1
- package/src/storage/memory-blockstore.ts +19 -59
- package/src/storage/types.ts +30 -0
- package/src/sync/consumer.ts +170 -113
- package/src/sync/provider.ts +6 -44
- package/src/types.ts +49 -25
- package/src/util.ts +57 -91
- package/tests/_util.ts +38 -67
- package/tests/mst.test.ts +4 -1
- package/tests/{sync/narrow.test.ts → proofs.test.ts} +14 -21
- package/tests/repo.test.ts +5 -4
- package/tests/sync.test.ts +97 -0
- package/tests/util.test.ts +21 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +1 -1
- package/dist/blockstore/index.d.ts +0 -2
- package/dist/blockstore/ipld-store.d.ts +0 -27
- package/dist/blockstore/memory-blockstore.d.ts +0 -13
- package/dist/storage/repo-storage.d.ts +0 -18
- package/dist/sync.d.ts +0 -9
- package/dist/verify.d.ts +0 -27
- package/src/storage/repo-storage.ts +0 -42
- package/src/verify.ts +0 -227
- package/tests/sync/checkout.test.ts +0 -57
- package/tests/sync/diff.test.ts +0 -87
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import { CommitData
|
|
2
|
+
import { CommitData } from '../types'
|
|
3
3
|
import BlockMap from '../block-map'
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { MissingCommitBlocksError } from '../error'
|
|
7
|
-
import RepoStorage from './repo-storage'
|
|
4
|
+
import ReadableBlockstore from './readable-blockstore'
|
|
5
|
+
import { RepoStorage } from './types'
|
|
8
6
|
|
|
9
|
-
export class MemoryBlockstore
|
|
7
|
+
export class MemoryBlockstore
|
|
8
|
+
extends ReadableBlockstore
|
|
9
|
+
implements RepoStorage
|
|
10
|
+
{
|
|
10
11
|
blocks: BlockMap
|
|
11
|
-
|
|
12
|
+
root: CID | null = null
|
|
12
13
|
|
|
13
14
|
constructor(blocks?: BlockMap) {
|
|
14
15
|
super()
|
|
@@ -18,8 +19,8 @@ export class MemoryBlockstore extends RepoStorage {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
async
|
|
22
|
-
return this.
|
|
22
|
+
async getRoot(): Promise<CID | null> {
|
|
23
|
+
return this.root
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
async getBytes(cid: CID): Promise<Uint8Array | null> {
|
|
@@ -42,60 +43,19 @@ export class MemoryBlockstore extends RepoStorage {
|
|
|
42
43
|
this.blocks.addMap(blocks)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
async
|
|
46
|
-
|
|
47
|
-
this.blocks.addMap(commit.blocks)
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async updateHead(cid: CID, _prev: CID | null): Promise<void> {
|
|
52
|
-
this.head = cid
|
|
46
|
+
async updateRoot(cid: CID): Promise<void> {
|
|
47
|
+
this.root = cid
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
async applyCommit(commit: CommitData): Promise<void> {
|
|
56
|
-
this.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
async getCommitPath(
|
|
61
|
-
latest: CID,
|
|
62
|
-
earliest: CID | null,
|
|
63
|
-
): Promise<CID[] | null> {
|
|
64
|
-
let curr: CID | null = latest
|
|
65
|
-
const path: CID[] = []
|
|
66
|
-
while (curr !== null) {
|
|
67
|
-
path.push(curr)
|
|
68
|
-
const commit = await this.readObj(curr, def.commit)
|
|
69
|
-
if (!earliest && commit.prev === null) {
|
|
70
|
-
return path.reverse()
|
|
71
|
-
} else if (earliest && commit.prev.equals(earliest)) {
|
|
72
|
-
return path.reverse()
|
|
73
|
-
}
|
|
74
|
-
curr = commit.prev
|
|
51
|
+
this.root = commit.cid
|
|
52
|
+
const rmCids = commit.removedCids.toList()
|
|
53
|
+
for (const cid of rmCids) {
|
|
54
|
+
this.blocks.delete(cid)
|
|
75
55
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
async getBlocksForCommits(
|
|
80
|
-
commits: CID[],
|
|
81
|
-
): Promise<{ [commit: string]: BlockMap }> {
|
|
82
|
-
const commitData: { [commit: string]: BlockMap } = {}
|
|
83
|
-
let prevData: MST | null = null
|
|
84
|
-
for (const commitCid of commits) {
|
|
85
|
-
const commit = await this.readObj(commitCid, def.commit)
|
|
86
|
-
const data = await MST.load(this, commit.data)
|
|
87
|
-
const diff = await DataDiff.of(data, prevData)
|
|
88
|
-
const { blocks, missing } = await this.getBlocks([
|
|
89
|
-
commitCid,
|
|
90
|
-
...diff.newCidList(),
|
|
91
|
-
])
|
|
92
|
-
if (missing.length > 0) {
|
|
93
|
-
throw new MissingCommitBlocksError(commitCid, missing)
|
|
94
|
-
}
|
|
95
|
-
commitData[commitCid.toString()] = blocks
|
|
96
|
-
prevData = data
|
|
97
|
-
}
|
|
98
|
-
return commitData
|
|
56
|
+
commit.newBlocks.forEach((bytes, cid) => {
|
|
57
|
+
this.blocks.set(cid, bytes)
|
|
58
|
+
})
|
|
99
59
|
}
|
|
100
60
|
|
|
101
61
|
async sizeInBytes(): Promise<number> {
|
package/src/storage/types.ts
CHANGED
|
@@ -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>
|
|
@@ -9,6 +38,7 @@ export interface BlobStore {
|
|
|
9
38
|
unquarantine(cid: CID): Promise<void>
|
|
10
39
|
getBytes(cid: CID): Promise<Uint8Array>
|
|
11
40
|
getStream(cid: CID): Promise<stream.Readable>
|
|
41
|
+
hasStored(cid: CID): Promise<boolean>
|
|
12
42
|
delete(cid: CID): Promise<void>
|
|
13
43
|
}
|
|
14
44
|
|
package/src/sync/consumer.ts
CHANGED
|
@@ -1,137 +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
|
-
|
|
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
|
+
}
|
|
68
80
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
},
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
repo
|
|
84
|
-
updateStorage,
|
|
85
|
-
root,
|
|
86
|
-
did,
|
|
87
|
-
signingKey,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
const [writeLog] = await Promise.all([
|
|
91
|
-
persistUpdates(repo.storage, updateStorage, updates),
|
|
92
|
-
repo.storage.updateHead(root, repo.cid),
|
|
93
|
-
])
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
root,
|
|
97
|
-
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}`)
|
|
98
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
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
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}`)
|
|
112
126
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
const validSig = await util.verifyCommitSig(commit, didKey)
|
|
128
|
+
if (!validSig) {
|
|
129
|
+
throw new RepoVerificationError(
|
|
130
|
+
`Invalid signature on commit: ${car.root.toString()}`,
|
|
131
|
+
)
|
|
117
132
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
}
|
|
127
154
|
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
155
|
+
}
|
|
156
|
+
return { verified, unverified }
|
|
157
|
+
}
|
|
131
158
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
192
|
}
|
|
193
|
+
|
|
194
|
+
export class RepoVerificationError extends Error {}
|
package/src/sync/provider.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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'
|
|
@@ -7,13 +7,13 @@ 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> => {
|
|
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,52 +22,14 @@ export const getCheckout = async (
|
|
|
22
22
|
})
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// Diffs
|
|
26
|
-
// -------------
|
|
27
|
-
|
|
28
|
-
export const getDiff = async (
|
|
29
|
-
storage: RepoStorage,
|
|
30
|
-
latest: CID,
|
|
31
|
-
earliest: CID | null,
|
|
32
|
-
): Promise<Uint8Array> => {
|
|
33
|
-
return util.writeCar(latest, (car: BlockWriter) => {
|
|
34
|
-
return writeCommitsToCarStream(storage, car, latest, earliest)
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const getFullRepo = async (
|
|
39
|
-
storage: RepoStorage,
|
|
40
|
-
cid: CID,
|
|
41
|
-
): Promise<Uint8Array> => {
|
|
42
|
-
return getDiff(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
|
|
|
66
|
-
export const getRecords =
|
|
28
|
+
export const getRecords = (
|
|
67
29
|
storage: RepoStorage,
|
|
68
30
|
commitCid: CID,
|
|
69
31
|
paths: RecordPath[],
|
|
70
|
-
):
|
|
32
|
+
): AsyncIterable<Uint8Array> => {
|
|
71
33
|
return util.writeCar(commitCid, async (car: BlockWriter) => {
|
|
72
34
|
const commit = await storage.readObjAndBytes(commitCid, def.commit)
|
|
73
35
|
await car.put({ cid: commitCid, bytes: commit.bytes })
|
package/src/types.ts
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
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'
|
|
6
5
|
import { RepoRecord } from '@atproto/lexicon'
|
|
6
|
+
import CidSet from './cid-set'
|
|
7
7
|
|
|
8
8
|
// Repo nodes
|
|
9
9
|
// ---------------
|
|
10
10
|
|
|
11
11
|
const unsignedCommit = z.object({
|
|
12
12
|
did: z.string(),
|
|
13
|
-
version: z.
|
|
14
|
-
prev: common.cid.nullable(),
|
|
13
|
+
version: z.literal(3),
|
|
15
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(),
|
|
16
18
|
})
|
|
17
19
|
export type UnsignedCommit = z.infer<typeof unsignedCommit> & { sig?: never }
|
|
18
20
|
|
|
19
21
|
const commit = z.object({
|
|
20
22
|
did: z.string(),
|
|
21
|
-
version: z.
|
|
22
|
-
prev: common.cid.nullable(),
|
|
23
|
+
version: z.literal(3),
|
|
23
24
|
data: common.cid,
|
|
25
|
+
rev: z.string(),
|
|
26
|
+
prev: common.cid.nullable().optional(),
|
|
24
27
|
sig: common.bytes,
|
|
25
28
|
})
|
|
26
29
|
export type Commit = z.infer<typeof commit>
|
|
27
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
|
+
|
|
28
47
|
export const schema = {
|
|
29
48
|
...common,
|
|
30
49
|
commit,
|
|
50
|
+
legacyV2Commit,
|
|
51
|
+
versionedCommit,
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
export const def = {
|
|
@@ -36,6 +57,10 @@ export const def = {
|
|
|
36
57
|
name: 'commit',
|
|
37
58
|
schema: schema.commit,
|
|
38
59
|
},
|
|
60
|
+
versionedCommit: {
|
|
61
|
+
name: 'versioned_commit',
|
|
62
|
+
schema: schema.versionedCommit,
|
|
63
|
+
},
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
// Repo Operations
|
|
@@ -92,13 +117,13 @@ export type WriteLog = RecordWriteDescript[][]
|
|
|
92
117
|
// Updates/Commits
|
|
93
118
|
// ---------------
|
|
94
119
|
|
|
95
|
-
export type
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
export type CommitData = CommitBlockData & {
|
|
120
|
+
export type CommitData = {
|
|
121
|
+
cid: CID
|
|
122
|
+
rev: string
|
|
123
|
+
since: string | null
|
|
101
124
|
prev: CID | null
|
|
125
|
+
newBlocks: BlockMap
|
|
126
|
+
removedCids: CidSet
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
export type RepoUpdate = CommitData & {
|
|
@@ -108,6 +133,12 @@ export type RepoUpdate = CommitData & {
|
|
|
108
133
|
export type CollectionContents = Record<string, RepoRecord>
|
|
109
134
|
export type RepoContents = Record<string, CollectionContents>
|
|
110
135
|
|
|
136
|
+
export type RepoRecordWithCid = { cid: CID; value: RepoRecord }
|
|
137
|
+
export type CollectionContentsWithCids = Record<string, RepoRecordWithCid>
|
|
138
|
+
export type RepoContentsWithCids = Record<string, CollectionContentsWithCids>
|
|
139
|
+
|
|
140
|
+
export type DatastoreContents = Record<string, CID>
|
|
141
|
+
|
|
111
142
|
export type RecordPath = {
|
|
112
143
|
collection: string
|
|
113
144
|
rkey: string
|
|
@@ -119,22 +150,15 @@ export type RecordClaim = {
|
|
|
119
150
|
record: RepoRecord | null
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
//
|
|
153
|
+
// Sync
|
|
123
154
|
// ---------------
|
|
124
155
|
|
|
125
|
-
export type
|
|
126
|
-
|
|
127
|
-
|
|
156
|
+
export type VerifiedDiff = {
|
|
157
|
+
writes: RecordWriteDescript[]
|
|
158
|
+
commit: CommitData
|
|
128
159
|
}
|
|
129
160
|
|
|
130
|
-
export
|
|
131
|
-
|
|
132
|
-
|
|
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[]>
|
|
161
|
+
export type VerifiedRepo = {
|
|
162
|
+
creates: RecordCreateDescript[]
|
|
163
|
+
commit: CommitData
|
|
140
164
|
}
|