@fireproof/core 0.0.2 → 0.0.4

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/src/fireproof.js CHANGED
@@ -1,28 +1,34 @@
1
- // import { vis } from './clock.js'
2
1
  import { put, get, getAll, eventsSince } from './prolly.js'
3
2
  import Blockstore, { doTransaction } from './blockstore.js'
4
3
 
5
4
  // const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
6
5
 
7
6
  /**
8
- * Represents a Fireproof instance that wraps a ProllyDB instance and Merkle clock head.
7
+ * @class Fireproof
8
+ * @classdesc Fireproof stores data in IndexedDB and provides a Merkle clock.
9
+ * This is the main class for saving and loading JSON and other documents with the database. You can find additional examples and
10
+ * usage guides in the repository README.
9
11
  *
10
- * @class
11
- * @classdesc A Fireproof instance can be used to store and retrieve values from a ProllyDB instance.
12
- *
13
- * @param {Blockstore} blocks - The block storage instance to use for the underlying ProllyDB instance.
14
- * @param {import('../clock').EventLink<import('../crdt').EventData>[]} clock - The Merkle clock head to use for the Fireproof instance.
12
+ * @param {Blockstore} blocks - The block storage instance to use documents and indexes
13
+ * @param {CID[]} clock - The Merkle clock head to use for the Fireproof instance.
15
14
  * @param {object} [config] - Optional configuration options for the Fireproof instance.
16
15
  * @param {object} [authCtx] - Optional authorization context object to use for any authentication checks.
17
16
  *
18
17
  */
19
-
20
18
  export default class Fireproof {
19
+ #listeners = new Set()
20
+
21
21
  /**
22
- * @param {Blockstore} blocks
23
- * @param {import('../clock').EventLink<import('../crdt').EventData>[]} clock
22
+ * @function storage
23
+ * @memberof Fireproof
24
+ * Creates a new Fireproof instance with default storage settings
25
+ * Most apps should use this and not worry about the details.
26
+ * @static
27
+ * @returns {Fireproof} - a new Fireproof instance
24
28
  */
25
- #listeners = new Set()
29
+ static storage = () => {
30
+ return new Fireproof(new Blockstore(), [])
31
+ }
26
32
 
27
33
  constructor (blocks, clock, config = {}, authCtx = {}) {
28
34
  this.blocks = blocks
@@ -33,12 +39,13 @@ export default class Fireproof {
33
39
  }
34
40
 
35
41
  /**
36
- * Returns a snapshot of the current Fireproof instance.
37
- *
38
- * @param {import('../clock').EventLink<import('../crdt').EventData>[]} clock
39
- * Clock to use for the snapshot.
42
+ * Returns a snapshot of the current Fireproof instance as a new instance.
43
+ * @function snapshot
44
+ * @param {CID[]} clock - The Merkle clock head to use for the snapshot.
40
45
  * @returns {Fireproof}
41
46
  * A new Fireproof instance representing the snapshot.
47
+ * @memberof Fireproof
48
+ * @instance
42
49
  */
43
50
  snapshot (clock) {
44
51
  // how to handle listeners, views, and config?
@@ -47,14 +54,26 @@ export default class Fireproof {
47
54
  }
48
55
 
49
56
  /**
50
- * This triggers a notification to all listeners of the Fireproof instance.
57
+ * Move the current instance to a new point in time. This triggers a notification to all listeners
58
+ * of the Fireproof instance so they can repaint UI, etc.
59
+ * @param {CID[] } clock
60
+ * Clock to use for the snapshot.
61
+ * @returns {Promise<void>}
62
+ * @memberof Fireproof
63
+ * @instance
51
64
  */
52
65
  async setClock (clock) {
53
66
  // console.log('setClock', this.instanceId, clock)
54
- this.clock = clock.map((item) => item['/'] ? item['/'] : item)
67
+ this.clock = clock.map((item) => (item['/'] ? item['/'] : item))
55
68
  await this.#notifyListeners({ reset: true, clock })
56
69
  }
57
70
 
71
+ /**
72
+ * Renders the Fireproof instance as a JSON object.
73
+ * @returns {Object} - The JSON representation of the Fireproof instance. Includes clock heads for the database and its indexes.
74
+ * @memberof Fireproof
75
+ * @instance
76
+ */
58
77
  toJSON () {
59
78
  // todo this also needs to return the index roots...
60
79
  return { clock: this.clock }
@@ -62,13 +81,11 @@ export default class Fireproof {
62
81
 
63
82
  /**
64
83
  * Returns the changes made to the Fireproof instance since the specified event.
65
- *
66
- * @param {import('../clock').EventLink<import('../crdt').EventData>?} event -
67
- * The event to retrieve changes since. If null or undefined, retrieves all changes.
68
- * @returns {Promise<{
69
- * rows: { key: string, value?: any, del?: boolean }[],
70
- * head: import('../clock').EventLink<import('../crdt').EventData>[]
71
- * }>} - An object `{rows : [...{key, value, del}], head}` containing the rows and the head of the instance's clock.
84
+ * @function changesSince
85
+ * @param {CID[]} [event] - The clock head to retrieve changes since. If null or undefined, retrieves all changes.
86
+ * @returns {Object<{rows : Object[], clock: CID[]}>} An object containing the rows and the head of the instance's clock.
87
+ * @memberof Fireproof
88
+ * @instance
72
89
  */
73
90
  async changesSince (event) {
74
91
  // console.log('changesSince', this.instanceId, event, this.clock)
@@ -97,6 +114,7 @@ export default class Fireproof {
97
114
  * Recieves live changes from the database after they are committed.
98
115
  * @param {Function} listener - The listener to be called when the clock is updated.
99
116
  * @returns {Function} - A function that can be called to unregister the listener.
117
+ * @memberof Fireproof
100
118
  */
101
119
  registerListener (listener) {
102
120
  this.#listeners.add(listener)
@@ -107,15 +125,21 @@ export default class Fireproof {
107
125
 
108
126
  async #notifyListeners (changes) {
109
127
  // await sleep(0)
110
- this.#listeners.forEach((listener) => listener(changes))
128
+ for (const listener of this.#listeners) {
129
+ await listener(changes)
130
+ }
111
131
  }
112
132
 
113
133
  /**
114
- * Runs validation on the specified document using the Fireproof instance's configuration.
134
+ * Runs validation on the specified document using the Fireproof instance's configuration. Throws an error if the document is invalid.
115
135
  *
116
136
  * @param {Object} doc - The document to validate.
137
+ * @returns {Promise<void>}
138
+ * @throws {Error} - Throws an error if the document is invalid.
139
+ * @memberof Fireproof
140
+ * @instance
117
141
  */
118
- async runValidation (doc) {
142
+ async #runValidation (doc) {
119
143
  if (this.config && this.config.validateChange) {
120
144
  const oldDoc = await this.get(doc._id)
121
145
  .then((doc) => doc)
@@ -125,35 +149,50 @@ export default class Fireproof {
125
149
  }
126
150
 
127
151
  /**
128
- * Adds a new document to the database, or updates an existing document.
152
+ * Adds a new document to the database, or updates an existing document. Returns the ID of the document and the new clock head.
129
153
  *
130
154
  * @param {Object} doc - the document to be added
131
155
  * @param {string} doc._id - the document ID. If not provided, a random ID will be generated.
132
156
  * @param {Object} doc.* - the document data to be added
133
- * @returns {Promise<import('./prolly').PutResult>} - the result of adding the document
157
+ * @returns {Object<{ id: string, clock: CID[] }>} - The result of adding the document to the database
158
+ * @memberof Fireproof
159
+ * @instance
134
160
  */
135
161
  async put ({ _id, ...doc }) {
136
162
  const id = _id || 'f' + Math.random().toString(36).slice(2)
137
- await this.runValidation({ _id: id, ...doc })
138
- return await this.putToProllyTree({ key: id, value: doc })
163
+ await this.#runValidation({ _id: id, ...doc })
164
+ return await this.#putToProllyTree({ key: id, value: doc })
139
165
  }
140
166
 
141
167
  /**
142
168
  * Deletes a document from the database
143
169
  * @param {string} id - the document ID
144
- * @returns {Promise<import('./prolly').PutResult>} - the result of deleting the document
170
+ * @returns {Object<{ id: string, clock: CID[] }>} - The result of deleting the document from the database
171
+ * @memberof Fireproof
172
+ * @instance
145
173
  */
146
174
  async del (id) {
147
- await this.runValidation({ _id: id, _deleted: true })
148
- // return await this.putToProllyTree({ key: id, del: true }) // not working at prolly tree layer?
175
+ await this.#runValidation({ _id: id, _deleted: true })
176
+ // return await this.#putToProllyTree({ key: id, del: true }) // not working at prolly tree layer?
149
177
  // this tombstone is temporary until we can get the prolly tree to delete
150
- return await this.putToProllyTree({ key: id, value: null })
178
+ return await this.#putToProllyTree({ key: id, value: null })
151
179
  }
152
180
 
153
- async putToProllyTree (event) {
154
- const result = await doTransaction('putToProllyTree', this.blocks, async (blocks) => await put(blocks, this.clock, event))
181
+ /**
182
+ * Updates the underlying storage with the specified event.
183
+ * @private
184
+ * @param {CID[]} event - the event to add
185
+ * @returns {Object<{ id: string, clock: CID[] }>} - The result of adding the event to storage
186
+ */
187
+ async #putToProllyTree (event) {
188
+ const result = await doTransaction(
189
+ '#putToProllyTree',
190
+ this.blocks,
191
+ async (blocks) => await put(blocks, this.clock, event)
192
+ )
155
193
  if (!result) {
156
- console.log('failed', event)
194
+ console.error('failed', event)
195
+ throw new Error('failed to put at storage layer')
157
196
  }
158
197
  this.clock = result.head // do we want to do this as a finally block
159
198
  result.id = event.key
@@ -164,8 +203,6 @@ export default class Fireproof {
164
203
  // /**
165
204
  // * Advances the clock to the specified event and updates the root CID
166
205
  // * Will be used by replication
167
- // * @param {import('../clock').EventLink<import('../crdt').EventData>} event - the event to advance to
168
- // * @returns {import('../clock').EventLink<import('../crdt').EventData>[]} - the new clock after advancing
169
206
  // */
170
207
  // async advance (event) {
171
208
  // this.clock = await advance(this.blocks, this.clock, event)
@@ -191,7 +228,9 @@ export default class Fireproof {
191
228
  * Retrieves the document with the specified ID from the database
192
229
  *
193
230
  * @param {string} key - the ID of the document to retrieve
194
- * @returns {Promise<import('./prolly').GetResult>} - the document with the specified ID
231
+ * @returns {Object<{_id: string, ...doc: Object}>} - the document with the specified ID
232
+ * @memberof Fireproof
233
+ * @instance
195
234
  */
196
235
  async get (key) {
197
236
  const got = await get(this.blocks, this.clock, key)
@@ -202,8 +241,15 @@ export default class Fireproof {
202
241
  got._id = key
203
242
  return got
204
243
  }
205
- }
206
244
 
207
- Fireproof.storage = (_email) => {
208
- return new Fireproof(new Blockstore(), [])
245
+ setCarUploader (carUploaderFn) {
246
+ console.log('registering car uploader')
247
+ // https://en.wikipedia.org/wiki/Law_of_Demeter - this is a violation of the law of demeter
248
+ this.blocks.valet.uploadFunction = carUploaderFn
249
+ }
250
+
251
+ setRemoteBlockReader (remoteBlockReaderFn) {
252
+ // console.log('registering remote block reader')
253
+ this.blocks.valet.remoteBlockFunction = remoteBlockReaderFn
254
+ }
209
255
  }
package/src/listener.js CHANGED
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * A Fireproof database Listener allows you to react to events in the database.
3
3
  *
4
- * @class
5
- * @classdesc An listener can be notified of events as they happen or on reconection
6
- *
7
- * @param {import('./fireproof').Fireproof} database - The Fireproof database instance to index.
8
- * @param {Function} eventFun - The map function to apply to each entry in the database.
4
+ * @class Listener
5
+ * @classdesc An listener attaches to a Fireproof database and runs a routing function on each change, sending the results to subscribers.
9
6
  *
7
+ * @param {Fireproof} database - The Fireproof database instance to index.
8
+ * @param {Function} routingFn - The routing function to apply to each entry in the database.
10
9
  */
10
+ // import { ChangeEvent } from './db-index'
11
+
11
12
  export default class Listener {
12
13
  #subcribers = new Map()
13
14
 
@@ -18,15 +19,10 @@ export default class Listener {
18
19
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef
19
20
  #doStopListening = null
20
21
 
21
- /**
22
- * Creates a new index with the given map function and database.
23
- * @param {import('./fireproof').Fireproof} database - The Fireproof database instance to index.
24
- * @param {Function} eventFun - The event function to apply to each current change to the database.
25
- */
26
- constructor (database, eventFun) {
27
- /** eventFun
22
+ constructor (database, routingFn) {
23
+ /** routingFn
28
24
  * The database instance to index.
29
- * @type {import('./fireproof').Fireproof}
25
+ * @type {Fireproof}
30
26
  */
31
27
  this.database = database
32
28
  this.#doStopListening = database.registerListener((changes) => this.#onChanges(changes))
@@ -34,7 +30,11 @@ export default class Listener {
34
30
  * The map function to apply to each entry in the database.
35
31
  * @type {Function}
36
32
  */
37
- this.eventFun = eventFun || function (_, emit) { emit('*') }
33
+ this.routingFn =
34
+ routingFn ||
35
+ function (_, emit) {
36
+ emit('*')
37
+ }
38
38
  this.dbHead = null
39
39
  }
40
40
 
@@ -43,13 +43,15 @@ export default class Listener {
43
43
  * @param {string} topic - The topic to subscribe to.
44
44
  * @param {Function} subscriber - The function to call when the topic is emitted.
45
45
  * @returns {Function} A function to unsubscribe from the topic.
46
+ * @memberof Listener
47
+ * @instance
46
48
  */
47
49
  on (topic, subscriber, since) {
48
50
  const listOfTopicSubscribers = getTopicList(this.#subcribers, topic)
49
51
  listOfTopicSubscribers.push(subscriber)
50
52
  if (typeof since !== 'undefined') {
51
53
  this.database.changesSince(since).then(({ rows: changes }) => {
52
- const keys = topicsForChanges(changes, this.eventFun).get(topic)
54
+ const keys = topicsForChanges(changes, this.routingFn).get(topic)
53
55
  if (keys) keys.forEach((key) => subscriber(key))
54
56
  })
55
57
  }
@@ -61,7 +63,7 @@ export default class Listener {
61
63
 
62
64
  #onChanges (changes) {
63
65
  if (Array.isArray(changes)) {
64
- const seenTopics = topicsForChanges(changes, this.eventFun)
66
+ const seenTopics = topicsForChanges(changes, this.routingFn)
65
67
  for (const [topic, keys] of seenTopics) {
66
68
  const listOfTopicSubscribers = getTopicList(this.#subcribers, topic)
67
69
  listOfTopicSubscribers.forEach((subscriber) => keys.forEach((key) => subscriber(key)))
@@ -94,15 +96,16 @@ const makeDoc = ({ key, value }) => ({ _id: key, ...value })
94
96
  /**
95
97
  * Transforms a set of changes to events using an emitter function.
96
98
  *
97
- * @param {Array<{ key: string, value: import('./link').AnyLink, del?: boolean }>} changes
98
- * @param {Function} eventFun
99
+ * @param {ChangeEvent[]} changes
100
+ * @param {Function} routingFn
99
101
  * @returns {Array<string>} The topics emmitted by the event function.
102
+ * @private
100
103
  */
101
- const topicsForChanges = (changes, eventFun) => {
104
+ const topicsForChanges = (changes, routingFn) => {
102
105
  const seenTopics = new Map()
103
106
  changes.forEach(({ key, value, del }) => {
104
107
  if (del || !value) value = { _deleted: true }
105
- eventFun(makeDoc({ key, value }), (t) => {
108
+ routingFn(makeDoc({ key, value }), (t) => {
106
109
  const topicList = getTopicList(seenTopics, t)
107
110
  topicList.push(key)
108
111
  })
package/src/prolly.js CHANGED
@@ -1,4 +1,10 @@
1
- import { advance, EventFetcher, EventBlock, findCommonAncestorWithSortedEvents, findUnknownSortedEvents } from './clock.js'
1
+ import {
2
+ advance,
3
+ EventFetcher,
4
+ EventBlock,
5
+ findCommonAncestorWithSortedEvents,
6
+ findUnknownSortedEvents
7
+ } from './clock.js'
2
8
  import { create, load } from 'prolly-trees/map'
3
9
  import * as codec from '@ipld/dag-cbor'
4
10
  import { sha256 as hasher } from 'multiformats/hashes/sha2'
@@ -21,7 +27,36 @@ const makeGetBlock = (blocks) => async (address) => {
21
27
  return createBlock({ cid, bytes, hasher, codec })
22
28
  }
23
29
 
24
- async function createAndSaveNewEvent (inBlocks, mblocks, getBlock, bigPut, root, { key, value, del }, head, additions, removals = []) {
30
+ /**
31
+ * Creates and saves a new event.
32
+ * @param {import('./blockstore.js').Blockstore} inBlocks - A persistent blockstore.
33
+ * @param {MemoryBlockstore} mblocks - A temporary blockstore.
34
+ * @param {Function} getBlock - A function that gets a block.
35
+ * @param {Function} bigPut - A function that puts a block.
36
+ * @param {import('prolly-trees/map').Root} root - The root node.
37
+ * @param {Object<{ key: string, value: any, del: boolean }>} event - The update event.
38
+ * @param {CID[]} head - The head of the event chain.
39
+ * @param {Array<import('multiformats/block').Block>} additions - A array of additions.
40
+ * @param {Array<mport('multiformats/block').Block>>} removals - An array of removals.
41
+ * @returns {Promise<{
42
+ * root: import('prolly-trees/map').Root,
43
+ * additions: Map<string, import('multiformats/block').Block>,
44
+ * removals: Array<string>,
45
+ * head: CID[],
46
+ * event: CID[]
47
+ * }>}
48
+ */
49
+ async function createAndSaveNewEvent (
50
+ inBlocks,
51
+ mblocks,
52
+ getBlock,
53
+ bigPut,
54
+ root,
55
+ { key, value, del },
56
+ head,
57
+ additions,
58
+ removals = []
59
+ ) {
25
60
  const data = {
26
61
  type: 'put',
27
62
  root: {
@@ -100,7 +135,7 @@ const prollyRootFromAncestor = async (events, ancestor, getBlock) => {
100
135
  * @param {import('./block').BlockFetcher} blocks Bucket block storage.
101
136
  * @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
102
137
  * @param {string} key The key of the value to put.
103
- * @param {import('./link').AnyLink} value The value to put.
138
+ * @param {CID} value The value to put.
104
139
  * @param {object} [options]
105
140
  * @returns {Promise<Result>}
106
141
  */
@@ -132,8 +167,16 @@ export async function put (inBlocks, head, event, options) {
132
167
  bigPut(nb, additions)
133
168
  }
134
169
 
135
- return createAndSaveNewEvent(inBlocks, mblocks, getBlock, bigPut,
136
- prollyRootBlock, event, head, Array.from(additions.values()) /*, Array.from(removals.values()) */)
170
+ return createAndSaveNewEvent(
171
+ inBlocks,
172
+ mblocks,
173
+ getBlock,
174
+ bigPut,
175
+ prollyRootBlock,
176
+ event,
177
+ head,
178
+ Array.from(additions.values()) /*, Array.from(removals.values()) */
179
+ )
137
180
  }
138
181
 
139
182
  /**
@@ -180,8 +223,11 @@ export async function eventsSince (blocks, head, since) {
180
223
  throw new Error('no head')
181
224
  }
182
225
  const sinceHead = [...since, ...head]
183
- const unknownSorted3 = await findUnknownSortedEvents(blocks, sinceHead,
184
- await findCommonAncestorWithSortedEvents(blocks, sinceHead))
226
+ const unknownSorted3 = await findUnknownSortedEvents(
227
+ blocks,
228
+ sinceHead,
229
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
230
+ )
185
231
  return unknownSorted3.map(({ value: { data } }) => data)
186
232
  }
187
233
 
@@ -218,18 +264,3 @@ export async function get (blocks, head, key) {
218
264
  const { result } = await prollyRootNode.get(key)
219
265
  return result
220
266
  }
221
-
222
- /**
223
- * @typedef {{
224
- * type: 'put'|'del'
225
- * key: string
226
- * value: import('./link').AnyLink
227
- * root: import('./shard').ShardLink
228
- * }} EventData
229
- *
230
- * @typedef {{
231
- * root: import('./shard').ShardLink
232
- * head: import('./clock').EventLink<EventData>[]
233
- * event: import('./clock').EventBlockView<EventData>
234
- * } & import('./db-index').ShardDiff} Result
235
- */