@atproto/repo 0.0.1 → 0.1.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/bench/mst.bench.ts +7 -4
- package/bench/repo.bench.ts +25 -16
- package/dist/block-map.d.ts +25 -0
- package/dist/data-diff.d.ts +36 -0
- package/dist/error.d.ts +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11605 -10399
- package/dist/index.js.map +4 -4
- package/dist/mst/diff.d.ts +4 -33
- package/dist/mst/mst.d.ts +68 -25
- package/dist/mst/util.d.ts +13 -5
- package/dist/parse.d.ts +16 -0
- package/dist/readable-repo.d.ts +22 -0
- package/dist/repo.d.ts +14 -30
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/memory-blockstore.d.ts +28 -0
- package/dist/storage/readable-blockstore.d.ts +24 -0
- package/dist/storage/repo-storage.d.ts +18 -0
- package/dist/storage/sync-storage.d.ts +15 -0
- package/dist/storage/types.d.ts +3 -0
- package/dist/sync/consumer.d.ts +18 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/provider.d.ts +9 -0
- package/dist/types.d.ts +124 -317
- package/dist/util.d.ts +31 -12
- package/dist/verify.d.ts +26 -4
- package/package.json +4 -2
- package/src/block-map.ts +95 -0
- package/src/cid-set.ts +1 -2
- package/src/data-diff.ts +121 -0
- package/src/error.ts +31 -0
- package/src/index.ts +3 -1
- package/src/mst/diff.ts +120 -90
- package/src/mst/mst.ts +185 -184
- package/src/mst/util.ts +54 -31
- package/src/parse.ts +44 -0
- package/src/readable-repo.ts +75 -0
- package/src/repo.ts +119 -249
- package/src/storage/index.ts +4 -0
- package/src/storage/memory-blockstore.ts +114 -0
- package/src/storage/readable-blockstore.ts +56 -0
- package/src/storage/repo-storage.ts +42 -0
- package/src/storage/sync-storage.ts +35 -0
- package/src/storage/types.ts +3 -0
- package/src/sync/consumer.ts +137 -0
- package/src/sync/index.ts +2 -0
- package/src/sync/provider.ts +91 -0
- package/src/types.ts +101 -62
- package/src/util.ts +237 -56
- package/src/verify.ts +207 -42
- package/tests/_util.ts +132 -97
- package/tests/mst.test.ts +269 -122
- package/tests/repo.test.ts +48 -50
- package/tests/sync/checkout.test.ts +57 -0
- package/tests/sync/diff.test.ts +87 -0
- package/tests/sync/narrow.test.ts +145 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +2 -1
- package/src/blockstore/index.ts +0 -2
- package/src/blockstore/ipld-store.ts +0 -103
- package/src/blockstore/memory-blockstore.ts +0 -49
- package/src/sync.ts +0 -38
- package/tests/sync.test.ts +0 -129
package/src/parse.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { check, cborDecode } from '@atproto/common'
|
|
2
|
+
import { RepoRecord } from '@atproto/lexicon'
|
|
3
|
+
import { CID } from 'multiformats/cid'
|
|
4
|
+
import BlockMap from './block-map'
|
|
5
|
+
import { MissingBlockError, UnexpectedObjectError } from './error'
|
|
6
|
+
import { cborToLexRecord } from './util'
|
|
7
|
+
|
|
8
|
+
export const getAndParseRecord = async (
|
|
9
|
+
blocks: BlockMap,
|
|
10
|
+
cid: CID,
|
|
11
|
+
): Promise<{ record: RepoRecord; bytes: Uint8Array }> => {
|
|
12
|
+
const bytes = blocks.get(cid)
|
|
13
|
+
if (!bytes) {
|
|
14
|
+
throw new MissingBlockError(cid, 'record')
|
|
15
|
+
}
|
|
16
|
+
const record = await 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 = cborDecode(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
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { Commit, def, DataStore, RepoContents } from './types'
|
|
3
|
+
import { ReadableBlockstore } from './storage'
|
|
4
|
+
import { MST } from './mst'
|
|
5
|
+
import log from './logger'
|
|
6
|
+
import * as util from './util'
|
|
7
|
+
import * as parse from './parse'
|
|
8
|
+
import { MissingBlocksError } from './error'
|
|
9
|
+
|
|
10
|
+
type Params = {
|
|
11
|
+
storage: ReadableBlockstore
|
|
12
|
+
data: DataStore
|
|
13
|
+
commit: Commit
|
|
14
|
+
cid: CID
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ReadableRepo {
|
|
18
|
+
storage: ReadableBlockstore
|
|
19
|
+
data: DataStore
|
|
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.commit)
|
|
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,
|
|
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 getRecord(collection: string, rkey: string): Promise<unknown | null> {
|
|
51
|
+
const dataKey = collection + '/' + rkey
|
|
52
|
+
const cid = await this.data.get(dataKey)
|
|
53
|
+
if (!cid) return null
|
|
54
|
+
return this.storage.readObj(cid, def.unknown)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getContents(): Promise<RepoContents> {
|
|
58
|
+
const entries = await this.data.list()
|
|
59
|
+
const cids = entries.map((e) => e.value)
|
|
60
|
+
const { blocks, missing } = await this.storage.getBlocks(cids)
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
throw new MissingBlocksError('getContents record', missing)
|
|
63
|
+
}
|
|
64
|
+
const contents: RepoContents = {}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const { collection, rkey } = util.parseDataKey(entry.key)
|
|
67
|
+
contents[collection] ??= {}
|
|
68
|
+
const parsed = await parse.getAndParseRecord(blocks, entry.value)
|
|
69
|
+
contents[collection][rkey] = parsed.record
|
|
70
|
+
}
|
|
71
|
+
return contents
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default ReadableRepo
|
package/src/repo.ts
CHANGED
|
@@ -1,311 +1,181 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid'
|
|
2
|
-
import
|
|
3
|
-
import { BlockWriter } from '@ipld/car/writer'
|
|
2
|
+
import * as crypto from '@atproto/crypto'
|
|
4
3
|
import {
|
|
5
|
-
RepoRoot,
|
|
6
4
|
Commit,
|
|
7
5
|
def,
|
|
8
6
|
DataStore,
|
|
9
|
-
RepoMeta,
|
|
10
7
|
RecordCreateOp,
|
|
11
8
|
RecordWriteOp,
|
|
9
|
+
CommitData,
|
|
10
|
+
WriteOpAction,
|
|
12
11
|
} from './types'
|
|
13
|
-
import {
|
|
14
|
-
import IpldStore from './blockstore/ipld-store'
|
|
15
|
-
import * as auth from '@atproto/auth'
|
|
12
|
+
import { RepoStorage } from './storage'
|
|
16
13
|
import { MST } from './mst'
|
|
14
|
+
import DataDiff from './data-diff'
|
|
17
15
|
import log from './logger'
|
|
16
|
+
import BlockMap from './block-map'
|
|
17
|
+
import { ReadableRepo } from './readable-repo'
|
|
18
18
|
import * as util from './util'
|
|
19
19
|
|
|
20
20
|
type Params = {
|
|
21
|
-
|
|
21
|
+
storage: RepoStorage
|
|
22
22
|
data: DataStore
|
|
23
23
|
commit: Commit
|
|
24
|
-
root: RepoRoot
|
|
25
|
-
meta: RepoMeta
|
|
26
24
|
cid: CID
|
|
27
|
-
stagedWrites: RecordWriteOp[]
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
export class Repo {
|
|
31
|
-
|
|
32
|
-
data: DataStore
|
|
33
|
-
commit: Commit
|
|
34
|
-
root: RepoRoot
|
|
35
|
-
meta: RepoMeta
|
|
36
|
-
cid: CID
|
|
37
|
-
stagedWrites: RecordWriteOp[]
|
|
27
|
+
export class Repo extends ReadableRepo {
|
|
28
|
+
storage: RepoStorage
|
|
38
29
|
|
|
39
30
|
constructor(params: Params) {
|
|
40
|
-
|
|
41
|
-
this.
|
|
42
|
-
this.commit = params.commit
|
|
43
|
-
this.root = params.root
|
|
44
|
-
this.meta = params.meta
|
|
45
|
-
this.cid = params.cid
|
|
46
|
-
this.stagedWrites = params.stagedWrites
|
|
31
|
+
super(params)
|
|
32
|
+
this.storage = params.storage
|
|
47
33
|
}
|
|
48
34
|
|
|
49
|
-
static async
|
|
50
|
-
|
|
35
|
+
static async formatInitCommit(
|
|
36
|
+
storage: RepoStorage,
|
|
51
37
|
did: string,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
): Promise<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
tokenCid = await blockstore.stage(auth.encodeUcan(foundUcan))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let data = await MST.create(blockstore)
|
|
65
|
-
for (const write of initialRecords) {
|
|
66
|
-
const cid = await blockstore.stage(write.value)
|
|
67
|
-
const dataKey = write.collection + '/' + write.rkey
|
|
38
|
+
keypair: crypto.Keypair,
|
|
39
|
+
initialWrites: RecordCreateOp[] = [],
|
|
40
|
+
): Promise<CommitData> {
|
|
41
|
+
const newBlocks = new BlockMap()
|
|
42
|
+
|
|
43
|
+
let data = await MST.create(storage)
|
|
44
|
+
for (const record of initialWrites) {
|
|
45
|
+
const cid = await newBlocks.add(record.record)
|
|
46
|
+
const dataKey = util.formatDataKey(record.collection, record.rkey)
|
|
68
47
|
data = await data.add(dataKey, cid)
|
|
69
48
|
}
|
|
70
|
-
const dataCid = await data.stage()
|
|
71
49
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
50
|
+
const unstoredData = await data.getUnstoredBlocks()
|
|
51
|
+
newBlocks.addMap(unstoredData.blocks)
|
|
52
|
+
|
|
53
|
+
const commit = await util.signCommit(
|
|
54
|
+
{
|
|
55
|
+
did,
|
|
56
|
+
version: 2,
|
|
57
|
+
prev: null,
|
|
58
|
+
data: unstoredData.root,
|
|
59
|
+
},
|
|
60
|
+
keypair,
|
|
61
|
+
)
|
|
62
|
+
const commitCid = await newBlocks.add(commit)
|
|
78
63
|
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
return {
|
|
65
|
+
commit: commitCid,
|
|
81
66
|
prev: null,
|
|
82
|
-
|
|
83
|
-
data: dataCid,
|
|
67
|
+
blocks: newBlocks,
|
|
84
68
|
}
|
|
69
|
+
}
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
await blockstore.saveStaged()
|
|
71
|
+
static async createFromCommit(
|
|
72
|
+
storage: RepoStorage,
|
|
73
|
+
commit: CommitData,
|
|
74
|
+
): Promise<Repo> {
|
|
75
|
+
await storage.applyCommit(commit)
|
|
76
|
+
return Repo.load(storage, commit.commit)
|
|
77
|
+
}
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
79
|
+
static async create(
|
|
80
|
+
storage: RepoStorage,
|
|
81
|
+
did: string,
|
|
82
|
+
keypair: crypto.Keypair,
|
|
83
|
+
initialWrites: RecordCreateOp[] = [],
|
|
84
|
+
): Promise<Repo> {
|
|
85
|
+
const commit = await Repo.formatInitCommit(
|
|
86
|
+
storage,
|
|
87
|
+
did,
|
|
88
|
+
keypair,
|
|
89
|
+
initialWrites,
|
|
90
|
+
)
|
|
91
|
+
return Repo.createFromCommit(storage, commit)
|
|
106
92
|
}
|
|
107
93
|
|
|
108
|
-
static async load(
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
94
|
+
static async load(storage: RepoStorage, cid?: CID) {
|
|
95
|
+
const commitCid = cid || (await storage.getHead())
|
|
96
|
+
if (!commitCid) {
|
|
97
|
+
throw new Error('No cid provided and none in storage')
|
|
98
|
+
}
|
|
99
|
+
const commit = await storage.readObj(commitCid, def.commit)
|
|
100
|
+
const data = await MST.load(storage, commit.data)
|
|
101
|
+
log.info({ did: commit.did }, 'loaded repo for')
|
|
114
102
|
return new Repo({
|
|
115
|
-
|
|
103
|
+
storage,
|
|
116
104
|
data,
|
|
117
105
|
commit,
|
|
118
|
-
|
|
119
|
-
meta,
|
|
120
|
-
cid,
|
|
121
|
-
stagedWrites: [],
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private updateRepo(params: Partial<Params>): Repo {
|
|
126
|
-
return new Repo({
|
|
127
|
-
blockstore: params.blockstore || this.blockstore,
|
|
128
|
-
data: params.data || this.data,
|
|
129
|
-
commit: params.commit || this.commit,
|
|
130
|
-
root: params.root || this.root,
|
|
131
|
-
meta: params.meta || this.meta,
|
|
132
|
-
cid: params.cid || this.cid,
|
|
133
|
-
stagedWrites: params.stagedWrites || this.stagedWrites,
|
|
106
|
+
cid: commitCid,
|
|
134
107
|
})
|
|
135
108
|
}
|
|
136
109
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const cid = await this.data.get(dataKey)
|
|
144
|
-
if (!cid) return null
|
|
145
|
-
return this.blockstore.getUnchecked(cid)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
stageUpdate(write: RecordWriteOp | RecordWriteOp[]): Repo {
|
|
149
|
-
const writeArr = Array.isArray(write) ? write : [write]
|
|
150
|
-
return this.updateRepo({
|
|
151
|
-
stagedWrites: [...this.stagedWrites, ...writeArr],
|
|
152
|
-
})
|
|
153
|
-
}
|
|
110
|
+
async formatCommit(
|
|
111
|
+
toWrite: RecordWriteOp | RecordWriteOp[],
|
|
112
|
+
keypair: crypto.Keypair,
|
|
113
|
+
): Promise<CommitData> {
|
|
114
|
+
const writes = Array.isArray(toWrite) ? toWrite : [toWrite]
|
|
115
|
+
const commitBlocks = new BlockMap()
|
|
154
116
|
|
|
155
|
-
async createCommit(
|
|
156
|
-
authStore: auth.AuthStore,
|
|
157
|
-
performUpdate?: (prev: CID, curr: CID) => Promise<CID | null>,
|
|
158
|
-
): Promise<Repo> {
|
|
159
117
|
let data = this.data
|
|
160
|
-
for (const write of
|
|
161
|
-
if (write.action ===
|
|
162
|
-
const cid = await
|
|
118
|
+
for (const write of writes) {
|
|
119
|
+
if (write.action === WriteOpAction.Create) {
|
|
120
|
+
const cid = await commitBlocks.add(write.record)
|
|
163
121
|
const dataKey = write.collection + '/' + write.rkey
|
|
164
122
|
data = await data.add(dataKey, cid)
|
|
165
|
-
} else if (write.action ===
|
|
166
|
-
const cid = await
|
|
123
|
+
} else if (write.action === WriteOpAction.Update) {
|
|
124
|
+
const cid = await commitBlocks.add(write.record)
|
|
167
125
|
const dataKey = write.collection + '/' + write.rkey
|
|
168
126
|
data = await data.update(dataKey, cid)
|
|
169
|
-
} else if (write.action ===
|
|
127
|
+
} else if (write.action === WriteOpAction.Delete) {
|
|
170
128
|
const dataKey = write.collection + '/' + write.rkey
|
|
171
129
|
data = await data.delete(dataKey)
|
|
172
130
|
}
|
|
173
131
|
}
|
|
174
|
-
const token = (await authStore.canSignForDid(this.did))
|
|
175
|
-
? null
|
|
176
|
-
: await util.ucanForOperation(this.data, data, this.did, authStore)
|
|
177
|
-
const tokenCid = token ? await this.blockstore.stage(token) : null
|
|
178
|
-
|
|
179
|
-
const dataCid = await data.stage()
|
|
180
|
-
const root: RepoRoot = {
|
|
181
|
-
meta: this.root.meta,
|
|
182
|
-
prev: this.cid,
|
|
183
|
-
auth_token: tokenCid,
|
|
184
|
-
data: dataCid,
|
|
185
|
-
}
|
|
186
|
-
const rootCid = await this.blockstore.stage(root)
|
|
187
|
-
const commit: Commit = {
|
|
188
|
-
root: rootCid,
|
|
189
|
-
sig: await authStore.sign(rootCid.bytes),
|
|
190
|
-
}
|
|
191
|
-
const commitCid = await this.blockstore.stage(commit)
|
|
192
|
-
|
|
193
|
-
if (performUpdate) {
|
|
194
|
-
const rebaseOn = await performUpdate(this.cid, commitCid)
|
|
195
|
-
if (rebaseOn) {
|
|
196
|
-
await this.blockstore.clearStaged()
|
|
197
|
-
const rebaseRepo = await Repo.load(this.blockstore, rebaseOn)
|
|
198
|
-
return rebaseRepo.createCommit(authStore, performUpdate)
|
|
199
|
-
} else {
|
|
200
|
-
await this.blockstore.saveStaged()
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
await this.blockstore.saveStaged()
|
|
204
|
-
}
|
|
205
132
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const root = await this.blockstore.get(commit.root, def.repoRoot)
|
|
220
|
-
if (root.prev === null) {
|
|
221
|
-
throw new Error(`Could not revert ${count} commits`)
|
|
133
|
+
const unstoredData = await data.getUnstoredBlocks()
|
|
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 diff = await DataDiff.of(data, this.data)
|
|
138
|
+
const found = commitBlocks.getMany(diff.newCidList())
|
|
139
|
+
if (found.missing.length > 0) {
|
|
140
|
+
const fromStorage = await this.storage.getBlocks(found.missing)
|
|
141
|
+
if (fromStorage.missing.length > 0) {
|
|
142
|
+
// this shouldn't ever happen
|
|
143
|
+
throw new Error(
|
|
144
|
+
'Could not find block for commit in Datastore or storage',
|
|
145
|
+
)
|
|
222
146
|
}
|
|
223
|
-
|
|
147
|
+
commitBlocks.addMap(fromStorage.blocks)
|
|
224
148
|
}
|
|
225
|
-
return Repo.load(this.blockstore, revertTo)
|
|
226
|
-
}
|
|
227
149
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return this.openCar((car: BlockWriter) => {
|
|
239
|
-
return this.writeCommitsToCarStream(car, to, this.cid)
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async getFullHistory(): Promise<Uint8Array> {
|
|
244
|
-
return this.getDiffCar(null)
|
|
245
|
-
}
|
|
150
|
+
const commit = await util.signCommit(
|
|
151
|
+
{
|
|
152
|
+
did: this.did,
|
|
153
|
+
version: 2,
|
|
154
|
+
prev: this.cid,
|
|
155
|
+
data: unstoredData.root,
|
|
156
|
+
},
|
|
157
|
+
keypair,
|
|
158
|
+
)
|
|
159
|
+
const commitCid = await commitBlocks.add(commit)
|
|
246
160
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
await fn(writer)
|
|
253
|
-
} finally {
|
|
254
|
-
writer.close()
|
|
161
|
+
return {
|
|
162
|
+
commit: commitCid,
|
|
163
|
+
prev: this.cid,
|
|
164
|
+
blocks: commitBlocks,
|
|
255
165
|
}
|
|
256
|
-
return streamToArray(out)
|
|
257
166
|
}
|
|
258
167
|
|
|
259
|
-
async
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
await this.blockstore.addToCar(car, this.cid)
|
|
263
|
-
await this.blockstore.addToCar(car, commit.root)
|
|
264
|
-
await this.blockstore.addToCar(car, root.meta)
|
|
265
|
-
if (root.auth_token) {
|
|
266
|
-
await this.blockstore.addToCar(car, root.auth_token)
|
|
267
|
-
}
|
|
268
|
-
await this.data.writeToCarStream(car)
|
|
168
|
+
async applyCommit(commitData: CommitData): Promise<Repo> {
|
|
169
|
+
await this.storage.applyCommit(commitData)
|
|
170
|
+
return Repo.load(this.storage, commitData.commit)
|
|
269
171
|
}
|
|
270
172
|
|
|
271
|
-
async
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this.blockstore,
|
|
278
|
-
oldestCommit,
|
|
279
|
-
recentCommit,
|
|
280
|
-
)
|
|
281
|
-
if (commitPath === null) {
|
|
282
|
-
throw new Error('Could not find shared history')
|
|
283
|
-
}
|
|
284
|
-
if (commitPath.length === 0) return
|
|
285
|
-
const firstHeadInPath = await Repo.load(this.blockstore, commitPath[0])
|
|
286
|
-
// handle the first commit
|
|
287
|
-
let prevHead: Repo | null =
|
|
288
|
-
firstHeadInPath.root.prev !== null
|
|
289
|
-
? await Repo.load(this.blockstore, firstHeadInPath.root.prev)
|
|
290
|
-
: null
|
|
291
|
-
for (const commit of commitPath) {
|
|
292
|
-
const nextHead = await Repo.load(this.blockstore, commit)
|
|
293
|
-
await this.blockstore.addToCar(car, nextHead.cid)
|
|
294
|
-
await this.blockstore.addToCar(car, nextHead.commit.root)
|
|
295
|
-
await this.blockstore.addToCar(car, nextHead.root.meta)
|
|
296
|
-
if (nextHead.root.auth_token) {
|
|
297
|
-
await this.blockstore.addToCar(car, nextHead.root.auth_token)
|
|
298
|
-
}
|
|
299
|
-
if (prevHead === null) {
|
|
300
|
-
await nextHead.data.writeToCarStream(car)
|
|
301
|
-
} else {
|
|
302
|
-
const diff = await prevHead.data.diff(nextHead.data)
|
|
303
|
-
await Promise.all(
|
|
304
|
-
diff.newCidList().map((cid) => this.blockstore.addToCar(car, cid)),
|
|
305
|
-
)
|
|
306
|
-
}
|
|
307
|
-
prevHead = nextHead
|
|
308
|
-
}
|
|
173
|
+
async applyWrites(
|
|
174
|
+
toWrite: RecordWriteOp | RecordWriteOp[],
|
|
175
|
+
keypair: crypto.Keypair,
|
|
176
|
+
): Promise<Repo> {
|
|
177
|
+
const commit = await this.formatCommit(toWrite, keypair)
|
|
178
|
+
return this.applyCommit(commit)
|
|
309
179
|
}
|
|
310
180
|
}
|
|
311
181
|
|
package/src/storage/index.ts
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { CommitData, def } from '../types'
|
|
3
|
+
import BlockMap from '../block-map'
|
|
4
|
+
import { MST } from '../mst'
|
|
5
|
+
import DataDiff from '../data-diff'
|
|
6
|
+
import { MissingCommitBlocksError } from '../error'
|
|
7
|
+
import RepoStorage from './repo-storage'
|
|
8
|
+
|
|
9
|
+
export class MemoryBlockstore extends RepoStorage {
|
|
10
|
+
blocks: BlockMap
|
|
11
|
+
head: CID | null = null
|
|
12
|
+
|
|
13
|
+
constructor(blocks?: BlockMap) {
|
|
14
|
+
super()
|
|
15
|
+
this.blocks = new BlockMap()
|
|
16
|
+
if (blocks) {
|
|
17
|
+
this.blocks.addMap(blocks)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getHead(): Promise<CID | null> {
|
|
22
|
+
return this.head
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getBytes(cid: CID): Promise<Uint8Array | null> {
|
|
26
|
+
return this.blocks.get(cid) || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async has(cid: CID): Promise<boolean> {
|
|
30
|
+
return this.blocks.has(cid)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getBlocks(cids: CID[]): Promise<{ blocks: BlockMap; missing: CID[] }> {
|
|
34
|
+
return this.blocks.getMany(cids)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async putBlock(cid: CID, block: Uint8Array): Promise<void> {
|
|
38
|
+
this.blocks.set(cid, block)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async putMany(blocks: BlockMap): Promise<void> {
|
|
42
|
+
this.blocks.addMap(blocks)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async indexCommits(commits: CommitData[]): Promise<void> {
|
|
46
|
+
commits.forEach((commit) => {
|
|
47
|
+
this.blocks.addMap(commit.blocks)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updateHead(cid: CID, _prev: CID | null): Promise<void> {
|
|
52
|
+
this.head = cid
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async applyCommit(commit: CommitData): Promise<void> {
|
|
56
|
+
this.blocks.addMap(commit.blocks)
|
|
57
|
+
this.head = commit.commit
|
|
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
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
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
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async sizeInBytes(): Promise<number> {
|
|
102
|
+
let total = 0
|
|
103
|
+
this.blocks.forEach((bytes) => {
|
|
104
|
+
total += bytes.byteLength
|
|
105
|
+
})
|
|
106
|
+
return total
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async destroy(): Promise<void> {
|
|
110
|
+
this.blocks.clear()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default MemoryBlockstore
|