tablinum 0.6.4 → 0.7.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.
package/dist/index.js CHANGED
@@ -224,6 +224,257 @@ function resolveRuntimeConfig(source) {
224
224
  );
225
225
  }
226
226
 
227
+ // src/storage/idb.ts
228
+ import { Effect as Effect2 } from "effect";
229
+ import { openDB, deleteDB } from "idb";
230
+
231
+ // src/sync/compact-event.ts
232
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
233
+ var VERSION = 1;
234
+ var HEADER_SIZE = 133;
235
+ function base64ToBytes(base64) {
236
+ const binary = atob(base64);
237
+ const bytes = new Uint8Array(binary.length);
238
+ for (let i = 0; i < binary.length; i++) {
239
+ bytes[i] = binary.charCodeAt(i);
240
+ }
241
+ return bytes;
242
+ }
243
+ function bytesToBase64(bytes) {
244
+ let binary = "";
245
+ for (let i = 0; i < bytes.length; i++) {
246
+ binary += String.fromCharCode(bytes[i]);
247
+ }
248
+ return btoa(binary);
249
+ }
250
+ function packEvent(event) {
251
+ const pubkey = hexToBytes2(event.pubkey);
252
+ const sig = hexToBytes2(event.sig);
253
+ const recipientTag = event.tags.find((t) => t[0] === "p");
254
+ if (!recipientTag) throw new Error("Gift wrap missing #p tag");
255
+ if (event.tags.some((t) => t[0] !== "p")) {
256
+ throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
257
+ }
258
+ const recipient = hexToBytes2(recipientTag[1]);
259
+ const createdAtBuf = new Uint8Array(4);
260
+ new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
261
+ const content = base64ToBytes(event.content);
262
+ const result = new Uint8Array(HEADER_SIZE + content.length);
263
+ result[0] = VERSION;
264
+ result.set(pubkey, 1);
265
+ result.set(sig, 33);
266
+ result.set(recipient, 97);
267
+ result.set(createdAtBuf, 129);
268
+ result.set(content, HEADER_SIZE);
269
+ return result;
270
+ }
271
+ function unpackEvent(id, compact) {
272
+ const version = compact[0];
273
+ if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
274
+ const pubkey = bytesToHex2(compact.slice(1, 33));
275
+ const sig = bytesToHex2(compact.slice(33, 97));
276
+ const recipient = bytesToHex2(compact.slice(97, 129));
277
+ const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
278
+ const createdAt = dv.getUint32(0, false);
279
+ const content = bytesToBase64(compact.slice(HEADER_SIZE));
280
+ return {
281
+ id,
282
+ pubkey,
283
+ sig,
284
+ created_at: createdAt,
285
+ kind: 1059,
286
+ tags: [["p", recipient]],
287
+ content
288
+ };
289
+ }
290
+
291
+ // src/storage/idb.ts
292
+ var DB_NAME = "tablinum";
293
+ function storeName(collection2) {
294
+ return `col_${collection2}`;
295
+ }
296
+ function computeSchemaSig(schema) {
297
+ return Object.entries(schema).sort(([a], [b]) => a.localeCompare(b)).map(([name, def]) => {
298
+ const indices = [...def.indices ?? []].sort().join(",");
299
+ return `${name}:${indices}`;
300
+ }).join("|");
301
+ }
302
+ function wrap(label, fn) {
303
+ return Effect2.tryPromise({
304
+ try: fn,
305
+ catch: (e) => new StorageError({
306
+ message: `IndexedDB ${label} failed: ${e instanceof Error ? e.message : String(e)}`,
307
+ cause: e
308
+ })
309
+ });
310
+ }
311
+ function upgradeSchema(database, schema, tx) {
312
+ if (!database.objectStoreNames.contains("_meta")) {
313
+ database.createObjectStore("_meta");
314
+ }
315
+ if (!database.objectStoreNames.contains("events")) {
316
+ const events = database.createObjectStore("events", { keyPath: "id" });
317
+ events.createIndex("by-record", ["collection", "recordId"]);
318
+ }
319
+ if (!database.objectStoreNames.contains("giftwraps")) {
320
+ database.createObjectStore("giftwraps", { keyPath: "id" });
321
+ }
322
+ const expectedStores = /* @__PURE__ */ new Set();
323
+ for (const [, def] of Object.entries(schema)) {
324
+ const sn = storeName(def.name);
325
+ expectedStores.add(sn);
326
+ if (!database.objectStoreNames.contains(sn)) {
327
+ const store = database.createObjectStore(sn, { keyPath: "id" });
328
+ for (const idx of def.indices ?? []) {
329
+ store.createIndex(idx, idx);
330
+ }
331
+ } else {
332
+ const store = tx.objectStore(sn);
333
+ const existingIndices = new Set(Array.from(store.indexNames));
334
+ const wantedIndices = new Set(def.indices ?? []);
335
+ for (const idx of existingIndices) {
336
+ if (!wantedIndices.has(idx)) store.deleteIndex(idx);
337
+ }
338
+ for (const idx of wantedIndices) {
339
+ if (!existingIndices.has(idx)) store.createIndex(idx, idx);
340
+ }
341
+ }
342
+ }
343
+ for (const existing of Array.from(database.objectStoreNames)) {
344
+ if (existing.startsWith("col_") && !expectedStores.has(existing)) {
345
+ database.deleteObjectStore(existing);
346
+ }
347
+ }
348
+ tx.objectStore("_meta").put(computeSchemaSig(schema), "schema_sig");
349
+ }
350
+ function deleteIDBStorage(dbName) {
351
+ if (typeof indexedDB === "undefined") {
352
+ return Effect2.fail(
353
+ new StorageError({
354
+ message: "IndexedDB is not available in this environment"
355
+ })
356
+ );
357
+ }
358
+ return wrap("deleteDatabase", () => deleteDB(dbName));
359
+ }
360
+ function openIDBStorage(dbName, schema) {
361
+ return Effect2.gen(function* () {
362
+ const name = dbName ?? DB_NAME;
363
+ const schemaSig = computeSchemaSig(schema);
364
+ if (typeof indexedDB === "undefined") {
365
+ return yield* Effect2.fail(
366
+ new StorageError({
367
+ message: "IndexedDB is not available in this environment"
368
+ })
369
+ );
370
+ }
371
+ const probeDb = yield* Effect2.tryPromise({
372
+ try: () => openDB(name),
373
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
374
+ });
375
+ const currentVersion = probeDb.version;
376
+ let needsUpgrade = true;
377
+ if (probeDb.objectStoreNames.contains("_meta")) {
378
+ const storedSig = yield* Effect2.tryPromise({
379
+ try: () => probeDb.get("_meta", "schema_sig"),
380
+ catch: () => new StorageError({ message: "Failed to read schema meta" })
381
+ }).pipe(Effect2.catch(() => Effect2.succeed(void 0)));
382
+ needsUpgrade = storedSig !== schemaSig;
383
+ }
384
+ probeDb.close();
385
+ const db = needsUpgrade ? yield* Effect2.tryPromise({
386
+ try: () => openDB(name, currentVersion + 1, {
387
+ upgrade(database, _oldVersion, _newVersion, transaction) {
388
+ upgradeSchema(database, schema, transaction);
389
+ }
390
+ }),
391
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
392
+ }) : yield* Effect2.tryPromise({
393
+ try: () => openDB(name),
394
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
395
+ });
396
+ yield* Effect2.addFinalizer(() => Effect2.sync(() => db.close()));
397
+ const handle = {
398
+ putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
399
+ getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
400
+ getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
401
+ countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
402
+ clearRecords: (collection2) => wrap("clearRecords", () => db.clear(storeName(collection2))),
403
+ getByIndex: (collection2, indexName, value) => wrap("getByIndex", () => db.getAllFromIndex(storeName(collection2), indexName, value)),
404
+ getByIndexRange: (collection2, indexName, range) => wrap("getByIndexRange", () => db.getAllFromIndex(storeName(collection2), indexName, range)),
405
+ getAllSorted: (collection2, indexName, direction) => wrap("getAllSorted", async () => {
406
+ const sn = storeName(collection2);
407
+ const tx = db.transaction(sn, "readonly");
408
+ const store = tx.objectStore(sn);
409
+ const index = store.index(indexName);
410
+ const results = [];
411
+ let cursor = await index.openCursor(null, direction ?? "next");
412
+ while (cursor) {
413
+ results.push(cursor.value);
414
+ cursor = await cursor.continue();
415
+ }
416
+ return results;
417
+ }),
418
+ putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
419
+ getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
420
+ getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
421
+ getEventsByRecord: (collection2, recordId) => wrap(
422
+ "getEventsByRecord",
423
+ () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
424
+ ),
425
+ putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
426
+ if (gw.event) {
427
+ const compact = packEvent(gw.event);
428
+ await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
429
+ } else {
430
+ await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
431
+ }
432
+ }),
433
+ getGiftWrap: (id) => wrap("getGiftWrap", async () => {
434
+ const raw = await db.get("giftwraps", id);
435
+ if (!raw) return void 0;
436
+ if (raw.compact) {
437
+ return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
438
+ }
439
+ if (raw.event) {
440
+ const compact = packEvent(raw.event);
441
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
442
+ return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
443
+ }
444
+ return { id: raw.id, createdAt: raw.createdAt };
445
+ }),
446
+ getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
447
+ const raws = await db.getAll("giftwraps");
448
+ const results = [];
449
+ for (const raw of raws) {
450
+ if (raw.compact) {
451
+ results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
452
+ } else if (raw.event) {
453
+ const compact = packEvent(raw.event);
454
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
455
+ results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
456
+ } else {
457
+ results.push({ id: raw.id, createdAt: raw.createdAt });
458
+ }
459
+ }
460
+ return results;
461
+ }),
462
+ deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
463
+ deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
464
+ stripEventData: (id) => wrap("stripEventData", async () => {
465
+ const existing = await db.get("events", id);
466
+ if (existing) {
467
+ await db.put("events", { ...existing, data: null });
468
+ }
469
+ }),
470
+ getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
471
+ putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
472
+ close: () => Effect2.sync(() => db.close())
473
+ };
474
+ return handle;
475
+ });
476
+ }
477
+
227
478
  // src/services/Config.ts
228
479
  import { ServiceMap } from "effect";
229
480
  var Config = class extends ServiceMap.Service()("tablinum/Config") {
@@ -240,16 +491,16 @@ var Tablinum = class extends ServiceMap2.Service()(
240
491
  import { Effect as Effect21, Exit, Layer as Layer9, Option as Option9, PubSub as PubSub2, References as References3, Ref as Ref5, Scope as Scope4 } from "effect";
241
492
 
242
493
  // src/crud/watch.ts
243
- import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
494
+ import { Effect as Effect3, PubSub, Ref, Stream } from "effect";
244
495
  function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
245
- const query = () => Effect2.map(storage.getAllRecords(collectionName), (all) => {
496
+ const query = () => Effect3.map(storage.getAllRecords(collectionName), (all) => {
246
497
  const filtered = all.filter((r) => !r._d && (filter ? filter(r) : true));
247
498
  return mapRecord2 ? filtered.map(mapRecord2) : filtered;
248
499
  });
249
500
  const changes = Stream.fromPubSub(ctx.pubsub).pipe(
250
501
  Stream.filter((event) => event.collection === collectionName),
251
502
  Stream.mapEffect(
252
- () => Effect2.gen(function* () {
503
+ () => Effect3.gen(function* () {
253
504
  const replaying = yield* Ref.get(ctx.replayingRef);
254
505
  if (replaying) return void 0;
255
506
  return yield* query();
@@ -258,18 +509,18 @@ function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
258
509
  Stream.filter((result) => result !== void 0)
259
510
  );
260
511
  return Stream.unwrap(
261
- Effect2.gen(function* () {
262
- yield* Effect2.sleep(0);
512
+ Effect3.gen(function* () {
513
+ yield* Effect3.sleep(0);
263
514
  const initial = yield* query();
264
515
  return Stream.concat(Stream.make(initial), changes);
265
516
  })
266
517
  );
267
518
  }
268
519
  function notifyChange(ctx, event) {
269
- return PubSub.publish(ctx.pubsub, event).pipe(Effect2.asVoid);
520
+ return PubSub.publish(ctx.pubsub, event).pipe(Effect3.asVoid);
270
521
  }
271
522
  function notifyReplayComplete(ctx, collections) {
272
- return Effect2.gen(function* () {
523
+ return Effect3.gen(function* () {
273
524
  yield* Ref.set(ctx.replayingRef, false);
274
525
  for (const collection2 of collections) {
275
526
  yield* notifyChange(ctx, {
@@ -282,7 +533,7 @@ function notifyReplayComplete(ctx, collections) {
282
533
  }
283
534
 
284
535
  // src/storage/records-store.ts
285
- import { Effect as Effect3 } from "effect";
536
+ import { Effect as Effect4 } from "effect";
286
537
 
287
538
  // src/storage/lww.ts
288
539
  function resolveWinner(existing, incoming) {
@@ -346,7 +597,7 @@ function buildRecord(event) {
346
597
  };
347
598
  }
348
599
  function applyEvent(storage, event) {
349
- return Effect3.gen(function* () {
600
+ return Effect4.gen(function* () {
350
601
  const existing = yield* storage.getRecord(event.collection, event.recordId);
351
602
  if (existing) {
352
603
  const existingMeta = {
@@ -366,7 +617,7 @@ function applyEvent(storage, event) {
366
617
  });
367
618
  }
368
619
  function rebuild(storage, collections) {
369
- return Effect3.gen(function* () {
620
+ return Effect4.gen(function* () {
370
621
  for (const col of collections) {
371
622
  yield* storage.clearRecords(col);
372
623
  }
@@ -382,7 +633,7 @@ function rebuild(storage, collections) {
382
633
  }
383
634
 
384
635
  // src/schema/validate.ts
385
- import { Effect as Effect4, Schema as Schema3 } from "effect";
636
+ import { Effect as Effect5, Schema as Schema3 } from "effect";
386
637
  function fieldDefToSchema(fd) {
387
638
  let base;
388
639
  switch (fd.kind) {
@@ -430,10 +681,10 @@ function buildStructSchema(def, options = {}) {
430
681
  function buildValidator(collectionName, def) {
431
682
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
432
683
  return (input) => decode(input).pipe(
433
- Effect4.map(
684
+ Effect5.map(
434
685
  (result) => result
435
686
  ),
436
- Effect4.mapError(
687
+ Effect5.mapError(
437
688
  (e) => new ValidationError({
438
689
  message: `Validation failed for collection "${collectionName}": ${e.message}`
439
690
  })
@@ -442,7 +693,7 @@ function buildValidator(collectionName, def) {
442
693
  }
443
694
  function buildPartialValidator(collectionName, def) {
444
695
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
445
- return (input) => Effect4.gen(function* () {
696
+ return (input) => Effect5.gen(function* () {
446
697
  if (typeof input !== "object" || input === null) {
447
698
  return yield* new ValidationError({
448
699
  message: `Validation failed for collection "${collectionName}": expected an object`
@@ -457,8 +708,8 @@ function buildPartialValidator(collectionName, def) {
457
708
  });
458
709
  }
459
710
  return yield* decode(record).pipe(
460
- Effect4.map((result) => result),
461
- Effect4.mapError(
711
+ Effect5.map((result) => result),
712
+ Effect5.mapError(
462
713
  (e) => new ValidationError({
463
714
  message: `Validation failed for collection "${collectionName}": ${e.message}`
464
715
  })
@@ -468,7 +719,7 @@ function buildPartialValidator(collectionName, def) {
468
719
  }
469
720
 
470
721
  // src/crud/collection-handle.ts
471
- import { Effect as Effect6, Option as Option3, References } from "effect";
722
+ import { Effect as Effect7, Option as Option3, References } from "effect";
472
723
 
473
724
  // src/utils/uuid.ts
474
725
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -501,12 +752,12 @@ function uuidv7() {
501
752
  }
502
753
 
503
754
  // src/crud/query-builder.ts
504
- import { Effect as Effect5, Option as Option2, Ref as Ref2, Stream as Stream2 } from "effect";
755
+ import { Effect as Effect6, Option as Option2, Ref as Ref2, Stream as Stream2 } from "effect";
505
756
  function emptyPlan() {
506
757
  return { filters: [] };
507
758
  }
508
759
  function executeQuery(ctx, plan) {
509
- return Effect5.gen(function* () {
760
+ return Effect6.gen(function* () {
510
761
  if (plan.fieldName) {
511
762
  const fieldDef = ctx.def.fields[plan.fieldName];
512
763
  if (!fieldDef) {
@@ -582,7 +833,7 @@ function watchQuery(ctx, plan) {
582
833
  const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
583
834
  Stream2.filter((event) => event.collection === ctx.collectionName),
584
835
  Stream2.mapEffect(
585
- () => Effect5.gen(function* () {
836
+ () => Effect6.gen(function* () {
586
837
  const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
587
838
  if (replaying) return void 0;
588
839
  return yield* query();
@@ -591,7 +842,7 @@ function watchQuery(ctx, plan) {
591
842
  Stream2.filter((result) => result !== void 0)
592
843
  );
593
844
  return Stream2.unwrap(
594
- Effect5.gen(function* () {
845
+ Effect6.gen(function* () {
595
846
  const initial = yield* query();
596
847
  return Stream2.concat(Stream2.make(initial), changes);
597
848
  })
@@ -617,11 +868,11 @@ function makeQueryBuilder(ctx, plan) {
617
868
  offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
618
869
  limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
619
870
  get: () => executeQuery(ctx, plan),
620
- first: () => Effect5.map(
871
+ first: () => Effect6.map(
621
872
  executeQuery(ctx, { ...plan, limit: 1 }),
622
873
  (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
623
874
  ),
624
- count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
875
+ count: () => Effect6.map(executeQuery(ctx, plan), (results) => results.length),
625
876
  watch: () => watchQuery(ctx, plan)
626
877
  };
627
878
  }
@@ -727,7 +978,7 @@ function replayState(recordId, events, stopAtId) {
727
978
  return state;
728
979
  }
729
980
  function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
730
- return Effect6.gen(function* () {
981
+ return Effect7.gen(function* () {
731
982
  const chronological = sortChronologically(allSorted);
732
983
  const state = replayState(recordId, chronological, target.id);
733
984
  if (state) {
@@ -736,7 +987,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
736
987
  });
737
988
  }
738
989
  function pruneEvents(storage, collection2, recordId, retention) {
739
- return Effect6.gen(function* () {
990
+ return Effect7.gen(function* () {
740
991
  const events = yield* storage.getEventsByRecord(collection2, recordId);
741
992
  if (events.length <= retention) return;
742
993
  const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
@@ -757,8 +1008,8 @@ function mapRecord(record) {
757
1008
  }
758
1009
  function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
759
1010
  const collectionName = def.name;
760
- const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
761
- const commitEvent = (event) => Effect6.gen(function* () {
1011
+ const withLog = (effect) => Effect7.provideService(effect, References.MinimumLogLevel, logLevel);
1012
+ const commitEvent = (event) => Effect7.gen(function* () {
762
1013
  yield* storage.putEvent(event);
763
1014
  yield* applyEvent(storage, event);
764
1015
  if (onWrite) yield* onWrite(event);
@@ -770,7 +1021,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
770
1021
  });
771
1022
  const handle = {
772
1023
  add: (data) => withLog(
773
- Effect6.gen(function* () {
1024
+ Effect7.gen(function* () {
774
1025
  const id = uuidv7();
775
1026
  const fullRecord = { id, ...data };
776
1027
  yield* validator(fullRecord);
@@ -784,7 +1035,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
784
1035
  author: localAuthor
785
1036
  };
786
1037
  yield* commitEvent(event);
787
- yield* Effect6.logDebug("Record added", {
1038
+ yield* Effect7.logDebug("Record added", {
788
1039
  collection: collectionName,
789
1040
  recordId: id,
790
1041
  data: fullRecord
@@ -793,7 +1044,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
793
1044
  })
794
1045
  ),
795
1046
  update: (id, data) => withLog(
796
- Effect6.gen(function* () {
1047
+ Effect7.gen(function* () {
797
1048
  const existing = yield* storage.getRecord(collectionName, id);
798
1049
  if (!existing || existing._d) {
799
1050
  return yield* new NotFoundError({
@@ -816,7 +1067,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
816
1067
  author: localAuthor
817
1068
  };
818
1069
  yield* commitEvent(event);
819
- yield* Effect6.logDebug("Record updated", {
1070
+ yield* Effect7.logDebug("Record updated", {
820
1071
  collection: collectionName,
821
1072
  recordId: id,
822
1073
  data: diff
@@ -825,7 +1076,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
825
1076
  })
826
1077
  ),
827
1078
  delete: (id) => withLog(
828
- Effect6.gen(function* () {
1079
+ Effect7.gen(function* () {
829
1080
  const existing = yield* storage.getRecord(collectionName, id);
830
1081
  if (!existing || existing._d) {
831
1082
  return yield* new NotFoundError({
@@ -843,11 +1094,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
843
1094
  author: localAuthor
844
1095
  };
845
1096
  yield* commitEvent(event);
846
- yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
1097
+ yield* Effect7.logDebug("Record deleted", { collection: collectionName, recordId: id });
847
1098
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
848
1099
  })
849
1100
  ),
850
- undo: (id) => Effect6.gen(function* () {
1101
+ undo: (id) => Effect7.gen(function* () {
851
1102
  const existing = yield* storage.getRecord(collectionName, id);
852
1103
  if (!existing) {
853
1104
  return yield* new NotFoundError({ collection: collectionName, id });
@@ -872,7 +1123,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
872
1123
  yield* commitEvent(event);
873
1124
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
874
1125
  }),
875
- get: (id) => Effect6.gen(function* () {
1126
+ get: (id) => Effect7.gen(function* () {
876
1127
  const record = yield* storage.getRecord(collectionName, id);
877
1128
  if (!record || record._d) {
878
1129
  return yield* new NotFoundError({
@@ -882,11 +1133,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
882
1133
  }
883
1134
  return mapRecord(record);
884
1135
  }),
885
- first: () => Effect6.map(storage.getAllRecords(collectionName), (all) => {
1136
+ first: () => Effect7.map(storage.getAllRecords(collectionName), (all) => {
886
1137
  const found = all.find((r) => !r._d);
887
1138
  return found ? Option3.some(mapRecord(found)) : Option3.none();
888
1139
  }),
889
- count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
1140
+ count: () => Effect7.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
890
1141
  watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
891
1142
  where: (fieldName) => createWhereClause(
892
1143
  storage,
@@ -909,12 +1160,12 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
909
1160
  }
910
1161
 
911
1162
  // src/sync/sync-service.ts
912
- import { Duration, Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
1163
+ import { Duration, Effect as Effect9, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
913
1164
  import { unwrapEvent } from "nostr-tools/nip59";
914
1165
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
915
1166
 
916
1167
  // src/sync/negentropy.ts
917
- import { Effect as Effect7 } from "effect";
1168
+ import { Effect as Effect8 } from "effect";
918
1169
 
919
1170
  // src/vendor/negentropy.js
920
1171
  var PROTOCOL_VERSION = 97;
@@ -1401,14 +1652,14 @@ function itemCompare(a, b) {
1401
1652
  }
1402
1653
 
1403
1654
  // src/sync/negentropy.ts
1404
- import { hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
1655
+ import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1405
1656
  import { GiftWrap } from "nostr-tools/kinds";
1406
1657
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1407
- return Effect7.gen(function* () {
1658
+ return Effect8.gen(function* () {
1408
1659
  const allGiftWraps = yield* storage.getAllGiftWraps();
1409
1660
  const storageVector = new NegentropyStorageVector();
1410
1661
  for (const gw of allGiftWraps) {
1411
- storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1662
+ storageVector.insert(gw.createdAt, hexToBytes3(gw.id));
1412
1663
  }
1413
1664
  storageVector.seal();
1414
1665
  const neg = new Negentropy(storageVector, 0);
@@ -1419,7 +1670,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1419
1670
  const allHaveIds = [];
1420
1671
  const allNeedIds = [];
1421
1672
  const subId = `neg-${Date.now()}`;
1422
- const initialMsg = yield* Effect7.tryPromise({
1673
+ const initialMsg = yield* Effect8.tryPromise({
1423
1674
  try: () => neg.initiate(),
1424
1675
  catch: (e) => new SyncError({
1425
1676
  message: `Negentropy initiate failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1431,7 +1682,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1431
1682
  while (currentMsg !== null) {
1432
1683
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1433
1684
  if (response.msgHex === null) break;
1434
- const reconcileResult = yield* Effect7.tryPromise({
1685
+ const reconcileResult = yield* Effect8.tryPromise({
1435
1686
  try: () => neg.reconcile(response.msgHex),
1436
1687
  catch: (e) => new SyncError({
1437
1688
  message: `Negentropy reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1444,13 +1695,13 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1444
1695
  for (const id of needIds) allNeedIds.push(id);
1445
1696
  currentMsg = nextMsg;
1446
1697
  }
1447
- yield* Effect7.logDebug("Negentropy reconciliation complete", {
1698
+ yield* Effect8.logDebug("Negentropy reconciliation complete", {
1448
1699
  relay: relayUrl,
1449
1700
  have: allHaveIds.length,
1450
1701
  need: allNeedIds.length
1451
1702
  });
1452
1703
  return { haveIds: allHaveIds, needIds: allNeedIds };
1453
- }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1704
+ }).pipe(Effect8.withLogSpan("tablinum.negentropy"));
1454
1705
  }
1455
1706
 
1456
1707
  // src/db/key-rotation.ts
@@ -1544,52 +1795,52 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1544
1795
  kind: "create"
1545
1796
  });
1546
1797
  const forkHandled = (effect) => {
1547
- Effect8.runFork(
1798
+ Effect9.runFork(
1548
1799
  effect.pipe(
1549
- Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
1550
- Effect8.ignore,
1551
- Effect8.provide(logLayer),
1552
- Effect8.forkIn(scope)
1800
+ Effect9.tapError((e) => Effect9.sync(() => onSyncError?.(e))),
1801
+ Effect9.ignore,
1802
+ Effect9.provide(logLayer),
1803
+ Effect9.forkIn(scope)
1553
1804
  )
1554
1805
  );
1555
1806
  };
1556
1807
  let autoFlushActive = false;
1557
- const autoFlushEffect = Effect8.gen(function* () {
1808
+ const autoFlushEffect = Effect9.gen(function* () {
1558
1809
  const size = yield* publishQueue.size();
1559
1810
  if (size === 0) return;
1560
1811
  yield* syncStatus.set("syncing");
1561
1812
  yield* publishQueue.flush(relayUrls);
1562
1813
  const remaining = yield* publishQueue.size();
1563
- if (remaining > 0) yield* Effect8.fail("pending");
1814
+ if (remaining > 0) yield* Effect9.fail("pending");
1564
1815
  }).pipe(
1565
- Effect8.ensuring(syncStatus.set("idle")),
1566
- Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1567
- Effect8.ignore
1816
+ Effect9.ensuring(syncStatus.set("idle")),
1817
+ Effect9.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1818
+ Effect9.ignore
1568
1819
  );
1569
1820
  const scheduleAutoFlush = () => {
1570
1821
  if (autoFlushActive) return;
1571
1822
  autoFlushActive = true;
1572
1823
  forkHandled(
1573
1824
  autoFlushEffect.pipe(
1574
- Effect8.ensuring(
1575
- Effect8.sync(() => {
1825
+ Effect9.ensuring(
1826
+ Effect9.sync(() => {
1576
1827
  autoFlushActive = false;
1577
1828
  })
1578
1829
  )
1579
1830
  )
1580
1831
  );
1581
1832
  };
1582
- const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1833
+ const shouldRejectWrite = (authorPubkey) => Effect9.gen(function* () {
1583
1834
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1584
1835
  if (!memberRecord) return false;
1585
1836
  return !!memberRecord.removedAt;
1586
1837
  });
1587
1838
  const storeGiftWrapShell = (gw) => storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
1588
- const unwrapGiftWrap = (remoteGw) => Effect8.gen(function* () {
1839
+ const unwrapGiftWrap = (remoteGw) => Effect9.gen(function* () {
1589
1840
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1590
1841
  if (existing) return null;
1591
1842
  const rumor = yield* giftWrapHandle.unwrap(remoteGw).pipe(
1592
- Effect8.orElseSucceed(() => null)
1843
+ Effect9.orElseSucceed(() => null)
1593
1844
  );
1594
1845
  if (!rumor) {
1595
1846
  yield* storeGiftWrapShell(remoteGw);
@@ -1617,7 +1868,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1617
1868
  recordId: dTag.substring(colonIdx + 1)
1618
1869
  };
1619
1870
  });
1620
- const applyUnwrappedEvent = (uw) => Effect8.gen(function* () {
1871
+ const applyUnwrappedEvent = (uw) => Effect9.gen(function* () {
1621
1872
  const { giftWrap: remoteGw, rumor, collection: collectionName, recordId } = uw;
1622
1873
  const retention = knownCollections.get(collectionName);
1623
1874
  if (retention === void 0) {
@@ -1627,17 +1878,17 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1627
1878
  if (rumor.pubkey) {
1628
1879
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1629
1880
  if (reject) {
1630
- yield* Effect8.logWarning("Rejected write from removed member", {
1881
+ yield* Effect9.logWarning("Rejected write from removed member", {
1631
1882
  author: rumor.pubkey.slice(0, 12)
1632
1883
  });
1633
1884
  yield* storeGiftWrapShell(remoteGw);
1634
1885
  return null;
1635
1886
  }
1636
1887
  }
1637
- const parsed = yield* Effect8.try({
1888
+ const parsed = yield* Effect9.try({
1638
1889
  try: () => JSON.parse(rumor.content),
1639
1890
  catch: () => void 0
1640
- }).pipe(Effect8.orElseSucceed(() => void 0));
1891
+ }).pipe(Effect9.orElseSucceed(() => void 0));
1641
1892
  if (parsed === void 0) {
1642
1893
  yield* storeGiftWrapShell(remoteGw);
1643
1894
  return null;
@@ -1669,7 +1920,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1669
1920
  if (didApply && (kind === "u" || kind === "d")) {
1670
1921
  yield* pruneEvents(storage, collectionName, recordId, retention);
1671
1922
  }
1672
- yield* Effect8.logDebug("Processed gift wrap", {
1923
+ yield* Effect9.logDebug("Processed gift wrap", {
1673
1924
  collection: collectionName,
1674
1925
  recordId,
1675
1926
  kind,
@@ -1680,33 +1931,33 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1680
1931
  }
1681
1932
  return collectionName;
1682
1933
  });
1683
- const reconcileRelay = (url, pubKeys) => Effect8.gen(function* () {
1684
- yield* Effect8.logDebug("Syncing relay", { relay: url });
1934
+ const reconcileRelay = (url, pubKeys) => Effect9.gen(function* () {
1935
+ yield* Effect9.logDebug("Syncing relay", { relay: url });
1685
1936
  const { haveIds, needIds } = yield* reconcileWithRelay(
1686
1937
  storage,
1687
1938
  relay,
1688
1939
  url,
1689
1940
  Array.from(pubKeys)
1690
1941
  ).pipe(
1691
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1692
- Effect8.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1942
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1943
+ Effect9.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1693
1944
  );
1694
- yield* Effect8.logDebug("Relay reconciliation result", {
1945
+ yield* Effect9.logDebug("Relay reconciliation result", {
1695
1946
  relay: url,
1696
1947
  need: needIds.length,
1697
1948
  have: haveIds.length
1698
1949
  });
1699
1950
  const events = needIds.length > 0 ? yield* relay.fetchEvents(needIds, url).pipe(
1700
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1701
- Effect8.orElseSucceed(() => [])
1951
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1952
+ Effect9.orElseSucceed(() => [])
1702
1953
  ) : [];
1703
1954
  return {
1704
1955
  events,
1705
1956
  haveIds: haveIds.map((id) => ({ id, url }))
1706
1957
  };
1707
- }).pipe(Effect8.withLogSpan("tablinum.reconcileRelay"));
1708
- const syncAllRelays = (pubKeys, changedCollections) => Effect8.gen(function* () {
1709
- const results = yield* Effect8.forEach(
1958
+ }).pipe(Effect9.withLogSpan("tablinum.reconcileRelay"));
1959
+ const syncAllRelays = (pubKeys, changedCollections) => Effect9.gen(function* () {
1960
+ const results = yield* Effect9.forEach(
1710
1961
  relayUrls,
1711
1962
  (url) => reconcileRelay(url, pubKeys),
1712
1963
  { concurrency: "unbounded" }
@@ -1723,44 +1974,44 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1723
1974
  }
1724
1975
  const unwrapped = [];
1725
1976
  for (const gw of allGiftWraps) {
1726
- const result = yield* unwrapGiftWrap(gw).pipe(Effect8.orElseSucceed(() => null));
1977
+ const result = yield* unwrapGiftWrap(gw).pipe(Effect9.orElseSucceed(() => null));
1727
1978
  if (result) unwrapped.push(result);
1728
1979
  }
1729
1980
  unwrapped.sort((a, b) => a.rumor.created_at - b.rumor.created_at || (a.rumor.id < b.rumor.id ? -1 : 1));
1730
1981
  for (const event of unwrapped) {
1731
1982
  const collection2 = yield* applyUnwrappedEvent(event).pipe(
1732
- Effect8.orElseSucceed(() => null)
1983
+ Effect9.orElseSucceed(() => null)
1733
1984
  );
1734
1985
  if (collection2) changedCollections.add(collection2);
1735
1986
  }
1736
1987
  const allHaveIds = results.flatMap((r) => r.haveIds);
1737
- yield* Effect8.forEach(
1988
+ yield* Effect9.forEach(
1738
1989
  allHaveIds,
1739
- ({ id, url }) => Effect8.gen(function* () {
1990
+ ({ id, url }) => Effect9.gen(function* () {
1740
1991
  const gw = yield* storage.getGiftWrap(id);
1741
1992
  if (!gw?.event) return;
1742
1993
  yield* relay.publish(gw.event, [url]).pipe(
1743
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1744
- Effect8.ignore
1994
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1995
+ Effect9.ignore
1745
1996
  );
1746
1997
  }),
1747
1998
  { concurrency: "unbounded", discard: true }
1748
1999
  );
1749
- }).pipe(Effect8.withLogSpan("tablinum.syncAllRelays"));
1750
- const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
2000
+ }).pipe(Effect9.withLogSpan("tablinum.syncAllRelays"));
2001
+ const processGiftWrap = (remoteGw) => Effect9.gen(function* () {
1751
2002
  const uw = yield* unwrapGiftWrap(remoteGw);
1752
2003
  if (!uw) return null;
1753
2004
  return yield* applyUnwrappedEvent(uw);
1754
2005
  });
1755
- const processRealtimeGiftWrap = (remoteGw) => Effect8.gen(function* () {
1756
- const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
2006
+ const processRealtimeGiftWrap = (remoteGw) => Effect9.gen(function* () {
2007
+ const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect9.orElseSucceed(() => null));
1757
2008
  if (collection2) {
1758
2009
  yield* notifyCollectionUpdated(collection2);
1759
2010
  }
1760
2011
  });
1761
- const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1762
- const unwrapResult = yield* Effect8.result(
1763
- Effect8.try({
2012
+ const processRotationGiftWrap = (remoteGw) => Effect9.gen(function* () {
2013
+ const unwrapResult = yield* Effect9.result(
2014
+ Effect9.try({
1764
2015
  try: () => unwrapEvent(remoteGw, personalPrivateKey),
1765
2016
  catch: (e) => new CryptoError({
1766
2017
  message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1810,73 +2061,73 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1810
2061
  yield* handle.addEpochSubscription(epoch.publicKey);
1811
2062
  return true;
1812
2063
  });
1813
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
2064
+ const subscribeAcrossRelays = (filter, onEvent) => Effect9.forEach(
1814
2065
  relayUrls,
1815
- (url) => Effect8.gen(function* () {
2066
+ (url) => Effect9.gen(function* () {
1816
2067
  yield* relay.subscribe(filter, url, (event) => {
1817
2068
  forkHandled(onEvent(event));
1818
2069
  }).pipe(
1819
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1820
- Effect8.ignore
2070
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2071
+ Effect9.ignore
1821
2072
  );
1822
2073
  }),
1823
2074
  { concurrency: "unbounded", discard: true }
1824
2075
  );
1825
2076
  let healingActive = false;
1826
- const healingEffect = Effect8.gen(function* () {
2077
+ const healingEffect = Effect9.gen(function* () {
1827
2078
  if (!healingActive) return;
1828
2079
  const status = yield* syncStatus.get();
1829
2080
  if (status === "syncing") return;
1830
2081
  yield* syncStatus.set("syncing");
1831
- yield* Effect8.gen(function* () {
2082
+ yield* Effect9.gen(function* () {
1832
2083
  const pubKeys = getSubscriptionPubKeys();
1833
2084
  const changedCollections = /* @__PURE__ */ new Set();
1834
2085
  yield* syncAllRelays(pubKeys, changedCollections);
1835
2086
  if (changedCollections.size > 0) {
1836
2087
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1837
2088
  }
1838
- }).pipe(Effect8.ensuring(syncStatus.set("idle")));
1839
- }).pipe(Effect8.ignore);
2089
+ }).pipe(Effect9.ensuring(syncStatus.set("idle")));
2090
+ }).pipe(Effect9.ignore);
1840
2091
  const handle = {
1841
- sync: () => Effect8.gen(function* () {
1842
- yield* Effect8.logInfo("Sync started");
2092
+ sync: () => Effect9.gen(function* () {
2093
+ yield* Effect9.logInfo("Sync started");
1843
2094
  yield* syncStatus.set("syncing");
1844
2095
  yield* Ref3.set(watchCtx.replayingRef, true);
1845
2096
  const changedCollections = /* @__PURE__ */ new Set();
1846
- yield* Effect8.gen(function* () {
2097
+ yield* Effect9.gen(function* () {
1847
2098
  const pubKeys = getSubscriptionPubKeys();
1848
2099
  yield* syncAllRelays(pubKeys, changedCollections);
1849
- yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
2100
+ yield* publishQueue.flush(relayUrls).pipe(Effect9.ignore);
1850
2101
  }).pipe(
1851
- Effect8.ensuring(
1852
- Effect8.gen(function* () {
2102
+ Effect9.ensuring(
2103
+ Effect9.gen(function* () {
1853
2104
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1854
2105
  yield* syncStatus.set("idle");
1855
2106
  })
1856
2107
  )
1857
2108
  );
1858
- yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1859
- }).pipe(Effect8.withLogSpan("tablinum.sync")),
1860
- publishLocal: (giftWrap) => Effect8.gen(function* () {
2109
+ yield* Effect9.logInfo("Sync complete", { changed: [...changedCollections] });
2110
+ }).pipe(Effect9.withLogSpan("tablinum.sync")),
2111
+ publishLocal: (giftWrap) => Effect9.gen(function* () {
1861
2112
  if (!giftWrap.event) return;
1862
2113
  yield* relay.publish(giftWrap.event, relayUrls).pipe(
1863
- Effect8.tapError(
2114
+ Effect9.tapError(
1864
2115
  () => storage.putGiftWrap(giftWrap).pipe(
1865
- Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
1866
- Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
2116
+ Effect9.andThen(publishQueue.enqueue(giftWrap.id)),
2117
+ Effect9.andThen(Effect9.sync(() => scheduleAutoFlush()))
1867
2118
  )
1868
2119
  ),
1869
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1870
- Effect8.ignore
2120
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2121
+ Effect9.ignore
1871
2122
  );
1872
2123
  }),
1873
- startSubscription: () => Effect8.gen(function* () {
2124
+ startSubscription: () => Effect9.gen(function* () {
1874
2125
  const pubKeys = getSubscriptionPubKeys();
1875
2126
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1876
2127
  if (!pubKeys.includes(personalPublicKey)) {
1877
2128
  yield* subscribeAcrossRelays(
1878
2129
  { kinds: [GiftWrap2], "#p": [personalPublicKey] },
1879
- (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
2130
+ (event) => Effect9.result(processRotationGiftWrap(event)).pipe(Effect9.asVoid)
1880
2131
  );
1881
2132
  }
1882
2133
  }),
@@ -1885,10 +2136,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1885
2136
  if (healingActive) return;
1886
2137
  healingActive = true;
1887
2138
  forkHandled(
1888
- Effect8.sleep(Duration.minutes(5)).pipe(
1889
- Effect8.andThen(healingEffect),
1890
- Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
1891
- Effect8.ensuring(Effect8.sync(() => {
2139
+ Effect9.sleep(Duration.minutes(5)).pipe(
2140
+ Effect9.andThen(healingEffect),
2141
+ Effect9.repeat(Schedule.spaced(Duration.minutes(5))),
2142
+ Effect9.ensuring(Effect9.sync(() => {
1892
2143
  healingActive = false;
1893
2144
  }))
1894
2145
  )
@@ -1900,8 +2151,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1900
2151
  };
1901
2152
  forkHandled(
1902
2153
  publishQueue.size().pipe(
1903
- Effect8.flatMap(
1904
- (size) => Effect8.sync(() => {
2154
+ Effect9.flatMap(
2155
+ (size) => Effect9.sync(() => {
1905
2156
  if (size > 0) scheduleAutoFlush();
1906
2157
  })
1907
2158
  )
@@ -1911,7 +2162,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1911
2162
  }
1912
2163
 
1913
2164
  // src/db/members.ts
1914
- import { Effect as Effect9, Option as Option6, Schema as Schema5 } from "effect";
2165
+ import { Effect as Effect10, Option as Option6, Schema as Schema5 } from "effect";
1915
2166
  var optionalString = {
1916
2167
  _tag: "FieldDef",
1917
2168
  kind: "string",
@@ -1960,15 +2211,15 @@ var AuthorProfileSchema = Schema5.Struct({
1960
2211
  });
1961
2212
  var decodeAuthorProfile = Schema5.decodeUnknownEffect(Schema5.fromJsonString(AuthorProfileSchema));
1962
2213
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
1963
- return Effect9.gen(function* () {
2214
+ return Effect10.gen(function* () {
1964
2215
  for (const url of relayUrls) {
1965
- const result = yield* Effect9.result(
2216
+ const result = yield* Effect10.result(
1966
2217
  relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
1967
2218
  );
1968
2219
  if (result._tag === "Success" && result.success.length > 0) {
1969
2220
  return yield* decodeAuthorProfile(result.success[0].content).pipe(
1970
- Effect9.map(Option6.some),
1971
- Effect9.orElseSucceed(() => Option6.none())
2221
+ Effect10.map(Option6.some),
2222
+ Effect10.orElseSucceed(() => Option6.none())
1972
2223
  );
1973
2224
  }
1974
2225
  }
@@ -2018,15 +2269,15 @@ var SyncStatus = class extends ServiceMap9.Service()(
2018
2269
  };
2019
2270
 
2020
2271
  // src/layers/IdentityLive.ts
2021
- import { Effect as Effect11, Layer as Layer2 } from "effect";
2022
- import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
2272
+ import { Effect as Effect12, Layer as Layer2 } from "effect";
2273
+ import { hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2023
2274
 
2024
2275
  // src/db/identity.ts
2025
- import { Effect as Effect10 } from "effect";
2276
+ import { Effect as Effect11 } from "effect";
2026
2277
  import { getPublicKey as getPublicKey2 } from "nostr-tools/pure";
2027
- import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
2278
+ import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2028
2279
  function createIdentity(suppliedKey) {
2029
- return Effect10.gen(function* () {
2280
+ return Effect11.gen(function* () {
2030
2281
  let privateKey;
2031
2282
  if (suppliedKey) {
2032
2283
  if (suppliedKey.length !== 32) {
@@ -2039,8 +2290,8 @@ function createIdentity(suppliedKey) {
2039
2290
  privateKey = new Uint8Array(32);
2040
2291
  crypto.getRandomValues(privateKey);
2041
2292
  }
2042
- const privateKeyHex = bytesToHex2(privateKey);
2043
- const publicKey = yield* Effect10.try({
2293
+ const privateKeyHex = bytesToHex3(privateKey);
2294
+ const publicKey = yield* Effect11.try({
2044
2295
  try: () => getPublicKey2(privateKey),
2045
2296
  catch: (e) => new CryptoError({
2046
2297
  message: `Failed to derive public key: ${e instanceof Error ? e.message : String(e)}`,
@@ -2058,14 +2309,14 @@ function createIdentity(suppliedKey) {
2058
2309
  // src/layers/IdentityLive.ts
2059
2310
  var IdentityLive = Layer2.effect(
2060
2311
  Identity,
2061
- Effect11.gen(function* () {
2312
+ Effect12.gen(function* () {
2062
2313
  const config = yield* Config;
2063
2314
  const storage = yield* Storage;
2064
2315
  const idbKey = yield* storage.getMeta("identity_key");
2065
- const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : void 0);
2316
+ const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes4(idbKey) : void 0);
2066
2317
  const identity = yield* createIdentity(resolvedKey);
2067
2318
  yield* storage.putMeta("identity_key", identity.exportKey());
2068
- yield* Effect11.logInfo("Identity loaded", {
2319
+ yield* Effect12.logInfo("Identity loaded", {
2069
2320
  publicKey: identity.publicKey.slice(0, 12) + "...",
2070
2321
  source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
2071
2322
  });
@@ -2074,12 +2325,12 @@ var IdentityLive = Layer2.effect(
2074
2325
  );
2075
2326
 
2076
2327
  // src/layers/EpochStoreLive.ts
2077
- import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
2328
+ import { Effect as Effect13, Layer as Layer3, Option as Option7 } from "effect";
2078
2329
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
2079
- import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2330
+ import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
2080
2331
  var EpochStoreLive = Layer3.effect(
2081
2332
  EpochStore,
2082
- Effect12.gen(function* () {
2333
+ Effect13.gen(function* () {
2083
2334
  const config = yield* Config;
2084
2335
  const identity = yield* Identity;
2085
2336
  const storage = yield* Storage;
@@ -2098,7 +2349,7 @@ var EpochStoreLive = Layer3.effect(
2098
2349
  return existing !== void 0 && existing.privateKey === ek.key;
2099
2350
  });
2100
2351
  if (configIsSubset) {
2101
- yield* Effect12.logInfo("Epoch store loaded", {
2352
+ yield* Effect13.logInfo("Epoch store loaded", {
2102
2353
  source: "storage",
2103
2354
  epochs: idbStore.epochs.size
2104
2355
  });
@@ -2107,271 +2358,28 @@ var EpochStoreLive = Layer3.effect(
2107
2358
  }
2108
2359
  const store2 = createEpochStoreFromInputs(config.epochKeys);
2109
2360
  yield* storage.putMeta("epochs", stringifyEpochStore(store2));
2110
- yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2361
+ yield* Effect13.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2111
2362
  return store2;
2112
2363
  }
2113
2364
  if (idbStore) {
2114
- yield* Effect12.logInfo("Epoch store loaded", {
2365
+ yield* Effect13.logInfo("Epoch store loaded", {
2115
2366
  source: "storage",
2116
2367
  epochs: idbStore.epochs.size
2117
2368
  });
2118
2369
  return idbStore;
2119
2370
  }
2120
2371
  const store = createEpochStoreFromInputs(
2121
- [{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
2372
+ [{ epochId: EpochId("epoch-0"), key: bytesToHex4(generateSecretKey2()) }],
2122
2373
  { createdBy: identity.publicKey }
2123
2374
  );
2124
2375
  yield* storage.putMeta("epochs", stringifyEpochStore(store));
2125
- yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2376
+ yield* Effect13.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2126
2377
  return store;
2127
2378
  })
2128
2379
  );
2129
2380
 
2130
2381
  // src/layers/StorageLive.ts
2131
2382
  import { Effect as Effect14, Layer as Layer4 } from "effect";
2132
-
2133
- // src/storage/idb.ts
2134
- import { Effect as Effect13 } from "effect";
2135
- import { openDB } from "idb";
2136
-
2137
- // src/sync/compact-event.ts
2138
- import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2139
- var VERSION = 1;
2140
- var HEADER_SIZE = 133;
2141
- function base64ToBytes(base64) {
2142
- const binary = atob(base64);
2143
- const bytes = new Uint8Array(binary.length);
2144
- for (let i = 0; i < binary.length; i++) {
2145
- bytes[i] = binary.charCodeAt(i);
2146
- }
2147
- return bytes;
2148
- }
2149
- function bytesToBase64(bytes) {
2150
- let binary = "";
2151
- for (let i = 0; i < bytes.length; i++) {
2152
- binary += String.fromCharCode(bytes[i]);
2153
- }
2154
- return btoa(binary);
2155
- }
2156
- function packEvent(event) {
2157
- const pubkey = hexToBytes4(event.pubkey);
2158
- const sig = hexToBytes4(event.sig);
2159
- const recipientTag = event.tags.find((t) => t[0] === "p");
2160
- if (!recipientTag) throw new Error("Gift wrap missing #p tag");
2161
- if (event.tags.some((t) => t[0] !== "p")) {
2162
- throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
2163
- }
2164
- const recipient = hexToBytes4(recipientTag[1]);
2165
- const createdAtBuf = new Uint8Array(4);
2166
- new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
2167
- const content = base64ToBytes(event.content);
2168
- const result = new Uint8Array(HEADER_SIZE + content.length);
2169
- result[0] = VERSION;
2170
- result.set(pubkey, 1);
2171
- result.set(sig, 33);
2172
- result.set(recipient, 97);
2173
- result.set(createdAtBuf, 129);
2174
- result.set(content, HEADER_SIZE);
2175
- return result;
2176
- }
2177
- function unpackEvent(id, compact) {
2178
- const version = compact[0];
2179
- if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
2180
- const pubkey = bytesToHex4(compact.slice(1, 33));
2181
- const sig = bytesToHex4(compact.slice(33, 97));
2182
- const recipient = bytesToHex4(compact.slice(97, 129));
2183
- const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
2184
- const createdAt = dv.getUint32(0, false);
2185
- const content = bytesToBase64(compact.slice(HEADER_SIZE));
2186
- return {
2187
- id,
2188
- pubkey,
2189
- sig,
2190
- created_at: createdAt,
2191
- kind: 1059,
2192
- tags: [["p", recipient]],
2193
- content
2194
- };
2195
- }
2196
-
2197
- // src/storage/idb.ts
2198
- var DB_NAME = "tablinum";
2199
- function storeName(collection2) {
2200
- return `col_${collection2}`;
2201
- }
2202
- function computeSchemaSig(schema) {
2203
- return Object.entries(schema).sort(([a], [b]) => a.localeCompare(b)).map(([name, def]) => {
2204
- const indices = [...def.indices ?? []].sort().join(",");
2205
- return `${name}:${indices}`;
2206
- }).join("|");
2207
- }
2208
- function wrap(label, fn) {
2209
- return Effect13.tryPromise({
2210
- try: fn,
2211
- catch: (e) => new StorageError({
2212
- message: `IndexedDB ${label} failed: ${e instanceof Error ? e.message : String(e)}`,
2213
- cause: e
2214
- })
2215
- });
2216
- }
2217
- function upgradeSchema(database, schema, tx) {
2218
- if (!database.objectStoreNames.contains("_meta")) {
2219
- database.createObjectStore("_meta");
2220
- }
2221
- if (!database.objectStoreNames.contains("events")) {
2222
- const events = database.createObjectStore("events", { keyPath: "id" });
2223
- events.createIndex("by-record", ["collection", "recordId"]);
2224
- }
2225
- if (!database.objectStoreNames.contains("giftwraps")) {
2226
- database.createObjectStore("giftwraps", { keyPath: "id" });
2227
- }
2228
- const expectedStores = /* @__PURE__ */ new Set();
2229
- for (const [, def] of Object.entries(schema)) {
2230
- const sn = storeName(def.name);
2231
- expectedStores.add(sn);
2232
- if (!database.objectStoreNames.contains(sn)) {
2233
- const store = database.createObjectStore(sn, { keyPath: "id" });
2234
- for (const idx of def.indices ?? []) {
2235
- store.createIndex(idx, idx);
2236
- }
2237
- } else {
2238
- const store = tx.objectStore(sn);
2239
- const existingIndices = new Set(Array.from(store.indexNames));
2240
- const wantedIndices = new Set(def.indices ?? []);
2241
- for (const idx of existingIndices) {
2242
- if (!wantedIndices.has(idx)) store.deleteIndex(idx);
2243
- }
2244
- for (const idx of wantedIndices) {
2245
- if (!existingIndices.has(idx)) store.createIndex(idx, idx);
2246
- }
2247
- }
2248
- }
2249
- for (const existing of Array.from(database.objectStoreNames)) {
2250
- if (existing.startsWith("col_") && !expectedStores.has(existing)) {
2251
- database.deleteObjectStore(existing);
2252
- }
2253
- }
2254
- tx.objectStore("_meta").put(computeSchemaSig(schema), "schema_sig");
2255
- }
2256
- function openIDBStorage(dbName, schema) {
2257
- return Effect13.gen(function* () {
2258
- const name = dbName ?? DB_NAME;
2259
- const schemaSig = computeSchemaSig(schema);
2260
- if (typeof indexedDB === "undefined") {
2261
- return yield* Effect13.fail(
2262
- new StorageError({
2263
- message: "IndexedDB is not available in this environment"
2264
- })
2265
- );
2266
- }
2267
- const probeDb = yield* Effect13.tryPromise({
2268
- try: () => openDB(name),
2269
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2270
- });
2271
- const currentVersion = probeDb.version;
2272
- let needsUpgrade = true;
2273
- if (probeDb.objectStoreNames.contains("_meta")) {
2274
- const storedSig = yield* Effect13.tryPromise({
2275
- try: () => probeDb.get("_meta", "schema_sig"),
2276
- catch: () => new StorageError({ message: "Failed to read schema meta" })
2277
- }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
2278
- needsUpgrade = storedSig !== schemaSig;
2279
- }
2280
- probeDb.close();
2281
- const db = needsUpgrade ? yield* Effect13.tryPromise({
2282
- try: () => openDB(name, currentVersion + 1, {
2283
- upgrade(database, _oldVersion, _newVersion, transaction) {
2284
- upgradeSchema(database, schema, transaction);
2285
- }
2286
- }),
2287
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2288
- }) : yield* Effect13.tryPromise({
2289
- try: () => openDB(name),
2290
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2291
- });
2292
- yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2293
- const handle = {
2294
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2295
- getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2296
- getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2297
- countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
2298
- clearRecords: (collection2) => wrap("clearRecords", () => db.clear(storeName(collection2))),
2299
- getByIndex: (collection2, indexName, value) => wrap("getByIndex", () => db.getAllFromIndex(storeName(collection2), indexName, value)),
2300
- getByIndexRange: (collection2, indexName, range) => wrap("getByIndexRange", () => db.getAllFromIndex(storeName(collection2), indexName, range)),
2301
- getAllSorted: (collection2, indexName, direction) => wrap("getAllSorted", async () => {
2302
- const sn = storeName(collection2);
2303
- const tx = db.transaction(sn, "readonly");
2304
- const store = tx.objectStore(sn);
2305
- const index = store.index(indexName);
2306
- const results = [];
2307
- let cursor = await index.openCursor(null, direction ?? "next");
2308
- while (cursor) {
2309
- results.push(cursor.value);
2310
- cursor = await cursor.continue();
2311
- }
2312
- return results;
2313
- }),
2314
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2315
- getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2316
- getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2317
- getEventsByRecord: (collection2, recordId) => wrap(
2318
- "getEventsByRecord",
2319
- () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
2320
- ),
2321
- putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
2322
- if (gw.event) {
2323
- const compact = packEvent(gw.event);
2324
- await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
2325
- } else {
2326
- await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
2327
- }
2328
- }),
2329
- getGiftWrap: (id) => wrap("getGiftWrap", async () => {
2330
- const raw = await db.get("giftwraps", id);
2331
- if (!raw) return void 0;
2332
- if (raw.compact) {
2333
- return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
2334
- }
2335
- if (raw.event) {
2336
- const compact = packEvent(raw.event);
2337
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2338
- return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
2339
- }
2340
- return { id: raw.id, createdAt: raw.createdAt };
2341
- }),
2342
- getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
2343
- const raws = await db.getAll("giftwraps");
2344
- const results = [];
2345
- for (const raw of raws) {
2346
- if (raw.compact) {
2347
- results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
2348
- } else if (raw.event) {
2349
- const compact = packEvent(raw.event);
2350
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2351
- results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
2352
- } else {
2353
- results.push({ id: raw.id, createdAt: raw.createdAt });
2354
- }
2355
- }
2356
- return results;
2357
- }),
2358
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
2359
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
2360
- stripEventData: (id) => wrap("stripEventData", async () => {
2361
- const existing = await db.get("events", id);
2362
- if (existing) {
2363
- await db.put("events", { ...existing, data: null });
2364
- }
2365
- }),
2366
- getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2367
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2368
- close: () => Effect13.sync(() => db.close())
2369
- };
2370
- return handle;
2371
- });
2372
- }
2373
-
2374
- // src/layers/StorageLive.ts
2375
2383
  var StorageLive = Layer4.effect(
2376
2384
  Storage,
2377
2385
  Effect14.gen(function* () {
@@ -3050,6 +3058,60 @@ var TablinumLive = Layer9.effect(
3050
3058
  yield* Scope4.close(scope, Exit.void);
3051
3059
  })
3052
3060
  ),
3061
+ destroy: () => withLog(
3062
+ Effect21.gen(function* () {
3063
+ if (!(yield* Ref5.get(closedRef))) {
3064
+ yield* Ref5.set(closedRef, true);
3065
+ syncHandle.stopHealing();
3066
+ yield* Scope4.close(scope, Exit.void);
3067
+ }
3068
+ yield* deleteIDBStorage(config.dbName);
3069
+ })
3070
+ ),
3071
+ leave: () => withLog(
3072
+ Effect21.gen(function* () {
3073
+ if (yield* Ref5.get(closedRef)) {
3074
+ return yield* new SyncError({ message: "Database is closed", phase: "leave" });
3075
+ }
3076
+ const allMembers = yield* storage.getAllRecords("_members");
3077
+ const activeMembers = allMembers.filter(
3078
+ (member) => !member.removedAt && member.id !== identity.publicKey
3079
+ );
3080
+ const activePubkeys = activeMembers.map((member) => member.id);
3081
+ const result = createRotation(
3082
+ epochStore,
3083
+ identity.privateKey,
3084
+ identity.publicKey,
3085
+ activePubkeys,
3086
+ [identity.publicKey]
3087
+ );
3088
+ addEpoch(epochStore, result.epoch);
3089
+ epochStore.currentEpochId = result.epoch.id;
3090
+ yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
3091
+ const memberRecord = yield* storage.getRecord("_members", identity.publicKey);
3092
+ yield* putMemberRecord({
3093
+ ...memberRecord ?? {
3094
+ id: identity.publicKey,
3095
+ addedAt: 0,
3096
+ addedInEpoch: EpochId("epoch-0")
3097
+ },
3098
+ removedAt: Date.now(),
3099
+ removedInEpoch: result.epoch.id
3100
+ });
3101
+ yield* Effect21.forEach(
3102
+ result.wrappedEvents,
3103
+ (wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(
3104
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3105
+ Effect21.ignore
3106
+ ),
3107
+ { discard: true }
3108
+ );
3109
+ yield* Ref5.set(closedRef, true);
3110
+ syncHandle.stopHealing();
3111
+ yield* Scope4.close(scope, Exit.void);
3112
+ yield* deleteIDBStorage(config.dbName);
3113
+ })
3114
+ ),
3053
3115
  rebuild: () => ensureOpen(
3054
3116
  rebuild(
3055
3117
  storage,
@@ -3187,6 +3249,9 @@ function validateConfig(config) {
3187
3249
  }
3188
3250
  });
3189
3251
  }
3252
+ function deleteDatabase(dbName) {
3253
+ return deleteIDBStorage(DatabaseName(dbName ?? "tablinum"));
3254
+ }
3190
3255
  function createTablinum(config) {
3191
3256
  return Effect22.gen(function* () {
3192
3257
  yield* validateConfig(config);
@@ -3259,6 +3324,7 @@ export {
3259
3324
  collection,
3260
3325
  createTablinum,
3261
3326
  decodeInvite,
3327
+ deleteDatabase,
3262
3328
  encodeInvite,
3263
3329
  field
3264
3330
  };