@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
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ATP Repo
2
+
3
+ The "ATP repository" core implementation (a Merkle Search Tree).
@@ -0,0 +1 @@
1
+ module.exports = require('../../babel.config.js')
@@ -0,0 +1,162 @@
1
+ import { CID } from 'multiformats'
2
+ import { Fanout, MemoryBlockstore, MST, NodeEntry } from '../src'
3
+ import * as util from '../tests/_util'
4
+ import fs from 'fs'
5
+
6
+ type BenchmarkData = {
7
+ fanout: number
8
+ size: number
9
+ addTime: string
10
+ saveTime: string
11
+ walkTime: string
12
+ depth: number
13
+ maxWidth: number
14
+ blockstoreSize: number
15
+ largestProofSize: number
16
+ avgProofSize: number
17
+ widths: Record<number, number>
18
+ }
19
+
20
+ describe('MST Benchmarks', () => {
21
+ let mapping: Record<string, CID>
22
+ let shuffled: [string, CID][]
23
+
24
+ const size = 500000
25
+
26
+ beforeAll(async () => {
27
+ mapping = await util.generateBulkTidMapping(size)
28
+ shuffled = util.shuffle(Object.entries(mapping))
29
+ })
30
+
31
+ // const fanouts: Fanout[] = [8, 16, 32]
32
+ const fanouts: Fanout[] = [16, 32]
33
+ it('benchmarks various fanouts', async () => {
34
+ let benches: BenchmarkData[] = []
35
+ for (const fanout of fanouts) {
36
+ const blockstore = new MemoryBlockstore()
37
+ let mst = await MST.create(blockstore, [], { fanout })
38
+
39
+ const start = Date.now()
40
+
41
+ for (const entry of shuffled) {
42
+ mst = await mst.add(entry[0], entry[1])
43
+ }
44
+
45
+ const doneAdding = Date.now()
46
+
47
+ const root = await mst.save()
48
+
49
+ const doneSaving = Date.now()
50
+
51
+ let reloaded = await MST.load(blockstore, root, { fanout })
52
+ const widthTracker = new NodeWidths()
53
+ for await (const entry of reloaded.walk()) {
54
+ await widthTracker.trackEntry(entry)
55
+ }
56
+
57
+ const doneWalking = Date.now()
58
+
59
+ const paths = await reloaded.paths()
60
+ let largestProof = 0
61
+ let combinedProofSizes = 0
62
+ for (const path of paths) {
63
+ let proofSize = 0
64
+ for (const entry of path) {
65
+ if (entry.isTree()) {
66
+ const bytes = await blockstore.getBytes(entry.pointer)
67
+ proofSize += bytes.byteLength
68
+ }
69
+ }
70
+ largestProof = Math.max(largestProof, proofSize)
71
+ combinedProofSizes += proofSize
72
+ }
73
+ const avgProofSize = Math.ceil(combinedProofSizes / paths.length)
74
+
75
+ const blockstoreSize = await blockstore.sizeInBytes()
76
+
77
+ benches.push({
78
+ fanout,
79
+ size,
80
+ addTime: secDiff(start, doneAdding),
81
+ saveTime: secDiff(doneAdding, doneSaving),
82
+ walkTime: secDiff(doneSaving, doneWalking),
83
+ depth: await mst.getLayer(),
84
+ blockstoreSize,
85
+ largestProofSize: largestProof,
86
+ avgProofSize: avgProofSize,
87
+ maxWidth: widthTracker.max,
88
+ widths: widthTracker.data,
89
+ })
90
+ }
91
+ writeBenchData(benches, 'mst-benchmarks')
92
+ })
93
+ })
94
+
95
+ const secDiff = (first: number, second: number): string => {
96
+ return ((second - first) / 1000).toFixed(3)
97
+ }
98
+
99
+ class NodeWidths {
100
+ data = {
101
+ 0: 0,
102
+ 16: 0,
103
+ 32: 0,
104
+ 48: 0,
105
+ 64: 0,
106
+ 96: 0,
107
+ 128: 0,
108
+ 160: 0,
109
+ 192: 0,
110
+ 224: 0,
111
+ 256: 0,
112
+ }
113
+ max = 0
114
+
115
+ async trackEntry(entry: NodeEntry) {
116
+ if (!entry.isTree()) return
117
+ const entries = await entry.getEntries()
118
+ const width = entries.filter((e) => e.isLeaf()).length
119
+ this.max = Math.max(this.max, width)
120
+ if (width >= 0) this.data[0]++
121
+ if (width >= 16) this.data[16]++
122
+ if (width >= 32) this.data[32]++
123
+ if (width >= 48) this.data[48]++
124
+ if (width >= 64) this.data[64]++
125
+ if (width >= 96) this.data[96]++
126
+ if (width >= 128) this.data[128]++
127
+ if (width >= 160) this.data[160]++
128
+ if (width >= 192) this.data[192]++
129
+ if (width >= 224) this.data[224]++
130
+ if (width >= 256) this.data[256]++
131
+ }
132
+ }
133
+
134
+ const writeBenchData = (benches: BenchmarkData[], fileLoc: string) => {
135
+ let toWrite = ''
136
+ for (const bench of benches) {
137
+ toWrite += `Fanout: ${bench.fanout}
138
+ ----------------------
139
+ Time to add ${bench.size} leaves: ${bench.addTime}s
140
+ Time to save tree with ${bench.size} leaves: ${bench.saveTime}s
141
+ Time to reconstruct & walk ${bench.size} leaves: ${bench.walkTime}s
142
+ Tree depth: ${bench.depth}
143
+ Max Node Width (only counting leaves): ${bench.maxWidth}
144
+ The total blockstore size is: ${bench.blockstoreSize} bytes
145
+ Largest proof size: ${bench.largestProofSize} bytes
146
+ Average proof size: ${bench.avgProofSize} bytes
147
+ Nodes with >= 0 leaves: ${bench.widths[0]}
148
+ Nodes with >= 16 leaves: ${bench.widths[16]}
149
+ Nodes with >= 32 leaves: ${bench.widths[32]}
150
+ Nodes with >= 48 leaves: ${bench.widths[48]}
151
+ Nodes with >= 64 leaves: ${bench.widths[64]}
152
+ Nodes with >= 96 leaves: ${bench.widths[96]}
153
+ Nodes with >= 128 leaves: ${bench.widths[128]}
154
+ Nodes with >= 160 leaves: ${bench.widths[160]}
155
+ Nodes with >= 192 leaves: ${bench.widths[192]}
156
+ Nodes with >= 224 leaves: ${bench.widths[224]}
157
+ Nodes with >= 256 leaves: ${bench.widths[256]}
158
+
159
+ `
160
+ }
161
+ fs.writeFileSync(fileLoc, toWrite)
162
+ }
@@ -0,0 +1,39 @@
1
+ import * as auth from '@atproto/auth'
2
+ import { MemoryBlockstore, Repo } from '../src'
3
+ import * as util from '../tests/_util'
4
+
5
+ describe('Repo Benchmarks', () => {
6
+ const verifier = new auth.Verifier()
7
+ const size = 10000
8
+
9
+ let blockstore: MemoryBlockstore
10
+ let authStore: auth.AuthStore
11
+ let repo: Repo
12
+
13
+ beforeAll(async () => {
14
+ blockstore = new MemoryBlockstore()
15
+ authStore = await verifier.createTempAuthStore()
16
+ await authStore.claimFull()
17
+ repo = await Repo.create(blockstore, await authStore.did(), authStore)
18
+ })
19
+
20
+ it('calculates size', async () => {
21
+ const posts = repo.getCollection('app.bsky.post')
22
+ for (let i = 0; i < size; i++) {
23
+ if (i % 500 === 0) {
24
+ console.log(i)
25
+ }
26
+ await posts.createRecord({
27
+ $type: 'app.bsky.post',
28
+ text: util.randomStr(150),
29
+ reply: {
30
+ root: 'at://did:plc:1234abdefeoi23/app.bsky.post/12345678912345',
31
+ parent: 'at://did:plc:1234abdefeoi23/app.bsky.post/12345678912345',
32
+ },
33
+ createdAt: new Date().toISOString(),
34
+ })
35
+ }
36
+
37
+ console.log('SIZE: ', await blockstore.sizeInBytes())
38
+ })
39
+ })
package/build.js ADDED
@@ -0,0 +1,22 @@
1
+ const pkgJson = require('@npmcli/package-json')
2
+ const { nodeExternalsPlugin } = require('esbuild-node-externals')
3
+
4
+ const buildShallow =
5
+ process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true'
6
+
7
+ if (process.argv.includes('--update-main-to-dist')) {
8
+ return pkgJson
9
+ .load(__dirname)
10
+ .then((pkg) => pkg.update({ main: 'dist/index.js' }))
11
+ .then((pkg) => pkg.save())
12
+ }
13
+
14
+ require('esbuild').build({
15
+ logLevel: 'info',
16
+ entryPoints: ['src/index.ts'],
17
+ bundle: true,
18
+ sourcemap: true,
19
+ outdir: 'dist',
20
+ platform: 'node',
21
+ plugins: buildShallow ? [nodeExternalsPlugin()] : [],
22
+ })
@@ -0,0 +1,2 @@
1
+ export * from './ipld-store';
2
+ export * from './memory-blockstore';
@@ -0,0 +1,27 @@
1
+ import { CID } from 'multiformats/cid';
2
+ import { BlockWriter } from '@ipld/car/writer';
3
+ import { check } from '@atproto/common';
4
+ import { BlockReader } from '@ipld/car/api';
5
+ import CidSet from '../cid-set';
6
+ export declare abstract class IpldStore {
7
+ staged: Map<string, Uint8Array>;
8
+ constructor();
9
+ abstract getSavedBytes(cid: CID): Promise<Uint8Array | null>;
10
+ abstract hasSavedBlock(cid: CID): Promise<boolean>;
11
+ abstract saveStaged(): Promise<void>;
12
+ abstract destroySaved(): Promise<void>;
13
+ stageBytes(k: CID, v: Uint8Array): Promise<void>;
14
+ stage(value: unknown): Promise<CID>;
15
+ getBytes(cid: CID): Promise<Uint8Array>;
16
+ get<T>(cid: CID, schema: check.Def<T>): Promise<T>;
17
+ getUnchecked(cid: CID): Promise<unknown>;
18
+ has(cid: CID): Promise<boolean>;
19
+ isMissing(cid: CID): Promise<boolean>;
20
+ checkMissing(cids: CidSet): Promise<CidSet>;
21
+ clearStaged(): Promise<void>;
22
+ destroy(): Promise<void>;
23
+ addToCar(car: BlockWriter, cid: CID): Promise<void>;
24
+ stageCar(buf: Uint8Array): Promise<CID>;
25
+ stageCarBlocks(car: BlockReader): Promise<void>;
26
+ }
27
+ export default IpldStore;
@@ -0,0 +1,13 @@
1
+ import { CID } from 'multiformats/cid';
2
+ import IpldStore from './ipld-store';
3
+ export declare class MemoryBlockstore extends IpldStore {
4
+ blocks: Map<string, Uint8Array>;
5
+ constructor();
6
+ getSavedBytes(cid: CID): Promise<Uint8Array | null>;
7
+ hasSavedBlock(cid: CID): Promise<boolean>;
8
+ saveStaged(): Promise<void>;
9
+ sizeInBytes(): Promise<number>;
10
+ destroySaved(): Promise<void>;
11
+ getContents(): Promise<Record<string, unknown>>;
12
+ }
13
+ export default MemoryBlockstore;
@@ -0,0 +1,14 @@
1
+ import { CID } from 'multiformats';
2
+ export declare class CidSet {
3
+ private set;
4
+ constructor(arr?: CID[]);
5
+ add(cid: CID): CidSet;
6
+ addSet(toMerge: CidSet): CidSet;
7
+ subtractSet(toSubtract: CidSet): CidSet;
8
+ delete(cid: CID): this;
9
+ has(cid: CID): boolean;
10
+ size(): number;
11
+ clear(): CidSet;
12
+ toList(): CID[];
13
+ }
14
+ export default CidSet;
@@ -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';