@fireproof/core 0.0.5 → 0.0.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Realtime database for IPFS",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -37,12 +37,13 @@
37
37
  "cli-color": "^2.0.3",
38
38
  "idb": "^7.1.1",
39
39
  "multiformats": "^11.0.1",
40
- "prolly-trees": "^0.2.2",
40
+ "prolly-trees": "^0.2.5",
41
41
  "sade": "^1.8.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "c8": "^7.12.0",
45
45
  "fake-indexeddb": "^4.0.1",
46
+ "flexsearch": "^0.7.31",
46
47
  "mocha": "^10.2.0",
47
48
  "nanoid": "^4.0.0",
48
49
  "standard": "^17.0.0"
package/src/clock.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Block, encode, decode } from 'multiformats/block'
2
2
  import { sha256 } from 'multiformats/hashes/sha2'
3
3
  import * as cbor from '@ipld/dag-cbor'
4
+ import { CIDCounter } from 'prolly-trees/utils'
4
5
 
5
6
  /**
6
7
  * @template T
@@ -29,10 +30,10 @@ import * as cbor from '@ipld/dag-cbor'
29
30
  export async function advance (blocks, head, event) {
30
31
  /** @type {EventFetcher<T>} */
31
32
  const events = new EventFetcher(blocks)
32
- const headmap = new Map(head.map(cid => [cid.toString(), cid]))
33
+ const headmap = new Map(head.map((cid) => [cid.toString(), cid]))
33
34
 
34
35
  // Check if the headmap already includes the event, return head if it does
35
- if (headmap.has(event.toString())) return head
36
+ if (headmap.has(event.toString())) return { head, cids: events.cids }
36
37
 
37
38
  // Does event contain the clock?
38
39
  let changed = false
@@ -46,18 +47,18 @@ export async function advance (blocks, head, event) {
46
47
 
47
48
  // If the headmap has been changed, return the new headmap values
48
49
  if (changed) {
49
- return [...headmap.values()]
50
+ return { head: [...headmap.values()], cids: events.cids }
50
51
  }
51
52
 
52
53
  // Does clock contain the event?
53
54
  for (const p of head) {
54
55
  if (await contains(events, p, event)) {
55
- return head
56
+ return { head, cids: events.cids }
56
57
  }
57
58
  }
58
59
 
59
60
  // Return the head concatenated with the new event if it passes both checks
60
- return head.concat(event)
61
+ return { head: head.concat(event), cids: events.cids }
61
62
  }
62
63
 
63
64
  /**
@@ -92,6 +93,7 @@ export class EventFetcher {
92
93
  constructor (blocks) {
93
94
  /** @private */
94
95
  this._blocks = blocks
96
+ this._cids = new CIDCounter()
95
97
  }
96
98
 
97
99
  /**
@@ -100,9 +102,15 @@ export class EventFetcher {
100
102
  */
101
103
  async get (link) {
102
104
  const block = await this._blocks.get(link)
105
+ this._cids.add({ address: link })
103
106
  if (!block) throw new Error(`missing block: ${link}`)
104
107
  return decodeEventBlock(block.bytes)
105
108
  }
109
+
110
+ async all () {
111
+ await Promise.all([...this._cids])
112
+ return this._cids
113
+ }
106
114
  }
107
115
 
108
116
  /**
@@ -145,7 +153,7 @@ async function contains (events, a, b) {
145
153
  if (link.toString() === b.toString()) return true
146
154
  // if any of b's parents are this link, then b cannot exist in any of the
147
155
  // tree below, since that would create a cycle.
148
- if (bevent.parents.some(p => link.toString() === p.toString())) continue
156
+ if (bevent.parents.some((p) => link.toString() === p.toString())) continue
149
157
  const { value: event } = await events.get(link)
150
158
  links.push(...event.parents)
151
159
  }
@@ -160,11 +168,11 @@ async function contains (events, a, b) {
160
168
  * @param {(b: EventBlockView<T>) => string} [options.renderNodeLabel]
161
169
  */
162
170
  export async function * vis (blocks, head, options = {}) {
163
- const renderNodeLabel = options.renderNodeLabel ?? (b => (b.value.data.value))
171
+ const renderNodeLabel = options.renderNodeLabel ?? ((b) => b.value.data.value)
164
172
  const events = new EventFetcher(blocks)
165
173
  yield 'digraph clock {'
166
174
  yield ' node [shape=point fontname="Courier"]; head;'
167
- const hevents = await Promise.all(head.map(link => events.get(link)))
175
+ const hevents = await Promise.all(head.map((link) => events.get(link)))
168
176
  const links = []
169
177
  const nodes = new Set()
170
178
  for (const e of hevents) {
@@ -192,46 +200,22 @@ export async function * vis (blocks, head, options = {}) {
192
200
  }
193
201
 
194
202
  export async function findEventsToSync (blocks, head) {
195
- const toSync = await findUnknownSortedEvents(blocks, head, await findCommonAncestorWithSortedEvents(blocks, head))
196
- return toSync
197
- }
198
-
199
- export async function findUnknownSortedEvents (blocks, children, { ancestor, sorted }) {
200
203
  const events = new EventFetcher(blocks)
201
- // const childrenCids = children.map(c => c.toString())
202
- // const lowerEvent = sorted.find(({ cid }) => childrenCids.includes(cid.toString()))
203
- // const knownAncestor = await findCommonAncestor(events, [lowerEvent.cid]) // should this be [lowerEvent.cid] ?
204
- // const knownAncestor = await findCommonAncestor(events, [...children]) // should this be [lowerEvent.cid] ?
205
- // console.x('already knownAncestor', knownAncestor.toString() === ancestor.toString(),
206
- // (await (await decodeEventBlock((await blocks.get(knownAncestor)).bytes)).value.data?.value), knownAncestor
207
- // )
208
-
209
- const matchHead = [ancestor]
210
- const unknownSorted = await asyncFilter(sorted, async (uks) => {
211
- for (const ev of matchHead) {
212
- const isIn = await contains(events, ev, uks.cid)
213
- if (isIn) return false
214
- }
215
- return true
216
- })
217
- // console.x('unknownSorted contains', unknownSorted.length, sorted.length)
218
- return unknownSorted
204
+ const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
205
+ const toSync = await asyncFilter(sorted, async (uks) => !(await contains(events, ancestor, uks.cid)))
206
+ return { cids: events.cids, events: toSync }
219
207
  }
220
208
 
221
- const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate))
222
- .then((results) => arr.filter((_v, index) => results[index]))
223
-
224
- export async function findCommonAncestorWithSortedEvents (blocks, children) {
225
- const events = new EventFetcher(blocks)
209
+ const asyncFilter = async (arr, predicate) =>
210
+ Promise.all(arr.map(predicate)).then((results) => arr.filter((_v, index) => results[index]))
226
211
 
212
+ export async function findCommonAncestorWithSortedEvents (events, children) {
227
213
  const ancestor = await findCommonAncestor(events, children)
228
214
  if (!ancestor) {
229
215
  throw new Error('failed to find common ancestor event')
230
216
  }
231
217
  // Sort the events by their sequence number
232
218
  const sorted = await findSortedEvents(events, children, ancestor)
233
- // console.x('ancstor', ancestor, (await decodeEventBlock((await blocks.get(ancestor)).bytes)).value.data?.value)
234
- // sorted.forEach(({ cid, value }) => console.x('xsorted', cid, value.data.value))
235
219
  return { ancestor, sorted }
236
220
  }
237
221
 
@@ -266,9 +250,7 @@ async function findCommonAncestor (events, children) {
266
250
  async function findAncestorCandidate (events, root) {
267
251
  const { value: event } = await events.get(root)
268
252
  if (!event.parents.length) return root
269
- return event.parents.length === 1
270
- ? event.parents[0]
271
- : findCommonAncestor(events, event.parents)
253
+ return event.parents.length === 1 ? event.parents[0] : findCommonAncestor(events, event.parents)
272
254
  }
273
255
 
274
256
  /**
@@ -327,9 +309,7 @@ async function findSortedEvents (events, head, tail) {
327
309
  // sort by weight, and by CID within weight
328
310
  const sorted = Array.from(buckets)
329
311
  .sort((a, b) => b[0] - a[0])
330
- .flatMap(([, es]) =>
331
- es.sort((a, b) => (String(a.cid) < String(b.cid) ? -1 : 1))
332
- )
312
+ .flatMap(([, es]) => es.sort((a, b) => (String(a.cid) < String(b.cid) ? -1 : 1)))
333
313
  // console.log('sorted', sorted.map(s => s.value.data.value))
334
314
  return sorted
335
315
  }
@@ -345,8 +325,6 @@ async function findEvents (events, start, end, depth = 0) {
345
325
  const acc = [{ event, depth }]
346
326
  const { parents } = event.value
347
327
  if (parents.length === 1 && String(parents[0]) === String(end)) return acc
348
- const rest = await Promise.all(
349
- parents.map((p) => findEvents(events, p, end, depth + 1))
350
- )
328
+ const rest = await Promise.all(parents.map((p) => findEvents(events, p, end, depth + 1)))
351
329
  return acc.concat(...rest)
352
330
  }
package/src/db-index.js CHANGED
@@ -2,11 +2,15 @@ import { create, load } from 'prolly-trees/db-index'
2
2
  import { sha256 as hasher } from 'multiformats/hashes/sha2'
3
3
  import { nocache as cache } from 'prolly-trees/cache'
4
4
  import { bf, simpleCompare } from 'prolly-trees/utils'
5
+ import { makeGetBlock } from './prolly.js'
6
+ import { cidsToProof } from './fireproof.js'
5
7
  import * as codec from '@ipld/dag-cbor'
6
- import { create as createBlock } from 'multiformats/block'
8
+ // import { create as createBlock } from 'multiformats/block'
7
9
  import { doTransaction } from './blockstore.js'
8
10
  import charwise from 'charwise'
9
11
 
12
+ const ALWAYS_REBUILD = true // todo: remove this
13
+
10
14
  const arrayCompare = (a, b) => {
11
15
  if (Array.isArray(a) && Array.isArray(b)) {
12
16
  const len = Math.min(a.length, b.length)
@@ -24,12 +28,6 @@ const arrayCompare = (a, b) => {
24
28
 
25
29
  const opts = { cache, chunker: bf(3), codec, hasher, compare: arrayCompare }
26
30
 
27
- const ALWAYS_REBUILD = false // todo: remove this
28
-
29
- const makeGetBlock = (blocks) => async (address) => {
30
- const { cid, bytes } = await blocks.get(address)
31
- return createBlock({ cid, bytes, hasher, codec })
32
- }
33
31
  const makeDoc = ({ key, value }) => ({ _id: key, ...value })
34
32
 
35
33
  /**
@@ -74,11 +72,11 @@ const indexEntriesForChanges = (changes, mapFun) => {
74
72
  }
75
73
 
76
74
  const indexEntriesForOldChanges = async (blocks, byIDindexRoot, ids, mapFun) => {
77
- const getBlock = makeGetBlock(blocks)
75
+ const { getBlock } = makeGetBlock(blocks)
78
76
  const byIDindex = await load({ cid: byIDindexRoot.cid, get: getBlock, ...opts })
79
77
 
80
78
  const result = await byIDindex.getMany(ids)
81
- return result.result
79
+ return result
82
80
  }
83
81
 
84
82
  /**
@@ -103,7 +101,10 @@ export default class DbIndex {
103
101
  * @type {Function}
104
102
  */
105
103
  this.mapFun = mapFun
106
- this.indexRoot = null
104
+
105
+ this.dbIndexRoot = null
106
+ this.dbIndex = null
107
+
107
108
  this.byIDindexRoot = null
108
109
  this.dbHead = null
109
110
  }
@@ -118,22 +119,21 @@ export default class DbIndex {
118
119
  /**
119
120
  * Query object can have {range}
120
121
  * @param {DbQuery} query - the query range to use
121
- * @param {CID} [root] - an optional root to query a snapshot
122
122
  * @returns {Promise<{rows: Array<{id: string, key: string, value: any}>}>}
123
123
  * @memberof DbIndex
124
124
  * @instance
125
125
  */
126
- async query (query, root = null) {
127
- if (!root) {
128
- // pass a root to query a snapshot
129
- await doTransaction('#updateIndex', this.database.blocks, async (blocks) => {
130
- await this.#updateIndex(blocks)
131
- })
132
- }
133
- const response = await doIndexQuery(this.database.blocks, root || this.indexRoot, query)
126
+ async query (query) {
127
+ // if (!root) {
128
+ // pass a root to query a snapshot
129
+ await doTransaction('#updateIndex', this.database.blocks, async (blocks) => {
130
+ await this.#updateIndex(blocks)
131
+ })
132
+ // }
133
+ const response = await doIndexQuery(this.database.blocks, this.dbIndexRoot, this.dbIndex, query)
134
134
  return {
135
- // TODO fix this naming upstream in prolly/db-DbIndex
136
- // todo maybe this is a hint about why deletes arent working?
135
+ proof: { index: await cidsToProof(response.cids) },
136
+ // TODO fix this naming upstream in prolly/db-DbIndex?
137
137
  rows: response.result.map(({ id, key, row }) => ({ id: key, key: charwise.decode(id), value: row }))
138
138
  }
139
139
  }
@@ -147,27 +147,45 @@ export default class DbIndex {
147
147
  // todo remove this hack
148
148
  if (ALWAYS_REBUILD) {
149
149
  this.dbHead = null // hack
150
- this.indexRoot = null // hack
150
+ this.dbIndex = null // hack
151
+ this.dbIndexRoot = null
151
152
  }
152
153
  const result = await this.database.changesSince(this.dbHead) // {key, value, del}
153
154
  if (this.dbHead) {
154
- const oldIndexEntries = (
155
- await indexEntriesForOldChanges(
156
- blocks,
157
- this.byIDindexRoot,
158
- result.rows.map(({ key }) => key),
159
- this.mapFun
160
- )
161
- ).map((key) => ({ key, del: true })) // should be this
162
- this.indexRoot = await bulkIndex(blocks, this.indexRoot, oldIndexEntries, opts)
155
+ const oldChangeEntries = await indexEntriesForOldChanges(
156
+ blocks,
157
+ this.byIDindexRoot,
158
+ result.rows.map(({ key }) => key),
159
+ this.mapFun
160
+ )
161
+ const oldIndexEntries = oldChangeEntries.result.map((key) => ({ key, del: true }))
162
+ const removalResult = await bulkIndex(blocks, this.dbIndexRoot, this.dbIndex, oldIndexEntries, opts)
163
+ this.dbIndexRoot = removalResult.root
164
+ this.dbIndex = removalResult.dbIndex
165
+
163
166
  const removeByIdIndexEntries = oldIndexEntries.map(({ key }) => ({ key: key[1], del: true }))
164
- this.byIDindexRoot = await bulkIndex(blocks, this.byIDindexRoot, removeByIdIndexEntries, opts)
167
+ const purgedRemovalResults = await bulkIndex(
168
+ blocks,
169
+ this.byIDindexRoot,
170
+ this.byIDIndex,
171
+ removeByIdIndexEntries,
172
+ opts
173
+ )
174
+ this.byIDindexRoot = purgedRemovalResults.root
175
+ this.byIDIndex = purgedRemovalResults.dbIndex
165
176
  }
166
177
  const indexEntries = indexEntriesForChanges(result.rows, this.mapFun)
167
178
  const byIdIndexEntries = indexEntries.map(({ key }) => ({ key: key[1], value: key }))
168
- this.byIDindexRoot = await bulkIndex(blocks, this.byIDindexRoot, byIdIndexEntries, opts)
179
+ const addFutureRemovalsResult = await bulkIndex(blocks, this.byIDindexRoot, this.byIDIndex, byIdIndexEntries, opts)
180
+ this.byIDindexRoot = addFutureRemovalsResult.root
181
+ this.byIDIndex = addFutureRemovalsResult.dbIndex
182
+
169
183
  // console.log('indexEntries', indexEntries)
170
- this.indexRoot = await bulkIndex(blocks, this.indexRoot, indexEntries, opts)
184
+
185
+ const updateIndexResult = await bulkIndex(blocks, this.dbIndexRoot, this.dbIndex, indexEntries, opts)
186
+ this.dbIndexRoot = updateIndexResult.root
187
+ this.dbIndex = updateIndexResult.dbIndex
188
+
171
189
  this.dbHead = result.clock
172
190
  }
173
191
 
@@ -184,37 +202,44 @@ export default class DbIndex {
184
202
  * @param {DbIndexEntry[]} indexEntries
185
203
  * @private
186
204
  */
187
- async function bulkIndex (blocks, inRoot, indexEntries) {
188
- if (!indexEntries.length) return inRoot
205
+ async function bulkIndex (blocks, inRoot, inDBindex, indexEntries) {
206
+ if (!indexEntries.length) return { dbIndex: inDBindex, root: inRoot }
189
207
  const putBlock = blocks.put.bind(blocks)
190
- const getBlock = makeGetBlock(blocks)
191
- if (!inRoot) {
208
+ const { getBlock } = makeGetBlock(blocks)
209
+ let returnRootBlock
210
+ let returnNode
211
+ if (!inDBindex) {
192
212
  for await (const node of await create({ get: getBlock, list: indexEntries, ...opts })) {
193
213
  const block = await node.block
194
214
  await putBlock(block.cid, block.bytes)
195
- inRoot = block
215
+ returnRootBlock = block
216
+ returnNode = node
196
217
  }
197
- return inRoot
198
218
  } else {
199
- const DbIndex = await load({ cid: inRoot.cid, get: getBlock, ...opts })
200
- const { root, blocks } = await DbIndex.bulk(indexEntries)
219
+ // const dbIndex = await load({ cid: inRoot.cid, get: getBlock, ...opts }) // todo load from root on refresh
220
+ const { root, blocks } = await inDBindex.bulk(indexEntries)
221
+ returnRootBlock = await root.block
222
+ returnNode = root
201
223
  for await (const block of blocks) {
202
224
  await putBlock(block.cid, block.bytes)
203
225
  }
204
- return await root.block // if we hold the root we won't have to load every time
226
+ await putBlock(returnRootBlock.cid, returnRootBlock.bytes)
205
227
  }
228
+ return { dbIndex: returnNode, root: returnRootBlock }
206
229
  }
207
230
 
208
- async function doIndexQuery (blocks, root, query) {
209
- const cid = root && root.cid
210
- if (!cid) return { result: [] }
211
- const getBlock = makeGetBlock(blocks)
212
- const DbIndex = await load({ cid, get: getBlock, ...opts })
231
+ async function doIndexQuery (blocks, dbIndexRoot, dbIndex, query) {
232
+ if (!dbIndex) {
233
+ const cid = dbIndexRoot && dbIndexRoot.cid
234
+ if (!cid) return { result: [] }
235
+ const { getBlock } = makeGetBlock(blocks)
236
+ dbIndex = await load({ cid, get: getBlock, ...opts })
237
+ }
213
238
  if (query.range) {
214
239
  const encodedRange = query.range.map((key) => charwise.encode(key))
215
- return DbIndex.range(...encodedRange)
240
+ return dbIndex.range(...encodedRange)
216
241
  } else if (query.key) {
217
242
  const encodedKey = charwise.encode(query.key)
218
- return DbIndex.get(encodedKey)
243
+ return dbIndex.get(encodedKey)
219
244
  }
220
245
  }
package/src/fireproof.js CHANGED
@@ -89,11 +89,11 @@ export default class Fireproof {
89
89
  */
90
90
  async changesSince (event) {
91
91
  // console.log('changesSince', this.instanceId, event, this.clock)
92
- let rows
92
+ let rows, dataCIDs, clockCIDs
93
93
  if (event) {
94
94
  const resp = await eventsSince(this.blocks, this.clock, event)
95
95
  const docsMap = new Map()
96
- for (const { key, type, value } of resp) {
96
+ for (const { key, type, value } of resp.result) {
97
97
  if (type === 'del') {
98
98
  docsMap.set(key, { key, del: true })
99
99
  } else {
@@ -101,12 +101,15 @@ export default class Fireproof {
101
101
  }
102
102
  }
103
103
  rows = Array.from(docsMap.values())
104
+ clockCIDs = resp.cids
104
105
  // console.log('change rows', this.instanceId, rows)
105
106
  } else {
106
- rows = (await getAll(this.blocks, this.clock)).map(({ key, value }) => ({ key, value }))
107
+ const allResp = await getAll(this.blocks, this.clock)
108
+ rows = allResp.result.map(({ key, value }) => ({ key, value }))
109
+ dataCIDs = allResp.cids
107
110
  // console.log('dbdoc rows', this.instanceId, rows)
108
111
  }
109
- return { rows, clock: this.clock }
112
+ return { rows, clock: this.clock, proof: { data: await cidsToProof(dataCIDs), clock: await cidsToProof(clockCIDs) } }
110
113
  }
111
114
 
112
115
  /**
@@ -158,7 +161,7 @@ export default class Fireproof {
158
161
  * @memberof Fireproof
159
162
  * @instance
160
163
  */
161
- async put ({ _id, ...doc }) {
164
+ async put ({ _id, _proof, ...doc }) {
162
165
  const id = _id || 'f' + Math.random().toString(36).slice(2)
163
166
  await this.#runValidation({ _id: id, ...doc })
164
167
  return await this.#putToProllyTree({ key: id, value: doc }, doc._clock)
@@ -197,7 +200,7 @@ export default class Fireproof {
197
200
  // we need to check and see what version of the document exists at the clock specified
198
201
  // if it is the same as the one we are trying to put, then we can proceed
199
202
  const resp = await eventsSince(this.blocks, this.clock, event.value._clock)
200
- const missedChange = resp.find(({ key }) => key === event.key)
203
+ const missedChange = resp.result.find(({ key }) => key === event.key)
201
204
  if (missedChange) {
202
205
  throw new Error('MVCC conflict, document is changed, please reload the document and try again.')
203
206
  }
@@ -212,9 +215,9 @@ export default class Fireproof {
212
215
  throw new Error('failed to put at storage layer')
213
216
  }
214
217
  this.clock = result.head // do we want to do this as a finally block
215
- result.id = event.key
216
218
  await this.#notifyListeners([event])
217
- return { id: result.id, clock: this.clock }
219
+ return { id: event.key, clock: this.clock, proof: { data: await cidsToProof(result.cids), clock: await cidsToProof(result.clockCIDs) } }
220
+ // todo should include additions (or split clock)
218
221
  }
219
222
 
220
223
  // /**
@@ -251,21 +254,23 @@ export default class Fireproof {
251
254
  * @instance
252
255
  */
253
256
  async get (key, opts = {}) {
254
- let got
255
- if (opts.clock) {
256
- got = await get(this.blocks, opts.clock, key)
257
- } else {
258
- got = await get(this.blocks, this.clock, key)
259
- }
257
+ const clock = opts.clock || this.clock
258
+ const resp = await get(this.blocks, clock, key)
259
+
260
260
  // this tombstone is temporary until we can get the prolly tree to delete
261
- if (got === null) {
261
+ if (!resp || resp.result === null) {
262
262
  throw new Error('Not found')
263
263
  }
264
+ const doc = resp.result
264
265
  if (opts.mvcc === true) {
265
- got._clock = this.clock
266
+ doc._clock = this.clock
266
267
  }
267
- got._id = key
268
- return got
268
+ doc._proof = {
269
+ data: await cidsToProof(resp.cids),
270
+ clock: this.clock
271
+ }
272
+ doc._id = key
273
+ return doc
269
274
  }
270
275
 
271
276
  setCarUploader (carUploaderFn) {
@@ -279,3 +284,9 @@ export default class Fireproof {
279
284
  this.blocks.valet.remoteBlockFunction = remoteBlockReaderFn
280
285
  }
281
286
  }
287
+
288
+ export async function cidsToProof (cids) {
289
+ if (!cids || !cids.all) return []
290
+ const all = await cids.all()
291
+ return [...all].map((cid) => cid.toString())
292
+ }
package/src/prolly.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  EventFetcher,
4
4
  EventBlock,
5
5
  findCommonAncestorWithSortedEvents,
6
- findUnknownSortedEvents
6
+ findEventsToSync
7
7
  } from './clock.js'
8
8
  import { create, load } from 'prolly-trees/map'
9
9
  import * as codec from '@ipld/dag-cbor'
@@ -12,7 +12,7 @@ import { MemoryBlockstore, MultiBlockFetcher } from './block.js'
12
12
  import { doTransaction } from './blockstore.js'
13
13
 
14
14
  import { nocache as cache } from 'prolly-trees/cache'
15
- import { bf, simpleCompare as compare } from 'prolly-trees/utils'
15
+ import { CIDCounter, bf, simpleCompare as compare } from 'prolly-trees/utils'
16
16
  import { create as createBlock } from 'multiformats/block'
17
17
  const opts = { cache, chunker: bf(3), codec, hasher, compare }
18
18
 
@@ -22,9 +22,18 @@ const withLog = async (label, fn) => {
22
22
  return resp
23
23
  }
24
24
 
25
- const makeGetBlock = (blocks) => async (address) => {
26
- const { cid, bytes } = await withLog(address, () => blocks.get(address))
27
- return createBlock({ cid, bytes, hasher, codec })
25
+ // todo should also return a CIDCounter
26
+ export const makeGetBlock = (blocks) => {
27
+ // const cids = new CIDCounter() // todo this could be used for proofs of mutations
28
+ const getBlockFn = async (address) => {
29
+ const { cid, bytes } = await withLog(address, () => blocks.get(address))
30
+ // cids.add({ address: cid })
31
+ return createBlock({ cid, bytes, hasher, codec })
32
+ }
33
+ return {
34
+ // cids,
35
+ getBlock: getBlockFn
36
+ }
28
37
  }
29
38
 
30
39
  /**
@@ -57,6 +66,7 @@ async function createAndSaveNewEvent (
57
66
  additions,
58
67
  removals = []
59
68
  ) {
69
+ let cids
60
70
  const data = {
61
71
  type: 'put',
62
72
  root: {
@@ -77,13 +87,14 @@ async function createAndSaveNewEvent (
77
87
 
78
88
  const event = await EventBlock.create(data, head)
79
89
  bigPut(event)
80
- head = await advance(inBlocks, head, event.cid)
90
+ ;({ head, cids } = await advance(inBlocks, head, event.cid))
81
91
 
82
92
  return {
83
93
  root,
84
94
  additions,
85
95
  removals,
86
96
  head,
97
+ clockCIDs: cids,
87
98
  event
88
99
  }
89
100
  }
@@ -91,7 +102,7 @@ async function createAndSaveNewEvent (
91
102
  const makeGetAndPutBlock = (inBlocks) => {
92
103
  const mblocks = new MemoryBlockstore()
93
104
  const blocks = new MultiBlockFetcher(mblocks, inBlocks)
94
- const getBlock = makeGetBlock(blocks)
105
+ const { getBlock, cids } = makeGetBlock(blocks)
95
106
  const put = inBlocks.put.bind(inBlocks)
96
107
  const bigPut = async (block, additions) => {
97
108
  // console.log('bigPut', block.cid.toString())
@@ -102,7 +113,7 @@ const makeGetAndPutBlock = (inBlocks) => {
102
113
  additions.set(cid.toString(), block)
103
114
  }
104
115
  }
105
- return { getBlock, bigPut, mblocks, blocks }
116
+ return { getBlock, bigPut, mblocks, blocks, cids }
106
117
  }
107
118
 
108
119
  const bulkFromEvents = (sorted) =>
@@ -166,7 +177,7 @@ export async function put (inBlocks, head, event, options) {
166
177
  for (const nb of newBlocks) {
167
178
  bigPut(nb, additions)
168
179
  }
169
-
180
+ // additions are new blocks
170
181
  return createAndSaveNewEvent(
171
182
  inBlocks,
172
183
  mblocks,
@@ -175,7 +186,7 @@ export async function put (inBlocks, head, event, options) {
175
186
  prollyRootBlock,
176
187
  event,
177
188
  head,
178
- Array.from(additions.values()) /*, Array.from(removals.values()) */
189
+ Array.from(additions.values()) /*, todo? Array.from(removals.values()) */
179
190
  )
180
191
  }
181
192
 
@@ -207,8 +218,7 @@ export async function root (inBlocks, head) {
207
218
  }
208
219
  bigPut(prollyRootBlock)
209
220
  })
210
-
211
- return newProllyRootNode // .block).cid // todo return live object not cid
221
+ return { cids: events.cids, node: newProllyRootNode }
212
222
  }
213
223
 
214
224
  /**
@@ -223,12 +233,8 @@ export async function eventsSince (blocks, head, since) {
223
233
  throw new Error('no head')
224
234
  }
225
235
  const sinceHead = [...since, ...head]
226
- const unknownSorted3 = await findUnknownSortedEvents(
227
- blocks,
228
- sinceHead,
229
- await findCommonAncestorWithSortedEvents(blocks, sinceHead)
230
- )
231
- return unknownSorted3.map(({ value: { data } }) => data)
236
+ const { cids, events: unknownSorted3 } = await findEventsToSync(blocks, sinceHead)
237
+ return { clockCIDs: cids, result: unknownSorted3.map(({ value: { data } }) => data) }
232
238
  }
233
239
 
234
240
  /**
@@ -243,11 +249,11 @@ export async function getAll (blocks, head) {
243
249
  // todo use the root node left around from put, etc
244
250
  // move load to a central place
245
251
  if (!head.length) {
246
- return []
252
+ return { clockCIDs: new CIDCounter(), cids: new CIDCounter(), result: [] }
247
253
  }
248
- const prollyRootNode = await root(blocks, head)
249
- const { result } = await prollyRootNode.getAllEntries()
250
- return result.map(({ key, value }) => ({ key, value }))
254
+ const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head)
255
+ const { result, cids } = await prollyRootNode.getAllEntries() // todo params
256
+ return { clockCIDs, cids, result: result.map(({ key, value }) => ({ key, value })) }
251
257
  }
252
258
 
253
259
  /**
@@ -258,9 +264,9 @@ export async function getAll (blocks, head) {
258
264
  export async function get (blocks, head, key) {
259
265
  // instead pass root from db? and always update on change
260
266
  if (!head.length) {
261
- return null
267
+ return { cids: new CIDCounter(), result: null }
262
268
  }
263
- const prollyRootNode = await root(blocks, head)
264
- const { result } = await prollyRootNode.get(key)
265
- return result
269
+ const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head)
270
+ const { result, cids } = await prollyRootNode.get(key)
271
+ return { result, cids, clockCIDs }
266
272
  }