@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  Fireproof is a realtime database for today's interactive applications. It uses immutable data and distributed protocols
4
4
  to offer a new kind of database that:
5
5
  - can be embedded in any page or app, with a flexible data ownership model
6
- - scales without incurring developer costs, thanks to Filecoin
6
+ - can be hosted on any cloud
7
7
  - uses cryptographically verifiable protocols (what plants crave)
8
8
 
9
9
  Learn more about the [concepts and architecture behind Fireproof](https://fireproof.storage/documentation/how-the-database-engine-works/), or jump to the [quick start](#quick-start) for React and server-side examples.
@@ -71,7 +71,7 @@ export class TransactionBlockstore {
71
71
  return old;
72
72
  if (!this.valet)
73
73
  throw new Error('Missing block: ' + key);
74
- const got = await this.valet.getBlock(key);
74
+ const got = await this.valet.getValetBlock(key);
75
75
  this.committedBlocks.set(key, got);
76
76
  return got;
77
77
  }
package/dist/src/clock.js CHANGED
@@ -78,7 +78,7 @@ export class EventBlock extends Block {
78
78
  * @param {Uint8Array} config.bytes
79
79
  */
80
80
  constructor({ cid, value, bytes }) {
81
- // @ts-expect-error
81
+ // @ts-ignore
82
82
  super({ cid, value, bytes });
83
83
  }
84
84
  /**
@@ -129,7 +129,7 @@ export class EventFetcher {
129
129
  export async function encodeEventBlock(value) {
130
130
  // TODO: sort parents
131
131
  const { cid, bytes } = await encode({ value, codec: cbor, hasher: sha256 });
132
- // @ts-expect-error
132
+ // @ts-ignore
133
133
  return new Block({ cid, value, bytes });
134
134
  }
135
135
  /**
@@ -139,7 +139,7 @@ export async function encodeEventBlock(value) {
139
139
  */
140
140
  export async function decodeEventBlock(bytes) {
141
141
  const { cid, value } = await decode({ bytes, codec: cbor, hasher: sha256 });
142
- // @ts-expect-error
142
+ // @ts-ignore
143
143
  return new Block({ cid, value, bytes });
144
144
  }
145
145
  /**
@@ -227,7 +227,9 @@ export async function findEventsToSync(blocks, head) {
227
227
  // console.time(callTag + '.contains')
228
228
  const toSync = ancestor ? await asyncFilter(sorted, async (uks) => !(await contains(events, ancestor, uks.cid))) : sorted;
229
229
  // console.timeEnd(callTag + '.contains')
230
- console.log('optimize sorted', !!ancestor, sorted.length - toSync.length);
230
+ const sortDifference = sorted.length - toSync.length;
231
+ if (sortDifference / sorted.length > 0.6)
232
+ console.log('optimize sorted', !!ancestor, sortDifference);
231
233
  return { cids: events, events: toSync };
232
234
  }
233
235
  const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate)).then(results => arr.filter((_v, index) => results[index]));
@@ -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';
@@ -17,7 +17,6 @@ export const parseCID = cid => (typeof cid === 'string' ? CID.parse(cid) : cid);
17
17
  * This is the main class for saving and loading JSON and other documents with the database. You can find additional examples and
18
18
  * usage guides in the repository README.
19
19
  *
20
- * @param {import('./blockstore.js').TransactionBlockstore} blocks - The block storage instance to use documents and indexes
21
20
  * @param {CID[]} clock - The Merkle clock head to use for the Fireproof instance.
22
21
  * @param {object} [config] - Optional configuration options for the Fireproof instance.
23
22
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
@@ -28,10 +27,11 @@ export class Database {
28
27
  indexes = new Map();
29
28
  rootCache = null;
30
29
  eventsCache = new Map();
31
- constructor(blocks, clock, config = {}) {
32
- this.name = config.name;
30
+ constructor(name, clock, config = {}) {
31
+ this.name = name;
33
32
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`;
34
- this.blocks = blocks;
33
+ this.blocks = new TransactionBlockstore(name, config.key);
34
+ this.indexBlocks = new TransactionBlockstore(name + '.indexes', config.key);
35
35
  this.clock = clock;
36
36
  this.config = config;
37
37
  }
@@ -47,6 +47,8 @@ export class Database {
47
47
  clock: this.clockToJSON(),
48
48
  name: this.name,
49
49
  key: this.blocks.valet?.getKeyMaterial(),
50
+ car: this.blocks.valet?.valetRootCarCid.toString(),
51
+ indexCar: this.indexBlocks.valet?.valetRootCarCid?.toString(),
50
52
  indexes: [...this.indexes.values()].map(index => index.toJSON())
51
53
  };
52
54
  }
@@ -59,17 +61,31 @@ export class Database {
59
61
  clockToJSON(clock = null) {
60
62
  return (clock || this.clock).map(cid => cid.toString());
61
63
  }
62
- hydrate({ clock, name, key }) {
64
+ hydrate({ clock, name, key, car, indexCar }) {
63
65
  this.name = name;
64
66
  this.clock = clock;
65
67
  this.blocks.valet?.setKeyMaterial(key);
66
- this.indexBlocks = null;
68
+ this.blocks.valet?.setRootCarCid(car); // maybe
69
+ this.indexBlocks.valet?.setKeyMaterial(key);
70
+ this.indexBlocks.valet?.setRootCarCid(indexCar); // maybe
71
+ // this.indexBlocks = null
67
72
  }
68
73
  maybeSaveClock() {
69
74
  if (this.name && this.blocks.valet) {
70
75
  localSet('fp.' + this.name, JSON.stringify(this));
71
76
  }
72
77
  }
78
+ index(name) {
79
+ // iterate over the indexes and gather any with the same name
80
+ // if there are more than one, throw an error
81
+ // if there is one, return it
82
+ // if there are none, return null
83
+ const indexes = [...this.indexes.values()].filter(index => index.name === name);
84
+ if (indexes.length > 1) {
85
+ throw new Error(`Multiple indexes found with name ${name}`);
86
+ }
87
+ return indexes[0] || null;
88
+ }
73
89
  /**
74
90
  * Triggers a notification to all listeners
75
91
  * of the Fireproof instance so they can repaint UI, etc.
@@ -98,7 +114,7 @@ export class Database {
98
114
  let rows, dataCIDs, clockCIDs;
99
115
  // if (!aClock) aClock = []
100
116
  if (aClock && aClock.length > 0) {
101
- aClock = aClock.map((cid) => cid.toString());
117
+ aClock = aClock.map(cid => cid.toString());
102
118
  const eventKey = JSON.stringify([...this.clockToJSON(aClock), ...this.clockToJSON()]);
103
119
  let resp;
104
120
  if (this.eventsCache.has(eventKey)) {
@@ -210,12 +226,11 @@ export class Database {
210
226
  return doc;
211
227
  }
212
228
  /**
213
- * @typedef {Object} Document
229
+ * @typedef {any} Document
214
230
  * @property {string} _id - The ID of the document (required)
215
231
  * @property {string} [_proof] - The proof of the document (optional)
216
232
  * @property {string} [_clock] - The clock of the document (optional)
217
- * @property {any} [key: string] - Index signature notation to allow any other unknown fields
218
- * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
233
+ * @property {Object.<string, any>} [unknown: string] - Any other unknown properties (optional)
219
234
  */
220
235
  /**
221
236
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
@@ -259,6 +274,7 @@ export class Database {
259
274
  * @returns {Promise<{ proof:{}, id: string, clock: CID[] }>} - The result of adding the event to storage
260
275
  */
261
276
  async putToProllyTree(decodedEvent, clock = null) {
277
+ // console.log('putToProllyTree', decodedEvent)
262
278
  const event = encodeEvent(decodedEvent);
263
279
  if (clock && JSON.stringify(this.clockToJSON(clock)) !== JSON.stringify(this.clockToJSON())) {
264
280
  // console.log('this.clock', this.clockToJSON())
@@ -11,7 +11,7 @@ import { makeGetBlock, visMerkleTree } from './prolly.js';
11
11
  import { Database, cidsToProof } from './database.js';
12
12
  import * as codec from '@ipld/dag-cbor';
13
13
  // import { create as createBlock } from 'multiformats/block'
14
- import { TransactionBlockstore, doTransaction } from './blockstore.js';
14
+ import { doTransaction } from './blockstore.js';
15
15
  // @ts-ignore
16
16
  import charwise from 'charwise';
17
17
  const ALWAYS_REBUILD = false; // todo: remove
@@ -63,23 +63,23 @@ const makeDoc = ({ key, value }) => ({ _id: key, ...value });
63
63
  */
64
64
  const indexEntriesForChanges = (changes, mapFn) => {
65
65
  const indexEntries = [];
66
- changes.forEach(({ key, value, del }) => {
66
+ changes.forEach(({ key: _id, value, del }) => {
67
67
  // key is _id, value is the document
68
68
  if (del || !value)
69
69
  return;
70
70
  let mapCalled = false;
71
- const mapReturn = mapFn(makeDoc({ key, value }), (k, v) => {
71
+ const mapReturn = mapFn(makeDoc({ key: _id, value }), (k, v) => {
72
72
  mapCalled = true;
73
73
  if (typeof k === 'undefined')
74
74
  return;
75
75
  indexEntries.push({
76
- key: [charwise.encode(k), key],
76
+ key: [charwise.encode(k), _id],
77
77
  value: v || null
78
78
  });
79
79
  });
80
80
  if (!mapCalled && mapReturn) {
81
81
  indexEntries.push({
82
- key: [charwise.encode(mapReturn), key],
82
+ key: [charwise.encode(mapReturn), _id],
83
83
  value: null
84
84
  });
85
85
  }
@@ -102,9 +102,6 @@ export class DbIndex {
102
102
  */
103
103
  constructor(database, name, mapFn, clock = null, opts = {}) {
104
104
  this.database = database;
105
- if (!database.indexBlocks) {
106
- database.indexBlocks = new TransactionBlockstore(database?.name + '.indexes', database.blocks.valet?.getKeyMaterial());
107
- }
108
105
  if (typeof name === 'function') {
109
106
  // app is using deprecated API, remove in 0.7
110
107
  opts = clock || {};
@@ -249,7 +246,16 @@ export class DbIndex {
249
246
  return { result: [] };
250
247
  if (query.includeDocs === undefined)
251
248
  query.includeDocs = this.includeDocsDefault;
252
- if (query.range) {
249
+ if (query.prefix) {
250
+ // ensure prefix is an array
251
+ if (!Array.isArray(query.prefix))
252
+ query.prefix = [query.prefix];
253
+ const start = [...query.prefix, NaN];
254
+ const end = [...query.prefix, Infinity];
255
+ const prefixRange = [start, end].map(key => charwise.encode(key));
256
+ return await this.applyQuery(await this.indexByKey.root.range(...prefixRange), query);
257
+ }
258
+ else if (query.range) {
253
259
  const encodedRange = query.range.map(key => charwise.encode(key));
254
260
  return await this.applyQuery(await this.indexByKey.root.range(...encodedRange), query);
255
261
  }
@@ -1,5 +1,195 @@
1
1
  import * as multiformats from 'multiformats';
2
- import { CID } from 'multiformats';
2
+ import { Link, CID } from 'multiformats';
3
+ import * as multiformats_cid from 'multiformats/cid';
4
+
5
+ type AnyLink = Link<unknown, number, number, 1|0>
6
+
7
+ declare class Valet {
8
+ constructor(name: string, keyMaterial: any);
9
+ idb: any;
10
+ name: any;
11
+ uploadQueue: any;
12
+ alreadyEnqueued: Set<any>;
13
+ keyMaterial: any;
14
+ keyId: string;
15
+ valetRoot: any;
16
+ valetRootCid: any;
17
+ valetRootCarCid: any;
18
+ valetCidBlocks: VMemoryBlockstore;
19
+ instanceId: string;
20
+ /**
21
+ * Function installed by the database to upload car files
22
+ * @type {null|function(string, Uint8Array):Promise<void>}
23
+ */
24
+ uploadFunction: null | ((arg0: string, arg1: Uint8Array) => Promise<void>);
25
+ getKeyMaterial(): any;
26
+ setKeyMaterial(km: any): void;
27
+ /**
28
+ * Group the blocks into a car and write it to the valet.
29
+ * @param {import('./blockstore.js').InnerBlockstore} innerBlockstore
30
+ * @param {Set<string>} cids
31
+ * @returns {Promise<void>}
32
+ * @memberof Valet
33
+ */
34
+ writeTransaction(innerBlockstore: InnerBlockstore, cids: Set<string>): Promise<void>;
35
+ withDB: (dbWorkFun: any) => Promise<any>;
36
+ /**
37
+ * Iterate over all blocks in the store.
38
+ *
39
+ * @yields {{cid: string, value: Uint8Array}}
40
+ * @returns {AsyncGenerator<any, any, any>}
41
+ */
42
+ cids(): AsyncGenerator<any, any, any>;
43
+ setRootCarCid(cid: any): void;
44
+ getCarCIDForCID(cid: any): Promise<{
45
+ result: any;
46
+ }>;
47
+ OLDgetCarCIDForCID(cid: any): Promise<{
48
+ result: any;
49
+ }>;
50
+ getCombinedReader(carCid: any): Promise<{
51
+ root: any;
52
+ put: (cid: any, bytes: any) => Promise<void>;
53
+ get: (cid: any) => Promise<any>;
54
+ }>;
55
+ /**
56
+ *
57
+ * @param {string} carCid
58
+ * @param {*} value
59
+ */
60
+ parkCar(carCid: string, value: any, cids: any): Promise<void>;
61
+ remoteBlockFunction: any;
62
+ getCarReader(carCid: any): Promise<{
63
+ root: any;
64
+ get: (dataCID: any) => Promise<any>;
65
+ }>;
66
+ getValetBlock(dataCID: any): Promise<any>;
67
+ }
68
+ declare class VMemoryBlockstore {
69
+ /** @type {Map<string, Uint8Array>} */
70
+ blocks: Map<string, Uint8Array>;
71
+ instanceId: string;
72
+ get(cid: any): Promise<{
73
+ cid: any;
74
+ bytes: Uint8Array;
75
+ }>;
76
+ /**
77
+ * @param {import('../src/link').AnyLink} cid
78
+ * @param {Uint8Array} bytes
79
+ */
80
+ put(cid: AnyLink, bytes: Uint8Array): Promise<void>;
81
+ entries(): Generator<{
82
+ cid: multiformats_cid.Link<unknown, number, number, multiformats_cid.Version>;
83
+ bytes: Uint8Array;
84
+ }, void, unknown>;
85
+ }
86
+
87
+ /**
88
+ * @typedef {{ get: (link: import('../src/link').AnyLink) => Promise<AnyBlock | undefined> }} BlockFetcher
89
+ */
90
+ /**
91
+ * @typedef {Object} AnyBlock
92
+ * @property {import('./link').AnyLink} cid - The CID of the block
93
+ * @property {Uint8Array} bytes - The block's data
94
+ *
95
+ * @typedef {Object} Blockstore
96
+ * @property {function(import('./link').AnyLink): Promise<AnyBlock|undefined>} get - A function to retrieve a block by CID
97
+ * @property {function(import('./link').AnyLink, Uint8Array): Promise<void>} put - A function to store a block's data and CID
98
+ *
99
+ * A blockstore that caches writes to a transaction and only persists them when committed.
100
+ */
101
+ declare class TransactionBlockstore {
102
+ constructor(name: any, encryptionKey: any);
103
+ /** @type {Map<string, Uint8Array>} */
104
+ committedBlocks: Map<string, Uint8Array>;
105
+ /** @type {Valet} */
106
+ valet: Valet;
107
+ instanceId: string;
108
+ inflightTransactions: Set<any>;
109
+ syncs: Set<any>;
110
+ remoteBlockFunction: any;
111
+ /**
112
+ * Get a block from the store.
113
+ *
114
+ * @param {import('./link').AnyLink} cid
115
+ * @returns {Promise<AnyBlock | undefined>}
116
+ */
117
+ get(cid: AnyLink): Promise<AnyBlock | undefined>;
118
+ transactionsGet(key: any): Promise<any>;
119
+ committedGet(key: any): Promise<any>;
120
+ clearCommittedCache(): Promise<void>;
121
+ networkGet(key: any): Promise<any>;
122
+ /**
123
+ * Add a block to the store. Usually bound to a transaction by a closure.
124
+ * It sets the lastCid property to the CID of the block that was put.
125
+ * This is used by the transaction as the head of the car when written to the valet.
126
+ * We don't have to worry about which transaction we are when we are here because
127
+ * we are the transactionBlockstore.
128
+ *
129
+ * @param {import('./link').AnyLink} cid
130
+ * @param {Uint8Array} bytes
131
+ */
132
+ put(cid: AnyLink, bytes: Uint8Array): void;
133
+ /**
134
+ * Iterate over all blocks in the store.
135
+ *
136
+ * @yields {{cid: string, bytes: Uint8Array}}
137
+ * @returns {AsyncGenerator<any, any, any>}
138
+ */
139
+ entries(): AsyncGenerator<any, any, any>;
140
+ /**
141
+ * Begin a transaction. Ensures the uncommited blocks are empty at the begining.
142
+ * Returns the blocks to read and write during the transaction.
143
+ * @returns {InnerBlockstore}
144
+ * @memberof TransactionBlockstore
145
+ */
146
+ begin(label?: string): InnerBlockstore;
147
+ /**
148
+ * Commit the transaction. Writes the blocks to the store.
149
+ * @returns {Promise<void>}
150
+ * @memberof TransactionBlockstore
151
+ */
152
+ commit(innerBlockstore: any, doSync?: boolean): Promise<void>;
153
+ doCommit: (innerBlockstore: any) => Promise<void>;
154
+ /**
155
+ * Retire the transaction. Clears the uncommited blocks.
156
+ * @returns {void}
157
+ * @memberof TransactionBlockstore
158
+ */
159
+ retire(innerBlockstore: any): void;
160
+ }
161
+ declare class InnerBlockstore {
162
+ constructor(label: any, parentBlockstore: any);
163
+ /** @type {Map<string, Uint8Array>} */
164
+ blocks: Map<string, Uint8Array>;
165
+ lastCid: any;
166
+ label: string;
167
+ parentBlockstore: any;
168
+ /**
169
+ * @param {import('./link').AnyLink} cid
170
+ * @returns {Promise<AnyBlock | undefined>}
171
+ */
172
+ get(cid: AnyLink): Promise<AnyBlock | undefined>;
173
+ /**
174
+ * @param {import('./link').AnyLink} cid
175
+ * @param {Uint8Array} bytes
176
+ */
177
+ put(cid: AnyLink, bytes: Uint8Array): Promise<void>;
178
+ entries(): Generator<{
179
+ cid: multiformats.Link<unknown, number, number, multiformats.Version>;
180
+ bytes: Uint8Array;
181
+ }, void, unknown>;
182
+ }
183
+ type AnyBlock = {
184
+ /**
185
+ * - The CID of the block
186
+ */
187
+ cid: AnyLink;
188
+ /**
189
+ * - The block's data
190
+ */
191
+ bytes: Uint8Array;
192
+ };
3
193
 
4
194
  /**
5
195
  * @class Fireproof
@@ -7,21 +197,21 @@ import { CID } from 'multiformats';
7
197
  * This is the main class for saving and loading JSON and other documents with the database. You can find additional examples and
8
198
  * usage guides in the repository README.
9
199
  *
10
- * @param {import('./blockstore.js').TransactionBlockstore} blocks - The block storage instance to use documents and indexes
11
200
  * @param {CID[]} clock - The Merkle clock head to use for the Fireproof instance.
12
201
  * @param {object} [config] - Optional configuration options for the Fireproof instance.
13
202
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
14
203
  *
15
204
  */
16
205
  declare class Database {
17
- constructor(blocks: any, clock: any, config?: {});
206
+ constructor(name: any, clock: any, config?: {});
18
207
  listeners: Set<any>;
19
208
  indexes: Map<any, any>;
20
209
  rootCache: any;
21
210
  eventsCache: Map<any, any>;
22
211
  name: any;
23
212
  instanceId: string;
24
- blocks: any;
213
+ blocks: TransactionBlockstore;
214
+ indexBlocks: TransactionBlockstore;
25
215
  clock: any;
26
216
  config: {};
27
217
  /**
@@ -38,13 +228,15 @@ declare class Database {
38
228
  * @instance
39
229
  */
40
230
  clockToJSON(clock?: any): string[];
41
- hydrate({ clock, name, key }: {
231
+ hydrate({ clock, name, key, car, indexCar }: {
42
232
  clock: any;
43
233
  name: any;
44
234
  key: any;
235
+ car: any;
236
+ indexCar: any;
45
237
  }): void;
46
- indexBlocks: any;
47
238
  maybeSaveClock(): void;
239
+ index(name: any): any;
48
240
  /**
49
241
  * Triggers a notification to all listeners
50
242
  * of the Fireproof instance so they can repaint UI, etc.
@@ -99,12 +291,11 @@ declare class Database {
99
291
  _id: string;
100
292
  }>;
101
293
  /**
102
- * @typedef {Object} Document
294
+ * @typedef {any} Document
103
295
  * @property {string} _id - The ID of the document (required)
104
296
  * @property {string} [_proof] - The proof of the document (optional)
105
297
  * @property {string} [_clock] - The clock of the document (optional)
106
- * @property {any} [key: string] - Index signature notation to allow any other unknown fields
107
- * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
298
+ * @property {Object.<string, any>} [unknown: string] - Any other unknown properties (optional)
108
299
  */
109
300
  /**
110
301
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
@@ -114,31 +305,7 @@ declare class Database {
114
305
  * @memberof Fireproof
115
306
  * @instance
116
307
  */
117
- put({ _id, _proof, ...doc }: {
118
- /**
119
- * - The ID of the document (required)
120
- */
121
- _id: string;
122
- /**
123
- * - The proof of the document (optional)
124
- */
125
- _proof?: string;
126
- /**
127
- * - The clock of the document (optional)
128
- */
129
- _clock?: string;
130
- /**
131
- * : string] - Index signature notation to allow any other unknown fields
132
- * *
133
- */
134
- key?: any;
135
- /**
136
- * - Any other unknown properties (optional)
137
- */
138
- otherProperties?: {
139
- [x: string]: any;
140
- };
141
- }): Promise<{
308
+ put({ _id, _proof, ...doc }: any): Promise<{
142
309
  id: string;
143
310
  clock: CID[];
144
311
  }>;
@@ -405,6 +572,7 @@ declare class Fireproof {
405
572
  * @returns {Database} - a new Fireproof instance
406
573
  */
407
574
  static storage: (name?: any, opts?: {}) => Database;
575
+ static fromConfig(name: any, existingConfig: any, opts?: {}): any;
408
576
  static fromJSON(json: any, database: any): any;
409
577
  static snapshot(database: any, clock: any): any;
410
578
  static zoom(database: any, clock: any): Promise<any>;