@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
|
@@ -1,103 +0,0 @@
|
|
|
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
|
|
@@ -1,49 +0,0 @@
|
|
|
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/sync.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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/tests/sync.test.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import * as auth from '@atproto/auth'
|
|
2
|
-
import { TID } from '@atproto/common'
|
|
3
|
-
import { Repo, RepoRoot, verifyUpdates, ucanForOperation } from '../src'
|
|
4
|
-
import { MemoryBlockstore } from '../src/blockstore'
|
|
5
|
-
import * as sync from '../src/sync'
|
|
6
|
-
|
|
7
|
-
import * as util from './_util'
|
|
8
|
-
|
|
9
|
-
describe('Sync', () => {
|
|
10
|
-
const verifier = new auth.Verifier()
|
|
11
|
-
|
|
12
|
-
let aliceBlockstore: MemoryBlockstore, bobBlockstore: MemoryBlockstore
|
|
13
|
-
let aliceRepo: Repo
|
|
14
|
-
let aliceAuth: auth.AuthStore
|
|
15
|
-
let repoData: util.RepoData
|
|
16
|
-
|
|
17
|
-
beforeAll(async () => {
|
|
18
|
-
aliceBlockstore = new MemoryBlockstore()
|
|
19
|
-
aliceAuth = await verifier.createTempAuthStore()
|
|
20
|
-
await aliceAuth.claimFull()
|
|
21
|
-
aliceRepo = await Repo.create(
|
|
22
|
-
aliceBlockstore,
|
|
23
|
-
await aliceAuth.did(),
|
|
24
|
-
aliceAuth,
|
|
25
|
-
)
|
|
26
|
-
bobBlockstore = new MemoryBlockstore()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('syncs an empty repo', async () => {
|
|
30
|
-
const car = await aliceRepo.getFullHistory()
|
|
31
|
-
const repoBob = await sync.loadRepoFromCar(car, bobBlockstore, verifier)
|
|
32
|
-
const data = await repoBob.data.list(10)
|
|
33
|
-
expect(data.length).toBe(0)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
let bobRepo: Repo
|
|
37
|
-
|
|
38
|
-
it('syncs a repo that is starting from scratch', async () => {
|
|
39
|
-
const filled = await util.fillRepo(aliceRepo, aliceAuth, 100)
|
|
40
|
-
aliceRepo = filled.repo
|
|
41
|
-
repoData = filled.data
|
|
42
|
-
await aliceRepo.getFullHistory()
|
|
43
|
-
|
|
44
|
-
const car = await aliceRepo.getFullHistory()
|
|
45
|
-
bobRepo = await sync.loadRepoFromCar(car, bobBlockstore, verifier)
|
|
46
|
-
const diff = await verifyUpdates(bobBlockstore, null, bobRepo.cid, verifier)
|
|
47
|
-
await util.checkRepo(bobRepo, repoData)
|
|
48
|
-
await util.checkRepoDiff(diff, {}, repoData)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('syncs a repo that is behind', async () => {
|
|
52
|
-
// add more to alice's repo & have bob catch up
|
|
53
|
-
const beforeData = JSON.parse(JSON.stringify(repoData))
|
|
54
|
-
const edited = await util.editRepo(aliceRepo, repoData, aliceAuth, {
|
|
55
|
-
adds: 20,
|
|
56
|
-
updates: 20,
|
|
57
|
-
deletes: 20,
|
|
58
|
-
})
|
|
59
|
-
aliceRepo = edited.repo
|
|
60
|
-
repoData = edited.data
|
|
61
|
-
const diffCar = await aliceRepo.getDiffCar(bobRepo.cid)
|
|
62
|
-
const loaded = await sync.loadDiff(bobRepo, diffCar, verifier)
|
|
63
|
-
await util.checkRepo(loaded.repo, repoData)
|
|
64
|
-
await util.checkRepoDiff(loaded.diff, beforeData, repoData)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('throws an error on invalid UCANs', async () => {
|
|
68
|
-
const obj = util.generateObject()
|
|
69
|
-
const cid = await aliceBlockstore.stage(obj)
|
|
70
|
-
const updatedData = await aliceRepo.data.add(
|
|
71
|
-
`com.example.test/${TID.next()}`,
|
|
72
|
-
cid,
|
|
73
|
-
)
|
|
74
|
-
// we create an unrelated token for bob & try to permission alice's repo commit with it
|
|
75
|
-
const bobAuth = await verifier.createTempAuthStore()
|
|
76
|
-
const badUcan = await bobAuth.claimFull()
|
|
77
|
-
const auth_token = await aliceBlockstore.stage(auth.encodeUcan(badUcan))
|
|
78
|
-
const dataCid = await updatedData.stage()
|
|
79
|
-
const root: RepoRoot = {
|
|
80
|
-
meta: aliceRepo.root.meta,
|
|
81
|
-
prev: aliceRepo.cid,
|
|
82
|
-
auth_token,
|
|
83
|
-
data: dataCid,
|
|
84
|
-
}
|
|
85
|
-
const rootCid = await aliceBlockstore.stage(root)
|
|
86
|
-
const commit = {
|
|
87
|
-
root: rootCid,
|
|
88
|
-
sig: await aliceAuth.sign(rootCid.bytes),
|
|
89
|
-
}
|
|
90
|
-
const commitCid = await aliceBlockstore.stage(commit)
|
|
91
|
-
const badAliceRepo = await Repo.load(aliceBlockstore, commitCid)
|
|
92
|
-
const diffCar = await badAliceRepo.getDiffCar(bobRepo.cid)
|
|
93
|
-
await expect(sync.loadDiff(bobRepo, diffCar, verifier)).rejects.toThrow()
|
|
94
|
-
// await aliceBlockstore.clearStaged()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('throws on a bad signature', async () => {
|
|
98
|
-
const obj = util.generateObject()
|
|
99
|
-
const cid = await aliceBlockstore.stage(obj)
|
|
100
|
-
const updatedData = await aliceRepo.data.add(
|
|
101
|
-
`com.example.test/${TID.next()}`,
|
|
102
|
-
cid,
|
|
103
|
-
)
|
|
104
|
-
const authToken = await ucanForOperation(
|
|
105
|
-
aliceRepo.data,
|
|
106
|
-
updatedData,
|
|
107
|
-
aliceRepo.did,
|
|
108
|
-
aliceAuth,
|
|
109
|
-
)
|
|
110
|
-
const authCid = await aliceBlockstore.stage(authToken)
|
|
111
|
-
const dataCid = await updatedData.stage()
|
|
112
|
-
const root: RepoRoot = {
|
|
113
|
-
meta: aliceRepo.root.meta,
|
|
114
|
-
prev: aliceRepo.cid,
|
|
115
|
-
auth_token: authCid,
|
|
116
|
-
data: dataCid,
|
|
117
|
-
}
|
|
118
|
-
const rootCid = await aliceBlockstore.stage(root)
|
|
119
|
-
// we generated a bad sig by signing the data cid instead of root cid
|
|
120
|
-
const commit = {
|
|
121
|
-
root: rootCid,
|
|
122
|
-
sig: await aliceAuth.sign(dataCid.bytes),
|
|
123
|
-
}
|
|
124
|
-
const commitCid = await aliceBlockstore.stage(commit)
|
|
125
|
-
const badAliceRepo = await Repo.load(aliceBlockstore, commitCid)
|
|
126
|
-
const diffCar = await badAliceRepo.getDiffCar(bobRepo.cid)
|
|
127
|
-
await expect(sync.loadDiff(bobRepo, diffCar, verifier)).rejects.toThrow()
|
|
128
|
-
})
|
|
129
|
-
})
|