@fireproof/core 0.8.0 → 0.10.1-dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/README.md +5 -184
  2. package/dist/fireproof.browser.js +18879 -0
  3. package/dist/fireproof.browser.js.map +7 -0
  4. package/dist/fireproof.cjs.js +9305 -0
  5. package/dist/fireproof.cjs.js.map +7 -0
  6. package/dist/fireproof.esm.js +9295 -0
  7. package/dist/fireproof.esm.js.map +7 -0
  8. package/package.json +57 -105
  9. package/dist/blockstore.js +0 -268
  10. package/dist/clock.js +0 -459
  11. package/dist/crypto.js +0 -63
  12. package/dist/database.js +0 -434
  13. package/dist/db-index.js +0 -403
  14. package/dist/encrypted-block.js +0 -48
  15. package/dist/fireproof.js +0 -84
  16. package/dist/import.js +0 -29
  17. package/dist/listener.js +0 -111
  18. package/dist/loader.js +0 -13
  19. package/dist/prolly.js +0 -405
  20. package/dist/remote.js +0 -102
  21. package/dist/sha1.js +0 -74
  22. package/dist/src/fireproof.d.ts +0 -472
  23. package/dist/src/fireproof.js +0 -81191
  24. package/dist/src/fireproof.js.map +0 -1
  25. package/dist/src/fireproof.mjs +0 -81186
  26. package/dist/src/fireproof.mjs.map +0 -1
  27. package/dist/storage/base.js +0 -426
  28. package/dist/storage/blocksToEncryptedCarBlock.js +0 -144
  29. package/dist/storage/browser.js +0 -62
  30. package/dist/storage/filesystem.js +0 -67
  31. package/dist/storage/rest.js +0 -57
  32. package/dist/storage/ucan.js +0 -0
  33. package/dist/storage/utils.js +0 -144
  34. package/dist/sync.js +0 -218
  35. package/dist/utils.js +0 -16
  36. package/dist/valet.js +0 -102
  37. package/src/blockstore.js +0 -283
  38. package/src/clock.js +0 -486
  39. package/src/crypto.js +0 -70
  40. package/src/database.js +0 -469
  41. package/src/db-index.js +0 -426
  42. package/src/encrypted-block.js +0 -57
  43. package/src/fireproof.js +0 -98
  44. package/src/import.js +0 -34
  45. package/src/link.d.ts +0 -3
  46. package/src/loader.js +0 -16
  47. package/src/prolly.js +0 -445
  48. package/src/remote.js +0 -113
  49. package/src/sha1.js +0 -83
  50. package/src/storage/base.js +0 -463
  51. package/src/storage/browser.js +0 -67
  52. package/src/storage/filesystem.js +0 -73
  53. package/src/storage/rest.js +0 -59
  54. package/src/storage/ucan.js +0 -0
  55. package/src/storage/utils.js +0 -152
  56. package/src/sync.js +0 -237
  57. package/src/valet.js +0 -105
package/src/clock.js DELETED
@@ -1,486 +0,0 @@
1
- import { Block, encode, decode } from 'multiformats/block'
2
- import { sha256 } from 'multiformats/hashes/sha2'
3
- import * as cbor from '@ipld/dag-cbor'
4
- // @ts-ignore
5
- import { CIDCounter } from 'prolly-trees/utils'
6
-
7
- /**
8
- * @template T
9
- * @typedef {{ parents: EventLink<T>[], data: T }} EventView
10
- */
11
-
12
- /**
13
- * @template T
14
- * @typedef {import('multiformats').BlockView<EventView<T>>} EventBlockView
15
- */
16
-
17
- /**
18
- * @template T
19
- * @typedef {import('multiformats').Link<EventView<T>>} EventLink
20
- */
21
-
22
- /**
23
- * @typedef {{
24
- * type: 'put'|'del'
25
- * key: string
26
- * value: import('./link').AnyLink
27
- * root: import('./link').AnyLink
28
- * }} EventData
29
- * @typedef {{
30
- * root: import('./link').AnyLink
31
- * head: import('./clock').EventLink<EventData>[]
32
- * event: import('./clock').EventBlockView<EventData>
33
- * }} Result
34
- */
35
-
36
- /**
37
- * Advance the clock by adding an event.
38
- *
39
- * @template T
40
- * @param {import('./blockstore').TransactionBlockstore} blocks Block storage.
41
- * @param {EventLink<T>[]} head The head of the clock.
42
- * @param {EventLink<T>} event The event to add.
43
- * @returns {Promise<{head:EventLink<T>[], cids:any[]}>} The new head of the clock.
44
- */
45
- export async function advance (blocks, head, event) {
46
- /** @type {EventFetcher<T>} */
47
- const events = new EventFetcher(blocks)
48
- const headmap = new Map(head.map(cid => [cid.toString(), cid]))
49
-
50
- // Check if the headmap already includes the event, return head if it does
51
- if (headmap.has(event.toString())) return { head, cids: await events.all() }
52
-
53
- // Does event contain the clock?
54
- let changed = false
55
- for (const cid of head) {
56
- if (await contains(events, event, cid)) {
57
- headmap.delete(cid.toString())
58
- headmap.set(event.toString(), event)
59
- changed = true
60
- }
61
- }
62
-
63
- // If the headmap has been changed, return the new headmap values
64
- if (changed) {
65
- return { head: [...headmap.values()], cids: await events.all() }
66
- }
67
-
68
- // Does clock contain the event?
69
- for (const p of head) {
70
- if (await contains(events, p, event)) {
71
- return { head, cids: await events.all() }
72
- }
73
- }
74
-
75
- // Return the head concatenated with the new event if it passes both checks
76
- return { head: head.concat(event), cids: await events.all() }
77
- }
78
-
79
- /**
80
- * @template T
81
- * @implements {EventBlockView<T>}
82
- */
83
- export class EventBlock extends Block {
84
- /**
85
- * @param {object} config
86
- * @param {EventLink<T>} config.cid
87
- * @param {Event} config.value
88
- * @param {Uint8Array} config.bytes
89
- */
90
- constructor ({ cid, value, bytes }) {
91
- // @ts-ignore
92
- super({ cid, value, bytes })
93
- }
94
-
95
- /**
96
- * @template T
97
- * @param {T} data
98
- * @param {EventLink<T>[]} [parents]
99
- */
100
- static create (data, parents) {
101
- return encodeEventBlock({ data, parents: parents ?? [] })
102
- }
103
- }
104
-
105
- /** @template T */
106
- export class EventFetcher {
107
- /** @param {import('./blockstore').TransactionBlockstore} blocks */
108
- constructor (blocks) {
109
- /** @private */
110
- this._blocks = blocks
111
- this._cids = new CIDCounter()
112
- this._cache = new Map()
113
- }
114
-
115
- /**
116
- * @param {EventLink<T>} link
117
- * @returns {Promise<EventBlockView<T>>}
118
- */
119
- async get (link) {
120
- const slink = link.toString()
121
- // console.log('get', link.toString())
122
- if (this._cache.has(slink)) return this._cache.get(slink)
123
- const block = await this._blocks.get(link)
124
- this._cids.add({ address: link })
125
- if (!block) throw new Error(`missing block: ${link}`)
126
- const got = decodeEventBlock(block.bytes)
127
- this._cache.set(slink, got)
128
- return got
129
- }
130
-
131
- async all () {
132
- // await Promise.all([...this._cids])
133
- return this._cids.all()
134
- }
135
- }
136
-
137
- /**
138
- * @template T
139
- * @param {EventView<T>} value
140
- * @returns {Promise<EventBlockView<T>>}
141
- */
142
- export async function encodeEventBlock (value) {
143
- // TODO: sort parents
144
- const { cid, bytes } = await encode({ value, codec: cbor, hasher: sha256 })
145
- // @ts-ignore
146
- return new Block({ cid, value, bytes })
147
- }
148
-
149
- /**
150
- * @template T
151
- * @param {Uint8Array} bytes
152
- * @returns {Promise<EventBlockView<T>>}
153
- */
154
- export async function decodeEventBlock (bytes) {
155
- const { cid, value } = await decode({ bytes, codec: cbor, hasher: sha256 })
156
- // @ts-ignore
157
- return new Block({ cid, value, bytes })
158
- }
159
-
160
- /**
161
- * Returns true if event "a" contains event "b". Breadth first search.
162
- * @template T
163
- * @param {EventFetcher} events
164
- * @param {EventLink<T>} a
165
- * @param {EventLink<T>} b
166
- */
167
- async function contains (events, a, b) {
168
- if (a.toString() === b.toString()) return true
169
- const [{ value: aevent }, { value: bevent }] = await Promise.all([events.get(a), events.get(b)])
170
- // const links = [...aevent.parents]
171
- // console.log('aevent', aevent.parents)
172
- const links = [...(aevent.parents || [])]
173
- while (links.length) {
174
- const link = links.shift()
175
- if (!link) break
176
- if (link.toString() === b.toString()) return true
177
- // if any of b's parents are this link, then b cannot exist in any of the
178
- // tree below, since that would create a cycle.
179
- if (bevent.parents.some(p => link.toString() === p.toString())) continue
180
- const { value: event } = await events.get(link)
181
- links.push(...event.parents)
182
- }
183
- return false
184
- }
185
-
186
- /**
187
- * @template T
188
- * @param {import('./blockstore').TransactionBlockstore} blocks Block storage.
189
- * @param {EventLink<T>[]} head
190
- * @param {object} [options]
191
- * @param {(b: EventBlockView<T>) => string} [options.renderNodeLabel]
192
- */
193
- export async function * vis (blocks, head, options = {}) {
194
- // @ts-ignore
195
- const renderNodeLabel =
196
- options.renderNodeLabel ??
197
- (b => {
198
- // @ts-ignore
199
- const { key, root, type } = b.value.data
200
- return (
201
- b.cid.toString() + '\n' + JSON.stringify({ key, root: root.cid.toString(), type }, null, 2).replace(/"/g, "'")
202
- )
203
- })
204
- const events = new EventFetcher(blocks)
205
- yield 'digraph clock {'
206
- yield ' node [shape=point fontname="Courier"]; head;'
207
- const hevents = await Promise.all(head.map(link => events.get(link)))
208
- const links = []
209
- const nodes = new Set()
210
- for (const e of hevents) {
211
- nodes.add(e.cid.toString())
212
- yield ` node [shape=oval fontname="Courier"]; ${e.cid} [label="${renderNodeLabel(e)}"];`
213
- yield ` head -> ${e.cid};`
214
- for (const p of e.value.parents) {
215
- yield ` ${e.cid} -> ${p};`
216
- }
217
- links.push(...e.value.parents)
218
- }
219
- while (links.length) {
220
- const link = links.shift()
221
- if (!link) break
222
- if (nodes.has(link.toString())) continue
223
- nodes.add(link.toString())
224
- const block = await events.get(link)
225
- yield ` node [shape=oval]; ${link} [label="${renderNodeLabel(block)}" fontname="Courier"];`
226
- for (const p of block.value.parents) {
227
- yield ` ${link} -> ${p};`
228
- }
229
- links.push(...block.value.parents)
230
- }
231
- yield '}'
232
- }
233
-
234
- export async function findEventsToSync (blocks, head) {
235
- // const callTag = Math.random().toString(36).substring(7)
236
- const events = new EventFetcher(blocks)
237
- // console.time(callTag + '.findCommonAncestorWithSortedEvents')
238
- const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
239
- // console.timeEnd(callTag + '.findCommonAncestorWithSortedEvents')
240
- // console.log('sorted', !!ancestor, sorted)
241
- // console.time(callTag + '.contains')
242
-
243
- const toSync = ancestor ? await asyncFilter(sorted, async uks => !(await contains(events, ancestor, uks.cid))) : sorted
244
- // console.timeEnd(callTag + '.contains')
245
- const sortDifference = sorted.length - toSync.length
246
- if (sortDifference / sorted.length > 0.6) console.log('optimize sorted', !!ancestor, sortDifference)
247
-
248
- return { cids: events, events: toSync }
249
- }
250
-
251
- const asyncFilter = async (arr, predicate) =>
252
- Promise.all(arr.map(predicate)).then(results => arr.filter((_v, index) => results[index]))
253
-
254
- export async function findCommonAncestorWithSortedEvents (events, children, doFull = false) {
255
- // console.trace('findCommonAncestorWithSortedEvents')
256
- // const callTag = Math.random().toString(36).substring(7)
257
- // console.log(callTag + '.children', children.map((c) => c.toString()))
258
- // console.time(callTag + '.findCommonAncestor')
259
- const ancestor = await findCommonAncestor(events, children)
260
- // console.timeEnd(callTag + '.findCommonAncestor')
261
- // console.log('ancestor', ancestor.toString())
262
- if (!ancestor) {
263
- console.log('no common ancestor', children)
264
- // throw new Error('no common ancestor')
265
- const sorted = await findSortedEvents(events, children, children, doFull)
266
- return { ancestor: null, sorted }
267
- }
268
- // console.time(callTag + '.findSortedEvents')
269
- const sorted = await findSortedEvents(events, children, [ancestor], doFull)
270
- // console.timeEnd(callTag + '.findSortedEvents')
271
- // console.log('sorted', sorted.length)
272
- // console.log('ancestor', JSON.stringify(ancestor, null, 2))
273
- return { ancestor, sorted }
274
- }
275
-
276
- /**
277
- * Find the common ancestor event of the passed children. A common ancestor is
278
- * the first single event in the DAG that _all_ paths from children lead to.
279
- *
280
- * @param {import('./clock').EventFetcher} events
281
- * @param {import('./clock').EventLink<EventData>[]} children
282
- */
283
- // async function NEWfindCommonAncestor (events, children) {
284
- // if (!children.length) return
285
- // if (children.length === 1) return children[0]
286
-
287
- // const candidates = children.map(c => [c])
288
- // const visited = new Set()
289
-
290
- // while (true) {
291
- // let changed = false
292
- // for (const c of candidates) {
293
- // const candidate = await findAncestorCandidate(events, c[c.length - 1])
294
-
295
- // if (!candidate) continue
296
-
297
- // if (visited.has(candidate)) {
298
- // return candidate // Common ancestor found
299
- // }
300
-
301
- // visited.add(candidate)
302
- // changed = true
303
- // c.push(candidate)
304
- // }
305
-
306
- // if (!changed) {
307
- // // No common ancestor found, exhausted candidates
308
- // return null
309
- // }
310
- // }
311
- // }
312
-
313
- async function findCommonAncestor (events, children) {
314
- if (!children.length) return
315
- children = [...new Set(children)]
316
- if (children.length === 1) return children[0]
317
- const candidates = children.map((c) => [c])
318
- // console.log(
319
- // 'og candidates',
320
- // candidates.map((c) => c.toString())
321
- // )
322
- while (true) {
323
- let changed = false
324
- for (const c of candidates) {
325
- const candidate = await findAncestorCandidate(events, c[c.length - 1])
326
- if (!candidate) continue
327
-
328
- // Check if the candidate is already in the list, and if so, skip it.
329
- if (c.includes(candidate)) continue
330
-
331
- // if set size is all cids, then no common ancestor
332
- changed = true
333
- c.push(candidate) // make set?
334
- // console.log('candidate', candidates.map((c) => c.toString()))
335
- const ancestor = findCommonString(candidates)
336
- if (ancestor) return ancestor
337
- }
338
- if (!changed) return
339
- }
340
- }
341
-
342
- // async function OGfindCommonAncestor (events, children) {
343
- // if (!children.length) return
344
- // if (children.length === 1) return children[0]
345
- // const candidates = children.map(c => [c])
346
- // console.log(
347
- // 'og candidates',
348
- // candidates.map(c => c.toString())
349
- // )
350
- // while (true) {
351
- // let changed = false
352
- // for (const c of candidates) {
353
- // const candidate = await findAncestorCandidate(events, c[c.length - 1])
354
- // if (!candidate) continue
355
- // // if set size is all cids, then no common ancestor
356
- // changed = true
357
- // c.push(candidate) // make set?
358
- // console.log(
359
- // 'candidate',
360
- // candidates.map(c => c.toString())
361
- // )
362
- // const ancestor = findCommonString(candidates)
363
- // if (ancestor) return ancestor
364
- // }
365
- // if (!changed) return
366
- // }
367
- // }
368
-
369
- /**
370
- * @param {import('./clock').EventFetcher} events
371
- * @param {import('./clock').EventLink<EventData>} root
372
- */
373
- async function findAncestorCandidate (events, root) {
374
- const { value: event } = await events.get(root) // .catch(() => ({ value: { parents: [] } }))
375
- // console.log(
376
- // 'findAncestorCandidate',
377
- // root.toString(),
378
- // 'parents',
379
- // event.parents.map(p => p.toString())
380
- // )
381
- if (!event.parents.length) return root
382
- return event.parents.length === 1 ? event.parents[0] : findCommonAncestor(events, event.parents)
383
- }
384
-
385
- /**
386
- * @template {{ toString: () => string }} T
387
- * @param {Array<T[]>} arrays
388
- */
389
- function findCommonString (arrays) {
390
- // console.log('findCommonString', arrays.map((a) => a.map((i) => String(i))))
391
- arrays = arrays.map(a => [...a])
392
- for (const arr of arrays) {
393
- for (const item of arr) {
394
- let matched = true
395
- for (const other of arrays) {
396
- if (arr === other) continue
397
- matched = other.some(i => String(i) === String(item))
398
- if (!matched) break
399
- }
400
- if (matched) return item
401
- }
402
- }
403
- }
404
-
405
- /**
406
- * Find and sort events between the head(s) and the tail.
407
- * @param {import('./clock').EventFetcher} events
408
- * @param {any[]} head
409
- * @param {import('./clock').EventLink<EventData>[]} tails
410
- */
411
- async function findSortedEvents (events, head, tails, doFull) {
412
- // const callTag = Math.random().toString(36).substring(7)
413
- // get weighted events - heavier events happened first
414
- // const callTag = Math.random().toString(36).substring(7)
415
-
416
- /** @type {Map<string, { event: import('./clock').EventBlockView<EventData>, weight: number }>} */
417
- const weights = new Map()
418
- head = [...new Set([...head.map(h => h.toString())])]
419
- // console.log(callTag + '.head', head.length)
420
-
421
- const allEvents = new Set([tails.map((t) => t.toString()).toString(), ...head])
422
- if (!doFull && allEvents.size === 1) {
423
- // console.log('head contains tail', tail.toString())
424
- return []
425
- // const event = await events.get(tail)
426
- // return [event]
427
- }
428
-
429
- // console.log('finding events')
430
- // console.log(callTag + '.head', head.length, [...head.map((h) => h.toString())], tail.toString())
431
-
432
- // console.time(callTag + '.findEvents')
433
- const all = await (await Promise.all(tails.map((t) => Promise.all(head.map(h => findEvents(events, h, t)))))).flat()
434
- // console.log('all', all.length)
435
- // console.timeEnd(callTag + '.findEvents')
436
- for (const arr of all) {
437
- for (const { event, depth } of arr) {
438
- // console.log('event value', event.value.data.value)
439
- const info = weights.get(event.cid.toString())
440
- if (info) {
441
- info.weight += depth
442
- } else {
443
- weights.set(event.cid.toString(), { event, weight: depth })
444
- }
445
- }
446
- }
447
-
448
- // group events into buckets by weight
449
- /** @type {Map<number, import('./clock').EventBlockView<EventData>[]>} */
450
- const buckets = new Map()
451
- for (const { event, weight } of weights.values()) {
452
- const bucket = buckets.get(weight)
453
- if (bucket) {
454
- bucket.push(event)
455
- } else {
456
- buckets.set(weight, [event])
457
- }
458
- }
459
-
460
- // sort by weight, and by CID within weight
461
- const sorted = Array.from(buckets)
462
- .sort((a, b) => b[0] - a[0])
463
- .flatMap(([, es]) => es.sort((a, b) => (String(a.cid) < String(b.cid) ? -1 : 1)))
464
- // console.log('sorted', sorted.map(s => s.cid))
465
-
466
- return sorted
467
- }
468
-
469
- /**
470
- * @param {EventFetcher} events
471
- * @param {EventLink<EventData>} start
472
- * @param {EventLink<EventData>} end
473
- * @returns {Promise<Array<{ event: EventBlockView<EventData>, depth: number }>>}
474
- */
475
- async function findEvents (events, start, end, depth = 0) {
476
- // console.log('findEvents', start.toString(), end.toString(), depth)
477
- const event = await events.get(start)
478
- const send = String(end)
479
- const acc = [{ event, depth }]
480
- const { parents } = event.value
481
- // if (parents.length === 1 && String(parents[0]) === send) return acc
482
- if (parents.findIndex(p => String(p) === send) !== -1) return acc
483
- // if (parents.length === 1) return acc
484
- const rest = await Promise.all(parents.map(p => findEvents(events, p, end, depth + 1)))
485
- return acc.concat(...rest)
486
- }
package/src/crypto.js DELETED
@@ -1,70 +0,0 @@
1
- // @ts-nocheck
2
- import * as codec from './encrypted-block.js'
3
- import {
4
- create,
5
- load
6
- } from 'prolly-trees/cid-set'
7
- import { CID } from 'multiformats'
8
- import { encode, decode, create as mfCreate } from 'multiformats/block'
9
- import * as dagcbor from '@ipld/dag-cbor'
10
- import { sha256 as hasher } from 'multiformats/hashes/sha2'
11
-
12
- const createBlock = (bytes, cid) => mfCreate({ cid, bytes, hasher, codec })
13
-
14
- const encrypt = async function * ({ get, cids, hasher, key, cache, chunker, root }) {
15
- const set = new Set()
16
- let eroot
17
- for (const string of cids) {
18
- const cid = CID.parse(string)
19
- let unencrypted = await get(cid)
20
- if (!unencrypted.cid) {
21
- unencrypted = { cid, bytes: unencrypted }
22
- }
23
- // console.log('unencrypted', unencrypted)
24
- const block = await encode({ ...await codec.encrypt({ ...unencrypted, key }), codec, hasher })
25
- // console.log(`encrypting ${string} as ${block.cid}`)
26
- yield block
27
- set.add(block.cid.toString())
28
- if (unencrypted.cid.equals(root)) eroot = block.cid
29
- }
30
- if (!eroot) throw new Error('cids does not include root')
31
- const list = [...set].map(s => CID.parse(s))
32
- let last
33
- for await (const node of create({ list, get, cache, chunker, hasher, codec: dagcbor })) {
34
- const block = await node.block
35
- yield block
36
- last = block
37
- }
38
- const head = [eroot, last.cid]
39
- const block = await encode({ value: head, codec: dagcbor, hasher })
40
- yield block
41
- }
42
-
43
- const decrypt = async function * ({ root, get, key, cache, chunker, hasher }) {
44
- const o = { ...await get(root), codec: dagcbor, hasher }
45
- const decodedRoot = await decode(o)
46
- // console.log('decodedRoot', decodedRoot)
47
- const { value: [eroot, tree] } = decodedRoot
48
- const rootBlock = await get(eroot) // should I decrypt?
49
- const cidset = await load({ cid: tree, get, cache, chunker, codec, hasher })
50
- const { result: nodes } = await cidset.getAllEntries()
51
- const unwrap = async (eblock) => {
52
- const { bytes, cid } = await codec.decrypt({ ...eblock, key }).catch(e => {
53
- // console.log('ekey', e)
54
- throw new Error('bad key: ' + key.toString('hex'))
55
- })
56
- const block = await createBlock(bytes, cid)
57
- return block
58
- }
59
- const promises = []
60
- for (const { cid } of nodes) {
61
- if (!rootBlock.cid.equals(cid)) promises.push(get(cid).then(unwrap))
62
- }
63
- yield * promises
64
- yield unwrap(rootBlock)
65
- }
66
-
67
- export {
68
- encrypt,
69
- decrypt
70
- }