@fireproof/core 0.5.17 → 0.5.19

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.
package/dist/src/valet.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { CarReader } from '@ipld/car';
2
2
  import { CID } from 'multiformats/cid';
3
3
  import { sha256 } from 'multiformats/hashes/sha2';
4
+ import { parse } from 'multiformats/link';
4
5
  import * as CBW from '@ipld/car/buffer-writer';
5
6
  import * as raw from 'multiformats/codecs/raw';
6
7
  import * as Block from 'multiformats/block';
@@ -8,15 +9,19 @@ import * as dagcbor from '@ipld/dag-cbor';
8
9
  import { openDB } from 'idb';
9
10
  import cargoQueue from 'async/cargoQueue.js';
10
11
  // @ts-ignore
11
- import { bf } from 'prolly-trees/utils';
12
+ // @ts-ignore
13
+ import { bf, simpleCompare as compare } from 'prolly-trees/utils';
12
14
  // @ts-ignore
13
15
  import { nocache as cache } from 'prolly-trees/cache';
16
+ // import { makeGetBlock } from './prolly.js'
14
17
  import { encrypt, decrypt } from './crypto.js';
15
18
  import { Buffer } from 'buffer';
16
19
  // @ts-ignore
17
20
  import * as codec from 'encrypted-block';
21
+ import { create, load } from 'ipld-hashmap';
18
22
  import { rawSha1 as sha1sync } from './sha1.js';
19
23
  const chunker = bf(30);
24
+ const blockOpts = { cache, chunker, codec: dagcbor, hasher: sha256, compare };
20
25
  const NO_ENCRYPT = typeof process !== 'undefined' && !!process.env?.NO_ENCRYPT;
21
26
  // ? process.env.NO_ENCRYPT : import.meta && import.meta.env.VITE_NO_ENCRYPT
22
27
  export class Valet {
@@ -26,6 +31,11 @@ export class Valet {
26
31
  alreadyEnqueued = new Set();
27
32
  keyMaterial = null;
28
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);
29
39
  /**
30
40
  * Function installed by the database to upload car files
31
41
  * @type {null|function(string, Uint8Array):Promise<void>}
@@ -140,18 +150,113 @@ export class Valet {
140
150
  cursor = await cursor.continue();
141
151
  }
142
152
  }
153
+ setRootCarCid(cid) {
154
+ this.valetRootCarCid = cid;
155
+ this.valetRoot = null;
156
+ this.valetRootCid = null;
157
+ }
158
+ async getCarCIDForCID(cid) {
159
+ // make a car reader for this.valetRootCarCid
160
+ if (!this.valetRootCarCid)
161
+ return;
162
+ let indexNode;
163
+ if (this.valetRoot) {
164
+ indexNode = this.valetRoot;
165
+ }
166
+ else {
167
+ const combinedReader = await this.getCombinedReader(this.valetRootCarCid);
168
+ if (!this.valetRootCid) {
169
+ const root = combinedReader.root.cid;
170
+ // console.log('roots', this.instanceId, this.name, root, this.valetRootCarCid, this.valetRootCid)
171
+ this.valetRootCid = root;
172
+ }
173
+ indexNode = await load(combinedReader, this.valetRootCid, {
174
+ blockHasher: blockOpts.hasher,
175
+ blockCodec: blockOpts.codec
176
+ });
177
+ }
178
+ const got = await indexNode.get(cid);
179
+ // console.log('getCarCIDForCID', cid, got)
180
+ return { result: got };
181
+ }
182
+ async OLDgetCarCIDForCID(cid) {
183
+ const carCid = await this.withDB(async (db) => {
184
+ const tx = db.transaction(['cars', 'cidToCar'], 'readonly');
185
+ const indexResp = await tx.objectStore('cidToCar').index('cids').get(cid);
186
+ return indexResp?.car;
187
+ });
188
+ return { result: carCid };
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
+ const theseValetCidBlocks = this.valetCidBlocks;
197
+ // console.log('theseValetCidBlocks', theseValetCidBlocks)
198
+ const combinedReader = {
199
+ root: carMapReader?.root,
200
+ put: async (cid, bytes) => {
201
+ // console.log('mapPut', cid, bytes.length)
202
+ return await theseValetCidBlocks.put(cid, bytes);
203
+ },
204
+ get: async (cid) => {
205
+ // console.log('mapGet', cid)
206
+ try {
207
+ const got = await theseValetCidBlocks.get(cid);
208
+ return got.bytes;
209
+ }
210
+ catch (e) {
211
+ // console.log('get from car', cid, carMapReader)
212
+ if (!carMapReader)
213
+ throw e;
214
+ const bytes = await carMapReader.get(cid);
215
+ await theseValetCidBlocks.put(cid, bytes);
216
+ // console.log('mapGet', cid, bytes.length, bytes.constructor.name)
217
+ return bytes;
218
+ }
219
+ }
220
+ };
221
+ return combinedReader;
222
+ }
143
223
  /**
144
224
  *
145
225
  * @param {string} carCid
146
226
  * @param {*} value
147
227
  */
148
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(combinedReader, this.valetRoot, this.valetRootCid, Array.from(cids).map(cid => ({ key: cid.toString(), value: carCid.toString() })));
232
+ this.valetRoot = mapNode;
233
+ this.valetRootCid = mapNode.cid;
234
+ // make a block set with all the cids of the map
235
+ const saveValetBlocks = new VMemoryBlockstore(); // todo this blockstore should read from the last valetCid car also
236
+ for await (const cidx of mapNode.cids()) {
237
+ const bytes = await combinedReader.get(cidx);
238
+ saveValetBlocks.put(cidx, bytes);
239
+ }
240
+ let newValetCidCar;
241
+ if (this.keyMaterial) {
242
+ newValetCidCar = await blocksToEncryptedCarBlock(this.valetRootCid, saveValetBlocks, this.keyMaterial);
243
+ }
244
+ else {
245
+ newValetCidCar = await blocksToCarBlock(this.valetRootCid, saveValetBlocks);
246
+ }
247
+ // console.log('newValetCidCar', this.name, Math.floor(newValetCidCar.bytes.length / 1024))
149
248
  await this.withDB(async (db) => {
150
- const tx = db.transaction(['cars', 'cidToCar'], 'readwrite');
151
- await tx.objectStore('cars').put(value, carCid);
152
- await tx.objectStore('cidToCar').put({ pending: 'y', car: carCid, cids: Array.from(cids) });
249
+ const tx = db.transaction(['cars'], 'readwrite');
250
+ await tx.objectStore('cars').put(value, carCid.toString());
251
+ if (newValetCidCar) {
252
+ if (this.valetRootCarCid) {
253
+ // await tx.objectStore('cars').delete(this.valetRootCarCid.toString())
254
+ }
255
+ await tx.objectStore('cars').put(newValetCidCar.bytes, newValetCidCar.cid.toString());
256
+ }
153
257
  return await tx.done;
154
258
  });
259
+ this.valetRootCarCid = newValetCidCar.cid; // goes to clock
155
260
  // console.log('parked car', carCid, value.length, Array.from(cids))
156
261
  // upload to web3.storage if we have credentials
157
262
  if (this.uploadFunction) {
@@ -169,49 +274,73 @@ export class Valet {
169
274
  }
170
275
  }
171
276
  remoteBlockFunction = null;
172
- async getBlock(dataCID) {
173
- return await this.withDB(async (db) => {
174
- const tx = db.transaction(['cars', 'cidToCar'], 'readonly');
175
- const indexResp = await tx.objectStore('cidToCar').index('cids').get(dataCID);
176
- const carCid = indexResp?.car;
177
- if (!carCid) {
178
- throw new Error('Missing block: ' + dataCID);
179
- }
180
- const carBytes = await tx.objectStore('cars').get(carCid);
181
- const reader = await CarReader.fromBytes(carBytes);
182
- if (this.keyMaterial) {
183
- const roots = await reader.getRoots();
184
- const readerGetWithCodec = async (cid) => {
185
- const got = await reader.get(cid);
186
- // console.log('got.', cid.toString())
187
- let useCodec = codec;
188
- if (cid.toString().indexOf('bafy') === 0) {
189
- useCodec = dagcbor;
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 ???
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;
190
315
  }
191
- const decoded = await Block.decode({
192
- ...got,
193
- codec: useCodec,
194
- hasher: sha256
195
- });
196
- // console.log('decoded', decoded.value)
197
- return decoded;
198
- };
199
- const { blocks } = await blocksFromEncryptedCarBlock(roots[0], readerGetWithCodec, this.keyMaterial);
200
- const block = blocks.find(b => b.cid.toString() === dataCID);
201
- if (block) {
202
- return block.bytes;
203
316
  }
204
- }
205
- else {
206
- const gotBlock = await reader.get(CID.parse(dataCID));
207
- if (gotBlock) {
208
- return gotBlock.bytes;
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
+ }
209
327
  }
210
- }
211
- });
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);
212
340
  }
213
341
  }
214
342
  export const blocksToCarBlock = async (rootCids, blocks) => {
343
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
215
344
  let size = 0;
216
345
  if (!Array.isArray(rootCids)) {
217
346
  rootCids = [rootCids];
@@ -293,3 +422,55 @@ const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
293
422
  return blocksPromise;
294
423
  }
295
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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.5.17",
4
- "description": "Cloudless database for apps, the browser, and IPFS",
3
+ "version": "0.5.19",
4
+ "description": "Live data for your React app, powered by IPFS",
5
5
  "main": "dist/src/fireproof.js",
6
6
  "module": "dist/src/fireproof.mjs",
7
7
  "typings": "dist/src/fireproof.d.ts",
@@ -49,6 +49,7 @@
49
49
  "crypto-browserify": "^3.12.0",
50
50
  "encrypted-block": "^0.0.3",
51
51
  "idb": "^7.1.1",
52
+ "ipld-hashmap": "^2.1.18",
52
53
  "multiformats": "^11.0.1",
53
54
  "node-polyfill-webpack-plugin": "^2.0.1",
54
55
  "prolly-trees": "1.0.4",
package/src/blockstore.js CHANGED
@@ -80,7 +80,7 @@ export class TransactionBlockstore {
80
80
  // console.log('committedGet: ' + key + ' ' + this.instanceId, old.length)
81
81
  if (old) return old
82
82
  if (!this.valet) throw new Error('Missing block: ' + key)
83
- const got = await this.valet.getBlock(key)
83
+ const got = await this.valet.getValetBlock(key)
84
84
  this.committedBlocks.set(key, got)
85
85
  return got
86
86
  }
package/src/clock.js CHANGED
@@ -88,7 +88,7 @@ export class EventBlock extends Block {
88
88
  * @param {Uint8Array} config.bytes
89
89
  */
90
90
  constructor ({ cid, value, bytes }) {
91
- // @ts-expect-error
91
+ // @ts-ignore
92
92
  super({ cid, value, bytes })
93
93
  }
94
94
 
@@ -142,7 +142,7 @@ export class EventFetcher {
142
142
  export async function encodeEventBlock (value) {
143
143
  // TODO: sort parents
144
144
  const { cid, bytes } = await encode({ value, codec: cbor, hasher: sha256 })
145
- // @ts-expect-error
145
+ // @ts-ignore
146
146
  return new Block({ cid, value, bytes })
147
147
  }
148
148
 
@@ -153,7 +153,7 @@ export async function encodeEventBlock (value) {
153
153
  */
154
154
  export async function decodeEventBlock (bytes) {
155
155
  const { cid, value } = await decode({ bytes, codec: cbor, hasher: sha256 })
156
- // @ts-expect-error
156
+ // @ts-ignore
157
157
  return new Block({ cid, value, bytes })
158
158
  }
159
159
 
@@ -242,7 +242,8 @@ export async function findEventsToSync (blocks, head) {
242
242
 
243
243
  const toSync = ancestor ? await asyncFilter(sorted, async uks => !(await contains(events, ancestor, uks.cid))) : sorted
244
244
  // console.timeEnd(callTag + '.contains')
245
- console.log('optimize sorted', !!ancestor, sorted.length - toSync.length)
245
+ const sortDifference = sorted.length - toSync.length
246
+ if (sortDifference / sorted.length > 0.6) console.log('optimize sorted', !!ancestor, sortDifference)
246
247
 
247
248
  return { cids: events, events: toSync }
248
249
  }
package/src/database.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  import { visMerkleClock, visMerkleTree, vis, put, get, getAll, eventsSince } from './prolly.js'
3
- import { doTransaction } from './blockstore.js'
3
+ import { doTransaction, TransactionBlockstore } from './blockstore.js'
4
4
  import charwise from 'charwise'
5
5
  import { localSet } from './utils.js'
6
6
  import { CID } from 'multiformats'
@@ -19,7 +19,6 @@ export const parseCID = cid => (typeof cid === 'string' ? CID.parse(cid) : cid)
19
19
  * This is the main class for saving and loading JSON and other documents with the database. You can find additional examples and
20
20
  * usage guides in the repository README.
21
21
  *
22
- * @param {import('./blockstore.js').TransactionBlockstore} blocks - The block storage instance to use documents and indexes
23
22
  * @param {CID[]} clock - The Merkle clock head to use for the Fireproof instance.
24
23
  * @param {object} [config] - Optional configuration options for the Fireproof instance.
25
24
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
@@ -31,10 +30,11 @@ export class Database {
31
30
  rootCache = null
32
31
  eventsCache = new Map()
33
32
 
34
- constructor (blocks, clock, config = {}) {
35
- this.name = config.name
33
+ constructor (name, clock, config = {}) {
34
+ this.name = name
36
35
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
37
- this.blocks = blocks
36
+ this.blocks = new TransactionBlockstore(name, config.key)
37
+ this.indexBlocks = new TransactionBlockstore(name + '.indexes', config.key)
38
38
  this.clock = clock
39
39
  this.config = config
40
40
  }
@@ -51,6 +51,8 @@ export class Database {
51
51
  clock: this.clockToJSON(),
52
52
  name: this.name,
53
53
  key: this.blocks.valet?.getKeyMaterial(),
54
+ car: this.blocks.valet?.valetRootCarCid.toString(),
55
+ indexCar: this.indexBlocks.valet?.valetRootCarCid?.toString(),
54
56
  indexes: [...this.indexes.values()].map(index => index.toJSON())
55
57
  }
56
58
  }
@@ -65,11 +67,14 @@ export class Database {
65
67
  return (clock || this.clock).map(cid => cid.toString())
66
68
  }
67
69
 
68
- hydrate ({ clock, name, key }) {
70
+ hydrate ({ clock, name, key, car, indexCar }) {
69
71
  this.name = name
70
72
  this.clock = clock
71
73
  this.blocks.valet?.setKeyMaterial(key)
72
- this.indexBlocks = null
74
+ this.blocks.valet?.setRootCarCid(car) // maybe
75
+ this.indexBlocks.valet?.setKeyMaterial(key)
76
+ this.indexBlocks.valet?.setRootCarCid(indexCar) // maybe
77
+ // this.indexBlocks = null
73
78
  }
74
79
 
75
80
  maybeSaveClock () {
@@ -78,6 +83,18 @@ export class Database {
78
83
  }
79
84
  }
80
85
 
86
+ index (name) {
87
+ // iterate over the indexes and gather any with the same name
88
+ // if there are more than one, throw an error
89
+ // if there is one, return it
90
+ // if there are none, return null
91
+ const indexes = [...this.indexes.values()].filter(index => index.name === name)
92
+ if (indexes.length > 1) {
93
+ throw new Error(`Multiple indexes found with name ${name}`)
94
+ }
95
+ return indexes[0] || null
96
+ }
97
+
81
98
  /**
82
99
  * Triggers a notification to all listeners
83
100
  * of the Fireproof instance so they can repaint UI, etc.
@@ -108,7 +125,7 @@ export class Database {
108
125
  let rows, dataCIDs, clockCIDs
109
126
  // if (!aClock) aClock = []
110
127
  if (aClock && aClock.length > 0) {
111
- aClock = aClock.map((cid) => cid.toString())
128
+ aClock = aClock.map(cid => cid.toString())
112
129
  const eventKey = JSON.stringify([...this.clockToJSON(aClock), ...this.clockToJSON()])
113
130
 
114
131
  let resp
@@ -225,12 +242,11 @@ export class Database {
225
242
  return doc
226
243
  }
227
244
  /**
228
- * @typedef {Object} Document
245
+ * @typedef {any} Document
229
246
  * @property {string} _id - The ID of the document (required)
230
247
  * @property {string} [_proof] - The proof of the document (optional)
231
248
  * @property {string} [_clock] - The clock of the document (optional)
232
- * @property {any} [key: string] - Index signature notation to allow any other unknown fields
233
- * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
249
+ * @property {Object.<string, any>} [unknown: string] - Any other unknown properties (optional)
234
250
  */
235
251
 
236
252
  /**
@@ -276,6 +292,7 @@ export class Database {
276
292
  * @returns {Promise<{ proof:{}, id: string, clock: CID[] }>} - The result of adding the event to storage
277
293
  */
278
294
  async putToProllyTree (decodedEvent, clock = null) {
295
+ // console.log('putToProllyTree', decodedEvent)
279
296
  const event = encodeEvent(decodedEvent)
280
297
  if (clock && JSON.stringify(this.clockToJSON(clock)) !== JSON.stringify(this.clockToJSON())) {
281
298
  // console.log('this.clock', this.clockToJSON())
@@ -393,7 +410,9 @@ export class Database {
393
410
 
394
411
  export async function cidsToProof (cids) {
395
412
  if (!cids) return []
396
- if (!cids.all) { return [...cids] }
413
+ if (!cids.all) {
414
+ return [...cids]
415
+ }
397
416
 
398
417
  const all = await cids.all()
399
418
  return [...all].map(cid => cid.toString())
package/src/db-index.js CHANGED
@@ -13,7 +13,7 @@ import { Database, cidsToProof } from './database.js'
13
13
 
14
14
  import * as codec from '@ipld/dag-cbor'
15
15
  // import { create as createBlock } from 'multiformats/block'
16
- import { TransactionBlockstore, doTransaction } from './blockstore.js'
16
+ import { doTransaction } from './blockstore.js'
17
17
  // @ts-ignore
18
18
  import charwise from 'charwise'
19
19
 
@@ -69,21 +69,21 @@ const makeDoc = ({ key, value }) => ({ _id: key, ...value })
69
69
  */
70
70
  const indexEntriesForChanges = (changes, mapFn) => {
71
71
  const indexEntries = []
72
- changes.forEach(({ key, value, del }) => {
72
+ changes.forEach(({ key: _id, value, del }) => {
73
73
  // key is _id, value is the document
74
74
  if (del || !value) return
75
75
  let mapCalled = false
76
- const mapReturn = mapFn(makeDoc({ key, value }), (k, v) => {
76
+ const mapReturn = mapFn(makeDoc({ key: _id, value }), (k, v) => {
77
77
  mapCalled = true
78
78
  if (typeof k === 'undefined') return
79
79
  indexEntries.push({
80
- key: [charwise.encode(k), key],
80
+ key: [charwise.encode(k), _id],
81
81
  value: v || null
82
82
  })
83
83
  })
84
84
  if (!mapCalled && mapReturn) {
85
85
  indexEntries.push({
86
- key: [charwise.encode(mapReturn), key],
86
+ key: [charwise.encode(mapReturn), _id],
87
87
  value: null
88
88
  })
89
89
  }
@@ -107,12 +107,6 @@ export class DbIndex {
107
107
  */
108
108
  constructor (database, name, mapFn, clock = null, opts = {}) {
109
109
  this.database = database
110
- if (!database.indexBlocks) {
111
- database.indexBlocks = new TransactionBlockstore(
112
- database?.name + '.indexes',
113
- database.blocks.valet?.getKeyMaterial()
114
- )
115
- }
116
110
  if (typeof name === 'function') {
117
111
  // app is using deprecated API, remove in 0.7
118
112
  opts = clock || {}
@@ -265,7 +259,14 @@ export class DbIndex {
265
259
  await loadIndex(this.database.indexBlocks, this.indexByKey, dbIndexOpts)
266
260
  if (!this.indexByKey.root) return { result: [] }
267
261
  if (query.includeDocs === undefined) query.includeDocs = this.includeDocsDefault
268
- if (query.range) {
262
+ if (query.prefix) {
263
+ // ensure prefix is an array
264
+ if (!Array.isArray(query.prefix)) query.prefix = [query.prefix]
265
+ const start = [...query.prefix, NaN]
266
+ const end = [...query.prefix, Infinity]
267
+ const prefixRange = [start, end].map(key => charwise.encode(key))
268
+ return await this.applyQuery(await this.indexByKey.root.range(...prefixRange), query)
269
+ } else if (query.range) {
269
270
  const encodedRange = query.range.map(key => charwise.encode(key))
270
271
  return await this.applyQuery(await this.indexByKey.root.range(...encodedRange), query)
271
272
  } else if (query.key) {
package/src/fireproof.js CHANGED
@@ -2,7 +2,7 @@ import randomBytes from 'randombytes'
2
2
  import { Database, parseCID } from './database.js'
3
3
  import { Listener } from './listener.js'
4
4
  import { DbIndex as Index } from './db-index.js'
5
- import { TransactionBlockstore } from './blockstore.js'
5
+ // import { TransactionBlockstore } from './blockstore.js'
6
6
  import { localGet } from './utils.js'
7
7
  import { Sync } from './sync.js'
8
8
 
@@ -20,22 +20,29 @@ export class Fireproof {
20
20
  static storage = (name = null, opts = {}) => {
21
21
  if (name) {
22
22
  opts.name = name
23
+ // todo this can come from a registry also
23
24
  const existing = localGet('fp.' + name)
24
25
  if (existing) {
25
26
  const existingConfig = JSON.parse(existing)
26
- const fp = new Database(new TransactionBlockstore(name, existingConfig.key), [], opts)
27
- return Fireproof.fromJSON(existingConfig, fp)
27
+ return Fireproof.fromConfig(name, existingConfig, opts)
28
28
  } else {
29
29
  const instanceKey = randomBytes(32).toString('hex') // pass null to disable encryption
30
- return new Database(new TransactionBlockstore(name, instanceKey), [], opts)
30
+ opts.key = instanceKey
31
+ return new Database(name, [], opts)
31
32
  }
32
33
  } else {
33
- return new Database(new TransactionBlockstore(), [], opts)
34
+ return new Database(null, [], opts)
34
35
  }
35
36
  }
36
37
 
38
+ static fromConfig (name, existingConfig, opts = {}) {
39
+ opts.key = existingConfig.key
40
+ const fp = new Database(name, [], opts)
41
+ return Fireproof.fromJSON(existingConfig, fp)
42
+ }
43
+
37
44
  static fromJSON (json, database) {
38
- database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key })
45
+ database.hydrate({ car: json.car, indexCar: json.indexCar, clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key })
39
46
  if (json.indexes) {
40
47
  for (const {
41
48
  name,
@@ -58,7 +65,8 @@ export class Fireproof {
58
65
 
59
66
  static snapshot (database, clock) {
60
67
  const definition = database.toJSON()
61
- const withBlocks = new Database(database.blocks)
68
+ const withBlocks = new Database(database.name)
69
+ withBlocks.blocks = database.blocks
62
70
  if (clock) {
63
71
  definition.clock = clock.map(c => parseCID(c))
64
72
  definition.indexes.forEach(index => {