@atproto/repo 0.1.0 → 0.3.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 (49) hide show
  1. package/dist/block-map.d.ts +2 -0
  2. package/dist/data-diff.d.ts +12 -10
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +12388 -4431
  5. package/dist/index.js.map +4 -4
  6. package/dist/mst/mst.d.ts +19 -16
  7. package/dist/readable-repo.d.ts +4 -3
  8. package/dist/repo.d.ts +3 -2
  9. package/dist/storage/index.d.ts +0 -1
  10. package/dist/storage/memory-blockstore.d.ts +6 -10
  11. package/dist/storage/types.d.ts +29 -0
  12. package/dist/sync/consumer.d.ts +13 -16
  13. package/dist/sync/provider.d.ts +2 -6
  14. package/dist/types.d.ts +236 -48
  15. package/dist/util.d.ts +9 -7
  16. package/jest.bench.config.js +2 -1
  17. package/package.json +12 -7
  18. package/src/block-map.ts +8 -0
  19. package/src/data-diff.ts +47 -49
  20. package/src/index.ts +1 -1
  21. package/src/mst/diff.ts +14 -36
  22. package/src/mst/mst.ts +15 -14
  23. package/src/readable-repo.ts +5 -5
  24. package/src/repo.ts +50 -40
  25. package/src/storage/index.ts +0 -1
  26. package/src/storage/memory-blockstore.ts +19 -59
  27. package/src/storage/types.ts +30 -0
  28. package/src/sync/consumer.ts +170 -113
  29. package/src/sync/provider.ts +6 -44
  30. package/src/types.ts +49 -25
  31. package/src/util.ts +57 -91
  32. package/tests/_util.ts +38 -67
  33. package/tests/mst.test.ts +4 -1
  34. package/tests/{sync/narrow.test.ts → proofs.test.ts} +14 -21
  35. package/tests/repo.test.ts +5 -4
  36. package/tests/sync.test.ts +97 -0
  37. package/tests/util.test.ts +21 -0
  38. package/tsconfig.build.tsbuildinfo +1 -1
  39. package/tsconfig.json +1 -1
  40. package/dist/blockstore/index.d.ts +0 -2
  41. package/dist/blockstore/ipld-store.d.ts +0 -27
  42. package/dist/blockstore/memory-blockstore.d.ts +0 -13
  43. package/dist/storage/repo-storage.d.ts +0 -18
  44. package/dist/sync.d.ts +0 -9
  45. package/dist/verify.d.ts +0 -27
  46. package/src/storage/repo-storage.ts +0 -42
  47. package/src/verify.ts +0 -227
  48. package/tests/sync/checkout.test.ts +0 -57
  49. package/tests/sync/diff.test.ts +0 -87
@@ -2,6 +2,7 @@ const base = require('./jest.config')
2
2
 
3
3
  module.exports = {
4
4
  ...base,
5
- testRegex: '(/bench/.*.bench)',
5
+ roots: ['<rootDir>/bench'],
6
+ testRegex: '(.*.bench)',
6
7
  testTimeout: 3000000,
7
8
  }
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "@atproto/repo",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/bluesky-social/atproto.git",
9
+ "directory": "packages/repo"
10
+ },
6
11
  "scripts": {
7
12
  "test": "jest",
8
13
  "test:profile": "node --inspect ../../node_modules/.bin/jest",
9
14
  "bench": "jest --config jest.bench.config.js",
10
- "bench:profile": "node --inspect ../../node_modules/.bin/jest --config jest.bench.config.js",
11
- "prettier": "prettier --check src/",
12
- "prettier:fix": "prettier --write src/",
15
+ "bench:profile": "node --inspect-brk ../../node_modules/.bin/jest --config jest.bench.config.js",
16
+ "prettier": "prettier --check src/ tests/",
17
+ "prettier:fix": "prettier --write src/ tests/",
13
18
  "lint": "eslint . --ext .ts,.tsx",
14
19
  "lint:fix": "yarn lint --fix",
15
20
  "verify": "run-p prettier lint",
@@ -24,13 +29,13 @@
24
29
  "dependencies": {
25
30
  "@atproto/common": "*",
26
31
  "@atproto/crypto": "*",
27
- "@atproto/did-resolver": "*",
32
+ "@atproto/identity": "*",
28
33
  "@atproto/lexicon": "*",
29
- "@atproto/nsid": "*",
34
+ "@atproto/syntax": "*",
30
35
  "@ipld/car": "^3.2.3",
31
36
  "@ipld/dag-cbor": "^7.0.0",
32
37
  "multiformats": "^9.6.4",
33
38
  "uint8arrays": "3.0.0",
34
- "zod": "^3.14.2"
39
+ "zod": "^3.21.4"
35
40
  }
36
41
  }
package/src/block-map.ts CHANGED
@@ -20,6 +20,10 @@ export class BlockMap {
20
20
  return this.map.get(cid.toString())
21
21
  }
22
22
 
23
+ delete(cid: CID) {
24
+ this.map.delete(cid.toString())
25
+ }
26
+
23
27
  getMany(cids: CID[]): { blocks: BlockMap; missing: CID[] } {
24
28
  const missing: CID[] = []
25
29
  const blocks = new BlockMap()
@@ -54,6 +58,10 @@ export class BlockMap {
54
58
  return entries
55
59
  }
56
60
 
61
+ cids(): CID[] {
62
+ return this.entries().map((e) => e.cid)
63
+ }
64
+
57
65
  addMap(toAdd: BlockMap) {
58
66
  toAdd.forEach((bytes, cid) => {
59
67
  this.set(cid, bytes)
package/src/data-diff.ts CHANGED
@@ -1,81 +1,83 @@
1
1
  import { CID } from 'multiformats'
2
2
  import CidSet from './cid-set'
3
- import { MST, mstDiff } from './mst'
4
- import { DataStore } from './types'
3
+ import { MST, NodeEntry, mstDiff } from './mst'
4
+ import BlockMap from './block-map'
5
5
 
6
6
  export class DataDiff {
7
7
  adds: Record<string, DataAdd> = {}
8
8
  updates: Record<string, DataUpdate> = {}
9
9
  deletes: Record<string, DataDelete> = {}
10
10
 
11
- newCids: CidSet = new CidSet()
11
+ newMstBlocks: BlockMap = new BlockMap()
12
+ newLeafCids: CidSet = new CidSet()
12
13
  removedCids: CidSet = new CidSet()
13
14
 
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)
15
+ static async of(curr: MST, prev: MST | null): Promise<DataDiff> {
16
+ return mstDiff(curr, prev)
17
+ }
18
+
19
+ async nodeAdd(node: NodeEntry) {
20
+ if (node.isLeaf()) {
21
+ this.leafAdd(node.key, node.value)
22
+ } else {
23
+ const data = await node.serialize()
24
+ this.treeAdd(data.cid, data.bytes)
17
25
  }
18
- throw new Error('Unsupported DataStore type for diff')
19
26
  }
20
27
 
21
- recordAdd(key: string, cid: CID): void {
28
+ async nodeDelete(node: NodeEntry) {
29
+ if (node.isLeaf()) {
30
+ const key = node.key
31
+ const cid = node.value
32
+ this.deletes[key] = { key, cid }
33
+ this.removedCids.add(cid)
34
+ } else {
35
+ const cid = await node.getPointer()
36
+ this.treeDelete(cid)
37
+ }
38
+ }
39
+
40
+ leafAdd(key: string, cid: CID) {
22
41
  this.adds[key] = { key, cid }
23
- this.newCids.add(cid)
42
+ if (this.removedCids.has(cid)) {
43
+ this.removedCids.delete(cid)
44
+ } else {
45
+ this.newLeafCids.add(cid)
46
+ }
24
47
  }
25
48
 
26
- recordUpdate(key: string, prev: CID, cid: CID): void {
49
+ leafUpdate(key: string, prev: CID, cid: CID) {
50
+ if (prev.equals(cid)) return
27
51
  this.updates[key] = { key, prev, cid }
28
- this.newCids.add(cid)
52
+ this.removedCids.add(prev)
53
+ this.newLeafCids.add(cid)
29
54
  }
30
55
 
31
- recordDelete(key: string, cid: CID): void {
56
+ leafDelete(key: string, cid: CID) {
32
57
  this.deletes[key] = { key, cid }
58
+ if (this.newLeafCids.has(cid)) {
59
+ this.newLeafCids.delete(cid)
60
+ } else {
61
+ this.removedCids.add(cid)
62
+ }
33
63
  }
34
64
 
35
- recordNewCid(cid: CID): void {
65
+ treeAdd(cid: CID, bytes: Uint8Array) {
36
66
  if (this.removedCids.has(cid)) {
37
67
  this.removedCids.delete(cid)
38
68
  } else {
39
- this.newCids.add(cid)
69
+ this.newMstBlocks.set(cid, bytes)
40
70
  }
41
71
  }
42
72
 
43
- recordRemovedCid(cid: CID): void {
44
- if (this.newCids.has(cid)) {
45
- this.newCids.delete(cid)
73
+ treeDelete(cid: CID) {
74
+ if (this.newMstBlocks.has(cid)) {
75
+ this.newMstBlocks.delete(cid)
46
76
  } else {
47
77
  this.removedCids.add(cid)
48
78
  }
49
79
  }
50
80
 
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
81
  addList(): DataAdd[] {
80
82
  return Object.values(this.adds)
81
83
  }
@@ -88,10 +90,6 @@ export class DataDiff {
88
90
  return Object.values(this.deletes)
89
91
  }
90
92
 
91
- newCidList(): CID[] {
92
- return this.newCids.toList()
93
- }
94
-
95
93
  updatedKeys(): string[] {
96
94
  const keys = [
97
95
  ...Object.keys(this.adds),
package/src/index.ts CHANGED
@@ -5,5 +5,5 @@ export * from './mst'
5
5
  export * from './storage'
6
6
  export * from './sync'
7
7
  export * from './types'
8
- export * from './verify'
8
+ export * from './data-diff'
9
9
  export * from './util'
package/src/mst/diff.ts CHANGED
@@ -5,11 +5,7 @@ import MstWalker from './walker'
5
5
  export const nullDiff = async (tree: MST): Promise<DataDiff> => {
6
6
  const diff = new DataDiff()
7
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
- }
8
+ await diff.nodeAdd(entry)
13
9
  }
14
10
  return diff
15
11
  }
@@ -31,21 +27,11 @@ export const mstDiff = async (
31
27
  while (!leftWalker.status.done || !rightWalker.status.done) {
32
28
  // if one walker is finished, continue walking the other & logging all nodes
33
29
  if (leftWalker.status.done && !rightWalker.status.done) {
34
- const node = rightWalker.status.curr
35
- if (node.isLeaf()) {
36
- diff.recordAdd(node.key, node.value)
37
- } else {
38
- diff.recordNewCid(node.pointer)
39
- }
30
+ await diff.nodeAdd(rightWalker.status.curr)
40
31
  await rightWalker.advance()
41
32
  continue
42
33
  } 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
- }
34
+ await diff.nodeDelete(leftWalker.status.curr)
49
35
  await leftWalker.advance()
50
36
  continue
51
37
  }
@@ -58,15 +44,15 @@ export const mstDiff = async (
58
44
  if (left.isLeaf() && right.isLeaf()) {
59
45
  if (left.key === right.key) {
60
46
  if (!left.value.equals(right.value)) {
61
- diff.recordUpdate(left.key, left.value, right.value)
47
+ diff.leafUpdate(left.key, left.value, right.value)
62
48
  }
63
49
  await leftWalker.advance()
64
50
  await rightWalker.advance()
65
51
  } else if (left.key < right.key) {
66
- diff.recordDelete(left.key, left.value)
52
+ diff.leafDelete(left.key, left.value)
67
53
  await leftWalker.advance()
68
54
  } else {
69
- diff.recordAdd(right.key, right.value)
55
+ diff.leafAdd(right.key, right.value)
70
56
  await rightWalker.advance()
71
57
  }
72
58
  continue
@@ -78,27 +64,19 @@ export const mstDiff = async (
78
64
  // if the higher walker is pointed at a leaf, then advance the lower walker to try to catch up the higher
79
65
  if (leftWalker.layer() > rightWalker.layer()) {
80
66
  if (left.isLeaf()) {
81
- if (right.isLeaf()) {
82
- diff.recordAdd(right.key, right.value)
83
- } else {
84
- diff.recordNewCid(right.pointer)
85
- }
67
+ await diff.nodeAdd(right)
86
68
  await rightWalker.advance()
87
69
  } else {
88
- diff.recordRemovedCid(left.pointer)
70
+ await diff.nodeDelete(left)
89
71
  await leftWalker.stepInto()
90
72
  }
91
73
  continue
92
74
  } else if (leftWalker.layer() < rightWalker.layer()) {
93
75
  if (right.isLeaf()) {
94
- if (left.isLeaf()) {
95
- diff.recordDelete(left.key, left.value)
96
- } else {
97
- diff.recordRemovedCid(left.pointer)
98
- }
76
+ await diff.nodeDelete(left)
99
77
  await leftWalker.advance()
100
78
  } else {
101
- diff.recordNewCid(right.pointer)
79
+ await diff.nodeAdd(right)
102
80
  await rightWalker.stepInto()
103
81
  }
104
82
  continue
@@ -111,8 +89,8 @@ export const mstDiff = async (
111
89
  await leftWalker.stepOver()
112
90
  await rightWalker.stepOver()
113
91
  } else {
114
- diff.recordNewCid(right.pointer)
115
- diff.recordRemovedCid(left.pointer)
92
+ await diff.nodeAdd(right)
93
+ await diff.nodeDelete(left)
116
94
  await leftWalker.stepInto()
117
95
  await rightWalker.stepInto()
118
96
  }
@@ -121,11 +99,11 @@ export const mstDiff = async (
121
99
 
122
100
  // finally, if one pointer is a tree and the other is a leaf, simply step into the tree
123
101
  if (left.isLeaf() && right.isTree()) {
124
- diff.recordNewCid(right.pointer)
102
+ await diff.nodeAdd(right)
125
103
  await rightWalker.stepInto()
126
104
  continue
127
105
  } else if (left.isTree() && right.isLeaf()) {
128
- diff.recordRemovedCid(left.pointer)
106
+ await diff.nodeDelete(left)
129
107
  await leftWalker.stepInto()
130
108
  continue
131
109
  }
package/src/mst/mst.ts CHANGED
@@ -2,8 +2,7 @@ import z from 'zod'
2
2
  import { CID } from 'multiformats'
3
3
 
4
4
  import { ReadableBlockstore } from '../storage'
5
- import { schema as common, cidForCbor } from '@atproto/common'
6
- import { DataStore } from '../types'
5
+ import { schema as common, cidForCbor, dataToCborBlock } from '@atproto/common'
7
6
  import { BlockWriter } from '@ipld/car/api'
8
7
  import * as util from './util'
9
8
  import BlockMap from '../block-map'
@@ -68,7 +67,7 @@ export type MstOpts = {
68
67
  layer: number
69
68
  }
70
69
 
71
- export class MST implements DataStore {
70
+ export class MST {
72
71
  storage: ReadableBlockstore
73
72
  entries: NodeEntry[] | null
74
73
  layer: number | null
@@ -154,6 +153,13 @@ export class MST implements DataStore {
154
153
  // Instead we keep track of whether the pointer is outdated and only (recursively) calculate when needed
155
154
  async getPointer(): Promise<CID> {
156
155
  if (!this.outdatedPointer) return this.pointer
156
+ const { cid } = await this.serialize()
157
+ this.pointer = cid
158
+ this.outdatedPointer = false
159
+ return this.pointer
160
+ }
161
+
162
+ async serialize(): Promise<{ cid: CID; bytes: Uint8Array }> {
157
163
  let entries = await this.getEntries()
158
164
  const outdated = entries.filter(
159
165
  (e) => e.isTree() && e.outdatedPointer,
@@ -162,9 +168,12 @@ export class MST implements DataStore {
162
168
  await Promise.all(outdated.map((e) => e.getPointer()))
163
169
  entries = await this.getEntries()
164
170
  }
165
- this.pointer = await util.cidForEntries(entries)
166
- this.outdatedPointer = false
167
- return this.pointer
171
+ const data = util.serializeNodeData(entries)
172
+ const block = await dataToCborBlock(data)
173
+ return {
174
+ cid: block.cid,
175
+ bytes: block.bytes,
176
+ }
168
177
  }
169
178
 
170
179
  // In most cases, we get the layer of a node from a hint on creation
@@ -695,17 +704,9 @@ export class MST implements DataStore {
695
704
  // Sync Protocol
696
705
 
697
706
  async writeToCarStream(car: BlockWriter): Promise<void> {
698
- const entries = await this.getEntries()
699
707
  const leaves = new CidSet()
700
708
  let toFetch = new CidSet()
701
709
  toFetch.add(await this.getPointer())
702
- for (const entry of entries) {
703
- if (entry.isLeaf()) {
704
- leaves.add(entry.value)
705
- } else {
706
- toFetch.add(await entry.getPointer())
707
- }
708
- }
709
710
  while (toFetch.size() > 0) {
710
711
  const nextLayer = new CidSet()
711
712
  const fetched = await this.storage.getBlocks(toFetch.toList())
@@ -1,5 +1,5 @@
1
1
  import { CID } from 'multiformats/cid'
2
- import { Commit, def, DataStore, RepoContents } from './types'
2
+ import { def, RepoContents, Commit } from './types'
3
3
  import { ReadableBlockstore } from './storage'
4
4
  import { MST } from './mst'
5
5
  import log from './logger'
@@ -9,14 +9,14 @@ import { MissingBlocksError } from './error'
9
9
 
10
10
  type Params = {
11
11
  storage: ReadableBlockstore
12
- data: DataStore
12
+ data: MST
13
13
  commit: Commit
14
14
  cid: CID
15
15
  }
16
16
 
17
17
  export class ReadableRepo {
18
18
  storage: ReadableBlockstore
19
- data: DataStore
19
+ data: MST
20
20
  commit: Commit
21
21
  cid: CID
22
22
 
@@ -28,13 +28,13 @@ export class ReadableRepo {
28
28
  }
29
29
 
30
30
  static async load(storage: ReadableBlockstore, commitCid: CID) {
31
- const commit = await storage.readObj(commitCid, def.commit)
31
+ const commit = await storage.readObj(commitCid, def.versionedCommit)
32
32
  const data = await MST.load(storage, commit.data)
33
33
  log.info({ did: commit.did }, 'loaded repo for')
34
34
  return new ReadableRepo({
35
35
  storage,
36
36
  data,
37
- commit,
37
+ commit: util.ensureV3Commit(commit),
38
38
  cid: commitCid,
39
39
  })
40
40
  }
package/src/repo.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { CID } from 'multiformats/cid'
2
+ import { TID } from '@atproto/common'
2
3
  import * as crypto from '@atproto/crypto'
3
4
  import {
4
5
  Commit,
6
+ CommitData,
5
7
  def,
6
- DataStore,
7
8
  RecordCreateOp,
8
9
  RecordWriteOp,
9
- CommitData,
10
10
  WriteOpAction,
11
11
  } from './types'
12
12
  import { RepoStorage } from './storage'
@@ -19,7 +19,7 @@ import * as util from './util'
19
19
 
20
20
  type Params = {
21
21
  storage: RepoStorage
22
- data: DataStore
22
+ data: MST
23
23
  commit: Commit
24
24
  cid: CID
25
25
  }
@@ -46,25 +46,29 @@ export class Repo extends ReadableRepo {
46
46
  const dataKey = util.formatDataKey(record.collection, record.rkey)
47
47
  data = await data.add(dataKey, cid)
48
48
  }
49
+ const dataCid = await data.getPointer()
50
+ const diff = await DataDiff.of(data, null)
51
+ newBlocks.addMap(diff.newMstBlocks)
49
52
 
50
- const unstoredData = await data.getUnstoredBlocks()
51
- newBlocks.addMap(unstoredData.blocks)
52
-
53
+ const rev = TID.nextStr()
53
54
  const commit = await util.signCommit(
54
55
  {
55
56
  did,
56
- version: 2,
57
- prev: null,
58
- data: unstoredData.root,
57
+ version: 3,
58
+ rev,
59
+ prev: null, // added for backwards compatibility with v2
60
+ data: dataCid,
59
61
  },
60
62
  keypair,
61
63
  )
62
64
  const commitCid = await newBlocks.add(commit)
63
-
64
65
  return {
65
- commit: commitCid,
66
+ cid: commitCid,
67
+ rev,
68
+ since: null,
66
69
  prev: null,
67
- blocks: newBlocks,
70
+ newBlocks,
71
+ removedCids: diff.removedCids,
68
72
  }
69
73
  }
70
74
 
@@ -73,7 +77,7 @@ export class Repo extends ReadableRepo {
73
77
  commit: CommitData,
74
78
  ): Promise<Repo> {
75
79
  await storage.applyCommit(commit)
76
- return Repo.load(storage, commit.commit)
80
+ return Repo.load(storage, commit.cid)
77
81
  }
78
82
 
79
83
  static async create(
@@ -92,17 +96,17 @@ export class Repo extends ReadableRepo {
92
96
  }
93
97
 
94
98
  static async load(storage: RepoStorage, cid?: CID) {
95
- const commitCid = cid || (await storage.getHead())
99
+ const commitCid = cid || (await storage.getRoot())
96
100
  if (!commitCid) {
97
101
  throw new Error('No cid provided and none in storage')
98
102
  }
99
- const commit = await storage.readObj(commitCid, def.commit)
103
+ const commit = await storage.readObj(commitCid, def.versionedCommit)
100
104
  const data = await MST.load(storage, commit.data)
101
105
  log.info({ did: commit.did }, 'loaded repo for')
102
106
  return new Repo({
103
107
  storage,
104
108
  data,
105
- commit,
109
+ commit: util.ensureV3Commit(commit),
106
110
  cid: commitCid,
107
111
  })
108
112
  }
@@ -112,16 +116,16 @@ export class Repo extends ReadableRepo {
112
116
  keypair: crypto.Keypair,
113
117
  ): Promise<CommitData> {
114
118
  const writes = Array.isArray(toWrite) ? toWrite : [toWrite]
115
- const commitBlocks = new BlockMap()
119
+ const leaves = new BlockMap()
116
120
 
117
121
  let data = this.data
118
122
  for (const write of writes) {
119
123
  if (write.action === WriteOpAction.Create) {
120
- const cid = await commitBlocks.add(write.record)
124
+ const cid = await leaves.add(write.record)
121
125
  const dataKey = write.collection + '/' + write.rkey
122
126
  data = await data.add(dataKey, cid)
123
127
  } else if (write.action === WriteOpAction.Update) {
124
- const cid = await commitBlocks.add(write.record)
128
+ const cid = await leaves.add(write.record)
125
129
  const dataKey = write.collection + '/' + write.rkey
126
130
  data = await data.update(dataKey, cid)
127
131
  } else if (write.action === WriteOpAction.Delete) {
@@ -130,44 +134,50 @@ export class Repo extends ReadableRepo {
130
134
  }
131
135
  }
132
136
 
133
- const unstoredData = await data.getUnstoredBlocks()
134
- commitBlocks.addMap(unstoredData.blocks)
135
-
136
- // ensure we're not missing any blocks that were removed and then readded in this commit
137
+ const dataCid = await data.getPointer()
137
138
  const diff = await DataDiff.of(data, this.data)
138
- const found = commitBlocks.getMany(diff.newCidList())
139
- if (found.missing.length > 0) {
140
- const fromStorage = await this.storage.getBlocks(found.missing)
141
- if (fromStorage.missing.length > 0) {
142
- // this shouldn't ever happen
143
- throw new Error(
144
- 'Could not find block for commit in Datastore or storage',
145
- )
146
- }
147
- commitBlocks.addMap(fromStorage.blocks)
139
+ const newBlocks = diff.newMstBlocks
140
+ const removedCids = diff.removedCids
141
+
142
+ const addedLeaves = leaves.getMany(diff.newLeafCids.toList())
143
+ if (addedLeaves.missing.length > 0) {
144
+ throw new Error(`Missing leaf blocks: ${addedLeaves.missing}`)
148
145
  }
146
+ newBlocks.addMap(addedLeaves.blocks)
149
147
 
148
+ const rev = TID.nextStr(this.commit.rev)
150
149
  const commit = await util.signCommit(
151
150
  {
152
151
  did: this.did,
153
- version: 2,
154
- prev: this.cid,
155
- data: unstoredData.root,
152
+ version: 3,
153
+ rev,
154
+ prev: null, // added for backwards compatibility with v2
155
+ data: dataCid,
156
156
  },
157
157
  keypair,
158
158
  )
159
- const commitCid = await commitBlocks.add(commit)
159
+ const commitCid = await newBlocks.add(commit)
160
+
161
+ // ensure the commit cid actually changed
162
+ if (commitCid.equals(this.cid)) {
163
+ newBlocks.delete(commitCid)
164
+ } else {
165
+ removedCids.add(this.cid)
166
+ }
160
167
 
161
168
  return {
162
- commit: commitCid,
169
+ cid: commitCid,
170
+ rev,
171
+ since: this.commit.rev,
163
172
  prev: this.cid,
164
- blocks: commitBlocks,
173
+ newBlocks,
174
+ removedCids,
165
175
  }
166
176
  }
167
177
 
168
178
  async applyCommit(commitData: CommitData): Promise<Repo> {
169
179
  await this.storage.applyCommit(commitData)
170
- return Repo.load(this.storage, commitData.commit)
180
+ return Repo.load(this.storage, commitData.cid)
171
181
  }
172
182
 
173
183
  async applyWrites(
@@ -1,5 +1,4 @@
1
1
  export * from './readable-blockstore'
2
- export * from './repo-storage'
3
2
  export * from './memory-blockstore'
4
3
  export * from './sync-storage'
5
4
  export * from './types'