@fireproof/core 0.6.5 → 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 (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 +92 -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 +137 -136
  9. package/dist/src/fireproof.js +18179 -11484
  10. package/dist/src/fireproof.js.map +1 -1
  11. package/dist/src/fireproof.mjs +18180 -11484
  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 +41 -30
  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
@@ -1,5 +1,5 @@
1
- import { readFileSync } from 'fs';
2
- import { mkdir, writeFile } from 'fs/promises';
1
+ import { readFileSync } from 'node:fs';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { join, dirname } from 'path';
4
4
  import { homedir } from 'os';
5
5
  import { Base } from './base.js';
@@ -193,7 +193,7 @@ export class Sync {
193
193
  return null;
194
194
  }
195
195
  if (typeof key === 'undefined') {
196
- key = blocks.valet?.getKeyMaterial();
196
+ key = blocks.valet?.primary.keyMaterial;
197
197
  }
198
198
  if (key) {
199
199
  return blocksToEncryptedCarBlock(rootCIDs, {
package/dist/valet.js ADDED
@@ -0,0 +1,200 @@
1
+ import { sha256 } from 'multiformats/hashes/sha2';
2
+ import * as CBW from '@ipld/car/buffer-writer';
3
+ import * as raw from 'multiformats/codecs/raw';
4
+ import * as Block from 'multiformats/block';
5
+ import { Loader } from './loader.js';
6
+ // @ts-ignore
7
+ import { bf } from 'prolly-trees/utils';
8
+ // @ts-ignore
9
+ import { nocache as cache } from 'prolly-trees/cache';
10
+ import { encrypt, decrypt } from './crypto.js';
11
+ import { Buffer } from 'buffer';
12
+ const chunker = bf(30);
13
+ export class Valet {
14
+ idb = null;
15
+ name = null;
16
+ uploadQueue = null;
17
+ alreadyEnqueued = new Set();
18
+ instanceId = Math.random().toString(36).slice(2);
19
+ constructor(name = 'default', config = {}) {
20
+ this.name = name;
21
+ // console.log('new Valet', name, config.primary)
22
+ this.primary = Loader.appropriate(name, config.primary);
23
+ this.secondary = config.secondary ? Loader.appropriate(name, config.secondary) : null;
24
+ // set up a promise listener that applies all the headers to the clock
25
+ // when they resolve
26
+ const readyP = [this.primary.ready];
27
+ if (this.secondary)
28
+ readyP.push(this.secondary.ready);
29
+ this.ready = Promise.all(readyP).then((blocksReady) => {
30
+ // console.log('blocksReady valet', this.name, blocksReady)
31
+ return blocksReady;
32
+ });
33
+ }
34
+ async saveHeader(header) {
35
+ // each storage needs to add its own carCidMapCarCid to the header
36
+ if (this.secondary) {
37
+ this.secondary.saveHeader(header);
38
+ } // todo: await?
39
+ return await this.primary.saveHeader(header);
40
+ }
41
+ /**
42
+ * Group the blocks into a car and write it to the valet.
43
+ * @param {import('./blockstore.js').InnerBlockstore} innerBlockstore
44
+ * @param {Set<string>} cids
45
+ * @returns {Promise<void>}
46
+ * @memberof Valet
47
+ */
48
+ async writeTransaction(innerBlockstore, cids) {
49
+ if (innerBlockstore.lastCid) {
50
+ await parkCar(this.primary, innerBlockstore, cids);
51
+ if (this.secondary)
52
+ await parkCar(this.secondary, innerBlockstore, cids);
53
+ }
54
+ else {
55
+ throw new Error('missing lastCid for car header');
56
+ }
57
+ }
58
+ /**
59
+ * Iterate over all blocks in the store.
60
+ *
61
+ * @yields {{cid: string, value: Uint8Array}}
62
+ * @returns {AsyncGenerator<any, any, any>}
63
+ */
64
+ async *cids() {
65
+ // console.log('valet cids')
66
+ // todo use cidMap
67
+ // while (cursor) {
68
+ // yield { cid: cursor.key, car: cursor.value.car }
69
+ // cursor = await cursor.continue()
70
+ // }
71
+ }
72
+ remoteBlockFunction = null;
73
+ async getValetBlock(dataCID) {
74
+ // console.log('getValetBlock primary', dataCID)
75
+ try {
76
+ const { block } = await this.primary.getLoaderBlock(dataCID);
77
+ return block;
78
+ }
79
+ catch (e) {
80
+ // console.log('getValetBlock error', e)
81
+ if (this.secondary) {
82
+ // console.log('getValetBlock secondary', dataCID)
83
+ try {
84
+ const { block, reader } = await this.secondary.getLoaderBlock(dataCID);
85
+ const cids = new Set();
86
+ for await (const { cid } of reader.entries()) {
87
+ // console.log(cid, bytes)
88
+ cids.add(cid.toString());
89
+ }
90
+ reader.get = reader.gat; // some consumers prefer get
91
+ // console.log('replicating', reader.root)
92
+ reader.lastCid = reader.root.cid;
93
+ await parkCar(this.primary, reader, [...cids]);
94
+ return block;
95
+ }
96
+ catch (e) {
97
+ // console.log('getValetBlock secondary error', e)
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ async function parkCar(storage, innerBlockstore, cids) {
104
+ // console.log('parkCar', this.instanceId, this.name, carCid, cids)
105
+ let newCar;
106
+ if (storage.keyMaterial) {
107
+ // console.log('encrypting car', innerBlockstore.label)
108
+ newCar = await blocksToEncryptedCarBlock(innerBlockstore.lastCid, innerBlockstore, storage.keyMaterial);
109
+ }
110
+ else {
111
+ // todo should we pass cids in instead of iterating innerBlockstore?
112
+ newCar = await blocksToCarBlock(innerBlockstore.lastCid, innerBlockstore);
113
+ }
114
+ // console.log('new car', newCar.cid.toString())
115
+ return await storage.saveCar(newCar.cid.toString(), newCar.bytes, cids);
116
+ }
117
+ export const blocksToCarBlock = async (rootCids, blocks) => {
118
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
119
+ let size = 0;
120
+ if (!Array.isArray(rootCids)) {
121
+ rootCids = [rootCids];
122
+ }
123
+ const headerSize = CBW.headerLength({ roots: rootCids });
124
+ size += headerSize;
125
+ if (!Array.isArray(blocks)) {
126
+ blocks = Array.from(blocks.entries());
127
+ }
128
+ for (const { cid, bytes } of blocks) {
129
+ // console.log(cid, bytes)
130
+ size += CBW.blockLength({ cid, bytes });
131
+ }
132
+ const buffer = new Uint8Array(size);
133
+ const writer = await CBW.createWriter(buffer, { headerSize });
134
+ for (const cid of rootCids) {
135
+ writer.addRoot(cid);
136
+ }
137
+ for (const { cid, bytes } of blocks) {
138
+ writer.write({ cid, bytes });
139
+ }
140
+ await writer.close();
141
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
142
+ };
143
+ export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial) => {
144
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
145
+ const encryptedBlocks = [];
146
+ const theCids = [];
147
+ // console.trace('blocksToEncryptedCarBlock', blocks)
148
+ for (const { cid } of blocks.entries()) {
149
+ theCids.push(cid.toString());
150
+ }
151
+ // console.log('encrypting', theCids.length, 'blocks', theCids.includes(innerBlockStoreClockRootCid.toString()), keyMaterial)
152
+ // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
153
+ let last;
154
+ for await (const block of encrypt({
155
+ cids: theCids,
156
+ get: async (cid) => blocks.get(cid),
157
+ key: encryptionKey,
158
+ hasher: sha256,
159
+ chunker,
160
+ cache,
161
+ // codec: dagcbor, // should be crypto?
162
+ root: innerBlockStoreClockRootCid
163
+ })) {
164
+ encryptedBlocks.push(block);
165
+ last = block;
166
+ }
167
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
168
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
169
+ return encryptedCar;
170
+ };
171
+ // { root, get, key, cache, chunker, hasher }
172
+ const memoizeDecryptedCarBlocks = new Map();
173
+ export const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
174
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
175
+ return memoizeDecryptedCarBlocks.get(cid.toString());
176
+ }
177
+ else {
178
+ const blocksPromise = (async () => {
179
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
180
+ // console.log('decrypting', keyMaterial, cid.toString())
181
+ const cids = new Set();
182
+ const decryptedBlocks = [];
183
+ for await (const block of decrypt({
184
+ root: cid,
185
+ get,
186
+ key: decryptionKey,
187
+ chunker,
188
+ hasher: sha256,
189
+ cache
190
+ // codec: dagcbor
191
+ })) {
192
+ decryptedBlocks.push(block);
193
+ cids.add(block.cid.toString());
194
+ }
195
+ return { blocks: decryptedBlocks, cids };
196
+ })();
197
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
198
+ return blocksPromise;
199
+ }
200
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.6.5",
3
+ "version": "0.7.0-alpha.0",
4
4
  "description": "Live data for React, accelerated by proofs, powered by IPFS",
5
5
  "main": "dist/src/fireproof.js",
6
6
  "module": "dist/src/fireproof.mjs",
@@ -9,7 +9,7 @@
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "keygen": "node scripts/keygen.js",
12
- "test": "standard && npm run test:unencrypted && npm run test:mocha",
12
+ "test": "standard && tsc && npm run test:unencrypted && npm run test:mocha",
13
13
  "test:unencrypted": "set NO_ENCRYPT=true && npm run test:mocha",
14
14
  "test:mocha": "mocha test/*.test.js",
15
15
  "test:watch": "npm run test:mocha -- -w --parallel",
@@ -40,6 +40,7 @@
40
40
  "dependencies": {
41
41
  "@ipld/car": "^5.1.0",
42
42
  "@ipld/dag-cbor": "^9.0.0",
43
+ "@jsonlines/core": "^1.0.2",
43
44
  "@rollup/plugin-commonjs": "^24.0.1",
44
45
  "archy": "^1.0.0",
45
46
  "async": "^3.2.4",
@@ -51,6 +52,7 @@
51
52
  "idb": "^7.1.1",
52
53
  "ipld-hashmap": "^2.1.18",
53
54
  "multiformats": "^11.0.1",
55
+ "node-fetch": "^3.3.1",
54
56
  "node-polyfill-webpack-plugin": "^2.0.1",
55
57
  "prolly-trees": "1.0.4",
56
58
  "randombytes": "^2.1.0",
@@ -110,9 +112,6 @@
110
112
  "default": "./dist/src/fireproof.js",
111
113
  "require": "./dist/src/fireproof.js"
112
114
  },
113
- "./hooks/use-fireproof": {
114
- "import": "./hooks/use-fireproof.js"
115
- },
116
115
  "./package.json": "./package.json"
117
116
  },
118
117
  "files": [
package/src/blockstore.js CHANGED
@@ -41,9 +41,12 @@ export class TransactionBlockstore {
41
41
  inflightTransactions = new Set()
42
42
  syncs = new Set()
43
43
 
44
- constructor (name, encryptionKey) {
44
+ constructor (name, config) {
45
45
  if (name) {
46
- this.valet = new Valet(name, encryptionKey)
46
+ this.valet = new Valet(name, config)
47
+ this.ready = this.valet.ready
48
+ } else {
49
+ this.ready = Promise.resolve()
47
50
  }
48
51
  this.remoteBlockFunction = null
49
52
  }
@@ -58,7 +61,7 @@ export class TransactionBlockstore {
58
61
  const key = cid.toString()
59
62
  // it is safe to read from the in-flight transactions becauase they are immutable
60
63
  const bytes = await Promise.any([this.transactionsGet(key), this.committedGet(key)]).catch(e => {
61
- // console.log('networkGet', cid.toString(), e)
64
+ console.log('get error', cid.toString(), e)
62
65
  return this.networkGet(key)
63
66
  })
64
67
  if (!bytes) throw new Error('Missing block: ' + key)
package/src/database.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { visMerkleClock, visMerkleTree, vis, put, get, getAll, eventsSince } from './prolly.js'
3
3
  import { doTransaction, TransactionBlockstore } from './blockstore.js'
4
4
  import charwise from 'charwise'
5
- import { localSet } from './utils.js'
6
5
  import { CID } from 'multiformats'
6
+ import { DbIndex as Index } from './db-index.js'
7
7
 
8
8
  // TypeScript Types
9
9
  // eslint-disable-next-line no-unused-vars
@@ -30,13 +30,53 @@ export class Database {
30
30
  rootCache = null
31
31
  eventsCache = new Map()
32
32
 
33
- constructor (name, clock, config = {}) {
33
+ constructor (name, config = {}) {
34
34
  this.name = name
35
+ this.clock = []
35
36
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
36
- this.blocks = new TransactionBlockstore(name, config.key)
37
- this.indexBlocks = new TransactionBlockstore(name + '.indexes', config.key)
38
- this.clock = clock
37
+ this.blocks = new TransactionBlockstore(name, config)
38
+ this.indexBlocks = new TransactionBlockstore(name ? name + '.indexes' : null, { primary: config.index })
39
+
39
40
  this.config = config
41
+ // todo we can wait for index blocks elsewhere
42
+ this.ready = Promise.all([this.blocks.ready, this.indexBlocks.ready]).then(([blocksReady, indexReady]) => {
43
+ const clock = new Set()
44
+ // console.log('blocksReady', blocksReady)
45
+ if (!blocksReady) {
46
+ return
47
+ }
48
+ for (const headers of blocksReady) {
49
+ for (const [, header] of Object.entries(headers)) {
50
+ if (!header) continue
51
+ for (const cid of header.clock) {
52
+ clock.add(cid)
53
+ }
54
+ if (header.index) {
55
+ this.indexBlocks.valet.primary.setCarCidMapCarCid(header.index.car)
56
+ this.indexBlocks.valet.primary.setKeyMaterial(header.index.key)
57
+ }
58
+ if (header.indexes) {
59
+ for (const {
60
+ name,
61
+ code,
62
+ clock: { byId, byKey, db }
63
+ } of header.indexes) {
64
+ // console.log('index', name, code, { byId, byKey }, db, header.indexes)
65
+ Index.fromJSON(this, {
66
+ clock: {
67
+ byId: byId ? parseCID(byId) : null,
68
+ byKey: byKey ? parseCID(byKey) : null,
69
+ db: (db && db.length > 0) ? db.map(c => parseCID(c)) : null
70
+ },
71
+ code,
72
+ name
73
+ })
74
+ }
75
+ }
76
+ }
77
+ }
78
+ this.clock = [...clock]
79
+ })
40
80
  }
41
81
 
42
82
  /**
@@ -46,12 +86,17 @@ export class Database {
46
86
  * @instance
47
87
  */
48
88
  toJSON () {
89
+ return this.blocks.valet ? this.blocks.valet.primary.prepareHeader(this.toHeader(), false) : this.toHeader() // omg
90
+ }
91
+
92
+ toHeader () {
49
93
  return {
50
94
  clock: this.clockToJSON(),
51
95
  name: this.name,
52
- key: this.blocks.valet?.getKeyMaterial(),
53
- car: this.blocks.valet?.valetRootCarCid.toString(),
54
- indexCar: this.indexBlocks.valet?.valetRootCarCid?.toString(),
96
+ index: {
97
+ key: this.indexBlocks.valet?.primary.keyMaterial,
98
+ car: this.indexBlocks.valet?.primary.valetRootCarCid?.toString()
99
+ },
55
100
  indexes: [...this.indexes.values()].map(index => index.toJSON())
56
101
  }
57
102
  }
@@ -66,27 +111,13 @@ export class Database {
66
111
  return (clock || this.clock).map(cid => cid.toString())
67
112
  }
68
113
 
69
- hydrate ({ clock, name, key, car, indexCar }) {
70
- this.name = name
71
- this.clock = clock
72
- this.blocks.valet?.setKeyMaterial(key)
73
- this.blocks.valet?.setRootCarCid(car) // maybe
74
- this.indexBlocks.valet?.setKeyMaterial(key)
75
- this.indexBlocks.valet?.setRootCarCid(indexCar) // maybe
76
- // this.indexBlocks = null
77
- }
78
-
79
114
  maybeSaveClock () {
80
115
  if (this.name && this.blocks.valet) {
81
- localSet('fp.' + this.name, JSON.stringify(this))
116
+ this.blocks.valet.saveHeader(this.toHeader())
82
117
  }
83
118
  }
84
119
 
85
120
  index (name) {
86
- // iterate over the indexes and gather any with the same name
87
- // if there are more than one, throw an error
88
- // if there is one, return it
89
- // if there are none, return null
90
121
  const indexes = [...this.indexes.values()].filter(index => index.name === name)
91
122
  if (indexes.length > 1) {
92
123
  throw new Error(`Multiple indexes found with name ${name}`)
@@ -102,14 +133,10 @@ export class Database {
102
133
  * @instance
103
134
  */
104
135
  async notifyReset () {
136
+ await this.ready
105
137
  await this.notifyListeners({ _reset: true, _clock: this.clockToJSON() })
106
138
  }
107
139
 
108
- // used be indexes etc to notify database listeners of new availability
109
- // async notifyExternal (source = 'unknown') {
110
- // // await this.notifyListeners({ _external: source, _clock: this.clockToJSON() })
111
- // }
112
-
113
140
  /**
114
141
  * Returns the changes made to the Fireproof instance since the specified event.
115
142
  * @function changesSince
@@ -119,6 +146,7 @@ export class Database {
119
146
  * @instance
120
147
  */
121
148
  async changesSince (aClock) {
149
+ await this.ready
122
150
  // console.log('events for', this.instanceId, aClock?.constructor.name)
123
151
  // console.log('changesSince', this.instanceId, this.clockToJSON(aClock), this.clockToJSON())
124
152
  let rows, dataCIDs, clockCIDs
@@ -162,6 +190,7 @@ export class Database {
162
190
  }
163
191
 
164
192
  async allDocuments () {
193
+ await this.ready
165
194
  const allResp = await getAll(this.blocks, this.clock, this.rootCache)
166
195
  this.rootCache = { root: allResp.root, clockCIDs: allResp.clockCIDs }
167
196
 
@@ -176,6 +205,7 @@ export class Database {
176
205
  }
177
206
 
178
207
  async allCIDs () {
208
+ await this.ready
179
209
  const allResp = await getAll(this.blocks, this.clock, this.rootCache, true)
180
210
  this.rootCache = { root: allResp.root, clockCIDs: allResp.clockCIDs }
181
211
  // console.log('allcids', allResp.cids, allResp.clockCIDs)
@@ -186,6 +216,7 @@ export class Database {
186
216
  }
187
217
 
188
218
  async allStoredCIDs () {
219
+ await this.ready
189
220
  const allCIDs = []
190
221
  for await (const { cid } of this.blocks.entries()) {
191
222
  allCIDs.push(cid)
@@ -193,24 +224,6 @@ export class Database {
193
224
  return allCIDs
194
225
  }
195
226
 
196
- /**
197
- * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
198
- *
199
- * @param {Object} doc - The document to validate.
200
- * @returns {Promise<void>}
201
- * @throws {Error} - Throws an error if the document is invalid.
202
- * @memberof Fireproof
203
- * @instance
204
- */
205
- async runValidation (doc) {
206
- if (this.config && this.config.validateChange) {
207
- const oldDoc = await this.get(doc._id)
208
- .then(doc => doc)
209
- .catch(() => ({}))
210
- this.config.validateChange(doc, oldDoc, this.authCtx)
211
- }
212
- }
213
-
214
227
  /**
215
228
  * Retrieves the document with the specified ID from the database
216
229
  *
@@ -221,6 +234,7 @@ export class Database {
221
234
  * @instance
222
235
  */
223
236
  async get (key, opts = {}) {
237
+ await this.ready
224
238
  const clock = opts.clock || this.clock
225
239
  const resp = await get(this.blocks, clock, charwise.encode(key), this.rootCache)
226
240
  this.rootCache = { root: resp.root, clockCIDs: resp.clockCIDs }
@@ -256,6 +270,7 @@ export class Database {
256
270
  * @instance
257
271
  */
258
272
  async put ({ _id, _proof, ...doc }) {
273
+ await this.ready
259
274
  const id = _id || 'f' + Math.random().toString(36).slice(2)
260
275
  await this.runValidation({ _id: id, ...doc })
261
276
  return await this.putToProllyTree({ key: id, value: doc }, doc._clock)
@@ -269,6 +284,7 @@ export class Database {
269
284
  * @instance
270
285
  */
271
286
  async del (docOrId) {
287
+ await this.ready
272
288
  let id
273
289
  let clock = null
274
290
  if (docOrId._id) {
@@ -283,6 +299,24 @@ export class Database {
283
299
  // return await this.putToProllyTree({ key: id, value: null }, clock)
284
300
  }
285
301
 
302
+ /**
303
+ * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
304
+ *
305
+ * @param {Object} doc - The document to validate.
306
+ * @returns {Promise<void>}
307
+ * @throws {Error} - Throws an error if the document is invalid.
308
+ * @memberof Fireproof
309
+ * @instance
310
+ */
311
+ async runValidation (doc) {
312
+ if (this.config && this.config.validateChange) {
313
+ const oldDoc = await this.get(doc._id)
314
+ .then(doc => doc)
315
+ .catch(() => ({}))
316
+ this.config.validateChange(doc, oldDoc, this.authCtx)
317
+ }
318
+ }
319
+
286
320
  /**
287
321
  * Updates the underlying storage with the specified event.
288
322
  * @private
@@ -395,12 +429,6 @@ export class Database {
395
429
  }
396
430
  }
397
431
 
398
- setCarUploader (carUploaderFn) {
399
- // console.log('registering car uploader')
400
- // https://en.wikipedia.org/wiki/Law_of_Demeter - this is a violation of the law of demeter
401
- this.blocks.valet.uploadFunction = carUploaderFn
402
- }
403
-
404
432
  setRemoteBlockReader (remoteBlockReaderFn) {
405
433
  this.blocks.remoteBlockFunction = remoteBlockReaderFn
406
434
  }
package/src/db-index.js CHANGED
@@ -247,6 +247,7 @@ export class DbIndex {
247
247
  * @returns
248
248
  */
249
249
  async applyQuery (resp, query) {
250
+ // console.log('applyQuery', resp, query)
250
251
  if (query.descending) {
251
252
  resp.result = resp.result.reverse()
252
253
  }
@@ -280,7 +281,7 @@ export class DbIndex {
280
281
  return await this.applyQuery(await this.indexByKey.root.range(...encodedRange), query)
281
282
  } else if (query.key) {
282
283
  const encodedKey = charwise.encode(query.key)
283
- return await this.applyQuery(this.indexByKey.root.get(encodedKey), query)
284
+ return await this.applyQuery(await this.indexByKey.root.get(encodedKey), query)
284
285
  } else {
285
286
  const { result, ...all } = await this.indexByKey.root.getAllEntries()
286
287
  return await this.applyQuery(
package/src/fireproof.js CHANGED
@@ -1,13 +1,8 @@
1
- import randomBytes from 'randombytes'
2
1
  import { Database, parseCID } from './database.js'
3
- import { Listener } from './listener.js'
4
2
  import { DbIndex as Index } from './db-index.js'
5
- // import { TransactionBlockstore } from './blockstore.js'
6
- import { localGet } from './utils.js'
7
3
  import { Sync } from './sync.js'
8
4
 
9
- // todo remove Listener in 0.7.0
10
- export { Index, Listener, Database, Sync }
5
+ export { Index, Database, Sync }
11
6
 
12
7
  export class Fireproof {
13
8
  /**
@@ -16,34 +11,47 @@ export class Fireproof {
16
11
  * Creates a new Fireproof instance with default storage settings
17
12
  * Most apps should use this and not worry about the details.
18
13
  * @static
19
- * @returns {Database} - a new Fireproof instance
14
+ * @returns {Database|Promise<Database>} - a new Fireproof instance or a promise for remote loaders
20
15
  */
21
16
  static storage = (name = null, opts = {}) => {
22
- if (name) {
23
- opts.name = name
24
- // todo this can come from a registry also eg remote database / config, etc
25
- const existing = localGet('fp.' + name)
26
- if (existing) {
27
- const existingConfig = JSON.parse(existing)
28
- return Fireproof.fromConfig(name, existingConfig, opts)
29
- } else {
30
- const instanceKey = randomBytes(32).toString('hex') // pass null to disable encryption
31
- opts.key = instanceKey
32
- return new Database(name, [], opts)
33
- }
17
+ if (!name) {
18
+ return new Database(null, opts)
34
19
  } else {
35
- return new Database(null, [], opts)
20
+ // const primaryLoader = Loader.appropriate(name, opts.primary, { key: null })
21
+ // const secondaryLoader = opts.secondary ? Loader.appropriate(name, opts.secondary, { key: null }) : null
22
+ const db = new Database(name, opts)
23
+ return db
24
+ // const loaders = [pr]
25
+
26
+ // todo we need branch names here
27
+
28
+ // console.log('storage', name, opts, primaryLoader, secondaryLoader)
36
29
  }
37
30
  }
38
31
 
39
- static fromConfig (name, existingConfig, opts = {}) {
40
- opts.key = existingConfig.key
41
- const fp = new Database(name, [], opts)
42
- return Fireproof.fromJSON(existingConfig, fp)
43
- }
32
+ // static fromConfig (name, primary, secondary, opts = {}) {
33
+ // console.log('fromConfig', name, primary, secondary, opts)
34
+ // let clock = []
35
+ // if (primary && primary.clock) {
36
+ // clock = clock.concat(primary.clock)
37
+ // }
38
+ // if (secondary && secondary.clock) {
39
+ // clock = clock.concat(secondary.clock)
40
+ // }
41
+
42
+ // const mergedClock = [...new Set(clock)].map(c => parseCID(c))
43
+
44
+ // opts.primaryHeader = primary
45
+ // opts.secondaryHeader = secondary
46
+
47
+ // opts.index = primary ? primary.index : {}
44
48
 
45
- static fromJSON (json, database) {
46
- database.hydrate({ car: json.car, indexCar: json.indexCar, clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key })
49
+ // const fp = new Database(name, mergedClock, opts)
50
+ // return Fireproof.fromJSON(primary, secondary, fp)
51
+ // }
52
+
53
+ static fromJSON (primary, secondary, database) {
54
+ const json = primary && primary.indexes ? primary : secondary
47
55
  if (json.indexes) {
48
56
  for (const {
49
57
  name,
@@ -66,8 +74,6 @@ export class Fireproof {
66
74
 
67
75
  static snapshot (database, clock) {
68
76
  const definition = database.toJSON()
69
- const withBlocks = new Database(database.name)
70
- withBlocks.blocks = database.blocks
71
77
  if (clock) {
72
78
  definition.clock = clock.map(c => parseCID(c))
73
79
  definition.indexes.forEach(index => {
@@ -76,7 +82,12 @@ export class Fireproof {
76
82
  index.clock.db = null
77
83
  })
78
84
  }
79
- const snappedDb = Fireproof.fromJSON(definition, withBlocks)
85
+
86
+ const withBlocks = new Database(database.name)
87
+ withBlocks.blocks = database.blocks
88
+ withBlocks.clock = definition.clock
89
+
90
+ const snappedDb = Fireproof.fromJSON(definition, null, withBlocks)
80
91
  ;[...database.indexes.values()].forEach(index => {
81
92
  snappedDb.indexes.get(index.mapFnString).mapFn = index.mapFn
82
93
  })