@fireproof/core 0.3.22 → 0.4.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.
Files changed (55) hide show
  1. package/dist/blockstore.js +242 -0
  2. package/dist/clock.js +355 -0
  3. package/dist/crypto.js +59 -0
  4. package/dist/database.js +308 -0
  5. package/dist/db-index.js +314 -0
  6. package/dist/fireproof.js +83 -0
  7. package/dist/hooks/use-fireproof.js +100 -0
  8. package/dist/listener.js +110 -0
  9. package/dist/prolly.js +316 -0
  10. package/dist/sha1.js +74 -0
  11. package/dist/src/blockstore.js +242 -0
  12. package/dist/src/clock.js +355 -0
  13. package/dist/src/crypto.js +59 -0
  14. package/dist/src/database.js +312 -0
  15. package/dist/src/db-index.js +314 -0
  16. package/dist/src/fireproof.d.ts +319 -0
  17. package/dist/src/fireproof.js +38976 -0
  18. package/dist/src/fireproof.js.map +1 -0
  19. package/dist/src/fireproof.mjs +38972 -0
  20. package/dist/src/fireproof.mjs.map +1 -0
  21. package/dist/src/index.d.ts +1 -1
  22. package/dist/src/index.js +19 -14
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/index.mjs +19 -14
  25. package/dist/src/index.mjs.map +1 -1
  26. package/dist/src/listener.js +108 -0
  27. package/dist/src/prolly.js +319 -0
  28. package/dist/src/sha1.js +74 -0
  29. package/dist/src/utils.js +16 -0
  30. package/dist/src/valet.js +262 -0
  31. package/dist/test/block.js +57 -0
  32. package/dist/test/clock.test.js +556 -0
  33. package/dist/test/db-index.test.js +231 -0
  34. package/dist/test/fireproof.test.js +444 -0
  35. package/dist/test/fulltext.test.js +61 -0
  36. package/dist/test/helpers.js +39 -0
  37. package/dist/test/hydrator.test.js +142 -0
  38. package/dist/test/listener.test.js +103 -0
  39. package/dist/test/prolly.test.js +162 -0
  40. package/dist/test/proofs.test.js +45 -0
  41. package/dist/test/reproduce-fixture-bug.test.js +57 -0
  42. package/dist/test/valet.test.js +56 -0
  43. package/dist/utils.js +16 -0
  44. package/dist/valet.js +262 -0
  45. package/hooks/use-fireproof.js +38 -63
  46. package/package.json +13 -14
  47. package/src/blockstore.js +8 -4
  48. package/src/database.js +338 -0
  49. package/src/db-index.js +3 -3
  50. package/src/fireproof.js +65 -322
  51. package/src/listener.js +10 -8
  52. package/src/prolly.js +10 -6
  53. package/src/utils.js +16 -0
  54. package/src/hydrator.js +0 -54
  55. package/src/index.js +0 -6
@@ -0,0 +1,83 @@
1
+ import randomBytes from 'randombytes';
2
+ import { Database } from './database.js';
3
+ import { Listener } from './listener.js';
4
+ import { DbIndex as Index } from './db-index.js';
5
+ import { CID } from 'multiformats';
6
+ import { TransactionBlockstore } from './blockstore.js';
7
+ import { localGet } from './utils.js';
8
+ export { Index, Listener };
9
+ const parseCID = cid => typeof cid === 'string' ? CID.parse(cid) : cid;
10
+ class Fireproof {
11
+ /**
12
+ * @function storage
13
+ * @memberof Fireproof
14
+ * Creates a new Fireproof instance with default storage settings
15
+ * Most apps should use this and not worry about the details.
16
+ * @static
17
+ * @returns {Fireproof} - a new Fireproof instance
18
+ */
19
+ static storage = (name = null, opts = {}) => {
20
+ if (name) {
21
+ opts.name = name;
22
+ const existing = localGet('fp.' + name);
23
+ if (existing) {
24
+ const existingConfig = JSON.parse(existing);
25
+ const fp = new Database(new TransactionBlockstore(name, existingConfig.key), [], opts);
26
+ return this.fromJSON(existingConfig, fp);
27
+ }
28
+ else {
29
+ const instanceKey = randomBytes(32).toString('hex'); // pass null to disable encryption
30
+ return new Database(new TransactionBlockstore(name, instanceKey), [], opts);
31
+ }
32
+ }
33
+ else {
34
+ return new Database(new TransactionBlockstore(), [], opts);
35
+ }
36
+ };
37
+ static fromJSON(json, database) {
38
+ database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key });
39
+ if (json.indexes) {
40
+ for (const { name, code, clock: { byId, byKey, db } } of json.indexes) {
41
+ Index.fromJSON(database, {
42
+ clock: {
43
+ byId: byId ? parseCID(byId) : null,
44
+ byKey: byKey ? parseCID(byKey) : null,
45
+ db: db ? db.map(c => parseCID(c)) : null
46
+ },
47
+ code,
48
+ name
49
+ });
50
+ }
51
+ }
52
+ return database;
53
+ }
54
+ static snapshot(database, clock) {
55
+ const definition = database.toJSON();
56
+ const withBlocks = new Database(database.blocks);
57
+ if (clock) {
58
+ definition.clock = clock.map(c => parseCID(c));
59
+ definition.indexes.forEach(index => {
60
+ index.clock.byId = null;
61
+ index.clock.byKey = null;
62
+ index.clock.db = null;
63
+ });
64
+ }
65
+ const snappedDb = this.fromJSON(definition, withBlocks);
66
+ ([...database.indexes.values()]).forEach(index => {
67
+ snappedDb.indexes.get(index.mapFnString).mapFn = index.mapFn;
68
+ });
69
+ return snappedDb;
70
+ }
71
+ static async zoom(database, clock) {
72
+ ;
73
+ ([...database.indexes.values()]).forEach(index => {
74
+ index.indexById = { root: null, cid: null };
75
+ index.indexByKey = { root: null, cid: null };
76
+ index.dbHead = null;
77
+ });
78
+ database.clock = clock.map(c => parseCID(c));
79
+ await database.notifyReset(); // hmm... indexes should listen to this? might be more complex than worth it. so far this is the only caller
80
+ return database;
81
+ }
82
+ }
83
+ export { Fireproof };
@@ -0,0 +1,100 @@
1
+ // @ts-ignore
2
+ import { useEffect, useState } from 'react';
3
+ import { Fireproof, Listener } from '../src/fireproof.js';
4
+ /**
5
+ @typedef {Object} FireproofCtxValue
6
+ @property {Function} addSubscriber - A function to add a subscriber with a label and function.
7
+ @property {Fireproof} database - An instance of the Fireproof class.
8
+ @property {boolean} ready - A boolean indicating whether the database is ready.
9
+ @param {string} label - A label for the subscriber.
10
+ @param {Function} fn - A function to be added as a subscriber.
11
+ @returns {void}
12
+ */
13
+ const inboundSubscriberQueue = new Map();
14
+ let startedSetup = false;
15
+ let database;
16
+ let listener;
17
+ const initializeDatabase = name => {
18
+ if (database)
19
+ return;
20
+ database = Fireproof.storage(name);
21
+ listener = new Listener(database);
22
+ };
23
+ /**
24
+
25
+ @function useFireproof
26
+ React hook to initialize a Fireproof database, automatically saving and loading the clock.
27
+ You might need to import { nodePolyfills } from 'vite-plugin-node-polyfills' in your vite.config.ts
28
+ @param {string} name - The path to the database file
29
+ @param {function(database): void} [defineDatabaseFn] - Synchronous function that defines the database, run this before any async calls
30
+ @param {function(database): Promise<void>} [setupDatabaseFn] - Asynchronous function that sets up the database, run this to load fixture data etc
31
+ @returns {FireproofCtxValue} { addSubscriber, database, ready }
32
+ */
33
+ export function useFireproof(name, defineDatabaseFn = () => { }, setupDatabaseFn = async () => { }) {
34
+ const [ready, setReady] = useState(false);
35
+ initializeDatabase(name || 'useFireproof');
36
+ const addSubscriber = (label, fn) => {
37
+ inboundSubscriberQueue.set(label, fn);
38
+ };
39
+ const listenerCallback = async (event) => {
40
+ if (event._external)
41
+ return;
42
+ for (const [, fn] of inboundSubscriberQueue)
43
+ fn();
44
+ };
45
+ useEffect(() => {
46
+ const doSetup = async () => {
47
+ if (ready)
48
+ return;
49
+ if (startedSetup)
50
+ return;
51
+ startedSetup = true;
52
+ defineDatabaseFn(database); // define indexes before querying them
53
+ if (database.clock.length === 0) {
54
+ await setupDatabaseFn(database);
55
+ }
56
+ setReady(true);
57
+ listener.on('*', listenerCallback); // hushed('*', listenerCallback, 250))
58
+ };
59
+ doSetup();
60
+ }, [ready]);
61
+ return {
62
+ addSubscriber,
63
+ database,
64
+ ready
65
+ };
66
+ }
67
+ // const husherMap = new Map()
68
+ // const husher = (id, workFn, ms) => {
69
+ // if (!husherMap.has(id)) {
70
+ // const start = Date.now()
71
+ // husherMap.set(
72
+ // id,
73
+ // workFn().finally(() => setTimeout(() => husherMap.delete(id), ms - (Date.now() - start)))
74
+ // )
75
+ // }
76
+ // return husherMap.get(id)
77
+ // }
78
+ // const hushed =
79
+ // (id, workFn, ms) =>
80
+ // (...args) =>
81
+ // husher(id, () => workFn(...args), ms)
82
+ // let storageSupported = false
83
+ // try {
84
+ // storageSupported = window.localStorage && true
85
+ // } catch (e) {}
86
+ // export function localGet (key) {
87
+ // if (storageSupported) {
88
+ // return localStorage && localStorage.getItem(key)
89
+ // }
90
+ // }
91
+ // function localSet (key, value) {
92
+ // if (storageSupported) {
93
+ // return localStorage && localStorage.setItem(key, value)
94
+ // }
95
+ // }
96
+ // function localRemove(key) {
97
+ // if (storageSupported) {
98
+ // return localStorage && localStorage.removeItem(key)
99
+ // }
100
+ // }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * A Fireproof database Listener allows you to react to events in the database.
3
+ *
4
+ * @class Listener
5
+ * @classdesc An listener attaches to a Fireproof database and runs a routing function on each change, sending the results to subscribers.
6
+ *
7
+ * @param {import('./database.js').Database} database - The Database database instance to index.
8
+ * @param {Function} routingFn - The routing function to apply to each entry in the database.
9
+ */
10
+ // import { ChangeEvent } from './db-index'
11
+ export class Listener {
12
+ subcribers = new Map();
13
+ doStopListening = null;
14
+ /**
15
+ * @param {import('./database.js').Database} database
16
+ * @param {(_: any, emit: any) => void} routingFn
17
+ */
18
+ constructor(database, routingFn) {
19
+ this.database = database;
20
+ this.doStopListening = database.registerListener((/** @type {any} */ changes) => this.onChanges(changes));
21
+ /**
22
+ * The map function to apply to each entry in the database.
23
+ * @type {Function}
24
+ */
25
+ this.routingFn =
26
+ routingFn ||
27
+ function (/** @type {any} */ _, /** @type {(arg0: string) => void} */ emit) {
28
+ emit('*');
29
+ };
30
+ this.dbHead = null;
31
+ }
32
+ /**
33
+ * Subscribe to a topic emitted by the event function.
34
+ * @param {string} topic - The topic to subscribe to.
35
+ * @param {Function} subscriber - The function to call when the topic is emitted.
36
+ * @returns {Function} A function to unsubscribe from the topic.
37
+ * @memberof Listener
38
+ * @instance
39
+ * @param {any} since
40
+ */
41
+ on(topic, subscriber, since) {
42
+ const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
43
+ listOfTopicSubscribers.push(subscriber);
44
+ if (typeof since !== 'undefined') {
45
+ this.database.changesSince(since).then(({ rows: changes }) => {
46
+ const keys = topicsForChanges(changes, this.routingFn).get(topic);
47
+ if (keys)
48
+ keys.forEach((/** @type {any} */ key) => subscriber(key));
49
+ });
50
+ }
51
+ return () => {
52
+ const index = listOfTopicSubscribers.indexOf(subscriber);
53
+ if (index > -1)
54
+ listOfTopicSubscribers.splice(index, 1);
55
+ };
56
+ }
57
+ /**
58
+ * @typedef {import('./db-index').ChangeEvent} ChangeEvent
59
+ */
60
+ /**
61
+ * @param {ChangeEvent[]} changes
62
+ */
63
+ onChanges(changes) {
64
+ if (Array.isArray(changes)) {
65
+ const seenTopics = topicsForChanges(changes, this.routingFn);
66
+ for (const [topic, keys] of seenTopics) {
67
+ const listOfTopicSubscribers = getTopicList(this.subcribers, topic);
68
+ listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) => keys.forEach((/** @type {any} */ key) => subscriber(key)));
69
+ }
70
+ }
71
+ else {
72
+ // non-arrays go to all subscribers
73
+ for (const [, listOfTopicSubscribers] of this.subcribers) {
74
+ listOfTopicSubscribers.forEach((/** @type {(arg0: any) => any} */ subscriber) => subscriber(changes));
75
+ }
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * @param {Map<any, any>} subscribersMap
81
+ * @param {string} name
82
+ */
83
+ function getTopicList(subscribersMap, name) {
84
+ let topicList = subscribersMap.get(name);
85
+ if (!topicList) {
86
+ topicList = [];
87
+ subscribersMap.set(name, topicList);
88
+ }
89
+ return topicList;
90
+ }
91
+ /**
92
+ * Transforms a set of changes to events using an emitter function.
93
+ *
94
+ * @param {ChangeEvent[]} changes
95
+ * @param {Function} routingFn
96
+ * @returns {Map<string,string[]>} The topics emmitted by the event function.
97
+ * @private
98
+ */
99
+ const topicsForChanges = (changes, routingFn) => {
100
+ const seenTopics = new Map();
101
+ changes.forEach(({ key, value, del }) => {
102
+ if (del || !value)
103
+ value = { _deleted: true };
104
+ routingFn({ _id: key, ...value }, (/** @type {any} */ t) => {
105
+ const topicList = getTopicList(seenTopics, t);
106
+ topicList.push(key);
107
+ });
108
+ });
109
+ return seenTopics;
110
+ };
package/dist/prolly.js ADDED
@@ -0,0 +1,316 @@
1
+ import { advance, EventFetcher, EventBlock, findCommonAncestorWithSortedEvents, findEventsToSync, vis as visClock } from './clock.js';
2
+ // import { create, load } from '../../../../prolly-trees/src/map.js'
3
+ // @ts-ignore
4
+ import { create, load } from 'prolly-trees/map';
5
+ // @ts-ignore
6
+ import { nocache as cache } from 'prolly-trees/cache';
7
+ // @ts-ignore
8
+ import { CIDCounter, bf, simpleCompare as compare } from 'prolly-trees/utils';
9
+ import * as codec from '@ipld/dag-cbor';
10
+ import { sha256 as hasher } from 'multiformats/hashes/sha2';
11
+ import { doTransaction } from './blockstore.js';
12
+ import { create as createBlock } from 'multiformats/block';
13
+ const blockOpts = { cache, chunker: bf(3), codec, hasher, compare };
14
+ const withLog = async (label, fn) => {
15
+ const resp = await fn();
16
+ // console.log('withLog', label, !!resp)
17
+ return resp;
18
+ };
19
+ // should also return a CIDCounter
20
+ export const makeGetBlock = (blocks) => {
21
+ // const cids = new CIDCounter() // this could be used for proofs of mutations
22
+ const getBlockFn = async (address) => {
23
+ const { cid, bytes } = await withLog(address, () => blocks.get(address));
24
+ // cids.add({ address: cid })
25
+ return createBlock({ cid, bytes, hasher, codec });
26
+ };
27
+ return {
28
+ // cids,
29
+ getBlock: getBlockFn
30
+ };
31
+ };
32
+ /**
33
+ *
34
+ * @param {*} param0
35
+ * @returns
36
+ */
37
+ async function createAndSaveNewEvent({ inBlocks, bigPut, root, event: inEvent, head, additions, removals = [] }) {
38
+ let cids;
39
+ const { key, value, del } = inEvent;
40
+ const data = {
41
+ root: (root
42
+ ? {
43
+ cid: root.cid,
44
+ bytes: root.bytes,
45
+ value: root.value // can we remove this?
46
+ }
47
+ : null),
48
+ key
49
+ };
50
+ // import('./clock').EventLink<import('./clock').EventData>
51
+ if (del) {
52
+ data.value = null;
53
+ data.type = 'del';
54
+ }
55
+ else {
56
+ data.value = value;
57
+ data.type = 'put';
58
+ }
59
+ /** @type {import('./clock').EventData} */
60
+ // @ts-ignore
61
+ const event = await EventBlock.create(data, head);
62
+ bigPut(event);
63
+ ({ head, cids } = await advance(inBlocks, head, event.cid));
64
+ return {
65
+ root,
66
+ additions,
67
+ removals,
68
+ head,
69
+ clockCIDs: cids,
70
+ event
71
+ };
72
+ }
73
+ const makeGetAndPutBlock = (inBlocks) => {
74
+ // const mblocks = new MemoryBlockstore()
75
+ // const blocks = new MultiBlockFetcher(mblocks, inBlocks)
76
+ const { getBlock, cids } = makeGetBlock(inBlocks);
77
+ const put = inBlocks.put.bind(inBlocks);
78
+ const bigPut = async (block, additions) => {
79
+ // console.log('bigPut', block.cid.toString())
80
+ const { cid, bytes } = block;
81
+ put(cid, bytes);
82
+ // mblocks.putSync(cid, bytes)
83
+ if (additions) {
84
+ additions.set(cid.toString(), block);
85
+ }
86
+ };
87
+ return { getBlock, bigPut, blocks: inBlocks, cids };
88
+ };
89
+ const bulkFromEvents = (sorted, event) => {
90
+ if (event) {
91
+ const update = { value: { data: { key: event.key } } };
92
+ if (event.del) {
93
+ update.value.data.type = 'del';
94
+ }
95
+ else {
96
+ update.value.data.type = 'put';
97
+ update.value.data.value = event.value;
98
+ }
99
+ sorted.push(update);
100
+ }
101
+ const bulk = new Map();
102
+ for (const { value: event } of sorted) {
103
+ const { data: { type, value, key } } = event;
104
+ const bulkEvent = type === 'put' ? { key, value } : { key, del: true };
105
+ bulk.set(bulkEvent.key, bulkEvent); // last wins
106
+ }
107
+ return Array.from(bulk.values());
108
+ };
109
+ // Get the value of the root from the ancestor event
110
+ /**
111
+ *
112
+ * @param {EventFetcher} events
113
+ * @param {import('./clock').EventLink<import('./clock').EventData>} ancestor
114
+ * @param {*} getBlock
115
+ * @returns
116
+ */
117
+ const prollyRootFromAncestor = async (events, ancestor, getBlock) => {
118
+ // console.log('prollyRootFromAncestor', ancestor)
119
+ const event = await events.get(ancestor);
120
+ const { root } = event.value.data;
121
+ // console.log('prollyRootFromAncestor', root.cid, JSON.stringify(root.value))
122
+ if (root) {
123
+ return load({ cid: root.cid, get: getBlock, ...blockOpts });
124
+ }
125
+ else {
126
+ return null;
127
+ }
128
+ };
129
+ const doProllyBulk = async (inBlocks, head, event) => {
130
+ const { getBlock, blocks } = makeGetAndPutBlock(inBlocks);
131
+ let bulkSorted = [];
132
+ let prollyRootNode = null;
133
+ if (head.length) {
134
+ // Otherwise, we find the common ancestor and update the root and other blocks
135
+ const events = new EventFetcher(blocks);
136
+ // todo this is returning more events than necessary, lets define the desired semantics from the top down
137
+ // good semantics mean we can cache the results of this call
138
+ const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head);
139
+ bulkSorted = sorted;
140
+ // console.log('sorted', JSON.stringify(sorted.map(({ value: { data: { key, value } } }) => ({ key, value }))))
141
+ prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock);
142
+ // console.log('event', event)
143
+ }
144
+ const bulkOperations = bulkFromEvents(bulkSorted, event);
145
+ // if prolly root node is null, we need to create a new one
146
+ if (!prollyRootNode) {
147
+ let root;
148
+ const newBlocks = [];
149
+ // if all operations are deletes, we can just return an empty root
150
+ if (bulkOperations.every((op) => op.del)) {
151
+ return { root: null, blocks: [] };
152
+ }
153
+ for await (const node of create({ get: getBlock, list: bulkOperations, ...blockOpts })) {
154
+ root = await node.block;
155
+ newBlocks.push(root);
156
+ }
157
+ return { root, blocks: newBlocks };
158
+ }
159
+ else {
160
+ return await prollyRootNode.bulk(bulkOperations); // { root: newProllyRootNode, blocks: newBlocks }
161
+ }
162
+ };
163
+ /**
164
+ * Put a value (a CID) for the given key. If the key exists it's value is overwritten.
165
+ *
166
+ * @param {import('./blockstore.js').Blockstore} inBlocks Bucket block storage.
167
+ * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
168
+ * @param {{key: string, value: import('./clock').EventLink<import('./clock').EventData>}} event The key of the value to put.
169
+ * @param {object} [options]
170
+ * @returns {Promise<any>}
171
+ */
172
+ export async function put(inBlocks, head, event, options) {
173
+ const { bigPut } = makeGetAndPutBlock(inBlocks);
174
+ // If the head is empty, we create a new event and return the root and addition blocks
175
+ if (!head.length) {
176
+ const additions = new Map();
177
+ const { root, blocks } = await doProllyBulk(inBlocks, head, event);
178
+ for (const b of blocks) {
179
+ bigPut(b, additions);
180
+ }
181
+ return createAndSaveNewEvent({ inBlocks, bigPut, root, event, head, additions: Array.from(additions.values()) });
182
+ }
183
+ const { root: newProllyRootNode, blocks: newBlocks } = await doProllyBulk(inBlocks, head, event);
184
+ if (!newProllyRootNode) {
185
+ return createAndSaveNewEvent({
186
+ inBlocks,
187
+ bigPut,
188
+ root: null,
189
+ event,
190
+ head,
191
+ additions: []
192
+ });
193
+ }
194
+ else {
195
+ const prollyRootBlock = await newProllyRootNode.block;
196
+ const additions = new Map(); // ; const removals = new Map()
197
+ bigPut(prollyRootBlock, additions);
198
+ for (const nb of newBlocks) {
199
+ bigPut(nb, additions);
200
+ }
201
+ // additions are new blocks
202
+ return createAndSaveNewEvent({
203
+ inBlocks,
204
+ bigPut,
205
+ root: prollyRootBlock,
206
+ event,
207
+ head,
208
+ additions: Array.from(additions.values()) /*, todo? Array.from(removals.values()) */
209
+ });
210
+ }
211
+ }
212
+ /**
213
+ * Determine the effective prolly root given the current merkle clock head.
214
+ *
215
+ * @param {import('./blockstore.js').TransactionBlockstore} inBlocks Bucket block storage.
216
+ * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
217
+ */
218
+ export async function root(inBlocks, head) {
219
+ if (!head.length) {
220
+ throw new Error('no head');
221
+ }
222
+ const { root: newProllyRootNode, blocks: newBlocks, cids } = await doProllyBulk(inBlocks, head);
223
+ // todo maybe these should go to a temp blockstore?
224
+ await doTransaction('root', inBlocks, async (transactionBlockstore) => {
225
+ const { bigPut } = makeGetAndPutBlock(transactionBlockstore);
226
+ for (const nb of newBlocks) {
227
+ bigPut(nb);
228
+ }
229
+ });
230
+ return { cids, node: newProllyRootNode };
231
+ }
232
+ /**
233
+ * Get the list of events not known by the `since` event
234
+ * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
235
+ * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
236
+ * @param {import('./clock').EventLink<import('./clock').EventData>} since Event to compare against.
237
+ * @returns {Promise<{clockCIDs: CIDCounter, result: import('./clock').EventData[]}>}
238
+ */
239
+ export async function eventsSince(blocks, head, since) {
240
+ if (!head.length) {
241
+ throw new Error('no head');
242
+ }
243
+ // @ts-ignore
244
+ const sinceHead = [...since, ...head]; // ?
245
+ const { cids, events: unknownSorted3 } = await findEventsToSync(blocks, sinceHead);
246
+ return { clockCIDs: cids, result: unknownSorted3.map(({ value: { data } }) => data) };
247
+ }
248
+ /**
249
+ *
250
+ * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
251
+ * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
252
+ *
253
+ * @returns {Promise<{cids: CIDCounter, clockCIDs: CIDCounter, result: import('./clock').EventData[]}>}
254
+ *
255
+ */
256
+ export async function getAll(blocks, head) {
257
+ // todo use the root node left around from put, etc
258
+ // move load to a central place
259
+ if (!head.length) {
260
+ return { clockCIDs: new CIDCounter(), cids: new CIDCounter(), result: [] };
261
+ }
262
+ const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head);
263
+ if (!prollyRootNode) {
264
+ return { clockCIDs, cids: new CIDCounter(), result: [] };
265
+ }
266
+ const { result, cids } = await prollyRootNode.getAllEntries(); // todo params
267
+ return { clockCIDs, cids, result: result.map(({ key, value }) => ({ key, value })) };
268
+ }
269
+ /**
270
+ * @param {import('./blockstore.js').TransactionBlockstore} blocks Bucket block storage.
271
+ * @param {import('./clock').EventLink<import('./clock').EventData>[]} head Merkle clock head.
272
+ * @param {string} key The key of the value to retrieve.
273
+ */
274
+ export async function get(blocks, head, key) {
275
+ // instead pass root from db? and always update on change
276
+ if (!head.length) {
277
+ return { cids: new CIDCounter(), result: null };
278
+ }
279
+ const { node: prollyRootNode, cids: clockCIDs } = await root(blocks, head);
280
+ if (!prollyRootNode) {
281
+ return { clockCIDs, cids: new CIDCounter(), result: null };
282
+ }
283
+ const { result, cids } = await prollyRootNode.get(key);
284
+ return { result, cids, clockCIDs };
285
+ }
286
+ export async function* vis(blocks, head) {
287
+ if (!head.length) {
288
+ return { cids: new CIDCounter(), result: null };
289
+ }
290
+ const { node: prollyRootNode, cids } = await root(blocks, head);
291
+ const lines = [];
292
+ for await (const line of prollyRootNode.vis()) {
293
+ yield line;
294
+ lines.push(line);
295
+ }
296
+ return { vis: lines.join('\n'), cids };
297
+ }
298
+ export async function visMerkleTree(blocks, head) {
299
+ if (!head.length) {
300
+ return { cids: new CIDCounter(), result: null };
301
+ }
302
+ const { node: prollyRootNode, cids } = await root(blocks, head);
303
+ const lines = [];
304
+ for await (const line of prollyRootNode.vis()) {
305
+ lines.push(line);
306
+ }
307
+ return { vis: lines.join('\n'), cids };
308
+ }
309
+ export async function visMerkleClock(blocks, head) {
310
+ const lines = [];
311
+ for await (const line of visClock(blocks, head)) {
312
+ // yield line
313
+ lines.push(line);
314
+ }
315
+ return { vis: lines.join('\n') };
316
+ }
package/dist/sha1.js ADDED
@@ -0,0 +1,74 @@
1
+ // @ts-nocheck
2
+ // from https://github.com/duzun/sync-sha1/blob/master/rawSha1.js
3
+ // MIT License Copyright (c) 2020 Dumitru Uzun
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ // SOFTWARE.
19
+ // import {
20
+ // isLittleEndian, switchEndianness32
21
+ // } from 'string-encode'
22
+ /**
23
+ * SHA1 on binary array
24
+ *
25
+ * @param {Uint8Array} b Data to hash
26
+ *
27
+ * @return {Uint8Array} sha1 hash
28
+ */
29
+ export function rawSha1(b) {
30
+ let i = b.byteLength;
31
+ let bs = 0;
32
+ let A;
33
+ let B;
34
+ let C;
35
+ let D;
36
+ let G;
37
+ const H = Uint32Array.from([A = 0x67452301, B = 0xEFCDAB89, ~A, ~B, 0xC3D2E1F0]);
38
+ const W = new Uint32Array(80);
39
+ const nrWords = (i / 4 + 2) | 15;
40
+ const words = new Uint32Array(nrWords + 1);
41
+ let j;
42
+ words[nrWords] = i * 8;
43
+ words[i >> 2] |= 0x80 << (~i << 3);
44
+ for (; i--;) {
45
+ words[i >> 2] |= b[i] << (~i << 3);
46
+ }
47
+ for (A = H.slice(); bs < nrWords; bs += 16, A.set(H)) {
48
+ for (i = 0; i < 80; A[0] = (G = ((b = A[0]) << 5 | b >>> 27) +
49
+ A[4] +
50
+ (W[i] = (i < 16) ? words[bs + i] : G << 1 | G >>> 31) +
51
+ 0x5A827999,
52
+ B = A[1],
53
+ C = A[2],
54
+ D = A[3],
55
+ G + ((j = i / 5 >> 2) // eslint-disable-line no-cond-assign
56
+ ? j !== 2
57
+ ? (B ^ C ^ D) + (j & 2 ? 0x6FE0483D : 0x14577208)
58
+ : (B & C | B & D | C & D) + 0x34994343
59
+ : B & C | ~B & D))
60
+ , A[1] = b
61
+ , A[2] = B << 30 | B >>> 2
62
+ , A[3] = C
63
+ , A[4] = D
64
+ , ++i) {
65
+ G = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
66
+ }
67
+ for (i = 5; i;)
68
+ H[--i] = H[i] + A[i];
69
+ }
70
+ // if (isLittleEndian()) {
71
+ // H = H.map(switchEndianness32)
72
+ // }
73
+ return new Uint8Array(H.buffer, H.byteOffset, H.byteLength);
74
+ }