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