@fireproof/core 0.16.1 → 0.16.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. package/README.md +14 -7
  2. package/dist/browser/fireproof.cjs +251 -34623
  3. package/dist/browser/fireproof.cjs.map +1 -7
  4. package/dist/browser/fireproof.d.cts +286 -0
  5. package/dist/browser/fireproof.d.ts +286 -0
  6. package/dist/browser/fireproof.global.js +21282 -0
  7. package/dist/browser/fireproof.global.js.map +1 -0
  8. package/dist/browser/fireproof.js +1114 -0
  9. package/dist/browser/fireproof.js.map +1 -0
  10. package/dist/browser/metafile-cjs.json +1 -0
  11. package/dist/browser/metafile-esm.json +1 -0
  12. package/dist/browser/metafile-iife.json +1 -0
  13. package/dist/memory/fireproof.cjs +1143 -0
  14. package/dist/memory/fireproof.cjs.map +1 -0
  15. package/dist/memory/fireproof.d.cts +286 -0
  16. package/dist/memory/fireproof.d.ts +286 -0
  17. package/dist/memory/fireproof.global.js +21282 -0
  18. package/dist/memory/fireproof.global.js.map +1 -0
  19. package/dist/memory/fireproof.js +1114 -0
  20. package/dist/memory/fireproof.js.map +1 -0
  21. package/dist/memory/metafile-cjs.json +1 -0
  22. package/dist/memory/metafile-esm.json +1 -0
  23. package/dist/memory/metafile-iife.json +1 -0
  24. package/dist/node/fireproof.cjs +210 -43910
  25. package/dist/node/fireproof.cjs.map +1 -7
  26. package/dist/node/fireproof.d.cts +286 -0
  27. package/dist/node/fireproof.d.ts +286 -0
  28. package/dist/node/fireproof.global.js +21338 -0
  29. package/dist/node/fireproof.global.js.map +1 -0
  30. package/dist/node/fireproof.js +1114 -0
  31. package/dist/node/fireproof.js.map +1 -0
  32. package/dist/node/metafile-cjs.json +1 -0
  33. package/dist/node/metafile-esm.json +1 -0
  34. package/dist/node/metafile-iife.json +1 -0
  35. package/package.json +38 -33
  36. package/dist/browser/fireproof.esm.js +0 -35509
  37. package/dist/browser/fireproof.esm.js.map +0 -7
  38. package/dist/browser/fireproof.iife.js +0 -35517
  39. package/dist/browser/fireproof.iife.js.map +0 -7
  40. package/dist/node/fireproof.esm.js +0 -44848
  41. package/dist/node/fireproof.esm.js.map +0 -7
  42. package/dist/types/apply-head-queue.d.ts +0 -15
  43. package/dist/types/crdt-clock.d.ts +0 -20
  44. package/dist/types/crdt-helpers.d.ts +0 -14
  45. package/dist/types/crdt.d.ts +0 -27
  46. package/dist/types/database.d.ts +0 -48
  47. package/dist/types/eb-edge.d.ts +0 -0
  48. package/dist/types/eb-node.d.ts +0 -3
  49. package/dist/types/eb-web.d.ts +0 -3
  50. package/dist/types/files.d.ts +0 -12
  51. package/dist/types/fireproof.d.ts +0 -3
  52. package/dist/types/index.d.ts +0 -31
  53. package/dist/types/indexer-helpers.d.ts +0 -57
  54. package/dist/types/types.d.ts +0 -136
  55. package/dist/types/version.d.ts +0 -1
  56. package/dist/types/write-queue.d.ts +0 -7
@@ -0,0 +1,1143 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/fireproof.ts
31
+ var fireproof_exports = {};
32
+ __export(fireproof_exports, {
33
+ Database: () => Database,
34
+ fireproof: () => fireproof
35
+ });
36
+ module.exports = __toCommonJS(fireproof_exports);
37
+
38
+ // src/database.ts
39
+ var import_uuidv7 = require("uuidv7");
40
+
41
+ // src/write-queue.ts
42
+ function writeQueue(worker, payload = Infinity, unbounded = false) {
43
+ const queue = [];
44
+ let isProcessing = false;
45
+ async function process() {
46
+ if (isProcessing || queue.length === 0)
47
+ return;
48
+ isProcessing = true;
49
+ const tasksToProcess = queue.splice(0, payload);
50
+ const updates = tasksToProcess.map((item) => item.task);
51
+ if (unbounded) {
52
+ const promises = updates.map(async (update, index2) => {
53
+ try {
54
+ const result = await worker([update]);
55
+ tasksToProcess[index2].resolve(result);
56
+ } catch (error) {
57
+ tasksToProcess[index2].reject(error);
58
+ }
59
+ });
60
+ await Promise.all(promises);
61
+ } else {
62
+ try {
63
+ const result = await worker(updates);
64
+ tasksToProcess.forEach((task) => task.resolve(result));
65
+ } catch (error) {
66
+ tasksToProcess.forEach((task) => task.reject(error));
67
+ }
68
+ }
69
+ isProcessing = false;
70
+ void process();
71
+ }
72
+ return {
73
+ push(task) {
74
+ return new Promise((resolve, reject) => {
75
+ queue.push({ task, resolve, reject });
76
+ void process();
77
+ });
78
+ }
79
+ };
80
+ }
81
+
82
+ // src/crdt.ts
83
+ var import_encrypted_blockstore2 = require("@fireproof/encrypted-blockstore");
84
+
85
+ // src/eb-web.ts
86
+ var crypto = __toESM(require("@fireproof/encrypted-blockstore/crypto-web"), 1);
87
+ var store = __toESM(require("@fireproof/encrypted-blockstore/store-web"), 1);
88
+
89
+ // src/crdt-helpers.ts
90
+ var import_block = require("multiformats/block");
91
+ var import_link = require("multiformats/link");
92
+ var import_sha2 = require("multiformats/hashes/sha2");
93
+ var codec = __toESM(require("@ipld/dag-cbor"), 1);
94
+ var import_crdt = require("@alanshaw/pail/crdt");
95
+ var import_clock = require("@alanshaw/pail/clock");
96
+ var import_encrypted_blockstore = require("@fireproof/encrypted-blockstore");
97
+
98
+ // src/files.ts
99
+ var UnixFS = __toESM(require("@ipld/unixfs"), 1);
100
+ var raw = __toESM(require("multiformats/codecs/raw"), 1);
101
+ var import_fixed = require("@ipld/unixfs/file/chunker/fixed");
102
+ var import_balanced = require("@ipld/unixfs/file/layout/balanced");
103
+ var import_ipfs_unixfs_exporter = require("ipfs-unixfs-exporter");
104
+ var queuingStrategy = UnixFS.withCapacity();
105
+ var settings = UnixFS.configure({
106
+ fileChunkEncoder: raw,
107
+ smallFileEncoder: raw,
108
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
109
+ chunker: (0, import_fixed.withMaxChunkSize)(1024 * 1024),
110
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
111
+ fileLayout: (0, import_balanced.withWidth)(1024)
112
+ });
113
+ async function encodeFile(blob) {
114
+ const readable = createFileEncoderStream(blob);
115
+ const blocks = await collect(readable);
116
+ return { cid: blocks.at(-1).cid, blocks };
117
+ }
118
+ async function decodeFile(blocks, cid, meta) {
119
+ const entry = await (0, import_ipfs_unixfs_exporter.exporter)(cid.toString(), blocks, { length: meta.size });
120
+ const chunks = [];
121
+ for await (const chunk of entry.content())
122
+ chunks.push(chunk);
123
+ return new File(chunks, entry.name, { type: meta.type, lastModified: 0 });
124
+ }
125
+ function createFileEncoderStream(blob) {
126
+ const { readable, writable } = new TransformStream({}, queuingStrategy);
127
+ const unixfsWriter = UnixFS.createWriter({ writable, settings });
128
+ const fileBuilder = new UnixFSFileBuilder("", blob);
129
+ void (async () => {
130
+ await fileBuilder.finalize(unixfsWriter);
131
+ await unixfsWriter.close();
132
+ })();
133
+ return readable;
134
+ }
135
+ async function collect(collectable) {
136
+ const chunks = [];
137
+ await collectable.pipeTo(
138
+ new WritableStream({
139
+ write(chunk) {
140
+ chunks.push(chunk);
141
+ }
142
+ })
143
+ );
144
+ return chunks;
145
+ }
146
+ var UnixFSFileBuilder = class {
147
+ #file;
148
+ name;
149
+ constructor(name, file) {
150
+ this.name = name;
151
+ this.#file = file;
152
+ }
153
+ async finalize(writer) {
154
+ const unixfsFileWriter = UnixFS.createFileWriter(writer);
155
+ await this.#file.stream().pipeTo(
156
+ new WritableStream({
157
+ async write(chunk) {
158
+ await unixfsFileWriter.write(chunk);
159
+ }
160
+ })
161
+ );
162
+ return await unixfsFileWriter.close();
163
+ }
164
+ };
165
+
166
+ // src/crdt-helpers.ts
167
+ async function applyBulkUpdateToCrdt(tblocks, head, updates, options) {
168
+ let result;
169
+ for (const update of updates) {
170
+ const link = await writeDocContent(tblocks, update);
171
+ result = await (0, import_crdt.put)(tblocks, head, update.key, link, options);
172
+ const resRoot = result.root.toString();
173
+ const isReturned = result.additions.some((a) => a.cid.toString() === resRoot);
174
+ if (!isReturned) {
175
+ const hasRoot = await tblocks.get(result.root);
176
+ if (!hasRoot) {
177
+ throw new Error(
178
+ `missing root in additions: ${result.additions.length} ${resRoot} keys: ${updates.map((u) => u.key).toString()}`
179
+ );
180
+ result.head = head;
181
+ }
182
+ }
183
+ if (result.event) {
184
+ for (const { cid, bytes } of [...result.additions, result.event]) {
185
+ tblocks.putSync(cid, bytes);
186
+ }
187
+ head = result.head;
188
+ }
189
+ }
190
+ return { head };
191
+ }
192
+ async function writeDocContent(blocks, update) {
193
+ let value;
194
+ if (update.del) {
195
+ value = { del: true };
196
+ } else {
197
+ await processFiles(blocks, update.value);
198
+ value = { doc: update.value };
199
+ }
200
+ const block = await (0, import_block.encode)({ value, hasher: import_sha2.sha256, codec });
201
+ blocks.putSync(block.cid, block.bytes);
202
+ return block.cid;
203
+ }
204
+ async function processFiles(blocks, doc) {
205
+ if (doc._files) {
206
+ await processFileset(blocks, doc._files);
207
+ }
208
+ if (doc._publicFiles) {
209
+ await processFileset(blocks, doc._publicFiles, true);
210
+ }
211
+ }
212
+ async function processFileset(blocks, files, publicFiles = false) {
213
+ const dbBlockstore = blocks.parent;
214
+ const t = new import_encrypted_blockstore.CarTransaction(dbBlockstore);
215
+ const didPut = [];
216
+ for (const filename in files) {
217
+ if (File === files[filename].constructor) {
218
+ const file = files[filename];
219
+ const { cid, blocks: fileBlocks } = await encodeFile(file);
220
+ didPut.push(filename);
221
+ for (const block of fileBlocks) {
222
+ t.putSync(block.cid, block.bytes);
223
+ }
224
+ files[filename] = { cid, type: file.type, size: file.size };
225
+ }
226
+ }
227
+ if (didPut.length) {
228
+ const car = await dbBlockstore.loader?.commitFiles(t, { files }, {
229
+ public: publicFiles
230
+ });
231
+ if (car) {
232
+ for (const name of didPut) {
233
+ files[name] = { car, ...files[name] };
234
+ }
235
+ }
236
+ }
237
+ }
238
+ async function getValueFromCrdt(blocks, head, key) {
239
+ if (!head.length)
240
+ throw new Error("Getting from an empty database");
241
+ const link = await (0, import_crdt.get)(blocks, head, key);
242
+ if (!link)
243
+ throw new Error(`Missing key ${key}`);
244
+ return await getValueFromLink(blocks, link);
245
+ }
246
+ function readFiles(blocks, { doc }) {
247
+ if (!doc)
248
+ return;
249
+ if (doc._files) {
250
+ readFileset(blocks, doc._files);
251
+ }
252
+ if (doc._publicFiles) {
253
+ readFileset(blocks, doc._publicFiles, true);
254
+ }
255
+ }
256
+ function readFileset(blocks, files, isPublic = false) {
257
+ for (const filename in files) {
258
+ const fileMeta = files[filename];
259
+ if (fileMeta.cid) {
260
+ if (isPublic) {
261
+ fileMeta.url = `https://${fileMeta.cid.toString()}.ipfs.w3s.link/`;
262
+ }
263
+ if (fileMeta.car) {
264
+ fileMeta.file = async () => await decodeFile(
265
+ {
266
+ get: async (cid) => {
267
+ return await blocks.getFile(fileMeta.car, cid, isPublic);
268
+ }
269
+ },
270
+ fileMeta.cid,
271
+ fileMeta
272
+ );
273
+ }
274
+ }
275
+ files[filename] = fileMeta;
276
+ }
277
+ }
278
+ async function getValueFromLink(blocks, link) {
279
+ const block = await blocks.get(link);
280
+ if (!block)
281
+ throw new Error(`Missing linked block ${link.toString()}`);
282
+ const { value } = await (0, import_block.decode)({ bytes: block.bytes, hasher: import_sha2.sha256, codec });
283
+ readFiles(blocks, value);
284
+ return value;
285
+ }
286
+ var DirtyEventFetcher = class extends import_clock.EventFetcher {
287
+ // @ts-ignore
288
+ async get(link) {
289
+ try {
290
+ return await super.get(link);
291
+ } catch (e) {
292
+ console.error("missing event", link.toString(), e);
293
+ return { value: null };
294
+ }
295
+ }
296
+ };
297
+ async function clockChangesSince(blocks, head, since, opts) {
298
+ const eventsFetcher = opts.dirty ? new DirtyEventFetcher(blocks) : new import_clock.EventFetcher(blocks);
299
+ const keys = /* @__PURE__ */ new Set();
300
+ const updates = await gatherUpdates(
301
+ blocks,
302
+ eventsFetcher,
303
+ head,
304
+ since,
305
+ [],
306
+ keys,
307
+ /* @__PURE__ */ new Set(),
308
+ opts.limit || Infinity
309
+ );
310
+ return { result: updates.reverse(), head };
311
+ }
312
+ async function gatherUpdates(blocks, eventsFetcher, head, since, updates = [], keys, didLinks, limit) {
313
+ if (limit <= 0)
314
+ return updates;
315
+ const sHead = head.map((l) => l.toString());
316
+ for (const link of since) {
317
+ if (sHead.includes(link.toString())) {
318
+ return updates;
319
+ }
320
+ }
321
+ for (const link of head) {
322
+ if (didLinks.has(link.toString()))
323
+ continue;
324
+ didLinks.add(link.toString());
325
+ const { value: event } = await eventsFetcher.get(link);
326
+ if (!event)
327
+ continue;
328
+ const { key, value } = event.data;
329
+ if (keys.has(key)) {
330
+ if (event.parents) {
331
+ updates = await gatherUpdates(
332
+ blocks,
333
+ eventsFetcher,
334
+ event.parents,
335
+ since,
336
+ updates,
337
+ keys,
338
+ didLinks,
339
+ limit
340
+ );
341
+ }
342
+ } else {
343
+ keys.add(key);
344
+ const docValue = await getValueFromLink(blocks, value);
345
+ updates.push({ key, value: docValue.doc, del: docValue.del, clock: link });
346
+ limit--;
347
+ if (event.parents) {
348
+ updates = await gatherUpdates(
349
+ blocks,
350
+ eventsFetcher,
351
+ event.parents,
352
+ since,
353
+ updates,
354
+ keys,
355
+ didLinks,
356
+ limit
357
+ );
358
+ }
359
+ }
360
+ }
361
+ return updates;
362
+ }
363
+ async function* getAllEntries(blocks, head) {
364
+ for await (const [key, link] of (0, import_crdt.entries)(blocks, head)) {
365
+ const docValue = await getValueFromLink(blocks, link);
366
+ yield { key, value: docValue.doc, del: docValue.del };
367
+ }
368
+ }
369
+ async function* clockVis(blocks, head) {
370
+ for await (const line of (0, import_clock.vis)(blocks, head)) {
371
+ yield line;
372
+ }
373
+ }
374
+ var isCompacting = false;
375
+ async function doCompact(blockLog, head) {
376
+ if (isCompacting) {
377
+ console.log("already compacting");
378
+ return;
379
+ }
380
+ isCompacting = true;
381
+ for (const cid of head) {
382
+ const bl = await blockLog.get(cid);
383
+ if (!bl)
384
+ throw new Error("Missing head block: " + cid.toString());
385
+ }
386
+ for await (const entry of getAllEntries(blockLog, head)) {
387
+ }
388
+ for await (const [, link] of (0, import_crdt.entries)(blockLog, head)) {
389
+ const bl = await blockLog.get(link);
390
+ if (!bl)
391
+ throw new Error("Missing entry block: " + link.toString());
392
+ }
393
+ for await (const _line of (0, import_clock.vis)(blockLog, head)) {
394
+ }
395
+ const result = await (0, import_crdt.root)(blockLog, head);
396
+ for (const { cid, bytes } of [...result.additions, ...result.removals]) {
397
+ blockLog.loggedBlocks.putSync(cid, bytes);
398
+ }
399
+ await clockChangesSince(blockLog, head, [], {});
400
+ isCompacting = false;
401
+ }
402
+ async function getBlock(blocks, cidString) {
403
+ const block = await blocks.get((0, import_link.parse)(cidString));
404
+ if (!block)
405
+ throw new Error(`Missing block ${cidString}`);
406
+ const { cid, value } = await (0, import_block.decode)({ bytes: block.bytes, codec, hasher: import_sha2.sha256 });
407
+ return new import_block.Block({ cid, value, bytes: block.bytes });
408
+ }
409
+
410
+ // src/indexer-helpers.ts
411
+ var import_block2 = require("multiformats/block");
412
+ var import_sha22 = require("multiformats/hashes/sha2");
413
+ var codec2 = __toESM(require("@ipld/dag-cbor"), 1);
414
+ var import_charwise = __toESM(require("charwise"), 1);
415
+ var DbIndex = __toESM(require("prolly-trees/db-index"), 1);
416
+ var import_utils = require("prolly-trees/utils");
417
+ var import_cache = require("prolly-trees/cache");
418
+ var IndexTree = class {
419
+ cid = null;
420
+ root = null;
421
+ };
422
+ var refCompare = (aRef, bRef) => {
423
+ if (Number.isNaN(aRef))
424
+ return -1;
425
+ if (Number.isNaN(bRef))
426
+ throw new Error("ref may not be Infinity or NaN");
427
+ if (aRef === Infinity)
428
+ return 1;
429
+ return (0, import_utils.simpleCompare)(aRef, bRef);
430
+ };
431
+ var compare = (a, b) => {
432
+ const [aKey, aRef] = a;
433
+ const [bKey, bRef] = b;
434
+ const comp = (0, import_utils.simpleCompare)(aKey, bKey);
435
+ if (comp !== 0)
436
+ return comp;
437
+ return refCompare(aRef, bRef);
438
+ };
439
+ var byKeyOpts = { cache: import_cache.nocache, chunker: (0, import_utils.bf)(30), codec: codec2, hasher: import_sha22.sha256, compare };
440
+ var byIdOpts = { cache: import_cache.nocache, chunker: (0, import_utils.bf)(30), codec: codec2, hasher: import_sha22.sha256, compare: import_utils.simpleCompare };
441
+ function indexEntriesForChanges(changes, mapFn) {
442
+ const indexEntries = [];
443
+ changes.forEach(({ key: _id, value, del }) => {
444
+ if (del || !value)
445
+ return;
446
+ let mapCalled = false;
447
+ const mapReturn = mapFn({ _id, ...value }, (k, v) => {
448
+ mapCalled = true;
449
+ if (typeof k === "undefined")
450
+ return;
451
+ indexEntries.push({
452
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
453
+ key: [import_charwise.default.encode(k), _id],
454
+ value: v || null
455
+ });
456
+ });
457
+ if (!mapCalled && mapReturn) {
458
+ indexEntries.push({
459
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
460
+ key: [import_charwise.default.encode(mapReturn), _id],
461
+ value: null
462
+ });
463
+ }
464
+ });
465
+ return indexEntries;
466
+ }
467
+ function makeProllyGetBlock(blocks) {
468
+ return async (address) => {
469
+ const block = await blocks.get(address);
470
+ if (!block)
471
+ throw new Error(`Missing block ${address.toString()}`);
472
+ const { cid, bytes } = block;
473
+ return (0, import_block2.create)({ cid, bytes, hasher: import_sha22.sha256, codec: codec2 });
474
+ };
475
+ }
476
+ async function bulkIndex(tblocks, inIndex, indexEntries, opts) {
477
+ if (!indexEntries.length)
478
+ return inIndex;
479
+ if (!inIndex.root) {
480
+ if (!inIndex.cid) {
481
+ let returnRootBlock = null;
482
+ let returnNode = null;
483
+ for await (const node of await DbIndex.create({ get: makeProllyGetBlock(tblocks), list: indexEntries, ...opts })) {
484
+ const block = await node.block;
485
+ await tblocks.put(block.cid, block.bytes);
486
+ returnRootBlock = block;
487
+ returnNode = node;
488
+ }
489
+ if (!returnNode || !returnRootBlock)
490
+ throw new Error("failed to create index");
491
+ return { root: returnNode, cid: returnRootBlock.cid };
492
+ } else {
493
+ inIndex.root = await DbIndex.load({ cid: inIndex.cid, get: makeProllyGetBlock(tblocks), ...opts });
494
+ }
495
+ }
496
+ const { root: root3, blocks: newBlocks } = await inIndex.root.bulk(indexEntries);
497
+ if (root3) {
498
+ for await (const block of newBlocks) {
499
+ await tblocks.put(block.cid, block.bytes);
500
+ }
501
+ return { root: root3, cid: (await root3.block).cid };
502
+ } else {
503
+ return { root: null, cid: null };
504
+ }
505
+ }
506
+ async function loadIndex(tblocks, cid, opts) {
507
+ return await DbIndex.load({ cid, get: makeProllyGetBlock(tblocks), ...opts });
508
+ }
509
+ async function applyQuery(crdt, resp, query) {
510
+ if (query.descending) {
511
+ resp.result = resp.result.reverse();
512
+ }
513
+ if (query.limit) {
514
+ resp.result = resp.result.slice(0, query.limit);
515
+ }
516
+ if (query.includeDocs) {
517
+ resp.result = await Promise.all(
518
+ resp.result.map(async (row) => {
519
+ const val = await crdt.get(row.id);
520
+ const doc = val ? { _id: row.id, ...val.doc } : null;
521
+ return { ...row, doc };
522
+ })
523
+ );
524
+ }
525
+ return {
526
+ rows: resp.result.map((row) => {
527
+ row.key = import_charwise.default.decode(row.key);
528
+ if (row.row && !row.value) {
529
+ row.value = row.row;
530
+ delete row.row;
531
+ }
532
+ return row;
533
+ })
534
+ };
535
+ }
536
+ function encodeRange(range) {
537
+ return range.map((key) => import_charwise.default.encode(key));
538
+ }
539
+ function encodeKey(key) {
540
+ return import_charwise.default.encode(key);
541
+ }
542
+
543
+ // src/index.ts
544
+ function index({ _crdt }, name, mapFn, meta) {
545
+ if (mapFn && meta)
546
+ throw new Error("cannot provide both mapFn and meta");
547
+ if (mapFn && mapFn.constructor.name !== "Function")
548
+ throw new Error("mapFn must be a function");
549
+ if (_crdt.indexers.has(name)) {
550
+ const idx = _crdt.indexers.get(name);
551
+ idx.applyMapFn(name, mapFn, meta);
552
+ } else {
553
+ const idx = new Index(_crdt, name, mapFn, meta);
554
+ _crdt.indexers.set(name, idx);
555
+ }
556
+ return _crdt.indexers.get(name);
557
+ }
558
+ var Index = class {
559
+ blockstore;
560
+ crdt;
561
+ name = null;
562
+ mapFn = null;
563
+ mapFnString = "";
564
+ byKey = new IndexTree();
565
+ byId = new IndexTree();
566
+ indexHead = void 0;
567
+ includeDocsDefault = false;
568
+ initError = null;
569
+ ready;
570
+ constructor(crdt, name, mapFn, meta) {
571
+ this.blockstore = crdt.indexBlockstore;
572
+ this.crdt = crdt;
573
+ this.applyMapFn(name, mapFn, meta);
574
+ if (!(this.mapFnString || this.initError))
575
+ throw new Error("missing mapFnString");
576
+ this.ready = this.blockstore.ready.then(() => {
577
+ });
578
+ }
579
+ applyMapFn(name, mapFn, meta) {
580
+ if (mapFn && meta)
581
+ throw new Error("cannot provide both mapFn and meta");
582
+ if (this.name && this.name !== name)
583
+ throw new Error("cannot change name");
584
+ this.name = name;
585
+ try {
586
+ if (meta) {
587
+ if (this.indexHead && this.indexHead.map((c) => c.toString()).join() !== meta.head.map((c) => c.toString()).join()) {
588
+ throw new Error("cannot apply meta to existing index");
589
+ }
590
+ if (this.mapFnString) {
591
+ if (this.mapFnString !== meta.map) {
592
+ console.log(
593
+ "cannot apply different mapFn meta: old mapFnString",
594
+ this.mapFnString,
595
+ "new mapFnString",
596
+ meta.map
597
+ );
598
+ } else {
599
+ this.byId.cid = meta.byId;
600
+ this.byKey.cid = meta.byKey;
601
+ this.indexHead = meta.head;
602
+ }
603
+ } else {
604
+ this.mapFnString = meta.map;
605
+ this.byId.cid = meta.byId;
606
+ this.byKey.cid = meta.byKey;
607
+ this.indexHead = meta.head;
608
+ }
609
+ } else {
610
+ if (this.mapFn) {
611
+ if (mapFn) {
612
+ if (this.mapFn.toString() !== mapFn.toString())
613
+ throw new Error("cannot apply different mapFn app2");
614
+ }
615
+ } else {
616
+ if (!mapFn) {
617
+ mapFn = makeMapFnFromName(name);
618
+ }
619
+ if (this.mapFnString) {
620
+ if (this.mapFnString !== mapFn.toString())
621
+ throw new Error("cannot apply different mapFn app");
622
+ } else {
623
+ this.mapFnString = mapFn.toString();
624
+ }
625
+ this.mapFn = mapFn;
626
+ }
627
+ }
628
+ const matches = /=>\s*(.*)/.test(this.mapFnString);
629
+ this.includeDocsDefault = matches;
630
+ } catch (e) {
631
+ this.initError = e;
632
+ }
633
+ }
634
+ async query(opts = {}) {
635
+ await this._updateIndex();
636
+ await this._hydrateIndex();
637
+ if (!this.byKey.root)
638
+ return await applyQuery(this.crdt, { result: [] }, opts);
639
+ if (this.includeDocsDefault && opts.includeDocs === void 0)
640
+ opts.includeDocs = true;
641
+ if (opts.range) {
642
+ const { result: result2, ...all2 } = await this.byKey.root.range(...encodeRange(opts.range));
643
+ return await applyQuery(this.crdt, { result: result2, ...all2 }, opts);
644
+ }
645
+ if (opts.key) {
646
+ const encodedKey = encodeKey(opts.key);
647
+ return await applyQuery(this.crdt, await this.byKey.root.get(encodedKey), opts);
648
+ }
649
+ if (Array.isArray(opts.keys)) {
650
+ const results = await Promise.all(
651
+ opts.keys.map(async (key) => {
652
+ const encodedKey = encodeKey(key);
653
+ return (await applyQuery(this.crdt, await this.byKey.root.get(encodedKey), opts)).rows;
654
+ })
655
+ );
656
+ return { rows: results.flat() };
657
+ }
658
+ if (opts.prefix) {
659
+ if (!Array.isArray(opts.prefix))
660
+ opts.prefix = [opts.prefix];
661
+ const start = [...opts.prefix, NaN];
662
+ const end = [...opts.prefix, Infinity];
663
+ const encodedR = encodeRange([start, end]);
664
+ return await applyQuery(this.crdt, await this.byKey.root.range(...encodedR), opts);
665
+ }
666
+ const { result, ...all } = await this.byKey.root.getAllEntries();
667
+ return await applyQuery(
668
+ this.crdt,
669
+ {
670
+ result: result.map(({ key: [k, id], value }) => ({ key: k, id, value })),
671
+ ...all
672
+ },
673
+ opts
674
+ );
675
+ }
676
+ _resetIndex() {
677
+ this.byId = new IndexTree();
678
+ this.byKey = new IndexTree();
679
+ this.indexHead = void 0;
680
+ }
681
+ async _hydrateIndex() {
682
+ if (this.byId.root && this.byKey.root)
683
+ return;
684
+ if (!this.byId.cid || !this.byKey.cid)
685
+ return;
686
+ this.byId.root = await loadIndex(this.blockstore, this.byId.cid, byIdOpts);
687
+ this.byKey.root = await loadIndex(this.blockstore, this.byKey.cid, byKeyOpts);
688
+ }
689
+ async _updateIndex() {
690
+ await this.ready;
691
+ if (this.initError)
692
+ throw this.initError;
693
+ if (!this.mapFn)
694
+ throw new Error("No map function defined");
695
+ let result, head;
696
+ if (!this.indexHead || this.indexHead.length === 0) {
697
+ ;
698
+ ({ result, head } = await this.crdt.allDocs());
699
+ } else {
700
+ ;
701
+ ({ result, head } = await this.crdt.changes(this.indexHead));
702
+ }
703
+ if (result.length === 0) {
704
+ this.indexHead = head;
705
+ return { byId: this.byId, byKey: this.byKey };
706
+ }
707
+ let staleKeyIndexEntries = [];
708
+ let removeIdIndexEntries = [];
709
+ if (this.byId.root) {
710
+ const removeIds = result.map(({ key }) => key);
711
+ const { result: oldChangeEntries } = await this.byId.root.getMany(removeIds);
712
+ staleKeyIndexEntries = oldChangeEntries.map((key) => ({ key, del: true }));
713
+ removeIdIndexEntries = oldChangeEntries.map((key) => ({ key: key[1], del: true }));
714
+ }
715
+ const indexEntries = indexEntriesForChanges(result, this.mapFn);
716
+ const byIdIndexEntries = indexEntries.map(({ key }) => ({
717
+ key: key[1],
718
+ value: key
719
+ }));
720
+ const indexerMeta = { indexes: /* @__PURE__ */ new Map() };
721
+ for (const [name, indexer] of this.crdt.indexers) {
722
+ if (indexer.indexHead) {
723
+ indexerMeta.indexes.set(name, {
724
+ byId: indexer.byId.cid,
725
+ byKey: indexer.byKey.cid,
726
+ head: indexer.indexHead,
727
+ map: indexer.mapFnString,
728
+ name: indexer.name
729
+ });
730
+ }
731
+ }
732
+ return await this.blockstore.transaction(async (tblocks) => {
733
+ this.byId = await bulkIndex(
734
+ tblocks,
735
+ this.byId,
736
+ removeIdIndexEntries.concat(byIdIndexEntries),
737
+ byIdOpts
738
+ );
739
+ this.byKey = await bulkIndex(
740
+ tblocks,
741
+ this.byKey,
742
+ staleKeyIndexEntries.concat(indexEntries),
743
+ byKeyOpts
744
+ );
745
+ this.indexHead = head;
746
+ const idxMeta = {
747
+ byId: this.byId.cid,
748
+ byKey: this.byKey.cid,
749
+ head,
750
+ map: this.mapFnString,
751
+ name: this.name
752
+ };
753
+ indexerMeta.indexes.set(this.name, idxMeta);
754
+ return indexerMeta;
755
+ });
756
+ }
757
+ };
758
+ function makeMapFnFromName(name) {
759
+ return (doc) => doc[name] ?? void 0;
760
+ }
761
+
762
+ // src/crdt-clock.ts
763
+ var import_clock2 = require("@alanshaw/pail/clock");
764
+ var import_crdt2 = require("@alanshaw/pail/crdt");
765
+
766
+ // src/apply-head-queue.ts
767
+ function applyHeadQueue(worker) {
768
+ const queue = [];
769
+ let isProcessing = false;
770
+ async function* process() {
771
+ if (isProcessing || queue.length === 0)
772
+ return;
773
+ isProcessing = true;
774
+ const allUpdates = [];
775
+ try {
776
+ while (queue.length > 0) {
777
+ queue.sort((a, b) => b.updates ? 1 : -1);
778
+ const task = queue.shift();
779
+ if (!task)
780
+ continue;
781
+ await worker(task.newHead, task.prevHead);
782
+ if (task.updates) {
783
+ allUpdates.push(...task.updates);
784
+ }
785
+ if (!queue.some((t) => t.updates) || task.updates) {
786
+ const allTasksHaveUpdates = queue.every((task2) => task2.updates !== null);
787
+ yield { updates: allUpdates, all: allTasksHaveUpdates };
788
+ allUpdates.length = 0;
789
+ }
790
+ }
791
+ } finally {
792
+ isProcessing = false;
793
+ const generator = process();
794
+ let result = await generator.next();
795
+ while (!result.done) {
796
+ result = await generator.next();
797
+ }
798
+ }
799
+ }
800
+ return {
801
+ push(task) {
802
+ queue.push(task);
803
+ return process();
804
+ }
805
+ };
806
+ }
807
+
808
+ // src/crdt-clock.ts
809
+ var CRDTClock = class {
810
+ // todo: track local and remote clocks independently, merge on read
811
+ // that way we can drop the whole remote if we need to
812
+ // should go with making sure the local clock only references locally available blockstore on write
813
+ head = [];
814
+ zoomers = /* @__PURE__ */ new Set();
815
+ watchers = /* @__PURE__ */ new Set();
816
+ emptyWatchers = /* @__PURE__ */ new Set();
817
+ blockstore = null;
818
+ applyHeadQueue;
819
+ constructor() {
820
+ this.applyHeadQueue = applyHeadQueue(this.int_applyHead.bind(this));
821
+ }
822
+ setHead(head) {
823
+ this.head = head;
824
+ }
825
+ async applyHead(newHead, prevHead, updates = null) {
826
+ for await (const { updates: updatesAcc, all } of this.applyHeadQueue.push({
827
+ newHead,
828
+ prevHead,
829
+ updates
830
+ })) {
831
+ this.processUpdates(updatesAcc, all, prevHead);
832
+ }
833
+ }
834
+ async processUpdates(updatesAcc, all, prevHead) {
835
+ let internalUpdates = updatesAcc;
836
+ if (this.watchers.size && !all) {
837
+ const changes = await clockChangesSince(this.blockstore, this.head, prevHead, {});
838
+ internalUpdates = changes.result;
839
+ }
840
+ this.zoomers.forEach((fn) => fn());
841
+ this.notifyWatchers(internalUpdates || []);
842
+ }
843
+ notifyWatchers(updates) {
844
+ this.emptyWatchers.forEach((fn) => fn());
845
+ this.watchers.forEach((fn) => fn(updates || []));
846
+ }
847
+ onTick(fn) {
848
+ this.watchers.add(fn);
849
+ }
850
+ onTock(fn) {
851
+ this.emptyWatchers.add(fn);
852
+ }
853
+ onZoom(fn) {
854
+ this.zoomers.add(fn);
855
+ }
856
+ async int_applyHead(newHead, prevHead) {
857
+ const ogHead = sortClockHead(this.head);
858
+ newHead = sortClockHead(newHead);
859
+ if (compareClockHeads(ogHead, newHead)) {
860
+ return;
861
+ }
862
+ const ogPrev = sortClockHead(prevHead);
863
+ if (compareClockHeads(ogHead, ogPrev)) {
864
+ this.setHead(newHead);
865
+ return;
866
+ }
867
+ let head = this.head;
868
+ const noLoader = false;
869
+ if (!this.blockstore)
870
+ throw new Error("missing blockstore");
871
+ await validateBlocks(newHead, this.blockstore);
872
+ await this.blockstore.transaction(
873
+ async (tblocks) => {
874
+ head = await advanceBlocks(newHead, tblocks, head);
875
+ const result = await (0, import_crdt2.root)(tblocks, head);
876
+ for (const { cid, bytes } of [...result.additions, ...result.removals]) {
877
+ tblocks.putSync(cid, bytes);
878
+ }
879
+ return { head };
880
+ },
881
+ { noLoader }
882
+ );
883
+ this.setHead(head);
884
+ }
885
+ };
886
+ function sortClockHead(clockHead) {
887
+ return clockHead.sort((a, b) => a.toString().localeCompare(b.toString()));
888
+ }
889
+ async function validateBlocks(newHead, blockstore) {
890
+ newHead.map(async (cid) => {
891
+ const got = await blockstore.get(cid);
892
+ if (!got) {
893
+ throw new Error("int_applyHead missing block: " + cid.toString());
894
+ }
895
+ });
896
+ }
897
+ function compareClockHeads(head1, head2) {
898
+ return head1.toString() === head2.toString();
899
+ }
900
+ async function advanceBlocks(newHead, tblocks, head) {
901
+ for (const cid of newHead) {
902
+ try {
903
+ head = await (0, import_clock2.advance)(tblocks, head, cid);
904
+ } catch (e) {
905
+ console.error("failed to advance", cid.toString(), e);
906
+ continue;
907
+ }
908
+ }
909
+ return head;
910
+ }
911
+
912
+ // src/crdt.ts
913
+ var CRDT = class {
914
+ name;
915
+ opts = {};
916
+ ready;
917
+ blockstore;
918
+ indexBlockstore;
919
+ indexers = /* @__PURE__ */ new Map();
920
+ clock = new CRDTClock();
921
+ constructor(name, opts) {
922
+ this.name = name || null;
923
+ this.opts = opts || this.opts;
924
+ this.blockstore = new import_encrypted_blockstore2.EncryptedBlockstore({
925
+ name,
926
+ applyMeta: async (meta) => {
927
+ const crdtMeta = meta;
928
+ await this.clock.applyHead(crdtMeta.head, []);
929
+ },
930
+ compact: async (blocks) => {
931
+ await doCompact(blocks, this.clock.head);
932
+ return { head: this.clock.head };
933
+ },
934
+ autoCompact: this.opts.autoCompact || 100,
935
+ crypto,
936
+ store,
937
+ public: this.opts.public,
938
+ meta: this.opts.meta
939
+ });
940
+ this.clock.blockstore = this.blockstore;
941
+ this.indexBlockstore = new import_encrypted_blockstore2.EncryptedBlockstore({
942
+ name: this.opts.persistIndexes && this.name ? this.name + ".idx" : void 0,
943
+ applyMeta: async (meta) => {
944
+ const idxCarMeta = meta;
945
+ for (const [name2, idx] of Object.entries(idxCarMeta.indexes)) {
946
+ index({ _crdt: this }, name2, void 0, idx);
947
+ }
948
+ },
949
+ crypto,
950
+ public: this.opts.public,
951
+ store
952
+ });
953
+ this.ready = Promise.all([this.blockstore.ready, this.indexBlockstore.ready]).then(() => {
954
+ });
955
+ this.clock.onZoom(() => {
956
+ for (const idx of this.indexers.values()) {
957
+ idx._resetIndex();
958
+ }
959
+ });
960
+ }
961
+ async bulk(updates, options) {
962
+ await this.ready;
963
+ const prevHead = [...this.clock.head];
964
+ const meta = await this.blockstore.transaction(
965
+ async (blocks) => {
966
+ const { head } = await applyBulkUpdateToCrdt(blocks, this.clock.head, updates, options);
967
+ updates = updates.map(({ key, value, del, clock }) => {
968
+ readFiles(this.blockstore, { doc: value });
969
+ return { key, value, del, clock };
970
+ });
971
+ return { head };
972
+ }
973
+ );
974
+ await this.clock.applyHead(meta.head, prevHead, updates);
975
+ return meta;
976
+ }
977
+ // if (snap) await this.clock.applyHead(crdtMeta.head, this.clock.head)
978
+ async allDocs() {
979
+ await this.ready;
980
+ const result = [];
981
+ for await (const entry of getAllEntries(this.blockstore, this.clock.head)) {
982
+ result.push(entry);
983
+ }
984
+ return { result, head: this.clock.head };
985
+ }
986
+ async vis() {
987
+ await this.ready;
988
+ const txt = [];
989
+ for await (const line of clockVis(this.blockstore, this.clock.head)) {
990
+ txt.push(line);
991
+ }
992
+ return txt.join("\n");
993
+ }
994
+ async getBlock(cidString) {
995
+ await this.ready;
996
+ return await getBlock(this.blockstore, cidString);
997
+ }
998
+ async get(key) {
999
+ await this.ready;
1000
+ const result = await getValueFromCrdt(this.blockstore, this.clock.head, key);
1001
+ if (result.del)
1002
+ return null;
1003
+ return result;
1004
+ }
1005
+ async changes(since = [], opts = {}) {
1006
+ await this.ready;
1007
+ return await clockChangesSince(this.blockstore, this.clock.head, since, opts);
1008
+ }
1009
+ async compact() {
1010
+ return await this.blockstore.compact();
1011
+ }
1012
+ };
1013
+
1014
+ // src/database.ts
1015
+ var Database = class {
1016
+ static databases = /* @__PURE__ */ new Map();
1017
+ name;
1018
+ opts = {};
1019
+ _listening = false;
1020
+ _listeners = /* @__PURE__ */ new Set();
1021
+ _noupdate_listeners = /* @__PURE__ */ new Set();
1022
+ _crdt;
1023
+ _writeQueue;
1024
+ blockstore;
1025
+ constructor(name, opts) {
1026
+ this.name = name || null;
1027
+ this.opts = opts || this.opts;
1028
+ this._crdt = new CRDT(name, this.opts);
1029
+ this.blockstore = this._crdt.blockstore;
1030
+ this._writeQueue = writeQueue(async (updates) => {
1031
+ return await this._crdt.bulk(updates);
1032
+ });
1033
+ this._crdt.clock.onTock(() => {
1034
+ this._no_update_notify();
1035
+ });
1036
+ }
1037
+ async get(id) {
1038
+ const got = await this._crdt.get(id).catch((e) => {
1039
+ e.message = `Not found: ${id} - ` + e.message;
1040
+ throw e;
1041
+ });
1042
+ if (!got)
1043
+ throw new Error(`Not found: ${id}`);
1044
+ const { doc } = got;
1045
+ return { _id: id, ...doc };
1046
+ }
1047
+ async put(doc) {
1048
+ const { _id, ...value } = doc;
1049
+ const docId = _id || (0, import_uuidv7.uuidv7)();
1050
+ const result = await this._writeQueue.push({ key: docId, value });
1051
+ return { id: docId, clock: result?.head };
1052
+ }
1053
+ async del(id) {
1054
+ const result = await this._writeQueue.push({ key: id, del: true });
1055
+ return { id, clock: result?.head };
1056
+ }
1057
+ async changes(since = [], opts = {}) {
1058
+ const { result, head } = await this._crdt.changes(since, opts);
1059
+ const rows = result.map(({ key, value, del, clock }) => ({
1060
+ key,
1061
+ value: del ? { _id: key, _deleted: true } : { _id: key, ...value },
1062
+ clock
1063
+ }));
1064
+ return { rows, clock: head };
1065
+ }
1066
+ async allDocs() {
1067
+ const { result, head } = await this._crdt.allDocs();
1068
+ const rows = result.map(({ key, value, del }) => ({
1069
+ key,
1070
+ value: del ? { _id: key, _deleted: true } : { _id: key, ...value }
1071
+ }));
1072
+ return { rows, clock: head };
1073
+ }
1074
+ async allDocuments() {
1075
+ return this.allDocs();
1076
+ }
1077
+ subscribe(listener, updates) {
1078
+ if (updates) {
1079
+ if (!this._listening) {
1080
+ this._listening = true;
1081
+ this._crdt.clock.onTick((updates2) => {
1082
+ void this._notify(updates2);
1083
+ });
1084
+ }
1085
+ this._listeners.add(listener);
1086
+ return () => {
1087
+ this._listeners.delete(listener);
1088
+ };
1089
+ } else {
1090
+ this._noupdate_listeners.add(listener);
1091
+ return () => {
1092
+ this._noupdate_listeners.delete(listener);
1093
+ };
1094
+ }
1095
+ }
1096
+ // todo if we add this onto dbs in fireproof.ts then we can make index.ts a separate package
1097
+ async query(field, opts = {}) {
1098
+ const idx = typeof field === "string" ? index({ _crdt: this._crdt }, field) : index({ _crdt: this._crdt }, makeName(field.toString()), field);
1099
+ return await idx.query(opts);
1100
+ }
1101
+ async compact() {
1102
+ await this._crdt.compact();
1103
+ }
1104
+ async _notify(updates) {
1105
+ if (this._listeners.size) {
1106
+ const docs = updates.map(({ key, value }) => ({ _id: key, ...value }));
1107
+ for (const listener of this._listeners) {
1108
+ await (async () => await listener(docs))().catch((e) => {
1109
+ console.error("subscriber error", e);
1110
+ });
1111
+ }
1112
+ }
1113
+ }
1114
+ async _no_update_notify() {
1115
+ if (this._noupdate_listeners.size) {
1116
+ for (const listener of this._noupdate_listeners) {
1117
+ await (async () => await listener([]))().catch((e) => {
1118
+ console.error("subscriber error", e);
1119
+ });
1120
+ }
1121
+ }
1122
+ }
1123
+ };
1124
+ function fireproof(name, opts) {
1125
+ if (!Database.databases.has(name)) {
1126
+ Database.databases.set(name, new Database(name, opts));
1127
+ }
1128
+ return Database.databases.get(name);
1129
+ }
1130
+ function makeName(fnString) {
1131
+ const regex = /\(([^,()]+,\s*[^,()]+|\[[^\]]+\],\s*[^,()]+)\)/g;
1132
+ let found = null;
1133
+ const matches = Array.from(fnString.matchAll(regex), (match) => match[1].trim());
1134
+ if (matches.length === 0) {
1135
+ found = /=>\s*(.*)/.exec(fnString);
1136
+ }
1137
+ if (!found) {
1138
+ return fnString;
1139
+ } else {
1140
+ return found[1];
1141
+ }
1142
+ }
1143
+ //# sourceMappingURL=fireproof.cjs.map