@fireproof/core 0.6.5 → 0.7.0-alpha.1

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 (47) hide show
  1. package/README.md +2 -2
  2. package/dist/{src/blockstore.js → blockstore.js} +7 -3
  3. package/dist/{src/database.js → database.js} +74 -48
  4. package/dist/{src/db-index.js → db-index.js} +2 -1
  5. package/dist/fireproof.js +81 -0
  6. package/dist/{src/loader.js → loader.js} +2 -3
  7. package/dist/{src/prolly.js → prolly.js} +1 -0
  8. package/dist/src/fireproof.d.ts +135 -134
  9. package/dist/src/fireproof.js +18168 -11485
  10. package/dist/src/fireproof.js.map +1 -1
  11. package/dist/src/fireproof.mjs +18169 -11485
  12. package/dist/src/fireproof.mjs.map +1 -1
  13. package/dist/{src/storage → storage}/filesystem.js +2 -2
  14. package/dist/{src/sync.js → sync.js} +1 -1
  15. package/dist/valet.js +200 -0
  16. package/package.json +4 -5
  17. package/src/blockstore.js +6 -3
  18. package/src/database.js +80 -52
  19. package/src/db-index.js +2 -1
  20. package/src/fireproof.js +30 -31
  21. package/src/import.js +34 -0
  22. package/src/loader.js +26 -0
  23. package/src/prolly.js +2 -0
  24. package/src/storage/base.js +371 -0
  25. package/src/storage/browser.js +67 -0
  26. package/src/storage/filesystem.js +70 -0
  27. package/src/storage/rest.js +60 -0
  28. package/src/storage/ucan.js +0 -0
  29. package/src/sync.js +1 -1
  30. package/src/valet.js +57 -359
  31. package/dist/hooks/use-fireproof.js +0 -150
  32. package/dist/src/crypto-poly.js +0 -4
  33. package/dist/src/link.js +0 -1
  34. package/dist/src/valet.js +0 -476
  35. package/hooks/use-fireproof.js +0 -173
  36. package/src/listener.js +0 -119
  37. package/src/utils.js +0 -16
  38. /package/dist/{src/clock.js → clock.js} +0 -0
  39. /package/dist/{src/crypto.js → crypto.js} +0 -0
  40. /package/dist/{src/import.js → import.js} +0 -0
  41. /package/dist/{src/listener.js → listener.js} +0 -0
  42. /package/dist/{src/sha1.js → sha1.js} +0 -0
  43. /package/dist/{src/storage → storage}/base.js +0 -0
  44. /package/dist/{src/storage → storage}/browser.js +0 -0
  45. /package/dist/{src/storage → storage}/rest.js +0 -0
  46. /package/dist/{src/storage → storage}/ucan.js +0 -0
  47. /package/dist/{src/utils.js → utils.js} +0 -0
package/dist/src/valet.js DELETED
@@ -1,476 +0,0 @@
1
- import { CarReader } from '@ipld/car';
2
- import { CID } from 'multiformats/cid';
3
- import { sha256 } from 'multiformats/hashes/sha2';
4
- import { parse } from 'multiformats/link';
5
- import * as CBW from '@ipld/car/buffer-writer';
6
- import * as raw from 'multiformats/codecs/raw';
7
- import * as Block from 'multiformats/block';
8
- import * as dagcbor from '@ipld/dag-cbor';
9
- import { openDB } from 'idb';
10
- import cargoQueue from 'async/cargoQueue.js';
11
- // @ts-ignore
12
- // @ts-ignore
13
- import { bf, simpleCompare as compare } from 'prolly-trees/utils';
14
- // @ts-ignore
15
- import { nocache as cache } from 'prolly-trees/cache';
16
- // import { makeGetBlock } from './prolly.js'
17
- import { encrypt, decrypt } from './crypto.js';
18
- import { Buffer } from 'buffer';
19
- // @ts-ignore
20
- import * as codec from 'encrypted-block';
21
- import { create, load } from 'ipld-hashmap';
22
- import { rawSha1 as sha1sync } from './sha1.js';
23
- const chunker = bf(30);
24
- const blockOpts = { cache, chunker, codec: dagcbor, hasher: sha256, compare };
25
- const NO_ENCRYPT = typeof process !== 'undefined' && !!process.env?.NO_ENCRYPT;
26
- // ? process.env.NO_ENCRYPT : import.meta && import.meta.env.VITE_NO_ENCRYPT
27
- export class Valet {
28
- idb = null;
29
- name = null;
30
- uploadQueue = null;
31
- alreadyEnqueued = new Set();
32
- keyMaterial = null;
33
- keyId = 'null';
34
- valetRoot = null;
35
- valetRootCid = null; // set by hydrate
36
- valetRootCarCid = null; // most recent diff
37
- valetCidBlocks = new VMemoryBlockstore();
38
- instanceId = Math.random().toString(36).slice(2);
39
- /**
40
- * Function installed by the database to upload car files
41
- * @type {null|function(string, Uint8Array):Promise<void>}
42
- */
43
- uploadFunction = null;
44
- constructor(name = 'default', keyMaterial) {
45
- this.name = name;
46
- this.setKeyMaterial(keyMaterial);
47
- this.uploadQueue = cargoQueue(async (tasks, callback) => {
48
- // console.log(
49
- // 'queue worker',
50
- // tasks.length,
51
- // tasks.reduce((acc, t) => acc + t.value.length, 0)
52
- // )
53
- if (this.uploadFunction) {
54
- // todo we can coalesce these into a single car file
55
- // todo remove idb usage here
56
- for (const task of tasks) {
57
- await this.uploadFunction(task.carCid, task.value);
58
- // todo update syncCidMap to say this has been synced
59
- // const carMeta = await db.get('cidToCar', task.carCid)
60
- // delete carMeta.pending
61
- // await db.put('cidToCar', carMeta)
62
- }
63
- }
64
- callback();
65
- });
66
- this.uploadQueue.drain(async () => {
67
- // todo read syncCidMap and sync any that are still unsynced
68
- // return await this.withDB(async db => {
69
- // const carKeys = (await db.getAllFromIndex('cidToCar', 'pending')).map(c => c.car)
70
- // for (const carKey of carKeys) {
71
- // await this.uploadFunction(carKey, await db.get('cars', carKey))
72
- // const carMeta = await db.get('cidToCar', carKey)
73
- // delete carMeta.pending
74
- // await db.put('cidToCar', carMeta)
75
- // }
76
- // })
77
- });
78
- }
79
- getKeyMaterial() {
80
- return this.keyMaterial;
81
- }
82
- setKeyMaterial(km) {
83
- if (km && !NO_ENCRYPT) {
84
- const hex = Uint8Array.from(Buffer.from(km, 'hex'));
85
- this.keyMaterial = km;
86
- const hash = sha1sync(hex);
87
- this.keyId = Buffer.from(hash).toString('hex');
88
- }
89
- else {
90
- this.keyMaterial = null;
91
- this.keyId = 'null';
92
- }
93
- // console.trace('keyId', this.name, this.keyId)
94
- }
95
- /**
96
- * Group the blocks into a car and write it to the valet.
97
- * @param {import('./blockstore.js').InnerBlockstore} innerBlockstore
98
- * @param {Set<string>} cids
99
- * @returns {Promise<void>}
100
- * @memberof Valet
101
- */
102
- async writeTransaction(innerBlockstore, cids) {
103
- if (innerBlockstore.lastCid) {
104
- if (this.keyMaterial) {
105
- // console.log('encrypting car', innerBlockstore.label)
106
- // should we pass cids in instead of iterating frin innerBlockstore?
107
- const newCar = await blocksToEncryptedCarBlock(innerBlockstore.lastCid, innerBlockstore, this.keyMaterial);
108
- await this.parkCar(newCar.cid.toString(), newCar.bytes, cids);
109
- }
110
- else {
111
- const newCar = await blocksToCarBlock(innerBlockstore.lastCid, innerBlockstore);
112
- await this.parkCar(newCar.cid.toString(), newCar.bytes, cids);
113
- }
114
- }
115
- else {
116
- throw new Error('missing lastCid for car header');
117
- }
118
- }
119
- withDB = async (dbWorkFun) => {
120
- if (!this.idb) {
121
- this.idb = await openDB(`fp.${this.keyId}.${this.name}.valet`, 3, {
122
- upgrade(db, oldVersion, newVersion, transaction) {
123
- if (oldVersion < 1) {
124
- db.createObjectStore('cars');
125
- }
126
- }
127
- });
128
- }
129
- return await dbWorkFun(this.idb);
130
- };
131
- /**
132
- * Iterate over all blocks in the store.
133
- *
134
- * @yields {{cid: string, value: Uint8Array}}
135
- * @returns {AsyncGenerator<any, any, any>}
136
- */
137
- async *cids() {
138
- // console.log('valet cids')
139
- // todo use cidMap
140
- // while (cursor) {
141
- // yield { cid: cursor.key, car: cursor.value.car }
142
- // cursor = await cursor.continue()
143
- // }
144
- }
145
- setRootCarCid(cid) {
146
- this.valetRootCarCid = cid;
147
- this.valetRoot = null;
148
- this.valetRootCid = null;
149
- }
150
- // todo memoize this
151
- async getCarCIDForCID(cid) {
152
- // make a car reader for this.valetRootCarCid
153
- if (!this.valetRootCarCid)
154
- return { result: null };
155
- let indexNode;
156
- if (this.valetRoot) {
157
- indexNode = this.valetRoot;
158
- }
159
- else {
160
- const combinedReader = await this.getCombinedReader(this.valetRootCarCid);
161
- if (!this.valetRootCid) {
162
- const root = combinedReader.root.cid;
163
- // console.log('roots', this.instanceId, this.name, root, this.valetRootCarCid, this.valetRootCid)
164
- this.valetRootCid = root;
165
- }
166
- indexNode = await load(combinedReader, this.valetRootCid, {
167
- blockHasher: blockOpts.hasher,
168
- blockCodec: blockOpts.codec
169
- });
170
- }
171
- const got = await indexNode.get(cid);
172
- // console.log('getCarCIDForCID', cid, got)
173
- return { result: got };
174
- }
175
- async getCombinedReader(carCid) {
176
- let carMapReader;
177
- if (this.valetRootCarCid) {
178
- // todo only need this if we are cold starting
179
- carMapReader = await this.getCarReader(this.valetRootCarCid);
180
- }
181
- const theseValetCidBlocks = this.valetCidBlocks;
182
- // console.log('theseValetCidBlocks', theseValetCidBlocks)
183
- const combinedReader = {
184
- root: carMapReader?.root,
185
- put: async (cid, bytes) => {
186
- // console.log('mapPut', cid, bytes.length)
187
- return await theseValetCidBlocks.put(cid, bytes);
188
- },
189
- get: async (cid) => {
190
- // console.log('mapGet', cid)
191
- try {
192
- const got = await theseValetCidBlocks.get(cid);
193
- return got.bytes;
194
- }
195
- catch (e) {
196
- // console.log('get from car', cid, carMapReader)
197
- if (!carMapReader)
198
- throw e;
199
- const bytes = await carMapReader.get(cid);
200
- await theseValetCidBlocks.put(cid, bytes);
201
- // console.log('mapGet', cid, bytes.length, bytes.constructor.name)
202
- return bytes;
203
- }
204
- }
205
- };
206
- return combinedReader;
207
- }
208
- /**
209
- *
210
- * @param {string} carCid
211
- * @param {*} value
212
- */
213
- async parkCar(carCid, value, cids) {
214
- // console.log('parkCar', this.instanceId, this.name, carCid, cids)
215
- const combinedReader = await this.getCombinedReader(carCid);
216
- const mapNode = await addCidsToCarIndex(combinedReader, this.valetRoot, this.valetRootCid, Array.from(cids).map(cid => ({ key: cid.toString(), value: carCid.toString() })));
217
- this.valetRoot = mapNode;
218
- this.valetRootCid = mapNode.cid;
219
- // make a block set with all the cids of the map
220
- const saveValetBlocks = new VMemoryBlockstore(); // todo this blockstore should read from the last valetCid car also
221
- for await (const cidx of mapNode.cids()) {
222
- const bytes = await combinedReader.get(cidx);
223
- saveValetBlocks.put(cidx, bytes);
224
- }
225
- let newValetCidCar;
226
- if (this.keyMaterial) {
227
- newValetCidCar = await blocksToEncryptedCarBlock(this.valetRootCid, saveValetBlocks, this.keyMaterial);
228
- }
229
- else {
230
- newValetCidCar = await blocksToCarBlock(this.valetRootCid, saveValetBlocks);
231
- }
232
- // console.log('newValetCidCar', this.name, Math.floor(newValetCidCar.bytes.length / 1024))
233
- await this.writeCars([
234
- {
235
- cid: carCid,
236
- bytes: value,
237
- replaces: null
238
- },
239
- {
240
- cid: newValetCidCar.cid,
241
- bytes: newValetCidCar.bytes,
242
- replaces: null
243
- // replaces: this.valetRootCarCid // todo
244
- }
245
- ]);
246
- this.valetRootCarCid = newValetCidCar.cid; // goes to clock
247
- // console.log('parked car', carCid, value.length, Array.from(cids))
248
- // upload to web3.storage if we have credentials
249
- if (this.uploadFunction) {
250
- if (this.alreadyEnqueued.has(carCid)) {
251
- // console.log('already enqueued', carCid)
252
- return;
253
- }
254
- // don't await this, it will be done in the queue
255
- // console.log('add to queue', carCid, value.length)
256
- this.uploadQueue.push({ carCid, value });
257
- this.alreadyEnqueued.add(carCid);
258
- }
259
- else {
260
- // console.log('no upload function', carCid, value.length, this.uploadFunction)
261
- }
262
- }
263
- async writeCars(cars) {
264
- return await this.withDB(async (db) => {
265
- const tx = db.transaction(['cars'], 'readwrite');
266
- for (const { cid, bytes, replaces } of cars) {
267
- await tx.objectStore('cars').put(bytes, cid.toString());
268
- // todo remove old maps
269
- if (replaces) {
270
- await tx.objectStore('cars').delete(replaces.toString());
271
- }
272
- }
273
- return await tx.done;
274
- });
275
- }
276
- remoteBlockFunction = null;
277
- async getCarReader(carCid) {
278
- carCid = carCid.toString();
279
- const carBytes = await this.withDB(async (db) => {
280
- const tx = db.transaction(['cars'], 'readonly');
281
- // console.log('getCarReader', carCid)
282
- return await tx.objectStore('cars').get(carCid);
283
- });
284
- const reader = await CarReader.fromBytes(carBytes);
285
- if (this.keyMaterial) {
286
- const roots = await reader.getRoots();
287
- const readerGetWithCodec = async (cid) => {
288
- const got = await reader.get(cid);
289
- // console.log('got.', cid.toString())
290
- let useCodec = codec;
291
- if (cid.toString().indexOf('bafy') === 0) {
292
- // todo cleanup types
293
- useCodec = dagcbor;
294
- }
295
- const decoded = await Block.decode({
296
- ...got,
297
- codec: useCodec,
298
- hasher: sha256
299
- });
300
- // console.log('decoded', decoded.value)
301
- return decoded;
302
- };
303
- const { blocks } = await blocksFromEncryptedCarBlock(roots[0], readerGetWithCodec, this.keyMaterial);
304
- // last block is the root ??? todo
305
- const rootBlock = blocks[blocks.length - 1];
306
- return {
307
- root: rootBlock,
308
- get: async (dataCID) => {
309
- // console.log('getCarReader dataCID', dataCID)
310
- dataCID = dataCID.toString();
311
- const block = blocks.find(b => b.cid.toString() === dataCID);
312
- // console.log('getCarReader block', block)
313
- if (block) {
314
- return block.bytes;
315
- }
316
- }
317
- };
318
- }
319
- else {
320
- return {
321
- root: reader.getRoots()[0],
322
- get: async (dataCID) => {
323
- const gotBlock = await reader.get(CID.parse(dataCID));
324
- if (gotBlock) {
325
- return gotBlock.bytes;
326
- }
327
- }
328
- };
329
- }
330
- }
331
- // todo memoize this
332
- async getValetBlock(dataCID) {
333
- // console.log('get valet block', dataCID)
334
- const { result: carCid } = await this.getCarCIDForCID(dataCID);
335
- if (!carCid) {
336
- throw new Error('Missing block: ' + dataCID);
337
- }
338
- const reader = await this.getCarReader(carCid);
339
- return await reader.get(dataCID);
340
- }
341
- }
342
- export const blocksToCarBlock = async (rootCids, blocks) => {
343
- // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
344
- let size = 0;
345
- if (!Array.isArray(rootCids)) {
346
- rootCids = [rootCids];
347
- }
348
- const headerSize = CBW.headerLength({ roots: rootCids });
349
- size += headerSize;
350
- if (!Array.isArray(blocks)) {
351
- blocks = Array.from(blocks.entries());
352
- }
353
- for (const { cid, bytes } of blocks) {
354
- // console.log(cid, bytes)
355
- size += CBW.blockLength({ cid, bytes });
356
- }
357
- const buffer = new Uint8Array(size);
358
- const writer = await CBW.createWriter(buffer, { headerSize });
359
- for (const cid of rootCids) {
360
- writer.addRoot(cid);
361
- }
362
- for (const { cid, bytes } of blocks) {
363
- writer.write({ cid, bytes });
364
- }
365
- await writer.close();
366
- return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
367
- };
368
- export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial) => {
369
- const encryptionKey = Buffer.from(keyMaterial, 'hex');
370
- const encryptedBlocks = [];
371
- const theCids = [];
372
- for (const { cid } of blocks.entries()) {
373
- theCids.push(cid.toString());
374
- }
375
- // console.log('encrypting', theCids.length, 'blocks', theCids.includes(innerBlockStoreClockRootCid.toString()))
376
- // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
377
- let last;
378
- for await (const block of encrypt({
379
- cids: theCids,
380
- get: async (cid) => blocks.get(cid),
381
- key: encryptionKey,
382
- hasher: sha256,
383
- chunker,
384
- cache,
385
- // codec: dagcbor, // should be crypto?
386
- root: innerBlockStoreClockRootCid
387
- })) {
388
- encryptedBlocks.push(block);
389
- last = block;
390
- }
391
- // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
392
- const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
393
- return encryptedCar;
394
- };
395
- // { root, get, key, cache, chunker, hasher }
396
- const memoizeDecryptedCarBlocks = new Map();
397
- const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
398
- if (memoizeDecryptedCarBlocks.has(cid.toString())) {
399
- return memoizeDecryptedCarBlocks.get(cid.toString());
400
- }
401
- else {
402
- const blocksPromise = (async () => {
403
- const decryptionKey = Buffer.from(keyMaterial, 'hex');
404
- // console.log('decrypting', keyMaterial, cid.toString())
405
- const cids = new Set();
406
- const decryptedBlocks = [];
407
- for await (const block of decrypt({
408
- root: cid,
409
- get,
410
- key: decryptionKey,
411
- chunker,
412
- hasher: sha256,
413
- cache
414
- // codec: dagcbor
415
- })) {
416
- decryptedBlocks.push(block);
417
- cids.add(block.cid.toString());
418
- }
419
- return { blocks: decryptedBlocks, cids };
420
- })();
421
- memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
422
- return blocksPromise;
423
- }
424
- };
425
- const addCidsToCarIndex = async (blockstore, valetRoot, valetRootCid, bulkOperations) => {
426
- let indexNode;
427
- if (valetRootCid) {
428
- if (valetRoot) {
429
- indexNode = valetRoot;
430
- }
431
- else {
432
- indexNode = await load(blockstore, valetRootCid, { blockHasher: blockOpts.hasher, blockCodec: blockOpts.codec });
433
- }
434
- }
435
- else {
436
- indexNode = await create(blockstore, {
437
- bitWidth: 4,
438
- bucketSize: 2,
439
- blockHasher: blockOpts.hasher,
440
- blockCodec: blockOpts.codec
441
- });
442
- }
443
- // console.log('adding', bulkOperations.length, 'cids to index')
444
- for (const { key, value } of bulkOperations) {
445
- // console.log('adding', key, value)
446
- await indexNode.set(key, value);
447
- }
448
- return indexNode;
449
- };
450
- export class VMemoryBlockstore {
451
- /** @type {Map<string, Uint8Array>} */
452
- blocks = new Map();
453
- instanceId = Math.random().toString(36).slice(2);
454
- async get(cid) {
455
- const bytes = this.blocks.get(cid.toString());
456
- // console.log('getvm', bytes.constructor.name, this.instanceId, cid, bytes && bytes.length)
457
- if (bytes.length === 253) {
458
- // console.log('getvm', bytes.())
459
- }
460
- if (!bytes)
461
- throw new Error('block not found ' + cid.toString());
462
- return { cid, bytes };
463
- }
464
- /**
465
- * @param {import('../src/link').AnyLink} cid
466
- * @param {Uint8Array} bytes
467
- */
468
- async put(cid, bytes) {
469
- this.blocks.set(cid.toString(), bytes);
470
- }
471
- *entries() {
472
- for (const [str, bytes] of this.blocks) {
473
- yield { cid: parse(str), bytes };
474
- }
475
- }
476
- }
@@ -1,173 +0,0 @@
1
- // @ts-ignore
2
- import { useEffect, useState, useCallback, createContext } from 'react'
3
- import { Fireproof, Index } from '@fireproof/core'
4
-
5
- /**
6
- @typedef {Object} FireproofCtxValue
7
- @property {Function} addSubscriber - A function to add a subscriber with a label and function.
8
- @property {Fireproof} database - An instance of the Fireproof class.
9
- @property {Function} useLiveQuery - A hook to return a query result
10
- @property {Function} useLiveDocument - A hook to return a live document
11
- @property {boolean} ready - A boolean indicating whether the database is ready.
12
- @param {string} label - A label for the subscriber.
13
- @param {Function} fn - A function to be added as a subscriber.
14
- @returns {void}
15
- */
16
- export const FireproofCtx = createContext({
17
- addSubscriber: () => {},
18
- database: null,
19
- ready: false
20
- })
21
-
22
- // const inboundSubscriberQueue = new Map()
23
-
24
- let startedSetup = false
25
- let database
26
- const initializeDatabase = name => {
27
- if (database) return
28
- database = Fireproof.storage(name)
29
- }
30
-
31
- /**
32
- @function useFireproof
33
- React hook to initialize a Fireproof database, automatically saving and loading the clock.
34
- You might need to import { nodePolyfills } from 'vite-plugin-node-polyfills' in your vite.config.ts
35
- @deprecated - npm install @fireproof/react instead
36
- @param {string} name - The path to the database file
37
- @param {function(database): void} [defineDatabaseFn] - Synchronous function that defines the database, run this before any async calls
38
- @param {function(database): Promise<void>} [setupDatabaseFn] - Asynchronous function that sets up the database, run this to load fixture data etc
39
- @returns {FireproofCtxValue} { useLiveQuery, useLiveDocument, database, ready }
40
- */
41
- export function useFireproof (name = 'useFireproof', defineDatabaseFn = () => {}, setupDatabaseFn = async () => {}) {
42
- const [ready, setReady] = useState(false)
43
- initializeDatabase(name)
44
-
45
- /**
46
- * @deprecated - use database.subscribe instead
47
- */
48
- const addSubscriber = (label, fn) => {
49
- // todo test that the label is not needed
50
- return database.registerListener(fn)
51
- // inboundSubscriberQueue.set(label, fn)
52
- }
53
-
54
- useEffect(() => {
55
- const doSetup = async () => {
56
- if (ready) return
57
- if (startedSetup) return
58
- startedSetup = true
59
- defineDatabaseFn(database) // define indexes before querying them
60
- if (database.clock.length === 0) {
61
- await setupDatabaseFn(database)
62
- }
63
- setReady(true)
64
- }
65
- doSetup()
66
- }, [ready])
67
-
68
- function useLiveDocument (initialDoc) {
69
- const id = initialDoc._id
70
- const [doc, setDoc] = useState(initialDoc)
71
-
72
- const saveDoc = async newDoc => {
73
- await database.put({ _id: id, ...newDoc })
74
- }
75
- const refreshDoc = useCallback(async () => {
76
- // todo add option for mvcc checks
77
- const got = await database.get(id).catch(() => initialDoc)
78
- setDoc(got)
79
- }, [id, initialDoc])
80
-
81
- useEffect(
82
- () =>
83
- database.subscribe(change => {
84
- if (change.find(c => c.key === id)) {
85
- refreshDoc() // todo use change.value
86
- }
87
- }),
88
- [id, refreshDoc]
89
- )
90
-
91
- useEffect(() => {
92
- refreshDoc()
93
- }, [])
94
-
95
- return [doc, saveDoc]
96
- }
97
-
98
- function useLiveQuery (mapFn, query = null, initialRows = []) {
99
- const [rows, setRows] = useState({ rows: initialRows, proof: {} })
100
- const [index, setIndex] = useState(null)
101
-
102
- const refreshRows = useCallback(async () => {
103
- if (!index) return
104
- const got = await index.query(query || {})
105
- setRows(got)
106
- }, [index, JSON.stringify(query)])
107
-
108
- useEffect(
109
- () => {
110
- // todo listen to index changes
111
- return database.subscribe(() => {
112
- refreshRows()
113
- })
114
- },
115
- [refreshRows]
116
- )
117
-
118
- useEffect(() => {
119
- refreshRows()
120
- }, [index])
121
-
122
- useEffect(() => {
123
- const index = new Index(database, null, mapFn) // this should only be created once
124
- setIndex(index)
125
- }, [mapFn.toString()])
126
-
127
- return rows
128
- }
129
-
130
- return {
131
- addSubscriber,
132
- useLiveQuery,
133
- useLiveDocument,
134
- database,
135
- ready
136
- }
137
- }
138
-
139
- // const husherMap = new Map()
140
- // const husher = (id, workFn, ms) => {
141
- // if (!husherMap.has(id)) {
142
- // const start = Date.now()
143
- // husherMap.set(
144
- // id,
145
- // workFn().finally(() => setTimeout(() => husherMap.delete(id), ms - (Date.now() - start)))
146
- // )
147
- // }
148
- // return husherMap.get(id)
149
- // }
150
- // const hushed =
151
- // (id, workFn, ms) =>
152
- // (...args) =>
153
- // husher(id, () => workFn(...args), ms)
154
-
155
- // let storageSupported = false
156
- // try {
157
- // storageSupported = window.localStorage && true
158
- // } catch (e) {}
159
- // export function localGet (key) {
160
- // if (storageSupported) {
161
- // return localStorage && localStorage.getItem(key)
162
- // }
163
- // }
164
- // function localSet (key, value) {
165
- // if (storageSupported) {
166
- // return localStorage && localStorage.setItem(key, value)
167
- // }
168
- // }
169
- // function localRemove(key) {
170
- // if (storageSupported) {
171
- // return localStorage && localStorage.removeItem(key)
172
- // }
173
- // }