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.
@@ -230,9 +230,6 @@ var NotFoundError = class extends Data.TaggedError("NotFoundError") {
230
230
  var ClosedError = class extends Data.TaggedError("ClosedError") {
231
231
  };
232
232
 
233
- // src/svelte/tablinum.svelte.ts
234
- import { Effect as Effect25, Exit as Exit2, References as References6, Scope as Scope6 } from "effect";
235
-
236
233
  // src/db/create-tablinum.ts
237
234
  import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
238
235
 
@@ -261,6 +258,257 @@ function resolveRuntimeConfig(source) {
261
258
  );
262
259
  }
263
260
 
261
+ // src/storage/idb.ts
262
+ import { Effect as Effect2 } from "effect";
263
+ import { openDB, deleteDB } from "idb";
264
+
265
+ // src/sync/compact-event.ts
266
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
267
+ var VERSION = 1;
268
+ var HEADER_SIZE = 133;
269
+ function base64ToBytes(base64) {
270
+ const binary = atob(base64);
271
+ const bytes = new Uint8Array(binary.length);
272
+ for (let i = 0; i < binary.length; i++) {
273
+ bytes[i] = binary.charCodeAt(i);
274
+ }
275
+ return bytes;
276
+ }
277
+ function bytesToBase64(bytes) {
278
+ let binary = "";
279
+ for (let i = 0; i < bytes.length; i++) {
280
+ binary += String.fromCharCode(bytes[i]);
281
+ }
282
+ return btoa(binary);
283
+ }
284
+ function packEvent(event) {
285
+ const pubkey = hexToBytes2(event.pubkey);
286
+ const sig = hexToBytes2(event.sig);
287
+ const recipientTag = event.tags.find((t) => t[0] === "p");
288
+ if (!recipientTag) throw new Error("Gift wrap missing #p tag");
289
+ if (event.tags.some((t) => t[0] !== "p")) {
290
+ throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
291
+ }
292
+ const recipient = hexToBytes2(recipientTag[1]);
293
+ const createdAtBuf = new Uint8Array(4);
294
+ new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
295
+ const content = base64ToBytes(event.content);
296
+ const result = new Uint8Array(HEADER_SIZE + content.length);
297
+ result[0] = VERSION;
298
+ result.set(pubkey, 1);
299
+ result.set(sig, 33);
300
+ result.set(recipient, 97);
301
+ result.set(createdAtBuf, 129);
302
+ result.set(content, HEADER_SIZE);
303
+ return result;
304
+ }
305
+ function unpackEvent(id, compact) {
306
+ const version = compact[0];
307
+ if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
308
+ const pubkey = bytesToHex2(compact.slice(1, 33));
309
+ const sig = bytesToHex2(compact.slice(33, 97));
310
+ const recipient = bytesToHex2(compact.slice(97, 129));
311
+ const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
312
+ const createdAt = dv.getUint32(0, false);
313
+ const content = bytesToBase64(compact.slice(HEADER_SIZE));
314
+ return {
315
+ id,
316
+ pubkey,
317
+ sig,
318
+ created_at: createdAt,
319
+ kind: 1059,
320
+ tags: [["p", recipient]],
321
+ content
322
+ };
323
+ }
324
+
325
+ // src/storage/idb.ts
326
+ var DB_NAME = "tablinum";
327
+ function storeName(collection2) {
328
+ return `col_${collection2}`;
329
+ }
330
+ function computeSchemaSig(schema) {
331
+ return Object.entries(schema).sort(([a], [b]) => a.localeCompare(b)).map(([name, def]) => {
332
+ const indices = [...def.indices ?? []].sort().join(",");
333
+ return `${name}:${indices}`;
334
+ }).join("|");
335
+ }
336
+ function wrap(label, fn) {
337
+ return Effect2.tryPromise({
338
+ try: fn,
339
+ catch: (e) => new StorageError({
340
+ message: `IndexedDB ${label} failed: ${e instanceof Error ? e.message : String(e)}`,
341
+ cause: e
342
+ })
343
+ });
344
+ }
345
+ function upgradeSchema(database, schema, tx) {
346
+ if (!database.objectStoreNames.contains("_meta")) {
347
+ database.createObjectStore("_meta");
348
+ }
349
+ if (!database.objectStoreNames.contains("events")) {
350
+ const events = database.createObjectStore("events", { keyPath: "id" });
351
+ events.createIndex("by-record", ["collection", "recordId"]);
352
+ }
353
+ if (!database.objectStoreNames.contains("giftwraps")) {
354
+ database.createObjectStore("giftwraps", { keyPath: "id" });
355
+ }
356
+ const expectedStores = /* @__PURE__ */ new Set();
357
+ for (const [, def] of Object.entries(schema)) {
358
+ const sn = storeName(def.name);
359
+ expectedStores.add(sn);
360
+ if (!database.objectStoreNames.contains(sn)) {
361
+ const store = database.createObjectStore(sn, { keyPath: "id" });
362
+ for (const idx of def.indices ?? []) {
363
+ store.createIndex(idx, idx);
364
+ }
365
+ } else {
366
+ const store = tx.objectStore(sn);
367
+ const existingIndices = new Set(Array.from(store.indexNames));
368
+ const wantedIndices = new Set(def.indices ?? []);
369
+ for (const idx of existingIndices) {
370
+ if (!wantedIndices.has(idx)) store.deleteIndex(idx);
371
+ }
372
+ for (const idx of wantedIndices) {
373
+ if (!existingIndices.has(idx)) store.createIndex(idx, idx);
374
+ }
375
+ }
376
+ }
377
+ for (const existing of Array.from(database.objectStoreNames)) {
378
+ if (existing.startsWith("col_") && !expectedStores.has(existing)) {
379
+ database.deleteObjectStore(existing);
380
+ }
381
+ }
382
+ tx.objectStore("_meta").put(computeSchemaSig(schema), "schema_sig");
383
+ }
384
+ function deleteIDBStorage(dbName) {
385
+ if (typeof indexedDB === "undefined") {
386
+ return Effect2.fail(
387
+ new StorageError({
388
+ message: "IndexedDB is not available in this environment"
389
+ })
390
+ );
391
+ }
392
+ return wrap("deleteDatabase", () => deleteDB(dbName));
393
+ }
394
+ function openIDBStorage(dbName, schema) {
395
+ return Effect2.gen(function* () {
396
+ const name = dbName ?? DB_NAME;
397
+ const schemaSig = computeSchemaSig(schema);
398
+ if (typeof indexedDB === "undefined") {
399
+ return yield* Effect2.fail(
400
+ new StorageError({
401
+ message: "IndexedDB is not available in this environment"
402
+ })
403
+ );
404
+ }
405
+ const probeDb = yield* Effect2.tryPromise({
406
+ try: () => openDB(name),
407
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
408
+ });
409
+ const currentVersion = probeDb.version;
410
+ let needsUpgrade = true;
411
+ if (probeDb.objectStoreNames.contains("_meta")) {
412
+ const storedSig = yield* Effect2.tryPromise({
413
+ try: () => probeDb.get("_meta", "schema_sig"),
414
+ catch: () => new StorageError({ message: "Failed to read schema meta" })
415
+ }).pipe(Effect2.catch(() => Effect2.succeed(void 0)));
416
+ needsUpgrade = storedSig !== schemaSig;
417
+ }
418
+ probeDb.close();
419
+ const db = needsUpgrade ? yield* Effect2.tryPromise({
420
+ try: () => openDB(name, currentVersion + 1, {
421
+ upgrade(database, _oldVersion, _newVersion, transaction) {
422
+ upgradeSchema(database, schema, transaction);
423
+ }
424
+ }),
425
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
426
+ }) : yield* Effect2.tryPromise({
427
+ try: () => openDB(name),
428
+ catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
429
+ });
430
+ yield* Effect2.addFinalizer(() => Effect2.sync(() => db.close()));
431
+ const handle = {
432
+ putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
433
+ getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
434
+ getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
435
+ countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
436
+ clearRecords: (collection2) => wrap("clearRecords", () => db.clear(storeName(collection2))),
437
+ getByIndex: (collection2, indexName, value) => wrap("getByIndex", () => db.getAllFromIndex(storeName(collection2), indexName, value)),
438
+ getByIndexRange: (collection2, indexName, range) => wrap("getByIndexRange", () => db.getAllFromIndex(storeName(collection2), indexName, range)),
439
+ getAllSorted: (collection2, indexName, direction) => wrap("getAllSorted", async () => {
440
+ const sn = storeName(collection2);
441
+ const tx = db.transaction(sn, "readonly");
442
+ const store = tx.objectStore(sn);
443
+ const index = store.index(indexName);
444
+ const results = [];
445
+ let cursor = await index.openCursor(null, direction ?? "next");
446
+ while (cursor) {
447
+ results.push(cursor.value);
448
+ cursor = await cursor.continue();
449
+ }
450
+ return results;
451
+ }),
452
+ putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
453
+ getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
454
+ getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
455
+ getEventsByRecord: (collection2, recordId) => wrap(
456
+ "getEventsByRecord",
457
+ () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
458
+ ),
459
+ putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
460
+ if (gw.event) {
461
+ const compact = packEvent(gw.event);
462
+ await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
463
+ } else {
464
+ await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
465
+ }
466
+ }),
467
+ getGiftWrap: (id) => wrap("getGiftWrap", async () => {
468
+ const raw = await db.get("giftwraps", id);
469
+ if (!raw) return void 0;
470
+ if (raw.compact) {
471
+ return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
472
+ }
473
+ if (raw.event) {
474
+ const compact = packEvent(raw.event);
475
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
476
+ return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
477
+ }
478
+ return { id: raw.id, createdAt: raw.createdAt };
479
+ }),
480
+ getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
481
+ const raws = await db.getAll("giftwraps");
482
+ const results = [];
483
+ for (const raw of raws) {
484
+ if (raw.compact) {
485
+ results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
486
+ } else if (raw.event) {
487
+ const compact = packEvent(raw.event);
488
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
489
+ results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
490
+ } else {
491
+ results.push({ id: raw.id, createdAt: raw.createdAt });
492
+ }
493
+ }
494
+ return results;
495
+ }),
496
+ deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
497
+ deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
498
+ stripEventData: (id) => wrap("stripEventData", async () => {
499
+ const existing = await db.get("events", id);
500
+ if (existing) {
501
+ await db.put("events", { ...existing, data: null });
502
+ }
503
+ }),
504
+ getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
505
+ putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
506
+ close: () => Effect2.sync(() => db.close())
507
+ };
508
+ return handle;
509
+ });
510
+ }
511
+
264
512
  // src/services/Config.ts
265
513
  import { ServiceMap } from "effect";
266
514
  var Config = class extends ServiceMap.Service()("tablinum/Config") {
@@ -277,16 +525,16 @@ var Tablinum = class extends ServiceMap2.Service()(
277
525
  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";
278
526
 
279
527
  // src/crud/watch.ts
280
- import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
528
+ import { Effect as Effect3, PubSub, Ref, Stream } from "effect";
281
529
  function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
282
- const query = () => Effect2.map(storage.getAllRecords(collectionName), (all) => {
530
+ const query = () => Effect3.map(storage.getAllRecords(collectionName), (all) => {
283
531
  const filtered = all.filter((r) => !r._d && (filter ? filter(r) : true));
284
532
  return mapRecord2 ? filtered.map(mapRecord2) : filtered;
285
533
  });
286
534
  const changes = Stream.fromPubSub(ctx.pubsub).pipe(
287
535
  Stream.filter((event) => event.collection === collectionName),
288
536
  Stream.mapEffect(
289
- () => Effect2.gen(function* () {
537
+ () => Effect3.gen(function* () {
290
538
  const replaying = yield* Ref.get(ctx.replayingRef);
291
539
  if (replaying) return void 0;
292
540
  return yield* query();
@@ -295,18 +543,18 @@ function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
295
543
  Stream.filter((result) => result !== void 0)
296
544
  );
297
545
  return Stream.unwrap(
298
- Effect2.gen(function* () {
299
- yield* Effect2.sleep(0);
546
+ Effect3.gen(function* () {
547
+ yield* Effect3.sleep(0);
300
548
  const initial = yield* query();
301
549
  return Stream.concat(Stream.make(initial), changes);
302
550
  })
303
551
  );
304
552
  }
305
553
  function notifyChange(ctx, event) {
306
- return PubSub.publish(ctx.pubsub, event).pipe(Effect2.asVoid);
554
+ return PubSub.publish(ctx.pubsub, event).pipe(Effect3.asVoid);
307
555
  }
308
556
  function notifyReplayComplete(ctx, collections) {
309
- return Effect2.gen(function* () {
557
+ return Effect3.gen(function* () {
310
558
  yield* Ref.set(ctx.replayingRef, false);
311
559
  for (const collection2 of collections) {
312
560
  yield* notifyChange(ctx, {
@@ -319,7 +567,7 @@ function notifyReplayComplete(ctx, collections) {
319
567
  }
320
568
 
321
569
  // src/storage/records-store.ts
322
- import { Effect as Effect3 } from "effect";
570
+ import { Effect as Effect4 } from "effect";
323
571
 
324
572
  // src/storage/lww.ts
325
573
  function resolveWinner(existing, incoming) {
@@ -383,7 +631,7 @@ function buildRecord(event) {
383
631
  };
384
632
  }
385
633
  function applyEvent(storage, event) {
386
- return Effect3.gen(function* () {
634
+ return Effect4.gen(function* () {
387
635
  const existing = yield* storage.getRecord(event.collection, event.recordId);
388
636
  if (existing) {
389
637
  const existingMeta = {
@@ -403,7 +651,7 @@ function applyEvent(storage, event) {
403
651
  });
404
652
  }
405
653
  function rebuild(storage, collections) {
406
- return Effect3.gen(function* () {
654
+ return Effect4.gen(function* () {
407
655
  for (const col of collections) {
408
656
  yield* storage.clearRecords(col);
409
657
  }
@@ -419,7 +667,7 @@ function rebuild(storage, collections) {
419
667
  }
420
668
 
421
669
  // src/schema/validate.ts
422
- import { Effect as Effect4, Schema as Schema4 } from "effect";
670
+ import { Effect as Effect5, Schema as Schema4 } from "effect";
423
671
  function fieldDefToSchema(fd) {
424
672
  let base;
425
673
  switch (fd.kind) {
@@ -467,10 +715,10 @@ function buildStructSchema(def, options = {}) {
467
715
  function buildValidator(collectionName, def) {
468
716
  const decode = Schema4.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
469
717
  return (input) => decode(input).pipe(
470
- Effect4.map(
718
+ Effect5.map(
471
719
  (result) => result
472
720
  ),
473
- Effect4.mapError(
721
+ Effect5.mapError(
474
722
  (e) => new ValidationError({
475
723
  message: `Validation failed for collection "${collectionName}": ${e.message}`
476
724
  })
@@ -479,7 +727,7 @@ function buildValidator(collectionName, def) {
479
727
  }
480
728
  function buildPartialValidator(collectionName, def) {
481
729
  const decode = Schema4.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
482
- return (input) => Effect4.gen(function* () {
730
+ return (input) => Effect5.gen(function* () {
483
731
  if (typeof input !== "object" || input === null) {
484
732
  return yield* new ValidationError({
485
733
  message: `Validation failed for collection "${collectionName}": expected an object`
@@ -494,8 +742,8 @@ function buildPartialValidator(collectionName, def) {
494
742
  });
495
743
  }
496
744
  return yield* decode(record).pipe(
497
- Effect4.map((result) => result),
498
- Effect4.mapError(
745
+ Effect5.map((result) => result),
746
+ Effect5.mapError(
499
747
  (e) => new ValidationError({
500
748
  message: `Validation failed for collection "${collectionName}": ${e.message}`
501
749
  })
@@ -505,7 +753,7 @@ function buildPartialValidator(collectionName, def) {
505
753
  }
506
754
 
507
755
  // src/crud/collection-handle.ts
508
- import { Effect as Effect6, Option as Option3, References } from "effect";
756
+ import { Effect as Effect7, Option as Option3, References } from "effect";
509
757
 
510
758
  // src/utils/uuid.ts
511
759
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -538,12 +786,12 @@ function uuidv7() {
538
786
  }
539
787
 
540
788
  // src/crud/query-builder.ts
541
- import { Effect as Effect5, Option as Option2, Ref as Ref2, Stream as Stream2 } from "effect";
789
+ import { Effect as Effect6, Option as Option2, Ref as Ref2, Stream as Stream2 } from "effect";
542
790
  function emptyPlan() {
543
791
  return { filters: [] };
544
792
  }
545
793
  function executeQuery(ctx, plan) {
546
- return Effect5.gen(function* () {
794
+ return Effect6.gen(function* () {
547
795
  if (plan.fieldName) {
548
796
  const fieldDef = ctx.def.fields[plan.fieldName];
549
797
  if (!fieldDef) {
@@ -619,7 +867,7 @@ function watchQuery(ctx, plan) {
619
867
  const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
620
868
  Stream2.filter((event) => event.collection === ctx.collectionName),
621
869
  Stream2.mapEffect(
622
- () => Effect5.gen(function* () {
870
+ () => Effect6.gen(function* () {
623
871
  const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
624
872
  if (replaying) return void 0;
625
873
  return yield* query();
@@ -628,7 +876,7 @@ function watchQuery(ctx, plan) {
628
876
  Stream2.filter((result) => result !== void 0)
629
877
  );
630
878
  return Stream2.unwrap(
631
- Effect5.gen(function* () {
879
+ Effect6.gen(function* () {
632
880
  const initial = yield* query();
633
881
  return Stream2.concat(Stream2.make(initial), changes);
634
882
  })
@@ -654,11 +902,11 @@ function makeQueryBuilder(ctx, plan) {
654
902
  offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
655
903
  limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
656
904
  get: () => executeQuery(ctx, plan),
657
- first: () => Effect5.map(
905
+ first: () => Effect6.map(
658
906
  executeQuery(ctx, { ...plan, limit: 1 }),
659
907
  (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
660
908
  ),
661
- count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
909
+ count: () => Effect6.map(executeQuery(ctx, plan), (results) => results.length),
662
910
  watch: () => watchQuery(ctx, plan)
663
911
  };
664
912
  }
@@ -764,7 +1012,7 @@ function replayState(recordId, events, stopAtId) {
764
1012
  return state;
765
1013
  }
766
1014
  function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
767
- return Effect6.gen(function* () {
1015
+ return Effect7.gen(function* () {
768
1016
  const chronological = sortChronologically(allSorted);
769
1017
  const state = replayState(recordId, chronological, target.id);
770
1018
  if (state) {
@@ -773,7 +1021,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
773
1021
  });
774
1022
  }
775
1023
  function pruneEvents(storage, collection2, recordId, retention) {
776
- return Effect6.gen(function* () {
1024
+ return Effect7.gen(function* () {
777
1025
  const events = yield* storage.getEventsByRecord(collection2, recordId);
778
1026
  if (events.length <= retention) return;
779
1027
  const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
@@ -794,8 +1042,8 @@ function mapRecord(record) {
794
1042
  }
795
1043
  function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
796
1044
  const collectionName = def.name;
797
- const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
798
- const commitEvent = (event) => Effect6.gen(function* () {
1045
+ const withLog = (effect) => Effect7.provideService(effect, References.MinimumLogLevel, logLevel);
1046
+ const commitEvent = (event) => Effect7.gen(function* () {
799
1047
  yield* storage.putEvent(event);
800
1048
  yield* applyEvent(storage, event);
801
1049
  if (onWrite) yield* onWrite(event);
@@ -807,7 +1055,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
807
1055
  });
808
1056
  const handle = {
809
1057
  add: (data) => withLog(
810
- Effect6.gen(function* () {
1058
+ Effect7.gen(function* () {
811
1059
  const id = uuidv7();
812
1060
  const fullRecord = { id, ...data };
813
1061
  yield* validator(fullRecord);
@@ -821,7 +1069,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
821
1069
  author: localAuthor
822
1070
  };
823
1071
  yield* commitEvent(event);
824
- yield* Effect6.logDebug("Record added", {
1072
+ yield* Effect7.logDebug("Record added", {
825
1073
  collection: collectionName,
826
1074
  recordId: id,
827
1075
  data: fullRecord
@@ -830,7 +1078,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
830
1078
  })
831
1079
  ),
832
1080
  update: (id, data) => withLog(
833
- Effect6.gen(function* () {
1081
+ Effect7.gen(function* () {
834
1082
  const existing = yield* storage.getRecord(collectionName, id);
835
1083
  if (!existing || existing._d) {
836
1084
  return yield* new NotFoundError({
@@ -853,7 +1101,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
853
1101
  author: localAuthor
854
1102
  };
855
1103
  yield* commitEvent(event);
856
- yield* Effect6.logDebug("Record updated", {
1104
+ yield* Effect7.logDebug("Record updated", {
857
1105
  collection: collectionName,
858
1106
  recordId: id,
859
1107
  data: diff
@@ -862,7 +1110,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
862
1110
  })
863
1111
  ),
864
1112
  delete: (id) => withLog(
865
- Effect6.gen(function* () {
1113
+ Effect7.gen(function* () {
866
1114
  const existing = yield* storage.getRecord(collectionName, id);
867
1115
  if (!existing || existing._d) {
868
1116
  return yield* new NotFoundError({
@@ -880,11 +1128,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
880
1128
  author: localAuthor
881
1129
  };
882
1130
  yield* commitEvent(event);
883
- yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
1131
+ yield* Effect7.logDebug("Record deleted", { collection: collectionName, recordId: id });
884
1132
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
885
1133
  })
886
1134
  ),
887
- undo: (id) => Effect6.gen(function* () {
1135
+ undo: (id) => Effect7.gen(function* () {
888
1136
  const existing = yield* storage.getRecord(collectionName, id);
889
1137
  if (!existing) {
890
1138
  return yield* new NotFoundError({ collection: collectionName, id });
@@ -909,7 +1157,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
909
1157
  yield* commitEvent(event);
910
1158
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
911
1159
  }),
912
- get: (id) => Effect6.gen(function* () {
1160
+ get: (id) => Effect7.gen(function* () {
913
1161
  const record = yield* storage.getRecord(collectionName, id);
914
1162
  if (!record || record._d) {
915
1163
  return yield* new NotFoundError({
@@ -919,11 +1167,11 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
919
1167
  }
920
1168
  return mapRecord(record);
921
1169
  }),
922
- first: () => Effect6.map(storage.getAllRecords(collectionName), (all) => {
1170
+ first: () => Effect7.map(storage.getAllRecords(collectionName), (all) => {
923
1171
  const found = all.find((r) => !r._d);
924
1172
  return found ? Option3.some(mapRecord(found)) : Option3.none();
925
1173
  }),
926
- count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
1174
+ count: () => Effect7.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
927
1175
  watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
928
1176
  where: (fieldName) => createWhereClause(
929
1177
  storage,
@@ -946,12 +1194,12 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
946
1194
  }
947
1195
 
948
1196
  // src/sync/sync-service.ts
949
- import { Duration, Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
1197
+ import { Duration, Effect as Effect9, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
950
1198
  import { unwrapEvent } from "nostr-tools/nip59";
951
1199
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
952
1200
 
953
1201
  // src/sync/negentropy.ts
954
- import { Effect as Effect7 } from "effect";
1202
+ import { Effect as Effect8 } from "effect";
955
1203
 
956
1204
  // src/vendor/negentropy.js
957
1205
  var PROTOCOL_VERSION = 97;
@@ -1438,14 +1686,14 @@ function itemCompare(a, b) {
1438
1686
  }
1439
1687
 
1440
1688
  // src/sync/negentropy.ts
1441
- import { hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
1689
+ import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1442
1690
  import { GiftWrap } from "nostr-tools/kinds";
1443
1691
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1444
- return Effect7.gen(function* () {
1692
+ return Effect8.gen(function* () {
1445
1693
  const allGiftWraps = yield* storage.getAllGiftWraps();
1446
1694
  const storageVector = new NegentropyStorageVector();
1447
1695
  for (const gw of allGiftWraps) {
1448
- storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1696
+ storageVector.insert(gw.createdAt, hexToBytes3(gw.id));
1449
1697
  }
1450
1698
  storageVector.seal();
1451
1699
  const neg = new Negentropy(storageVector, 0);
@@ -1456,7 +1704,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1456
1704
  const allHaveIds = [];
1457
1705
  const allNeedIds = [];
1458
1706
  const subId = `neg-${Date.now()}`;
1459
- const initialMsg = yield* Effect7.tryPromise({
1707
+ const initialMsg = yield* Effect8.tryPromise({
1460
1708
  try: () => neg.initiate(),
1461
1709
  catch: (e) => new SyncError({
1462
1710
  message: `Negentropy initiate failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1468,7 +1716,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1468
1716
  while (currentMsg !== null) {
1469
1717
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1470
1718
  if (response.msgHex === null) break;
1471
- const reconcileResult = yield* Effect7.tryPromise({
1719
+ const reconcileResult = yield* Effect8.tryPromise({
1472
1720
  try: () => neg.reconcile(response.msgHex),
1473
1721
  catch: (e) => new SyncError({
1474
1722
  message: `Negentropy reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1481,13 +1729,13 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1481
1729
  for (const id of needIds) allNeedIds.push(id);
1482
1730
  currentMsg = nextMsg;
1483
1731
  }
1484
- yield* Effect7.logDebug("Negentropy reconciliation complete", {
1732
+ yield* Effect8.logDebug("Negentropy reconciliation complete", {
1485
1733
  relay: relayUrl,
1486
1734
  have: allHaveIds.length,
1487
1735
  need: allNeedIds.length
1488
1736
  });
1489
1737
  return { haveIds: allHaveIds, needIds: allNeedIds };
1490
- }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1738
+ }).pipe(Effect8.withLogSpan("tablinum.negentropy"));
1491
1739
  }
1492
1740
 
1493
1741
  // src/db/key-rotation.ts
@@ -1581,52 +1829,52 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1581
1829
  kind: "create"
1582
1830
  });
1583
1831
  const forkHandled = (effect) => {
1584
- Effect8.runFork(
1832
+ Effect9.runFork(
1585
1833
  effect.pipe(
1586
- Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
1587
- Effect8.ignore,
1588
- Effect8.provide(logLayer),
1589
- Effect8.forkIn(scope)
1834
+ Effect9.tapError((e) => Effect9.sync(() => onSyncError?.(e))),
1835
+ Effect9.ignore,
1836
+ Effect9.provide(logLayer),
1837
+ Effect9.forkIn(scope)
1590
1838
  )
1591
1839
  );
1592
1840
  };
1593
1841
  let autoFlushActive = false;
1594
- const autoFlushEffect = Effect8.gen(function* () {
1842
+ const autoFlushEffect = Effect9.gen(function* () {
1595
1843
  const size = yield* publishQueue.size();
1596
1844
  if (size === 0) return;
1597
1845
  yield* syncStatus.set("syncing");
1598
1846
  yield* publishQueue.flush(relayUrls);
1599
1847
  const remaining = yield* publishQueue.size();
1600
- if (remaining > 0) yield* Effect8.fail("pending");
1848
+ if (remaining > 0) yield* Effect9.fail("pending");
1601
1849
  }).pipe(
1602
- Effect8.ensuring(syncStatus.set("idle")),
1603
- Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1604
- Effect8.ignore
1850
+ Effect9.ensuring(syncStatus.set("idle")),
1851
+ Effect9.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1852
+ Effect9.ignore
1605
1853
  );
1606
1854
  const scheduleAutoFlush = () => {
1607
1855
  if (autoFlushActive) return;
1608
1856
  autoFlushActive = true;
1609
1857
  forkHandled(
1610
1858
  autoFlushEffect.pipe(
1611
- Effect8.ensuring(
1612
- Effect8.sync(() => {
1859
+ Effect9.ensuring(
1860
+ Effect9.sync(() => {
1613
1861
  autoFlushActive = false;
1614
1862
  })
1615
1863
  )
1616
1864
  )
1617
1865
  );
1618
1866
  };
1619
- const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1867
+ const shouldRejectWrite = (authorPubkey) => Effect9.gen(function* () {
1620
1868
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1621
1869
  if (!memberRecord) return false;
1622
1870
  return !!memberRecord.removedAt;
1623
1871
  });
1624
1872
  const storeGiftWrapShell = (gw) => storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
1625
- const unwrapGiftWrap = (remoteGw) => Effect8.gen(function* () {
1873
+ const unwrapGiftWrap = (remoteGw) => Effect9.gen(function* () {
1626
1874
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1627
1875
  if (existing) return null;
1628
1876
  const rumor = yield* giftWrapHandle.unwrap(remoteGw).pipe(
1629
- Effect8.orElseSucceed(() => null)
1877
+ Effect9.orElseSucceed(() => null)
1630
1878
  );
1631
1879
  if (!rumor) {
1632
1880
  yield* storeGiftWrapShell(remoteGw);
@@ -1654,7 +1902,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1654
1902
  recordId: dTag.substring(colonIdx + 1)
1655
1903
  };
1656
1904
  });
1657
- const applyUnwrappedEvent = (uw) => Effect8.gen(function* () {
1905
+ const applyUnwrappedEvent = (uw) => Effect9.gen(function* () {
1658
1906
  const { giftWrap: remoteGw, rumor, collection: collectionName, recordId } = uw;
1659
1907
  const retention = knownCollections.get(collectionName);
1660
1908
  if (retention === void 0) {
@@ -1664,17 +1912,17 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1664
1912
  if (rumor.pubkey) {
1665
1913
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1666
1914
  if (reject) {
1667
- yield* Effect8.logWarning("Rejected write from removed member", {
1915
+ yield* Effect9.logWarning("Rejected write from removed member", {
1668
1916
  author: rumor.pubkey.slice(0, 12)
1669
1917
  });
1670
1918
  yield* storeGiftWrapShell(remoteGw);
1671
1919
  return null;
1672
1920
  }
1673
1921
  }
1674
- const parsed = yield* Effect8.try({
1922
+ const parsed = yield* Effect9.try({
1675
1923
  try: () => JSON.parse(rumor.content),
1676
1924
  catch: () => void 0
1677
- }).pipe(Effect8.orElseSucceed(() => void 0));
1925
+ }).pipe(Effect9.orElseSucceed(() => void 0));
1678
1926
  if (parsed === void 0) {
1679
1927
  yield* storeGiftWrapShell(remoteGw);
1680
1928
  return null;
@@ -1706,7 +1954,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1706
1954
  if (didApply && (kind === "u" || kind === "d")) {
1707
1955
  yield* pruneEvents(storage, collectionName, recordId, retention);
1708
1956
  }
1709
- yield* Effect8.logDebug("Processed gift wrap", {
1957
+ yield* Effect9.logDebug("Processed gift wrap", {
1710
1958
  collection: collectionName,
1711
1959
  recordId,
1712
1960
  kind,
@@ -1717,33 +1965,33 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1717
1965
  }
1718
1966
  return collectionName;
1719
1967
  });
1720
- const reconcileRelay = (url, pubKeys) => Effect8.gen(function* () {
1721
- yield* Effect8.logDebug("Syncing relay", { relay: url });
1968
+ const reconcileRelay = (url, pubKeys) => Effect9.gen(function* () {
1969
+ yield* Effect9.logDebug("Syncing relay", { relay: url });
1722
1970
  const { haveIds, needIds } = yield* reconcileWithRelay(
1723
1971
  storage,
1724
1972
  relay,
1725
1973
  url,
1726
1974
  Array.from(pubKeys)
1727
1975
  ).pipe(
1728
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1729
- Effect8.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1976
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1977
+ Effect9.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1730
1978
  );
1731
- yield* Effect8.logDebug("Relay reconciliation result", {
1979
+ yield* Effect9.logDebug("Relay reconciliation result", {
1732
1980
  relay: url,
1733
1981
  need: needIds.length,
1734
1982
  have: haveIds.length
1735
1983
  });
1736
1984
  const events = needIds.length > 0 ? yield* relay.fetchEvents(needIds, url).pipe(
1737
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1738
- Effect8.orElseSucceed(() => [])
1985
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
1986
+ Effect9.orElseSucceed(() => [])
1739
1987
  ) : [];
1740
1988
  return {
1741
1989
  events,
1742
1990
  haveIds: haveIds.map((id) => ({ id, url }))
1743
1991
  };
1744
- }).pipe(Effect8.withLogSpan("tablinum.reconcileRelay"));
1745
- const syncAllRelays = (pubKeys, changedCollections) => Effect8.gen(function* () {
1746
- const results = yield* Effect8.forEach(
1992
+ }).pipe(Effect9.withLogSpan("tablinum.reconcileRelay"));
1993
+ const syncAllRelays = (pubKeys, changedCollections) => Effect9.gen(function* () {
1994
+ const results = yield* Effect9.forEach(
1747
1995
  relayUrls,
1748
1996
  (url) => reconcileRelay(url, pubKeys),
1749
1997
  { concurrency: "unbounded" }
@@ -1760,44 +2008,44 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1760
2008
  }
1761
2009
  const unwrapped = [];
1762
2010
  for (const gw of allGiftWraps) {
1763
- const result = yield* unwrapGiftWrap(gw).pipe(Effect8.orElseSucceed(() => null));
2011
+ const result = yield* unwrapGiftWrap(gw).pipe(Effect9.orElseSucceed(() => null));
1764
2012
  if (result) unwrapped.push(result);
1765
2013
  }
1766
2014
  unwrapped.sort((a, b) => a.rumor.created_at - b.rumor.created_at || (a.rumor.id < b.rumor.id ? -1 : 1));
1767
2015
  for (const event of unwrapped) {
1768
2016
  const collection2 = yield* applyUnwrappedEvent(event).pipe(
1769
- Effect8.orElseSucceed(() => null)
2017
+ Effect9.orElseSucceed(() => null)
1770
2018
  );
1771
2019
  if (collection2) changedCollections.add(collection2);
1772
2020
  }
1773
2021
  const allHaveIds = results.flatMap((r) => r.haveIds);
1774
- yield* Effect8.forEach(
2022
+ yield* Effect9.forEach(
1775
2023
  allHaveIds,
1776
- ({ id, url }) => Effect8.gen(function* () {
2024
+ ({ id, url }) => Effect9.gen(function* () {
1777
2025
  const gw = yield* storage.getGiftWrap(id);
1778
2026
  if (!gw?.event) return;
1779
2027
  yield* relay.publish(gw.event, [url]).pipe(
1780
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1781
- Effect8.ignore
2028
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2029
+ Effect9.ignore
1782
2030
  );
1783
2031
  }),
1784
2032
  { concurrency: "unbounded", discard: true }
1785
2033
  );
1786
- }).pipe(Effect8.withLogSpan("tablinum.syncAllRelays"));
1787
- const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
2034
+ }).pipe(Effect9.withLogSpan("tablinum.syncAllRelays"));
2035
+ const processGiftWrap = (remoteGw) => Effect9.gen(function* () {
1788
2036
  const uw = yield* unwrapGiftWrap(remoteGw);
1789
2037
  if (!uw) return null;
1790
2038
  return yield* applyUnwrappedEvent(uw);
1791
2039
  });
1792
- const processRealtimeGiftWrap = (remoteGw) => Effect8.gen(function* () {
1793
- const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
2040
+ const processRealtimeGiftWrap = (remoteGw) => Effect9.gen(function* () {
2041
+ const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect9.orElseSucceed(() => null));
1794
2042
  if (collection2) {
1795
2043
  yield* notifyCollectionUpdated(collection2);
1796
2044
  }
1797
2045
  });
1798
- const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1799
- const unwrapResult = yield* Effect8.result(
1800
- Effect8.try({
2046
+ const processRotationGiftWrap = (remoteGw) => Effect9.gen(function* () {
2047
+ const unwrapResult = yield* Effect9.result(
2048
+ Effect9.try({
1801
2049
  try: () => unwrapEvent(remoteGw, personalPrivateKey),
1802
2050
  catch: (e) => new CryptoError({
1803
2051
  message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1847,73 +2095,73 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1847
2095
  yield* handle.addEpochSubscription(epoch.publicKey);
1848
2096
  return true;
1849
2097
  });
1850
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
2098
+ const subscribeAcrossRelays = (filter, onEvent) => Effect9.forEach(
1851
2099
  relayUrls,
1852
- (url) => Effect8.gen(function* () {
2100
+ (url) => Effect9.gen(function* () {
1853
2101
  yield* relay.subscribe(filter, url, (event) => {
1854
2102
  forkHandled(onEvent(event));
1855
2103
  }).pipe(
1856
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1857
- Effect8.ignore
2104
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2105
+ Effect9.ignore
1858
2106
  );
1859
2107
  }),
1860
2108
  { concurrency: "unbounded", discard: true }
1861
2109
  );
1862
2110
  let healingActive = false;
1863
- const healingEffect = Effect8.gen(function* () {
2111
+ const healingEffect = Effect9.gen(function* () {
1864
2112
  if (!healingActive) return;
1865
2113
  const status = yield* syncStatus.get();
1866
2114
  if (status === "syncing") return;
1867
2115
  yield* syncStatus.set("syncing");
1868
- yield* Effect8.gen(function* () {
2116
+ yield* Effect9.gen(function* () {
1869
2117
  const pubKeys = getSubscriptionPubKeys();
1870
2118
  const changedCollections = /* @__PURE__ */ new Set();
1871
2119
  yield* syncAllRelays(pubKeys, changedCollections);
1872
2120
  if (changedCollections.size > 0) {
1873
2121
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1874
2122
  }
1875
- }).pipe(Effect8.ensuring(syncStatus.set("idle")));
1876
- }).pipe(Effect8.ignore);
2123
+ }).pipe(Effect9.ensuring(syncStatus.set("idle")));
2124
+ }).pipe(Effect9.ignore);
1877
2125
  const handle = {
1878
- sync: () => Effect8.gen(function* () {
1879
- yield* Effect8.logInfo("Sync started");
2126
+ sync: () => Effect9.gen(function* () {
2127
+ yield* Effect9.logInfo("Sync started");
1880
2128
  yield* syncStatus.set("syncing");
1881
2129
  yield* Ref3.set(watchCtx.replayingRef, true);
1882
2130
  const changedCollections = /* @__PURE__ */ new Set();
1883
- yield* Effect8.gen(function* () {
2131
+ yield* Effect9.gen(function* () {
1884
2132
  const pubKeys = getSubscriptionPubKeys();
1885
2133
  yield* syncAllRelays(pubKeys, changedCollections);
1886
- yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
2134
+ yield* publishQueue.flush(relayUrls).pipe(Effect9.ignore);
1887
2135
  }).pipe(
1888
- Effect8.ensuring(
1889
- Effect8.gen(function* () {
2136
+ Effect9.ensuring(
2137
+ Effect9.gen(function* () {
1890
2138
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1891
2139
  yield* syncStatus.set("idle");
1892
2140
  })
1893
2141
  )
1894
2142
  );
1895
- yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1896
- }).pipe(Effect8.withLogSpan("tablinum.sync")),
1897
- publishLocal: (giftWrap) => Effect8.gen(function* () {
2143
+ yield* Effect9.logInfo("Sync complete", { changed: [...changedCollections] });
2144
+ }).pipe(Effect9.withLogSpan("tablinum.sync")),
2145
+ publishLocal: (giftWrap) => Effect9.gen(function* () {
1898
2146
  if (!giftWrap.event) return;
1899
2147
  yield* relay.publish(giftWrap.event, relayUrls).pipe(
1900
- Effect8.tapError(
2148
+ Effect9.tapError(
1901
2149
  () => storage.putGiftWrap(giftWrap).pipe(
1902
- Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
1903
- Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
2150
+ Effect9.andThen(publishQueue.enqueue(giftWrap.id)),
2151
+ Effect9.andThen(Effect9.sync(() => scheduleAutoFlush()))
1904
2152
  )
1905
2153
  ),
1906
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1907
- Effect8.ignore
2154
+ Effect9.tapError((err) => Effect9.sync(() => onSyncError?.(err))),
2155
+ Effect9.ignore
1908
2156
  );
1909
2157
  }),
1910
- startSubscription: () => Effect8.gen(function* () {
2158
+ startSubscription: () => Effect9.gen(function* () {
1911
2159
  const pubKeys = getSubscriptionPubKeys();
1912
2160
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1913
2161
  if (!pubKeys.includes(personalPublicKey)) {
1914
2162
  yield* subscribeAcrossRelays(
1915
2163
  { kinds: [GiftWrap2], "#p": [personalPublicKey] },
1916
- (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
2164
+ (event) => Effect9.result(processRotationGiftWrap(event)).pipe(Effect9.asVoid)
1917
2165
  );
1918
2166
  }
1919
2167
  }),
@@ -1922,10 +2170,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1922
2170
  if (healingActive) return;
1923
2171
  healingActive = true;
1924
2172
  forkHandled(
1925
- Effect8.sleep(Duration.minutes(5)).pipe(
1926
- Effect8.andThen(healingEffect),
1927
- Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
1928
- Effect8.ensuring(Effect8.sync(() => {
2173
+ Effect9.sleep(Duration.minutes(5)).pipe(
2174
+ Effect9.andThen(healingEffect),
2175
+ Effect9.repeat(Schedule.spaced(Duration.minutes(5))),
2176
+ Effect9.ensuring(Effect9.sync(() => {
1929
2177
  healingActive = false;
1930
2178
  }))
1931
2179
  )
@@ -1937,8 +2185,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1937
2185
  };
1938
2186
  forkHandled(
1939
2187
  publishQueue.size().pipe(
1940
- Effect8.flatMap(
1941
- (size) => Effect8.sync(() => {
2188
+ Effect9.flatMap(
2189
+ (size) => Effect9.sync(() => {
1942
2190
  if (size > 0) scheduleAutoFlush();
1943
2191
  })
1944
2192
  )
@@ -1948,7 +2196,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1948
2196
  }
1949
2197
 
1950
2198
  // src/db/members.ts
1951
- import { Effect as Effect9, Option as Option6, Schema as Schema6 } from "effect";
2199
+ import { Effect as Effect10, Option as Option6, Schema as Schema6 } from "effect";
1952
2200
  var optionalString = {
1953
2201
  _tag: "FieldDef",
1954
2202
  kind: "string",
@@ -1997,15 +2245,15 @@ var AuthorProfileSchema = Schema6.Struct({
1997
2245
  });
1998
2246
  var decodeAuthorProfile = Schema6.decodeUnknownEffect(Schema6.fromJsonString(AuthorProfileSchema));
1999
2247
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
2000
- return Effect9.gen(function* () {
2248
+ return Effect10.gen(function* () {
2001
2249
  for (const url of relayUrls) {
2002
- const result = yield* Effect9.result(
2250
+ const result = yield* Effect10.result(
2003
2251
  relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
2004
2252
  );
2005
2253
  if (result._tag === "Success" && result.success.length > 0) {
2006
2254
  return yield* decodeAuthorProfile(result.success[0].content).pipe(
2007
- Effect9.map(Option6.some),
2008
- Effect9.orElseSucceed(() => Option6.none())
2255
+ Effect10.map(Option6.some),
2256
+ Effect10.orElseSucceed(() => Option6.none())
2009
2257
  );
2010
2258
  }
2011
2259
  }
@@ -2055,15 +2303,15 @@ var SyncStatus = class extends ServiceMap9.Service()(
2055
2303
  };
2056
2304
 
2057
2305
  // src/layers/IdentityLive.ts
2058
- import { Effect as Effect11, Layer as Layer2 } from "effect";
2059
- import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
2306
+ import { Effect as Effect12, Layer as Layer2 } from "effect";
2307
+ import { hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2060
2308
 
2061
2309
  // src/db/identity.ts
2062
- import { Effect as Effect10 } from "effect";
2310
+ import { Effect as Effect11 } from "effect";
2063
2311
  import { getPublicKey as getPublicKey2 } from "nostr-tools/pure";
2064
- import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
2312
+ import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2065
2313
  function createIdentity(suppliedKey) {
2066
- return Effect10.gen(function* () {
2314
+ return Effect11.gen(function* () {
2067
2315
  let privateKey;
2068
2316
  if (suppliedKey) {
2069
2317
  if (suppliedKey.length !== 32) {
@@ -2076,8 +2324,8 @@ function createIdentity(suppliedKey) {
2076
2324
  privateKey = new Uint8Array(32);
2077
2325
  crypto.getRandomValues(privateKey);
2078
2326
  }
2079
- const privateKeyHex = bytesToHex2(privateKey);
2080
- const publicKey = yield* Effect10.try({
2327
+ const privateKeyHex = bytesToHex3(privateKey);
2328
+ const publicKey = yield* Effect11.try({
2081
2329
  try: () => getPublicKey2(privateKey),
2082
2330
  catch: (e) => new CryptoError({
2083
2331
  message: `Failed to derive public key: ${e instanceof Error ? e.message : String(e)}`,
@@ -2095,14 +2343,14 @@ function createIdentity(suppliedKey) {
2095
2343
  // src/layers/IdentityLive.ts
2096
2344
  var IdentityLive = Layer2.effect(
2097
2345
  Identity,
2098
- Effect11.gen(function* () {
2346
+ Effect12.gen(function* () {
2099
2347
  const config = yield* Config;
2100
2348
  const storage = yield* Storage;
2101
2349
  const idbKey = yield* storage.getMeta("identity_key");
2102
- const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : void 0);
2350
+ const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes4(idbKey) : void 0);
2103
2351
  const identity = yield* createIdentity(resolvedKey);
2104
2352
  yield* storage.putMeta("identity_key", identity.exportKey());
2105
- yield* Effect11.logInfo("Identity loaded", {
2353
+ yield* Effect12.logInfo("Identity loaded", {
2106
2354
  publicKey: identity.publicKey.slice(0, 12) + "...",
2107
2355
  source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
2108
2356
  });
@@ -2111,12 +2359,12 @@ var IdentityLive = Layer2.effect(
2111
2359
  );
2112
2360
 
2113
2361
  // src/layers/EpochStoreLive.ts
2114
- import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
2362
+ import { Effect as Effect13, Layer as Layer3, Option as Option7 } from "effect";
2115
2363
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
2116
- import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
2364
+ import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
2117
2365
  var EpochStoreLive = Layer3.effect(
2118
2366
  EpochStore,
2119
- Effect12.gen(function* () {
2367
+ Effect13.gen(function* () {
2120
2368
  const config = yield* Config;
2121
2369
  const identity = yield* Identity;
2122
2370
  const storage = yield* Storage;
@@ -2135,7 +2383,7 @@ var EpochStoreLive = Layer3.effect(
2135
2383
  return existing !== void 0 && existing.privateKey === ek.key;
2136
2384
  });
2137
2385
  if (configIsSubset) {
2138
- yield* Effect12.logInfo("Epoch store loaded", {
2386
+ yield* Effect13.logInfo("Epoch store loaded", {
2139
2387
  source: "storage",
2140
2388
  epochs: idbStore.epochs.size
2141
2389
  });
@@ -2144,271 +2392,28 @@ var EpochStoreLive = Layer3.effect(
2144
2392
  }
2145
2393
  const store2 = createEpochStoreFromInputs(config.epochKeys);
2146
2394
  yield* storage.putMeta("epochs", stringifyEpochStore(store2));
2147
- yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2395
+ yield* Effect13.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2148
2396
  return store2;
2149
2397
  }
2150
2398
  if (idbStore) {
2151
- yield* Effect12.logInfo("Epoch store loaded", {
2399
+ yield* Effect13.logInfo("Epoch store loaded", {
2152
2400
  source: "storage",
2153
2401
  epochs: idbStore.epochs.size
2154
2402
  });
2155
2403
  return idbStore;
2156
2404
  }
2157
2405
  const store = createEpochStoreFromInputs(
2158
- [{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
2406
+ [{ epochId: EpochId("epoch-0"), key: bytesToHex4(generateSecretKey2()) }],
2159
2407
  { createdBy: identity.publicKey }
2160
2408
  );
2161
2409
  yield* storage.putMeta("epochs", stringifyEpochStore(store));
2162
- yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2410
+ yield* Effect13.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2163
2411
  return store;
2164
2412
  })
2165
2413
  );
2166
2414
 
2167
2415
  // src/layers/StorageLive.ts
2168
2416
  import { Effect as Effect14, Layer as Layer4 } from "effect";
2169
-
2170
- // src/storage/idb.ts
2171
- import { Effect as Effect13 } from "effect";
2172
- import { openDB } from "idb";
2173
-
2174
- // src/sync/compact-event.ts
2175
- import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2176
- var VERSION = 1;
2177
- var HEADER_SIZE = 133;
2178
- function base64ToBytes(base64) {
2179
- const binary = atob(base64);
2180
- const bytes = new Uint8Array(binary.length);
2181
- for (let i = 0; i < binary.length; i++) {
2182
- bytes[i] = binary.charCodeAt(i);
2183
- }
2184
- return bytes;
2185
- }
2186
- function bytesToBase64(bytes) {
2187
- let binary = "";
2188
- for (let i = 0; i < bytes.length; i++) {
2189
- binary += String.fromCharCode(bytes[i]);
2190
- }
2191
- return btoa(binary);
2192
- }
2193
- function packEvent(event) {
2194
- const pubkey = hexToBytes4(event.pubkey);
2195
- const sig = hexToBytes4(event.sig);
2196
- const recipientTag = event.tags.find((t) => t[0] === "p");
2197
- if (!recipientTag) throw new Error("Gift wrap missing #p tag");
2198
- if (event.tags.some((t) => t[0] !== "p")) {
2199
- throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
2200
- }
2201
- const recipient = hexToBytes4(recipientTag[1]);
2202
- const createdAtBuf = new Uint8Array(4);
2203
- new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
2204
- const content = base64ToBytes(event.content);
2205
- const result = new Uint8Array(HEADER_SIZE + content.length);
2206
- result[0] = VERSION;
2207
- result.set(pubkey, 1);
2208
- result.set(sig, 33);
2209
- result.set(recipient, 97);
2210
- result.set(createdAtBuf, 129);
2211
- result.set(content, HEADER_SIZE);
2212
- return result;
2213
- }
2214
- function unpackEvent(id, compact) {
2215
- const version = compact[0];
2216
- if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
2217
- const pubkey = bytesToHex4(compact.slice(1, 33));
2218
- const sig = bytesToHex4(compact.slice(33, 97));
2219
- const recipient = bytesToHex4(compact.slice(97, 129));
2220
- const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
2221
- const createdAt = dv.getUint32(0, false);
2222
- const content = bytesToBase64(compact.slice(HEADER_SIZE));
2223
- return {
2224
- id,
2225
- pubkey,
2226
- sig,
2227
- created_at: createdAt,
2228
- kind: 1059,
2229
- tags: [["p", recipient]],
2230
- content
2231
- };
2232
- }
2233
-
2234
- // src/storage/idb.ts
2235
- var DB_NAME = "tablinum";
2236
- function storeName(collection2) {
2237
- return `col_${collection2}`;
2238
- }
2239
- function computeSchemaSig(schema) {
2240
- return Object.entries(schema).sort(([a], [b]) => a.localeCompare(b)).map(([name, def]) => {
2241
- const indices = [...def.indices ?? []].sort().join(",");
2242
- return `${name}:${indices}`;
2243
- }).join("|");
2244
- }
2245
- function wrap(label, fn) {
2246
- return Effect13.tryPromise({
2247
- try: fn,
2248
- catch: (e) => new StorageError({
2249
- message: `IndexedDB ${label} failed: ${e instanceof Error ? e.message : String(e)}`,
2250
- cause: e
2251
- })
2252
- });
2253
- }
2254
- function upgradeSchema(database, schema, tx) {
2255
- if (!database.objectStoreNames.contains("_meta")) {
2256
- database.createObjectStore("_meta");
2257
- }
2258
- if (!database.objectStoreNames.contains("events")) {
2259
- const events = database.createObjectStore("events", { keyPath: "id" });
2260
- events.createIndex("by-record", ["collection", "recordId"]);
2261
- }
2262
- if (!database.objectStoreNames.contains("giftwraps")) {
2263
- database.createObjectStore("giftwraps", { keyPath: "id" });
2264
- }
2265
- const expectedStores = /* @__PURE__ */ new Set();
2266
- for (const [, def] of Object.entries(schema)) {
2267
- const sn = storeName(def.name);
2268
- expectedStores.add(sn);
2269
- if (!database.objectStoreNames.contains(sn)) {
2270
- const store = database.createObjectStore(sn, { keyPath: "id" });
2271
- for (const idx of def.indices ?? []) {
2272
- store.createIndex(idx, idx);
2273
- }
2274
- } else {
2275
- const store = tx.objectStore(sn);
2276
- const existingIndices = new Set(Array.from(store.indexNames));
2277
- const wantedIndices = new Set(def.indices ?? []);
2278
- for (const idx of existingIndices) {
2279
- if (!wantedIndices.has(idx)) store.deleteIndex(idx);
2280
- }
2281
- for (const idx of wantedIndices) {
2282
- if (!existingIndices.has(idx)) store.createIndex(idx, idx);
2283
- }
2284
- }
2285
- }
2286
- for (const existing of Array.from(database.objectStoreNames)) {
2287
- if (existing.startsWith("col_") && !expectedStores.has(existing)) {
2288
- database.deleteObjectStore(existing);
2289
- }
2290
- }
2291
- tx.objectStore("_meta").put(computeSchemaSig(schema), "schema_sig");
2292
- }
2293
- function openIDBStorage(dbName, schema) {
2294
- return Effect13.gen(function* () {
2295
- const name = dbName ?? DB_NAME;
2296
- const schemaSig = computeSchemaSig(schema);
2297
- if (typeof indexedDB === "undefined") {
2298
- return yield* Effect13.fail(
2299
- new StorageError({
2300
- message: "IndexedDB is not available in this environment"
2301
- })
2302
- );
2303
- }
2304
- const probeDb = yield* Effect13.tryPromise({
2305
- try: () => openDB(name),
2306
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2307
- });
2308
- const currentVersion = probeDb.version;
2309
- let needsUpgrade = true;
2310
- if (probeDb.objectStoreNames.contains("_meta")) {
2311
- const storedSig = yield* Effect13.tryPromise({
2312
- try: () => probeDb.get("_meta", "schema_sig"),
2313
- catch: () => new StorageError({ message: "Failed to read schema meta" })
2314
- }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
2315
- needsUpgrade = storedSig !== schemaSig;
2316
- }
2317
- probeDb.close();
2318
- const db = needsUpgrade ? yield* Effect13.tryPromise({
2319
- try: () => openDB(name, currentVersion + 1, {
2320
- upgrade(database, _oldVersion, _newVersion, transaction) {
2321
- upgradeSchema(database, schema, transaction);
2322
- }
2323
- }),
2324
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2325
- }) : yield* Effect13.tryPromise({
2326
- try: () => openDB(name),
2327
- catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
2328
- });
2329
- yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2330
- const handle = {
2331
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2332
- getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2333
- getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2334
- countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
2335
- clearRecords: (collection2) => wrap("clearRecords", () => db.clear(storeName(collection2))),
2336
- getByIndex: (collection2, indexName, value) => wrap("getByIndex", () => db.getAllFromIndex(storeName(collection2), indexName, value)),
2337
- getByIndexRange: (collection2, indexName, range) => wrap("getByIndexRange", () => db.getAllFromIndex(storeName(collection2), indexName, range)),
2338
- getAllSorted: (collection2, indexName, direction) => wrap("getAllSorted", async () => {
2339
- const sn = storeName(collection2);
2340
- const tx = db.transaction(sn, "readonly");
2341
- const store = tx.objectStore(sn);
2342
- const index = store.index(indexName);
2343
- const results = [];
2344
- let cursor = await index.openCursor(null, direction ?? "next");
2345
- while (cursor) {
2346
- results.push(cursor.value);
2347
- cursor = await cursor.continue();
2348
- }
2349
- return results;
2350
- }),
2351
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2352
- getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2353
- getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2354
- getEventsByRecord: (collection2, recordId) => wrap(
2355
- "getEventsByRecord",
2356
- () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
2357
- ),
2358
- putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
2359
- if (gw.event) {
2360
- const compact = packEvent(gw.event);
2361
- await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
2362
- } else {
2363
- await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
2364
- }
2365
- }),
2366
- getGiftWrap: (id) => wrap("getGiftWrap", async () => {
2367
- const raw = await db.get("giftwraps", id);
2368
- if (!raw) return void 0;
2369
- if (raw.compact) {
2370
- return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
2371
- }
2372
- if (raw.event) {
2373
- const compact = packEvent(raw.event);
2374
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2375
- return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
2376
- }
2377
- return { id: raw.id, createdAt: raw.createdAt };
2378
- }),
2379
- getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
2380
- const raws = await db.getAll("giftwraps");
2381
- const results = [];
2382
- for (const raw of raws) {
2383
- if (raw.compact) {
2384
- results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
2385
- } else if (raw.event) {
2386
- const compact = packEvent(raw.event);
2387
- await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2388
- results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
2389
- } else {
2390
- results.push({ id: raw.id, createdAt: raw.createdAt });
2391
- }
2392
- }
2393
- return results;
2394
- }),
2395
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
2396
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
2397
- stripEventData: (id) => wrap("stripEventData", async () => {
2398
- const existing = await db.get("events", id);
2399
- if (existing) {
2400
- await db.put("events", { ...existing, data: null });
2401
- }
2402
- }),
2403
- getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2404
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2405
- close: () => Effect13.sync(() => db.close())
2406
- };
2407
- return handle;
2408
- });
2409
- }
2410
-
2411
- // src/layers/StorageLive.ts
2412
2417
  var StorageLive = Layer4.effect(
2413
2418
  Storage,
2414
2419
  Effect14.gen(function* () {
@@ -3087,6 +3092,60 @@ var TablinumLive = Layer9.effect(
3087
3092
  yield* Scope4.close(scope, Exit.void);
3088
3093
  })
3089
3094
  ),
3095
+ destroy: () => withLog(
3096
+ Effect21.gen(function* () {
3097
+ if (!(yield* Ref5.get(closedRef))) {
3098
+ yield* Ref5.set(closedRef, true);
3099
+ syncHandle.stopHealing();
3100
+ yield* Scope4.close(scope, Exit.void);
3101
+ }
3102
+ yield* deleteIDBStorage(config.dbName);
3103
+ })
3104
+ ),
3105
+ leave: () => withLog(
3106
+ Effect21.gen(function* () {
3107
+ if (yield* Ref5.get(closedRef)) {
3108
+ return yield* new SyncError({ message: "Database is closed", phase: "leave" });
3109
+ }
3110
+ const allMembers = yield* storage.getAllRecords("_members");
3111
+ const activeMembers = allMembers.filter(
3112
+ (member) => !member.removedAt && member.id !== identity.publicKey
3113
+ );
3114
+ const activePubkeys = activeMembers.map((member) => member.id);
3115
+ const result = createRotation(
3116
+ epochStore,
3117
+ identity.privateKey,
3118
+ identity.publicKey,
3119
+ activePubkeys,
3120
+ [identity.publicKey]
3121
+ );
3122
+ addEpoch(epochStore, result.epoch);
3123
+ epochStore.currentEpochId = result.epoch.id;
3124
+ yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
3125
+ const memberRecord = yield* storage.getRecord("_members", identity.publicKey);
3126
+ yield* putMemberRecord({
3127
+ ...memberRecord ?? {
3128
+ id: identity.publicKey,
3129
+ addedAt: 0,
3130
+ addedInEpoch: EpochId("epoch-0")
3131
+ },
3132
+ removedAt: Date.now(),
3133
+ removedInEpoch: result.epoch.id
3134
+ });
3135
+ yield* Effect21.forEach(
3136
+ result.wrappedEvents,
3137
+ (wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(
3138
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3139
+ Effect21.ignore
3140
+ ),
3141
+ { discard: true }
3142
+ );
3143
+ yield* Ref5.set(closedRef, true);
3144
+ syncHandle.stopHealing();
3145
+ yield* Scope4.close(scope, Exit.void);
3146
+ yield* deleteIDBStorage(config.dbName);
3147
+ })
3148
+ ),
3090
3149
  rebuild: () => ensureOpen(
3091
3150
  rebuild(
3092
3151
  storage,
@@ -3224,6 +3283,9 @@ function validateConfig(config) {
3224
3283
  }
3225
3284
  });
3226
3285
  }
3286
+ function deleteDatabase(dbName) {
3287
+ return deleteIDBStorage(DatabaseName(dbName ?? "tablinum"));
3288
+ }
3227
3289
  function createTablinum(config) {
3228
3290
  return Effect22.gen(function* () {
3229
3291
  yield* validateConfig(config);
@@ -3245,6 +3307,9 @@ function createTablinum(config) {
3245
3307
  });
3246
3308
  }
3247
3309
 
3310
+ // src/svelte/tablinum.svelte.ts
3311
+ import { Effect as Effect25, Exit as Exit2, References as References6, Scope as Scope6 } from "effect";
3312
+
3248
3313
  // src/svelte/collection.svelte.ts
3249
3314
  import { Effect as Effect24, Fiber, Option as Option11, References as References5, Stream as Stream4 } from "effect";
3250
3315
 
@@ -3479,9 +3544,11 @@ var Tablinum2 = class {
3479
3544
  #closed = false;
3480
3545
  #readyState = createDeferred();
3481
3546
  #logLevel;
3547
+ #dbName;
3482
3548
  constructor(config) {
3483
3549
  this.ready = this.#readyState.promise;
3484
3550
  this.#logLevel = resolveLogLevel(config.logLevel);
3551
+ this.#dbName = config.dbName ?? "tablinum";
3485
3552
  this.#init(config);
3486
3553
  }
3487
3554
  #settleReady(err) {
@@ -3616,6 +3683,14 @@ var Tablinum2 = class {
3616
3683
  }
3617
3684
  this.status = "closed";
3618
3685
  };
3686
+ destroy = async () => {
3687
+ await this.close();
3688
+ await Effect25.runPromise(deleteDatabase(this.#dbName));
3689
+ };
3690
+ leave = async () => {
3691
+ await this.#runHandleEffect((handle) => handle.leave());
3692
+ await Effect25.runPromise(deleteDatabase(this.#dbName));
3693
+ };
3619
3694
  sync = async () => this.#runHandleEffect((handle) => handle.sync());
3620
3695
  rebuild = async () => this.#runHandleEffect((handle) => handle.rebuild());
3621
3696
  addMember = async (pubkey) => this.#runHandleEffect((handle) => handle.addMember(pubkey));
@@ -3636,6 +3711,7 @@ export {
3636
3711
  ValidationError,
3637
3712
  collection,
3638
3713
  decodeInvite,
3714
+ deleteDatabase,
3639
3715
  encodeInvite,
3640
3716
  field
3641
3717
  };