@atproto/repo 0.0.1

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.
Files changed (54) hide show
  1. package/README.md +3 -0
  2. package/babel.config.js +1 -0
  3. package/bench/mst.bench.ts +162 -0
  4. package/bench/repo.bench.ts +39 -0
  5. package/build.js +22 -0
  6. package/dist/blockstore/index.d.ts +2 -0
  7. package/dist/blockstore/ipld-store.d.ts +27 -0
  8. package/dist/blockstore/memory-blockstore.d.ts +13 -0
  9. package/dist/cid-set.d.ts +14 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.js +17731 -0
  12. package/dist/index.js.map +7 -0
  13. package/dist/logger.d.ts +2 -0
  14. package/dist/mst/diff.d.ts +33 -0
  15. package/dist/mst/index.d.ts +4 -0
  16. package/dist/mst/mst.d.ts +106 -0
  17. package/dist/mst/util.d.ts +9 -0
  18. package/dist/mst/walker.d.ts +22 -0
  19. package/dist/repo.d.ts +39 -0
  20. package/dist/storage/index.d.ts +1 -0
  21. package/dist/storage/types.d.ts +12 -0
  22. package/dist/sync.d.ts +9 -0
  23. package/dist/types.d.ts +368 -0
  24. package/dist/util.d.ts +13 -0
  25. package/dist/verify.d.ts +5 -0
  26. package/jest.bench.config.js +7 -0
  27. package/jest.config.js +6 -0
  28. package/package.json +34 -0
  29. package/src/blockstore/index.ts +2 -0
  30. package/src/blockstore/ipld-store.ts +103 -0
  31. package/src/blockstore/memory-blockstore.ts +49 -0
  32. package/src/cid-set.ts +50 -0
  33. package/src/index.ts +7 -0
  34. package/src/logger.ts +5 -0
  35. package/src/mst/diff.ts +106 -0
  36. package/src/mst/index.ts +4 -0
  37. package/src/mst/mst.ts +796 -0
  38. package/src/mst/util.ts +122 -0
  39. package/src/mst/walker.ts +120 -0
  40. package/src/repo.ts +312 -0
  41. package/src/storage/index.ts +1 -0
  42. package/src/storage/types.ts +12 -0
  43. package/src/sync.ts +38 -0
  44. package/src/types.ts +101 -0
  45. package/src/util.ts +88 -0
  46. package/src/verify.ts +62 -0
  47. package/tests/_util.ts +254 -0
  48. package/tests/mst.test.ts +280 -0
  49. package/tests/repo.test.ts +107 -0
  50. package/tests/sync.test.ts +129 -0
  51. package/tsconfig.build.json +4 -0
  52. package/tsconfig.build.tsbuildinfo +1 -0
  53. package/tsconfig.json +14 -0
  54. package/update-pkg.js +14 -0
@@ -0,0 +1,122 @@
1
+ import { CID } from 'multiformats'
2
+ import * as uint8arrays from 'uint8arrays'
3
+ import IpldStore from '../blockstore/ipld-store'
4
+ import { sha256 } from '@atproto/crypto'
5
+ import { MST, Leaf, NodeEntry, NodeData, MstOpts, Fanout } from './mst'
6
+ import { cidForData } from '@atproto/common'
7
+
8
+ type SupportedBases = 'base2' | 'base8' | 'base16' | 'base32' | 'base64'
9
+
10
+ export const leadingZerosOnHash = async (
11
+ key: string,
12
+ fanout: Fanout,
13
+ ): Promise<number> => {
14
+ if ([2, 8, 16, 32, 64].indexOf(fanout) < 0) {
15
+ throw new Error(`Not a valid fanout: ${fanout}`)
16
+ }
17
+ const base: SupportedBases = `base${fanout}`
18
+ const zeroChar = uint8arrays.toString(new Uint8Array(1), base)[0]
19
+ const hash = await sha256(key)
20
+ const encoded = uint8arrays.toString(hash, base)
21
+ let count = 0
22
+ for (const char of encoded) {
23
+ if (char === zeroChar) {
24
+ count++
25
+ } else {
26
+ break
27
+ }
28
+ }
29
+ return count
30
+ }
31
+
32
+ export const layerForEntries = async (
33
+ entries: NodeEntry[],
34
+ fanout: Fanout,
35
+ ): Promise<number | null> => {
36
+ const firstLeaf = entries.find((entry) => entry.isLeaf())
37
+ if (!firstLeaf || firstLeaf.isTree()) return null
38
+ return await leadingZerosOnHash(firstLeaf.key, fanout)
39
+ }
40
+
41
+ export const deserializeNodeData = async (
42
+ blockstore: IpldStore,
43
+ data: NodeData,
44
+ opts?: Partial<MstOpts>,
45
+ ): Promise<NodeEntry[]> => {
46
+ const { layer, fanout } = opts || {}
47
+ const entries: NodeEntry[] = []
48
+ if (data.l !== null) {
49
+ entries.push(
50
+ await MST.load(blockstore, data.l, {
51
+ layer: layer ? layer - 1 : undefined,
52
+ fanout,
53
+ }),
54
+ )
55
+ }
56
+ let lastKey = ''
57
+ for (const entry of data.e) {
58
+ const key = lastKey.slice(0, entry.p) + entry.k
59
+ entries.push(new Leaf(key, entry.v))
60
+ lastKey = key
61
+ if (entry.t !== null) {
62
+ entries.push(
63
+ await MST.load(blockstore, entry.t, {
64
+ layer: layer ? layer - 1 : undefined,
65
+ fanout,
66
+ }),
67
+ )
68
+ }
69
+ }
70
+ return entries
71
+ }
72
+
73
+ export const serializeNodeData = (entries: NodeEntry[]): NodeData => {
74
+ const data: NodeData = {
75
+ l: null,
76
+ e: [],
77
+ }
78
+ let i = 0
79
+ if (entries[0]?.isTree()) {
80
+ i++
81
+ data.l = entries[0].pointer
82
+ }
83
+ let lastKey = ''
84
+ while (i < entries.length) {
85
+ const leaf = entries[i]
86
+ const next = entries[i + 1]
87
+ if (!leaf.isLeaf()) {
88
+ throw new Error('Not a valid node: two subtrees next to each other')
89
+ }
90
+ i++
91
+ let subtree: CID | null = null
92
+ if (next?.isTree()) {
93
+ subtree = next.pointer
94
+ i++
95
+ }
96
+ const prefixLen = countPrefixLen(lastKey, leaf.key)
97
+ data.e.push({
98
+ p: prefixLen,
99
+ k: leaf.key.slice(prefixLen),
100
+ v: leaf.value,
101
+ t: subtree,
102
+ })
103
+
104
+ lastKey = leaf.key
105
+ }
106
+ return data
107
+ }
108
+
109
+ export const countPrefixLen = (a: string, b: string): number => {
110
+ let i
111
+ for (i = 0; i < a.length; i++) {
112
+ if (a[i] !== b[i]) {
113
+ break
114
+ }
115
+ }
116
+ return i
117
+ }
118
+
119
+ export const cidForEntries = async (entries: NodeEntry[]): Promise<CID> => {
120
+ const data = serializeNodeData(entries)
121
+ return cidForData(data)
122
+ }
@@ -0,0 +1,120 @@
1
+ import { MST, NodeEntry } from './mst'
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
+ }
119
+
120
+ export default MstWalker
package/src/repo.ts ADDED
@@ -0,0 +1,312 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { CarWriter } from '@ipld/car'
3
+ import { BlockWriter } from '@ipld/car/writer'
4
+ import {
5
+ RepoRoot,
6
+ Commit,
7
+ def,
8
+ DataStore,
9
+ RepoMeta,
10
+ RecordCreateOp,
11
+ RecordWriteOp,
12
+ } from './types'
13
+ import { streamToArray } from '@atproto/common'
14
+ import IpldStore from './blockstore/ipld-store'
15
+ import * as auth from '@atproto/auth'
16
+ import { MST } from './mst'
17
+ import log from './logger'
18
+ import * as util from './util'
19
+
20
+ type Params = {
21
+ blockstore: IpldStore
22
+ data: DataStore
23
+ commit: Commit
24
+ root: RepoRoot
25
+ meta: RepoMeta
26
+ cid: CID
27
+ stagedWrites: RecordWriteOp[]
28
+ }
29
+
30
+ export class Repo {
31
+ blockstore: IpldStore
32
+ data: DataStore
33
+ commit: Commit
34
+ root: RepoRoot
35
+ meta: RepoMeta
36
+ cid: CID
37
+ stagedWrites: RecordWriteOp[]
38
+
39
+ constructor(params: Params) {
40
+ this.blockstore = params.blockstore
41
+ this.data = params.data
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
47
+ }
48
+
49
+ static async create(
50
+ blockstore: IpldStore,
51
+ did: string,
52
+ authStore: auth.AuthStore,
53
+ initialRecords: RecordCreateOp[] = [],
54
+ ): Promise<Repo> {
55
+ let tokenCid: CID | null = null
56
+ if (!(await authStore.canSignForDid(did))) {
57
+ const foundUcan = await authStore.findUcan(auth.maintenanceCap(did))
58
+ if (foundUcan === null) {
59
+ throw new Error(`No valid Ucan for creating repo`)
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
68
+ data = await data.add(dataKey, cid)
69
+ }
70
+ const dataCid = await data.stage()
71
+
72
+ const meta: RepoMeta = {
73
+ did,
74
+ version: 1,
75
+ datastore: 'mst',
76
+ }
77
+ const metaCid = await blockstore.stage(meta)
78
+
79
+ const root: RepoRoot = {
80
+ meta: metaCid,
81
+ prev: null,
82
+ auth_token: tokenCid,
83
+ data: dataCid,
84
+ }
85
+
86
+ const rootCid = await blockstore.stage(root)
87
+ const commit: Commit = {
88
+ root: rootCid,
89
+ sig: await authStore.sign(rootCid.bytes),
90
+ }
91
+
92
+ const cid = await blockstore.stage(commit)
93
+
94
+ await blockstore.saveStaged()
95
+
96
+ log.info({ did }, `created repo`)
97
+ return new Repo({
98
+ blockstore,
99
+ data,
100
+ commit,
101
+ root,
102
+ meta,
103
+ cid,
104
+ stagedWrites: [],
105
+ })
106
+ }
107
+
108
+ static async load(blockstore: IpldStore, cid: CID) {
109
+ const commit = await blockstore.get(cid, def.commit)
110
+ const root = await blockstore.get(commit.root, def.repoRoot)
111
+ const meta = await blockstore.get(root.meta, def.repoMeta)
112
+ const data = await MST.load(blockstore, root.data)
113
+ log.info({ did: meta.did }, 'loaded repo for')
114
+ return new Repo({
115
+ blockstore,
116
+ data,
117
+ commit,
118
+ root,
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,
134
+ })
135
+ }
136
+
137
+ get did(): string {
138
+ return this.meta.did
139
+ }
140
+
141
+ async getRecord(collection: string, rkey: string): Promise<unknown | null> {
142
+ const dataKey = collection + '/' + rkey
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
+ }
154
+
155
+ async createCommit(
156
+ authStore: auth.AuthStore,
157
+ performUpdate?: (prev: CID, curr: CID) => Promise<CID | null>,
158
+ ): Promise<Repo> {
159
+ let data = this.data
160
+ for (const write of this.stagedWrites) {
161
+ if (write.action === 'create') {
162
+ const cid = await this.blockstore.stage(write.value)
163
+ const dataKey = write.collection + '/' + write.rkey
164
+ data = await data.add(dataKey, cid)
165
+ } else if (write.action === 'update') {
166
+ const cid = await this.blockstore.stage(write.value)
167
+ const dataKey = write.collection + '/' + write.rkey
168
+ data = await data.update(dataKey, cid)
169
+ } else if (write.action === 'delete') {
170
+ const dataKey = write.collection + '/' + write.rkey
171
+ data = await data.delete(dataKey)
172
+ }
173
+ }
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
+
206
+ return this.updateRepo({
207
+ cid: commitCid,
208
+ root,
209
+ commit,
210
+ data,
211
+ stagedWrites: [],
212
+ })
213
+ }
214
+
215
+ async revert(count: number): Promise<Repo> {
216
+ let revertTo = this.cid
217
+ for (let i = 0; i < count; i++) {
218
+ const commit = await this.blockstore.get(revertTo, def.commit)
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`)
222
+ }
223
+ revertTo = root.prev
224
+ }
225
+ return Repo.load(this.blockstore, revertTo)
226
+ }
227
+
228
+ // CAR FILES
229
+ // -----------
230
+
231
+ async getCarNoHistory(): Promise<Uint8Array> {
232
+ return this.openCar((car: BlockWriter) => {
233
+ return this.writeCheckoutToCarStream(car)
234
+ })
235
+ }
236
+
237
+ async getDiffCar(to: CID | null): Promise<Uint8Array> {
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
+ }
246
+
247
+ private async openCar(
248
+ fn: (car: BlockWriter) => Promise<void>,
249
+ ): Promise<Uint8Array> {
250
+ const { writer, out } = CarWriter.create([this.cid])
251
+ try {
252
+ await fn(writer)
253
+ } finally {
254
+ writer.close()
255
+ }
256
+ return streamToArray(out)
257
+ }
258
+
259
+ async writeCheckoutToCarStream(car: BlockWriter): Promise<void> {
260
+ const commit = await this.blockstore.get(this.cid, def.commit)
261
+ const root = await this.blockstore.get(commit.root, def.repoRoot)
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)
269
+ }
270
+
271
+ async writeCommitsToCarStream(
272
+ car: BlockWriter,
273
+ oldestCommit: CID | null,
274
+ recentCommit: CID,
275
+ ): Promise<void> {
276
+ const commitPath = await util.getCommitPath(
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
+ }
309
+ }
310
+ }
311
+
312
+ export default Repo
@@ -0,0 +1 @@
1
+ export * from './types'
@@ -0,0 +1,12 @@
1
+ import stream from 'stream'
2
+ import { CID } from 'multiformats/cid'
3
+
4
+ export interface BlobStore {
5
+ putTemp(bytes: Uint8Array | stream.Readable): Promise<string>
6
+ makePermanent(key: string, cid: CID): Promise<void>
7
+ putPermanent(cid: CID, bytes: Uint8Array | stream.Readable): Promise<void>
8
+ getBytes(cid: CID): Promise<Uint8Array>
9
+ getStream(cid: CID): Promise<stream.Readable>
10
+ }
11
+
12
+ export class BlobNotFoundError extends Error {}
package/src/sync.ts ADDED
@@ -0,0 +1,38 @@
1
+ import * as auth from '@atproto/auth'
2
+ import { IpldStore } from './blockstore'
3
+ import { DataDiff } from './mst'
4
+ import Repo from './repo'
5
+ import * as verify from './verify'
6
+
7
+ export const loadRepoFromCar = async (
8
+ carBytes: Uint8Array,
9
+ blockstore: IpldStore,
10
+ verifier: auth.Verifier,
11
+ ): Promise<Repo> => {
12
+ const root = await blockstore.stageCar(carBytes)
13
+ const repo = await Repo.load(blockstore, root)
14
+ await verify.verifyUpdates(blockstore, null, repo.cid, verifier)
15
+ await blockstore.saveStaged()
16
+ return repo
17
+ }
18
+
19
+ export const loadDiff = async (
20
+ repo: Repo,
21
+ diffCar: Uint8Array,
22
+ verifier: auth.Verifier,
23
+ ): Promise<{ repo: Repo; diff: DataDiff }> => {
24
+ const blockstore = repo.blockstore
25
+ const root = await blockstore.stageCar(diffCar)
26
+ const diff = await verify.verifyUpdates(
27
+ repo.blockstore,
28
+ repo.cid,
29
+ root,
30
+ verifier,
31
+ )
32
+ const updatedRepo = await Repo.load(blockstore, root)
33
+ await blockstore.saveStaged()
34
+ return {
35
+ repo: updatedRepo,
36
+ diff,
37
+ }
38
+ }
package/src/types.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod'
2
+ import { BlockWriter } from '@ipld/car/writer'
3
+ import { def as common } from '@atproto/common'
4
+ import { CID } from 'multiformats'
5
+ import { DataDiff } from './mst'
6
+
7
+ const repoMeta = z.object({
8
+ did: z.string(),
9
+ version: z.number(),
10
+ datastore: z.string(),
11
+ })
12
+ export type RepoMeta = z.infer<typeof repoMeta>
13
+
14
+ const repoRoot = z.object({
15
+ meta: common.cid,
16
+ prev: common.cid.nullable(),
17
+ auth_token: common.cid.nullable(),
18
+ data: common.cid,
19
+ })
20
+ export type RepoRoot = z.infer<typeof repoRoot>
21
+
22
+ const commit = z.object({
23
+ root: common.cid,
24
+ sig: common.bytes,
25
+ })
26
+ export type Commit = z.infer<typeof commit>
27
+
28
+ export const cidCreateOp = z.object({
29
+ action: z.literal('create'),
30
+ collection: z.string(),
31
+ rkey: z.string(),
32
+ cid: common.cid,
33
+ })
34
+ export type CidCreateOp = z.infer<typeof cidCreateOp>
35
+
36
+ export const cidUpdateOp = z.object({
37
+ action: z.literal('update'),
38
+ collection: z.string(),
39
+ rkey: z.string(),
40
+ cid: common.cid,
41
+ })
42
+ export type CidUpdateOp = z.infer<typeof cidUpdateOp>
43
+
44
+ export const deleteOp = z.object({
45
+ action: z.literal('delete'),
46
+ collection: z.string(),
47
+ rkey: z.string(),
48
+ })
49
+ export type DeleteOp = z.infer<typeof deleteOp>
50
+
51
+ export const cidWriteOp = z.union([cidCreateOp, cidUpdateOp, deleteOp])
52
+ export type CidWriteOp = z.infer<typeof cidWriteOp>
53
+
54
+ export const recordCreateOp = z.object({
55
+ action: z.literal('create'),
56
+ collection: z.string(),
57
+ rkey: z.string(),
58
+ value: z.any(),
59
+ })
60
+ export type RecordCreateOp = z.infer<typeof recordCreateOp>
61
+
62
+ export const recordUpdateOp = z.object({
63
+ action: z.literal('update'),
64
+ collection: z.string(),
65
+ rkey: z.string(),
66
+ value: z.any(),
67
+ })
68
+ export type RecordUpdateOp = z.infer<typeof recordUpdateOp>
69
+
70
+ export const recordWriteOp = z.union([recordCreateOp, recordUpdateOp, deleteOp])
71
+ export type RecordWriteOp = z.infer<typeof recordWriteOp>
72
+
73
+ export const def = {
74
+ ...common,
75
+ repoMeta,
76
+ repoRoot,
77
+ commit,
78
+ cidWriteOp,
79
+ recordWriteOp,
80
+ }
81
+
82
+ export interface CarStreamable {
83
+ writeToCarStream(car: BlockWriter): Promise<void>
84
+ }
85
+
86
+ export type DataValue = {
87
+ key: string
88
+ value: CID
89
+ }
90
+
91
+ export interface DataStore {
92
+ add(key: string, value: CID): Promise<DataStore>
93
+ update(key: string, value: CID): Promise<DataStore>
94
+ delete(key: string): Promise<DataStore>
95
+ get(key: string): Promise<CID | null>
96
+ list(count: number, after?: string, before?: string): Promise<DataValue[]>
97
+ listWithPrefix(prefix: string, count?: number): Promise<DataValue[]>
98
+ diff(other: DataStore): Promise<DataDiff>
99
+ stage(): Promise<CID>
100
+ writeToCarStream(car: BlockWriter): Promise<void>
101
+ }