@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.
- package/README.md +3 -0
- package/babel.config.js +1 -0
- package/bench/mst.bench.ts +162 -0
- package/bench/repo.bench.ts +39 -0
- package/build.js +22 -0
- package/dist/blockstore/index.d.ts +2 -0
- package/dist/blockstore/ipld-store.d.ts +27 -0
- package/dist/blockstore/memory-blockstore.d.ts +13 -0
- package/dist/cid-set.d.ts +14 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17731 -0
- package/dist/index.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/mst/diff.d.ts +33 -0
- package/dist/mst/index.d.ts +4 -0
- package/dist/mst/mst.d.ts +106 -0
- package/dist/mst/util.d.ts +9 -0
- package/dist/mst/walker.d.ts +22 -0
- package/dist/repo.d.ts +39 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/types.d.ts +12 -0
- package/dist/sync.d.ts +9 -0
- package/dist/types.d.ts +368 -0
- package/dist/util.d.ts +13 -0
- package/dist/verify.d.ts +5 -0
- package/jest.bench.config.js +7 -0
- package/jest.config.js +6 -0
- package/package.json +34 -0
- package/src/blockstore/index.ts +2 -0
- package/src/blockstore/ipld-store.ts +103 -0
- package/src/blockstore/memory-blockstore.ts +49 -0
- package/src/cid-set.ts +50 -0
- package/src/index.ts +7 -0
- package/src/logger.ts +5 -0
- package/src/mst/diff.ts +106 -0
- package/src/mst/index.ts +4 -0
- package/src/mst/mst.ts +796 -0
- package/src/mst/util.ts +122 -0
- package/src/mst/walker.ts +120 -0
- package/src/repo.ts +312 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/types.ts +12 -0
- package/src/sync.ts +38 -0
- package/src/types.ts +101 -0
- package/src/util.ts +88 -0
- package/src/verify.ts +62 -0
- package/tests/_util.ts +254 -0
- package/tests/mst.test.ts +280 -0
- package/tests/repo.test.ts +107 -0
- package/tests/sync.test.ts +129 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +14 -0
- package/update-pkg.js +14 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { BlockWriter } from '@ipld/car/writer'
|
|
3
|
+
|
|
4
|
+
import * as common from '@atproto/common'
|
|
5
|
+
import { check, util, valueToIpldBlock } from '@atproto/common'
|
|
6
|
+
import { BlockReader } from '@ipld/car/api'
|
|
7
|
+
import CidSet from '../cid-set'
|
|
8
|
+
import { CarReader } from '@ipld/car/reader'
|
|
9
|
+
|
|
10
|
+
export abstract class IpldStore {
|
|
11
|
+
staged: Map<string, Uint8Array>
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.staged = new Map()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
abstract getSavedBytes(cid: CID): Promise<Uint8Array | null>
|
|
18
|
+
abstract hasSavedBlock(cid: CID): Promise<boolean>
|
|
19
|
+
abstract saveStaged(): Promise<void>
|
|
20
|
+
abstract destroySaved(): Promise<void>
|
|
21
|
+
|
|
22
|
+
async stageBytes(k: CID, v: Uint8Array): Promise<void> {
|
|
23
|
+
this.staged.set(k.toString(), v)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async stage(value: unknown): Promise<CID> {
|
|
27
|
+
const block = await valueToIpldBlock(value)
|
|
28
|
+
await this.stageBytes(block.cid, block.bytes)
|
|
29
|
+
return block.cid
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getBytes(cid: CID): Promise<Uint8Array> {
|
|
33
|
+
const fromStaged = this.staged.get(cid.toString())
|
|
34
|
+
if (fromStaged) return fromStaged
|
|
35
|
+
const fromBlocks = await this.getSavedBytes(cid)
|
|
36
|
+
if (fromBlocks) return fromBlocks
|
|
37
|
+
throw new Error(`Not found: ${cid.toString()}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async get<T>(cid: CID, schema: check.Def<T>): Promise<T> {
|
|
41
|
+
const value = await this.getUnchecked(cid)
|
|
42
|
+
try {
|
|
43
|
+
return check.assure(schema, value)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Did not find expected object at ${cid.toString()}: ${err}`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getUnchecked(cid: CID): Promise<unknown> {
|
|
52
|
+
const bytes = await this.getBytes(cid)
|
|
53
|
+
return common.ipldBytesToValue(bytes)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async has(cid: CID): Promise<boolean> {
|
|
57
|
+
return this.staged.has(cid.toString()) || this.hasSavedBlock(cid)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async isMissing(cid: CID): Promise<boolean> {
|
|
61
|
+
const has = await this.has(cid)
|
|
62
|
+
return !has
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async checkMissing(cids: CidSet): Promise<CidSet> {
|
|
66
|
+
const missing = await util.asyncFilter(cids.toList(), (c) => {
|
|
67
|
+
return this.isMissing(c)
|
|
68
|
+
})
|
|
69
|
+
return new CidSet(missing)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async clearStaged(): Promise<void> {
|
|
73
|
+
this.staged.clear()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async destroy(): Promise<void> {
|
|
77
|
+
this.clearStaged()
|
|
78
|
+
await this.destroySaved()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async addToCar(car: BlockWriter, cid: CID) {
|
|
82
|
+
car.put({ cid, bytes: await this.getBytes(cid) })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async stageCar(buf: Uint8Array): Promise<CID> {
|
|
86
|
+
const car = await CarReader.fromBytes(buf)
|
|
87
|
+
const roots = await car.getRoots()
|
|
88
|
+
if (roots.length !== 1) {
|
|
89
|
+
throw new Error(`Expected one root, got ${roots.length}`)
|
|
90
|
+
}
|
|
91
|
+
const rootCid = roots[0]
|
|
92
|
+
await this.stageCarBlocks(car)
|
|
93
|
+
return rootCid
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async stageCarBlocks(car: BlockReader): Promise<void> {
|
|
97
|
+
for await (const block of car.blocks()) {
|
|
98
|
+
await this.stageBytes(block.cid, block.bytes)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default IpldStore
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import IpldStore from './ipld-store'
|
|
3
|
+
|
|
4
|
+
export class MemoryBlockstore extends IpldStore {
|
|
5
|
+
blocks: Map<string, Uint8Array>
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super()
|
|
9
|
+
this.blocks = new Map()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async getSavedBytes(cid: CID): Promise<Uint8Array | null> {
|
|
13
|
+
return this.blocks.get(cid.toString()) || null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async hasSavedBlock(cid: CID): Promise<boolean> {
|
|
17
|
+
return this.blocks.has(cid.toString())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async saveStaged(): Promise<void> {
|
|
21
|
+
this.staged.forEach((val, key) => {
|
|
22
|
+
this.blocks.set(key, val)
|
|
23
|
+
})
|
|
24
|
+
this.clearStaged()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async sizeInBytes(): Promise<number> {
|
|
28
|
+
let total = 0
|
|
29
|
+
for (const val of this.blocks.values()) {
|
|
30
|
+
total += val.byteLength
|
|
31
|
+
}
|
|
32
|
+
return total
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async destroySaved(): Promise<void> {
|
|
36
|
+
this.blocks.clear()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mainly for dev purposes
|
|
40
|
+
async getContents(): Promise<Record<string, unknown>> {
|
|
41
|
+
const contents: Record<string, unknown> = {}
|
|
42
|
+
for (const key of this.blocks.keys()) {
|
|
43
|
+
contents[key] = await this.getUnchecked(CID.parse(key))
|
|
44
|
+
}
|
|
45
|
+
return contents
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default MemoryBlockstore
|
package/src/cid-set.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CID } from 'multiformats'
|
|
2
|
+
|
|
3
|
+
export class CidSet {
|
|
4
|
+
private set: Set<string>
|
|
5
|
+
|
|
6
|
+
constructor(arr: CID[] = []) {
|
|
7
|
+
const strArr = arr.map((c) => c.toString())
|
|
8
|
+
this.set = new Set(strArr)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
add(cid: CID): CidSet {
|
|
12
|
+
this.set.add(cid.toString())
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
addSet(toMerge: CidSet): CidSet {
|
|
17
|
+
toMerge.toList().map((c) => this.add(c))
|
|
18
|
+
return this
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
subtractSet(toSubtract: CidSet): CidSet {
|
|
22
|
+
toSubtract.toList().map((c) => this.delete(c))
|
|
23
|
+
return this
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
delete(cid: CID) {
|
|
27
|
+
this.set.delete(cid.toString())
|
|
28
|
+
return this
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(cid: CID): boolean {
|
|
32
|
+
return this.set.has(cid.toString())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
size(): number {
|
|
36
|
+
return this.set.size
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
clear(): CidSet {
|
|
40
|
+
this.set.clear()
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toList(): CID[] {
|
|
45
|
+
const arr = [...this.set]
|
|
46
|
+
return arr.map((c) => CID.parse(c))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default CidSet
|
package/src/index.ts
ADDED
package/src/logger.ts
ADDED
package/src/mst/diff.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as auth from '@atproto/auth'
|
|
2
|
+
import { CID } from 'multiformats'
|
|
3
|
+
import CidSet from '../cid-set'
|
|
4
|
+
import { parseRecordKey } from '../util'
|
|
5
|
+
|
|
6
|
+
export class DataDiff {
|
|
7
|
+
adds: Record<string, DataAdd> = {}
|
|
8
|
+
updates: Record<string, DataUpdate> = {}
|
|
9
|
+
deletes: Record<string, DataDelete> = {}
|
|
10
|
+
|
|
11
|
+
newCids: CidSet = new CidSet()
|
|
12
|
+
|
|
13
|
+
recordAdd(key: string, cid: CID): void {
|
|
14
|
+
this.adds[key] = { key, cid }
|
|
15
|
+
this.newCids.add(cid)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
recordUpdate(key: string, prev: CID, cid: CID): void {
|
|
19
|
+
this.updates[key] = { key, prev, cid }
|
|
20
|
+
this.newCids.add(cid)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
recordDelete(key: string, cid: CID): void {
|
|
24
|
+
this.deletes[key] = { key, cid }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
recordNewCid(cid: CID): void {
|
|
28
|
+
this.newCids.add(cid)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
addDiff(diff: DataDiff) {
|
|
32
|
+
for (const add of diff.addList()) {
|
|
33
|
+
if (this.deletes[add.key]) {
|
|
34
|
+
const del = this.deletes[add.key]
|
|
35
|
+
if (del.cid !== add.cid) {
|
|
36
|
+
this.recordUpdate(add.key, del.cid, add.cid)
|
|
37
|
+
}
|
|
38
|
+
delete this.deletes[add.key]
|
|
39
|
+
} else {
|
|
40
|
+
this.recordAdd(add.key, add.cid)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const update of diff.updateList()) {
|
|
44
|
+
this.recordUpdate(update.key, update.prev, update.cid)
|
|
45
|
+
delete this.adds[update.key]
|
|
46
|
+
delete this.deletes[update.key]
|
|
47
|
+
}
|
|
48
|
+
for (const del of diff.deleteList()) {
|
|
49
|
+
if (this.adds[del.key]) {
|
|
50
|
+
delete this.adds[del.key]
|
|
51
|
+
} else {
|
|
52
|
+
delete this.updates[del.key]
|
|
53
|
+
this.recordDelete(del.key, del.cid)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.newCids.addSet(diff.newCids)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
addList(): DataAdd[] {
|
|
60
|
+
return Object.values(this.adds)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
updateList(): DataUpdate[] {
|
|
64
|
+
return Object.values(this.updates)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
deleteList(): DataDelete[] {
|
|
68
|
+
return Object.values(this.deletes)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
newCidList(): CID[] {
|
|
72
|
+
return this.newCids.toList()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updatedKeys(): string[] {
|
|
76
|
+
const keys = [
|
|
77
|
+
...Object.keys(this.adds),
|
|
78
|
+
...Object.keys(this.updates),
|
|
79
|
+
...Object.keys(this.deletes),
|
|
80
|
+
]
|
|
81
|
+
return [...new Set(keys)]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
neededCapabilities(rootDid: string): auth.ucans.Capability[] {
|
|
85
|
+
return this.updatedKeys().map((key) => {
|
|
86
|
+
const { collection, rkey } = parseRecordKey(key)
|
|
87
|
+
return auth.writeCap(rootDid, collection, rkey)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type DataAdd = {
|
|
93
|
+
key: string
|
|
94
|
+
cid: CID
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type DataUpdate = {
|
|
98
|
+
key: string
|
|
99
|
+
prev: CID
|
|
100
|
+
cid: CID
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type DataDelete = {
|
|
104
|
+
key: string
|
|
105
|
+
cid: CID
|
|
106
|
+
}
|
package/src/mst/index.ts
ADDED