@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +22 -17
  3. package/jest.config.cjs +0 -24
  4. package/src/block-map.ts +0 -131
  5. package/src/car.ts +0 -357
  6. package/src/cid-set.ts +0 -55
  7. package/src/data-diff.ts +0 -117
  8. package/src/error.ts +0 -43
  9. package/src/index.ts +0 -11
  10. package/src/logger.ts +0 -7
  11. package/src/mst/diff.ts +0 -114
  12. package/src/mst/index.ts +0 -4
  13. package/src/mst/mst.ts +0 -892
  14. package/src/mst/util.ts +0 -160
  15. package/src/mst/walker.ts +0 -118
  16. package/src/parse.ts +0 -44
  17. package/src/readable-repo.ts +0 -86
  18. package/src/repo.ts +0 -236
  19. package/src/storage/index.ts +0 -4
  20. package/src/storage/memory-blockstore.ts +0 -76
  21. package/src/storage/readable-blockstore.ts +0 -55
  22. package/src/storage/sync-storage.ts +0 -35
  23. package/src/storage/types.ts +0 -47
  24. package/src/sync/consumer.ts +0 -207
  25. package/src/sync/index.ts +0 -2
  26. package/src/sync/provider.ts +0 -67
  27. package/src/types.ts +0 -227
  28. package/src/util.ts +0 -146
  29. package/tests/_keys.ts +0 -156
  30. package/tests/_util.ts +0 -265
  31. package/tests/car-file-fixtures.json +0 -28
  32. package/tests/car.test.ts +0 -125
  33. package/tests/commit-data.test.ts +0 -94
  34. package/tests/commit-proof-fixtures.json +0 -118
  35. package/tests/commit-proofs.test.ts +0 -63
  36. package/tests/covering-proofs.test.ts +0 -256
  37. package/tests/mst.test.ts +0 -450
  38. package/tests/proofs.test.ts +0 -155
  39. package/tests/repo.test.ts +0 -106
  40. package/tests/sync.test.ts +0 -95
  41. package/tsconfig.build.json +0 -8
  42. package/tsconfig.build.tsbuildinfo +0 -1
  43. package/tsconfig.json +0 -7
  44. 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
- }
@@ -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
@@ -1,4 +0,0 @@
1
- export * from './readable-blockstore.js'
2
- export * from './memory-blockstore.js'
3
- export * from './sync-storage.js'
4
- export * from './types.js'