@atproto/repo 0.10.2 → 0.10.3
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/CHANGELOG.md +16 -0
- package/package.json +22 -17
- package/jest.config.cjs +0 -24
- package/src/block-map.ts +0 -131
- package/src/car.ts +0 -357
- package/src/cid-set.ts +0 -55
- package/src/data-diff.ts +0 -117
- package/src/error.ts +0 -43
- package/src/index.ts +0 -11
- package/src/logger.ts +0 -7
- package/src/mst/diff.ts +0 -114
- package/src/mst/index.ts +0 -4
- package/src/mst/mst.ts +0 -892
- package/src/mst/util.ts +0 -160
- package/src/mst/walker.ts +0 -118
- package/src/parse.ts +0 -44
- package/src/readable-repo.ts +0 -86
- package/src/repo.ts +0 -236
- package/src/storage/index.ts +0 -4
- package/src/storage/memory-blockstore.ts +0 -76
- package/src/storage/readable-blockstore.ts +0 -55
- package/src/storage/sync-storage.ts +0 -35
- package/src/storage/types.ts +0 -47
- package/src/sync/consumer.ts +0 -207
- package/src/sync/index.ts +0 -2
- package/src/sync/provider.ts +0 -67
- package/src/types.ts +0 -227
- package/src/util.ts +0 -146
- package/tests/_keys.ts +0 -156
- package/tests/_util.ts +0 -265
- package/tests/car-file-fixtures.json +0 -28
- package/tests/car.test.ts +0 -125
- package/tests/commit-data.test.ts +0 -94
- package/tests/commit-proof-fixtures.json +0 -118
- package/tests/commit-proofs.test.ts +0 -63
- package/tests/covering-proofs.test.ts +0 -256
- package/tests/mst.test.ts +0 -450
- package/tests/proofs.test.ts +0 -155
- package/tests/repo.test.ts +0 -106
- package/tests/sync.test.ts +0 -95
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/src/mst/util.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { sha256 } from '@atproto/crypto'
|
|
2
|
-
import { cidForLex } from '@atproto/lex-cbor'
|
|
3
|
-
import { Cid } from '@atproto/lex-data'
|
|
4
|
-
import { ReadableBlockstore } from '../storage/index.js'
|
|
5
|
-
import { Leaf, MST, MstOpts, NodeData, NodeEntry } from './mst.js'
|
|
6
|
-
|
|
7
|
-
function toAscii(bytes: Uint8Array): string {
|
|
8
|
-
let string = ''
|
|
9
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
10
|
-
string += String.fromCharCode(bytes[i])
|
|
11
|
-
}
|
|
12
|
-
return string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function fromAscii(str: string): Uint8Array<ArrayBuffer> {
|
|
16
|
-
const bytes = new Uint8Array(str.length)
|
|
17
|
-
for (let i = 0; i < str.length; i++) {
|
|
18
|
-
bytes[i] = str.charCodeAt(i)
|
|
19
|
-
}
|
|
20
|
-
return bytes
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const leadingZerosOnHash = async (key: string | Uint8Array) => {
|
|
24
|
-
const hash = await sha256(key)
|
|
25
|
-
let leadingZeros = 0
|
|
26
|
-
for (let i = 0; i < hash.length; i++) {
|
|
27
|
-
const byte = hash[i]
|
|
28
|
-
if (byte < 64) leadingZeros++
|
|
29
|
-
if (byte < 16) leadingZeros++
|
|
30
|
-
if (byte < 4) leadingZeros++
|
|
31
|
-
if (byte === 0) {
|
|
32
|
-
leadingZeros++
|
|
33
|
-
} else {
|
|
34
|
-
break
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return leadingZeros
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const layerForEntries = async (
|
|
41
|
-
entries: NodeEntry[],
|
|
42
|
-
): Promise<number | null> => {
|
|
43
|
-
const firstLeaf = entries.find((entry) => entry.isLeaf())
|
|
44
|
-
if (!firstLeaf || firstLeaf.isTree()) return null
|
|
45
|
-
return await leadingZerosOnHash(firstLeaf.key)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const deserializeNodeData = async (
|
|
49
|
-
storage: ReadableBlockstore,
|
|
50
|
-
data: NodeData,
|
|
51
|
-
opts?: Partial<MstOpts>,
|
|
52
|
-
): Promise<NodeEntry[]> => {
|
|
53
|
-
const { layer } = opts || {}
|
|
54
|
-
const entries: NodeEntry[] = []
|
|
55
|
-
if (data.l !== null) {
|
|
56
|
-
entries.push(
|
|
57
|
-
await MST.load(storage, data.l, {
|
|
58
|
-
layer: layer ? layer - 1 : undefined,
|
|
59
|
-
}),
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
let lastKey = ''
|
|
63
|
-
for (const entry of data.e) {
|
|
64
|
-
const keyStr = toAscii(entry.k)
|
|
65
|
-
const key = `${lastKey.slice(0, entry.p)}${keyStr}`
|
|
66
|
-
ensureValidMstKey(key)
|
|
67
|
-
entries.push(new Leaf(key, entry.v))
|
|
68
|
-
lastKey = key
|
|
69
|
-
if (entry.t !== null) {
|
|
70
|
-
entries.push(
|
|
71
|
-
await MST.load(storage, entry.t, {
|
|
72
|
-
layer: layer ? layer - 1 : undefined,
|
|
73
|
-
}),
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return entries
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const serializeNodeData = (entries: NodeEntry[]): NodeData => {
|
|
81
|
-
const data: NodeData = {
|
|
82
|
-
l: null,
|
|
83
|
-
e: [],
|
|
84
|
-
}
|
|
85
|
-
let i = 0
|
|
86
|
-
if (entries[0]?.isTree()) {
|
|
87
|
-
i++
|
|
88
|
-
data.l = entries[0].pointer
|
|
89
|
-
}
|
|
90
|
-
let lastKey = ''
|
|
91
|
-
while (i < entries.length) {
|
|
92
|
-
const leaf = entries[i]
|
|
93
|
-
const next = entries[i + 1]
|
|
94
|
-
if (!leaf.isLeaf()) {
|
|
95
|
-
throw new Error('Not a valid node: two subtrees next to each other')
|
|
96
|
-
}
|
|
97
|
-
i++
|
|
98
|
-
let subtree: Cid | null = null
|
|
99
|
-
if (next?.isTree()) {
|
|
100
|
-
subtree = next.pointer
|
|
101
|
-
i++
|
|
102
|
-
}
|
|
103
|
-
ensureValidMstKey(leaf.key)
|
|
104
|
-
const prefixLen = countPrefixLen(lastKey, leaf.key)
|
|
105
|
-
data.e.push({
|
|
106
|
-
p: prefixLen,
|
|
107
|
-
k: fromAscii(leaf.key.slice(prefixLen)),
|
|
108
|
-
v: leaf.value,
|
|
109
|
-
t: subtree,
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
lastKey = leaf.key
|
|
113
|
-
}
|
|
114
|
-
return data
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export const countPrefixLen = (a: string, b: string): number => {
|
|
118
|
-
let i
|
|
119
|
-
for (i = 0; i < a.length; i++) {
|
|
120
|
-
if (a[i] !== b[i]) {
|
|
121
|
-
break
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return i
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export const cidForEntries = async (entries: NodeEntry[]): Promise<Cid> => {
|
|
128
|
-
const data = serializeNodeData(entries)
|
|
129
|
-
return cidForLex(data)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const isValidMstKey = (str: string): boolean => {
|
|
133
|
-
const split = str.split('/')
|
|
134
|
-
return (
|
|
135
|
-
str.length <= 1024 &&
|
|
136
|
-
split.length === 2 &&
|
|
137
|
-
split[0].length > 0 &&
|
|
138
|
-
split[1].length > 0 &&
|
|
139
|
-
isValidChars(split[0]) &&
|
|
140
|
-
isValidChars(split[1])
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const validCharsRegex = /^[a-zA-Z0-9_~\-:.]*$/
|
|
145
|
-
|
|
146
|
-
export const isValidChars = (str: string): boolean => {
|
|
147
|
-
return str.match(validCharsRegex) !== null
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export const ensureValidMstKey = (str: string) => {
|
|
151
|
-
if (!isValidMstKey(str)) {
|
|
152
|
-
throw new InvalidMstKeyError(str)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export class InvalidMstKeyError extends Error {
|
|
157
|
-
constructor(public key: string) {
|
|
158
|
-
super(`Not a valid MST key: ${key}`)
|
|
159
|
-
}
|
|
160
|
-
}
|
package/src/mst/walker.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { MST, NodeEntry } from './mst.js'
|
|
2
|
-
|
|
3
|
-
type WalkerStatusDone = {
|
|
4
|
-
done: true
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type WalkerStatusProgress = {
|
|
8
|
-
done: false
|
|
9
|
-
curr: NodeEntry
|
|
10
|
-
walking: MST | null // walking set to null if `curr` is the root of the tree
|
|
11
|
-
index: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type WalkerStatus = WalkerStatusDone | WalkerStatusProgress
|
|
15
|
-
|
|
16
|
-
export class MstWalker {
|
|
17
|
-
stack: WalkerStatus[] = []
|
|
18
|
-
status: WalkerStatus
|
|
19
|
-
|
|
20
|
-
constructor(public root: MST) {
|
|
21
|
-
this.status = {
|
|
22
|
-
done: false,
|
|
23
|
-
curr: root,
|
|
24
|
-
walking: null,
|
|
25
|
-
index: 0,
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// return the current layer of the node you are walking
|
|
30
|
-
layer(): number {
|
|
31
|
-
if (this.status.done) {
|
|
32
|
-
throw new Error('Walk is done')
|
|
33
|
-
}
|
|
34
|
-
if (this.status.walking) {
|
|
35
|
-
return this.status.walking.layer ?? 0
|
|
36
|
-
}
|
|
37
|
-
// if curr is the root of the tree, add 1
|
|
38
|
-
if (this.status.curr.isTree()) {
|
|
39
|
-
return (this.status.curr.layer ?? 0) + 1
|
|
40
|
-
}
|
|
41
|
-
throw new Error('Could not identify layer of walk')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// move to the next node in the subtree, skipping over the subtree
|
|
45
|
-
async stepOver(): Promise<void> {
|
|
46
|
-
if (this.status.done) return
|
|
47
|
-
// if stepping over the root of the node, we're done
|
|
48
|
-
if (this.status.walking === null) {
|
|
49
|
-
this.status = { done: true }
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
const entries = await this.status.walking.getEntries()
|
|
53
|
-
this.status.index++
|
|
54
|
-
const next = entries[this.status.index]
|
|
55
|
-
if (!next) {
|
|
56
|
-
const popped = this.stack.pop()
|
|
57
|
-
if (!popped) {
|
|
58
|
-
this.status = { done: true }
|
|
59
|
-
return
|
|
60
|
-
} else {
|
|
61
|
-
this.status = popped
|
|
62
|
-
await this.stepOver()
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
this.status.curr = next
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// step into a subtree, throws if currently pointed at a leaf
|
|
71
|
-
async stepInto(): Promise<void> {
|
|
72
|
-
if (this.status.done) return
|
|
73
|
-
// edge case for very start of walk
|
|
74
|
-
if (this.status.walking === null) {
|
|
75
|
-
if (!this.status.curr.isTree()) {
|
|
76
|
-
throw new Error('The root of the tree cannot be a leaf')
|
|
77
|
-
}
|
|
78
|
-
const next = await this.status.curr.atIndex(0)
|
|
79
|
-
if (!next) {
|
|
80
|
-
this.status = { done: true }
|
|
81
|
-
} else {
|
|
82
|
-
this.status = {
|
|
83
|
-
done: false,
|
|
84
|
-
walking: this.status.curr,
|
|
85
|
-
curr: next,
|
|
86
|
-
index: 0,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
if (!this.status.curr.isTree()) {
|
|
92
|
-
throw new Error('No tree at pointer, cannot step into')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const next = await this.status.curr.atIndex(0)
|
|
96
|
-
if (!next) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
'Tried to step into a node with 0 entries which is invalid',
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
this.stack.push({ ...this.status })
|
|
103
|
-
this.status.walking = this.status.curr
|
|
104
|
-
this.status.curr = next
|
|
105
|
-
this.status.index = 0
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// advance the pointer to the next node in the tree,
|
|
109
|
-
// stepping into the current node if necessary
|
|
110
|
-
async advance(): Promise<void> {
|
|
111
|
-
if (this.status.done) return
|
|
112
|
-
if (this.status.curr.isLeaf()) {
|
|
113
|
-
await this.stepOver()
|
|
114
|
-
} else {
|
|
115
|
-
await this.stepInto()
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
package/src/parse.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { check } from '@atproto/common-web'
|
|
2
|
-
import { decode } from '@atproto/lex-cbor'
|
|
3
|
-
import { Cid, LexMap } from '@atproto/lex-data'
|
|
4
|
-
import { BlockMap } from './block-map.js'
|
|
5
|
-
import { MissingBlockError, UnexpectedObjectError } from './error.js'
|
|
6
|
-
import { cborToLexRecord } from './util.js'
|
|
7
|
-
|
|
8
|
-
export const getAndParseRecord = async (
|
|
9
|
-
blocks: BlockMap,
|
|
10
|
-
cid: Cid,
|
|
11
|
-
): Promise<{ record: LexMap; bytes: Uint8Array }> => {
|
|
12
|
-
const bytes = blocks.get(cid)
|
|
13
|
-
if (!bytes) {
|
|
14
|
-
throw new MissingBlockError(cid, 'record')
|
|
15
|
-
}
|
|
16
|
-
const record = cborToLexRecord(bytes)
|
|
17
|
-
return { record, bytes }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const getAndParseByDef = async <T>(
|
|
21
|
-
blocks: BlockMap,
|
|
22
|
-
cid: Cid,
|
|
23
|
-
def: check.Def<T>,
|
|
24
|
-
): Promise<{ obj: T; bytes: Uint8Array }> => {
|
|
25
|
-
const bytes = blocks.get(cid)
|
|
26
|
-
if (!bytes) {
|
|
27
|
-
throw new MissingBlockError(cid, def.name)
|
|
28
|
-
}
|
|
29
|
-
return parseObjByDef(bytes, cid, def)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const parseObjByDef = <T>(
|
|
33
|
-
bytes: Uint8Array,
|
|
34
|
-
cid: Cid,
|
|
35
|
-
def: check.Def<T>,
|
|
36
|
-
): { obj: T; bytes: Uint8Array } => {
|
|
37
|
-
const obj = decode(bytes)
|
|
38
|
-
const res = def.schema.safeParse(obj)
|
|
39
|
-
if (res.success) {
|
|
40
|
-
return { obj: res.data, bytes }
|
|
41
|
-
} else {
|
|
42
|
-
throw new UnexpectedObjectError(cid, def.name)
|
|
43
|
-
}
|
|
44
|
-
}
|
package/src/readable-repo.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { Cid, LexMap } from '@atproto/lex-data'
|
|
2
|
-
import { MissingBlocksError } from './error.js'
|
|
3
|
-
import log from './logger.js'
|
|
4
|
-
import { MST } from './mst/index.js'
|
|
5
|
-
import * as parse from './parse.js'
|
|
6
|
-
import { ReadableBlockstore } from './storage/index.js'
|
|
7
|
-
import { Commit, RepoContents, def } from './types.js'
|
|
8
|
-
import * as util from './util.js'
|
|
9
|
-
|
|
10
|
-
type Params = {
|
|
11
|
-
storage: ReadableBlockstore
|
|
12
|
-
data: MST
|
|
13
|
-
commit: Commit
|
|
14
|
-
cid: Cid
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ReadableRepo {
|
|
18
|
-
storage: ReadableBlockstore
|
|
19
|
-
data: MST
|
|
20
|
-
commit: Commit
|
|
21
|
-
cid: Cid
|
|
22
|
-
|
|
23
|
-
constructor(params: Params) {
|
|
24
|
-
this.storage = params.storage
|
|
25
|
-
this.data = params.data
|
|
26
|
-
this.commit = params.commit
|
|
27
|
-
this.cid = params.cid
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static async load(storage: ReadableBlockstore, commitCid: Cid) {
|
|
31
|
-
const commit = await storage.readObj(commitCid, def.versionedCommit)
|
|
32
|
-
const data = await MST.load(storage, commit.data)
|
|
33
|
-
log.info({ did: commit.did }, 'loaded repo for')
|
|
34
|
-
return new ReadableRepo({
|
|
35
|
-
storage,
|
|
36
|
-
data,
|
|
37
|
-
commit: util.ensureV3Commit(commit),
|
|
38
|
-
cid: commitCid,
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get did(): string {
|
|
43
|
-
return this.commit.did
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get version(): number {
|
|
47
|
-
return this.commit.version
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async *walkRecords(from?: string): AsyncIterable<{
|
|
51
|
-
collection: string
|
|
52
|
-
rkey: string
|
|
53
|
-
cid: Cid
|
|
54
|
-
record: LexMap
|
|
55
|
-
}> {
|
|
56
|
-
for await (const leaf of this.data.walkLeavesFrom(from ?? '')) {
|
|
57
|
-
const { collection, rkey } = util.parseDataKey(leaf.key)
|
|
58
|
-
const record = await this.storage.readRecord(leaf.value)
|
|
59
|
-
yield { collection, rkey, cid: leaf.value, record }
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async getRecord(collection: string, rkey: string): Promise<unknown | null> {
|
|
64
|
-
const dataKey = collection + '/' + rkey
|
|
65
|
-
const cid = await this.data.get(dataKey)
|
|
66
|
-
if (!cid) return null
|
|
67
|
-
return this.storage.readObj(cid, def.unknown)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async getContents(): Promise<RepoContents> {
|
|
71
|
-
const entries = await this.data.list()
|
|
72
|
-
const cids = entries.map((e) => e.value)
|
|
73
|
-
const { blocks, missing } = await this.storage.getBlocks(cids)
|
|
74
|
-
if (missing.length > 0) {
|
|
75
|
-
throw new MissingBlocksError('getContents record', missing)
|
|
76
|
-
}
|
|
77
|
-
const contents: RepoContents = {}
|
|
78
|
-
for (const entry of entries) {
|
|
79
|
-
const { collection, rkey } = util.parseDataKey(entry.key)
|
|
80
|
-
contents[collection] ??= {}
|
|
81
|
-
const parsed = await parse.getAndParseRecord(blocks, entry.value)
|
|
82
|
-
contents[collection][rkey] = parsed.record
|
|
83
|
-
}
|
|
84
|
-
return contents
|
|
85
|
-
}
|
|
86
|
-
}
|
package/src/repo.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { TID } from '@atproto/common-web'
|
|
2
|
-
import * as crypto from '@atproto/crypto'
|
|
3
|
-
import { encode } from '@atproto/lex-cbor'
|
|
4
|
-
import { Cid, cidForCbor } from '@atproto/lex-data'
|
|
5
|
-
import { BlockMap } from './block-map.js'
|
|
6
|
-
import { CidSet } from './cid-set.js'
|
|
7
|
-
import { DataDiff } from './data-diff.js'
|
|
8
|
-
import log from './logger.js'
|
|
9
|
-
import { MST } from './mst/index.js'
|
|
10
|
-
import { ReadableRepo } from './readable-repo.js'
|
|
11
|
-
import { RepoStorage } from './storage/index.js'
|
|
12
|
-
import {
|
|
13
|
-
Commit,
|
|
14
|
-
CommitData,
|
|
15
|
-
RecordCreateOp,
|
|
16
|
-
RecordWriteOp,
|
|
17
|
-
WriteOpAction,
|
|
18
|
-
def,
|
|
19
|
-
} from './types.js'
|
|
20
|
-
import * as util from './util.js'
|
|
21
|
-
|
|
22
|
-
type Params = {
|
|
23
|
-
storage: RepoStorage
|
|
24
|
-
data: MST
|
|
25
|
-
commit: Commit
|
|
26
|
-
cid: Cid
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class Repo extends ReadableRepo {
|
|
30
|
-
storage: RepoStorage
|
|
31
|
-
|
|
32
|
-
constructor(params: Params) {
|
|
33
|
-
super(params)
|
|
34
|
-
this.storage = params.storage
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
static async formatInitCommit(
|
|
38
|
-
storage: RepoStorage,
|
|
39
|
-
did: string,
|
|
40
|
-
keypair: crypto.Keypair,
|
|
41
|
-
initialWrites: RecordCreateOp[] = [],
|
|
42
|
-
revOverride?: string,
|
|
43
|
-
): Promise<CommitData> {
|
|
44
|
-
const newBlocks = new BlockMap()
|
|
45
|
-
|
|
46
|
-
let data = await MST.create(storage)
|
|
47
|
-
for (const record of initialWrites) {
|
|
48
|
-
const cid = await newBlocks.add(record.record)
|
|
49
|
-
const dataKey = util.formatDataKey(record.collection, record.rkey)
|
|
50
|
-
data = await data.add(dataKey, cid)
|
|
51
|
-
}
|
|
52
|
-
const dataCid = await data.getPointer()
|
|
53
|
-
const diff = await DataDiff.of(data, null)
|
|
54
|
-
newBlocks.addMap(diff.newMstBlocks)
|
|
55
|
-
|
|
56
|
-
const rev = revOverride ?? TID.nextStr()
|
|
57
|
-
const commit = await util.signCommit(
|
|
58
|
-
{
|
|
59
|
-
did,
|
|
60
|
-
version: 3,
|
|
61
|
-
rev,
|
|
62
|
-
prev: null, // added for backwards compatibility with v2
|
|
63
|
-
data: dataCid,
|
|
64
|
-
},
|
|
65
|
-
keypair,
|
|
66
|
-
)
|
|
67
|
-
const commitCid = await newBlocks.add(commit)
|
|
68
|
-
return {
|
|
69
|
-
cid: commitCid,
|
|
70
|
-
rev,
|
|
71
|
-
since: null,
|
|
72
|
-
prev: null,
|
|
73
|
-
newBlocks,
|
|
74
|
-
relevantBlocks: newBlocks,
|
|
75
|
-
removedCids: diff.removedCids,
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
static async createFromCommit(
|
|
80
|
-
storage: RepoStorage,
|
|
81
|
-
commit: CommitData,
|
|
82
|
-
): Promise<Repo> {
|
|
83
|
-
await storage.applyCommit(commit)
|
|
84
|
-
return Repo.load(storage, commit.cid)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static async create(
|
|
88
|
-
storage: RepoStorage,
|
|
89
|
-
did: string,
|
|
90
|
-
keypair: crypto.Keypair,
|
|
91
|
-
initialWrites: RecordCreateOp[] = [],
|
|
92
|
-
): Promise<Repo> {
|
|
93
|
-
const commit = await Repo.formatInitCommit(
|
|
94
|
-
storage,
|
|
95
|
-
did,
|
|
96
|
-
keypair,
|
|
97
|
-
initialWrites,
|
|
98
|
-
)
|
|
99
|
-
return Repo.createFromCommit(storage, commit)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static async load(storage: RepoStorage, cid?: Cid) {
|
|
103
|
-
const commitCid = cid || (await storage.getRoot())
|
|
104
|
-
if (!commitCid) {
|
|
105
|
-
throw new Error('No cid provided and none in storage')
|
|
106
|
-
}
|
|
107
|
-
const commit = await storage.readObj(commitCid, def.versionedCommit)
|
|
108
|
-
const data = await MST.load(storage, commit.data)
|
|
109
|
-
log.info({ did: commit.did }, 'loaded repo for')
|
|
110
|
-
return new Repo({
|
|
111
|
-
storage,
|
|
112
|
-
data,
|
|
113
|
-
commit: util.ensureV3Commit(commit),
|
|
114
|
-
cid: commitCid,
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async formatCommit(
|
|
119
|
-
toWrite: RecordWriteOp | RecordWriteOp[],
|
|
120
|
-
keypair: crypto.Keypair,
|
|
121
|
-
): Promise<CommitData> {
|
|
122
|
-
const writes = Array.isArray(toWrite) ? toWrite : [toWrite]
|
|
123
|
-
const leaves = new BlockMap()
|
|
124
|
-
|
|
125
|
-
let data = this.data
|
|
126
|
-
for (const write of writes) {
|
|
127
|
-
if (write.action === WriteOpAction.Create) {
|
|
128
|
-
const cid = await leaves.add(write.record)
|
|
129
|
-
const dataKey = write.collection + '/' + write.rkey
|
|
130
|
-
data = await data.add(dataKey, cid)
|
|
131
|
-
} else if (write.action === WriteOpAction.Update) {
|
|
132
|
-
const cid = await leaves.add(write.record)
|
|
133
|
-
const dataKey = write.collection + '/' + write.rkey
|
|
134
|
-
data = await data.update(dataKey, cid)
|
|
135
|
-
} else if (write.action === WriteOpAction.Delete) {
|
|
136
|
-
const dataKey = write.collection + '/' + write.rkey
|
|
137
|
-
data = await data.delete(dataKey)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const dataCid = await data.getPointer()
|
|
142
|
-
const diff = await DataDiff.of(data, this.data)
|
|
143
|
-
const newBlocks = diff.newMstBlocks
|
|
144
|
-
const removedCids = diff.removedCids
|
|
145
|
-
|
|
146
|
-
const proofs = await Promise.all(
|
|
147
|
-
writes.map((op) =>
|
|
148
|
-
data.getCoveringProof(util.formatDataKey(op.collection, op.rkey)),
|
|
149
|
-
),
|
|
150
|
-
)
|
|
151
|
-
const relevantBlocks = new BlockMap()
|
|
152
|
-
for (const proof of proofs) relevantBlocks.addMap(proof)
|
|
153
|
-
|
|
154
|
-
const addedLeaves = leaves.getMany(diff.newLeafCids.toList())
|
|
155
|
-
if (addedLeaves.missing.length > 0) {
|
|
156
|
-
throw new Error(`Missing leaf blocks: ${addedLeaves.missing}`)
|
|
157
|
-
}
|
|
158
|
-
newBlocks.addMap(addedLeaves.blocks)
|
|
159
|
-
relevantBlocks.addMap(addedLeaves.blocks)
|
|
160
|
-
|
|
161
|
-
const rev = TID.nextStr(this.commit.rev)
|
|
162
|
-
const commit = await util.signCommit(
|
|
163
|
-
{
|
|
164
|
-
did: this.did,
|
|
165
|
-
version: 3,
|
|
166
|
-
rev,
|
|
167
|
-
prev: null, // added for backwards compatibility with v2
|
|
168
|
-
data: dataCid,
|
|
169
|
-
},
|
|
170
|
-
keypair,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
const commitBytes = encode(commit)
|
|
174
|
-
const commitCid = await cidForCbor(commitBytes)
|
|
175
|
-
|
|
176
|
-
if (!commitCid.equals(this.cid)) {
|
|
177
|
-
newBlocks.set(commitCid, commitBytes)
|
|
178
|
-
relevantBlocks.set(commitCid, commitBytes)
|
|
179
|
-
removedCids.add(this.cid)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
cid: commitCid,
|
|
184
|
-
rev,
|
|
185
|
-
since: this.commit.rev,
|
|
186
|
-
prev: this.cid,
|
|
187
|
-
newBlocks,
|
|
188
|
-
relevantBlocks,
|
|
189
|
-
removedCids,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async applyCommit(commitData: CommitData): Promise<Repo> {
|
|
194
|
-
await this.storage.applyCommit(commitData)
|
|
195
|
-
return Repo.load(this.storage, commitData.cid)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async applyWrites(
|
|
199
|
-
toWrite: RecordWriteOp | RecordWriteOp[],
|
|
200
|
-
keypair: crypto.Keypair,
|
|
201
|
-
): Promise<Repo> {
|
|
202
|
-
const commit = await this.formatCommit(toWrite, keypair)
|
|
203
|
-
return this.applyCommit(commit)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async formatResignCommit(rev: string, keypair: crypto.Keypair) {
|
|
207
|
-
const commit = await util.signCommit(
|
|
208
|
-
{
|
|
209
|
-
did: this.did,
|
|
210
|
-
version: 3,
|
|
211
|
-
rev,
|
|
212
|
-
prev: null, // added for backwards compatibility with v2
|
|
213
|
-
data: this.commit.data,
|
|
214
|
-
},
|
|
215
|
-
keypair,
|
|
216
|
-
)
|
|
217
|
-
const newBlocks = new BlockMap()
|
|
218
|
-
const commitCid = await newBlocks.add(commit)
|
|
219
|
-
return {
|
|
220
|
-
cid: commitCid,
|
|
221
|
-
rev,
|
|
222
|
-
since: null,
|
|
223
|
-
prev: null,
|
|
224
|
-
newBlocks,
|
|
225
|
-
relevantBlocks: newBlocks,
|
|
226
|
-
removedCids: new CidSet([this.cid]),
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async resignCommit(rev: string, keypair: crypto.Keypair) {
|
|
231
|
-
const formatted = await this.formatResignCommit(rev, keypair)
|
|
232
|
-
return this.applyCommit(formatted)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export default Repo
|
package/src/storage/index.ts
DELETED