@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/README.md +1 -1
- package/dist/src/blockstore.js +1 -1
- package/dist/src/clock.js +6 -4
- package/dist/src/database.js +27 -11
- package/dist/src/db-index.js +15 -9
- package/dist/src/fireproof.d.ts +202 -34
- package/dist/src/fireproof.js +3270 -548
- package/dist/src/fireproof.js.map +1 -1
- package/dist/src/fireproof.mjs +3270 -548
- package/dist/src/fireproof.mjs.map +1 -1
- package/dist/src/valet.js +222 -41
- package/package.json +3 -2
- package/src/blockstore.js +1 -1
- package/src/clock.js +5 -4
- package/src/database.js +31 -12
- package/src/db-index.js +13 -12
- package/src/fireproof.js +15 -7
- package/src/valet.js +239 -38
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,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@fireproof/core",
|
3
|
-
"version": "0.5.
|
4
|
-
"description": "
|
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.
|
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-
|
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-
|
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-
|
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
|
-
|
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 () {
|
@@ -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(
|
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 {
|
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} [
|
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) {
|
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 {
|
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 => {
|