@fireproof/core 0.5.16 → 0.5.18
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/README.md +1 -1
- package/dist/src/blockstore.js +1 -1
- package/dist/src/clock.js +4 -2
- package/dist/src/database.js +14 -8
- package/dist/src/db-index.js +15 -9
- package/dist/src/fireproof.d.ts +198 -6
- package/dist/src/fireproof.js +3102 -425
- package/dist/src/fireproof.js.map +1 -1
- package/dist/src/fireproof.mjs +3102 -425
- package/dist/src/fireproof.mjs.map +1 -1
- package/dist/src/sync.js +1 -1
- package/dist/src/valet.js +222 -41
- package/package.json +2 -1
- package/src/blockstore.js +1 -1
- package/src/clock.js +3 -2
- package/src/database.js +17 -9
- package/src/db-index.js +13 -12
- package/src/fireproof.js +15 -7
- package/src/sync.js +1 -1
- package/src/valet.js +239 -38
package/dist/src/sync.js
CHANGED
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
|
-
|
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'
|
151
|
-
await tx.objectStore('cars').put(value, carCid);
|
152
|
-
|
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
|
173
|
-
|
174
|
-
|
175
|
-
const
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
const
|
182
|
-
|
183
|
-
const
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@fireproof/core",
|
3
|
-
"version": "0.5.
|
3
|
+
"version": "0.5.18",
|
4
4
|
"description": "Cloudless database for apps, the browser, and IPFS",
|
5
5
|
"main": "dist/src/fireproof.js",
|
6
6
|
"module": "dist/src/fireproof.mjs",
|
@@ -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.
|
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
@@ -237,12 +237,13 @@ export async function findEventsToSync (blocks, head) {
|
|
237
237
|
// console.time(callTag + '.findCommonAncestorWithSortedEvents')
|
238
238
|
const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
|
239
239
|
// console.timeEnd(callTag + '.findCommonAncestorWithSortedEvents')
|
240
|
-
// console.log('sorted', !!ancestor, sorted
|
240
|
+
// console.log('sorted', !!ancestor, sorted)
|
241
241
|
// console.time(callTag + '.contains')
|
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
|
-
|
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 (
|
35
|
-
this.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 =
|
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.
|
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 () {
|
@@ -108,7 +113,7 @@ export class Database {
|
|
108
113
|
let rows, dataCIDs, clockCIDs
|
109
114
|
// if (!aClock) aClock = []
|
110
115
|
if (aClock && aClock.length > 0) {
|
111
|
-
aClock = aClock.map(
|
116
|
+
aClock = aClock.map(cid => cid.toString())
|
112
117
|
const eventKey = JSON.stringify([...this.clockToJSON(aClock), ...this.clockToJSON()])
|
113
118
|
|
114
119
|
let resp
|
@@ -276,6 +281,7 @@ export class Database {
|
|
276
281
|
* @returns {Promise<{ proof:{}, id: string, clock: CID[] }>} - The result of adding the event to storage
|
277
282
|
*/
|
278
283
|
async putToProllyTree (decodedEvent, clock = null) {
|
284
|
+
// console.log('putToProllyTree', decodedEvent)
|
279
285
|
const event = encodeEvent(decodedEvent)
|
280
286
|
if (clock && JSON.stringify(this.clockToJSON(clock)) !== JSON.stringify(this.clockToJSON())) {
|
281
287
|
// console.log('this.clock', this.clockToJSON())
|
@@ -393,7 +399,9 @@ export class Database {
|
|
393
399
|
|
394
400
|
export async function cidsToProof (cids) {
|
395
401
|
if (!cids) return []
|
396
|
-
if (!cids.all) {
|
402
|
+
if (!cids.all) {
|
403
|
+
return [...cids]
|
404
|
+
}
|
397
405
|
|
398
406
|
const all = await cids.all()
|
399
407
|
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 {
|
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),
|
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),
|
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.
|
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
|
-
|
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
|
-
|
30
|
+
opts.key = instanceKey
|
31
|
+
return new Database(name, [], opts)
|
31
32
|
}
|
32
33
|
} else {
|
33
|
-
return new Database(
|
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.
|
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 => {
|