@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/valet.js CHANGED
@@ -1,27 +1,71 @@
1
1
  import { CarReader } from '@ipld/car'
2
2
  import { CID } from 'multiformats/cid'
3
3
  import { openDB } from 'idb'
4
+ import cargoQueue from 'async/cargoQueue.js'
4
5
 
5
6
  // const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
6
- let storageSupported = false
7
- try {
8
- storageSupported = (window.localStorage && true)
9
- } catch (e) { }
7
+ // let storageSupported = false
8
+ // try {
9
+ // storageSupported = window.localStorage && true
10
+ // } catch (e) {}
11
+ // const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
10
12
 
11
13
  export default class Valet {
12
14
  #cars = new Map() // cars by cid
13
15
  #cidToCar = new Map() // cid to car
14
-
15
16
  #db = null
17
+ #uploadQueue = null
18
+ #alreadyEnqueued = new Set()
19
+
20
+ /**
21
+ * Function installed by the database to upload car files
22
+ * @type {null|function(string, Uint8Array):Promise<void>}
23
+ */
24
+ uploadFunction = null
25
+
26
+ constructor () {
27
+ this.#uploadQueue = cargoQueue(async (tasks, callback) => {
28
+ console.log(
29
+ 'queue worker',
30
+ tasks.length,
31
+ tasks.reduce((acc, t) => acc + t.value.length, 0)
32
+ )
33
+ if (this.uploadFunction) {
34
+ // todo we can coalesce these into a single car file
35
+ for (const task of tasks) {
36
+ await this.uploadFunction(task.carCid, task.value)
37
+ }
38
+ }
39
+ callback()
40
+ })
41
+
42
+ this.#uploadQueue.drain(async () => {
43
+ return await this.withDB(async (db) => {
44
+ const carKeys = (await db.getAllFromIndex('cidToCar', 'pending')).map((c) => c.car)
45
+ for (const carKey of carKeys) {
46
+ await this.uploadFunction(carKey, await db.get('cars', carKey))
47
+ const carMeta = await db.get('cidToCar', carKey)
48
+ delete carMeta.pending
49
+ await db.put('cidToCar', carMeta)
50
+ }
51
+ })
52
+ })
53
+ }
16
54
 
17
55
  withDB = async (dbWorkFun) => {
18
- if (!storageSupported) return
56
+ // if (!storageSupported) return
19
57
  if (!this.#db) {
20
- this.#db = await openDB('valet', 1, {
21
- upgrade (db) {
22
- db.createObjectStore('cars') // todo use database name
23
- const cidToCar = db.createObjectStore('cidToCar', { keyPath: 'car' })
24
- cidToCar.createIndex('cids', 'cids', { multiEntry: true })
58
+ this.#db = await openDB('valet', 2, {
59
+ upgrade (db, oldVersion, newVersion, transaction) {
60
+ if (oldVersion < 1) {
61
+ db.createObjectStore('cars') // todo use database name
62
+ const cidToCar = db.createObjectStore('cidToCar', { keyPath: 'car' })
63
+ cidToCar.createIndex('cids', 'cids', { multiEntry: true })
64
+ }
65
+ if (oldVersion < 2) {
66
+ const cidToCar = transaction.objectStore('cidToCar')
67
+ cidToCar.createIndex('pending', 'pending')
68
+ }
25
69
  }
26
70
  })
27
71
  }
@@ -29,44 +73,47 @@ export default class Valet {
29
73
  }
30
74
 
31
75
  /**
32
- *
33
- * @param {string} carCid
34
- * @param {*} value
35
- */
76
+ *
77
+ * @param {string} carCid
78
+ * @param {*} value
79
+ */
36
80
  async parkCar (carCid, value, cids) {
37
- this.#cars.set(carCid, value)
38
- for (const cid of cids) {
39
- this.#cidToCar.set(cid, carCid)
40
- }
81
+ // this.#cars.set(carCid, value)
82
+ // for (const cid of cids) {
83
+ // this.#cidToCar.set(cid, carCid)
84
+ // }
41
85
 
42
86
  await this.withDB(async (db) => {
43
87
  const tx = db.transaction(['cars', 'cidToCar'], 'readwrite')
44
88
  await tx.objectStore('cars').put(value, carCid)
45
- await tx.objectStore('cidToCar').put({ car: carCid, cids: Array.from(cids) })
89
+ await tx.objectStore('cidToCar').put({ pending: 'y', car: carCid, cids: Array.from(cids) })
46
90
  return await tx.done
47
91
  })
48
- }
49
92
 
50
- async getBlock (dataCID) {
51
- // return await this.#valetGet(dataCID)
52
- // const MEMcarCid = this.#cidToCar.get(dataCID)
93
+ // upload to web3.storage if we have credentials
94
+ if (this.uploadFunction) {
95
+ if (this.#alreadyEnqueued.has(carCid)) {
96
+ // console.log('already enqueued', carCid)
97
+ return
98
+ }
99
+ // don't await this, it will be done in the queue
100
+ // console.log('add to queue', carCid, value.length)
101
+ this.#uploadQueue.push({ carCid, value })
102
+ this.#alreadyEnqueued.add(carCid)
103
+ } else {
104
+ // console.log('no upload function', carCid, value.length, this.uploadFunction)
105
+ }
106
+ }
53
107
 
54
- // return await this.withDB(async (db) => {
55
- // const tx = db.transaction(['cars', 'cidToCar'], 'readonly')
56
- // const carBytes = await tx.objectStore('cars').get(MEMcarCid)
57
- // const reader = await CarReader.fromBytes(carBytes)
58
- // const gotBlock = await reader.get(CID.parse(dataCID))
59
- // if (gotBlock) {
60
- // return gotBlock.bytes
61
- // }
62
- // })
108
+ remoteBlockFunction = null
63
109
 
110
+ async getBlock (dataCID) {
64
111
  return await this.withDB(async (db) => {
65
112
  const tx = db.transaction(['cars', 'cidToCar'], 'readonly')
66
113
  const indexResp = await tx.objectStore('cidToCar').index('cids').get(dataCID)
67
114
  const carCid = indexResp?.car
68
115
  if (!carCid) {
69
- return
116
+ throw new Error('Missing block: ' + dataCID)
70
117
  }
71
118
  const carBytes = await tx.objectStore('cars').get(carCid)
72
119
  const reader = await CarReader.fromBytes(carBytes)
@@ -76,67 +123,46 @@ export default class Valet {
76
123
  }
77
124
  })
78
125
  }
79
-
80
- /**
81
- * Internal function to load blocks from persistent storage.
82
- * Currently it just searches all the cars for the block, but in the future
83
- * we need to index the block CIDs to the cars, and reference that to find the block.
84
- * This index will also allow us to use accelerator links for the gateway when needed.
85
- * It can itself be a prolly tree...
86
- * @param {string} cid
87
- * @returns {Promise<Uint8Array|undefined>}
88
- */
89
- #valetGet = async (cid) => {
90
- const carCid = this.#cidToCar.get(cid)
91
- if (carCid) {
92
- const carBytes = this.#cars.get(carCid)
93
- const reader = await CarReader.fromBytes(carBytes)
94
- const gotBlock = await reader.get(CID.parse(cid))
95
- if (gotBlock) {
96
- return gotBlock.bytes
97
- }
98
- }
99
- }
100
126
  }
101
127
 
102
- export class MemoryValet {
103
- #cars = new Map() // cars by cid
104
- #cidToCar = new Map() // cid to car
128
+ // export class MemoryValet {
129
+ // #cars = new Map() // cars by cid
130
+ // #cidToCar = new Map() // cid to car
105
131
 
106
- /**
107
- *
108
- * @param {string} carCid
109
- * @param {*} value
110
- */
111
- async parkCar (carCid, value, cids) {
112
- this.#cars.set(carCid, value)
113
- for (const cid of cids) {
114
- this.#cidToCar.set(cid, carCid)
115
- }
116
- }
132
+ // /**
133
+ // *
134
+ // * @param {string} carCid
135
+ // * @param {*} value
136
+ // */
137
+ // async parkCar (carCid, value, cids) {
138
+ // this.#cars.set(carCid, value)
139
+ // for (const cid of cids) {
140
+ // this.#cidToCar.set(cid, carCid)
141
+ // }
142
+ // }
117
143
 
118
- async getBlock (dataCID) {
119
- return await this.#valetGet(dataCID)
120
- }
144
+ // async getBlock (dataCID) {
145
+ // return await this.#valetGet(dataCID)
146
+ // }
121
147
 
122
- /**
123
- * Internal function to load blocks from persistent storage.
124
- * Currently it just searches all the cars for the block, but in the future
125
- * we need to index the block CIDs to the cars, and reference that to find the block.
126
- * This index will also allow us to use accelerator links for the gateway when needed.
127
- * It can itself be a prolly tree...
128
- * @param {string} cid
129
- * @returns {Promise<Uint8Array|undefined>}
130
- */
131
- #valetGet = async (cid) => {
132
- const carCid = this.#cidToCar.get(cid)
133
- if (carCid) {
134
- const carBytes = this.#cars.get(carCid)
135
- const reader = await CarReader.fromBytes(carBytes)
136
- const gotBlock = await reader.get(CID.parse(cid))
137
- if (gotBlock) {
138
- return gotBlock.bytes
139
- }
140
- }
141
- }
142
- }
148
+ // /**
149
+ // * Internal function to load blocks from persistent storage.
150
+ // * Currently it just searches all the cars for the block, but in the future
151
+ // * we need to index the block CIDs to the cars, and reference that to find the block.
152
+ // * This index will also allow us to use accelerator links for the gateway when needed.
153
+ // * It can itself be a prolly tree...
154
+ // * @param {string} cid
155
+ // * @returns {Promise<Uint8Array|undefined>}
156
+ // */
157
+ // #valetGet = async (cid) => {
158
+ // const carCid = this.#cidToCar.get(cid)
159
+ // if (carCid) {
160
+ // const carBytes = this.#cars.get(carCid)
161
+ // const reader = await CarReader.fromBytes(carBytes)
162
+ // const gotBlock = await reader.get(CID.parse(cid))
163
+ // if (gotBlock) {
164
+ // return gotBlock.bytes
165
+ // }
166
+ // }
167
+ // }
168
+ // }
@@ -1,6 +1,14 @@
1
- import { describe, it } from 'mocha'
1
+ /* global describe, it */
2
+ // import { describe, it } from 'mocha'
2
3
  import assert from 'node:assert'
3
- import { advance, EventBlock, findCommonAncestorWithSortedEvents, findUnknownSortedEvents, decodeEventBlock, findEventsToSync } from '../src/clock.js'
4
+ import {
5
+ advance,
6
+ EventBlock,
7
+ findCommonAncestorWithSortedEvents,
8
+ findUnknownSortedEvents,
9
+ decodeEventBlock,
10
+ findEventsToSync
11
+ } from '../src/clock.js'
4
12
  // import { vis } from '../src/clock.js'
5
13
  import { Blockstore, seqEventData, setSeq } from './helpers.js'
6
14
 
@@ -34,7 +42,11 @@ describe('Clock', () => {
34
42
  assert.equal(head[0].toString(), event.cid.toString())
35
43
 
36
44
  const sinceHead = head
37
- const toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
45
+ const toSync = await findUnknownSortedEvents(
46
+ blocks,
47
+ sinceHead,
48
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
49
+ )
38
50
  assert.equal(toSync.length, 0)
39
51
  })
40
52
 
@@ -418,7 +430,11 @@ describe('Clock', () => {
418
430
  assert.equal(head[1].toString(), event1.cid.toString())
419
431
 
420
432
  let sinceHead = head1
421
- let toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
433
+ let toSync = await findUnknownSortedEvents(
434
+ blocks,
435
+ sinceHead,
436
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
437
+ )
422
438
  // assert.equal(toSync.length, 1) // 0
423
439
  // assert.equal(toSync[0].cid.toString(), event0.cid.toString())
424
440
 
@@ -430,12 +446,20 @@ describe('Clock', () => {
430
446
  assert.equal(head.length, 1)
431
447
 
432
448
  sinceHead = head2
433
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
449
+ toSync = await findUnknownSortedEvents(
450
+ blocks,
451
+ sinceHead,
452
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
453
+ )
434
454
  assert.equal(toSync.length, 0)
435
455
 
436
456
  // todo do these since heads make sense?
437
457
  sinceHead = [...head0, ...head2]
438
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
458
+ toSync = await findUnknownSortedEvents(
459
+ blocks,
460
+ sinceHead,
461
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
462
+ )
439
463
  // console.log('need', toSync.map(b => b.value.data))
440
464
  // assert.equal(toSync.length, 2) // 0
441
465
  // assert.equal(toSync[0].cid.toString(), event1.cid.toString())
@@ -668,7 +692,11 @@ describe('Clock', () => {
668
692
  const roothead = head
669
693
  // db root
670
694
  let sinceHead = [...roothead]
671
- let toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
695
+ let toSync = await findUnknownSortedEvents(
696
+ blocks,
697
+ sinceHead,
698
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
699
+ )
672
700
  assert.equal(toSync.length, 0) // we use all docs for first query in Fireproof
673
701
 
674
702
  // create bob
@@ -680,11 +708,19 @@ describe('Clock', () => {
680
708
 
681
709
  const event0head = head
682
710
  sinceHead = event0head
683
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
711
+ toSync = await findUnknownSortedEvents(
712
+ blocks,
713
+ sinceHead,
714
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
715
+ )
684
716
  assert.equal(toSync.length, 0)
685
717
 
686
718
  sinceHead = [...roothead, ...event0head]
687
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
719
+ toSync = await findUnknownSortedEvents(
720
+ blocks,
721
+ sinceHead,
722
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
723
+ )
688
724
  assert.equal(toSync.length, 1)
689
725
 
690
726
  // create carol
@@ -705,7 +741,11 @@ describe('Clock', () => {
705
741
  // for await (const line of vis(blocks, head)) console.log(line)
706
742
 
707
743
  sinceHead = [...event1head, ...roothead]
708
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
744
+ toSync = await findUnknownSortedEvents(
745
+ blocks,
746
+ sinceHead,
747
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
748
+ )
709
749
 
710
750
  assert.equal(toSync.length, 2)
711
751
 
@@ -715,11 +755,19 @@ describe('Clock', () => {
715
755
  const event2head = head
716
756
 
717
757
  sinceHead = [...event2head, ...event0head]
718
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
758
+ toSync = await findUnknownSortedEvents(
759
+ blocks,
760
+ sinceHead,
761
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
762
+ )
719
763
  assert.equal(toSync.length, 2)
720
764
 
721
765
  sinceHead = [...event2head, ...event1head]
722
- toSync = await findUnknownSortedEvents(blocks, sinceHead, await findCommonAncestorWithSortedEvents(blocks, sinceHead))
766
+ toSync = await findUnknownSortedEvents(
767
+ blocks,
768
+ sinceHead,
769
+ await findCommonAncestorWithSortedEvents(blocks, sinceHead)
770
+ )
723
771
  assert.equal(toSync.length, 1)
724
772
  })
725
773
  })
@@ -207,8 +207,11 @@ describe('Index query with bad index definition', () => {
207
207
  })
208
208
  })
209
209
  it('query index range', async () => {
210
+ const oldErrFn = console.error
211
+ console.error = () => {}
210
212
  await index.query({ range: [41, 44] }).catch((e) => {
211
213
  assert(/missingField/.test(e.message))
214
+ console.error = oldErrFn
212
215
  })
213
216
  })
214
217
  })
@@ -32,7 +32,6 @@ describe('Listener', () => {
32
32
  it('all listeners get the reset event', (done) => {
33
33
  let count = 0
34
34
  const check = () => {
35
- console.log('increment check count')
36
35
  count++
37
36
  if (count === 3) done()
38
37
  }
@@ -76,7 +75,7 @@ describe('Listener', () => {
76
75
  // it's safe to make this number longer if it start failing
77
76
  await sleep(50)
78
77
  assert.equal(people, 6)
79
- }).timeout(200)
78
+ }).timeout(2000)
80
79
  it('shares events since db.clock', (done) => {
81
80
  const clock = database.clock
82
81
  const afterEvent = () => {
@@ -96,7 +95,7 @@ describe('Listener', () => {
96
95
  if (people === 1) afterEvent()
97
96
  }, clock)
98
97
  })
99
- }).timeout(200)
98
+ }).timeout(2000)
100
99
  })
101
100
 
102
101
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
@@ -0,0 +1,59 @@
1
+ import { before, describe, it } from 'mocha'
2
+ import assert from 'node:assert'
3
+ import Valet from '../src/valet.js'
4
+
5
+ describe('new Valet', () => {
6
+ let val
7
+ const calls = []
8
+
9
+ before(async () => {
10
+ val = new Valet()
11
+ val.uploadFunction = async (carCid, value) => {
12
+ calls.push({ carCid, value })
13
+ }
14
+ })
15
+ it('has default attributes', async () => {
16
+ assert(val.getBlock)
17
+ assert(val.parkCar)
18
+ })
19
+ it('can park a car and serve the blocks', async () => {
20
+ await val
21
+ .parkCar('carCid', carBytes, [
22
+ 'bafyreifwghknmzabvgearl72url3v5leqqhtafvybcsrceelc3psqkocoi',
23
+ 'bafyreieth2ckopwivda5mf6vu76xwqvox3q5wsaxgbmxy2dgrd4hfuzmma'
24
+ ])
25
+ .then((car) => {
26
+ assert('car parked')
27
+ val.getBlock('bafyreieth2ckopwivda5mf6vu76xwqvox3q5wsaxgbmxy2dgrd4hfuzmma').then((block) => {
28
+ assert.equal(block.length, carBytes.length)
29
+ })
30
+ })
31
+ })
32
+ it('calls the upload function', () => {
33
+ assert.equal(calls.length, 1)
34
+ assert.equal(calls[0].carCid, 'carCid')
35
+ })
36
+ })
37
+
38
+ const carBytes = new Uint8Array([
39
+ 58, 162, 101, 114, 111, 111, 116, 115, 129, 216, 42, 88, 37, 0, 1, 113, 18, 32, 147, 62, 132, 167, 62, 200, 168, 193,
40
+ 214, 23, 213, 167, 253, 123, 66, 174, 190, 225, 219, 72, 23, 48, 89, 124, 104, 102, 136, 248, 114, 211, 44, 96, 103,
41
+ 118, 101, 114, 115, 105, 111, 110, 1, 108, 1, 113, 18, 32, 182, 49, 212, 214, 100, 1, 169, 136, 8, 175, 250, 164, 87,
42
+ 186, 245, 100, 132, 15, 48, 22, 184, 8, 165, 17, 16, 139, 22, 223, 40, 41, 194, 114, 162, 100, 108, 101, 97, 102, 129,
43
+ 130, 120, 36, 49, 101, 102, 51, 98, 51, 50, 97, 45, 51, 99, 51, 97, 45, 52, 98, 53, 101, 45, 57, 99, 49, 99, 45, 56,
44
+ 99, 53, 99, 48, 99, 53, 99, 48, 99, 53, 99, 162, 99, 97, 103, 101, 24, 42, 100, 110, 97, 109, 101, 101, 97, 108, 105,
45
+ 99, 101, 102, 99, 108, 111, 115, 101, 100, 244, 208, 2, 1, 113, 18, 32, 147, 62, 132, 167, 62, 200, 168, 193, 214, 23,
46
+ 213, 167, 253, 123, 66, 174, 190, 225, 219, 72, 23, 48, 89, 124, 104, 102, 136, 248, 114, 211, 44, 96, 162, 100, 100,
47
+ 97, 116, 97, 164, 99, 107, 101, 121, 120, 36, 49, 101, 102, 51, 98, 51, 50, 97, 45, 51, 99, 51, 97, 45, 52, 98, 53,
48
+ 101, 45, 57, 99, 49, 99, 45, 56, 99, 53, 99, 48, 99, 53, 99, 48, 99, 53, 99, 100, 114, 111, 111, 116, 163, 99, 99,
49
+ 105, 100, 216, 42, 88, 37, 0, 1, 113, 18, 32, 182, 49, 212, 214, 100, 1, 169, 136, 8, 175, 250, 164, 87, 186, 245,
50
+ 100, 132, 15, 48, 22, 184, 8, 165, 17, 16, 139, 22, 223, 40, 41, 194, 114, 101, 98, 121, 116, 101, 115, 88, 72, 162,
51
+ 100, 108, 101, 97, 102, 129, 130, 120, 36, 49, 101, 102, 51, 98, 51, 50, 97, 45, 51, 99, 51, 97, 45, 52, 98, 53, 101,
52
+ 45, 57, 99, 49, 99, 45, 56, 99, 53, 99, 48, 99, 53, 99, 48, 99, 53, 99, 162, 99, 97, 103, 101, 24, 42, 100, 110, 97,
53
+ 109, 101, 101, 97, 108, 105, 99, 101, 102, 99, 108, 111, 115, 101, 100, 244, 101, 118, 97, 108, 117, 101, 162, 100,
54
+ 108, 101, 97, 102, 129, 130, 120, 36, 49, 101, 102, 51, 98, 51, 50, 97, 45, 51, 99, 51, 97, 45, 52, 98, 53, 101, 45,
55
+ 57, 99, 49, 99, 45, 56, 99, 53, 99, 48, 99, 53, 99, 48, 99, 53, 99, 162, 99, 97, 103, 101, 24, 42, 100, 110, 97, 109,
56
+ 101, 101, 97, 108, 105, 99, 101, 102, 99, 108, 111, 115, 101, 100, 244, 100, 116, 121, 112, 101, 99, 112, 117, 116,
57
+ 101, 118, 97, 108, 117, 101, 162, 99, 97, 103, 101, 24, 42, 100, 110, 97, 109, 101, 101, 97, 108, 105, 99, 101, 103,
58
+ 112, 97, 114, 101, 110, 116, 115, 128
59
+ ])