tablinum 0.6.3 → 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) {
@@ -401,7 +652,8 @@ function fieldDefToSchema(fd) {
401
652
  case "object": {
402
653
  const nested = {};
403
654
  for (const [k, v] of Object.entries(fd.fields)) {
404
- nested[k] = fieldDefToSchema(v);
655
+ const fieldSchema = fieldDefToSchema(v);
656
+ nested[k] = v.isOptional ? Schema3.optionalKey(fieldSchema) : fieldSchema;
405
657
  }
406
658
  base = Schema3.Struct(nested);
407
659
  break;
@@ -429,10 +681,10 @@ function buildStructSchema(def, options = {}) {
429
681
  function buildValidator(collectionName, def) {
430
682
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
431
683
  return (input) => decode(input).pipe(
432
- Effect4.map(
684
+ Effect5.map(
433
685
  (result) => result
434
686
  ),
435
- Effect4.mapError(
687
+ Effect5.mapError(
436
688
  (e) => new ValidationError({
437
689
  message: `Validation failed for collection "${collectionName}": ${e.message}`
438
690
  })
@@ -441,7 +693,7 @@ function buildValidator(collectionName, def) {
441
693
  }
442
694
  function buildPartialValidator(collectionName, def) {
443
695
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
444
- return (input) => Effect4.gen(function* () {
696
+ return (input) => Effect5.gen(function* () {
445
697
  if (typeof input !== "object" || input === null) {
446
698
  return yield* new ValidationError({
447
699
  message: `Validation failed for collection "${collectionName}": expected an object`
@@ -456,8 +708,8 @@ function buildPartialValidator(collectionName, def) {
456
708
  });
457
709
  }
458
710
  return yield* decode(record).pipe(
459
- Effect4.map((result) => result),
460
- Effect4.mapError(
711
+ Effect5.map((result) => result),
712
+ Effect5.mapError(
461
713
  (e) => new ValidationError({
462
714
  message: `Validation failed for collection "${collectionName}": ${e.message}`
463
715
  })
@@ -467,7 +719,7 @@ function buildPartialValidator(collectionName, def) {
467
719
  }
468
720
 
469
721
  // src/crud/collection-handle.ts
470
- import { Effect as Effect6, Option as Option3, References } from "effect";
722
+ import { Effect as Effect7, Option as Option3, References } from "effect";
471
723
 
472
724
  // src/utils/uuid.ts
473
725
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -500,12 +752,12 @@ function uuidv7() {
500
752
  }
501
753
 
502
754
  // src/crud/query-builder.ts
503
- 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";
504
756
  function emptyPlan() {
505
757
  return { filters: [] };
506
758
  }
507
759
  function executeQuery(ctx, plan) {
508
- return Effect5.gen(function* () {
760
+ return Effect6.gen(function* () {
509
761
  if (plan.fieldName) {
510
762
  const fieldDef = ctx.def.fields[plan.fieldName];
511
763
  if (!fieldDef) {
@@ -581,7 +833,7 @@ function watchQuery(ctx, plan) {
581
833
  const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
582
834
  Stream2.filter((event) => event.collection === ctx.collectionName),
583
835
  Stream2.mapEffect(
584
- () => Effect5.gen(function* () {
836
+ () => Effect6.gen(function* () {
585
837
  const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
586
838
  if (replaying) return void 0;
587
839
  return yield* query();
@@ -590,7 +842,7 @@ function watchQuery(ctx, plan) {
590
842
  Stream2.filter((result) => result !== void 0)
591
843
  );
592
844
  return Stream2.unwrap(
593
- Effect5.gen(function* () {
845
+ Effect6.gen(function* () {
594
846
  const initial = yield* query();
595
847
  return Stream2.concat(Stream2.make(initial), changes);
596
848
  })
@@ -616,11 +868,11 @@ function makeQueryBuilder(ctx, plan) {
616
868
  offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
617
869
  limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
618
870
  get: () => executeQuery(ctx, plan),
619
- first: () => Effect5.map(
871
+ first: () => Effect6.map(
620
872
  executeQuery(ctx, { ...plan, limit: 1 }),
621
873
  (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
622
874
  ),
623
- count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
875
+ count: () => Effect6.map(executeQuery(ctx, plan), (results) => results.length),
624
876
  watch: () => watchQuery(ctx, plan)
625
877
  };
626
878
  }
@@ -726,7 +978,7 @@ function replayState(recordId, events, stopAtId) {
726
978
  return state;
727
979
  }
728
980
  function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
729
- return Effect6.gen(function* () {
981
+ return Effect7.gen(function* () {
730
982
  const chronological = sortChronologically(allSorted);
731
983
  const state = replayState(recordId, chronological, target.id);
732
984
  if (state) {
@@ -735,7 +987,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
735
987
  });
736
988
  }
737
989
  function pruneEvents(storage, collection2, recordId, retention) {
738
- return Effect6.gen(function* () {
990
+ return Effect7.gen(function* () {
739
991
  const events = yield* storage.getEventsByRecord(collection2, recordId);
740
992
  if (events.length <= retention) return;
741
993
  const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
@@ -756,8 +1008,8 @@ function mapRecord(record) {
756
1008
  }
757
1009
  function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
758
1010
  const collectionName = def.name;
759
- const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
760
- const commitEvent = (event) => Effect6.gen(function* () {
1011
+ const withLog = (effect) => Effect7.provideService(effect, References.MinimumLogLevel, logLevel);
1012
+ const commitEvent = (event) => Effect7.gen(function* () {
761
1013
  yield* storage.putEvent(event);
762
1014
  yield* applyEvent(storage, event);
763
1015
  if (onWrite) yield* onWrite(event);
@@ -769,7 +1021,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
769
1021
  });
770
1022
  const handle = {
771
1023
  add: (data) => withLog(
772
- Effect6.gen(function* () {
1024
+ Effect7.gen(function* () {
773
1025
  const id = uuidv7();
774
1026
  const fullRecord = { id, ...data };
775
1027
  yield* validator(fullRecord);
@@ -783,7 +1035,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
783
1035
  author: localAuthor
784
1036
  };
785
1037
  yield* commitEvent(event);
786
- yield* Effect6.logDebug("Record added", {
1038
+ yield* Effect7.logDebug("Record added", {
787
1039
  collection: collectionName,
788
1040
  recordId: id,
789
1041
  data: fullRecord
@@ -792,7 +1044,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
792
1044
  })
793
1045
  ),
794
1046
  update: (id, data) => withLog(
795
- Effect6.gen(function* () {
1047
+ Effect7.gen(function* () {
796
1048
  const existing = yield* storage.getRecord(collectionName, id);
797
1049
  if (!existing || existing._d) {
798
1050
  return yield* new NotFoundError({
@@ -815,7 +1067,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
815
1067
  author: localAuthor
816
1068
  };
817
1069
  yield* commitEvent(event);
818
- yield* Effect6.logDebug("Record updated", {
1070
+ yield* Effect7.logDebug("Record updated", {
819
1071
  collection: collectionName,
820
1072
  recordId: id,
821
1073
  data: diff
@@ -824,7 +1076,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
824
1076
  })
825
1077
  ),
826
1078
  delete: (id) => withLog(
827
- Effect6.gen(function* () {
1079
+ Effect7.gen(function* () {
828
1080
  const existing = yield* storage.getRecord(collectionName, id);
829
1081
  if (!existing || existing._d) {
830
1082
  return yield* new NotFoundError({
@@ -842,11 +1094,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
842
1094
  author: localAuthor
843
1095
  };
844
1096
  yield* commitEvent(event);
845
- yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
1097
+ yield* Effect7.logDebug("Record deleted", { collection: collectionName, recordId: id });
846
1098
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
847
1099
  })
848
1100
  ),
849
- undo: (id) => Effect6.gen(function* () {
1101
+ undo: (id) => Effect7.gen(function* () {
850
1102
  const existing = yield* storage.getRecord(collectionName, id);
851
1103
  if (!existing) {
852
1104
  return yield* new NotFoundError({ collection: collectionName, id });
@@ -871,7 +1123,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
871
1123
  yield* commitEvent(event);
872
1124
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
873
1125
  }),
874
- get: (id) => Effect6.gen(function* () {
1126
+ get: (id) => Effect7.gen(function* () {
875
1127
  const record = yield* storage.getRecord(collectionName, id);
876
1128
  if (!record || record._d) {
877
1129
  return yield* new NotFoundError({
@@ -881,11 +1133,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
881
1133
  }
882
1134
  return mapRecord(record);
883
1135
  }),
884
- first: () => Effect6.map(storage.getAllRecords(collectionName), (all) => {
1136
+ first: () => Effect7.map(storage.getAllRecords(collectionName), (all) => {
885
1137
  const found = all.find((r) => !r._d);
886
1138
  return found ? Option3.some(mapRecord(found)) : Option3.none();
887
1139
  }),
888
- 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),
889
1141
  watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
890
1142
  where: (fieldName) => createWhereClause(
891
1143
  storage,
@@ -908,12 +1160,12 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
908
1160
  }
909
1161
 
910
1162
  // src/sync/sync-service.ts
911
- 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";
912
1164
  import { unwrapEvent } from "nostr-tools/nip59";
913
1165
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
914
1166
 
915
1167
  // src/sync/negentropy.ts
916
- import { Effect as Effect7 } from "effect";
1168
+ import { Effect as Effect8 } from "effect";
917
1169
 
918
1170
  // src/vendor/negentropy.js
919
1171
  var PROTOCOL_VERSION = 97;
@@ -1400,14 +1652,14 @@ function itemCompare(a, b) {
1400
1652
  }
1401
1653
 
1402
1654
  // src/sync/negentropy.ts
1403
- import { hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
1655
+ import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1404
1656
  import { GiftWrap } from "nostr-tools/kinds";
1405
1657
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1406
- return Effect7.gen(function* () {
1658
+ return Effect8.gen(function* () {
1407
1659
  const allGiftWraps = yield* storage.getAllGiftWraps();
1408
1660
  const storageVector = new NegentropyStorageVector();
1409
1661
  for (const gw of allGiftWraps) {
1410
- storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1662
+ storageVector.insert(gw.createdAt, hexToBytes3(gw.id));
1411
1663
  }
1412
1664
  storageVector.seal();
1413
1665
  const neg = new Negentropy(storageVector, 0);
@@ -1418,7 +1670,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1418
1670
  const allHaveIds = [];
1419
1671
  const allNeedIds = [];
1420
1672
  const subId = `neg-${Date.now()}`;
1421
- const initialMsg = yield* Effect7.tryPromise({
1673
+ const initialMsg = yield* Effect8.tryPromise({
1422
1674
  try: () => neg.initiate(),
1423
1675
  catch: (e) => new SyncError({
1424
1676
  message: `Negentropy initiate failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1430,7 +1682,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1430
1682
  while (currentMsg !== null) {
1431
1683
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1432
1684
  if (response.msgHex === null) break;
1433
- const reconcileResult = yield* Effect7.tryPromise({
1685
+ const reconcileResult = yield* Effect8.tryPromise({
1434
1686
  try: () => neg.reconcile(response.msgHex),
1435
1687
  catch: (e) => new SyncError({
1436
1688
  message: `Negentropy reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1443,13 +1695,13 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1443
1695
  for (const id of needIds) allNeedIds.push(id);
1444
1696
  currentMsg = nextMsg;
1445
1697
  }
1446
- yield* Effect7.logDebug("Negentropy reconciliation complete", {
1698
+ yield* Effect8.logDebug("Negentropy reconciliation complete", {
1447
1699
  relay: relayUrl,
1448
1700
  have: allHaveIds.length,
1449
1701
  need: allNeedIds.length
1450
1702
  });
1451
1703
  return { haveIds: allHaveIds, needIds: allNeedIds };
1452
- }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1704
+ }).pipe(Effect8.withLogSpan("tablinum.negentropy"));
1453
1705
  }
1454
1706
 
1455
1707
  // src/db/key-rotation.ts
@@ -1543,52 +1795,52 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1543
1795
  kind: "create"
1544
1796
  });
1545
1797
  const forkHandled = (effect) => {
1546
- Effect8.runFork(
1798
+ Effect9.runFork(
1547
1799
  effect.pipe(
1548
- Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
1549
- Effect8.ignore,
1550
- Effect8.provide(logLayer),
1551
- Effect8.forkIn(scope)
1800
+ Effect9.tapError((e) => Effect9.sync(() => onSyncError?.(e))),
1801
+ Effect9.ignore,
1802
+ Effect9.provide(logLayer),
1803
+ Effect9.forkIn(scope)
1552
1804
  )
1553
1805
  );
1554
1806
  };
1555
1807
  let autoFlushActive = false;
1556
- const autoFlushEffect = Effect8.gen(function* () {
1808
+ const autoFlushEffect = Effect9.gen(function* () {
1557
1809
  const size = yield* publishQueue.size();
1558
1810
  if (size === 0) return;
1559
1811
  yield* syncStatus.set("syncing");
1560
1812
  yield* publishQueue.flush(relayUrls);
1561
1813
  const remaining = yield* publishQueue.size();
1562
- if (remaining > 0) yield* Effect8.fail("pending");
1814
+ if (remaining > 0) yield* Effect9.fail("pending");
1563
1815
  }).pipe(
1564
- Effect8.ensuring(syncStatus.set("idle")),
1565
- Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1566
- Effect8.ignore
1816
+ Effect9.ensuring(syncStatus.set("idle")),
1817
+ Effect9.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1818
+ Effect9.ignore
1567
1819
  );
1568
1820
  const scheduleAutoFlush = () => {
1569
1821
  if (autoFlushActive) return;
1570
1822
  autoFlushActive = true;
1571
1823
  forkHandled(
1572
1824
  autoFlushEffect.pipe(
1573
- Effect8.ensuring(
1574
- Effect8.sync(() => {
1825
+ Effect9.ensuring(
1826
+ Effect9.sync(() => {
1575
1827
  autoFlushActive = false;
1576
1828
  })
1577
1829
  )
1578
1830
  )
1579
1831
  );
1580
1832
  };
1581
- const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1833
+ const shouldRejectWrite = (authorPubkey) => Effect9.gen(function* () {
1582
1834
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1583
1835
  if (!memberRecord) return false;
1584
1836
  return !!memberRecord.removedAt;
1585
1837
  });
1586
1838
  const storeGiftWrapShell = (gw) => storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
1587
- const unwrapGiftWrap = (remoteGw) => Effect8.gen(function* () {
1839
+ const unwrapGiftWrap = (remoteGw) => Effect9.gen(function* () {
1588
1840
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1589
1841
  if (existing) return null;
1590
1842
  const rumor = yield* giftWrapHandle.unwrap(remoteGw).pipe(
1591
- Effect8.orElseSucceed(() => null)
1843
+ Effect9.orElseSucceed(() => null)
1592
1844
  );
1593
1845
  if (!rumor) {
1594
1846
  yield* storeGiftWrapShell(remoteGw);
@@ -1616,7 +1868,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1616
1868
  recordId: dTag.substring(colonIdx + 1)
1617
1869
  };
1618
1870
  });
1619
- const applyUnwrappedEvent = (uw) => Effect8.gen(function* () {
1871
+ const applyUnwrappedEvent = (uw) => Effect9.gen(function* () {
1620
1872
  const { giftWrap: remoteGw, rumor, collection: collectionName, recordId } = uw;
1621
1873
  const retention = knownCollections.get(collectionName);
1622
1874
  if (retention === void 0) {
@@ -1626,17 +1878,17 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1626
1878
  if (rumor.pubkey) {
1627
1879
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1628
1880
  if (reject) {
1629
- yield* Effect8.logWarning("Rejected write from removed member", {
1881
+ yield* Effect9.logWarning("Rejected write from removed member", {
1630
1882
  author: rumor.pubkey.slice(0, 12)
1631
1883
  });
1632
1884
  yield* storeGiftWrapShell(remoteGw);
1633
1885
  return null;
1634
1886
  }
1635
1887
  }
1636
- const parsed = yield* Effect8.try({
1888
+ const parsed = yield* Effect9.try({
1637
1889
  try: () => JSON.parse(rumor.content),
1638
1890
  catch: () => void 0
1639
- }).pipe(Effect8.orElseSucceed(() => void 0));
1891
+ }).pipe(Effect9.orElseSucceed(() => void 0));
1640
1892
  if (parsed === void 0) {
1641
1893
  yield* storeGiftWrapShell(remoteGw);
1642
1894
  return null;
@@ -1668,7 +1920,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1668
1920
  if (didApply && (kind === "u" || kind === "d")) {
1669
1921
  yield* pruneEvents(storage, collectionName, recordId, retention);
1670
1922
  }
1671
- yield* Effect8.logDebug("Processed gift wrap", {
1923
+ yield* Effect9.logDebug("Processed gift wrap", {
1672
1924
  collection: collectionName,
1673
1925
  recordId,
1674
1926
  kind,
@@ -1679,33 +1931,33 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1679
1931
  }
1680
1932
  return collectionName;
1681
1933
  });
1682
- const reconcileRelay = (url, pubKeys) => Effect8.gen(function* () {
1683
- yield* Effect8.logDebug("Syncing relay", { relay: url });
1934
+ const reconcileRelay = (url, pubKeys) => Effect9.gen(function* () {
1935
+ yield* Effect9.logDebug("Syncing relay", { relay: url });
1684
1936
  const { haveIds, needIds } = yield* reconcileWithRelay(
1685
1937
  storage,
1686
1938
  relay,
1687
1939
  url,
1688
1940
  Array.from(pubKeys)
1689
1941
  ).pipe(
1690
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1691
- Effect8.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1942
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1943
+ Effect9.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1692
1944
  );
1693
- yield* Effect8.logDebug("Relay reconciliation result", {
1945
+ yield* Effect9.logDebug("Relay reconciliation result", {
1694
1946
  relay: url,
1695
1947
  need: needIds.length,
1696
1948
  have: haveIds.length
1697
1949
  });
1698
1950
  const events = needIds.length > 0 ? yield* relay.fetchEvents(needIds, url).pipe(
1699
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1700
- Effect8.orElseSucceed(() => [])
1951
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1952
+ Effect9.orElseSucceed(() => [])
1701
1953
  ) : [];
1702
1954
  return {
1703
1955
  events,
1704
1956
  haveIds: haveIds.map((id) => ({ id, url }))
1705
1957
  };
1706
- }).pipe(Effect8.withLogSpan("tablinum.reconcileRelay"));
1707
- const syncAllRelays = (pubKeys, changedCollections) => Effect8.gen(function* () {
1708
- 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(
1709
1961
  relayUrls,
1710
1962
  (url) => reconcileRelay(url, pubKeys),
1711
1963
  { concurrency: "unbounded" }
@@ -1722,44 +1974,44 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1722
1974
  }
1723
1975
  const unwrapped = [];
1724
1976
  for (const gw of allGiftWraps) {
1725
- const result = yield* unwrapGiftWrap(gw).pipe(Effect8.orElseSucceed(() => null));
1977
+ const result = yield* unwrapGiftWrap(gw).pipe(Effect9.orElseSucceed(() => null));
1726
1978
  if (result) unwrapped.push(result);
1727
1979
  }
1728
1980
  unwrapped.sort((a, b) => a.rumor.created_at - b.rumor.created_at || (a.rumor.id < b.rumor.id ? -1 : 1));
1729
1981
  for (const event of unwrapped) {
1730
1982
  const collection2 = yield* applyUnwrappedEvent(event).pipe(
1731
- Effect8.orElseSucceed(() => null)
1983
+ Effect9.orElseSucceed(() => null)
1732
1984
  );
1733
1985
  if (collection2) changedCollections.add(collection2);
1734
1986
  }
1735
1987
  const allHaveIds = results.flatMap((r) => r.haveIds);
1736
- yield* Effect8.forEach(
1988
+ yield* Effect9.forEach(
1737
1989
  allHaveIds,
1738
- ({ id, url }) => Effect8.gen(function* () {
1990
+ ({ id, url }) => Effect9.gen(function* () {
1739
1991
  const gw = yield* storage.getGiftWrap(id);
1740
1992
  if (!gw?.event) return;
1741
1993
  yield* relay.publish(gw.event, [url]).pipe(
1742
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1743
- Effect8.ignore
1994
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1995
+ Effect9.ignore
1744
1996
  );
1745
1997
  }),
1746
1998
  { concurrency: "unbounded", discard: true }
1747
1999
  );
1748
- }).pipe(Effect8.withLogSpan("tablinum.syncAllRelays"));
1749
- const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
2000
+ }).pipe(Effect9.withLogSpan("tablinum.syncAllRelays"));
2001
+ const processGiftWrap = (remoteGw) => Effect9.gen(function* () {
1750
2002
  const uw = yield* unwrapGiftWrap(remoteGw);
1751
2003
  if (!uw) return null;
1752
2004
  return yield* applyUnwrappedEvent(uw);
1753
2005
  });
1754
- const processRealtimeGiftWrap = (remoteGw) => Effect8.gen(function* () {
1755
- 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));
1756
2008
  if (collection2) {
1757
2009
  yield* notifyCollectionUpdated(collection2);
1758
2010
  }
1759
2011
  });
1760
- const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1761
- const unwrapResult = yield* Effect8.result(
1762
- Effect8.try({
2012
+ const processRotationGiftWrap = (remoteGw) => Effect9.gen(function* () {
2013
+ const unwrapResult = yield* Effect9.result(
2014
+ Effect9.try({
1763
2015
  try: () => unwrapEvent(remoteGw, personalPrivateKey),
1764
2016
  catch: (e) => new CryptoError({
1765
2017
  message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1809,73 +2061,73 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1809
2061
  yield* handle.addEpochSubscription(epoch.publicKey);
1810
2062
  return true;
1811
2063
  });
1812
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
2064
+ const subscribeAcrossRelays = (filter, onEvent) => Effect9.forEach(
1813
2065
  relayUrls,
1814
- (url) => Effect8.gen(function* () {
2066
+ (url) => Effect9.gen(function* () {
1815
2067
  yield* relay.subscribe(filter, url, (event) => {
1816
2068
  forkHandled(onEvent(event));
1817
2069
  }).pipe(
1818
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1819
- Effect8.ignore
2070
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2071
+ Effect9.ignore
1820
2072
  );
1821
2073
  }),
1822
2074
  { concurrency: "unbounded", discard: true }
1823
2075
  );
1824
2076
  let healingActive = false;
1825
- const healingEffect = Effect8.gen(function* () {
2077
+ const healingEffect = Effect9.gen(function* () {
1826
2078
  if (!healingActive) return;
1827
2079
  const status = yield* syncStatus.get();
1828
2080
  if (status === "syncing") return;
1829
2081
  yield* syncStatus.set("syncing");
1830
- yield* Effect8.gen(function* () {
2082
+ yield* Effect9.gen(function* () {
1831
2083
  const pubKeys = getSubscriptionPubKeys();
1832
2084
  const changedCollections = /* @__PURE__ */ new Set();
1833
2085
  yield* syncAllRelays(pubKeys, changedCollections);
1834
2086
  if (changedCollections.size > 0) {
1835
2087
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1836
2088
  }
1837
- }).pipe(Effect8.ensuring(syncStatus.set("idle")));
1838
- }).pipe(Effect8.ignore);
2089
+ }).pipe(Effect9.ensuring(syncStatus.set("idle")));
2090
+ }).pipe(Effect9.ignore);
1839
2091
  const handle = {
1840
- sync: () => Effect8.gen(function* () {
1841
- yield* Effect8.logInfo("Sync started");
2092
+ sync: () => Effect9.gen(function* () {
2093
+ yield* Effect9.logInfo("Sync started");
1842
2094
  yield* syncStatus.set("syncing");
1843
2095
  yield* Ref3.set(watchCtx.replayingRef, true);
1844
2096
  const changedCollections = /* @__PURE__ */ new Set();
1845
- yield* Effect8.gen(function* () {
2097
+ yield* Effect9.gen(function* () {
1846
2098
  const pubKeys = getSubscriptionPubKeys();
1847
2099
  yield* syncAllRelays(pubKeys, changedCollections);
1848
- yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
2100
+ yield* publishQueue.flush(relayUrls).pipe(Effect9.ignore);
1849
2101
  }).pipe(
1850
- Effect8.ensuring(
1851
- Effect8.gen(function* () {
2102
+ Effect9.ensuring(
2103
+ Effect9.gen(function* () {
1852
2104
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1853
2105
  yield* syncStatus.set("idle");
1854
2106
  })
1855
2107
  )
1856
2108
  );
1857
- yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1858
- }).pipe(Effect8.withLogSpan("tablinum.sync")),
1859
- 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* () {
1860
2112
  if (!giftWrap.event) return;
1861
2113
  yield* relay.publish(giftWrap.event, relayUrls).pipe(
1862
- Effect8.tapError(
2114
+ Effect9.tapError(
1863
2115
  () => storage.putGiftWrap(giftWrap).pipe(
1864
- Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
1865
- Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
2116
+ Effect9.andThen(publishQueue.enqueue(giftWrap.id)),
2117
+ Effect9.andThen(Effect9.sync(() => scheduleAutoFlush()))
1866
2118
  )
1867
2119
  ),
1868
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1869
- Effect8.ignore
2120
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2121
+ Effect9.ignore
1870
2122
  );
1871
2123
  }),
1872
- startSubscription: () => Effect8.gen(function* () {
2124
+ startSubscription: () => Effect9.gen(function* () {
1873
2125
  const pubKeys = getSubscriptionPubKeys();
1874
2126
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1875
2127
  if (!pubKeys.includes(personalPublicKey)) {
1876
2128
  yield* subscribeAcrossRelays(
1877
2129
  { kinds: [GiftWrap2], "#p": [personalPublicKey] },
1878
- (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
2130
+ (event) => Effect9.result(processRotationGiftWrap(event)).pipe(Effect9.asVoid)
1879
2131
  );
1880
2132
  }
1881
2133
  }),
@@ -1884,10 +2136,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1884
2136
  if (healingActive) return;
1885
2137
  healingActive = true;
1886
2138
  forkHandled(
1887
- Effect8.sleep(Duration.minutes(5)).pipe(
1888
- Effect8.andThen(healingEffect),
1889
- Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
1890
- 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(() => {
1891
2143
  healingActive = false;
1892
2144
  }))
1893
2145
  )
@@ -1899,8 +2151,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1899
2151
  };
1900
2152
  forkHandled(
1901
2153
  publishQueue.size().pipe(
1902
- Effect8.flatMap(
1903
- (size) => Effect8.sync(() => {
2154
+ Effect9.flatMap(
2155
+ (size) => Effect9.sync(() => {
1904
2156
  if (size > 0) scheduleAutoFlush();
1905
2157
  })
1906
2158
  )
@@ -1910,7 +2162,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1910
2162
  }
1911
2163
 
1912
2164
  // src/db/members.ts
1913
- 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";
1914
2166
  var optionalString = {
1915
2167
  _tag: "FieldDef",
1916
2168
  kind: "string",
@@ -1959,15 +2211,15 @@ var AuthorProfileSchema = Schema5.Struct({
1959
2211
  });
1960
2212
  var decodeAuthorProfile = Schema5.decodeUnknownEffect(Schema5.fromJsonString(AuthorProfileSchema));
1961
2213
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
1962
- return Effect9.gen(function* () {
2214
+ return Effect10.gen(function* () {
1963
2215
  for (const url of relayUrls) {
1964
- const result = yield* Effect9.result(
2216
+ const result = yield* Effect10.result(
1965
2217
  relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
1966
2218
  );
1967
2219
  if (result._tag === "Success" && result.success.length > 0) {
1968
2220
  return yield* decodeAuthorProfile(result.success[0].content).pipe(
1969
- Effect9.map(Option6.some),
1970
- Effect9.orElseSucceed(() => Option6.none())
2221
+ Effect10.map(Option6.some),
2222
+ Effect10.orElseSucceed(() => Option6.none())
1971
2223
  );
1972
2224
  }
1973
2225
  }
@@ -2017,15 +2269,15 @@ var SyncStatus = class extends ServiceMap9.Service()(
2017
2269
  };
2018
2270
 
2019
2271
  // src/layers/IdentityLive.ts
2020
- import { Effect as Effect11, Layer as Layer2 } from "effect";
2021
- 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";
2022
2274
 
2023
2275
  // src/db/identity.ts
2024
- import { Effect as Effect10 } from "effect";
2276
+ import { Effect as Effect11 } from "effect";
2025
2277
  import { getPublicKey as getPublicKey2 } from "nostr-tools/pure";
2026
- import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
2278
+ import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2027
2279
  function createIdentity(suppliedKey) {
2028
- return Effect10.gen(function* () {
2280
+ return Effect11.gen(function* () {
2029
2281
  let privateKey;
2030
2282
  if (suppliedKey) {
2031
2283
  if (suppliedKey.length !== 32) {
@@ -2038,8 +2290,8 @@ function createIdentity(suppliedKey) {
2038
2290
  privateKey = new Uint8Array(32);
2039
2291
  crypto.getRandomValues(privateKey);
2040
2292
  }
2041
- const privateKeyHex = bytesToHex2(privateKey);
2042
- const publicKey = yield* Effect10.try({
2293
+ const privateKeyHex = bytesToHex3(privateKey);
2294
+ const publicKey = yield* Effect11.try({
2043
2295
  try: () => getPublicKey2(privateKey),
2044
2296
  catch: (e) => new CryptoError({
2045
2297
  message: `Failed to derive public key: ${e instanceof Error ? e.message : String(e)}`,
@@ -2057,14 +2309,14 @@ function createIdentity(suppliedKey) {
2057
2309
  // src/layers/IdentityLive.ts
2058
2310
  var IdentityLive = Layer2.effect(
2059
2311
  Identity,
2060
- Effect11.gen(function* () {
2312
+ Effect12.gen(function* () {
2061
2313
  const config = yield* Config;
2062
2314
  const storage = yield* Storage;
2063
2315
  const idbKey = yield* storage.getMeta("identity_key");
2064
- 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);
2065
2317
  const identity = yield* createIdentity(resolvedKey);
2066
2318
  yield* storage.putMeta("identity_key", identity.exportKey());
2067
- yield* Effect11.logInfo("Identity loaded", {
2319
+ yield* Effect12.logInfo("Identity loaded", {
2068
2320
  publicKey: identity.publicKey.slice(0, 12) + "...",
2069
2321
  source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
2070
2322
  });
@@ -2073,12 +2325,12 @@ var IdentityLive = Layer2.effect(
2073
2325
  );
2074
2326
 
2075
2327
  // src/layers/EpochStoreLive.ts
2076
- 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";
2077
2329
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
2078
- import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2330
+ import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
2079
2331
  var EpochStoreLive = Layer3.effect(
2080
2332
  EpochStore,
2081
- Effect12.gen(function* () {
2333
+ Effect13.gen(function* () {
2082
2334
  const config = yield* Config;
2083
2335
  const identity = yield* Identity;
2084
2336
  const storage = yield* Storage;
@@ -2097,7 +2349,7 @@ var EpochStoreLive = Layer3.effect(
2097
2349
  return existing !== void 0 && existing.privateKey === ek.key;
2098
2350
  });
2099
2351
  if (configIsSubset) {
2100
- yield* Effect12.logInfo("Epoch store loaded", {
2352
+ yield* Effect13.logInfo("Epoch store loaded", {
2101
2353
  source: "storage",
2102
2354
  epochs: idbStore.epochs.size
2103
2355
  });
@@ -2106,271 +2358,28 @@ var EpochStoreLive = Layer3.effect(
2106
2358
  }
2107
2359
  const store2 = createEpochStoreFromInputs(config.epochKeys);
2108
2360
  yield* storage.putMeta("epochs", stringifyEpochStore(store2));
2109
- 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 });
2110
2362
  return store2;
2111
2363
  }
2112
2364
  if (idbStore) {
2113
- yield* Effect12.logInfo("Epoch store loaded", {
2365
+ yield* Effect13.logInfo("Epoch store loaded", {
2114
2366
  source: "storage",
2115
2367
  epochs: idbStore.epochs.size
2116
2368
  });
2117
2369
  return idbStore;
2118
2370
  }
2119
2371
  const store = createEpochStoreFromInputs(
2120
- [{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
2372
+ [{ epochId: EpochId("epoch-0"), key: bytesToHex4(generateSecretKey2()) }],
2121
2373
  { createdBy: identity.publicKey }
2122
2374
  );
2123
2375
  yield* storage.putMeta("epochs", stringifyEpochStore(store));
2124
- 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 });
2125
2377
  return store;
2126
2378
  })
2127
2379
  );
2128
2380
 
2129
2381
  // src/layers/StorageLive.ts
2130
2382
  import { Effect as Effect14, Layer as Layer4 } from "effect";
2131
-
2132
- // src/storage/idb.ts
2133
- import { Effect as Effect13 } from "effect";
2134
- import { openDB } from "idb";
2135
-
2136
- // src/sync/compact-event.ts
2137
- import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2138
- var VERSION = 1;
2139
- var HEADER_SIZE = 133;
2140
- function base64ToBytes(base64) {
2141
- const binary = atob(base64);
2142
- const bytes = new Uint8Array(binary.length);
2143
- for (let i = 0; i < binary.length; i++) {
2144
- bytes[i] = binary.charCodeAt(i);
2145
- }
2146
- return bytes;
2147
- }
2148
- function bytesToBase64(bytes) {
2149
- let binary = "";
2150
- for (let i = 0; i < bytes.length; i++) {
2151
- binary += String.fromCharCode(bytes[i]);
2152
- }
2153
- return btoa(binary);
2154
- }
2155
- function packEvent(event) {
2156
- const pubkey = hexToBytes4(event.pubkey);
2157
- const sig = hexToBytes4(event.sig);
2158
- const recipientTag = event.tags.find((t) => t[0] === "p");
2159
- if (!recipientTag) throw new Error("Gift wrap missing #p tag");
2160
- if (event.tags.some((t) => t[0] !== "p")) {
2161
- throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
2162
- }
2163
- const recipient = hexToBytes4(recipientTag[1]);
2164
- const createdAtBuf = new Uint8Array(4);
2165
- new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
2166
- const content = base64ToBytes(event.content);
2167
- const result = new Uint8Array(HEADER_SIZE + content.length);
2168
- result[0] = VERSION;
2169
- result.set(pubkey, 1);
2170
- result.set(sig, 33);
2171
- result.set(recipient, 97);
2172
- result.set(createdAtBuf, 129);
2173
- result.set(content, HEADER_SIZE);
2174
- return result;
2175
- }
2176
- function unpackEvent(id, compact) {
2177
- const version = compact[0];
2178
- if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
2179
- const pubkey = bytesToHex4(compact.slice(1, 33));
2180
- const sig = bytesToHex4(compact.slice(33, 97));
2181
- const recipient = bytesToHex4(compact.slice(97, 129));
2182
- const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
2183
- const createdAt = dv.getUint32(0, false);
2184
- const content = bytesToBase64(compact.slice(HEADER_SIZE));
2185
- return {
2186
- id,
2187
- pubkey,
2188
- sig,
2189
- created_at: createdAt,
2190
- kind: 1059,
2191
- tags: [["p", recipient]],
2192
- content
2193
- };
2194
- }
2195
-
2196
- // src/storage/idb.ts
2197
- var DB_NAME = "tablinum";
2198
- function storeName(collection2) {
2199
- return `col_${collection2}`;
2200
- }
2201
- function computeSchemaSig(schema) {
2202
- return Object.entries(schema).sort(([a], [b]) => a.localeCompare(b)).map(([name, def]) => {
2203
- const indices = [...def.indices ?? []].sort().join(",");
2204
- return `${name}:${indices}`;
2205
- }).join("|");
2206
- }
2207
- function wrap(label, fn) {
2208
- return Effect13.tryPromise({
2209
- try: fn,
2210
- catch: (e) => new StorageError({
2211
- message: `IndexedDB ${label} failed: ${e instanceof Error ? e.message : String(e)}`,
2212
- cause: e
2213
- })
2214
- });
2215
- }
2216
- function upgradeSchema(database, schema, tx) {
2217
- if (!database.objectStoreNames.contains("_meta")) {
2218
- database.createObjectStore("_meta");
2219
- }
2220
- if (!database.objectStoreNames.contains("events")) {
2221
- const events = database.createObjectStore("events", { keyPath: "id" });
2222
- events.createIndex("by-record", ["collection", "recordId"]);
2223
- }
2224
- if (!database.objectStoreNames.contains("giftwraps")) {
2225
- database.createObjectStore("giftwraps", { keyPath: "id" });
2226
- }
2227
- const expectedStores = /* @__PURE__ */ new Set();
2228
- for (const [, def] of Object.entries(schema)) {
2229
- const sn = storeName(def.name);
2230
- expectedStores.add(sn);
2231
- if (!database.objectStoreNames.contains(sn)) {
2232
- const store = database.createObjectStore(sn, { keyPath: "id" });
2233
- for (const idx of def.indices ?? []) {
2234
- store.createIndex(idx, idx);
2235
- }
2236
- } else {
2237
- const store = tx.objectStore(sn);
2238
- const existingIndices = new Set(Array.from(store.indexNames));
2239
- const wantedIndices = new Set(def.indices ?? []);
2240
- for (const idx of existingIndices) {
2241
- if (!wantedIndices.has(idx)) store.deleteIndex(idx);
2242
- }
2243
- for (const idx of wantedIndices) {
2244
- if (!existingIndices.has(idx)) store.createIndex(idx, idx);
2245
- }
2246
- }
2247
- }
2248
- for (const existing of Array.from(database.objectStoreNames)) {
2249
- if (existing.startsWith("col_") && !expectedStores.has(existing)) {
2250
- database.deleteObjectStore(existing);
2251
- }
2252
- }
2253
- tx.objectStore("_meta").put(computeSchemaSig(schema), "schema_sig");
2254
- }
2255
- function openIDBStorage(dbName, schema) {
2256
- return Effect13.gen(function* () {
2257
- const name = dbName ?? DB_NAME;
2258
- const schemaSig = computeSchemaSig(schema);
2259
- if (typeof indexedDB === "undefined") {
2260
- return yield* Effect13.fail(
2261
- new StorageError({
2262
- message: "IndexedDB is not available in this environment"
2263
- })
2264
- );
2265
- }
2266
- const probeDb = yield* Effect13.tryPromise({
2267
- try: () => openDB(name),
2268
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2269
- });
2270
- const currentVersion = probeDb.version;
2271
- let needsUpgrade = true;
2272
- if (probeDb.objectStoreNames.contains("_meta")) {
2273
- const storedSig = yield* Effect13.tryPromise({
2274
- try: () => probeDb.get("_meta", "schema_sig"),
2275
- catch: () => new StorageError({ message: "Failed to read schema meta" })
2276
- }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
2277
- needsUpgrade = storedSig !== schemaSig;
2278
- }
2279
- probeDb.close();
2280
- const db = needsUpgrade ? yield* Effect13.tryPromise({
2281
- try: () => openDB(name, currentVersion + 1, {
2282
- upgrade(database, _oldVersion, _newVersion, transaction) {
2283
- upgradeSchema(database, schema, transaction);
2284
- }
2285
- }),
2286
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2287
- }) : yield* Effect13.tryPromise({
2288
- try: () => openDB(name),
2289
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2290
- });
2291
- yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2292
- const handle = {
2293
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2294
- getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2295
- getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2296
- countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
2297
- clearRecords: (collection2) => wrap("clearRecords", () => db.clear(storeName(collection2))),
2298
- getByIndex: (collection2, indexName, value) => wrap("getByIndex", () => db.getAllFromIndex(storeName(collection2), indexName, value)),
2299
- getByIndexRange: (collection2, indexName, range) => wrap("getByIndexRange", () => db.getAllFromIndex(storeName(collection2), indexName, range)),
2300
- getAllSorted: (collection2, indexName, direction) => wrap("getAllSorted", async () => {
2301
- const sn = storeName(collection2);
2302
- const tx = db.transaction(sn, "readonly");
2303
- const store = tx.objectStore(sn);
2304
- const index = store.index(indexName);
2305
- const results = [];
2306
- let cursor = await index.openCursor(null, direction ?? "next");
2307
- while (cursor) {
2308
- results.push(cursor.value);
2309
- cursor = await cursor.continue();
2310
- }
2311
- return results;
2312
- }),
2313
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2314
- getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2315
- getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2316
- getEventsByRecord: (collection2, recordId) => wrap(
2317
- "getEventsByRecord",
2318
- () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
2319
- ),
2320
- putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
2321
- if (gw.event) {
2322
- const compact = packEvent(gw.event);
2323
- await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
2324
- } else {
2325
- await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
2326
- }
2327
- }),
2328
- getGiftWrap: (id) => wrap("getGiftWrap", async () => {
2329
- const raw = await db.get("giftwraps", id);
2330
- if (!raw) return void 0;
2331
- if (raw.compact) {
2332
- return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
2333
- }
2334
- if (raw.event) {
2335
- const compact = packEvent(raw.event);
2336
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2337
- return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
2338
- }
2339
- return { id: raw.id, createdAt: raw.createdAt };
2340
- }),
2341
- getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
2342
- const raws = await db.getAll("giftwraps");
2343
- const results = [];
2344
- for (const raw of raws) {
2345
- if (raw.compact) {
2346
- results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
2347
- } else if (raw.event) {
2348
- const compact = packEvent(raw.event);
2349
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2350
- results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
2351
- } else {
2352
- results.push({ id: raw.id, createdAt: raw.createdAt });
2353
- }
2354
- }
2355
- return results;
2356
- }),
2357
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
2358
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
2359
- stripEventData: (id) => wrap("stripEventData", async () => {
2360
- const existing = await db.get("events", id);
2361
- if (existing) {
2362
- await db.put("events", { ...existing, data: null });
2363
- }
2364
- }),
2365
- getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2366
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2367
- close: () => Effect13.sync(() => db.close())
2368
- };
2369
- return handle;
2370
- });
2371
- }
2372
-
2373
- // src/layers/StorageLive.ts
2374
2383
  var StorageLive = Layer4.effect(
2375
2384
  Storage,
2376
2385
  Effect14.gen(function* () {
@@ -3049,6 +3058,60 @@ var TablinumLive = Layer9.effect(
3049
3058
  yield* Scope4.close(scope, Exit.void);
3050
3059
  })
3051
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
+ ),
3052
3115
  rebuild: () => ensureOpen(
3053
3116
  rebuild(
3054
3117
  storage,
@@ -3186,6 +3249,9 @@ function validateConfig(config) {
3186
3249
  }
3187
3250
  });
3188
3251
  }
3252
+ function deleteDatabase(dbName) {
3253
+ return deleteIDBStorage(DatabaseName(dbName ?? "tablinum"));
3254
+ }
3189
3255
  function createTablinum(config) {
3190
3256
  return Effect22.gen(function* () {
3191
3257
  yield* validateConfig(config);
@@ -3258,6 +3324,7 @@ export {
3258
3324
  collection,
3259
3325
  createTablinum,
3260
3326
  decodeInvite,
3327
+ deleteDatabase,
3261
3328
  encodeInvite,
3262
3329
  field
3263
3330
  };