@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 +3 -2
- package/src/clock.js +25 -47
- package/src/db-index.js +75 -50
- package/src/fireproof.js +29 -18
- package/src/prolly.js +32 -26
- package/test/clock.test.js +91 -165
- package/test/db-index.test.js +1 -0
- package/test/fireproof.test.js +11 -0
- package/test/fulltext.test.js +66 -0
- package/test/prolly.test.js +14 -27
- package/test/proofs.test.js +53 -0
- package/test/reproduce-fixture-bug.test.js +65 -0
- package/hooks/use-fireproof.md +0 -149
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@fireproof/core",
|
3
|
-
"version": "0.0.
|
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.
|
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 =>
|
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
|
-
|
202
|
-
|
203
|
-
|
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) =>
|
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
|
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
|
-
|
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
|
127
|
-
if (!root) {
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
}
|
133
|
-
const response = await doIndexQuery(this.database.blocks,
|
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
|
-
|
136
|
-
//
|
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.
|
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
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
this.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
215
|
+
returnRootBlock = block
|
216
|
+
returnNode = node
|
196
217
|
}
|
197
|
-
return inRoot
|
198
218
|
} else {
|
199
|
-
const
|
200
|
-
const { root, blocks } = await
|
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
|
-
|
226
|
+
await putBlock(returnRootBlock.cid, returnRootBlock.bytes)
|
205
227
|
}
|
228
|
+
return { dbIndex: returnNode, root: returnRootBlock }
|
206
229
|
}
|
207
230
|
|
208
|
-
async function doIndexQuery (blocks,
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
240
|
+
return dbIndex.range(...encodedRange)
|
216
241
|
} else if (query.key) {
|
217
242
|
const encodedKey = charwise.encode(query.key)
|
218
|
-
return
|
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
|
-
|
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:
|
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
|
-
|
255
|
-
|
256
|
-
|
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 (
|
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
|
-
|
266
|
+
doc._clock = this.clock
|
266
267
|
}
|
267
|
-
|
268
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
227
|
-
|
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
|
}
|