@fireproof/core 0.3.21 → 0.4.0

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.
Files changed (56) hide show
  1. package/dist/blockstore.js +242 -0
  2. package/dist/clock.js +355 -0
  3. package/dist/crypto.js +59 -0
  4. package/dist/database.js +308 -0
  5. package/dist/db-index.js +314 -0
  6. package/dist/fireproof.js +83 -0
  7. package/dist/hooks/use-fireproof.js +100 -0
  8. package/dist/listener.js +110 -0
  9. package/dist/prolly.js +316 -0
  10. package/dist/sha1.js +74 -0
  11. package/dist/src/blockstore.js +242 -0
  12. package/dist/src/clock.js +355 -0
  13. package/dist/src/crypto.js +59 -0
  14. package/dist/src/database.js +312 -0
  15. package/dist/src/db-index.js +314 -0
  16. package/dist/src/fireproof.d.ts +319 -0
  17. package/dist/src/fireproof.js +38976 -0
  18. package/dist/src/fireproof.js.map +1 -0
  19. package/dist/src/fireproof.mjs +38972 -0
  20. package/dist/src/fireproof.mjs.map +1 -0
  21. package/dist/src/index.d.ts +1 -1
  22. package/dist/src/index.js +21 -16
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/index.mjs +21 -16
  25. package/dist/src/index.mjs.map +1 -1
  26. package/dist/src/listener.js +108 -0
  27. package/dist/src/prolly.js +319 -0
  28. package/dist/src/sha1.js +74 -0
  29. package/dist/src/utils.js +16 -0
  30. package/dist/src/valet.js +262 -0
  31. package/dist/test/block.js +57 -0
  32. package/dist/test/clock.test.js +556 -0
  33. package/dist/test/db-index.test.js +231 -0
  34. package/dist/test/fireproof.test.js +444 -0
  35. package/dist/test/fulltext.test.js +61 -0
  36. package/dist/test/helpers.js +39 -0
  37. package/dist/test/hydrator.test.js +142 -0
  38. package/dist/test/listener.test.js +103 -0
  39. package/dist/test/prolly.test.js +162 -0
  40. package/dist/test/proofs.test.js +45 -0
  41. package/dist/test/reproduce-fixture-bug.test.js +57 -0
  42. package/dist/test/valet.test.js +56 -0
  43. package/dist/utils.js +16 -0
  44. package/dist/valet.js +262 -0
  45. package/hooks/use-fireproof.js +38 -63
  46. package/package.json +13 -14
  47. package/src/blockstore.js +8 -4
  48. package/src/database.js +338 -0
  49. package/src/db-index.js +3 -3
  50. package/src/fireproof.js +65 -322
  51. package/src/listener.js +10 -8
  52. package/src/prolly.js +10 -6
  53. package/src/utils.js +16 -0
  54. package/src/valet.js +2 -2
  55. package/src/hydrator.js +0 -54
  56. package/src/index.js +0 -6
package/dist/utils.js ADDED
@@ -0,0 +1,16 @@
1
+ /* global localStorage */
2
+ let storageSupported = false;
3
+ try {
4
+ storageSupported = window.localStorage && true;
5
+ }
6
+ catch (e) { }
7
+ export function localGet(key) {
8
+ if (storageSupported) {
9
+ return localStorage && localStorage.getItem(key);
10
+ }
11
+ }
12
+ export function localSet(key, value) {
13
+ if (storageSupported) {
14
+ return localStorage && localStorage.setItem(key, value);
15
+ }
16
+ }
package/dist/valet.js ADDED
@@ -0,0 +1,262 @@
1
+ import { CarReader } from '@ipld/car';
2
+ import { CID } from 'multiformats/cid';
3
+ import { sha256 } from 'multiformats/hashes/sha2';
4
+ import * as CBW from '@ipld/car/buffer-writer';
5
+ import * as raw from 'multiformats/codecs/raw';
6
+ import * as Block from 'multiformats/block';
7
+ import * as dagcbor from '@ipld/dag-cbor';
8
+ import { openDB } from 'idb';
9
+ import cargoQueue from 'async/cargoQueue.js';
10
+ // @ts-ignore
11
+ import { bf } from 'prolly-trees/utils';
12
+ // @ts-ignore
13
+ import { nocache as cache } from 'prolly-trees/cache';
14
+ import { encrypt, decrypt } from './crypto.js';
15
+ import { Buffer } from 'buffer';
16
+ // @ts-ignore
17
+ import * as codec from 'encrypted-block';
18
+ import { rawSha1 as sha1sync } from './sha1.js';
19
+ const chunker = bf(3);
20
+ const NO_ENCRYPT = typeof process !== 'undefined' && !!process.env?.NO_ENCRYPT;
21
+ // ? process.env.NO_ENCRYPT : import.meta && import.meta.env.VITE_NO_ENCRYPT
22
+ export class Valet {
23
+ idb = null;
24
+ name = null;
25
+ uploadQueue = null;
26
+ alreadyEnqueued = new Set();
27
+ keyMaterial = null;
28
+ keyId = 'null';
29
+ /**
30
+ * Function installed by the database to upload car files
31
+ * @type {null|function(string, Uint8Array):Promise<void>}
32
+ */
33
+ uploadFunction = null;
34
+ constructor(name = 'default', keyMaterial) {
35
+ this.name = name;
36
+ this.setKeyMaterial(keyMaterial);
37
+ this.uploadQueue = cargoQueue(async (tasks, callback) => {
38
+ console.log('queue worker', tasks.length, tasks.reduce((acc, t) => acc + t.value.length, 0));
39
+ if (this.uploadFunction) {
40
+ // todo we can coalesce these into a single car file
41
+ return await this.withDB(async (db) => {
42
+ for (const task of tasks) {
43
+ await this.uploadFunction(task.carCid, task.value);
44
+ // update the indexedb to mark this car as no longer pending
45
+ const carMeta = await db.get('cidToCar', task.carCid);
46
+ delete carMeta.pending;
47
+ await db.put('cidToCar', carMeta);
48
+ }
49
+ });
50
+ }
51
+ callback();
52
+ });
53
+ this.uploadQueue.drain(async () => {
54
+ return await this.withDB(async (db) => {
55
+ const carKeys = (await db.getAllFromIndex('cidToCar', 'pending')).map(c => c.car);
56
+ for (const carKey of carKeys) {
57
+ await this.uploadFunction(carKey, await db.get('cars', carKey));
58
+ const carMeta = await db.get('cidToCar', carKey);
59
+ delete carMeta.pending;
60
+ await db.put('cidToCar', carMeta);
61
+ }
62
+ });
63
+ });
64
+ }
65
+ getKeyMaterial() {
66
+ return this.keyMaterial;
67
+ }
68
+ setKeyMaterial(km) {
69
+ if (km && !NO_ENCRYPT) {
70
+ const hex = Uint8Array.from(Buffer.from(km, 'hex'));
71
+ this.keyMaterial = km;
72
+ const hash = sha1sync(hex);
73
+ this.keyId = Buffer.from(hash).toString('hex');
74
+ }
75
+ else {
76
+ this.keyMaterial = null;
77
+ this.keyId = 'null';
78
+ }
79
+ // console.trace('keyId', this.name, this.keyId)
80
+ }
81
+ /**
82
+ * Group the blocks into a car and write it to the valet.
83
+ * @param {import('./blockstore.js').InnerBlockstore} innerBlockstore
84
+ * @param {Set<string>} cids
85
+ * @returns {Promise<void>}
86
+ * @memberof Valet
87
+ */
88
+ async writeTransaction(innerBlockstore, cids) {
89
+ if (innerBlockstore.lastCid) {
90
+ if (this.keyMaterial) {
91
+ // console.log('encrypting car', innerBlockstore.label)
92
+ const newCar = await blocksToEncryptedCarBlock(innerBlockstore.lastCid, innerBlockstore, this.keyMaterial);
93
+ await this.parkCar(newCar.cid.toString(), newCar.bytes, cids);
94
+ }
95
+ else {
96
+ const newCar = await blocksToCarBlock(innerBlockstore.lastCid, innerBlockstore);
97
+ await this.parkCar(newCar.cid.toString(), newCar.bytes, cids);
98
+ }
99
+ }
100
+ }
101
+ withDB = async (dbWorkFun) => {
102
+ if (!this.idb) {
103
+ this.idb = await openDB(`fp.${this.keyId}.${this.name}.valet`, 2, {
104
+ upgrade(db, oldVersion, newVersion, transaction) {
105
+ if (oldVersion < 1) {
106
+ db.createObjectStore('cars'); // todo use database name
107
+ const cidToCar = db.createObjectStore('cidToCar', { keyPath: 'car' });
108
+ cidToCar.createIndex('cids', 'cids', { multiEntry: true });
109
+ }
110
+ if (oldVersion < 2) {
111
+ const cidToCar = transaction.objectStore('cidToCar');
112
+ cidToCar.createIndex('pending', 'pending');
113
+ }
114
+ }
115
+ });
116
+ }
117
+ return await dbWorkFun(this.idb);
118
+ };
119
+ /**
120
+ *
121
+ * @param {string} carCid
122
+ * @param {*} value
123
+ */
124
+ async parkCar(carCid, value, cids) {
125
+ await this.withDB(async (db) => {
126
+ const tx = db.transaction(['cars', 'cidToCar'], 'readwrite');
127
+ await tx.objectStore('cars').put(value, carCid);
128
+ await tx.objectStore('cidToCar').put({ pending: 'y', car: carCid, cids: Array.from(cids) });
129
+ return await tx.done;
130
+ });
131
+ // upload to web3.storage if we have credentials
132
+ if (this.uploadFunction) {
133
+ if (this.alreadyEnqueued.has(carCid)) {
134
+ // console.log('already enqueued', carCid)
135
+ return;
136
+ }
137
+ // don't await this, it will be done in the queue
138
+ // console.log('add to queue', carCid, value.length)
139
+ this.uploadQueue.push({ carCid, value });
140
+ this.alreadyEnqueued.add(carCid);
141
+ }
142
+ else {
143
+ // console.log('no upload function', carCid, value.length, this.uploadFunction)
144
+ }
145
+ }
146
+ remoteBlockFunction = null;
147
+ async getBlock(dataCID) {
148
+ return await this.withDB(async (db) => {
149
+ const tx = db.transaction(['cars', 'cidToCar'], 'readonly');
150
+ const indexResp = await tx.objectStore('cidToCar').index('cids').get(dataCID);
151
+ const carCid = indexResp?.car;
152
+ if (!carCid) {
153
+ throw new Error('Missing block: ' + dataCID);
154
+ }
155
+ const carBytes = await tx.objectStore('cars').get(carCid);
156
+ const reader = await CarReader.fromBytes(carBytes);
157
+ if (this.keyMaterial) {
158
+ const roots = await reader.getRoots();
159
+ const readerGetWithCodec = async (cid) => {
160
+ const got = await reader.get(cid);
161
+ // console.log('got.', cid.toString())
162
+ let useCodec = codec;
163
+ if (cid.toString().indexOf('bafy') === 0) {
164
+ useCodec = dagcbor;
165
+ }
166
+ const decoded = await Block.decode({
167
+ ...got,
168
+ codec: useCodec,
169
+ hasher: sha256
170
+ });
171
+ // console.log('decoded', decoded.value)
172
+ return decoded;
173
+ };
174
+ const { blocks } = await blocksFromEncryptedCarBlock(roots[0], readerGetWithCodec, this.keyMaterial);
175
+ const block = blocks.find(b => b.cid.toString() === dataCID);
176
+ if (block) {
177
+ return block.bytes;
178
+ }
179
+ }
180
+ else {
181
+ const gotBlock = await reader.get(CID.parse(dataCID));
182
+ if (gotBlock) {
183
+ return gotBlock.bytes;
184
+ }
185
+ }
186
+ });
187
+ }
188
+ }
189
+ const blocksToCarBlock = async (lastCid, blocks) => {
190
+ let size = 0;
191
+ const headerSize = CBW.headerLength({ roots: [lastCid] });
192
+ size += headerSize;
193
+ if (!Array.isArray(blocks)) {
194
+ blocks = Array.from(blocks.entries());
195
+ }
196
+ for (const { cid, bytes } of blocks) {
197
+ size += CBW.blockLength({ cid, bytes });
198
+ }
199
+ const buffer = new Uint8Array(size);
200
+ const writer = await CBW.createWriter(buffer, { headerSize });
201
+ writer.addRoot(lastCid);
202
+ for (const { cid, bytes } of blocks) {
203
+ writer.write({ cid, bytes });
204
+ }
205
+ await writer.close();
206
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
207
+ };
208
+ const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial) => {
209
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
210
+ const encryptedBlocks = [];
211
+ const theCids = [];
212
+ for (const { cid } of blocks.entries()) {
213
+ theCids.push(cid.toString());
214
+ }
215
+ let last;
216
+ for await (const block of encrypt({
217
+ cids: theCids,
218
+ get: async (cid) => blocks.get(cid),
219
+ key: encryptionKey,
220
+ hasher: sha256,
221
+ chunker,
222
+ cache,
223
+ // codec: dagcbor, // should be crypto?
224
+ root: innerBlockStoreClockRootCid
225
+ })) {
226
+ encryptedBlocks.push(block);
227
+ last = block;
228
+ }
229
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
230
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
231
+ return encryptedCar;
232
+ };
233
+ // { root, get, key, cache, chunker, hasher }
234
+ const memoizeDecryptedCarBlocks = new Map();
235
+ const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
236
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
237
+ return memoizeDecryptedCarBlocks.get(cid.toString());
238
+ }
239
+ else {
240
+ const blocksPromise = (async () => {
241
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
242
+ // console.log('decrypting', keyMaterial, cid.toString())
243
+ const cids = new Set();
244
+ const decryptedBlocks = [];
245
+ for await (const block of decrypt({
246
+ root: cid,
247
+ get,
248
+ key: decryptionKey,
249
+ chunker,
250
+ hasher: sha256,
251
+ cache
252
+ // codec: dagcbor
253
+ })) {
254
+ decryptedBlocks.push(block);
255
+ cids.add(block.cid.toString());
256
+ }
257
+ return { blocks: decryptedBlocks, cids };
258
+ })();
259
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
260
+ return blocksPromise;
261
+ }
262
+ };
@@ -1,14 +1,16 @@
1
- /* global localStorage */
2
1
  // @ts-ignore
3
2
  import { useEffect, useState, createContext } from 'react'
4
- import { Fireproof, Listener, Hydrator } from '../src/index'
3
+ import { Fireproof, Listener } from '../src/fireproof.js'
5
4
 
6
- // export interface FireproofCtxValue {
7
- // addSubscriber: (label: String, fn: Function) => void
8
- // database: Fireproof
9
- // ready: boolean
10
- // persist: () => void
11
- // }
5
+ /**
6
+ @typedef {Object} FireproofCtxValue
7
+ @property {Function} addSubscriber - A function to add a subscriber with a label and function.
8
+ @property {Fireproof} database - An instance of the Fireproof class.
9
+ @property {boolean} ready - A boolean indicating whether the database is ready.
10
+ @param {string} label - A label for the subscriber.
11
+ @param {Function} fn - A function to be added as a subscriber.
12
+ @returns {void}
13
+ */
12
14
  export const FireproofCtx = createContext({
13
15
  addSubscriber: () => {},
14
16
  database: null,
@@ -27,28 +29,24 @@ const initializeDatabase = name => {
27
29
  }
28
30
 
29
31
  /**
30
- * @function useFireproof
31
- * React hook to initialize a Fireproof database, automatically saving and loading the clock.
32
- * You might need to `import { nodePolyfills } from 'vite-plugin-node-polyfills'` in your vite.config.ts
33
- * @param [defineDatabaseFn] Synchronous function that defines the database, run this before any async calls
34
- * @param [setupDatabaseFn] Asynchronous function that sets up the database, run this to load fixture data etc
35
- * @returns {FireproofCtxValue} { addSubscriber, database, ready }
36
- */
37
- export function useFireproof (
38
- defineDatabaseFn = () => {},
39
- setupDatabaseFn = async () => {},
40
- name
41
- ) {
32
+
33
+ @function useFireproof
34
+ React hook to initialize a Fireproof database, automatically saving and loading the clock.
35
+ You might need to import { nodePolyfills } from 'vite-plugin-node-polyfills' in your vite.config.ts
36
+ @param {string} name - The path to the database file
37
+ @param {function(database): void} [defineDatabaseFn] - Synchronous function that defines the database, run this before any async calls
38
+ @param {function(database): Promise<void>} [setupDatabaseFn] - Asynchronous function that sets up the database, run this to load fixture data etc
39
+ @returns {FireproofCtxValue} { addSubscriber, database, ready }
40
+ */
41
+ export function useFireproof (name = 'useFireproof', defineDatabaseFn = () => {}, setupDatabaseFn = async () => {}) {
42
42
  const [ready, setReady] = useState(false)
43
- initializeDatabase(name || 'useFireproof')
44
- const localStorageKey = 'fp.' + database.name
43
+ initializeDatabase(name)
45
44
 
46
45
  const addSubscriber = (label, fn) => {
47
46
  inboundSubscriberQueue.set(label, fn)
48
47
  }
49
48
 
50
49
  const listenerCallback = async event => {
51
- localSet(localStorageKey, JSON.stringify(database))
52
50
  if (event._external) return
53
51
  for (const [, fn] of inboundSubscriberQueue) fn()
54
52
  }
@@ -59,31 +57,11 @@ export function useFireproof (
59
57
  if (startedSetup) return
60
58
  startedSetup = true
61
59
  defineDatabaseFn(database) // define indexes before querying them
62
- console.log('Initializing database', database.name)
63
- const fp = localGet(localStorageKey) // todo use db.name
64
- if (fp) {
65
- try {
66
- const serialized = JSON.parse(fp)
67
- // console.log('serialized', JSON.stringify(serialized.indexes.map(c => c.clock)))
68
- console.log(`Loading previous database clock. (localStorage.removeItem('${localStorageKey}') to reset)`)
69
- await Hydrator.fromJSON(serialized, database)
70
- const changes = await database.changesSince()
71
- if (changes.rows.length < 2) {
72
- // console.log('Resetting database')
73
- throw new Error('Resetting database')
74
- }
75
- } catch (e) {
76
- console.error(`Error loading previous database clock. ${fp} Resetting.`, e)
77
- await Hydrator.zoom(database, [])
78
- await setupDatabaseFn(database)
79
- localSet(localStorageKey, JSON.stringify(database))
80
- }
81
- } else {
60
+ if (database.clock.length === 0) {
82
61
  await setupDatabaseFn(database)
83
- localSet(localStorageKey, JSON.stringify(database))
84
62
  }
85
63
  setReady(true)
86
- listener.on('*', listenerCallback)// hushed('*', listenerCallback, 250))
64
+ listener.on('*', listenerCallback) // hushed('*', listenerCallback, 250))
87
65
  }
88
66
  doSetup()
89
67
  }, [ready])
@@ -91,10 +69,7 @@ export function useFireproof (
91
69
  return {
92
70
  addSubscriber,
93
71
  database,
94
- ready,
95
- persist: () => {
96
- localSet(localStorageKey, JSON.stringify(database))
97
- }
72
+ ready
98
73
  }
99
74
  }
100
75
 
@@ -114,20 +89,20 @@ export function useFireproof (
114
89
  // (...args) =>
115
90
  // husher(id, () => workFn(...args), ms)
116
91
 
117
- let storageSupported = false
118
- try {
119
- storageSupported = window.localStorage && true
120
- } catch (e) {}
121
- export function localGet (key) {
122
- if (storageSupported) {
123
- return localStorage && localStorage.getItem(key)
124
- }
125
- }
126
- function localSet (key, value) {
127
- if (storageSupported) {
128
- return localStorage && localStorage.setItem(key, value)
129
- }
130
- }
92
+ // let storageSupported = false
93
+ // try {
94
+ // storageSupported = window.localStorage && true
95
+ // } catch (e) {}
96
+ // export function localGet (key) {
97
+ // if (storageSupported) {
98
+ // return localStorage && localStorage.getItem(key)
99
+ // }
100
+ // }
101
+ // function localSet (key, value) {
102
+ // if (storageSupported) {
103
+ // return localStorage && localStorage.setItem(key, value)
104
+ // }
105
+ // }
131
106
  // function localRemove(key) {
132
107
  // if (storageSupported) {
133
108
  // return localStorage && localStorage.removeItem(key)
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.3.21",
3
+ "version": "0.4.0",
4
4
  "description": "Realtime database for IPFS",
5
- "main": "dist/src/index.js",
6
- "module": "dist/src/index.mjs",
7
- "typings": "dist/src/index.d.ts",
8
- "types": "dist/src/index.d.ts",
5
+ "main": "dist/src/fireproof.js",
6
+ "module": "dist/src/fireproof.mjs",
7
+ "typings": "dist/src/fireproof.d.ts",
8
+ "types": "dist/src/fireproof.d.ts",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "keygen": "node scripts/keygen.js",
@@ -14,11 +14,11 @@
14
14
  "test:mocha": "mocha --reporter list test/*.test.js",
15
15
  "test:watch": "npm run test:mocha -- -w --parallel",
16
16
  "coverage": "c8 -r html -r text npm test",
17
- "prepublishOnly": "cp ../../README.md .",
17
+ "prepublishOnly": "cp ../../README.md . && npm run build",
18
18
  "postpublish": "rm README.md",
19
19
  "lint": "standard",
20
20
  "lint:fix": "standard --fix",
21
- "tbuild": "tsc --build",
21
+ "tsc": "tsc --watch",
22
22
  "build": "rollup -c"
23
23
  },
24
24
  "keywords": [
@@ -99,14 +99,13 @@
99
99
  "homepage": "https://fireproof.storage",
100
100
  "exports": {
101
101
  ".": {
102
- "types": "./dist/src/index.d.ts",
103
- "import": "./dist/src/index.mjs",
104
- "module": "./dist/src/index.mjs",
105
- "default": "./dist/src/index.js",
106
- "require": "./dist/src/index.js"
102
+ "types": "./dist/src/fireproof.d.ts",
103
+ "import": "./dist/src/fireproof.mjs",
104
+ "module": "./dist/src/fireproof.mjs",
105
+ "default": "./dist/src/fireproof.js",
106
+ "require": "./dist/src/fireproof.js"
107
107
  },
108
- "./use-fireproof": {
109
- "types": "./dist/hooks/use-fireproof.d.ts",
108
+ "./hooks/use-fireproof": {
110
109
  "import": "./hooks/use-fireproof.js"
111
110
  },
112
111
  "./package.json": "./package.json"
package/src/blockstore.js CHANGED
@@ -40,7 +40,10 @@ export class TransactionBlockstore {
40
40
  inflightTransactions = new Set()
41
41
 
42
42
  constructor (name, encryptionKey) {
43
- this.valet = new Valet(name, encryptionKey)
43
+ if (name) {
44
+ this.valet = new Valet(name, encryptionKey)
45
+ }
46
+ this.remoteBlockFunction = null
44
47
  }
45
48
 
46
49
  /**
@@ -73,6 +76,7 @@ export class TransactionBlockstore {
73
76
  async committedGet (key) {
74
77
  const old = this.committedBlocks.get(key)
75
78
  if (old) return old
79
+ if (!this.valet) throw new Error('Missing block: ' + key)
76
80
  const got = await this.valet.getBlock(key)
77
81
  // console.log('committedGet: ' + key)
78
82
  this.committedBlocks.set(key, got)
@@ -84,9 +88,9 @@ export class TransactionBlockstore {
84
88
  }
85
89
 
86
90
  async networkGet (key) {
87
- if (this.valet.remoteBlockFunction) {
91
+ if (this.remoteBlockFunction) {
88
92
  // todo why is this on valet?
89
- const value = await husher(key, async () => await this.valet.remoteBlockFunction(key))
93
+ const value = await husher(key, async () => await this.remoteBlockFunction(key))
90
94
  if (value) {
91
95
  // console.log('networkGot: ' + key, value.length)
92
96
  doTransaction('networkGot: ' + key, this, async innerBlockstore => {
@@ -166,7 +170,7 @@ export class TransactionBlockstore {
166
170
  cids.add(stringCid)
167
171
  }
168
172
  }
169
- if (cids.size > 0) {
173
+ if (cids.size > 0 && this.valet) {
170
174
  // console.log(innerBlockstore.label, 'committing', cids.size, 'blocks')
171
175
  await this.valet.writeTransaction(innerBlockstore, cids)
172
176
  }