@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,2 @@
1
+ export * from './ipld-store'
2
+ export * from './memory-blockstore'
@@ -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
@@ -0,0 +1,7 @@
1
+ export * from './blockstore'
2
+ export * from './repo'
3
+ export * from './mst'
4
+ export * from './storage'
5
+ export * from './types'
6
+ export * from './verify'
7
+ export * from './util'
package/src/logger.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { subsystemLogger } from '@atproto/common'
2
+
3
+ export const logger = subsystemLogger('repo')
4
+
5
+ export default logger
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export * from './mst'
2
+ export * from './diff'
3
+ export * from './walker'
4
+ export * as mstUtil from './util'