@fireproof/core 0.0.5 → 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
  })
@@ -32,6 +32,7 @@ describe('Fireproof', () => {
32
32
  theDoc._clock = database.clock
33
33
  const put2 = await database.put(theDoc)
34
34
  assert.equal(put2.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
35
+ assert.equal(put2.clock.length, 1)
35
36
  assert.equal(put2.clock[0].toString(), 'bafyreida2c2ckhjfoz5ulmbbfe66ey4svvedrl4tzbvtoxags2qck7lj2i')
36
37
  })
37
38
  it('get should return an object instance that is not the same as the one in the db', async () => {
@@ -47,6 +48,16 @@ describe('Fireproof', () => {
47
48
  assert(theDoc._clock, 'should have _clock')
48
49
  assert.equal(theDoc._clock[0].toString(), 'bafyreieth2ckopwivda5mf6vu76xwqvox3q5wsaxgbmxy2dgrd4hfuzmma')
49
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
+ })
50
61
  it('get from an old snapshot with mvcc option', async () => {
51
62
  const ogClock = resp0.clock
52
63
  const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
@@ -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
+ }
@@ -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
-