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