@fireproof/core 0.6.1 → 0.6.3-dev

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.
@@ -0,0 +1,131 @@
1
+ import { readFileSync
2
+ // createReadStream
3
+ } from 'node:fs';
4
+ import { mkdir, writeFile } from 'fs/promises';
5
+ import { openDB } from 'idb';
6
+ import { join, dirname } from 'path';
7
+ // import { parse } from '@jsonlines/core'
8
+ // import cargoQueue from 'async/cargoQueue.js'
9
+ import { homedir } from 'os';
10
+ const defaultConfig = {
11
+ dataDir: join(homedir(), '.fireproof'),
12
+ headerKeyPrefix: 'fp.'
13
+ };
14
+ const FORCE_IDB = typeof process !== 'undefined' && !!process.env?.FORCE_IDB;
15
+ /* global localStorage */
16
+ export class Loader {
17
+ constructor(name, keyId, config = defaultConfig) {
18
+ this.name = name;
19
+ this.keyId = keyId;
20
+ this.config = config;
21
+ this.isBrowser = false;
22
+ try {
23
+ this.isBrowser = window.localStorage && true;
24
+ }
25
+ catch (e) { }
26
+ }
27
+ withDB = async (dbWorkFun) => {
28
+ if (!this.idb) {
29
+ this.idb = await openDB(`fp.${this.keyId}.${this.name}.valet`, 3, {
30
+ upgrade(db, oldVersion, newVersion, transaction) {
31
+ if (oldVersion < 1) {
32
+ db.createObjectStore('cars');
33
+ }
34
+ }
35
+ });
36
+ }
37
+ return await dbWorkFun(this.idb);
38
+ };
39
+ async writeCars(cars) {
40
+ // console.log('writeCars', this.config.dataDir, this.name, cars.map(c => c.cid.toString()))
41
+ // console.log('writeCars', cars.length)
42
+ if (FORCE_IDB || this.isBrowser) {
43
+ await this.writeCarsIDB(cars);
44
+ }
45
+ else {
46
+ const writes = [];
47
+ for (const { cid, bytes } of cars) {
48
+ const carFilename = join(this.config.dataDir, this.name, `${cid.toString()}.car`);
49
+ // console.log('writeCars', carFilename)
50
+ writes.push(writeSync(carFilename, bytes));
51
+ }
52
+ await Promise.all(writes);
53
+ }
54
+ }
55
+ async writeCarsIDB(cars) {
56
+ return await this.withDB(async (db) => {
57
+ const tx = db.transaction(['cars'], 'readwrite');
58
+ for (const { cid, bytes, replaces } of cars) {
59
+ await tx.objectStore('cars').put(bytes, cid.toString());
60
+ // todo remove old maps
61
+ if (replaces) {
62
+ await tx.objectStore('cars').delete(replaces.toString());
63
+ }
64
+ }
65
+ return await tx.done;
66
+ });
67
+ }
68
+ async readCar(carCid) {
69
+ if (FORCE_IDB || this.isBrowser) {
70
+ return await this.readCarIDB(carCid);
71
+ }
72
+ else {
73
+ const carFilename = join(this.config.dataDir, this.name, `${carCid.toString()}.car`);
74
+ const got = readFileSync(carFilename);
75
+ // console.log('readCar', carFilename, got.constructor.name)
76
+ return got;
77
+ }
78
+ }
79
+ async readCarIDB(carCid) {
80
+ return await this.withDB(async (db) => {
81
+ const tx = db.transaction(['cars'], 'readonly');
82
+ // console.log('getCarReader', carCid)
83
+ return await tx.objectStore('cars').get(carCid);
84
+ });
85
+ }
86
+ getHeader() {
87
+ if (this.isBrowser) {
88
+ return localStorage.getItem(this.config.headerKeyPrefix + this.name);
89
+ }
90
+ else {
91
+ return loadSync(this.headerFilename());
92
+ // return null
93
+ }
94
+ }
95
+ async saveHeader(stringValue) {
96
+ // console.log('saveHeader', this.isBrowser)
97
+ if (this.isBrowser) {
98
+ // console.log('localStorage!', this.config.headerKeyPrefix)
99
+ return localStorage.setItem(this.config.headerKeyPrefix + this.name, stringValue);
100
+ }
101
+ else {
102
+ // console.log('no localStorage', this.config.dataDir, this.name)
103
+ // console.log('saving clock to', this.headerFilename(), stringValue)
104
+ try {
105
+ await writeSync(this.headerFilename(), stringValue);
106
+ }
107
+ catch (error) {
108
+ console.log('error', error);
109
+ }
110
+ // console.log('saved clock to', this.headerFilename())
111
+ }
112
+ }
113
+ headerFilename() {
114
+ // console.log('headerFilename', this.config.dataDir, this.name)
115
+ return join(this.config.dataDir, this.name, 'header.json');
116
+ }
117
+ }
118
+ function loadSync(filename) {
119
+ try {
120
+ return readFileSync(filename, 'utf8').toString();
121
+ }
122
+ catch (error) {
123
+ // console.log('error', error)
124
+ return null;
125
+ }
126
+ }
127
+ async function writeSync(fullpath, stringValue) {
128
+ await mkdir(dirname(fullpath), { recursive: true });
129
+ // writeFileSync(fullpath, stringValue)
130
+ await writeFile(fullpath, stringValue);
131
+ }
@@ -8,6 +8,7 @@ import { nocache as cache } from 'prolly-trees/cache';
8
8
  import { CIDCounter, bf, simpleCompare as compare } from 'prolly-trees/utils';
9
9
  import * as codec from '@ipld/dag-cbor';
10
10
  import { sha256 as hasher } from 'multiformats/hashes/sha2';
11
+ // import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'
11
12
  import { doTransaction } from './blockstore.js';
12
13
  import { create as createBlock } from 'multiformats/block';
13
14
  const blockOpts = { cache, chunker: bf(30), codec, hasher, compare };
package/dist/src/valet.js CHANGED
@@ -6,8 +6,8 @@ import * as CBW from '@ipld/car/buffer-writer';
6
6
  import * as raw from 'multiformats/codecs/raw';
7
7
  import * as Block from 'multiformats/block';
8
8
  import * as dagcbor from '@ipld/dag-cbor';
9
- import { openDB } from 'idb';
10
9
  import cargoQueue from 'async/cargoQueue.js';
10
+ import { Loader } from './loader.js';
11
11
  // @ts-ignore
12
12
  // @ts-ignore
13
13
  import { bf, simpleCompare as compare } from 'prolly-trees/utils';
@@ -44,6 +44,7 @@ export class Valet {
44
44
  constructor(name = 'default', keyMaterial) {
45
45
  this.name = name;
46
46
  this.setKeyMaterial(keyMaterial);
47
+ this.loader = new Loader(name, this.keyId); // todo send this config.loader, if we ever need it
47
48
  this.uploadQueue = cargoQueue(async (tasks, callback) => {
48
49
  // console.log(
49
50
  // 'queue worker',
@@ -76,6 +77,9 @@ export class Valet {
76
77
  // })
77
78
  });
78
79
  }
80
+ saveHeader(header) {
81
+ return this.loader.saveHeader(header);
82
+ }
79
83
  getKeyMaterial() {
80
84
  return this.keyMaterial;
81
85
  }
@@ -116,18 +120,6 @@ export class Valet {
116
120
  throw new Error('missing lastCid for car header');
117
121
  }
118
122
  }
119
- withDB = async (dbWorkFun) => {
120
- if (!this.idb) {
121
- this.idb = await openDB(`fp.${this.keyId}.${this.name}.valet`, 3, {
122
- upgrade(db, oldVersion, newVersion, transaction) {
123
- if (oldVersion < 1) {
124
- db.createObjectStore('cars');
125
- }
126
- }
127
- });
128
- }
129
- return await dbWorkFun(this.idb);
130
- };
131
123
  /**
132
124
  * Iterate over all blocks in the store.
133
125
  *
@@ -167,6 +159,7 @@ export class Valet {
167
159
  blockHasher: blockOpts.hasher,
168
160
  blockCodec: blockOpts.codec
169
161
  });
162
+ this.valetRoot = indexNode;
170
163
  }
171
164
  const got = await indexNode.get(cid);
172
165
  // console.log('getCarCIDForCID', cid, got)
@@ -211,13 +204,14 @@ export class Valet {
211
204
  * @param {*} value
212
205
  */
213
206
  async parkCar(carCid, value, cids) {
207
+ // const callId = Math.random().toString(36).substring(7)
214
208
  // console.log('parkCar', this.instanceId, this.name, carCid, cids)
215
209
  const combinedReader = await this.getCombinedReader(carCid);
216
210
  const mapNode = await addCidsToCarIndex(combinedReader, this.valetRoot, this.valetRootCid, Array.from(cids).map(cid => ({ key: cid.toString(), value: carCid.toString() })));
217
211
  this.valetRoot = mapNode;
218
212
  this.valetRootCid = mapNode.cid;
219
213
  // make a block set with all the cids of the map
220
- const saveValetBlocks = new VMemoryBlockstore(); // todo this blockstore should read from the last valetCid car also
214
+ const saveValetBlocks = new VMemoryBlockstore();
221
215
  for await (const cidx of mapNode.cids()) {
222
216
  const bytes = await combinedReader.get(cidx);
223
217
  saveValetBlocks.put(cidx, bytes);
@@ -230,7 +224,8 @@ export class Valet {
230
224
  newValetCidCar = await blocksToCarBlock(this.valetRootCid, saveValetBlocks);
231
225
  }
232
226
  // console.log('newValetCidCar', this.name, Math.floor(newValetCidCar.bytes.length / 1024))
233
- await this.writeCars([
227
+ // console.log('writeCars', callId, carCid.toString(), newValetCidCar.cid.toString())
228
+ await this.loader.writeCars([
234
229
  {
235
230
  cid: carCid,
236
231
  bytes: value,
@@ -244,6 +239,7 @@ export class Valet {
244
239
  }
245
240
  ]);
246
241
  this.valetRootCarCid = newValetCidCar.cid; // goes to clock
242
+ // console.log('wroteCars', callId, carCid.toString(), newValetCidCar.cid.toString())
247
243
  // console.log('parked car', carCid, value.length, Array.from(cids))
248
244
  // upload to web3.storage if we have credentials
249
245
  if (this.uploadFunction) {
@@ -260,27 +256,12 @@ export class Valet {
260
256
  // console.log('no upload function', carCid, value.length, this.uploadFunction)
261
257
  }
262
258
  }
263
- async writeCars(cars) {
264
- return await this.withDB(async (db) => {
265
- const tx = db.transaction(['cars'], 'readwrite');
266
- for (const { cid, bytes, replaces } of cars) {
267
- await tx.objectStore('cars').put(bytes, cid.toString());
268
- // todo remove old maps
269
- if (replaces) {
270
- await tx.objectStore('cars').delete(replaces.toString());
271
- }
272
- }
273
- return await tx.done;
274
- });
275
- }
276
259
  remoteBlockFunction = null;
277
260
  async getCarReader(carCid) {
278
261
  carCid = carCid.toString();
279
- const carBytes = await this.withDB(async (db) => {
280
- const tx = db.transaction(['cars'], 'readonly');
281
- // console.log('getCarReader', carCid)
282
- return await tx.objectStore('cars').get(carCid);
283
- });
262
+ const carBytes = await this.loader.readCar(carCid);
263
+ // const callID = Math.random().toString(36).substring(7)
264
+ // console.log('getCarReader', callID, carCid)
284
265
  const reader = await CarReader.fromBytes(carBytes);
285
266
  if (this.keyMaterial) {
286
267
  const roots = await reader.getRoots();
@@ -303,6 +284,7 @@ export class Valet {
303
284
  const { blocks } = await blocksFromEncryptedCarBlock(roots[0], readerGetWithCodec, this.keyMaterial);
304
285
  // last block is the root ??? todo
305
286
  const rootBlock = blocks[blocks.length - 1];
287
+ // console.log('got reader', callID, carCid)
306
288
  return {
307
289
  root: rootBlock,
308
290
  get: async (dataCID) => {
@@ -424,6 +406,8 @@ const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
424
406
  };
425
407
  const addCidsToCarIndex = async (blockstore, valetRoot, valetRootCid, bulkOperations) => {
426
408
  let indexNode;
409
+ // const callID = Math.random().toString(32).substring(2, 8)
410
+ // console.log('addCidsToCarIndex', callID, valetRootCid, bulkOperations.length)
427
411
  if (valetRootCid) {
428
412
  if (valetRoot) {
429
413
  indexNode = valetRoot;
@@ -445,6 +429,7 @@ const addCidsToCarIndex = async (blockstore, valetRoot, valetRootCid, bulkOperat
445
429
  // console.log('adding', key, value)
446
430
  await indexNode.set(key, value);
447
431
  }
432
+ // console.log('newCidsToCarIndex', callID, indexNode.cid, bulkOperations.length)
448
433
  return indexNode;
449
434
  };
450
435
  export class VMemoryBlockstore {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.6.1",
3
+ "version": "0.6.3-dev",
4
4
  "description": "Live data for React, accelerated by proofs, powered by IPFS",
5
5
  "main": "dist/src/fireproof.js",
6
6
  "module": "dist/src/fireproof.mjs",
@@ -40,6 +40,8 @@
40
40
  "dependencies": {
41
41
  "@ipld/car": "^5.1.0",
42
42
  "@ipld/dag-cbor": "^9.0.0",
43
+ "@jsonlines/core": "^1.0.2",
44
+ "@multiformats/blake2": "^1.0.13",
43
45
  "@rollup/plugin-commonjs": "^24.0.1",
44
46
  "archy": "^1.0.0",
45
47
  "async": "^3.2.4",
@@ -74,6 +76,7 @@
74
76
  "rollup-plugin-esbuild": "^5.0.0",
75
77
  "rollup-plugin-node-builtins": "^2.1.2",
76
78
  "rollup-plugin-polyfill-node": "^0.12.0",
79
+ "rollup-plugin-visualizer": "^5.9.0",
77
80
  "standard": "^17.0.0",
78
81
  "typescript": "^5.0.2",
79
82
  "webpack": "^5.78.0",
@@ -119,8 +122,5 @@
119
122
  "dist",
120
123
  "hooks",
121
124
  "README.md"
122
- ],
123
- "workspaces": [
124
- "examples/todomvc"
125
125
  ]
126
126
  }
package/src/database.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import { visMerkleClock, visMerkleTree, vis, put, get, getAll, eventsSince } from './prolly.js'
3
3
  import { doTransaction, TransactionBlockstore } from './blockstore.js'
4
4
  import charwise from 'charwise'
5
- import { localSet } from './utils.js'
6
5
  import { CID } from 'multiformats'
7
6
 
8
7
  // TypeScript Types
@@ -34,7 +33,7 @@ export class Database {
34
33
  this.name = name
35
34
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
36
35
  this.blocks = new TransactionBlockstore(name, config.key)
37
- this.indexBlocks = new TransactionBlockstore(name + '.indexes', config.key)
36
+ this.indexBlocks = new TransactionBlockstore(name ? name + '.indexes' : null, config.key)
38
37
  this.clock = clock
39
38
  this.config = config
40
39
  }
@@ -78,7 +77,7 @@ export class Database {
78
77
 
79
78
  maybeSaveClock () {
80
79
  if (this.name && this.blocks.valet) {
81
- localSet('fp.' + this.name, JSON.stringify(this))
80
+ this.blocks.valet.saveHeader(JSON.stringify(this))
82
81
  }
83
82
  }
84
83
 
@@ -314,6 +313,7 @@ export class Database {
314
313
  console.error('failed', event)
315
314
  throw new Error('failed to put at storage layer')
316
315
  }
316
+ // await new Promise(resolve => setTimeout(resolve, 10)) // makes concurrent tests work
317
317
  this.applyClock(prevClock, result.head)
318
318
  await this.notifyListeners([decodedEvent]) // this type is odd
319
319
  return {
package/src/fireproof.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import randomBytes from 'randombytes'
2
+ // import { randomBytes } from 'crypto'
2
3
  import { Database, parseCID } from './database.js'
3
4
  import { Listener } from './listener.js'
4
5
  import { DbIndex as Index } from './db-index.js'
5
6
  // import { TransactionBlockstore } from './blockstore.js'
6
- import { localGet } from './utils.js'
7
+ import { Loader } from './loader.js'
7
8
  import { Sync } from './sync.js'
8
9
 
9
10
  // todo remove Listener in 0.7.0
@@ -21,8 +22,8 @@ export class Fireproof {
21
22
  static storage = (name = null, opts = {}) => {
22
23
  if (name) {
23
24
  opts.name = name
24
- // todo this can come from a registry also eg remote database / config, etc
25
- const existing = localGet('fp.' + name)
25
+ const loader = new Loader(name, opts.loader)
26
+ const existing = loader.getHeader()
26
27
  if (existing) {
27
28
  const existingConfig = JSON.parse(existing)
28
29
  return Fireproof.fromConfig(name, existingConfig, opts)
package/src/loader.js ADDED
@@ -0,0 +1,168 @@
1
+ import {
2
+ readFileSync
3
+ // createReadStream
4
+ } from 'node:fs'
5
+ import { mkdir, writeFile } from 'fs/promises'
6
+ import { openDB } from 'idb'
7
+ import { join, dirname } from 'path'
8
+ // import { parse } from '@jsonlines/core'
9
+ // import cargoQueue from 'async/cargoQueue.js'
10
+ import { homedir } from 'os'
11
+
12
+ const defaultConfig = {
13
+ dataDir: join(homedir(), '.fireproof'),
14
+ headerKeyPrefix: 'fp.'
15
+ }
16
+
17
+ const FORCE_IDB = typeof process !== 'undefined' && !!process.env?.FORCE_IDB
18
+
19
+ /* global localStorage */
20
+
21
+ export class Loader {
22
+ constructor (name, keyId, config = defaultConfig) {
23
+ this.name = name
24
+ this.keyId = keyId
25
+ this.config = config
26
+ this.isBrowser = false
27
+ try {
28
+ this.isBrowser = window.localStorage && true
29
+ } catch (e) {}
30
+ }
31
+
32
+ withDB = async (dbWorkFun) => {
33
+ if (!this.idb) {
34
+ this.idb = await openDB(`fp.${this.keyId}.${this.name}.valet`, 3, {
35
+ upgrade (db, oldVersion, newVersion, transaction) {
36
+ if (oldVersion < 1) {
37
+ db.createObjectStore('cars')
38
+ }
39
+ }
40
+ })
41
+ }
42
+ return await dbWorkFun(this.idb)
43
+ }
44
+
45
+ async writeCars (cars) {
46
+ // console.log('writeCars', this.config.dataDir, this.name, cars.map(c => c.cid.toString()))
47
+ // console.log('writeCars', cars.length)
48
+
49
+ if (FORCE_IDB || this.isBrowser) {
50
+ await this.writeCarsIDB(cars)
51
+ } else {
52
+ const writes = []
53
+ for (const { cid, bytes } of cars) {
54
+ const carFilename = join(this.config.dataDir, this.name, `${cid.toString()}.car`)
55
+ // console.log('writeCars', carFilename)
56
+ writes.push(writeSync(carFilename, bytes))
57
+ }
58
+ await Promise.all(writes)
59
+ }
60
+ }
61
+
62
+ async writeCarsIDB (cars) {
63
+ return await this.withDB(async db => {
64
+ const tx = db.transaction(['cars'], 'readwrite')
65
+ for (const { cid, bytes, replaces } of cars) {
66
+ await tx.objectStore('cars').put(bytes, cid.toString())
67
+ // todo remove old maps
68
+ if (replaces) {
69
+ await tx.objectStore('cars').delete(replaces.toString())
70
+ }
71
+ }
72
+ return await tx.done
73
+ })
74
+ }
75
+
76
+ async readCar (carCid) {
77
+ if (FORCE_IDB || this.isBrowser) {
78
+ return await this.readCarIDB(carCid)
79
+ } else {
80
+ const carFilename = join(this.config.dataDir, this.name, `${carCid.toString()}.car`)
81
+ const got = readFileSync(carFilename)
82
+ // console.log('readCar', carFilename, got.constructor.name)
83
+ return got
84
+ }
85
+ }
86
+
87
+ async readCarIDB (carCid) {
88
+ return await this.withDB(async db => {
89
+ const tx = db.transaction(['cars'], 'readonly')
90
+ // console.log('getCarReader', carCid)
91
+ return await tx.objectStore('cars').get(carCid)
92
+ })
93
+ }
94
+
95
+ getHeader () {
96
+ if (this.isBrowser) {
97
+ return localStorage.getItem(this.config.headerKeyPrefix + this.name)
98
+ } else {
99
+ return loadSync(this.headerFilename())
100
+ // return null
101
+ }
102
+ }
103
+
104
+ async saveHeader (stringValue) {
105
+ // console.log('saveHeader', this.isBrowser)
106
+ if (this.isBrowser) {
107
+ // console.log('localStorage!', this.config.headerKeyPrefix)
108
+ return localStorage.setItem(this.config.headerKeyPrefix + this.name, stringValue)
109
+ } else {
110
+ // console.log('no localStorage', this.config.dataDir, this.name)
111
+ // console.log('saving clock to', this.headerFilename(), stringValue)
112
+
113
+ try {
114
+ await writeSync(this.headerFilename(), stringValue)
115
+ } catch (error) {
116
+ console.log('error', error)
117
+ }
118
+
119
+ // console.log('saved clock to', this.headerFilename())
120
+ }
121
+ }
122
+
123
+ headerFilename () {
124
+ // console.log('headerFilename', this.config.dataDir, this.name)
125
+ return join(this.config.dataDir, this.name, 'header.json')
126
+ }
127
+
128
+ // async loadData (database, filename) {
129
+ // const fullFilePath = join(process.cwd(), filename)
130
+ // const readableStream = createReadStream(fullFilePath)
131
+ // const parseStream = parse()
132
+ // readableStream.pipe(parseStream)
133
+
134
+ // const saveQueue = cargoQueue(async (tasks, callback) => {
135
+ // for (const t of tasks) {
136
+ // await database.put(t)
137
+ // }
138
+ // callback()
139
+ // })
140
+
141
+ // parseStream.on('data', async (data) => {
142
+ // saveQueue.push(data)
143
+ // })
144
+ // let res
145
+ // const p = new Promise((resolve, reject) => {
146
+ // res = resolve
147
+ // })
148
+ // saveQueue.drain(async (x) => {
149
+ // res()
150
+ // })
151
+ // return p
152
+ // }
153
+ }
154
+
155
+ function loadSync (filename) {
156
+ try {
157
+ return readFileSync(filename, 'utf8').toString()
158
+ } catch (error) {
159
+ // console.log('error', error)
160
+ return null
161
+ }
162
+ }
163
+
164
+ async function writeSync (fullpath, stringValue) {
165
+ await mkdir(dirname(fullpath), { recursive: true })
166
+ // writeFileSync(fullpath, stringValue)
167
+ await writeFile(fullpath, stringValue)
168
+ }
package/src/prolly.js CHANGED
@@ -15,6 +15,8 @@ import { nocache as cache } from 'prolly-trees/cache'
15
15
  import { CIDCounter, bf, simpleCompare as compare } from 'prolly-trees/utils'
16
16
  import * as codec from '@ipld/dag-cbor'
17
17
  import { sha256 as hasher } from 'multiformats/hashes/sha2'
18
+ // import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'
19
+
18
20
  import { doTransaction } from './blockstore.js'
19
21
  import { create as createBlock } from 'multiformats/block'
20
22
  const blockOpts = { cache, chunker: bf(30), codec, hasher, compare }