@fireproof/core 0.0.4 → 0.0.6

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.
@@ -214,4 +214,5 @@ describe('Index query with bad index definition', () => {
214
214
  console.error = oldErrFn
215
215
  })
216
216
  })
217
+ it.skip('reproduce missing block error from browser so we can turn off always rebuild', async () => {})
217
218
  })
@@ -20,12 +20,84 @@ describe('Fireproof', () => {
20
20
  it('put and get document', async () => {
21
21
  assert(resp0.id, 'should have id')
22
22
  assert.equal(resp0.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
23
-
24
23
  const avalue = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
25
24
  assert.equal(avalue.name, 'alice')
26
25
  assert.equal(avalue.age, 42)
27
26
  assert.equal(avalue._id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
28
27
  })
28
+ it('mvcc put and get document with _clock that matches', async () => {
29
+ assert(resp0.clock, 'should have clock')
30
+ assert.equal(resp0.clock[0].toString(), 'bafyreieth2ckopwivda5mf6vu76xwqvox3q5wsaxgbmxy2dgrd4hfuzmma')
31
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
32
+ theDoc._clock = database.clock
33
+ const put2 = await database.put(theDoc)
34
+ assert.equal(put2.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
35
+ assert.equal(put2.clock.length, 1)
36
+ assert.equal(put2.clock[0].toString(), 'bafyreida2c2ckhjfoz5ulmbbfe66ey4svvedrl4tzbvtoxags2qck7lj2i')
37
+ })
38
+ it('get should return an object instance that is not the same as the one in the db', async () => {
39
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
40
+ const theDoc2 = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
41
+ assert.notEqual(theDoc, theDoc2)
42
+ theDoc.name = 'really alice'
43
+ assert.equal(theDoc.name, 'really alice')
44
+ assert.equal(theDoc2.name, 'alice')
45
+ })
46
+ it('get with mvcc option', async () => {
47
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
48
+ assert(theDoc._clock, 'should have _clock')
49
+ assert.equal(theDoc._clock[0].toString(), 'bafyreieth2ckopwivda5mf6vu76xwqvox3q5wsaxgbmxy2dgrd4hfuzmma')
50
+ })
51
+ it('get with mvcc option where someone else changed another document first', async () => {
52
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
53
+ const put2 = await database.put({ something: 'else' })
54
+ assert(put2.clock, 'should have clock')
55
+ assert.notEqual(put2.clock.toString(), resp0.clock.toString())
56
+ assert.equal(theDoc._clock.toString(), resp0.clock.toString())
57
+ theDoc.name = 'somone else'
58
+ const put3works = await database.put(theDoc)
59
+ assert(put3works.clock, 'should have id')
60
+ })
61
+ it('get from an old snapshot with mvcc option', async () => {
62
+ const ogClock = resp0.clock
63
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
64
+ theDoc.name = 'not alice'
65
+ const put2 = await database.put(theDoc)
66
+ assert.equal(put2.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
67
+ assert.notEqual(put2.clock.toString(), ogClock.toString())
68
+ const theDoc2 = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { clock: ogClock })
69
+ assert.equal(theDoc2.name, 'alice')
70
+ })
71
+ it('put and get document with _clock that does not match b/c the doc changed', async () => {
72
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
73
+ theDoc.name = 'not alice'
74
+ const put2 = await database.put(theDoc)
75
+ assert.equal(put2.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
76
+ assert.notEqual(put2.clock.toString(), theDoc._clock.toString())
77
+
78
+ const err = await database.put(theDoc).catch((err) => err)
79
+ assert.match(err.message, /MVCC conflict/)
80
+ })
81
+ it('put and get document with _clock that does not match b/c a different doc changed should succeed', async () => {
82
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
83
+ assert.equal(theDoc.name, 'alice')
84
+
85
+ const putAnotherDoc = await database.put({ nothing: 'to see here' })
86
+ assert.notEqual(putAnotherDoc.clock.toString(), theDoc._clock.toString())
87
+
88
+ const ok = await database.put({ name: "isn't alice", ...theDoc })
89
+ assert.equal(ok.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
90
+ })
91
+ it('put and get document with _clock that does not match b/c the doc was deleted', async () => {
92
+ const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
93
+ assert.equal(theDoc.name, 'alice')
94
+ const del = await database.del(theDoc)
95
+ assert(del.id)
96
+ const err = await database.put(theDoc).catch((err) => err)
97
+ console.log('err', err)
98
+ assert.match(err.message, /MVCC conflict/)
99
+ })
100
+
29
101
  it('has a factory for making new instances with default settings', async () => {
30
102
  // TODO if you pass it an email it asks the local keyring, and if no key, does the email validation thing
31
103
  const db = await Fireproof.storage({ email: 'jchris@gmail.com' })
@@ -0,0 +1,66 @@
1
+ import { describe, it, beforeEach } from 'mocha'
2
+ import assert from 'node:assert'
3
+ import Fireproof from '../src/fireproof.js'
4
+ import flexsearch from 'flexsearch'
5
+ const { Index } = flexsearch
6
+ // this is an illustration of how to use the flexsearch library
7
+
8
+ let database, flexed
9
+
10
+ describe('Fulltext with flexsearch', () => {
11
+ beforeEach(async () => {
12
+ database = Fireproof.storage()
13
+ flexed = withFlexsearch(database) // this is a function that adds the flexsearch library to the database object
14
+
15
+ const messages = [
16
+ 'Hello World, this is a test',
17
+ 'We are testing the flexsearch library',
18
+ 'When we test we test',
19
+ 'Apples are red',
20
+ 'Bananas are yellow',
21
+ 'Oranges are orange',
22
+ 'Pears are green',
23
+ 'Grapes are purple',
24
+ 'Strawberries are red',
25
+ 'Blueberries are blue',
26
+ 'Raspberries are red',
27
+ 'Watermelons are green',
28
+ 'Pineapples are yellow'
29
+ ]
30
+ for (let i = 0, len = messages.length; i < len; i++) {
31
+ await database.put({
32
+ _id: `message-${i}`,
33
+ message: messages[i]
34
+ })
35
+ }
36
+ })
37
+
38
+ it('search the index', async () => {
39
+ const changes = await database.changesSince()
40
+ assert.equal(changes.rows.length, 13)
41
+ const results = await flexed.search('red')
42
+ assert.equal(results.length, 3)
43
+ for (let i = 0, len = results.length; i < len; i++) {
44
+ const doc = await database.get(results[i])
45
+ assert.match(doc.message, /red/)
46
+ }
47
+ })
48
+ // it('add more docs and search again', async () => {})
49
+ // it('delete some docs and search again', async () => {})
50
+ // it('update some docs and search again', async () => {})
51
+ })
52
+
53
+ function withFlexsearch (database, flexsearchOptions = {}) {
54
+ const index = new Index(flexsearchOptions)
55
+ let clock = null
56
+ const search = async (query, options) => {
57
+ const changes = await database.changesSince(clock)
58
+ clock = changes.clock
59
+ for (let i = 0; i < changes.rows.length; i++) {
60
+ const { key, value } = changes.rows[i]
61
+ await index.add(key, value.message)
62
+ }
63
+ return index.search(query, options)
64
+ }
65
+ return { search }
66
+ }
@@ -146,9 +146,11 @@ class TestPail {
146
146
  */
147
147
  async put (key, value) {
148
148
  const result = await put(this.blocks, this.head, { key, value })
149
- if (!result) { console.log('failed', key, value) }
149
+ if (!result) {
150
+ console.log('failed', key, value)
151
+ }
150
152
  this.blocks.putSync(result.event.cid, result.event.bytes)
151
- result.additions.forEach(a => this.blocks.putSync(a.cid, a.bytes))
153
+ result.additions.forEach((a) => this.blocks.putSync(a.cid, a.bytes))
152
154
  this.head = result.head
153
155
  this.root = result.root.cid
154
156
  // this difference probably matters, but we need to test it
@@ -162,42 +164,27 @@ class TestPail {
162
164
 
163
165
  /** @param {import('../src/clock').EventLink<import('../src/crdt').EventData>} event */
164
166
  async advance (event) {
165
- this.head = await advance(this.blocks, this.head, event)
166
- this.root = (await root(this.blocks, this.head)).block.cid
167
+ const { head } = await advance(this.blocks, this.head, event)
168
+ this.head = head
169
+ this.root = (await root(this.blocks, this.head)).node.block.cid
167
170
  return this.head
168
171
  }
169
172
 
170
- // /**
171
- // * @param {string} key
172
- // * @param {import('../src/link.js').AnyLink} value
173
- // */
174
- // async putAndVis (key, value) {
175
- // const result = await this.put(key, value)
176
- // /** @param {import('../src/link').AnyLink} l */
177
- // const shortLink = l => `${String(l).slice(0, 4)}..${String(l).slice(-4)}`
178
- // /** @type {(e: import('../src/clock').EventBlockView<import('../src/crdt').EventData>) => string} */
179
- // const renderNodeLabel = event => {
180
- // return event.value.data.type === 'put'
181
- // ? `${shortLink(event.cid)}\\nput(${event.value.data.key}, ${shortLink(event.value.data.value)})`
182
- // : `${shortLink(event.cid)}\\ndel(${event.value.data.key})`
183
- // }
184
- // for await (const line of vis(this.blocks, result.head, { renderNodeLabel })) {
185
- // console.log(line)
186
- // }
187
- // return result
188
- // }
189
-
190
173
  /** @param {string} key */
191
174
  async get (key) {
192
- return get(this.blocks, this.head, key)
175
+ const resp = await get(this.blocks, this.head, key)
176
+ console.log('prolly GET', key, resp)
177
+ return resp.result
193
178
  }
194
179
 
195
180
  /** @param {string} key */
196
181
  async getAll () {
197
- return getAll(this.blocks, this.head)
182
+ const resp = await getAll(this.blocks, this.head)
183
+ return resp.result
198
184
  }
199
185
 
200
186
  async getSince (since) {
201
- return eventsSince(this.blocks, this.head, since)
187
+ const resp = await eventsSince(this.blocks, this.head, since)
188
+ return resp.result
202
189
  }
203
190
  }
@@ -0,0 +1,53 @@
1
+ import { describe, it, beforeEach } from 'mocha'
2
+ import assert from 'node:assert'
3
+ import Fireproof from '../src/fireproof.js'
4
+
5
+ let database, ok, doc
6
+
7
+ describe('Proofs', () => {
8
+ beforeEach(async () => {
9
+ database = Fireproof.storage()
10
+ ok = await database.put({
11
+ _id: 'test1',
12
+ score: 75
13
+ })
14
+ doc = await database.get(ok.id, { mvcc: true })
15
+ })
16
+
17
+ it.skip('first put result shoud not include proof', async () => {
18
+ assert(ok.proof)
19
+ assert(ok.proof.data)
20
+ assert(ok.proof.clock)
21
+ console.log('ok', ok)
22
+ assert.equal(ok.proof.data.length, 0)
23
+ assert.equal(ok.proof.clock.length, 0)
24
+
25
+ // assert.equal(ok.proof.data[0], 'bafyreibsbxxd4ueujryihk6xza2ekwhzsh6pzuu5fysft5ilz7cbw6bjju')
26
+ // assert.equal(ok.proof.clock[0].toString(), 'bafyreiactx5vku7zueq27i5zdrgcjnczxvepceo5yszjqb2exufwrwxg44')
27
+ })
28
+
29
+ it.skip('second put result shoud include proof', async () => {
30
+ const ok2 = await database.put({ ...doc, winner: true })
31
+ assert(ok2.proof)
32
+ assert(ok2.proof.data)
33
+ assert(ok2.proof.clock)
34
+ console.log('ok2', ok2)
35
+ assert.equal(ok2.proof.data.length, 1)
36
+ assert.equal(ok2.proof.clock.length, 1)
37
+
38
+ // assert.equal(ok.proof.data[0], 'bafyreibsbxxd4ueujryihk6xza2ekwhzsh6pzuu5fysft5ilz7cbw6bjju')
39
+ // assert.equal(ok.proof.clock[0].toString(), 'bafyreiactx5vku7zueq27i5zdrgcjnczxvepceo5yszjqb2exufwrwxg44')
40
+ })
41
+
42
+ it('get result shoud include proof', async () => {
43
+ assert(doc._clock)
44
+ assert(doc._proof)
45
+
46
+ assert(doc._proof.data)
47
+ assert(doc._proof.clock)
48
+ assert.equal(doc._proof.data.length, 1)
49
+ assert.equal(doc._proof.clock.length, 1)
50
+ assert.equal(doc._proof.data[0], 'bafyreibsbxxd4ueujryihk6xza2ekwhzsh6pzuu5fysft5ilz7cbw6bjju')
51
+ assert.equal(doc._proof.clock[0].toString(), 'bafyreiactx5vku7zueq27i5zdrgcjnczxvepceo5yszjqb2exufwrwxg44')
52
+ })
53
+ })
@@ -0,0 +1,65 @@
1
+ import { describe, it, beforeEach } from 'mocha'
2
+ import assert from 'node:assert'
3
+ import Fireproof from '../src/fireproof.js'
4
+ import DbIndex from '../src/db-index.js'
5
+
6
+ let database
7
+
8
+ describe('IPLD encode error', () => {
9
+ beforeEach(async () => {
10
+ database = Fireproof.storage()
11
+ defineIndexes(database)
12
+ })
13
+
14
+ it('reproduce', async () => {
15
+ await loadFixtures(database)
16
+ assert(true)
17
+ })
18
+ })
19
+
20
+ const defineIndexes = (database) => {
21
+ database.allLists = new DbIndex(database, function (doc, map) {
22
+ if (doc.type === 'list') map(doc.type, doc)
23
+ })
24
+ database.todosByList = new DbIndex(database, function (doc, map) {
25
+ if (doc.type === 'todo' && doc.listId) {
26
+ map([doc.listId, doc.createdAt], doc)
27
+ }
28
+ })
29
+ return database
30
+ }
31
+
32
+ function mulberry32 (a) {
33
+ return function () {
34
+ let t = (a += 0x6d2b79f5)
35
+ t = Math.imul(t ^ (t >>> 15), t | 1)
36
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
37
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
38
+ }
39
+ }
40
+ const rand = mulberry32(1) // determinstic fixtures
41
+
42
+ async function loadFixtures (database) {
43
+ const nextId = (prefix = '') => prefix + rand().toString(32).slice(2)
44
+ const ok = await database.put({ title: 'Building Apps', type: 'list', _id: nextId() })
45
+
46
+ await database.put({
47
+ _id: nextId(),
48
+ title: 'In the browser',
49
+ listId: ok.id,
50
+ completed: rand() > 0.75,
51
+ type: 'todo',
52
+ createdAt: '2'
53
+ })
54
+ await reproduceBug(database)
55
+ }
56
+
57
+ const reproduceBug = async (database) => {
58
+ const id = '02pkji8'
59
+ const doc = await database.get(id)
60
+ // (await database.put({ completed: !completed, ...doc }))
61
+ const ok = await database.put(doc)
62
+ await database.todosByList.query({ range: [0, 1] })
63
+
64
+ console.log('ok', ok)
65
+ }
package/README.md DELETED
@@ -1,148 +0,0 @@
1
- # 🔥 Fireproof
2
-
3
- Fireproof is a realtime database for today's interactive applications. It uses immutable data and distributed protocols
4
- to offer a new kind of database that:
5
- - can be embedded in any page or app, with a flexible data ownership model
6
- - scales without incurring developer costs, thanks to Filecoin
7
- - uses cryptographically verifiable protocols (what plants crave)
8
-
9
- Learn more about the concepts and architecture behind Fireproof [in our plan,](https://hackmd.io/@j-chris/SyoE-Plpj) or jump to the [quick start](#quick-start) for React and server-side examples.
10
-
11
- ### Status
12
-
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/jchris/fireproof/blob/main/examples/todomvc/src/hooks/useFireproof.js)
14
-
15
- [![Test](https://github.com/jchris/fireproof/actions/workflows/test.yml/badge.svg)](https://github.com/jchris/fireproof/actions/workflows/test.yml)
16
- [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
17
-
18
- ## Usage
19
-
20
- ```js
21
- import Fireproof from 'fireproof';
22
-
23
- async function main() {
24
- const database = new Fireproof();
25
- const ok = await database.put({
26
- name: 'alice',
27
- age: 42
28
- });
29
-
30
- const doc = await database.get(ok.id);
31
- console.log(doc.name); // 'alice'
32
- }
33
-
34
- main();
35
- ```
36
-
37
- ## Features
38
-
39
- ### Document Store
40
-
41
- A simple put, get, and delete interface for keeping track of all your JSON documents. Once your data is in Fireproof you can access it from any app or website. Fireproof document store uses MVCC versioning and Merkle clocks so you can always recover the version you are looking for.
42
-
43
- ```js
44
- const { id, ref } = await database.put({
45
- _id: 'three-thousand'
46
- name: 'André',
47
- age: 47
48
- });
49
- const doc = await database.get('three-thousand')
50
- // {
51
- // _id : 'three-thousand'
52
- // _ref : CID(bafy84...agfw7)
53
- // name : 'André',
54
- // age : 47
55
- // }
56
- ```
57
-
58
- The `_ref` allows you to query a stable snapshot of that version of the database. Fireproof uses immutable data structures under the hood, so you can always rollback to old data. Files can be embedded anywhere in your document using IPFS links like `{"/":"bafybeih3e3zdiehbqfpxzpppxrb6kaaw4xkbqzyr2f5pwr5refq2te2ape"}`, with API sugar coming soon.
59
-
60
- ### Flexible Indexes
61
-
62
- Fireproof indexes are defined by custom JavaScript functions that you write, allowing you to easily index and search your data in the way that works best for your application. Easily handle data variety and schema drift by normalizing any data to the desired index.
63
-
64
- ```js
65
- const index = new Index(database, function (doc, map) {
66
- map(doc.age, doc.name)
67
- })
68
- const { rows, ref } = await index.query({ range: [40, 52] })
69
- // [ { key: 42, value: 'alice', id: 'a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c' },
70
- // { key: 47, value: 'André', id: 'three-thousand' } ]
71
- ```
72
-
73
- ### Realtime Updates
74
-
75
- Subscribe to query changes in your application, so your UI updates automatically. Use the supplied React hooks, our Redux connector, or simple function calls to be notified of relevant changes.
76
-
77
- ```js
78
- const listener = new Listener(database, function(doc, emit) {
79
- if (doc.type == 'member') {
80
- emit('member')
81
- }
82
- })
83
- listener.on('member', (id) => {
84
- const doc = await db.get(id)
85
- alert(`Member update ${doc.name}`)
86
- })
87
- ```
88
-
89
- ### Self-soverign Identity
90
-
91
- Fireproof is so easy to integrate with any site or app because you can get started right away, and set up an account later. By default users write to their own database copy, so you can get pretty far before you even have to think about API keys. [Authorization is via non-extractable keypair](https://ucan.xyz), like TouchID / FaceID.
92
-
93
- ### Automatic Replication
94
-
95
- Documents changes are persisted to [Filecoin](https://filecoin.io) via [web3.storage](https://web3.storage), and made available over [IPFS] and on a global content delivery network. All you need to do to sync state is send a link to the latest database head, and Fireproof will take care of the rest. [Learn how to enable replication.](#status)
96
-
97
- ### Cryptographic Proofs
98
-
99
- The [UCAN protocol](https://ucan.xyz) verifably links Fireproof updates to authorized agents via cryptographic proof chains. These proofs are portable like bearer tokens, but because invocations are signed by end-user device keys, UCAN proofs don't need to be hidden to be secure, allowing for delegation of service capabilities across devices and parties. Additionally, Fireproof's Merkle clocks and hash trees are immutable and self-validating, making merging changes safe and efficient. Fireproof makes cryptographic proofs available for all of it's operations, making it an ideal verfiable document database for smart contracts and other applications running in trustless environments. [Proof chains provide performance benefits as well](https://purrfect-tracker-45c.notion.site/Data-Routing-23c37b269b4c4c3dacb60d0077113bcb), by allowing recipients to skip costly I/O operations and instead cryptographically verify that changes contain all of the required context.
100
-
101
- ## Limitations 💣
102
-
103
- ### Security
104
-
105
- Until encryption support is enabled, all data written to Fireproof is public. There are no big hurdles for this feature but it's not ready yet.
106
-
107
- ### Persistence
108
-
109
- Currently Fireproof writes transactions and proofs to in-memory [CAR files](https://ipld.io/specs/transport/car/carv2/) which are well suited for peer and cloud replication. Durability coming soon.
110
-
111
- ### Pre-beta Software
112
-
113
- While the underlying data structures and libraries Fireproof uses are trusted with billions of dollars worth of data, Fireproof started in February of 2023. Results may vary.
114
-
115
- ## Thanks 🙏
116
-
117
- Fireproof is a synthesis of work done by people in the web community over the years. I couldn't even begin to name all the folks who made pivotal contributions. Without npm, React, and VS Code all this would have taken so much longer. Thanks to everyone who supported me getting into database development via Apache CouchDB, one of the original document databases. The distinguishing work on immutable datastructures comes from the years of consideration [IPFS](https://ipfs.tech), [IPLD](https://ipld.io), and the [Filecoin APIs](https://docs.filecoin.io) have enjoyed.
118
-
119
- 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
-
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
- ```
128
- npm install @fireproof/core
129
- ```
130
-
131
- In your `app.js` or `app.tsx` file:
132
-
133
- ```
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
- # Contributing
143
-
144
- Feel free to join in. All welcome. [Open an issue](https://github.com/jchris/fireproof/issues)!
145
-
146
- # License
147
-
148
- Dual-licensed under [MIT or Apache 2.0](https://github.com/jchris/fireproof/blob/main/LICENSE.md)
@@ -1,149 +0,0 @@
1
- # useFireproof hook for React
2
-
3
- React hook to initialize a Fireproof database, automatically saving and loading the clock.
4
-
5
- The hook takes two optional setup function arguments, `defineDatabaseFn` and `setupDatabaseFn`. See below for examples.
6
-
7
- The return value looks like `{ ready, database, addSubscriber }` where the `database` is your Fireproof instance that you can interact with using `put` and `get`, or via your indexes. The `ready` flag turns true after setup completes, you can use this to activate your UI. The `addSubscriber` function is used to update your app in realtime, see example.
8
-
9
- ## Usage Example
10
-
11
- In App.js:
12
-
13
- ```js
14
- import { FireproofCtx, useFireproof } from '@fireproof/core/hooks/use-fireproof'
15
-
16
- function App() {
17
- // establish the Fireproof context value
18
- const fpCtxValue = useFireproof()
19
-
20
- // render the rest of the application wrapped in the Fireproof provider
21
- return (
22
- <FireproofCtx.Provider value={fpCtxValue}>
23
- <MyComponent />
24
- </FireproofCtx.Provider>
25
- )
26
- }
27
- ```
28
-
29
- In your components:
30
-
31
- ```js
32
- import { FireproofCtx } from '@fireproof/core/hooks/use-fireproof'
33
-
34
- function MyComponent() {
35
- // get Fireproof context
36
- const { ready, database, addSubscriber } = useContext(FireproofCtx)
37
-
38
- // set a default empty document
39
- const [doc, setDoc] = useState({})
40
-
41
- // function to load the document from the database
42
- const getDataFn = async () => {
43
- setDoc(await database.get("my-doc-id"))
44
- }
45
-
46
- // run that function when the database changes
47
- addSubscriber('MyComponent', getDataFn)
48
-
49
- // run the loader on first mount
50
- useEffect(() => getDataFn(), [])
51
-
52
- // a function to change the value of the document
53
- const updateFn = async () => {
54
- await database.put({ _id : "my-doc-id", hello: "world", updated_at: new Date()})
55
- }
56
-
57
- // render the document with a click handler to update it
58
- return <pre onclick={updateFn}>JSON.stringify(doc)</pre>
59
- }
60
- ```
61
-
62
- This should result in a tiny application that updates the document when you click it. In a real appliction you'd probably query an index to present eg. all of the photos in a gallery.
63
-
64
- ## Setup Functions
65
-
66
- ### defineDatabaseFn
67
-
68
- Synchronous function that defines the database, run this before any async calls. You can use it to do stuff like set up Indexes. Here's an example:
69
-
70
- ```js
71
- const defineIndexes = (database) => {
72
- database.allLists = new Index(database, function (doc, map) {
73
- if (doc.type === 'list') map(doc.type, doc)
74
- })
75
- database.todosByList = new Index(database, function (doc, map) {
76
- if (doc.type === 'todo' && doc.listId) {
77
- map([doc.listId, doc.createdAt], doc)
78
- }
79
- })
80
- window.fireproof = database // 🤫 for dev
81
- return database
82
- }
83
- ```
84
-
85
- ### setupDatabaseFn
86
-
87
- Asynchronous function that uses the database when it's ready, run this to load fixture data, insert a dataset from somewhere else, etc. Here's a simple example:
88
-
89
-
90
- ```
91
- async function setupDatabase(database)) {
92
- const apiData = await (await fetch('https://dummyjson.com/products')).json()
93
- for (const product of apiData.products) {
94
- await database.put(product)
95
- }
96
- }
97
- ```
98
-
99
- Note there are no protections against you running the same thing over and over again, so you probably want to put some logic in there to do the right thing.
100
-
101
- Here is an example of generating deterministic fixtures, using `mulberry32` for determinstic randomness so re-runs give the same CID, avoiding unnecessary bloat at development time, taken from the TodoMVC demo app.
102
-
103
- ```js
104
- function mulberry32(a) {
105
- return function () {
106
- let t = (a += 0x6d2b79f5)
107
- t = Math.imul(t ^ (t >>> 15), t | 1)
108
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
109
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296
110
- }
111
- }
112
- const rand = mulberry32(1) // determinstic fixtures
113
-
114
- export default async function loadFixtures(database) {
115
- const nextId = (prefix = '') => prefix + rand().toString(32).slice(2)
116
- const listTitles = ['Building Apps', 'Having Fun', 'Getting Groceries']
117
- const todoTitles = [
118
- [
119
- 'In the browser',
120
- 'On the phone',
121
- 'With or without Redux',
122
- 'Login components',
123
- 'GraphQL queries',
124
- 'Automatic replication and versioning',
125
- ],
126
- ['Rollerskating meetup', 'Motorcycle ride', 'Write a sci-fi story with ChatGPT'],
127
- ['Macadamia nut milk', 'Avocado toast', 'Coffee', 'Bacon', 'Sourdough bread', 'Fruit salad'],
128
- ]
129
- let ok
130
- for (let j = 0; j < 3; j++) {
131
- ok = await database.put({
132
- title: listTitles[j],
133
- type: 'list',
134
- _id: nextId('' + j)
135
- })
136
- for (let i = 0; i < todoTitles[j].length; i++) {
137
- await database.put({
138
- _id: nextId(),
139
- title: todoTitles[j][i],
140
- listId: ok.id,
141
- completed: rand() > 0.75,
142
- type: 'todo',
143
- })
144
- }
145
- }
146
- }
147
- ```
148
-
149
-