@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.
Files changed (63) hide show
  1. package/bench/mst.bench.ts +7 -4
  2. package/bench/repo.bench.ts +25 -16
  3. package/dist/block-map.d.ts +25 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +11605 -10399
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +68 -25
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +22 -0
  14. package/dist/repo.d.ts +14 -30
  15. package/dist/storage/index.d.ts +4 -0
  16. package/dist/storage/memory-blockstore.d.ts +28 -0
  17. package/dist/storage/readable-blockstore.d.ts +24 -0
  18. package/dist/storage/repo-storage.d.ts +18 -0
  19. package/dist/storage/sync-storage.d.ts +15 -0
  20. package/dist/storage/types.d.ts +3 -0
  21. package/dist/sync/consumer.d.ts +18 -0
  22. package/dist/sync/index.d.ts +2 -0
  23. package/dist/sync/provider.d.ts +9 -0
  24. package/dist/types.d.ts +124 -317
  25. package/dist/util.d.ts +31 -12
  26. package/dist/verify.d.ts +26 -4
  27. package/package.json +4 -2
  28. package/src/block-map.ts +95 -0
  29. package/src/cid-set.ts +1 -2
  30. package/src/data-diff.ts +121 -0
  31. package/src/error.ts +31 -0
  32. package/src/index.ts +3 -1
  33. package/src/mst/diff.ts +120 -90
  34. package/src/mst/mst.ts +185 -184
  35. package/src/mst/util.ts +54 -31
  36. package/src/parse.ts +44 -0
  37. package/src/readable-repo.ts +75 -0
  38. package/src/repo.ts +119 -249
  39. package/src/storage/index.ts +4 -0
  40. package/src/storage/memory-blockstore.ts +114 -0
  41. package/src/storage/readable-blockstore.ts +56 -0
  42. package/src/storage/repo-storage.ts +42 -0
  43. package/src/storage/sync-storage.ts +35 -0
  44. package/src/storage/types.ts +3 -0
  45. package/src/sync/consumer.ts +137 -0
  46. package/src/sync/index.ts +2 -0
  47. package/src/sync/provider.ts +91 -0
  48. package/src/types.ts +101 -62
  49. package/src/util.ts +237 -56
  50. package/src/verify.ts +207 -42
  51. package/tests/_util.ts +132 -97
  52. package/tests/mst.test.ts +269 -122
  53. package/tests/repo.test.ts +48 -50
  54. package/tests/sync/checkout.test.ts +57 -0
  55. package/tests/sync/diff.test.ts +87 -0
  56. package/tests/sync/narrow.test.ts +145 -0
  57. package/tsconfig.build.tsbuildinfo +1 -1
  58. package/tsconfig.json +2 -1
  59. package/src/blockstore/index.ts +0 -2
  60. package/src/blockstore/ipld-store.ts +0 -103
  61. package/src/blockstore/memory-blockstore.ts +0 -49
  62. package/src/sync.ts +0 -38
  63. 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
- }
@@ -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
- })