@fireproof/core 0.8.0 → 0.10.1-dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/README.md +5 -184
  2. package/dist/fireproof.browser.js +18879 -0
  3. package/dist/fireproof.browser.js.map +7 -0
  4. package/dist/fireproof.cjs.js +9305 -0
  5. package/dist/fireproof.cjs.js.map +7 -0
  6. package/dist/fireproof.esm.js +9295 -0
  7. package/dist/fireproof.esm.js.map +7 -0
  8. package/package.json +57 -105
  9. package/dist/blockstore.js +0 -268
  10. package/dist/clock.js +0 -459
  11. package/dist/crypto.js +0 -63
  12. package/dist/database.js +0 -434
  13. package/dist/db-index.js +0 -403
  14. package/dist/encrypted-block.js +0 -48
  15. package/dist/fireproof.js +0 -84
  16. package/dist/import.js +0 -29
  17. package/dist/listener.js +0 -111
  18. package/dist/loader.js +0 -13
  19. package/dist/prolly.js +0 -405
  20. package/dist/remote.js +0 -102
  21. package/dist/sha1.js +0 -74
  22. package/dist/src/fireproof.d.ts +0 -472
  23. package/dist/src/fireproof.js +0 -81191
  24. package/dist/src/fireproof.js.map +0 -1
  25. package/dist/src/fireproof.mjs +0 -81186
  26. package/dist/src/fireproof.mjs.map +0 -1
  27. package/dist/storage/base.js +0 -426
  28. package/dist/storage/blocksToEncryptedCarBlock.js +0 -144
  29. package/dist/storage/browser.js +0 -62
  30. package/dist/storage/filesystem.js +0 -67
  31. package/dist/storage/rest.js +0 -57
  32. package/dist/storage/ucan.js +0 -0
  33. package/dist/storage/utils.js +0 -144
  34. package/dist/sync.js +0 -218
  35. package/dist/utils.js +0 -16
  36. package/dist/valet.js +0 -102
  37. package/src/blockstore.js +0 -283
  38. package/src/clock.js +0 -486
  39. package/src/crypto.js +0 -70
  40. package/src/database.js +0 -469
  41. package/src/db-index.js +0 -426
  42. package/src/encrypted-block.js +0 -57
  43. package/src/fireproof.js +0 -98
  44. package/src/import.js +0 -34
  45. package/src/link.d.ts +0 -3
  46. package/src/loader.js +0 -16
  47. package/src/prolly.js +0 -445
  48. package/src/remote.js +0 -113
  49. package/src/sha1.js +0 -83
  50. package/src/storage/base.js +0 -463
  51. package/src/storage/browser.js +0 -67
  52. package/src/storage/filesystem.js +0 -73
  53. package/src/storage/rest.js +0 -59
  54. package/src/storage/ucan.js +0 -0
  55. package/src/storage/utils.js +0 -152
  56. package/src/sync.js +0 -237
  57. package/src/valet.js +0 -105
package/src/database.js DELETED
@@ -1,469 +0,0 @@
1
- // @ts-nocheck
2
- import { visMerkleClock, visMerkleTree, vis, put, get, getAll, eventsSince } from './prolly.js'
3
- import { doTransaction, TransactionBlockstore } from './blockstore.js'
4
- import charwise from 'charwise'
5
- import { CID } from 'multiformats'
6
- import { DbIndex as Index } from './db-index.js'
7
-
8
- import { Remote } from './remote.js'
9
-
10
- // TypeScript Types
11
- // eslint-disable-next-line no-unused-vars
12
- // import { CID } from 'multiformats/dist/types/src/cid.js'
13
-
14
- // eslint-disable-next-line no-unused-vars
15
- class Proof {}
16
- export const parseCID = cid => (typeof cid === 'string' ? CID.parse(cid) : cid)
17
-
18
- /**
19
- * @class Fireproof
20
- * @classdesc Fireproof stores data in IndexedDB and provides a Merkle clock.
21
- * This is the main class for saving and loading JSON and other documents with the database. You can find additional examples and
22
- * usage guides in the repository README.
23
- *
24
- * @param {CID[]} clock - The Merkle clock head to use for the Fireproof instance.
25
- * @param {object} [config] - Optional configuration options for the Fireproof instance.
26
- * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
27
- *
28
- */
29
- export class Database {
30
- listeners = new Set()
31
- indexes = new Map()
32
- rootCache = null
33
- eventsCache = new Map()
34
- remote = null
35
- name = ''
36
- constructor (name, config = {}) {
37
- this.name = name
38
- this.clock = []
39
- this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
40
- this.blocks = new TransactionBlockstore(name, config)
41
- this.indexBlocks = new TransactionBlockstore(name ? name + '.indexes' : null, { primary: config.index })
42
- this.remote = new Remote(this, name, config)
43
- this.config = config
44
- // todo we can wait for index blocks elsewhere
45
- this.ready = Promise.all([this.blocks.ready, this.indexBlocks.ready]).then(([blocksReady, indexReady]) => {
46
- const clock = new Set()
47
- // console.log('blocksReady', blocksReady)
48
- if (!blocksReady) {
49
- return
50
- }
51
- for (const headers of blocksReady) {
52
- for (const [, header] of Object.entries(headers)) {
53
- if (!header) continue
54
- for (const cid of header.clock) {
55
- clock.add(cid)
56
- }
57
- if (header.index) {
58
- this.indexBlocks.valet.primary.setLastCar(header.index.car)
59
- this.indexBlocks.valet.primary.setKeyMaterial(header.index.key)
60
- }
61
- if (header.indexes) {
62
- for (const {
63
- name,
64
- code,
65
- clock: { byId, byKey, db }
66
- } of header.indexes) {
67
- // console.log('index', name, code, { byId, byKey }, db, header.indexes)
68
- Index.fromJSON(this, {
69
- clock: {
70
- byId: byId ? parseCID(byId) : null,
71
- byKey: byKey ? parseCID(byKey) : null,
72
- db: db && db.length > 0 ? db.map(c => parseCID(c)) : null
73
- },
74
- code,
75
- name
76
- })
77
- }
78
- }
79
- }
80
- }
81
- this.clock = [...clock]
82
- })
83
- }
84
-
85
- /**
86
- * Renders the Fireproof instance as a JSON object.
87
- * @returns {Object} - The JSON representation of the Fireproof instance. Includes clock heads for the database and its indexes.
88
- * @memberof Fireproof
89
- * @instance
90
- */
91
- toJSON () {
92
- // todo this prepareHeader ignores secondary storage, need both
93
- return this.blocks.valet ? this.blocks.valet.primary.prepareHeader(this.toHeader(), false) : this.toHeader() // omg
94
- }
95
-
96
- toHeader () {
97
- return {
98
- // clock: this.clockToJSON(),
99
- name: this.name,
100
- index: {
101
- key: this.indexBlocks.valet?.primary.keyMaterial,
102
- car: this.indexBlocks.valet?.primary.lastCar?.toString()
103
- },
104
- indexes: [...this.indexes.values()].map(index => index.toJSON())
105
- }
106
- }
107
-
108
- /**
109
- * Returns the Merkle clock heads for the Fireproof instance.
110
- * @returns {string[]} - The Merkle clock heads for the Fireproof instance.
111
- * @memberof Fireproof
112
- * @instance
113
- */
114
- clockToJSON (clock = null) {
115
- return (clock || this.clock).map(cid => cid.toString())
116
- }
117
-
118
- async maybeSaveClock () {
119
- if (this.name && this.blocks.valet) {
120
- await this.blocks.valet.saveHeader(this.toHeader())
121
- }
122
- }
123
-
124
- index (name) {
125
- const indexes = [...this.indexes.values()].filter(index => index.name === name)
126
- if (indexes.length > 1) {
127
- throw new Error(`Multiple indexes found with name ${name}`)
128
- }
129
- return indexes[0] || null
130
- }
131
-
132
- /**
133
- * Triggers a notification to all listeners
134
- * of the Fireproof instance so they can repaint UI, etc.
135
- * @returns {Promise<void>}
136
- * @memberof Fireproof
137
- * @instance
138
- */
139
- async notifyReset () {
140
- await this.ready
141
- await this.notifyListeners({ _reset: true, _clock: this.clockToJSON() })
142
- }
143
-
144
- async compact () {
145
- if (this.name && this.blocks.valet) {
146
- await this.blocks.valet.compact(this.clock)
147
- await this.blocks.valet.saveHeader(this.toHeader())
148
- }
149
- }
150
-
151
- /**
152
- * Returns the changes made to the Fireproof instance since the specified event.
153
- * @function changesSince
154
- * @param {CID[]} [event] - The clock head to retrieve changes since. If null or undefined, retrieves all changes.
155
- * @returns {Promise<{rows : Object[], clock: CID[], proof: {}}>} An object containing the rows and the head of the instance's clock.
156
- * @memberof Fireproof
157
- * @instance
158
- */
159
- async changesSince (aClock) {
160
- await this.ready
161
- // console.log('events for', this.instanceId, aClock?.constructor.name)
162
- // console.log('changesSince', this.instanceId, this.clockToJSON(aClock), this.clockToJSON())
163
- let rows, dataCIDs, clockCIDs
164
- // if (!aClock) aClock = []
165
- if (aClock && aClock.length > 0) {
166
- aClock = aClock.map(cid => cid.toString())
167
- const eventKey = JSON.stringify([...this.clockToJSON(aClock), ...this.clockToJSON()])
168
-
169
- let resp
170
- if (this.eventsCache.has(eventKey)) {
171
- // console.log('events from cache')
172
- resp = this.eventsCache.get(eventKey)
173
- } else {
174
- resp = await eventsSince(this.blocks, this.clock, aClock)
175
- this.eventsCache.set(eventKey, resp)
176
- }
177
- const docsMap = new Map()
178
- for (const { key, type, value } of resp.result.map(decodeEvent)) {
179
- if (type === 'del') {
180
- docsMap.set(key, { key, del: true })
181
- } else {
182
- docsMap.set(key, { key, value })
183
- }
184
- }
185
- rows = Array.from(docsMap.values())
186
- clockCIDs = resp.clockCIDs
187
- // console.log('change rows', this.instanceId, rows)
188
- } else {
189
- const allResp = await getAll(this.blocks, this.clock, this.rootCache)
190
- this.rootCache = { root: allResp.root, clockCIDs: allResp.clockCIDs }
191
-
192
- rows = allResp.result.map(({ key, value }) => decodeEvent({ key, value }))
193
- dataCIDs = allResp.cids
194
- // console.log('dbdoc rows', this.instanceId, rows)
195
- }
196
- return {
197
- rows,
198
- clock: this.clockToJSON(),
199
- proof: { data: await cidsToProof(dataCIDs), clock: await cidsToProof(clockCIDs) }
200
- }
201
- }
202
-
203
- async allDocuments () {
204
- await this.ready
205
- const allResp = await getAll(this.blocks, this.clock, this.rootCache)
206
- this.rootCache = { root: allResp.root, clockCIDs: allResp.clockCIDs }
207
-
208
- const rows = allResp.result
209
- .map(({ key, value }) => decodeEvent({ key, value }))
210
- .map(({ key, value }) => ({ key, value: { _id: key, ...value } }))
211
- return {
212
- rows,
213
- clock: this.clockToJSON(),
214
- proof: await cidsToProof(allResp.cids)
215
- }
216
- }
217
-
218
- async allCIDs () {
219
- await this.ready
220
- const allResp = await getAll(this.blocks, this.clock, this.rootCache, true)
221
- this.rootCache = { root: allResp.root, clockCIDs: allResp.clockCIDs }
222
- // console.log('allcids', allResp.cids, allResp.clockCIDs)
223
- const cids = await cidsToProof(allResp.cids)
224
- const clockCids = await cidsToProof(allResp.clockCIDs)
225
- // console.log('allcids', cids, clockCids)
226
- return [...cids, ...clockCids] // clock CID last -- need to handle multiple entry clocks
227
- }
228
-
229
- async allStoredCIDs () {
230
- await this.ready
231
- const allCIDs = []
232
- for await (const { cid } of this.blocks.entries()) {
233
- allCIDs.push(cid)
234
- }
235
- return allCIDs
236
- }
237
-
238
- /**
239
- * Retrieves the document with the specified ID from the database
240
- *
241
- * @param {string} key - the ID of the document to retrieve
242
- * @param {Object} [opts] - options
243
- * @returns {Promise<{_id: string}>} - the document with the specified ID
244
- * @memberof Fireproof
245
- * @instance
246
- */
247
- async get (key, opts = {}) {
248
- await this.ready
249
- const clock = opts.clock || this.clock
250
- const resp = await get(this.blocks, clock, charwise.encode(key), this.rootCache)
251
- this.rootCache = { root: resp.root, clockCIDs: resp.clockCIDs }
252
- // ? this tombstone is temporary until we can get the prolly tree to delete
253
- if (!resp || resp.result === null) {
254
- throw new Error('Not found')
255
- }
256
- const doc = { ...resp.result }
257
- if (opts.mvcc === true) {
258
- doc._clock = this.clockToJSON()
259
- }
260
- doc._proof = {
261
- data: await cidsToProof(resp.cids),
262
- clock: this.clockToJSON()
263
- }
264
- doc._id = key
265
- return doc
266
- }
267
- /**
268
- * @typedef {any} Document
269
- * @property {string} _id - The ID of the document (required)
270
- * @property {string} [_proof] - The proof of the document (optional)
271
- * @property {string} [_clock] - The clock of the document (optional)
272
- * @property {Object.<string, any>} [unknown: string] - Any other unknown properties (optional)
273
- */
274
-
275
- /**
276
- * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
277
- *
278
- * @param {Document} doc - the document to be added
279
- * @returns {Promise<{ id: string, clock: CID[] }>} - The result of adding the document to the database
280
- * @memberof Fireproof
281
- * @instance
282
- */
283
- async put ({ _id, _proof, _clock, ...doc }) {
284
- await this.ready
285
- const id = _id || 'f' + Math.random().toString(36).slice(2)
286
- doc = JSON.parse(JSON.stringify(doc))
287
- if (_clock) doc._clock = _clock
288
- await this.runValidation({ _id: id, ...doc })
289
- return await this.putToProllyTree({ key: id, value: doc }, _clock)
290
- }
291
-
292
- /**
293
- * Deletes a document from the database
294
- * @param {string | any} docOrId - the document ID
295
- * @returns {Promise<{ id: string, clock: CID[] }>} - The result of deleting the document from the database
296
- * @memberof Fireproof
297
- * @instance
298
- */
299
- async del (docOrId) {
300
- await this.ready
301
- let id
302
- let clock = null
303
- if (docOrId._id) {
304
- id = docOrId._id
305
- clock = docOrId._clock
306
- } else {
307
- id = docOrId
308
- }
309
- await this.runValidation({ _id: id, _deleted: true })
310
- return await this.putToProllyTree({ key: id, del: true }, clock) // not working at prolly tree layer?
311
- // this tombstone is temporary until we can get the prolly tree to delete
312
- // return await this.putToProllyTree({ key: id, value: null }, clock)
313
- }
314
-
315
- /**
316
- * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
317
- *
318
- * @param {Object} doc - The document to validate.
319
- * @returns {Promise<void>}
320
- * @throws {Error} - Throws an error if the document is invalid.
321
- * @memberof Fireproof
322
- * @instance
323
- */
324
- async runValidation (doc) {
325
- if (this.config && this.config.validateChange) {
326
- const oldDoc = await this.get(doc._id)
327
- .then(doc => doc)
328
- .catch(() => ({}))
329
- this.config.validateChange(doc, oldDoc, this.authCtx)
330
- }
331
- }
332
-
333
- /**
334
- * Updates the underlying storage with the specified event.
335
- * @private
336
- * @param {{del?: true, key : string, value?: any}} decodedEvent - the event to add
337
- * @returns {Promise<{ proof:{}, id: string, clock: CID[] }>} - The result of adding the event to storage
338
- */
339
- async putToProllyTree (decodedEvent, clock = null) {
340
- // console.log('putToProllyTree', decodedEvent)
341
- const event = encodeEvent(decodedEvent)
342
- if (clock && JSON.stringify(this.clockToJSON(clock)) !== JSON.stringify(this.clockToJSON())) {
343
- // console.log('this.clock', this.clockToJSON())
344
- // console.log('that.clock', this.clockToJSON(clock))
345
- // we need to check and see what version of the document exists at the clock specified
346
- // if it is the same as the one we are trying to put, then we can proceed
347
- const resp = await eventsSince(this.blocks, this.clock, event.value._clock)
348
- const missedChange = resp.result.find(({ key }) => key === event.key)
349
- if (missedChange) {
350
- throw new Error('MVCC conflict, document is changed, please reload the document and try again.')
351
- }
352
- }
353
- const prevClock = [...this.clock]
354
- // console.log('putToProllyTree', this.clockToJSON(), decodedEvent)
355
- const result = await doTransaction(
356
- 'putToProllyTree',
357
- this.blocks,
358
- async blocks => await put(blocks, this.clock, event)
359
- )
360
- if (!result) {
361
- console.error('failed', event)
362
- throw new Error('failed to put at storage layer')
363
- }
364
- this.applyClock(prevClock, result.head)
365
- await this.notifyListeners([decodedEvent]) // this type is odd
366
- return {
367
- id: decodedEvent.key,
368
- clock: this.clockToJSON(),
369
- proof: { data: await cidsToProof(result.cids), clock: await cidsToProof(result.clockCIDs) }
370
- }
371
- // todo should include additions (or split clock)
372
- }
373
-
374
- applyClock (prevClock, newClock) {
375
- // console.log('prevClock', prevClock.length, prevClock.map((cid) => cid.toString()))
376
- // console.log('newClock', newClock.length, newClock.map((cid) => cid.toString()))
377
- // console.log('this.clock', this.clock.length, this.clockToJSON())
378
- const stPrev = prevClock.map(cid => cid.toString())
379
- const keptPrevClock = this.clock.filter(cid => stPrev.indexOf(cid.toString()) === -1)
380
- const merged = keptPrevClock.concat(newClock)
381
- const uniquebyCid = new Map()
382
- for (const cid of merged) {
383
- uniquebyCid.set(cid.toString(), cid)
384
- }
385
- this.clock = Array.from(uniquebyCid.values()).sort((a, b) => a.toString().localeCompare(b.toString()))
386
- this.rootCache = null
387
- this.eventsCache.clear()
388
- // console.log('afterClock', this.clock.length, this.clockToJSON())
389
- }
390
-
391
- // /**
392
- // * Advances the clock to the specified event and updates the root CID
393
- // * Will be used by replication
394
- // */
395
- // async advance (event) {
396
- // this.clock = await advance(this.blocks, this.clock, event)
397
- // this.rootCid = await root(this.blocks, this.clock)
398
- // return this.clock
399
- // }
400
-
401
- async * vis () {
402
- return yield * vis(this.blocks, this.clock)
403
- }
404
-
405
- async visTree () {
406
- return await visMerkleTree(this.blocks, this.clock)
407
- }
408
-
409
- async visClock () {
410
- return await visMerkleClock(this.blocks, this.clock)
411
- }
412
-
413
- /**
414
- * Registers a Listener to be called when the Fireproof instance's clock is updated.
415
- * Recieves live changes from the database after they are committed.
416
- * @param {Function} listener - The listener to be called when the clock is updated.
417
- * @returns {Function} - A function that can be called to unregister the listener.
418
- * @memberof Fireproof
419
- */
420
- subscribe (listener) {
421
- this.listeners.add(listener)
422
- return () => {
423
- this.listeners.delete(listener)
424
- }
425
- }
426
-
427
- /**
428
- * @deprecated 0.7.0 - renamed subscribe(listener)
429
- * @param {Function} listener - The listener to be called when the clock is updated.
430
- * @returns {Function} - A function that can be called to unregister the listener.
431
- * @memberof Fireproof
432
- */
433
- registerListener (listener) {
434
- return this.subscribe(listener)
435
- }
436
-
437
- async notifyListeners (changes) {
438
- // await sleep(10)
439
- await this.maybeSaveClock()
440
- for (const listener of this.listeners) {
441
- await listener(changes)
442
- }
443
- }
444
-
445
- setRemoteBlockReader (remoteBlockReaderFn) {
446
- this.blocks.remoteBlockFunction = remoteBlockReaderFn
447
- }
448
- }
449
-
450
- export async function cidsToProof (cids) {
451
- if (!cids) return []
452
- if (!cids.all) {
453
- return [...cids]
454
- }
455
-
456
- const all = await cids.all()
457
- return [...all].map(cid => cid.toString())
458
- }
459
-
460
- function decodeEvent (event) {
461
- const decodedKey = charwise.decode(event.key)
462
- return { ...event, key: decodedKey }
463
- }
464
-
465
- function encodeEvent (event) {
466
- if (!(event && event.key)) return
467
- const encodedKey = charwise.encode(event.key)
468
- return { ...event, key: encodedKey }
469
- }