@fireproof/core 0.1.1 → 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.
package/src/fireproof.js CHANGED
@@ -1,4 +1,5 @@
1
- import { vis, put, get, getAll, eventsSince } from './prolly.js'
1
+ import { randomBytes } from 'crypto'
2
+ import { visMerkleClock, visMerkleTree, vis, put, get, getAll, eventsSince } from './prolly.js'
2
3
  import TransactionBlockstore, { doTransaction } from './blockstore.js'
3
4
  import charwise from 'charwise'
4
5
 
@@ -28,7 +29,10 @@ export default class Fireproof {
28
29
  * @returns {Fireproof} - a new Fireproof instance
29
30
  */
30
31
  static storage = (name = 'global') => {
31
- return new Fireproof(new TransactionBlockstore(name), [], { name })
32
+ const instanceKey = randomBytes(32).toString('hex') // pass null to disable encryption
33
+ // pick a random key from const validatedKeys
34
+ // const instanceKey = validatedKeys[Math.floor(Math.random() * validatedKeys.length)]
35
+ return new Fireproof(new TransactionBlockstore(name, instanceKey), [], { name })
32
36
  }
33
37
 
34
38
  constructor (blocks, clock, config, authCtx = {}) {
@@ -50,15 +54,22 @@ export default class Fireproof {
50
54
  toJSON () {
51
55
  // todo this also needs to return the index roots...
52
56
  return {
53
- clock: this.clock.map(cid => cid.toString()),
57
+ clock: this.clockToJSON(),
54
58
  name: this.name,
59
+ key: this.blocks.valet.getKeyMaterial(),
55
60
  indexes: [...this.indexes.values()].map(index => index.toJSON())
56
61
  }
57
62
  }
58
63
 
59
- hydrate ({ clock, name }) {
64
+ clockToJSON () {
65
+ return this.clock.map(cid => cid.toString())
66
+ }
67
+
68
+ hydrate ({ clock, name, key }) {
60
69
  this.name = name
61
70
  this.clock = clock
71
+ this.blocks.valet.setKeyMaterial(key)
72
+ this.indexBlocks = null
62
73
  }
63
74
 
64
75
  /**
@@ -71,7 +82,12 @@ export default class Fireproof {
71
82
  * @instance
72
83
  */
73
84
  async notifyReset () {
74
- await this.#notifyListeners({ reset: true, clock: this.clock })
85
+ await this.#notifyListeners({ _reset: true, _clock: this.clockToJSON() })
86
+ }
87
+
88
+ // used be indexes etc to notify database listeners of new availability
89
+ async notifyExternal (source = 'unknown') {
90
+ await this.#notifyListeners({ _external: source, _clock: this.clockToJSON() })
75
91
  }
76
92
 
77
93
  /**
@@ -85,6 +101,7 @@ export default class Fireproof {
85
101
  async changesSince (event) {
86
102
  // console.log('changesSince', this.instanceId, event, this.clock)
87
103
  let rows, dataCIDs, clockCIDs
104
+ // if (!event) event = []
88
105
  if (event) {
89
106
  const resp = await eventsSince(this.blocks, this.clock, event)
90
107
  const docsMap = new Map()
@@ -106,29 +123,18 @@ export default class Fireproof {
106
123
  }
107
124
  return {
108
125
  rows,
109
- clock: this.clock,
126
+ clock: this.clockToJSON(),
110
127
  proof: { data: await cidsToProof(dataCIDs), clock: await cidsToProof(clockCIDs) }
111
128
  }
112
129
  }
113
130
 
114
- /**
115
- * Registers a Listener to be called when the Fireproof instance's clock is updated.
116
- * Recieves live changes from the database after they are committed.
117
- * @param {Function} listener - The listener to be called when the clock is updated.
118
- * @returns {Function} - A function that can be called to unregister the listener.
119
- * @memberof Fireproof
120
- */
121
- registerListener (listener) {
122
- this.#listeners.add(listener)
123
- return () => {
124
- this.#listeners.delete(listener)
125
- }
126
- }
127
-
128
- async #notifyListeners (changes) {
129
- // await sleep(10)
130
- for (const listener of this.#listeners) {
131
- await listener(changes)
131
+ async allDocuments () {
132
+ const allResp = await getAll(this.blocks, this.clock)
133
+ const rows = allResp.result.map(({ key, value }) => (decodeEvent({ key, value }))).map(({ key, value }) => ({ key, value: { _id: key, ...value } }))
134
+ return {
135
+ rows,
136
+ clock: this.clockToJSON(),
137
+ proof: await cidsToProof(allResp.cids)
132
138
  }
133
139
  }
134
140
 
@@ -150,6 +156,35 @@ export default class Fireproof {
150
156
  }
151
157
  }
152
158
 
159
+ /**
160
+ * Retrieves the document with the specified ID from the database
161
+ *
162
+ * @param {string} key - the ID of the document to retrieve
163
+ * @param {Object} [opts] - options
164
+ * @returns {Object<{_id: string, ...doc: Object}>} - the document with the specified ID
165
+ * @memberof Fireproof
166
+ * @instance
167
+ */
168
+ async get (key, opts = {}) {
169
+ const clock = opts.clock || this.clock
170
+ const resp = await get(this.blocks, clock, charwise.encode(key))
171
+
172
+ // this tombstone is temporary until we can get the prolly tree to delete
173
+ if (!resp || resp.result === null) {
174
+ throw new Error('Not found')
175
+ }
176
+ const doc = resp.result
177
+ if (opts.mvcc === true) {
178
+ doc._clock = this.clockToJSON()
179
+ }
180
+ doc._proof = {
181
+ data: await cidsToProof(resp.cids),
182
+ clock: this.clockToJSON()
183
+ }
184
+ doc._id = key
185
+ return doc
186
+ }
187
+
153
188
  /**
154
189
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
155
190
  *
@@ -183,9 +218,9 @@ export default class Fireproof {
183
218
  id = docOrId
184
219
  }
185
220
  await this.#runValidation({ _id: id, _deleted: true })
186
- // return await this.#putToProllyTree({ key: id, del: true }) // not working at prolly tree layer?
221
+ return await this.#putToProllyTree({ key: id, del: true }, clock) // not working at prolly tree layer?
187
222
  // this tombstone is temporary until we can get the prolly tree to delete
188
- return await this.#putToProllyTree({ key: id, value: null }, clock)
223
+ // return await this.#putToProllyTree({ key: id, value: null }, clock)
189
224
  }
190
225
 
191
226
  /**
@@ -196,7 +231,7 @@ export default class Fireproof {
196
231
  */
197
232
  async #putToProllyTree (decodedEvent, clock = null) {
198
233
  const event = encodeEvent(decodedEvent)
199
- if (clock && JSON.stringify(clock) !== JSON.stringify(this.clock)) {
234
+ if (clock && JSON.stringify(clock) !== JSON.stringify(this.clockToJSON())) {
200
235
  // we need to check and see what version of the document exists at the clock specified
201
236
  // if it is the same as the one we are trying to put, then we can proceed
202
237
  const resp = await eventsSince(this.blocks, this.clock, event.value._clock)
@@ -219,7 +254,7 @@ export default class Fireproof {
219
254
  await this.#notifyListeners([decodedEvent]) // this type is odd
220
255
  return {
221
256
  id: decodedEvent.key,
222
- clock: this.clock,
257
+ clock: this.clockToJSON(),
223
258
  proof: { data: await cidsToProof(result.cids), clock: await cidsToProof(result.clockCIDs) }
224
259
  }
225
260
  // todo should include additions (or split clock)
@@ -235,51 +270,37 @@ export default class Fireproof {
235
270
  // return this.clock
236
271
  // }
237
272
 
238
- /**
239
- * Displays a visualization of the current clock in the console
240
- */
241
- // async visClock () {
242
- // const shortLink = (l) => `${String(l).slice(0, 4)}..${String(l).slice(-4)}`
243
- // const renderNodeLabel = (event) => {
244
- // return event.value.data.type === 'put'
245
- // ? `${shortLink(event.cid)}\\nput(${shortLink(event.value.data.key)},
246
- // {${Object.values(event.value.data.value)}})`
247
- // : `${shortLink(event.cid)}\\ndel(${event.value.data.key})`
248
- // }
249
- // for await (const line of vis(this.blocks, this.clock, { renderNodeLabel })) console.log(line)
250
- // }
273
+ async * vis () {
274
+ return yield * vis(this.blocks, this.clock)
275
+ }
276
+
277
+ async visTree () {
278
+ return await visMerkleTree(this.blocks, this.clock)
279
+ }
280
+
281
+ async visClock () {
282
+ return await visMerkleClock(this.blocks, this.clock)
283
+ }
251
284
 
252
285
  /**
253
- * Retrieves the document with the specified ID from the database
254
- *
255
- * @param {string} key - the ID of the document to retrieve
256
- * @param {Object} [opts] - options
257
- * @returns {Object<{_id: string, ...doc: Object}>} - the document with the specified ID
286
+ * Registers a Listener to be called when the Fireproof instance's clock is updated.
287
+ * Recieves live changes from the database after they are committed.
288
+ * @param {Function} listener - The listener to be called when the clock is updated.
289
+ * @returns {Function} - A function that can be called to unregister the listener.
258
290
  * @memberof Fireproof
259
- * @instance
260
291
  */
261
- async get (key, opts = {}) {
262
- const clock = opts.clock || this.clock
263
- const resp = await get(this.blocks, clock, charwise.encode(key))
264
-
265
- // this tombstone is temporary until we can get the prolly tree to delete
266
- if (!resp || resp.result === null) {
267
- throw new Error('Not found')
268
- }
269
- const doc = resp.result
270
- if (opts.mvcc === true) {
271
- doc._clock = this.clock
272
- }
273
- doc._proof = {
274
- data: await cidsToProof(resp.cids),
275
- clock: this.clock
292
+ registerListener (listener) {
293
+ this.#listeners.add(listener)
294
+ return () => {
295
+ this.#listeners.delete(listener)
276
296
  }
277
- doc._id = key
278
- return doc
279
297
  }
280
298
 
281
- async * vis () {
282
- return yield * vis(this.blocks, this.clock)
299
+ async #notifyListeners (changes) {
300
+ // await sleep(10)
301
+ for (const listener of this.#listeners) {
302
+ await listener(changes)
303
+ }
283
304
  }
284
305
 
285
306
  setCarUploader (carUploaderFn) {
package/src/hydrator.js CHANGED
@@ -6,16 +6,19 @@ const parseCID = cid => typeof cid === 'string' ? CID.parse(cid) : cid
6
6
 
7
7
  export default class Hydrator {
8
8
  static fromJSON (json, database) {
9
- database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name })
10
- for (const { code, clock: { byId, byKey, db } } of json.indexes) {
11
- DbIndex.fromJSON(database, {
12
- clock: {
13
- byId: byId ? parseCID(byId) : null,
14
- byKey: byKey ? parseCID(byKey) : null,
15
- db: db ? db.map(c => parseCID(c)) : null
16
- },
17
- code
18
- })
9
+ database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key })
10
+ if (json.indexes) {
11
+ for (const { name, code, clock: { byId, byKey, db } } of json.indexes) {
12
+ DbIndex.fromJSON(database, {
13
+ clock: {
14
+ byId: byId ? parseCID(byId) : null,
15
+ byKey: byKey ? parseCID(byKey) : null,
16
+ db: db ? db.map(c => parseCID(c)) : null
17
+ },
18
+ code,
19
+ name
20
+ })
21
+ }
19
22
  }
20
23
  return database
21
24
  }
@@ -45,7 +48,7 @@ export default class Hydrator {
45
48
  index.dbHead = null
46
49
  })
47
50
  database.clock = clock.map(c => parseCID(c))
48
- await database.notifyReset()
51
+ await database.notifyReset() // hmm... indexes should listen to this? might be more complex than worth it. so far this is the only caller
49
52
  return database
50
53
  }
51
54
  }
package/src/listener.js CHANGED
@@ -19,7 +19,7 @@ export default class Listener {
19
19
  * @type {Fireproof}
20
20
  */
21
21
  this.database = database
22
- this.#doStopListening = database.registerListener((changes) => this.#onChanges(changes))
22
+ this.#doStopListening = database.registerListener(changes => this.#onChanges(changes))
23
23
  /**
24
24
  * The map function to apply to each entry in the database.
25
25
  * @type {Function}
@@ -46,7 +46,7 @@ export default class Listener {
46
46
  if (typeof since !== 'undefined') {
47
47
  this.database.changesSince(since).then(({ rows: changes }) => {
48
48
  const keys = topicsForChanges(changes, this.routingFn).get(topic)
49
- if (keys) keys.forEach((key) => subscriber(key))
49
+ if (keys) keys.forEach(key => subscriber(key))
50
50
  })
51
51
  }
52
52
  return () => {
@@ -60,18 +60,14 @@ export default class Listener {
60
60
  const seenTopics = topicsForChanges(changes, this.routingFn)
61
61
  for (const [topic, keys] of seenTopics) {
62
62
  const listOfTopicSubscribers = getTopicList(this.#subcribers, topic)
63
- listOfTopicSubscribers.forEach((subscriber) => keys.forEach((key) => subscriber(key)))
63
+ listOfTopicSubscribers.forEach(subscriber => keys.forEach(key => subscriber(key)))
64
64
  }
65
65
  } else {
66
- // reset event
67
- if (changes.reset) {
68
- for (const [, listOfTopicSubscribers] of this.#subcribers) {
69
- listOfTopicSubscribers.forEach((subscriber) => subscriber(changes))
70
- }
66
+ // non-arrays go to all subscribers
67
+ for (const [, listOfTopicSubscribers] of this.#subcribers) {
68
+ listOfTopicSubscribers.forEach(subscriber => subscriber(changes))
71
69
  }
72
70
  }
73
- // if changes is special, notify all listeners?
74
- // first make the example app use listeners
75
71
  }
76
72
  }
77
73
 
@@ -84,9 +80,6 @@ function getTopicList (subscribersMap, name) {
84
80
  return topicList
85
81
  }
86
82
 
87
- // copied from src/db-index.js
88
- const makeDoc = ({ key, value }) => ({ _id: key, ...value })
89
-
90
83
  /**
91
84
  * Transforms a set of changes to events using an emitter function.
92
85
  *
@@ -99,7 +92,7 @@ const topicsForChanges = (changes, routingFn) => {
99
92
  const seenTopics = new Map()
100
93
  changes.forEach(({ key, value, del }) => {
101
94
  if (del || !value) value = { _deleted: true }
102
- routingFn(makeDoc({ key, value }), (t) => {
95
+ routingFn(({ _id: key, ...value }), t => {
103
96
  const topicList = getTopicList(seenTopics, t)
104
97
  topicList.push(key)
105
98
  })
package/src/prolly.js CHANGED
@@ -3,7 +3,8 @@ import {
3
3
  EventFetcher,
4
4
  EventBlock,
5
5
  findCommonAncestorWithSortedEvents,
6
- findEventsToSync
6
+ findEventsToSync,
7
+ vis as visClock
7
8
  } from './clock.js'
8
9
  import { create, load } from 'prolly-trees/map'
9
10
  // import { create, load } from '../../../../prolly-trees/src/map.js'
@@ -13,7 +14,7 @@ import * as codec from '@ipld/dag-cbor'
13
14
  import { sha256 as hasher } from 'multiformats/hashes/sha2'
14
15
  import { doTransaction } from './blockstore.js'
15
16
  import { create as createBlock } from 'multiformats/block'
16
- const opts = { cache, chunker: bf(3), codec, hasher, compare }
17
+ const blockOpts = { cache, chunker: bf(3), codec, hasher, compare }
17
18
 
18
19
  const withLog = async (label, fn) => {
19
20
  const resp = await fn()
@@ -66,12 +67,13 @@ async function createAndSaveNewEvent ({
66
67
  let cids
67
68
  const { key, value, del } = inEvent
68
69
  const data = {
69
- type: 'put',
70
- root: {
71
- cid: root.cid,
72
- bytes: root.bytes,
73
- value: root.value
74
- },
70
+ root: (root
71
+ ? {
72
+ cid: root.cid,
73
+ bytes: root.bytes, // can we remove this?
74
+ value: root.value // can we remove this?
75
+ }
76
+ : null),
75
77
  key
76
78
  }
77
79
 
@@ -80,6 +82,7 @@ async function createAndSaveNewEvent ({
80
82
  data.type = 'del'
81
83
  } else {
82
84
  data.value = value
85
+ data.type = 'put'
83
86
  }
84
87
  /** @type {EventData} */
85
88
 
@@ -114,13 +117,27 @@ const makeGetAndPutBlock = (inBlocks) => {
114
117
  return { getBlock, bigPut, blocks: inBlocks, cids }
115
118
  }
116
119
 
117
- const bulkFromEvents = (sorted) =>
118
- sorted.map(({ value: event }) => {
120
+ const bulkFromEvents = (sorted, event) => {
121
+ if (event) {
122
+ const update = { value: { data: { key: event.key } } }
123
+ if (event.del) {
124
+ update.value.data.type = 'del'
125
+ } else {
126
+ update.value.data.type = 'put'
127
+ update.value.data.value = event.value
128
+ }
129
+ sorted.push(update)
130
+ }
131
+ const bulk = new Map()
132
+ for (const { value: event } of sorted) {
119
133
  const {
120
134
  data: { type, value, key }
121
135
  } = event
122
- return type === 'put' ? { key, value } : { key, del: true }
123
- })
136
+ const bulkEvent = type === 'put' ? { key, value } : { key, del: true }
137
+ bulk.set(bulkEvent.key, bulkEvent) // last wins
138
+ }
139
+ return Array.from(bulk.values())
140
+ }
124
141
 
125
142
  // Get the value of the root from the ancestor event
126
143
  /**
@@ -135,7 +152,47 @@ const prollyRootFromAncestor = async (events, ancestor, getBlock) => {
135
152
  const event = await events.get(ancestor)
136
153
  const { root } = event.value.data
137
154
  // console.log('prollyRootFromAncestor', root.cid, JSON.stringify(root.value))
138
- return load({ cid: root.cid, get: getBlock, ...opts })
155
+ if (root) {
156
+ return load({ cid: root.cid, get: getBlock, ...blockOpts })
157
+ } else {
158
+ return null
159
+ }
160
+ }
161
+
162
+ const doProllyBulk = async (inBlocks, head, event) => {
163
+ const { getBlock, blocks } = makeGetAndPutBlock(inBlocks)
164
+ let bulkSorted = []
165
+ let prollyRootNode = null
166
+ if (head.length) {
167
+ // Otherwise, we find the common ancestor and update the root and other blocks
168
+ const events = new EventFetcher(blocks)
169
+ // todo this is returning more events than necessary, lets define the desired semantics from the top down
170
+ // good semantics mean we can cache the results of this call
171
+ const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
172
+ bulkSorted = sorted
173
+ // console.log('sorted', JSON.stringify(sorted.map(({ value: { data: { key, value } } }) => ({ key, value }))))
174
+ prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock)
175
+ // console.log('event', event)
176
+ }
177
+
178
+ const bulkOperations = bulkFromEvents(bulkSorted, event)
179
+
180
+ // if prolly root node is null, we need to create a new one
181
+ if (!prollyRootNode) {
182
+ let root
183
+ const newBlocks = []
184
+ // if all operations are deletes, we can just return an empty root
185
+ if (bulkOperations.every((op) => op.del)) {
186
+ return { root: null, blocks: [] }
187
+ }
188
+ for await (const node of create({ get: getBlock, list: bulkOperations, ...blockOpts })) {
189
+ root = await node.block
190
+ newBlocks.push(root)
191
+ }
192
+ return { root, blocks: newBlocks }
193
+ } else {
194
+ return await prollyRootNode.bulk(bulkOperations) // { root: newProllyRootNode, blocks: newBlocks }
195
+ }
139
196
  }
140
197
 
141
198
  /**
@@ -149,44 +206,45 @@ const prollyRootFromAncestor = async (events, ancestor, getBlock) => {
149
206
  * @returns {Promise<Result>}
150
207
  */
151
208
  export async function put (inBlocks, head, event, options) {
152
- const { getBlock, bigPut, blocks } = makeGetAndPutBlock(inBlocks)
209
+ const { bigPut } = makeGetAndPutBlock(inBlocks)
153
210
 
154
211
  // If the head is empty, we create a new event and return the root and addition blocks
155
212
  if (!head.length) {
156
213
  const additions = new Map()
157
- let root
158
- for await (const node of create({ get: getBlock, list: [event], ...opts })) {
159
- root = await node.block
160
- bigPut(root, additions)
214
+ const { root, blocks } = await doProllyBulk(inBlocks, head, event)
215
+ for (const b of blocks) {
216
+ bigPut(b, additions)
161
217
  }
162
218
  return createAndSaveNewEvent({ inBlocks, bigPut, root, event, head, additions: Array.from(additions.values()) })
163
219
  }
220
+ const { root: newProllyRootNode, blocks: newBlocks } = await doProllyBulk(inBlocks, head, event)
164
221
 
165
- // Otherwise, we find the common ancestor and update the root and other blocks
166
- const events = new EventFetcher(blocks)
167
- // todo this is returning more events than necessary, lets define the desired semantics from the top down
168
- // good semantics mean we can cache the results of this call
169
- const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
170
- // console.log('sorted', JSON.stringify(sorted.map(({ value: { data: { key, value } } }) => ({ key, value }))))
171
- const prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock)
172
-
173
- const bulkOperations = bulkFromEvents(sorted)
174
- const { root: newProllyRootNode, blocks: newBlocks } = await prollyRootNode.bulk([...bulkOperations, event]) // ading delete support here
175
- const prollyRootBlock = await newProllyRootNode.block
176
- const additions = new Map() // ; const removals = new Map()
177
- bigPut(prollyRootBlock, additions)
178
- for (const nb of newBlocks) {
179
- bigPut(nb, additions)
222
+ if (!newProllyRootNode) {
223
+ return createAndSaveNewEvent({
224
+ inBlocks,
225
+ bigPut,
226
+ root: null,
227
+ event,
228
+ head,
229
+ additions: []
230
+ })
231
+ } else {
232
+ const prollyRootBlock = await newProllyRootNode.block
233
+ const additions = new Map() // ; const removals = new Map()
234
+ bigPut(prollyRootBlock, additions)
235
+ for (const nb of newBlocks) {
236
+ bigPut(nb, additions)
237
+ }
238
+ // additions are new blocks
239
+ return createAndSaveNewEvent({
240
+ inBlocks,
241
+ bigPut,
242
+ root: prollyRootBlock,
243
+ event,
244
+ head,
245
+ additions: Array.from(additions.values()) /*, todo? Array.from(removals.values()) */
246
+ })
180
247
  }
181
- // additions are new blocks
182
- return createAndSaveNewEvent({
183
- inBlocks,
184
- bigPut,
185
- root: prollyRootBlock,
186
- event,
187
- head,
188
- additions: Array.from(additions.values()) /*, todo? Array.from(removals.values()) */
189
- })
190
248
  }
191
249
 
192
250
  /**
@@ -199,25 +257,15 @@ export async function root (inBlocks, head) {
199
257
  if (!head.length) {
200
258
  throw new Error('no head')
201
259
  }
202
- const { getBlock, blocks } = makeGetAndPutBlock(inBlocks)
203
- const events = new EventFetcher(blocks)
204
- const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
205
- const prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock)
206
-
207
- // Perform bulk operations (put or delete) for each event in the sorted array
208
- const bulkOperations = bulkFromEvents(sorted)
209
- const { root: newProllyRootNode, blocks: newBlocks } = await prollyRootNode.bulk(bulkOperations)
210
- // const prollyRootBlock = await newProllyRootNode.block
211
- // console.log('newBlocks', newBlocks.map((nb) => nb.cid.toString()))
260
+ const { root: newProllyRootNode, blocks: newBlocks, cids } = await doProllyBulk(inBlocks, head)
212
261
  // todo maybe these should go to a temp blockstore?
213
262
  await doTransaction('root', inBlocks, async (transactionBlockstore) => {
214
263
  const { bigPut } = makeGetAndPutBlock(transactionBlockstore)
215
264
  for (const nb of newBlocks) {
216
265
  bigPut(nb)
217
266
  }
218
- // bigPut(prollyRootBlock)
219
267
  })
220
- return { cids: events.cids, node: newProllyRootNode }
268
+ return { cids, node: newProllyRootNode }
221
269
  }
222
270
 
223
271
  /**
@@ -251,6 +299,9 @@ export async function getAll (blocks, head) {
251
299
  return { clockCIDs: new CIDCounter(), cids: new CIDCounter(), result: [] }
252
300
  }
253
301
  const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head)
302
+ if (!prollyRootNode) {
303
+ return { clockCIDs, cids: new CIDCounter(), result: [] }
304
+ }
254
305
  const { result, cids } = await prollyRootNode.getAllEntries() // todo params
255
306
  return { clockCIDs, cids, result: result.map(({ key, value }) => ({ key, value })) }
256
307
  }
@@ -266,6 +317,9 @@ export async function get (blocks, head, key) {
266
317
  return { cids: new CIDCounter(), result: null }
267
318
  }
268
319
  const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head)
320
+ if (!prollyRootNode) {
321
+ return { clockCIDs, cids: new CIDCounter(), result: null }
322
+ }
269
323
  const { result, cids } = await prollyRootNode.get(key)
270
324
  return { result, cids, clockCIDs }
271
325
  }
@@ -278,6 +332,28 @@ export async function * vis (blocks, head) {
278
332
  const lines = []
279
333
  for await (const line of prollyRootNode.vis()) {
280
334
  yield line
335
+ lines.push(line)
281
336
  }
282
337
  return { vis: lines.join('\n'), cids }
283
338
  }
339
+
340
+ export async function visMerkleTree (blocks, head) {
341
+ if (!head.length) {
342
+ return { cids: new CIDCounter(), result: null }
343
+ }
344
+ const { node: prollyRootNode, cids } = await root(blocks, head)
345
+ const lines = []
346
+ for await (const line of prollyRootNode.vis()) {
347
+ lines.push(line)
348
+ }
349
+ return { vis: lines.join('\n'), cids }
350
+ }
351
+
352
+ export async function visMerkleClock (blocks, head) {
353
+ const lines = []
354
+ for await (const line of visClock(blocks, head)) {
355
+ // yield line
356
+ lines.push(line)
357
+ }
358
+ return { vis: lines.join('\n') }
359
+ }