@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
package/jest.bench.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/repo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/bluesky-social/atproto.git",
|
|
9
|
+
"directory": "packages/repo"
|
|
10
|
+
},
|
|
6
11
|
"scripts": {
|
|
7
12
|
"test": "jest",
|
|
8
13
|
"test:profile": "node --inspect ../../node_modules/.bin/jest",
|
|
9
14
|
"bench": "jest --config jest.bench.config.js",
|
|
10
|
-
"bench:profile": "node --inspect ../../node_modules/.bin/jest --config jest.bench.config.js",
|
|
11
|
-
"prettier": "prettier --check src/",
|
|
12
|
-
"prettier:fix": "prettier --write src/",
|
|
15
|
+
"bench:profile": "node --inspect-brk ../../node_modules/.bin/jest --config jest.bench.config.js",
|
|
16
|
+
"prettier": "prettier --check src/ tests/",
|
|
17
|
+
"prettier:fix": "prettier --write src/ tests/",
|
|
13
18
|
"lint": "eslint . --ext .ts,.tsx",
|
|
14
19
|
"lint:fix": "yarn lint --fix",
|
|
15
20
|
"verify": "run-p prettier lint",
|
|
@@ -24,13 +29,13 @@
|
|
|
24
29
|
"dependencies": {
|
|
25
30
|
"@atproto/common": "*",
|
|
26
31
|
"@atproto/crypto": "*",
|
|
27
|
-
"@atproto/
|
|
32
|
+
"@atproto/identity": "*",
|
|
28
33
|
"@atproto/lexicon": "*",
|
|
29
|
-
"@atproto/
|
|
34
|
+
"@atproto/syntax": "*",
|
|
30
35
|
"@ipld/car": "^3.2.3",
|
|
31
36
|
"@ipld/dag-cbor": "^7.0.0",
|
|
32
37
|
"multiformats": "^9.6.4",
|
|
33
38
|
"uint8arrays": "3.0.0",
|
|
34
|
-
"zod": "^3.
|
|
39
|
+
"zod": "^3.21.4"
|
|
35
40
|
}
|
|
36
41
|
}
|
package/src/block-map.ts
CHANGED
|
@@ -20,6 +20,10 @@ export class BlockMap {
|
|
|
20
20
|
return this.map.get(cid.toString())
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
delete(cid: CID) {
|
|
24
|
+
this.map.delete(cid.toString())
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
getMany(cids: CID[]): { blocks: BlockMap; missing: CID[] } {
|
|
24
28
|
const missing: CID[] = []
|
|
25
29
|
const blocks = new BlockMap()
|
|
@@ -54,6 +58,10 @@ export class BlockMap {
|
|
|
54
58
|
return entries
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
cids(): CID[] {
|
|
62
|
+
return this.entries().map((e) => e.cid)
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
addMap(toAdd: BlockMap) {
|
|
58
66
|
toAdd.forEach((bytes, cid) => {
|
|
59
67
|
this.set(cid, bytes)
|
package/src/data-diff.ts
CHANGED
|
@@ -1,81 +1,83 @@
|
|
|
1
1
|
import { CID } from 'multiformats'
|
|
2
2
|
import CidSet from './cid-set'
|
|
3
|
-
import { MST, mstDiff } from './mst'
|
|
4
|
-
import
|
|
3
|
+
import { MST, NodeEntry, mstDiff } from './mst'
|
|
4
|
+
import BlockMap from './block-map'
|
|
5
5
|
|
|
6
6
|
export class DataDiff {
|
|
7
7
|
adds: Record<string, DataAdd> = {}
|
|
8
8
|
updates: Record<string, DataUpdate> = {}
|
|
9
9
|
deletes: Record<string, DataDelete> = {}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
newMstBlocks: BlockMap = new BlockMap()
|
|
12
|
+
newLeafCids: CidSet = new CidSet()
|
|
12
13
|
removedCids: CidSet = new CidSet()
|
|
13
14
|
|
|
14
|
-
static async of(curr:
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
static async of(curr: MST, prev: MST | null): Promise<DataDiff> {
|
|
16
|
+
return mstDiff(curr, prev)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async nodeAdd(node: NodeEntry) {
|
|
20
|
+
if (node.isLeaf()) {
|
|
21
|
+
this.leafAdd(node.key, node.value)
|
|
22
|
+
} else {
|
|
23
|
+
const data = await node.serialize()
|
|
24
|
+
this.treeAdd(data.cid, data.bytes)
|
|
17
25
|
}
|
|
18
|
-
throw new Error('Unsupported DataStore type for diff')
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
async nodeDelete(node: NodeEntry) {
|
|
29
|
+
if (node.isLeaf()) {
|
|
30
|
+
const key = node.key
|
|
31
|
+
const cid = node.value
|
|
32
|
+
this.deletes[key] = { key, cid }
|
|
33
|
+
this.removedCids.add(cid)
|
|
34
|
+
} else {
|
|
35
|
+
const cid = await node.getPointer()
|
|
36
|
+
this.treeDelete(cid)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
leafAdd(key: string, cid: CID) {
|
|
22
41
|
this.adds[key] = { key, cid }
|
|
23
|
-
this.
|
|
42
|
+
if (this.removedCids.has(cid)) {
|
|
43
|
+
this.removedCids.delete(cid)
|
|
44
|
+
} else {
|
|
45
|
+
this.newLeafCids.add(cid)
|
|
46
|
+
}
|
|
24
47
|
}
|
|
25
48
|
|
|
26
|
-
|
|
49
|
+
leafUpdate(key: string, prev: CID, cid: CID) {
|
|
50
|
+
if (prev.equals(cid)) return
|
|
27
51
|
this.updates[key] = { key, prev, cid }
|
|
28
|
-
this.
|
|
52
|
+
this.removedCids.add(prev)
|
|
53
|
+
this.newLeafCids.add(cid)
|
|
29
54
|
}
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
leafDelete(key: string, cid: CID) {
|
|
32
57
|
this.deletes[key] = { key, cid }
|
|
58
|
+
if (this.newLeafCids.has(cid)) {
|
|
59
|
+
this.newLeafCids.delete(cid)
|
|
60
|
+
} else {
|
|
61
|
+
this.removedCids.add(cid)
|
|
62
|
+
}
|
|
33
63
|
}
|
|
34
64
|
|
|
35
|
-
|
|
65
|
+
treeAdd(cid: CID, bytes: Uint8Array) {
|
|
36
66
|
if (this.removedCids.has(cid)) {
|
|
37
67
|
this.removedCids.delete(cid)
|
|
38
68
|
} else {
|
|
39
|
-
this.
|
|
69
|
+
this.newMstBlocks.set(cid, bytes)
|
|
40
70
|
}
|
|
41
71
|
}
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
if (this.
|
|
45
|
-
this.
|
|
73
|
+
treeDelete(cid: CID) {
|
|
74
|
+
if (this.newMstBlocks.has(cid)) {
|
|
75
|
+
this.newMstBlocks.delete(cid)
|
|
46
76
|
} else {
|
|
47
77
|
this.removedCids.add(cid)
|
|
48
78
|
}
|
|
49
79
|
}
|
|
50
80
|
|
|
51
|
-
addDiff(diff: DataDiff) {
|
|
52
|
-
for (const add of diff.addList()) {
|
|
53
|
-
if (this.deletes[add.key]) {
|
|
54
|
-
const del = this.deletes[add.key]
|
|
55
|
-
if (del.cid !== add.cid) {
|
|
56
|
-
this.recordUpdate(add.key, del.cid, add.cid)
|
|
57
|
-
}
|
|
58
|
-
delete this.deletes[add.key]
|
|
59
|
-
} else {
|
|
60
|
-
this.recordAdd(add.key, add.cid)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
for (const update of diff.updateList()) {
|
|
64
|
-
this.recordUpdate(update.key, update.prev, update.cid)
|
|
65
|
-
delete this.adds[update.key]
|
|
66
|
-
delete this.deletes[update.key]
|
|
67
|
-
}
|
|
68
|
-
for (const del of diff.deleteList()) {
|
|
69
|
-
if (this.adds[del.key]) {
|
|
70
|
-
delete this.adds[del.key]
|
|
71
|
-
} else {
|
|
72
|
-
delete this.updates[del.key]
|
|
73
|
-
this.recordDelete(del.key, del.cid)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.newCids.addSet(diff.newCids)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
81
|
addList(): DataAdd[] {
|
|
80
82
|
return Object.values(this.adds)
|
|
81
83
|
}
|
|
@@ -88,10 +90,6 @@ export class DataDiff {
|
|
|
88
90
|
return Object.values(this.deletes)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
newCidList(): CID[] {
|
|
92
|
-
return this.newCids.toList()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
93
|
updatedKeys(): string[] {
|
|
96
94
|
const keys = [
|
|
97
95
|
...Object.keys(this.adds),
|
package/src/index.ts
CHANGED
package/src/mst/diff.ts
CHANGED
|
@@ -5,11 +5,7 @@ import MstWalker from './walker'
|
|
|
5
5
|
export const nullDiff = async (tree: MST): Promise<DataDiff> => {
|
|
6
6
|
const diff = new DataDiff()
|
|
7
7
|
for await (const entry of tree.walk()) {
|
|
8
|
-
|
|
9
|
-
diff.recordAdd(entry.key, entry.value)
|
|
10
|
-
} else {
|
|
11
|
-
diff.recordNewCid(entry.pointer)
|
|
12
|
-
}
|
|
8
|
+
await diff.nodeAdd(entry)
|
|
13
9
|
}
|
|
14
10
|
return diff
|
|
15
11
|
}
|
|
@@ -31,21 +27,11 @@ export const mstDiff = async (
|
|
|
31
27
|
while (!leftWalker.status.done || !rightWalker.status.done) {
|
|
32
28
|
// if one walker is finished, continue walking the other & logging all nodes
|
|
33
29
|
if (leftWalker.status.done && !rightWalker.status.done) {
|
|
34
|
-
|
|
35
|
-
if (node.isLeaf()) {
|
|
36
|
-
diff.recordAdd(node.key, node.value)
|
|
37
|
-
} else {
|
|
38
|
-
diff.recordNewCid(node.pointer)
|
|
39
|
-
}
|
|
30
|
+
await diff.nodeAdd(rightWalker.status.curr)
|
|
40
31
|
await rightWalker.advance()
|
|
41
32
|
continue
|
|
42
33
|
} else if (!leftWalker.status.done && rightWalker.status.done) {
|
|
43
|
-
|
|
44
|
-
if (node.isLeaf()) {
|
|
45
|
-
diff.recordDelete(node.key, node.value)
|
|
46
|
-
} else {
|
|
47
|
-
diff.recordRemovedCid(node.pointer)
|
|
48
|
-
}
|
|
34
|
+
await diff.nodeDelete(leftWalker.status.curr)
|
|
49
35
|
await leftWalker.advance()
|
|
50
36
|
continue
|
|
51
37
|
}
|
|
@@ -58,15 +44,15 @@ export const mstDiff = async (
|
|
|
58
44
|
if (left.isLeaf() && right.isLeaf()) {
|
|
59
45
|
if (left.key === right.key) {
|
|
60
46
|
if (!left.value.equals(right.value)) {
|
|
61
|
-
diff.
|
|
47
|
+
diff.leafUpdate(left.key, left.value, right.value)
|
|
62
48
|
}
|
|
63
49
|
await leftWalker.advance()
|
|
64
50
|
await rightWalker.advance()
|
|
65
51
|
} else if (left.key < right.key) {
|
|
66
|
-
diff.
|
|
52
|
+
diff.leafDelete(left.key, left.value)
|
|
67
53
|
await leftWalker.advance()
|
|
68
54
|
} else {
|
|
69
|
-
diff.
|
|
55
|
+
diff.leafAdd(right.key, right.value)
|
|
70
56
|
await rightWalker.advance()
|
|
71
57
|
}
|
|
72
58
|
continue
|
|
@@ -78,27 +64,19 @@ export const mstDiff = async (
|
|
|
78
64
|
// if the higher walker is pointed at a leaf, then advance the lower walker to try to catch up the higher
|
|
79
65
|
if (leftWalker.layer() > rightWalker.layer()) {
|
|
80
66
|
if (left.isLeaf()) {
|
|
81
|
-
|
|
82
|
-
diff.recordAdd(right.key, right.value)
|
|
83
|
-
} else {
|
|
84
|
-
diff.recordNewCid(right.pointer)
|
|
85
|
-
}
|
|
67
|
+
await diff.nodeAdd(right)
|
|
86
68
|
await rightWalker.advance()
|
|
87
69
|
} else {
|
|
88
|
-
diff.
|
|
70
|
+
await diff.nodeDelete(left)
|
|
89
71
|
await leftWalker.stepInto()
|
|
90
72
|
}
|
|
91
73
|
continue
|
|
92
74
|
} else if (leftWalker.layer() < rightWalker.layer()) {
|
|
93
75
|
if (right.isLeaf()) {
|
|
94
|
-
|
|
95
|
-
diff.recordDelete(left.key, left.value)
|
|
96
|
-
} else {
|
|
97
|
-
diff.recordRemovedCid(left.pointer)
|
|
98
|
-
}
|
|
76
|
+
await diff.nodeDelete(left)
|
|
99
77
|
await leftWalker.advance()
|
|
100
78
|
} else {
|
|
101
|
-
diff.
|
|
79
|
+
await diff.nodeAdd(right)
|
|
102
80
|
await rightWalker.stepInto()
|
|
103
81
|
}
|
|
104
82
|
continue
|
|
@@ -111,8 +89,8 @@ export const mstDiff = async (
|
|
|
111
89
|
await leftWalker.stepOver()
|
|
112
90
|
await rightWalker.stepOver()
|
|
113
91
|
} else {
|
|
114
|
-
diff.
|
|
115
|
-
diff.
|
|
92
|
+
await diff.nodeAdd(right)
|
|
93
|
+
await diff.nodeDelete(left)
|
|
116
94
|
await leftWalker.stepInto()
|
|
117
95
|
await rightWalker.stepInto()
|
|
118
96
|
}
|
|
@@ -121,11 +99,11 @@ export const mstDiff = async (
|
|
|
121
99
|
|
|
122
100
|
// finally, if one pointer is a tree and the other is a leaf, simply step into the tree
|
|
123
101
|
if (left.isLeaf() && right.isTree()) {
|
|
124
|
-
diff.
|
|
102
|
+
await diff.nodeAdd(right)
|
|
125
103
|
await rightWalker.stepInto()
|
|
126
104
|
continue
|
|
127
105
|
} else if (left.isTree() && right.isLeaf()) {
|
|
128
|
-
diff.
|
|
106
|
+
await diff.nodeDelete(left)
|
|
129
107
|
await leftWalker.stepInto()
|
|
130
108
|
continue
|
|
131
109
|
}
|
package/src/mst/mst.ts
CHANGED
|
@@ -2,8 +2,7 @@ import z from 'zod'
|
|
|
2
2
|
import { CID } from 'multiformats'
|
|
3
3
|
|
|
4
4
|
import { ReadableBlockstore } from '../storage'
|
|
5
|
-
import { schema as common, cidForCbor } from '@atproto/common'
|
|
6
|
-
import { DataStore } from '../types'
|
|
5
|
+
import { schema as common, cidForCbor, dataToCborBlock } from '@atproto/common'
|
|
7
6
|
import { BlockWriter } from '@ipld/car/api'
|
|
8
7
|
import * as util from './util'
|
|
9
8
|
import BlockMap from '../block-map'
|
|
@@ -68,7 +67,7 @@ export type MstOpts = {
|
|
|
68
67
|
layer: number
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
export class MST
|
|
70
|
+
export class MST {
|
|
72
71
|
storage: ReadableBlockstore
|
|
73
72
|
entries: NodeEntry[] | null
|
|
74
73
|
layer: number | null
|
|
@@ -154,6 +153,13 @@ export class MST implements DataStore {
|
|
|
154
153
|
// Instead we keep track of whether the pointer is outdated and only (recursively) calculate when needed
|
|
155
154
|
async getPointer(): Promise<CID> {
|
|
156
155
|
if (!this.outdatedPointer) return this.pointer
|
|
156
|
+
const { cid } = await this.serialize()
|
|
157
|
+
this.pointer = cid
|
|
158
|
+
this.outdatedPointer = false
|
|
159
|
+
return this.pointer
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async serialize(): Promise<{ cid: CID; bytes: Uint8Array }> {
|
|
157
163
|
let entries = await this.getEntries()
|
|
158
164
|
const outdated = entries.filter(
|
|
159
165
|
(e) => e.isTree() && e.outdatedPointer,
|
|
@@ -162,9 +168,12 @@ export class MST implements DataStore {
|
|
|
162
168
|
await Promise.all(outdated.map((e) => e.getPointer()))
|
|
163
169
|
entries = await this.getEntries()
|
|
164
170
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
171
|
+
const data = util.serializeNodeData(entries)
|
|
172
|
+
const block = await dataToCborBlock(data)
|
|
173
|
+
return {
|
|
174
|
+
cid: block.cid,
|
|
175
|
+
bytes: block.bytes,
|
|
176
|
+
}
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
// In most cases, we get the layer of a node from a hint on creation
|
|
@@ -695,17 +704,9 @@ export class MST implements DataStore {
|
|
|
695
704
|
// Sync Protocol
|
|
696
705
|
|
|
697
706
|
async writeToCarStream(car: BlockWriter): Promise<void> {
|
|
698
|
-
const entries = await this.getEntries()
|
|
699
707
|
const leaves = new CidSet()
|
|
700
708
|
let toFetch = new CidSet()
|
|
701
709
|
toFetch.add(await this.getPointer())
|
|
702
|
-
for (const entry of entries) {
|
|
703
|
-
if (entry.isLeaf()) {
|
|
704
|
-
leaves.add(entry.value)
|
|
705
|
-
} else {
|
|
706
|
-
toFetch.add(await entry.getPointer())
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
710
|
while (toFetch.size() > 0) {
|
|
710
711
|
const nextLayer = new CidSet()
|
|
711
712
|
const fetched = await this.storage.getBlocks(toFetch.toList())
|
package/src/readable-repo.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import {
|
|
2
|
+
import { def, RepoContents, Commit } from './types'
|
|
3
3
|
import { ReadableBlockstore } from './storage'
|
|
4
4
|
import { MST } from './mst'
|
|
5
5
|
import log from './logger'
|
|
@@ -9,14 +9,14 @@ import { MissingBlocksError } from './error'
|
|
|
9
9
|
|
|
10
10
|
type Params = {
|
|
11
11
|
storage: ReadableBlockstore
|
|
12
|
-
data:
|
|
12
|
+
data: MST
|
|
13
13
|
commit: Commit
|
|
14
14
|
cid: CID
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export class ReadableRepo {
|
|
18
18
|
storage: ReadableBlockstore
|
|
19
|
-
data:
|
|
19
|
+
data: MST
|
|
20
20
|
commit: Commit
|
|
21
21
|
cid: CID
|
|
22
22
|
|
|
@@ -28,13 +28,13 @@ export class ReadableRepo {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
static async load(storage: ReadableBlockstore, commitCid: CID) {
|
|
31
|
-
const commit = await storage.readObj(commitCid, def.
|
|
31
|
+
const commit = await storage.readObj(commitCid, def.versionedCommit)
|
|
32
32
|
const data = await MST.load(storage, commit.data)
|
|
33
33
|
log.info({ did: commit.did }, 'loaded repo for')
|
|
34
34
|
return new ReadableRepo({
|
|
35
35
|
storage,
|
|
36
36
|
data,
|
|
37
|
-
commit,
|
|
37
|
+
commit: util.ensureV3Commit(commit),
|
|
38
38
|
cid: commitCid,
|
|
39
39
|
})
|
|
40
40
|
}
|
package/src/repo.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { TID } from '@atproto/common'
|
|
2
3
|
import * as crypto from '@atproto/crypto'
|
|
3
4
|
import {
|
|
4
5
|
Commit,
|
|
6
|
+
CommitData,
|
|
5
7
|
def,
|
|
6
|
-
DataStore,
|
|
7
8
|
RecordCreateOp,
|
|
8
9
|
RecordWriteOp,
|
|
9
|
-
CommitData,
|
|
10
10
|
WriteOpAction,
|
|
11
11
|
} from './types'
|
|
12
12
|
import { RepoStorage } from './storage'
|
|
@@ -19,7 +19,7 @@ import * as util from './util'
|
|
|
19
19
|
|
|
20
20
|
type Params = {
|
|
21
21
|
storage: RepoStorage
|
|
22
|
-
data:
|
|
22
|
+
data: MST
|
|
23
23
|
commit: Commit
|
|
24
24
|
cid: CID
|
|
25
25
|
}
|
|
@@ -46,25 +46,29 @@ export class Repo extends ReadableRepo {
|
|
|
46
46
|
const dataKey = util.formatDataKey(record.collection, record.rkey)
|
|
47
47
|
data = await data.add(dataKey, cid)
|
|
48
48
|
}
|
|
49
|
+
const dataCid = await data.getPointer()
|
|
50
|
+
const diff = await DataDiff.of(data, null)
|
|
51
|
+
newBlocks.addMap(diff.newMstBlocks)
|
|
49
52
|
|
|
50
|
-
const
|
|
51
|
-
newBlocks.addMap(unstoredData.blocks)
|
|
52
|
-
|
|
53
|
+
const rev = TID.nextStr()
|
|
53
54
|
const commit = await util.signCommit(
|
|
54
55
|
{
|
|
55
56
|
did,
|
|
56
|
-
version:
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
version: 3,
|
|
58
|
+
rev,
|
|
59
|
+
prev: null, // added for backwards compatibility with v2
|
|
60
|
+
data: dataCid,
|
|
59
61
|
},
|
|
60
62
|
keypair,
|
|
61
63
|
)
|
|
62
64
|
const commitCid = await newBlocks.add(commit)
|
|
63
|
-
|
|
64
65
|
return {
|
|
65
|
-
|
|
66
|
+
cid: commitCid,
|
|
67
|
+
rev,
|
|
68
|
+
since: null,
|
|
66
69
|
prev: null,
|
|
67
|
-
|
|
70
|
+
newBlocks,
|
|
71
|
+
removedCids: diff.removedCids,
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -73,7 +77,7 @@ export class Repo extends ReadableRepo {
|
|
|
73
77
|
commit: CommitData,
|
|
74
78
|
): Promise<Repo> {
|
|
75
79
|
await storage.applyCommit(commit)
|
|
76
|
-
return Repo.load(storage, commit.
|
|
80
|
+
return Repo.load(storage, commit.cid)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
static async create(
|
|
@@ -92,17 +96,17 @@ export class Repo extends ReadableRepo {
|
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
static async load(storage: RepoStorage, cid?: CID) {
|
|
95
|
-
const commitCid = cid || (await storage.
|
|
99
|
+
const commitCid = cid || (await storage.getRoot())
|
|
96
100
|
if (!commitCid) {
|
|
97
101
|
throw new Error('No cid provided and none in storage')
|
|
98
102
|
}
|
|
99
|
-
const commit = await storage.readObj(commitCid, def.
|
|
103
|
+
const commit = await storage.readObj(commitCid, def.versionedCommit)
|
|
100
104
|
const data = await MST.load(storage, commit.data)
|
|
101
105
|
log.info({ did: commit.did }, 'loaded repo for')
|
|
102
106
|
return new Repo({
|
|
103
107
|
storage,
|
|
104
108
|
data,
|
|
105
|
-
commit,
|
|
109
|
+
commit: util.ensureV3Commit(commit),
|
|
106
110
|
cid: commitCid,
|
|
107
111
|
})
|
|
108
112
|
}
|
|
@@ -112,16 +116,16 @@ export class Repo extends ReadableRepo {
|
|
|
112
116
|
keypair: crypto.Keypair,
|
|
113
117
|
): Promise<CommitData> {
|
|
114
118
|
const writes = Array.isArray(toWrite) ? toWrite : [toWrite]
|
|
115
|
-
const
|
|
119
|
+
const leaves = new BlockMap()
|
|
116
120
|
|
|
117
121
|
let data = this.data
|
|
118
122
|
for (const write of writes) {
|
|
119
123
|
if (write.action === WriteOpAction.Create) {
|
|
120
|
-
const cid = await
|
|
124
|
+
const cid = await leaves.add(write.record)
|
|
121
125
|
const dataKey = write.collection + '/' + write.rkey
|
|
122
126
|
data = await data.add(dataKey, cid)
|
|
123
127
|
} else if (write.action === WriteOpAction.Update) {
|
|
124
|
-
const cid = await
|
|
128
|
+
const cid = await leaves.add(write.record)
|
|
125
129
|
const dataKey = write.collection + '/' + write.rkey
|
|
126
130
|
data = await data.update(dataKey, cid)
|
|
127
131
|
} else if (write.action === WriteOpAction.Delete) {
|
|
@@ -130,44 +134,50 @@ export class Repo extends ReadableRepo {
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
const
|
|
134
|
-
commitBlocks.addMap(unstoredData.blocks)
|
|
135
|
-
|
|
136
|
-
// ensure we're not missing any blocks that were removed and then readded in this commit
|
|
137
|
+
const dataCid = await data.getPointer()
|
|
137
138
|
const diff = await DataDiff.of(data, this.data)
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
'Could not find block for commit in Datastore or storage',
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
commitBlocks.addMap(fromStorage.blocks)
|
|
139
|
+
const newBlocks = diff.newMstBlocks
|
|
140
|
+
const removedCids = diff.removedCids
|
|
141
|
+
|
|
142
|
+
const addedLeaves = leaves.getMany(diff.newLeafCids.toList())
|
|
143
|
+
if (addedLeaves.missing.length > 0) {
|
|
144
|
+
throw new Error(`Missing leaf blocks: ${addedLeaves.missing}`)
|
|
148
145
|
}
|
|
146
|
+
newBlocks.addMap(addedLeaves.blocks)
|
|
149
147
|
|
|
148
|
+
const rev = TID.nextStr(this.commit.rev)
|
|
150
149
|
const commit = await util.signCommit(
|
|
151
150
|
{
|
|
152
151
|
did: this.did,
|
|
153
|
-
version:
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
version: 3,
|
|
153
|
+
rev,
|
|
154
|
+
prev: null, // added for backwards compatibility with v2
|
|
155
|
+
data: dataCid,
|
|
156
156
|
},
|
|
157
157
|
keypair,
|
|
158
158
|
)
|
|
159
|
-
const commitCid = await
|
|
159
|
+
const commitCid = await newBlocks.add(commit)
|
|
160
|
+
|
|
161
|
+
// ensure the commit cid actually changed
|
|
162
|
+
if (commitCid.equals(this.cid)) {
|
|
163
|
+
newBlocks.delete(commitCid)
|
|
164
|
+
} else {
|
|
165
|
+
removedCids.add(this.cid)
|
|
166
|
+
}
|
|
160
167
|
|
|
161
168
|
return {
|
|
162
|
-
|
|
169
|
+
cid: commitCid,
|
|
170
|
+
rev,
|
|
171
|
+
since: this.commit.rev,
|
|
163
172
|
prev: this.cid,
|
|
164
|
-
|
|
173
|
+
newBlocks,
|
|
174
|
+
removedCids,
|
|
165
175
|
}
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
async applyCommit(commitData: CommitData): Promise<Repo> {
|
|
169
179
|
await this.storage.applyCommit(commitData)
|
|
170
|
-
return Repo.load(this.storage, commitData.
|
|
180
|
+
return Repo.load(this.storage, commitData.cid)
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
async applyWrites(
|