@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/hooks/use-fireproof.ts +44 -27
- package/package.json +9 -6
- package/scripts/keygen.js +3 -0
- package/src/blockstore.js +27 -56
- package/src/clock.js +0 -1
- package/src/crypto.js +65 -0
- package/src/db-index.js +34 -45
- package/src/fireproof.js +87 -66
- package/src/hydrator.js +14 -11
- package/src/listener.js +7 -14
- package/src/prolly.js +130 -54
- package/src/sha1.js +82 -0
- package/src/valet.js +169 -11
- package/test/db-index.test.js +15 -1
- package/test/fireproof.test.js +83 -4
- package/test/hydrator.test.js +8 -2
package/src/fireproof.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
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
|
-
|
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.
|
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
|
-
|
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({
|
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.
|
126
|
+
clock: this.clockToJSON(),
|
110
127
|
proof: { data: await cidsToProof(dataCIDs), clock: await cidsToProof(clockCIDs) }
|
111
128
|
}
|
112
129
|
}
|
113
130
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
*
|
254
|
-
*
|
255
|
-
* @param {
|
256
|
-
* @
|
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
|
-
|
262
|
-
|
263
|
-
|
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
|
282
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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(
|
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(
|
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(
|
63
|
+
listOfTopicSubscribers.forEach(subscriber => keys.forEach(key => subscriber(key)))
|
64
64
|
}
|
65
65
|
} else {
|
66
|
-
//
|
67
|
-
|
68
|
-
|
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(
|
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
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 {
|
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
|
-
|
158
|
-
for
|
159
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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 {
|
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
|
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
|
+
}
|