@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
@@ -0,0 +1,121 @@
1
+ import { CID } from 'multiformats'
2
+ import CidSet from './cid-set'
3
+ import { MST, mstDiff } from './mst'
4
+ import { DataStore } from './types'
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
+ removedCids: CidSet = new CidSet()
13
+
14
+ static async of(curr: DataStore, prev: DataStore | null): Promise<DataDiff> {
15
+ if (curr instanceof MST && (prev === null || prev instanceof MST)) {
16
+ return mstDiff(curr, prev)
17
+ }
18
+ throw new Error('Unsupported DataStore type for diff')
19
+ }
20
+
21
+ recordAdd(key: string, cid: CID): void {
22
+ this.adds[key] = { key, cid }
23
+ this.newCids.add(cid)
24
+ }
25
+
26
+ recordUpdate(key: string, prev: CID, cid: CID): void {
27
+ this.updates[key] = { key, prev, cid }
28
+ this.newCids.add(cid)
29
+ }
30
+
31
+ recordDelete(key: string, cid: CID): void {
32
+ this.deletes[key] = { key, cid }
33
+ }
34
+
35
+ recordNewCid(cid: CID): void {
36
+ if (this.removedCids.has(cid)) {
37
+ this.removedCids.delete(cid)
38
+ } else {
39
+ this.newCids.add(cid)
40
+ }
41
+ }
42
+
43
+ recordRemovedCid(cid: CID): void {
44
+ if (this.newCids.has(cid)) {
45
+ this.newCids.delete(cid)
46
+ } else {
47
+ this.removedCids.add(cid)
48
+ }
49
+ }
50
+
51
+ addDiff(diff: DataDiff) {
52
+ for (const add of diff.addList()) {
53
+ if (this.deletes[add.key]) {
54
+ const del = this.deletes[add.key]
55
+ if (del.cid !== add.cid) {
56
+ this.recordUpdate(add.key, del.cid, add.cid)
57
+ }
58
+ delete this.deletes[add.key]
59
+ } else {
60
+ this.recordAdd(add.key, add.cid)
61
+ }
62
+ }
63
+ for (const update of diff.updateList()) {
64
+ this.recordUpdate(update.key, update.prev, update.cid)
65
+ delete this.adds[update.key]
66
+ delete this.deletes[update.key]
67
+ }
68
+ for (const del of diff.deleteList()) {
69
+ if (this.adds[del.key]) {
70
+ delete this.adds[del.key]
71
+ } else {
72
+ delete this.updates[del.key]
73
+ this.recordDelete(del.key, del.cid)
74
+ }
75
+ }
76
+ this.newCids.addSet(diff.newCids)
77
+ }
78
+
79
+ addList(): DataAdd[] {
80
+ return Object.values(this.adds)
81
+ }
82
+
83
+ updateList(): DataUpdate[] {
84
+ return Object.values(this.updates)
85
+ }
86
+
87
+ deleteList(): DataDelete[] {
88
+ return Object.values(this.deletes)
89
+ }
90
+
91
+ newCidList(): CID[] {
92
+ return this.newCids.toList()
93
+ }
94
+
95
+ updatedKeys(): string[] {
96
+ const keys = [
97
+ ...Object.keys(this.adds),
98
+ ...Object.keys(this.updates),
99
+ ...Object.keys(this.deletes),
100
+ ]
101
+ return [...new Set(keys)]
102
+ }
103
+ }
104
+
105
+ export type DataAdd = {
106
+ key: string
107
+ cid: CID
108
+ }
109
+
110
+ export type DataUpdate = {
111
+ key: string
112
+ prev: CID
113
+ cid: CID
114
+ }
115
+
116
+ export type DataDelete = {
117
+ key: string
118
+ cid: CID
119
+ }
120
+
121
+ export default DataDiff
package/src/error.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { CID } from 'multiformats/cid'
2
+
3
+ export class MissingBlockError extends Error {
4
+ constructor(public cid: CID, def?: string) {
5
+ let msg = `block not found: ${cid.toString()}`
6
+ if (def) {
7
+ msg += `, expected type: ${def}`
8
+ }
9
+ super(msg)
10
+ }
11
+ }
12
+
13
+ export class MissingBlocksError extends Error {
14
+ constructor(public context: string, public cids: CID[]) {
15
+ const cidStr = cids.map((c) => c.toString())
16
+ super(`missing ${context} blocks: ${cidStr}`)
17
+ }
18
+ }
19
+
20
+ export class MissingCommitBlocksError extends Error {
21
+ constructor(public commit: CID, public cids: CID[]) {
22
+ const cidStr = cids.map((c) => c.toString())
23
+ super(`missing blocks for commit ${commit.toString()}: ${cidStr}`)
24
+ }
25
+ }
26
+
27
+ export class UnexpectedObjectError extends Error {
28
+ constructor(public cid: CID, public def: string) {
29
+ super(`unexpected object at ${cid.toString()}, expected: ${def}`)
30
+ }
31
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
- export * from './blockstore'
1
+ export * from './block-map'
2
+ export * from './cid-set'
2
3
  export * from './repo'
3
4
  export * from './mst'
4
5
  export * from './storage'
6
+ export * from './sync'
5
7
  export * from './types'
6
8
  export * from './verify'
7
9
  export * from './util'
package/src/mst/diff.ts CHANGED
@@ -1,106 +1,136 @@
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 }
1
+ import { DataDiff } from '../data-diff'
2
+ import MST from './mst'
3
+ import MstWalker from './walker'
4
+
5
+ export const nullDiff = async (tree: MST): Promise<DataDiff> => {
6
+ const diff = new DataDiff()
7
+ for await (const entry of tree.walk()) {
8
+ if (entry.isLeaf()) {
9
+ diff.recordAdd(entry.key, entry.value)
10
+ } else {
11
+ diff.recordNewCid(entry.pointer)
12
+ }
25
13
  }
14
+ return diff
15
+ }
26
16
 
27
- recordNewCid(cid: CID): void {
28
- this.newCids.add(cid)
17
+ export const mstDiff = async (
18
+ curr: MST,
19
+ prev: MST | null,
20
+ ): Promise<DataDiff> => {
21
+ await curr.getPointer()
22
+ if (prev === null) {
23
+ return nullDiff(curr)
29
24
  }
30
25
 
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]
26
+ await prev.getPointer()
27
+ const diff = new DataDiff()
28
+
29
+ const leftWalker = new MstWalker(prev)
30
+ const rightWalker = new MstWalker(curr)
31
+ while (!leftWalker.status.done || !rightWalker.status.done) {
32
+ // if one walker is finished, continue walking the other & logging all nodes
33
+ if (leftWalker.status.done && !rightWalker.status.done) {
34
+ const node = rightWalker.status.curr
35
+ if (node.isLeaf()) {
36
+ diff.recordAdd(node.key, node.value)
39
37
  } else {
40
- this.recordAdd(add.key, add.cid)
38
+ diff.recordNewCid(node.pointer)
41
39
  }
40
+ await rightWalker.advance()
41
+ continue
42
+ } else if (!leftWalker.status.done && rightWalker.status.done) {
43
+ const node = leftWalker.status.curr
44
+ if (node.isLeaf()) {
45
+ diff.recordDelete(node.key, node.value)
46
+ } else {
47
+ diff.recordRemovedCid(node.pointer)
48
+ }
49
+ await leftWalker.advance()
50
+ continue
42
51
  }
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]
52
+ if (leftWalker.status.done || rightWalker.status.done) break
53
+ const left = leftWalker.status.curr
54
+ const right = rightWalker.status.curr
55
+ if (left === null || right === null) break
56
+
57
+ // if both pointers are leaves, record an update & advance both or record the lowest key and advance that pointer
58
+ if (left.isLeaf() && right.isLeaf()) {
59
+ if (left.key === right.key) {
60
+ if (!left.value.equals(right.value)) {
61
+ diff.recordUpdate(left.key, left.value, right.value)
62
+ }
63
+ await leftWalker.advance()
64
+ await rightWalker.advance()
65
+ } else if (left.key < right.key) {
66
+ diff.recordDelete(left.key, left.value)
67
+ await leftWalker.advance()
51
68
  } else {
52
- delete this.updates[del.key]
53
- this.recordDelete(del.key, del.cid)
69
+ diff.recordAdd(right.key, right.value)
70
+ await rightWalker.advance()
54
71
  }
72
+ continue
55
73
  }
56
- this.newCids.addSet(diff.newCids)
57
- }
58
74
 
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
- }
75
+ // next, ensure that we're on the same layer
76
+ // if one walker is at a higher layer than the other, we need to do one of two things
77
+ // if the higher walker is pointed at a tree, step into that tree to try to catch up with the lower
78
+ // if the higher walker is pointed at a leaf, then advance the lower walker to try to catch up the higher
79
+ if (leftWalker.layer() > rightWalker.layer()) {
80
+ if (left.isLeaf()) {
81
+ if (right.isLeaf()) {
82
+ diff.recordAdd(right.key, right.value)
83
+ } else {
84
+ diff.recordNewCid(right.pointer)
85
+ }
86
+ await rightWalker.advance()
87
+ } else {
88
+ diff.recordRemovedCid(left.pointer)
89
+ await leftWalker.stepInto()
90
+ }
91
+ continue
92
+ } else if (leftWalker.layer() < rightWalker.layer()) {
93
+ if (right.isLeaf()) {
94
+ if (left.isLeaf()) {
95
+ diff.recordDelete(left.key, left.value)
96
+ } else {
97
+ diff.recordRemovedCid(left.pointer)
98
+ }
99
+ await leftWalker.advance()
100
+ } else {
101
+ diff.recordNewCid(right.pointer)
102
+ await rightWalker.stepInto()
103
+ }
104
+ continue
105
+ }
70
106
 
71
- newCidList(): CID[] {
72
- return this.newCids.toList()
73
- }
107
+ // if we're on the same level, and both pointers are trees, do a comparison
108
+ // if they're the same, step over. if they're different, step in to find the subdiff
109
+ if (left.isTree() && right.isTree()) {
110
+ if (left.pointer.equals(right.pointer)) {
111
+ await leftWalker.stepOver()
112
+ await rightWalker.stepOver()
113
+ } else {
114
+ diff.recordNewCid(right.pointer)
115
+ diff.recordRemovedCid(left.pointer)
116
+ await leftWalker.stepInto()
117
+ await rightWalker.stepInto()
118
+ }
119
+ continue
120
+ }
74
121
 
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
- }
122
+ // finally, if one pointer is a tree and the other is a leaf, simply step into the tree
123
+ if (left.isLeaf() && right.isTree()) {
124
+ diff.recordNewCid(right.pointer)
125
+ await rightWalker.stepInto()
126
+ continue
127
+ } else if (left.isTree() && right.isLeaf()) {
128
+ diff.recordRemovedCid(left.pointer)
129
+ await leftWalker.stepInto()
130
+ continue
131
+ }
83
132
 
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
- })
133
+ throw new Error('Unidentifiable case in diff walk')
89
134
  }
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
135
+ return diff
106
136
  }