@fireproof/core 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,9 +8,28 @@ to offer a new kind of database that:
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.
10
10
 
11
+ ## Quick Start
12
+
13
+ Look in the `examples/` directory for projects using the database, or see [examples on CodePen](https://codepen.io/jchrisa/pen/GRYJJEM). If you are adding Fireproof to an existing page, just install it and try some operations.
14
+
15
+ ```sh
16
+ npm install @fireproof/core
17
+ ```
18
+
19
+ In your `app.js` or `app.tsx` file:
20
+
21
+ ```js
22
+ import { Fireproof } from '@fireproof/core'
23
+ const fireproof = Fireproof.storage("my-db")
24
+ const ok = await fireproof.put({ hello: 'world' })
25
+ const doc = await fireproof.get(ok.id)
26
+ ```
27
+
28
+ 🤫 I like to drop a `window.fireproof = fireproof` in there as a development aid.
29
+
11
30
  ### Status
12
31
 
13
- Fireproof is alpha software, you should only use it if you are planning to contribute. For now, [check out our React TodoMVC implementation running in browser-local mode.](https://main--lucky-naiad-5aa507.netlify.app/) It demonstrates document persistence, index queries, and event subscriptions, and uses the [`useFireproof()` React hook.](https://github.com/fireproof-storage/fireproof/blob/main/packages/fireproof/hooks/use-fireproof.tsx)
32
+ Fireproof is alpha software, ready for you to evaluate for your future applications. For now, [check out our React TodoMVC implementation running in browser-local mode.](https://main--lucky-naiad-5aa507.netlify.app/) It demonstrates document persistence, index queries, and event subscriptions, and uses the [`useFireproof()` React hook.](https://github.com/fireproof-storage/fireproof/blob/main/packages/fireproof/hooks/use-fireproof.tsx)
14
33
 
15
34
  [![Test](https://github.com/jchris/fireproof/actions/workflows/test.yml/badge.svg)](https://github.com/jchris/fireproof/actions/workflows/test.yml)
16
35
  [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
@@ -118,27 +137,6 @@ Fireproof is a synthesis of work done by people in the web community over the ye
118
137
 
119
138
  Thanks to Alan Shaw and Mikeal Rogers without whom this project would have never got started. The core Merkle hash-tree clock is based on [Alan's Pail](https://github.com/alanshaw/pail), and you can see the repository history goes all the way back to work begun as a branch of that repo. Mikeal wrote [the prolly trees implementation](https://github.com/mikeal/prolly-trees).
120
139
 
121
- ## Quick Start
122
-
123
- Look in the `examples/` directory for projects using the database. It's not picky how you use it, but we want to provide convenient jumping off places. Think of the examples as great to fork when starting your next project.
124
-
125
- If are adding Fireproof to an existing page, just install it and try some operations.
126
-
127
- ```sh
128
- npm install @fireproof/core
129
- ```
130
-
131
- In your `app.js` or `app.tsx` file:
132
-
133
- ```js
134
- import { Fireproof } from '@fireproof/core'
135
- const fireproof = Fireproof.storage()
136
- const ok = await fireproof.put({ hello: 'world' })
137
- const doc = await fireproof.get(ok.id)
138
- ```
139
-
140
- 🤫 I like to drop a `window.fireproof = fireproof` in there as a development aid.
141
-
142
140
  # Contributing
143
141
 
144
142
  Feel free to join in. All welcome. [Open an issue](https://github.com/jchris/fireproof/issues)!
@@ -1,3 +1,5 @@
1
+ import * as multiformats from 'multiformats';
2
+
1
3
  /**
2
4
  * Represents an DbIndex for a Fireproof database.
3
5
  *
@@ -15,7 +17,7 @@ declare class DbIndex {
15
17
  clock: any;
16
18
  name: any;
17
19
  }): DbIndex;
18
- constructor(database: any, mapFn: any, clock: any, opts?: {});
20
+ constructor(database: any, name: any, mapFn: any, clock?: any, opts?: {});
19
21
  database: any;
20
22
  mapFnString: any;
21
23
  mapFn: any;
@@ -30,7 +32,7 @@ declare class DbIndex {
30
32
  };
31
33
  dbHead: any;
32
34
  instanceId: string;
33
- updateIndexPromise: Promise<void>;
35
+ updateIndexPromise: Promise<any>;
34
36
  makeName(): any;
35
37
  toJSON(): {
36
38
  name: any;
@@ -54,7 +56,7 @@ declare class DbIndex {
54
56
  * @memberof DbIndex
55
57
  * @instance
56
58
  */
57
- query(query: {
59
+ query(query?: {
58
60
  /**
59
61
  * - The range to query.
60
62
  */
@@ -73,7 +75,7 @@ declare class DbIndex {
73
75
  * @returns {Promise<void>}
74
76
  */
75
77
  private updateIndex;
76
- innerUpdateIndex(inBlocks: any): Promise<void>;
78
+ innerUpdateIndex(inBlocks: any): Promise<any>;
77
79
  }
78
80
  /**
79
81
  * JDoc for the result row type.
@@ -105,7 +107,7 @@ type ChangeEvent = {
105
107
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
106
108
  *
107
109
  */
108
- declare class Database$1 {
110
+ declare class Database {
109
111
  constructor(blocks: any, clock: any, config?: {});
110
112
  listeners: Set<any>;
111
113
  name: any;
@@ -165,6 +167,7 @@ declare class Database$1 {
165
167
  clock: string[];
166
168
  proof: any[];
167
169
  }>;
170
+ allCIDs(): Promise<any[]>;
168
171
  /**
169
172
  * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
170
173
  *
@@ -188,13 +191,13 @@ declare class Database$1 {
188
191
  _id: string;
189
192
  }>;
190
193
  /**
191
- * @typedef {Object} Document
192
- * @property {string} _id - The ID of the document (required)
193
- * @property {string} [_proof] - The proof of the document (optional)
194
- * @property {string} [_clock] - The clock of the document (optional)
195
- * @property {any} [key: string] - Index signature notation to allow any other unknown fields
196
- * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
197
- */
194
+ * @typedef {Object} Document
195
+ * @property {string} _id - The ID of the document (required)
196
+ * @property {string} [_proof] - The proof of the document (optional)
197
+ * @property {string} [_clock] - The clock of the document (optional)
198
+ * @property {any} [key: string] - Index signature notation to allow any other unknown fields
199
+ * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
200
+ */
198
201
  /**
199
202
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
200
203
  *
@@ -249,6 +252,7 @@ declare class Database$1 {
249
252
  * @returns {Promise<{ proof:{}, id: string, clock: CID[] }>} - The result of adding the event to storage
250
253
  */
251
254
  private putToProllyTree;
255
+ applyClock(prevClock: any, newClock: any): void;
252
256
  vis(): AsyncGenerator<any, {
253
257
  cids: any;
254
258
  result: any;
@@ -297,10 +301,10 @@ declare class Listener {
297
301
  * @param {import('./database.js').Database} database
298
302
  * @param {(_: any, emit: any) => void} routingFn
299
303
  */
300
- constructor(database: Database$1, routingFn?: (_: any, emit: any) => void);
304
+ constructor(database: Database, routingFn?: (_: any, emit: any) => void);
301
305
  subcribers: Map<any, any>;
302
306
  doStopListening: any;
303
- database: Database$1;
307
+ database: Database;
304
308
  /**
305
309
  * The map function to apply to each entry in the database.
306
310
  * @type {Function}
@@ -339,6 +343,7 @@ declare class Fireproof {
339
343
  static fromJSON(json: any, database: any): any;
340
344
  static snapshot(database: any, clock: any): any;
341
345
  static zoom(database: any, clock: any): Promise<any>;
346
+ static makeCar(database: any, key: any): Promise<multiformats.BlockView<Uint8Array, 85, 18, 1>>;
342
347
  }
343
348
 
344
- export { Fireproof, DbIndex as Index, Listener };
349
+ export { Database, Fireproof, DbIndex as Index, Listener };
@@ -37118,6 +37118,7 @@ const blocksToCarBlock = async (lastCid, blocks) => {
37118
37118
  blocks = Array.from(blocks.entries());
37119
37119
  }
37120
37120
  for (const { cid, bytes } of blocks) {
37121
+ // console.log(cid, bytes)
37121
37122
  size += blockLength({ cid, bytes });
37122
37123
  }
37123
37124
  const buffer = new Uint8Array(size);
@@ -37697,7 +37698,8 @@ async function root (inBlocks, head) {
37697
37698
  */
37698
37699
  async function eventsSince (blocks, head, since) {
37699
37700
  if (!head.length) {
37700
- throw new Error('no head')
37701
+ // throw new Error('no head')
37702
+ return { clockCIDs: [], result: [] }
37701
37703
  }
37702
37704
  // @ts-ignore
37703
37705
  const sinceHead = [...since, ...head]; // ?
@@ -38135,7 +38137,7 @@ class Database {
38135
38137
  // console.log('change rows', this.instanceId, rows)
38136
38138
  } else {
38137
38139
  const allResp = await getAll(this.blocks, this.clock);
38138
- rows = allResp.result.map(({ key, value }) => (decodeEvent({ key, value })));
38140
+ rows = allResp.result.map(({ key, value }) => decodeEvent({ key, value }));
38139
38141
  dataCIDs = allResp.cids;
38140
38142
  // console.log('dbdoc rows', this.instanceId, rows)
38141
38143
  }
@@ -38148,7 +38150,9 @@ class Database {
38148
38150
 
38149
38151
  async allDocuments () {
38150
38152
  const allResp = await getAll(this.blocks, this.clock);
38151
- const rows = allResp.result.map(({ key, value }) => (decodeEvent({ key, value }))).map(({ key, value }) => ({ key, value: { _id: key, ...value } }));
38153
+ const rows = allResp.result
38154
+ .map(({ key, value }) => decodeEvent({ key, value }))
38155
+ .map(({ key, value }) => ({ key, value: { _id: key, ...value } }));
38152
38156
  return {
38153
38157
  rows,
38154
38158
  clock: this.clockToJSON(),
@@ -38156,6 +38160,15 @@ class Database {
38156
38160
  }
38157
38161
  }
38158
38162
 
38163
+ async allCIDs () {
38164
+ const allResp = await getAll(this.blocks, this.clock);
38165
+ const cids = await cidsToProof(allResp.cids);
38166
+ const clockCids = await cidsToProof(allResp.clockCIDs);
38167
+ // console.log('allcids', cids, clockCids)
38168
+ // todo we need to put the clock head as the last block in the encrypted car
38169
+ return [...cids, ...clockCids] // need a single block version of clock head, maybe an encoded block for it
38170
+ }
38171
+
38159
38172
  /**
38160
38173
  * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
38161
38174
  *
@@ -38168,7 +38181,7 @@ class Database {
38168
38181
  async runValidation (doc) {
38169
38182
  if (this.config && this.config.validateChange) {
38170
38183
  const oldDoc = await this.get(doc._id)
38171
- .then((doc) => doc)
38184
+ .then(doc => doc)
38172
38185
  .catch(() => ({}));
38173
38186
  this.config.validateChange(doc, oldDoc, this.authCtx);
38174
38187
  }
@@ -38203,13 +38216,13 @@ class Database {
38203
38216
  return doc
38204
38217
  }
38205
38218
  /**
38206
- * @typedef {Object} Document
38207
- * @property {string} _id - The ID of the document (required)
38208
- * @property {string} [_proof] - The proof of the document (optional)
38209
- * @property {string} [_clock] - The clock of the document (optional)
38210
- * @property {any} [key: string] - Index signature notation to allow any other unknown fields
38211
- * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
38212
- */
38219
+ * @typedef {Object} Document
38220
+ * @property {string} _id - The ID of the document (required)
38221
+ * @property {string} [_proof] - The proof of the document (optional)
38222
+ * @property {string} [_clock] - The clock of the document (optional)
38223
+ * @property {any} [key: string] - Index signature notation to allow any other unknown fields
38224
+ * * @property {Object.<string, any>} [otherProperties] - Any other unknown properties (optional)
38225
+ */
38213
38226
 
38214
38227
  /**
38215
38228
  * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
@@ -38264,17 +38277,17 @@ class Database {
38264
38277
  throw new Error('MVCC conflict, document is changed, please reload the document and try again.')
38265
38278
  }
38266
38279
  }
38280
+ const prevClock = [...this.clock];
38267
38281
  const result = await doTransaction(
38268
38282
  'putToProllyTree',
38269
38283
  this.blocks,
38270
- async (blocks) => await put(blocks, this.clock, event)
38284
+ async blocks => await put(blocks, this.clock, event)
38271
38285
  );
38272
38286
  if (!result) {
38273
38287
  console.error('failed', event);
38274
38288
  throw new Error('failed to put at storage layer')
38275
38289
  }
38276
- // console.log('new clock head', this.instanceId, result.head.toString())
38277
- this.clock = result.head; // do we want to do this as a finally block
38290
+ this.applyClock(prevClock, result.head);
38278
38291
  await this.notifyListeners([decodedEvent]); // this type is odd
38279
38292
  return {
38280
38293
  id: decodedEvent.key,
@@ -38284,6 +38297,12 @@ class Database {
38284
38297
  // todo should include additions (or split clock)
38285
38298
  }
38286
38299
 
38300
+ applyClock (prevClock, newClock) {
38301
+ // console.log('applyClock', prevClock, newClock, this.clock)
38302
+ const removedprevCIDs = this.clock.filter(cid => prevClock.indexOf(cid) === -1);
38303
+ this.clock = removedprevCIDs.concat(newClock);
38304
+ }
38305
+
38287
38306
  // /**
38288
38307
  // * Advances the clock to the specified event and updates the root CID
38289
38308
  // * Will be used by replication
@@ -38342,7 +38361,7 @@ class Database {
38342
38361
  async function cidsToProof (cids) {
38343
38362
  if (!cids || !cids.all) return []
38344
38363
  const all = await cids.all();
38345
- return [...all].map((cid) => cid.toString())
38364
+ return [...all].map(cid => cid.toString())
38346
38365
  }
38347
38366
 
38348
38367
  function decodeEvent (event) {
@@ -38652,10 +38671,10 @@ const indexEntriesForChanges = (changes, mapFn) => {
38652
38671
  changes.forEach(({ key, value, del }) => {
38653
38672
  if (del || !value) return
38654
38673
  mapFn(makeDoc({ key, value }), (k, v) => {
38655
- if (typeof v === 'undefined' || typeof k === 'undefined') return
38674
+ if (typeof k === 'undefined') return
38656
38675
  indexEntries.push({
38657
38676
  key: [charwise.encode(k), key],
38658
- value: v
38677
+ value: v || null
38659
38678
  });
38660
38679
  });
38661
38680
  });
@@ -38673,7 +38692,7 @@ const indexEntriesForChanges = (changes, mapFn) => {
38673
38692
  *
38674
38693
  */
38675
38694
  class DbIndex {
38676
- constructor (database, mapFn, clock, opts = {}) {
38695
+ constructor (database, name, mapFn, clock = null, opts = {}) {
38677
38696
  this.database = database;
38678
38697
  if (!database.indexBlocks) {
38679
38698
  database.indexBlocks = new TransactionBlockstore(database?.name + '.indexes', database.blocks.valet?.getKeyMaterial());
@@ -38689,7 +38708,7 @@ class DbIndex {
38689
38708
  this.mapFn = mapFn;
38690
38709
  this.mapFnString = mapFn.toString();
38691
38710
  }
38692
- this.name = opts.name || this.makeName();
38711
+ this.name = name || this.makeName();
38693
38712
  this.indexById = { root: null, cid: null };
38694
38713
  this.indexByKey = { root: null, cid: null };
38695
38714
  this.dbHead = null;
@@ -38739,7 +38758,7 @@ class DbIndex {
38739
38758
 
38740
38759
  static fromJSON (database, { code, clock, name }) {
38741
38760
  // console.log('DbIndex.fromJSON', database.constructor.name, code, clock)
38742
- return new DbIndex(database, code, clock, { name })
38761
+ return new DbIndex(database, name, code, clock)
38743
38762
  }
38744
38763
 
38745
38764
  /**
@@ -38756,7 +38775,7 @@ class DbIndex {
38756
38775
  * @memberof DbIndex
38757
38776
  * @instance
38758
38777
  */
38759
- async query (query, update = true) {
38778
+ async query (query = {}, update = true) {
38760
38779
  // const callId = Math.random().toString(36).substring(2, 7)
38761
38780
  // todo pass a root to query a snapshot
38762
38781
  // console.time(callId + '.updateIndex')
@@ -38783,7 +38802,12 @@ class DbIndex {
38783
38802
  async updateIndex (blocks) {
38784
38803
  // todo this could enqueue the request and give fresh ones to all second comers -- right now it gives out stale promises while working
38785
38804
  // what would it do in a world where all indexes provide a database snapshot to query?
38786
- if (this.updateIndexPromise) return this.updateIndexPromise
38805
+ if (this.updateIndexPromise) {
38806
+ return this.updateIndexPromise.then(() => {
38807
+ this.updateIndexPromise = null;
38808
+ return this.updateIndex(blocks)
38809
+ })
38810
+ }
38787
38811
  this.updateIndexPromise = this.innerUpdateIndex(blocks);
38788
38812
  this.updateIndexPromise.finally(() => { this.updateIndexPromise = null; });
38789
38813
  return this.updateIndexPromise
@@ -38804,7 +38828,7 @@ class DbIndex {
38804
38828
  this.dbHead = result.clock;
38805
38829
  return
38806
38830
  }
38807
- await doTransaction('updateIndex', inBlocks, async (blocks) => {
38831
+ const didT = await doTransaction('updateIndex', inBlocks, async (blocks) => {
38808
38832
  let oldIndexEntries = [];
38809
38833
  let removeByIdIndexEntries = [];
38810
38834
  await loadIndex(blocks, this.indexById, idIndexOpts);
@@ -38826,6 +38850,7 @@ class DbIndex {
38826
38850
  this.database.notifyExternal('dbIndex');
38827
38851
  // console.timeEnd(callTag + '.doTransactionupdateIndex')
38828
38852
  // console.log(`updateIndex ${callTag} <`, this.instanceId, this.dbHead?.toString(), this.indexByKey.cid?.toString(), this.indexById.cid?.toString())
38853
+ return didT
38829
38854
  }
38830
38855
  }
38831
38856
 
@@ -38868,7 +38893,11 @@ async function bulkIndex (blocks, inIndex, indexEntries, opts) {
38868
38893
  async function loadIndex (blocks, index, indexOpts) {
38869
38894
  if (!index.root) {
38870
38895
  const cid = index.cid;
38871
- if (!cid) return
38896
+ if (!cid) {
38897
+ // console.log('no cid', index)
38898
+ // throw new Error('cannot load index')
38899
+ return null
38900
+ }
38872
38901
  const { getBlock } = makeGetBlock(blocks);
38873
38902
  index.root = await load({ cid, get: getBlock, ...indexOpts });
38874
38903
  }
@@ -38895,7 +38924,7 @@ async function doIndexQuery (blocks, indexByKey, query = {}) {
38895
38924
  }
38896
38925
  }
38897
38926
 
38898
- const parseCID = cid => typeof cid === 'string' ? CID$1.parse(cid) : cid;
38927
+ const parseCID = cid => (typeof cid === 'string' ? CID$1.parse(cid) : cid);
38899
38928
 
38900
38929
  class Fireproof {
38901
38930
  /**
@@ -38926,7 +38955,11 @@ class Fireproof {
38926
38955
  static fromJSON (json, database) {
38927
38956
  database.hydrate({ clock: json.clock.map(c => parseCID(c)), name: json.name, key: json.key });
38928
38957
  if (json.indexes) {
38929
- for (const { name, code, clock: { byId, byKey, db } } of json.indexes) {
38958
+ for (const {
38959
+ name,
38960
+ code,
38961
+ clock: { byId, byKey, db }
38962
+ } of json.indexes) {
38930
38963
  DbIndex.fromJSON(database, {
38931
38964
  clock: {
38932
38965
  byId: byId ? parseCID(byId) : null,
@@ -38953,14 +38986,14 @@ class Fireproof {
38953
38986
  });
38954
38987
  }
38955
38988
  const snappedDb = this.fromJSON(definition, withBlocks)
38956
- ;([...database.indexes.values()]).forEach(index => {
38989
+ ;[...database.indexes.values()].forEach(index => {
38957
38990
  snappedDb.indexes.get(index.mapFnString).mapFn = index.mapFn;
38958
38991
  });
38959
38992
  return snappedDb
38960
38993
  }
38961
38994
 
38962
38995
  static async zoom (database, clock) {
38963
- ([...database.indexes.values()]).forEach(index => {
38996
+ [...database.indexes.values()].forEach(index => {
38964
38997
  index.indexById = { root: null, cid: null };
38965
38998
  index.indexByKey = { root: null, cid: null };
38966
38999
  index.dbHead = null;
@@ -38969,8 +39002,44 @@ class Fireproof {
38969
39002
  await database.notifyReset(); // hmm... indexes should listen to this? might be more complex than worth it. so far this is the only caller
38970
39003
  return database
38971
39004
  }
39005
+
39006
+ // get all the cids
39007
+ // tell valet to make a file
39008
+ static async makeCar (database, key) {
39009
+ const allCIDs = await database.allCIDs();
39010
+ const blocks = database.blocks;
39011
+
39012
+ const rootCid = CID$1.parse(allCIDs[allCIDs.length - 1]);
39013
+ if (typeof key === 'undefined') {
39014
+ key = blocks.valet?.getKeyMaterial();
39015
+ }
39016
+ if (key) {
39017
+ return blocksToEncryptedCarBlock(
39018
+ rootCid,
39019
+ {
39020
+ entries: () => allCIDs.map(cid => ({ cid })),
39021
+ get: async cid => await blocks.get(cid)
39022
+ },
39023
+ key
39024
+ )
39025
+ } else {
39026
+ const carBlocks = await Promise.all(
39027
+ allCIDs.map(async c => {
39028
+ const b = await blocks.get(c);
39029
+ // console.log('block', b)
39030
+ if (typeof b.cid === 'string') { b.cid = CID$1.parse(b.cid); }
39031
+ // if (b.bytes.constructor.name === 'Buffer') console.log('conver vbuff')
39032
+ return b
39033
+ })
39034
+ );
39035
+ return blocksToCarBlock(rootCid, {
39036
+ entries: () => carBlocks
39037
+ })
39038
+ }
39039
+ }
38972
39040
  }
38973
39041
 
39042
+ exports.Database = Database;
38974
39043
  exports.Fireproof = Fireproof;
38975
39044
  exports.Index = DbIndex;
38976
39045
  exports.Listener = Listener;