@fireproof/core 0.0.5 → 0.0.7
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/hooks/use-fireproof.ts +0 -2
- package/package.json +6 -5
- package/src/blockstore.js +14 -10
- package/src/clock.js +29 -50
- package/src/db-index.js +109 -69
- package/src/fireproof.js +64 -27
- package/src/listener.js +0 -6
- package/src/prolly.js +68 -52
- package/src/valet.js +6 -62
- package/{src → test}/block.js +6 -6
- package/test/clock.test.js +91 -170
- package/test/db-index.test.js +10 -8
- package/test/fireproof.test.js +84 -16
- package/test/fulltext.test.js +66 -0
- package/test/helpers.js +1 -1
- package/test/prolly.test.js +15 -28
- package/test/proofs.test.js +53 -0
- package/test/reproduce-fixture-bug.test.js +65 -0
- package/hooks/use-fireproof.md +0 -149
- package/scripts/propernames/gen.sh +0 -3
- package/scripts/randomcid.js +0 -12
- package/scripts/words/gen.js +0 -55
package/test/db-index.test.js
CHANGED
@@ -2,13 +2,13 @@ import { describe, it, beforeEach } from 'mocha'
|
|
2
2
|
import assert from 'node:assert'
|
3
3
|
import Blockstore from '../src/blockstore.js'
|
4
4
|
import Fireproof from '../src/fireproof.js'
|
5
|
-
import
|
5
|
+
import DbIndex from '../src/db-index.js'
|
6
6
|
console.x = function () {}
|
7
7
|
|
8
|
-
describe('
|
8
|
+
describe('DbIndex query', () => {
|
9
9
|
let database, index
|
10
10
|
beforeEach(async () => {
|
11
|
-
database =
|
11
|
+
database = Fireproof.storage()
|
12
12
|
const docs = [
|
13
13
|
{ _id: 'a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'alice', age: 40 },
|
14
14
|
{ _id: 'b2s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'bob', age: 40 },
|
@@ -24,7 +24,7 @@ describe('Index query', () => {
|
|
24
24
|
assert(response.id, 'should have id')
|
25
25
|
assert.equal(response.id, id)
|
26
26
|
}
|
27
|
-
index = new
|
27
|
+
index = new DbIndex(database, function (doc, map) {
|
28
28
|
map(doc.age, doc.name)
|
29
29
|
})
|
30
30
|
})
|
@@ -36,8 +36,10 @@ describe('Index query', () => {
|
|
36
36
|
assert.equal(result.rows[0].key, 43)
|
37
37
|
assert(result.rows[0].value === 'carol', 'correct value')
|
38
38
|
})
|
39
|
-
it
|
40
|
-
|
39
|
+
it('query exact key', async () => {
|
40
|
+
let result = await index.query({ range: [41, 44] })
|
41
|
+
assert(result.rows[0].key === 43, 'correct key')
|
42
|
+
result = await index.query({ key: 43 })
|
41
43
|
assert(result, 'did return result')
|
42
44
|
assert(result.rows)
|
43
45
|
assert.equal(result.rows.length, 1, 'one row matched')
|
@@ -197,12 +199,12 @@ describe('Index query', () => {
|
|
197
199
|
})
|
198
200
|
})
|
199
201
|
|
200
|
-
describe('
|
202
|
+
describe('DbIndex query with bad index definition', () => {
|
201
203
|
let database, index
|
202
204
|
beforeEach(async () => {
|
203
205
|
database = new Fireproof(new Blockstore(), []) // todo: these need a cloud name aka w3name, add this after we have cloud storage of blocks
|
204
206
|
await database.put({ _id: 'a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'alice', age: 40 })
|
205
|
-
index = new
|
207
|
+
index = new DbIndex(database, function (doc, map) {
|
206
208
|
map(doc.oops.missingField, doc.name)
|
207
209
|
})
|
208
210
|
})
|
package/test/fireproof.test.js
CHANGED
@@ -2,6 +2,7 @@ import { describe, it, beforeEach } from 'mocha'
|
|
2
2
|
import assert from 'node:assert'
|
3
3
|
import Blockstore from '../src/blockstore.js'
|
4
4
|
import Fireproof from '../src/fireproof.js'
|
5
|
+
import * as codec from '@ipld/dag-cbor'
|
5
6
|
|
6
7
|
let database, resp0
|
7
8
|
|
@@ -9,14 +10,18 @@ let database, resp0
|
|
9
10
|
|
10
11
|
describe('Fireproof', () => {
|
11
12
|
beforeEach(async () => {
|
12
|
-
database =
|
13
|
+
database = Fireproof.storage('helloName')
|
13
14
|
resp0 = await database.put({
|
14
15
|
_id: '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c',
|
15
16
|
name: 'alice',
|
16
17
|
age: 42
|
17
18
|
})
|
18
19
|
})
|
19
|
-
|
20
|
+
it('takes an optional name', () => {
|
21
|
+
assert.equal(database.name, 'helloName')
|
22
|
+
const x = database.blocks.valet.idb
|
23
|
+
assert.equal(x.name.toString(), 'fp.helloName.valet')
|
24
|
+
})
|
20
25
|
it('put and get document', async () => {
|
21
26
|
assert(resp0.id, 'should have id')
|
22
27
|
assert.equal(resp0.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
@@ -27,12 +32,13 @@ describe('Fireproof', () => {
|
|
27
32
|
})
|
28
33
|
it('mvcc put and get document with _clock that matches', async () => {
|
29
34
|
assert(resp0.clock, 'should have clock')
|
30
|
-
assert.equal(resp0.clock[0].toString(), '
|
35
|
+
assert.equal(resp0.clock[0].toString(), 'bafyreiadhnnxgaeeqdxujfew6zxr4lnjyskkrg26cdjvk7tivy6dt4xmsm')
|
31
36
|
const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
32
37
|
theDoc._clock = database.clock
|
33
38
|
const put2 = await database.put(theDoc)
|
34
39
|
assert.equal(put2.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
35
|
-
assert.equal(put2.clock
|
40
|
+
assert.equal(put2.clock.length, 1)
|
41
|
+
assert.equal(put2.clock[0].toString(), 'bafyreib2kck2fv73lgahfcd5imarslgxcmachbxxavhtwahx5ppjfts4qe')
|
36
42
|
})
|
37
43
|
it('get should return an object instance that is not the same as the one in the db', async () => {
|
38
44
|
const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
@@ -45,7 +51,17 @@ describe('Fireproof', () => {
|
|
45
51
|
it('get with mvcc option', async () => {
|
46
52
|
const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
|
47
53
|
assert(theDoc._clock, 'should have _clock')
|
48
|
-
assert.equal(theDoc._clock[0].toString(), '
|
54
|
+
assert.equal(theDoc._clock[0].toString(), 'bafyreiadhnnxgaeeqdxujfew6zxr4lnjyskkrg26cdjvk7tivy6dt4xmsm')
|
55
|
+
})
|
56
|
+
it('get with mvcc option where someone else changed another document first', async () => {
|
57
|
+
const theDoc = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', { mvcc: true })
|
58
|
+
const put2 = await database.put({ something: 'else' })
|
59
|
+
assert(put2.clock, 'should have clock')
|
60
|
+
assert.notEqual(put2.clock.toString(), resp0.clock.toString())
|
61
|
+
assert.equal(theDoc._clock.toString(), resp0.clock.toString())
|
62
|
+
theDoc.name = 'somone else'
|
63
|
+
const put3works = await database.put(theDoc)
|
64
|
+
assert(put3works.clock, 'should have id')
|
49
65
|
})
|
50
66
|
it('get from an old snapshot with mvcc option', async () => {
|
51
67
|
const ogClock = resp0.clock
|
@@ -226,6 +242,34 @@ describe('Fireproof', () => {
|
|
226
242
|
assert.equal(prevBob.age, 11)
|
227
243
|
})
|
228
244
|
|
245
|
+
it('provides docs since tiny', async () => {
|
246
|
+
const result = await database.changesSince()
|
247
|
+
assert.equal(result.rows.length, 1)
|
248
|
+
assert.equal(result.rows[0].key, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
249
|
+
|
250
|
+
// console.log('result', result)
|
251
|
+
|
252
|
+
// const result2 = await database.changesSince(result.clock)
|
253
|
+
// console.log('result2', result2)
|
254
|
+
// assert.equal(result2.rows.length, 0)
|
255
|
+
|
256
|
+
const bKey = 'befbef-3c3a-4b5e-9c1c-bbbbbb'
|
257
|
+
const bvalue = {
|
258
|
+
_id: bKey,
|
259
|
+
name: 'bob',
|
260
|
+
age: 44
|
261
|
+
}
|
262
|
+
const response = await database.put(bvalue)
|
263
|
+
assert(response.id, 'should have id')
|
264
|
+
assert.equal(response.id, bKey)
|
265
|
+
|
266
|
+
const res3 = await database.changesSince()
|
267
|
+
assert.equal(res3.rows.length, 2)
|
268
|
+
|
269
|
+
const res4 = await database.changesSince(result.clock)
|
270
|
+
assert.equal(res4.rows.length, 1)
|
271
|
+
})
|
272
|
+
|
229
273
|
it('provides docs since', async () => {
|
230
274
|
const result = await database.changesSince()
|
231
275
|
assert.equal(result.rows.length, 1)
|
@@ -264,8 +308,6 @@ describe('Fireproof', () => {
|
|
264
308
|
|
265
309
|
const res5 = await database.changesSince(res4.clock)
|
266
310
|
|
267
|
-
// await database.visClock()
|
268
|
-
|
269
311
|
assert.equal(res5.rows.length, 1)
|
270
312
|
|
271
313
|
const res6 = await database.changesSince(result2.clock)
|
@@ -297,22 +339,48 @@ describe('Fireproof', () => {
|
|
297
339
|
assert.equal((await database.changesSince()).rows.length, 1)
|
298
340
|
let resp, doc, changes
|
299
341
|
for (let index = 0; index < 200; index++) {
|
300
|
-
const id = '' + (
|
342
|
+
const id = '1' + (301 - index).toString()
|
343
|
+
console.log(`Putting id: ${id}, index: ${index}`)
|
301
344
|
resp = await database.put({ index, _id: id }).catch(e => {
|
302
|
-
assert.
|
345
|
+
assert.fail(`put failed on _id: ${id}, error: ${e.message}`)
|
303
346
|
})
|
304
|
-
assert(resp.id)
|
347
|
+
assert(resp.id, `Failed to obtain resp.id for _id: ${id}`)
|
348
|
+
|
349
|
+
console.log(`vis for update id: ${id}, index:`, index)
|
350
|
+
for await (const line of database.vis()) {
|
351
|
+
console.log(line)
|
352
|
+
}
|
353
|
+
|
305
354
|
doc = await database.get(resp.id).catch(e => {
|
306
|
-
console.
|
307
|
-
assert.
|
355
|
+
console.log('failed', e)
|
356
|
+
assert.fail(`get failed on _id: ${id}, error: ${e.message}`)
|
357
|
+
})
|
358
|
+
|
359
|
+
assert.equal(doc.index, index, `doc.index is not equal to index for _id: ${id}`)
|
360
|
+
changes = await database.changesSince().catch(async e => {
|
361
|
+
assert.fail(`changesSince failed on _id: ${id}, error: ${e.message}`)
|
308
362
|
})
|
309
|
-
|
310
|
-
|
311
|
-
|
363
|
+
changes.rows.forEach(row => {
|
364
|
+
for (const key in row) {
|
365
|
+
const value = row[key]
|
366
|
+
assert(!/^bafy/.test(value), `Unexpected "bafy..." value found at index ${index} in row ${JSON.stringify(row)}`)
|
367
|
+
}
|
312
368
|
})
|
313
|
-
|
369
|
+
if (index > 3) {
|
370
|
+
const stored = await database.blocks.get('bafyreicumn7tvssch4xslbe4jjq55c6w3jt4yxyjagkr2tengsudato7vi').catch((e) => {
|
371
|
+
console.log(`Error getting block for index ${index}: ${e.message}`)
|
372
|
+
})
|
373
|
+
if (stored) {
|
374
|
+
const doc = codec.decode(await stored.bytes)
|
375
|
+
// console.log('stored', JSON.stringify(dec))
|
376
|
+
assert.equal(doc.closed, false)
|
377
|
+
}
|
378
|
+
}
|
379
|
+
console.log('changes: ', index, changes.rows.length, JSON.stringify(changes.rows))
|
380
|
+
assert.equal(changes.rows.length, index + 2, `failed on ${index}, with ${changes.rows.length} ${id}`)
|
314
381
|
}
|
315
382
|
}).timeout(20000)
|
383
|
+
|
316
384
|
it('concurrent transactions', async () => {
|
317
385
|
assert.equal((await database.changesSince()).rows.length, 1)
|
318
386
|
const promises = []
|
@@ -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
|
+
}
|
package/test/helpers.js
CHANGED
@@ -3,7 +3,7 @@ import crypto from 'node:crypto'
|
|
3
3
|
import * as Link from 'multiformats/link'
|
4
4
|
import * as raw from 'multiformats/codecs/raw'
|
5
5
|
import { sha256 } from 'multiformats/hashes/sha2'
|
6
|
-
import { MemoryBlockstore } from '
|
6
|
+
import { MemoryBlockstore } from './block.js'
|
7
7
|
|
8
8
|
// console.x = console.log
|
9
9
|
// console.log = function (...args) {
|
package/test/prolly.test.js
CHANGED
@@ -110,7 +110,7 @@ describe('Prolly', () => {
|
|
110
110
|
assert.equal(bvalue.toString(), data[0][1].toString())
|
111
111
|
})
|
112
112
|
|
113
|
-
it.skip('linear put hundreds of values', async () => {
|
113
|
+
it.skip('passing, slow: linear put hundreds of values', async () => {
|
114
114
|
const blocks = new Blockstore()
|
115
115
|
const alice = new TestPail(blocks, [])
|
116
116
|
|
@@ -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) {
|
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
|
-
|
166
|
-
this.
|
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
|
-
|
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
|
-
|
182
|
+
const resp = await getAll(this.blocks, this.head)
|
183
|
+
return resp.result
|
198
184
|
}
|
199
185
|
|
200
186
|
async getSince (since) {
|
201
|
-
|
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('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], 'bafyreieilmvxq6wudu46i2ssmuyrmaszr4onzlqxzlvngrczbn7ppyvloq')
|
51
|
+
assert.equal(doc._proof.clock[0].toString(), 'bafyreict4aip45uwnm4xcsn4oikh73t5n7nzdmc2u36rdbguroun2yaf2y')
|
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
|
+
await database.put(doc)
|
62
|
+
await database.todosByList.query({ range: [0, 1] })
|
63
|
+
|
64
|
+
// console.log('ok', ok)
|
65
|
+
}
|
package/hooks/use-fireproof.md
DELETED
@@ -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
|
-
|
package/scripts/randomcid.js
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
import crypto from 'node:crypto'
|
2
|
-
import { CID } from 'multiformats/cid'
|
3
|
-
import * as raw from 'multiformats/codecs/raw'
|
4
|
-
import { sha256 } from 'multiformats/hashes/sha2'
|
5
|
-
|
6
|
-
async function main () {
|
7
|
-
const bytes = crypto.webcrypto.getRandomValues(new Uint8Array(32))
|
8
|
-
const hash = await sha256.digest(bytes)
|
9
|
-
process.stdout.write(CID.create(1, raw.code, hash).toString())
|
10
|
-
}
|
11
|
-
|
12
|
-
main()
|