tablinum 0.4.0 → 0.6.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.
@@ -1,14 +1,13 @@
1
1
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
2
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
3
  }) : x)(function(x) {
4
- if (typeof require !== "undefined")
5
- return require.apply(this, arguments);
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
5
  throw Error('Dynamic require of "' + x + '" is not supported');
7
6
  });
8
7
 
9
8
  // src/schema/field.ts
10
9
  function make(kind, isOptional, isArray, fields) {
11
- return { _tag: "FieldDef", kind, isOptional, isArray, ...fields !== undefined && { fields } };
10
+ return { _tag: "FieldDef", kind, isOptional, isArray, ...fields !== void 0 && { fields } };
12
11
  }
13
12
  var field = {
14
13
  string: () => make("string", false, false),
@@ -19,8 +18,9 @@ var field = {
19
18
  optional: (inner) => make(inner.kind, true, inner.isArray, inner.fields),
20
19
  array: (inner) => make(inner.kind, inner.isOptional, true, inner.fields)
21
20
  };
21
+
22
22
  // src/schema/collection.ts
23
- var RESERVED_NAMES = new Set(["id", "_d", "_u", "_e", "_a"]);
23
+ var RESERVED_NAMES = /* @__PURE__ */ new Set(["id", "_d", "_u", "_e", "_a"]);
24
24
  function collection(name, fields, options) {
25
25
  if (!name || name.trim().length === 0) {
26
26
  throw new Error("Collection name must not be empty");
@@ -42,7 +42,9 @@ function collection(name, fields, options) {
42
42
  throw new Error(`Index field "${idx}" does not exist in collection "${name}"`);
43
43
  }
44
44
  if (fieldDef.kind === "json" || fieldDef.kind === "object" || fieldDef.isArray) {
45
- throw new Error(`Field "${idx}" cannot be indexed (type: ${fieldDef.kind}${fieldDef.isArray ? "[]" : ""})`);
45
+ throw new Error(
46
+ `Field "${idx}" cannot be indexed (type: ${fieldDef.kind}${fieldDef.isArray ? "[]" : ""})`
47
+ );
46
48
  }
47
49
  indices.push(idx);
48
50
  }
@@ -53,6 +55,7 @@ function collection(name, fields, options) {
53
55
  }
54
56
  return { _tag: "CollectionDef", name, fields, indices, eventRetention };
55
57
  }
58
+
56
59
  // src/db/invite.ts
57
60
  import { Schema as Schema2 } from "effect";
58
61
 
@@ -82,15 +85,17 @@ var PersistedEpochStoreSchema = Schema.Struct({
82
85
  epochs: Schema.Array(PersistedEpochSchema),
83
86
  currentEpochId: Schema.String
84
87
  });
85
- var decodePersistedEpochStore = Schema.decodeUnknownSync(Schema.fromJsonString(PersistedEpochStoreSchema));
88
+ var decodePersistedEpochStore = Schema.decodeUnknownSync(
89
+ Schema.fromJsonString(PersistedEpochStoreSchema)
90
+ );
86
91
  function createEpochKey(id, privateKeyHex, createdBy, parentEpoch) {
87
92
  const publicKey = getPublicKey(hexToBytes(privateKeyHex));
88
93
  const base = { id, privateKey: privateKeyHex, publicKey, createdBy };
89
- return parentEpoch !== undefined ? { ...base, parentEpoch } : base;
94
+ return parentEpoch !== void 0 ? { ...base, parentEpoch } : base;
90
95
  }
91
96
  function createEpochStore(initialEpoch) {
92
- const epochs = new Map;
93
- const keysByPublicKey = new Map;
97
+ const epochs = /* @__PURE__ */ new Map();
98
+ const keysByPublicKey = /* @__PURE__ */ new Map();
94
99
  epochs.set(initialEpoch.id, initialEpoch);
95
100
  keysByPublicKey.set(initialEpoch.publicKey, hexToBytes(initialEpoch.privateKey));
96
101
  return { epochs, keysByPublicKey, currentEpochId: initialEpoch.id };
@@ -100,7 +105,14 @@ function addEpoch(store, epoch) {
100
105
  store.keysByPublicKey.set(epoch.publicKey, hexToBytes(epoch.privateKey));
101
106
  }
102
107
  function hydrateEpochStore(snapshot) {
103
- const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map((epoch) => createEpochKey(EpochId(epoch.id), epoch.privateKey, epoch.createdBy, epoch.parentEpoch !== undefined ? EpochId(epoch.parentEpoch) : undefined));
108
+ const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map(
109
+ (epoch) => createEpochKey(
110
+ EpochId(epoch.id),
111
+ epoch.privateKey,
112
+ epoch.createdBy,
113
+ epoch.parentEpoch !== void 0 ? EpochId(epoch.parentEpoch) : void 0
114
+ )
115
+ );
104
116
  if (!firstEpoch) {
105
117
  throw new Error("Epoch snapshot must contain at least one epoch");
106
118
  }
@@ -116,9 +128,16 @@ function createEpochStoreFromInputs(epochKeys, options = {}) {
116
128
  throw new Error("Epoch input must contain at least one key");
117
129
  }
118
130
  const createdBy = options.createdBy ?? "";
119
- const epochs = epochKeys.map((epochKey, index) => createEpochKey(epochKey.epochId, epochKey.key, createdBy, index > 0 ? epochKeys[index - 1].epochId : undefined));
131
+ const epochs = epochKeys.map(
132
+ (epochKey, index) => createEpochKey(
133
+ epochKey.epochId,
134
+ epochKey.key,
135
+ createdBy,
136
+ index > 0 ? epochKeys[index - 1].epochId : void 0
137
+ )
138
+ );
120
139
  const store = createEpochStore(epochs[0]);
121
- for (let i = 1;i < epochs.length; i++) {
140
+ for (let i = 1; i < epochs.length; i++) {
122
141
  addEpoch(store, epochs[i]);
123
142
  }
124
143
  store.currentEpochId = epochs[epochs.length - 1].id;
@@ -145,7 +164,7 @@ function serializeEpochStore(store) {
145
164
  id: epoch.id,
146
165
  privateKey: epoch.privateKey,
147
166
  createdBy: epoch.createdBy,
148
- ...epoch.parentEpoch !== undefined ? { parentEpoch: epoch.parentEpoch } : {}
167
+ ...epoch.parentEpoch !== void 0 ? { parentEpoch: epoch.parentEpoch } : {}
149
168
  })),
150
169
  currentEpochId: store.currentEpochId
151
170
  };
@@ -193,34 +212,29 @@ function decodeInvite(encoded) {
193
212
  throw new Error("Invalid invite: unexpected shape");
194
213
  }
195
214
  }
215
+
196
216
  // src/errors.ts
197
217
  import { Data } from "effect";
218
+ var ValidationError = class extends Data.TaggedError("ValidationError") {
219
+ };
220
+ var StorageError = class extends Data.TaggedError("StorageError") {
221
+ };
222
+ var CryptoError = class extends Data.TaggedError("CryptoError") {
223
+ };
224
+ var RelayError = class extends Data.TaggedError("RelayError") {
225
+ };
226
+ var SyncError = class extends Data.TaggedError("SyncError") {
227
+ };
228
+ var NotFoundError = class extends Data.TaggedError("NotFoundError") {
229
+ };
230
+ var ClosedError = class extends Data.TaggedError("ClosedError") {
231
+ };
198
232
 
199
- class ValidationError extends Data.TaggedError("ValidationError") {
200
- }
201
-
202
- class StorageError extends Data.TaggedError("StorageError") {
203
- }
204
-
205
- class CryptoError extends Data.TaggedError("CryptoError") {
206
- }
207
-
208
- class RelayError extends Data.TaggedError("RelayError") {
209
- }
210
-
211
- class SyncError extends Data.TaggedError("SyncError") {
212
- }
213
-
214
- class NotFoundError extends Data.TaggedError("NotFoundError") {
215
- }
216
-
217
- class ClosedError extends Data.TaggedError("ClosedError") {
218
- }
219
233
  // src/svelte/tablinum.svelte.ts
220
- import { Effect as Effect25, Exit as Exit2, Scope as Scope6 } from "effect";
234
+ import { Effect as Effect25, Exit as Exit2, References as References6, Scope as Scope6 } from "effect";
221
235
 
222
236
  // src/db/create-tablinum.ts
223
- import { Effect as Effect22, Layer as Layer9, ServiceMap as ServiceMap10 } from "effect";
237
+ import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
224
238
 
225
239
  // src/db/runtime-config.ts
226
240
  import { Effect, Schema as Schema3 } from "effect";
@@ -232,49 +246,61 @@ var RuntimeConfigSchema = Schema3.Struct({
232
246
  epochKeys: Schema3.optional(Schema3.Array(EpochKeyInputSchema))
233
247
  });
234
248
  function resolveRuntimeConfig(source) {
235
- return Schema3.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(Effect.map((config) => ({
236
- relays: [...config.relays],
237
- privateKey: config.privateKey,
238
- epochKeys: config.epochKeys?.map((ek) => ({ epochId: EpochId(ek.epochId), key: ek.key })),
239
- dbName: DatabaseName(config.dbName ?? "tablinum")
240
- })), Effect.mapError((error) => new ValidationError({
241
- message: `Invalid Tablinum configuration: ${error.message}`
242
- })));
249
+ return Schema3.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(
250
+ Effect.map((config) => ({
251
+ relays: [...config.relays],
252
+ privateKey: config.privateKey,
253
+ epochKeys: config.epochKeys?.map((ek) => ({ epochId: EpochId(ek.epochId), key: ek.key })),
254
+ dbName: DatabaseName(config.dbName ?? "tablinum")
255
+ })),
256
+ Effect.mapError(
257
+ (error) => new ValidationError({
258
+ message: `Invalid Tablinum configuration: ${error.message}`
259
+ })
260
+ )
261
+ );
243
262
  }
244
263
 
245
264
  // src/services/Config.ts
246
265
  import { ServiceMap } from "effect";
247
-
248
- class Config extends ServiceMap.Service()("tablinum/Config") {
249
- }
266
+ var Config = class extends ServiceMap.Service()("tablinum/Config") {
267
+ };
250
268
 
251
269
  // src/services/Tablinum.ts
252
270
  import { ServiceMap as ServiceMap2 } from "effect";
253
-
254
- class Tablinum extends ServiceMap2.Service()("tablinum/Tablinum") {
255
- }
271
+ var Tablinum = class extends ServiceMap2.Service()(
272
+ "tablinum/Tablinum"
273
+ ) {
274
+ };
256
275
 
257
276
  // src/layers/TablinumLive.ts
258
- import { Effect as Effect21, Exit, Layer as Layer8, Option as Option9, PubSub as PubSub2, Ref as Ref5, Scope as Scope4 } from "effect";
277
+ 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";
259
278
 
260
279
  // src/crud/watch.ts
261
280
  import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
262
- function watchCollection(ctx, storage, collectionName, filter, mapRecord) {
281
+ function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
263
282
  const query = () => Effect2.map(storage.getAllRecords(collectionName), (all) => {
264
283
  const filtered = all.filter((r) => !r._d && (filter ? filter(r) : true));
265
- return mapRecord ? filtered.map(mapRecord) : filtered;
284
+ return mapRecord2 ? filtered.map(mapRecord2) : filtered;
266
285
  });
267
- const changes = Stream.fromPubSub(ctx.pubsub).pipe(Stream.filter((event) => event.collection === collectionName), Stream.mapEffect(() => Effect2.gen(function* () {
268
- const replaying = yield* Ref.get(ctx.replayingRef);
269
- if (replaying)
270
- return;
271
- return yield* query();
272
- })), Stream.filter((result) => result !== undefined));
273
- return Stream.unwrap(Effect2.gen(function* () {
274
- yield* Effect2.sleep(0);
275
- const initial = yield* query();
276
- return Stream.concat(Stream.make(initial), changes);
277
- }));
286
+ const changes = Stream.fromPubSub(ctx.pubsub).pipe(
287
+ Stream.filter((event) => event.collection === collectionName),
288
+ Stream.mapEffect(
289
+ () => Effect2.gen(function* () {
290
+ const replaying = yield* Ref.get(ctx.replayingRef);
291
+ if (replaying) return void 0;
292
+ return yield* query();
293
+ })
294
+ ),
295
+ Stream.filter((result) => result !== void 0)
296
+ );
297
+ return Stream.unwrap(
298
+ Effect2.gen(function* () {
299
+ yield* Effect2.sleep(0);
300
+ const initial = yield* query();
301
+ return Stream.concat(Stream.make(initial), changes);
302
+ })
303
+ );
278
304
  }
279
305
  function notifyChange(ctx, event) {
280
306
  return PubSub.publish(ctx.pubsub, event).pipe(Effect2.asVoid);
@@ -297,12 +323,9 @@ import { Effect as Effect3 } from "effect";
297
323
 
298
324
  // src/storage/lww.ts
299
325
  function resolveWinner(existing, incoming) {
300
- if (existing === null)
301
- return incoming;
302
- if (incoming.createdAt > existing.createdAt)
303
- return incoming;
304
- if (incoming.createdAt < existing.createdAt)
305
- return existing;
326
+ if (existing === null) return incoming;
327
+ if (incoming.createdAt > existing.createdAt) return incoming;
328
+ if (incoming.createdAt < existing.createdAt) return existing;
306
329
  return incoming.id < existing.id ? incoming : existing;
307
330
  }
308
331
 
@@ -369,8 +392,7 @@ function applyEvent(storage, event) {
369
392
  };
370
393
  const incomingMeta = { id: event.id, createdAt: event.createdAt };
371
394
  const winner = resolveWinner(existingMeta, incomingMeta);
372
- if (winner.id !== event.id)
373
- return false;
395
+ if (winner.id !== event.id) return false;
374
396
  }
375
397
  if (existing && event.kind === "u") {
376
398
  yield* storage.putRecord(event.collection, deepMerge(existing, buildRecord(event)));
@@ -386,10 +408,11 @@ function rebuild(storage, collections) {
386
408
  yield* storage.clearRecords(col);
387
409
  }
388
410
  const allEvents = yield* storage.getAllEvents();
389
- const sorted = [...allEvents].sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1));
411
+ const sorted = [...allEvents].sort(
412
+ (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1)
413
+ );
390
414
  for (const event of sorted) {
391
- if (event.data === null && event.kind !== "d")
392
- continue;
415
+ if (event.data === null && event.kind !== "d") continue;
393
416
  yield* applyEvent(storage, event);
394
417
  }
395
418
  });
@@ -442,9 +465,16 @@ function buildStructSchema(def, options = {}) {
442
465
  }
443
466
  function buildValidator(collectionName, def) {
444
467
  const decode = Schema4.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
445
- return (input) => decode(input).pipe(Effect4.map((result) => result), Effect4.mapError((e) => new ValidationError({
446
- message: `Validation failed for collection "${collectionName}": ${e.message}`
447
- })));
468
+ return (input) => decode(input).pipe(
469
+ Effect4.map(
470
+ (result) => result
471
+ ),
472
+ Effect4.mapError(
473
+ (e) => new ValidationError({
474
+ message: `Validation failed for collection "${collectionName}": ${e.message}`
475
+ })
476
+ )
477
+ );
448
478
  }
449
479
  function buildPartialValidator(collectionName, def) {
450
480
  const decode = Schema4.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
@@ -456,35 +486,38 @@ function buildPartialValidator(collectionName, def) {
456
486
  }
457
487
  const record = input;
458
488
  const unknownField = Object.keys(record).find((key) => !Object.hasOwn(def.fields, key));
459
- if (unknownField !== undefined) {
489
+ if (unknownField !== void 0) {
460
490
  return yield* new ValidationError({
461
491
  message: `Unknown field "${unknownField}" in collection "${collectionName}"`,
462
492
  field: unknownField
463
493
  });
464
494
  }
465
- return yield* decode(record).pipe(Effect4.map((result) => result), Effect4.mapError((e) => new ValidationError({
466
- message: `Validation failed for collection "${collectionName}": ${e.message}`
467
- })));
495
+ return yield* decode(record).pipe(
496
+ Effect4.map((result) => result),
497
+ Effect4.mapError(
498
+ (e) => new ValidationError({
499
+ message: `Validation failed for collection "${collectionName}": ${e.message}`
500
+ })
501
+ )
502
+ );
468
503
  });
469
504
  }
470
505
 
471
506
  // src/crud/collection-handle.ts
472
- import { Effect as Effect6, Option as Option3 } from "effect";
507
+ import { Effect as Effect6, Option as Option3, References } from "effect";
473
508
 
474
509
  // src/utils/uuid.ts
475
510
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
476
511
  function toBase64url(bytes) {
477
512
  let result = "";
478
- for (let i = 0;i < bytes.length; i += 3) {
513
+ for (let i = 0; i < bytes.length; i += 3) {
479
514
  const b0 = bytes[i];
480
515
  const b1 = bytes[i + 1] ?? 0;
481
516
  const b2 = bytes[i + 2] ?? 0;
482
517
  result += alphabet[b0 >> 2];
483
518
  result += alphabet[(b0 & 3) << 4 | b1 >> 4];
484
- if (i + 1 < bytes.length)
485
- result += alphabet[(b1 & 15) << 2 | b2 >> 6];
486
- if (i + 2 < bytes.length)
487
- result += alphabet[b2 & 63];
519
+ if (i + 1 < bytes.length) result += alphabet[(b1 & 15) << 2 | b2 >> 6];
520
+ if (i + 2 < bytes.length) result += alphabet[b2 & 63];
488
521
  }
489
522
  return result;
490
523
  }
@@ -529,16 +562,28 @@ function executeQuery(ctx, plan) {
529
562
  if (plan.indexQuery && ctx.def.indices.includes(plan.indexQuery.field)) {
530
563
  if (plan.indexQuery.type === "value") {
531
564
  results = [
532
- ...yield* ctx.storage.getByIndex(ctx.collectionName, plan.indexQuery.field, plan.indexQuery.range)
565
+ ...yield* ctx.storage.getByIndex(
566
+ ctx.collectionName,
567
+ plan.indexQuery.field,
568
+ plan.indexQuery.range
569
+ )
533
570
  ];
534
571
  } else {
535
572
  results = [
536
- ...yield* ctx.storage.getByIndexRange(ctx.collectionName, plan.indexQuery.field, plan.indexQuery.range)
573
+ ...yield* ctx.storage.getByIndexRange(
574
+ ctx.collectionName,
575
+ plan.indexQuery.field,
576
+ plan.indexQuery.range
577
+ )
537
578
  ];
538
579
  }
539
580
  } else if (plan.orderBy && ctx.def.indices.includes(plan.orderBy.field) && plan.filters.length === 0) {
540
581
  results = [
541
- ...yield* ctx.storage.getAllSorted(ctx.collectionName, plan.orderBy.field, plan.orderBy.direction === "desc" ? "prev" : "next")
582
+ ...yield* ctx.storage.getAllSorted(
583
+ ctx.collectionName,
584
+ plan.orderBy.field,
585
+ plan.orderBy.direction === "desc" ? "prev" : "next"
586
+ )
542
587
  ];
543
588
  } else {
544
589
  results = [...yield* ctx.storage.getAllRecords(ctx.collectionName)];
@@ -562,7 +607,7 @@ function executeQuery(ctx, plan) {
562
607
  if (plan.offset) {
563
608
  results = results.slice(plan.offset);
564
609
  }
565
- if (plan.limit !== null && plan.limit !== undefined) {
610
+ if (plan.limit !== null && plan.limit !== void 0) {
566
611
  results = results.slice(0, plan.limit);
567
612
  }
568
613
  return results.map(ctx.mapRecord);
@@ -570,16 +615,23 @@ function executeQuery(ctx, plan) {
570
615
  }
571
616
  function watchQuery(ctx, plan) {
572
617
  const query = () => executeQuery(ctx, plan);
573
- const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(Stream2.filter((event) => event.collection === ctx.collectionName), Stream2.mapEffect(() => Effect5.gen(function* () {
574
- const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
575
- if (replaying)
576
- return;
577
- return yield* query();
578
- })), Stream2.filter((result) => result !== undefined));
579
- return Stream2.unwrap(Effect5.gen(function* () {
580
- const initial = yield* query();
581
- return Stream2.concat(Stream2.make(initial), changes);
582
- }));
618
+ const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
619
+ Stream2.filter((event) => event.collection === ctx.collectionName),
620
+ Stream2.mapEffect(
621
+ () => Effect5.gen(function* () {
622
+ const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
623
+ if (replaying) return void 0;
624
+ return yield* query();
625
+ })
626
+ ),
627
+ Stream2.filter((result) => result !== void 0)
628
+ );
629
+ return Stream2.unwrap(
630
+ Effect5.gen(function* () {
631
+ const initial = yield* query();
632
+ return Stream2.concat(Stream2.make(initial), changes);
633
+ })
634
+ );
583
635
  }
584
636
  function makeQueryBuilder(ctx, plan) {
585
637
  return {
@@ -601,15 +653,18 @@ function makeQueryBuilder(ctx, plan) {
601
653
  offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
602
654
  limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
603
655
  get: () => executeQuery(ctx, plan),
604
- first: () => Effect5.map(executeQuery(ctx, { ...plan, limit: 1 }), (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()),
656
+ first: () => Effect5.map(
657
+ executeQuery(ctx, { ...plan, limit: 1 }),
658
+ (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
659
+ ),
605
660
  count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
606
661
  watch: () => watchQuery(ctx, plan)
607
662
  };
608
663
  }
609
- function createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord) {
610
- const ctx = { storage, watchCtx, collectionName, def, mapRecord };
664
+ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
665
+ const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
611
666
  const fieldDef = def.fields[fieldName];
612
- const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !== undefined && fieldDef.kind !== "boolean";
667
+ const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !== void 0 && fieldDef.kind !== "boolean";
613
668
  const withFilter = (filterFn, indexQuery) => {
614
669
  const plan = {
615
670
  ...emptyPlan(),
@@ -620,30 +675,51 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
620
675
  return makeQueryBuilder(ctx, plan);
621
676
  };
622
677
  return {
623
- equals: (value) => withFilter((r) => r[fieldName] === value, isIndexed ? { field: fieldName, range: value, type: "value" } : undefined),
624
- above: (value) => withFilter((r) => r[fieldName] > value, isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, true), type: "range" } : undefined),
625
- aboveOrEqual: (value) => withFilter((r) => r[fieldName] >= value, isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, false), type: "range" } : undefined),
626
- below: (value) => withFilter((r) => r[fieldName] < value, isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, true), type: "range" } : undefined),
627
- belowOrEqual: (value) => withFilter((r) => r[fieldName] <= value, isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, false), type: "range" } : undefined),
678
+ equals: (value) => withFilter(
679
+ (r) => r[fieldName] === value,
680
+ isIndexed ? { field: fieldName, range: value, type: "value" } : void 0
681
+ ),
682
+ above: (value) => withFilter(
683
+ (r) => r[fieldName] > value,
684
+ isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, true), type: "range" } : void 0
685
+ ),
686
+ aboveOrEqual: (value) => withFilter(
687
+ (r) => r[fieldName] >= value,
688
+ isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, false), type: "range" } : void 0
689
+ ),
690
+ below: (value) => withFilter(
691
+ (r) => r[fieldName] < value,
692
+ isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, true), type: "range" } : void 0
693
+ ),
694
+ belowOrEqual: (value) => withFilter(
695
+ (r) => r[fieldName] <= value,
696
+ isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, false), type: "range" } : void 0
697
+ ),
628
698
  between: (lower, upper, options) => {
629
699
  const includeLower = options?.includeLower ?? true;
630
700
  const includeUpper = options?.includeUpper ?? true;
631
- return withFilter((r) => {
632
- const v = r[fieldName];
633
- const aboveLower = includeLower ? v >= lower : v > lower;
634
- const belowUpper = includeUpper ? v <= upper : v < upper;
635
- return aboveLower && belowUpper;
636
- }, isIndexed ? {
701
+ return withFilter(
702
+ (r) => {
703
+ const v = r[fieldName];
704
+ const aboveLower = includeLower ? v >= lower : v > lower;
705
+ const belowUpper = includeUpper ? v <= upper : v < upper;
706
+ return aboveLower && belowUpper;
707
+ },
708
+ isIndexed ? {
709
+ field: fieldName,
710
+ range: IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper),
711
+ type: "range"
712
+ } : void 0
713
+ );
714
+ },
715
+ startsWith: (prefix) => withFilter(
716
+ (r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix),
717
+ isIndexed ? {
637
718
  field: fieldName,
638
- range: IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper),
719
+ range: IDBKeyRange.bound(prefix, prefix + "\uFFFF", false, false),
639
720
  type: "range"
640
- } : undefined);
641
- },
642
- startsWith: (prefix) => withFilter((r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix), isIndexed ? {
643
- field: fieldName,
644
- range: IDBKeyRange.bound(prefix, prefix + "￿", false, false),
645
- type: "range"
646
- } : undefined),
721
+ } : void 0
722
+ ),
647
723
  anyOf: (values) => {
648
724
  const set = new Set(values);
649
725
  return withFilter((r) => set.has(r[fieldName]));
@@ -654,8 +730,8 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
654
730
  }
655
731
  };
656
732
  }
657
- function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord) {
658
- const ctx = { storage, watchCtx, collectionName, def, mapRecord };
733
+ function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
734
+ const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
659
735
  const plan = {
660
736
  ...emptyPlan(),
661
737
  orderBy: { field: fieldName, direction: "asc" }
@@ -680,7 +756,7 @@ function replayState(recordId, events, stopAtId) {
680
756
  state = deepMerge(state, e.data);
681
757
  }
682
758
  }
683
- if (stopAtId !== undefined && e.id === stopAtId) {
759
+ if (stopAtId !== void 0 && e.id === stopAtId) {
684
760
  break;
685
761
  }
686
762
  }
@@ -698,8 +774,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
698
774
  function pruneEvents(storage, collection2, recordId, retention) {
699
775
  return Effect6.gen(function* () {
700
776
  const events = yield* storage.getEventsByRecord(collection2, recordId);
701
- if (events.length <= retention)
702
- return;
777
+ if (events.length <= retention) return;
703
778
  const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
704
779
  const retained = sorted.slice(0, retention);
705
780
  const toStrip = sorted.slice(retention);
@@ -708,8 +783,7 @@ function pruneEvents(storage, collection2, recordId, retention) {
708
783
  yield* promoteToSnapshot(storage, collection2, recordId, oldestRetained, sorted);
709
784
  }
710
785
  for (const e of toStrip) {
711
- if (e.data !== null)
712
- yield* storage.stripEventData(e.id);
786
+ if (e.data !== null) yield* storage.stripEventData(e.id);
713
787
  }
714
788
  });
715
789
  }
@@ -717,13 +791,13 @@ function mapRecord(record) {
717
791
  const { _d, _u, _a, _e, ...fields } = record;
718
792
  return fields;
719
793
  }
720
- function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite) {
794
+ function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
721
795
  const collectionName = def.name;
796
+ const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
722
797
  const commitEvent = (event) => Effect6.gen(function* () {
723
798
  yield* storage.putEvent(event);
724
799
  yield* applyEvent(storage, event);
725
- if (onWrite)
726
- yield* onWrite(event);
800
+ if (onWrite) yield* onWrite(event);
727
801
  yield* notifyChange(watchCtx, {
728
802
  collection: collectionName,
729
803
  recordId: event.recordId,
@@ -731,67 +805,84 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
731
805
  });
732
806
  });
733
807
  const handle = {
734
- add: (data) => Effect6.gen(function* () {
735
- const id = uuidv7();
736
- const fullRecord = { id, ...data };
737
- yield* validator(fullRecord);
738
- const event = {
739
- id: makeEventId(),
740
- collection: collectionName,
741
- recordId: id,
742
- kind: "c",
743
- data: fullRecord,
744
- createdAt: Date.now(),
745
- author: localAuthor
746
- };
747
- yield* commitEvent(event);
748
- return id;
749
- }),
750
- update: (id, data) => Effect6.gen(function* () {
751
- const existing = yield* storage.getRecord(collectionName, id);
752
- if (!existing || existing._d) {
753
- return yield* new NotFoundError({
808
+ add: (data) => withLog(
809
+ Effect6.gen(function* () {
810
+ const id = uuidv7();
811
+ const fullRecord = { id, ...data };
812
+ yield* validator(fullRecord);
813
+ const event = {
814
+ id: makeEventId(),
754
815
  collection: collectionName,
755
- id
816
+ recordId: id,
817
+ kind: "c",
818
+ data: fullRecord,
819
+ createdAt: Date.now(),
820
+ author: localAuthor
821
+ };
822
+ yield* commitEvent(event);
823
+ yield* Effect6.logDebug("Record added", {
824
+ collection: collectionName,
825
+ recordId: id,
826
+ data: fullRecord
756
827
  });
757
- }
758
- yield* partialValidator(data);
759
- const { _d, _u, _a, _e, ...existingFields } = existing;
760
- const merged = { ...existingFields, ...data, id };
761
- yield* validator(merged);
762
- const diff = deepDiff(existingFields, merged);
763
- const event = {
764
- id: makeEventId(),
765
- collection: collectionName,
766
- recordId: id,
767
- kind: "u",
768
- data: diff ?? { id },
769
- createdAt: Date.now(),
770
- author: localAuthor
771
- };
772
- yield* commitEvent(event);
773
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
774
- }),
775
- delete: (id) => Effect6.gen(function* () {
776
- const existing = yield* storage.getRecord(collectionName, id);
777
- if (!existing || existing._d) {
778
- return yield* new NotFoundError({
828
+ return id;
829
+ })
830
+ ),
831
+ update: (id, data) => withLog(
832
+ Effect6.gen(function* () {
833
+ const existing = yield* storage.getRecord(collectionName, id);
834
+ if (!existing || existing._d) {
835
+ return yield* new NotFoundError({
836
+ collection: collectionName,
837
+ id
838
+ });
839
+ }
840
+ yield* partialValidator(data);
841
+ const { _d, _u, _a, _e, ...existingFields } = existing;
842
+ const merged = { ...existingFields, ...data, id };
843
+ yield* validator(merged);
844
+ const diff = deepDiff(existingFields, merged);
845
+ const event = {
846
+ id: makeEventId(),
779
847
  collection: collectionName,
780
- id
848
+ recordId: id,
849
+ kind: "u",
850
+ data: diff ?? { id },
851
+ createdAt: Date.now(),
852
+ author: localAuthor
853
+ };
854
+ yield* commitEvent(event);
855
+ yield* Effect6.logDebug("Record updated", {
856
+ collection: collectionName,
857
+ recordId: id,
858
+ data: diff
781
859
  });
782
- }
783
- const event = {
784
- id: makeEventId(),
785
- collection: collectionName,
786
- recordId: id,
787
- kind: "d",
788
- data: null,
789
- createdAt: Date.now(),
790
- author: localAuthor
791
- };
792
- yield* commitEvent(event);
793
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
794
- }),
860
+ yield* pruneEvents(storage, collectionName, id, def.eventRetention);
861
+ })
862
+ ),
863
+ delete: (id) => withLog(
864
+ Effect6.gen(function* () {
865
+ const existing = yield* storage.getRecord(collectionName, id);
866
+ if (!existing || existing._d) {
867
+ return yield* new NotFoundError({
868
+ collection: collectionName,
869
+ id
870
+ });
871
+ }
872
+ const event = {
873
+ id: makeEventId(),
874
+ collection: collectionName,
875
+ recordId: id,
876
+ kind: "d",
877
+ data: null,
878
+ createdAt: Date.now(),
879
+ author: localAuthor
880
+ };
881
+ yield* commitEvent(event);
882
+ yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
883
+ yield* pruneEvents(storage, collectionName, id, def.eventRetention);
884
+ })
885
+ ),
795
886
  undo: (id) => Effect6.gen(function* () {
796
887
  const existing = yield* storage.getRecord(collectionName, id);
797
888
  if (!existing) {
@@ -832,15 +923,29 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
832
923
  return found ? Option3.some(mapRecord(found)) : Option3.none();
833
924
  }),
834
925
  count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
835
- watch: () => watchCollection(watchCtx, storage, collectionName, undefined, mapRecord),
836
- where: (fieldName) => createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord),
837
- orderBy: (fieldName) => createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord)
926
+ watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
927
+ where: (fieldName) => createWhereClause(
928
+ storage,
929
+ watchCtx,
930
+ collectionName,
931
+ def,
932
+ fieldName,
933
+ mapRecord
934
+ ),
935
+ orderBy: (fieldName) => createOrderByBuilder(
936
+ storage,
937
+ watchCtx,
938
+ collectionName,
939
+ def,
940
+ fieldName,
941
+ mapRecord
942
+ )
838
943
  };
839
944
  return handle;
840
945
  }
841
946
 
842
947
  // src/sync/sync-service.ts
843
- import { Effect as Effect8, Option as Option5, Ref as Ref3, Schedule } from "effect";
948
+ import { Duration, Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
844
949
  import { unwrapEvent } from "nostr-tools/nip59";
845
950
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
846
951
 
@@ -856,8 +961,7 @@ var Mode = {
856
961
  Fingerprint: 1,
857
962
  IdList: 2
858
963
  };
859
-
860
- class WrappedBuffer {
964
+ var WrappedBuffer = class {
861
965
  constructor(buffer) {
862
966
  this._raw = new Uint8Array(buffer || 512);
863
967
  this.length = buffer ? buffer.length : 0;
@@ -869,10 +973,8 @@ class WrappedBuffer {
869
973
  return this._raw.byteLength;
870
974
  }
871
975
  extend(buf) {
872
- if (buf._raw)
873
- buf = buf.unwrap();
874
- if (typeof buf.length !== "number")
875
- throw Error("bad length");
976
+ if (buf._raw) buf = buf.unwrap();
977
+ if (typeof buf.length !== "number") throw Error("bad length");
876
978
  const targetSize = buf.length + this.length;
877
979
  if (this.capacity < targetSize) {
878
980
  const oldRaw = this._raw;
@@ -895,42 +997,36 @@ class WrappedBuffer {
895
997
  this.length -= n;
896
998
  return firstSubarray;
897
999
  }
898
- }
1000
+ };
899
1001
  function decodeVarInt(buf) {
900
1002
  let res = 0;
901
- while (true) {
902
- if (buf.length === 0)
903
- throw Error("parse ends prematurely");
1003
+ while (1) {
1004
+ if (buf.length === 0) throw Error("parse ends prematurely");
904
1005
  let byte = buf.shift();
905
1006
  res = res << 7 | byte & 127;
906
- if ((byte & 128) === 0)
907
- break;
1007
+ if ((byte & 128) === 0) break;
908
1008
  }
909
1009
  return res;
910
1010
  }
911
1011
  function encodeVarInt(n) {
912
- if (n === 0)
913
- return new WrappedBuffer([0]);
1012
+ if (n === 0) return new WrappedBuffer([0]);
914
1013
  let o = [];
915
1014
  while (n !== 0) {
916
1015
  o.push(n & 127);
917
1016
  n >>>= 7;
918
1017
  }
919
1018
  o.reverse();
920
- for (let i = 0;i < o.length - 1; i++)
921
- o[i] |= 128;
1019
+ for (let i = 0; i < o.length - 1; i++) o[i] |= 128;
922
1020
  return new WrappedBuffer(o);
923
1021
  }
924
1022
  function getByte(buf) {
925
1023
  return getBytes(buf, 1)[0];
926
1024
  }
927
1025
  function getBytes(buf, n) {
928
- if (buf.length < n)
929
- throw Error("parse ends prematurely");
1026
+ if (buf.length < n) throw Error("parse ends prematurely");
930
1027
  return buf.shiftN(n);
931
1028
  }
932
-
933
- class Accumulator {
1029
+ var Accumulator = class {
934
1030
  constructor() {
935
1031
  this.setToZero();
936
1032
  if (typeof window === "undefined") {
@@ -947,15 +1043,14 @@ class Accumulator {
947
1043
  let currCarry = 0, nextCarry = 0;
948
1044
  let p = new DataView(this.buf.buffer);
949
1045
  let po = new DataView(otherBuf.buffer);
950
- for (let i = 0;i < 8; i++) {
1046
+ for (let i = 0; i < 8; i++) {
951
1047
  let offset = i * 4;
952
1048
  let orig = p.getUint32(offset, true);
953
1049
  let otherV = po.getUint32(offset, true);
954
1050
  let next = orig;
955
1051
  next += currCarry;
956
1052
  next += otherV;
957
- if (next > 4294967295)
958
- nextCarry = 1;
1053
+ if (next > 4294967295) nextCarry = 1;
959
1054
  p.setUint32(offset, next & 4294967295, true);
960
1055
  currCarry = nextCarry;
961
1056
  nextCarry = 0;
@@ -963,7 +1058,7 @@ class Accumulator {
963
1058
  }
964
1059
  negate() {
965
1060
  let p = new DataView(this.buf.buffer);
966
- for (let i = 0;i < 8; i++) {
1061
+ for (let i = 0; i < 8; i++) {
967
1062
  let offset = i * 4;
968
1063
  p.setUint32(offset, ~p.getUint32(offset, true));
969
1064
  }
@@ -972,33 +1067,29 @@ class Accumulator {
972
1067
  this.add(one);
973
1068
  }
974
1069
  async getFingerprint(n) {
975
- let input = new WrappedBuffer;
1070
+ let input = new WrappedBuffer();
976
1071
  input.extend(this.buf);
977
1072
  input.extend(encodeVarInt(n));
978
1073
  let hash = await this.sha256(input.unwrap());
979
1074
  return hash.subarray(0, FINGERPRINT_SIZE);
980
1075
  }
981
- }
982
-
983
- class NegentropyStorageVector {
1076
+ };
1077
+ var NegentropyStorageVector = class {
984
1078
  constructor() {
985
1079
  this.items = [];
986
1080
  this.sealed = false;
987
1081
  }
988
1082
  insert(timestamp, id) {
989
- if (this.sealed)
990
- throw Error("already sealed");
1083
+ if (this.sealed) throw Error("already sealed");
991
1084
  id = loadInputBuffer(id);
992
- if (id.byteLength !== ID_SIZE)
993
- throw Error("bad id size for added item");
1085
+ if (id.byteLength !== ID_SIZE) throw Error("bad id size for added item");
994
1086
  this.items.push({ timestamp, id });
995
1087
  }
996
1088
  seal() {
997
- if (this.sealed)
998
- throw Error("already sealed");
1089
+ if (this.sealed) throw Error("already sealed");
999
1090
  this.sealed = true;
1000
1091
  this.items.sort(itemCompare);
1001
- for (let i = 1;i < this.items.length; i++) {
1092
+ for (let i = 1; i < this.items.length; i++) {
1002
1093
  if (itemCompare(this.items[i - 1], this.items[i]) === 0)
1003
1094
  throw Error("duplicate item inserted");
1004
1095
  }
@@ -1012,16 +1103,14 @@ class NegentropyStorageVector {
1012
1103
  }
1013
1104
  getItem(i) {
1014
1105
  this._checkSealed();
1015
- if (i >= this.items.length)
1016
- throw Error("out of range");
1106
+ if (i >= this.items.length) throw Error("out of range");
1017
1107
  return this.items[i];
1018
1108
  }
1019
1109
  iterate(begin, end, cb) {
1020
1110
  this._checkSealed();
1021
1111
  this._checkBounds(begin, end);
1022
- for (let i = begin;i < end; ++i) {
1023
- if (!cb(this.items[i], i))
1024
- break;
1112
+ for (let i = begin; i < end; ++i) {
1113
+ if (!cb(this.items[i], i)) break;
1025
1114
  }
1026
1115
  }
1027
1116
  findLowerBound(begin, end, bound) {
@@ -1030,7 +1119,7 @@ class NegentropyStorageVector {
1030
1119
  return this._binarySearch(this.items, begin, end, (a) => itemCompare(a, bound) < 0);
1031
1120
  }
1032
1121
  async fingerprint(begin, end) {
1033
- let out = new Accumulator;
1122
+ let out = new Accumulator();
1034
1123
  out.setToZero();
1035
1124
  this.iterate(begin, end, (item, i) => {
1036
1125
  out.add(item.id);
@@ -1039,12 +1128,10 @@ class NegentropyStorageVector {
1039
1128
  return await out.getFingerprint(end - begin);
1040
1129
  }
1041
1130
  _checkSealed() {
1042
- if (!this.sealed)
1043
- throw Error("not sealed");
1131
+ if (!this.sealed) throw Error("not sealed");
1044
1132
  }
1045
1133
  _checkBounds(begin, end) {
1046
- if (begin > end || end > this.items.length)
1047
- throw Error("bad range");
1134
+ if (begin > end || end > this.items.length) throw Error("bad range");
1048
1135
  }
1049
1136
  _binarySearch(arr, first, last, cmp) {
1050
1137
  let count = last - first;
@@ -1061,12 +1148,10 @@ class NegentropyStorageVector {
1061
1148
  }
1062
1149
  return first;
1063
1150
  }
1064
- }
1065
-
1066
- class Negentropy {
1151
+ };
1152
+ var Negentropy = class {
1067
1153
  constructor(storage, frameSizeLimit = 0) {
1068
- if (frameSizeLimit !== 0 && frameSizeLimit < 4096)
1069
- throw Error("frameSizeLimit too small");
1154
+ if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small");
1070
1155
  this.storage = storage;
1071
1156
  this.frameSizeLimit = frameSizeLimit;
1072
1157
  this.lastTimestampIn = 0;
@@ -1076,10 +1161,9 @@ class Negentropy {
1076
1161
  return { timestamp, id: id ? id : new Uint8Array(0) };
1077
1162
  }
1078
1163
  async initiate() {
1079
- if (this.isInitiator)
1080
- throw Error("already initiated");
1164
+ if (this.isInitiator) throw Error("already initiated");
1081
1165
  this.isInitiator = true;
1082
- let output = new WrappedBuffer;
1166
+ let output = new WrappedBuffer();
1083
1167
  output.extend([PROTOCOL_VERSION]);
1084
1168
  await this.splitRange(0, this.storage.size(), this._bound(Number.MAX_VALUE), output);
1085
1169
  return this._renderOutput(output);
@@ -1091,23 +1175,24 @@ class Negentropy {
1091
1175
  let haveIds = [], needIds = [];
1092
1176
  query = new WrappedBuffer(loadInputBuffer(query));
1093
1177
  this.lastTimestampIn = this.lastTimestampOut = 0;
1094
- let fullOutput = new WrappedBuffer;
1178
+ let fullOutput = new WrappedBuffer();
1095
1179
  fullOutput.extend([PROTOCOL_VERSION]);
1096
1180
  let protocolVersion = getByte(query);
1097
1181
  if (protocolVersion < 96 || protocolVersion > 111)
1098
1182
  throw Error("invalid negentropy protocol version byte");
1099
1183
  if (protocolVersion !== PROTOCOL_VERSION) {
1100
1184
  if (this.isInitiator)
1101
- throw Error("unsupported negentropy protocol version requested: " + (protocolVersion - 96));
1102
- else
1103
- return [this._renderOutput(fullOutput), haveIds, needIds];
1185
+ throw Error(
1186
+ "unsupported negentropy protocol version requested: " + (protocolVersion - 96)
1187
+ );
1188
+ else return [this._renderOutput(fullOutput), haveIds, needIds];
1104
1189
  }
1105
1190
  let storageSize = this.storage.size();
1106
1191
  let prevBound = this._bound(0);
1107
1192
  let prevIndex = 0;
1108
1193
  let skip = false;
1109
1194
  while (query.length !== 0) {
1110
- let o = new WrappedBuffer;
1195
+ let o = new WrappedBuffer();
1111
1196
  let doSkip = () => {
1112
1197
  if (skip) {
1113
1198
  skip = false;
@@ -1133,10 +1218,9 @@ class Negentropy {
1133
1218
  } else if (mode === Mode.IdList) {
1134
1219
  let numIds = decodeVarInt(query);
1135
1220
  let theirElems = {};
1136
- for (let i = 0;i < numIds; i++) {
1221
+ for (let i = 0; i < numIds; i++) {
1137
1222
  let e = getBytes(query, ID_SIZE);
1138
- if (this.isInitiator)
1139
- theirElems[e] = e;
1223
+ if (this.isInitiator) theirElems[e] = e;
1140
1224
  }
1141
1225
  if (this.isInitiator) {
1142
1226
  skip = true;
@@ -1155,7 +1239,7 @@ class Negentropy {
1155
1239
  }
1156
1240
  } else {
1157
1241
  doSkip();
1158
- let responseIds = new WrappedBuffer;
1242
+ let responseIds = new WrappedBuffer();
1159
1243
  let numResponseIds = 0;
1160
1244
  let endBound = currBound;
1161
1245
  this.storage.iterate(lower, upper, (item, index) => {
@@ -1173,7 +1257,7 @@ class Negentropy {
1173
1257
  o.extend(encodeVarInt(numResponseIds));
1174
1258
  o.extend(responseIds);
1175
1259
  fullOutput.extend(o);
1176
- o = new WrappedBuffer;
1260
+ o = new WrappedBuffer();
1177
1261
  }
1178
1262
  } else {
1179
1263
  throw Error("unexpected mode");
@@ -1211,7 +1295,7 @@ class Negentropy {
1211
1295
  let itemsPerBucket = Math.floor(numElems / buckets);
1212
1296
  let bucketsWithExtra = numElems % buckets;
1213
1297
  let curr = lower;
1214
- for (let i = 0;i < buckets; i++) {
1298
+ for (let i = 0; i < buckets; i++) {
1215
1299
  let bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);
1216
1300
  let ourFingerprint = await this.storage.fingerprint(curr, curr + bucketSize);
1217
1301
  curr += bucketSize;
@@ -1221,10 +1305,8 @@ class Negentropy {
1221
1305
  } else {
1222
1306
  let prevItem, currItem;
1223
1307
  this.storage.iterate(curr - 1, curr + 1, (item, index) => {
1224
- if (index === curr - 1)
1225
- prevItem = item;
1226
- else
1227
- currItem = item;
1308
+ if (index === curr - 1) prevItem = item;
1309
+ else currItem = item;
1228
1310
  return true;
1229
1311
  });
1230
1312
  nextBound = this.getMinimalBound(prevItem, currItem);
@@ -1237,13 +1319,13 @@ class Negentropy {
1237
1319
  }
1238
1320
  _renderOutput(o) {
1239
1321
  o = o.unwrap();
1240
- if (!this.wantUint8ArrayOutput)
1241
- o = uint8ArrayToHex(o);
1322
+ if (!this.wantUint8ArrayOutput) o = uint8ArrayToHex(o);
1242
1323
  return o;
1243
1324
  }
1244
1325
  exceededFrameSizeLimit(n) {
1245
1326
  return this.frameSizeLimit && n > this.frameSizeLimit - 200;
1246
1327
  }
1328
+ // Decoding
1247
1329
  decodeTimestampIn(encoded) {
1248
1330
  let timestamp = decodeVarInt(encoded);
1249
1331
  timestamp = timestamp === 0 ? Number.MAX_VALUE : timestamp - 1;
@@ -1258,11 +1340,11 @@ class Negentropy {
1258
1340
  decodeBound(encoded) {
1259
1341
  let timestamp = this.decodeTimestampIn(encoded);
1260
1342
  let len = decodeVarInt(encoded);
1261
- if (len > ID_SIZE)
1262
- throw Error("bound key too long");
1343
+ if (len > ID_SIZE) throw Error("bound key too long");
1263
1344
  let id = getBytes(encoded, len);
1264
1345
  return { timestamp, id };
1265
1346
  }
1347
+ // Encoding
1266
1348
  encodeTimestampOut(timestamp) {
1267
1349
  if (timestamp === Number.MAX_VALUE) {
1268
1350
  this.lastTimestampOut = Number.MAX_VALUE;
@@ -1274,7 +1356,7 @@ class Negentropy {
1274
1356
  return encodeVarInt(timestamp + 1);
1275
1357
  }
1276
1358
  encodeBound(key) {
1277
- let output = new WrappedBuffer;
1359
+ let output = new WrappedBuffer();
1278
1360
  output.extend(this.encodeTimestampOut(key.timestamp));
1279
1361
  output.extend(encodeVarInt(key.id.length));
1280
1362
  output.extend(key.id);
@@ -1287,30 +1369,24 @@ class Negentropy {
1287
1369
  let sharedPrefixBytes = 0;
1288
1370
  let currKey = curr.id;
1289
1371
  let prevKey = prev.id;
1290
- for (let i = 0;i < ID_SIZE; i++) {
1291
- if (currKey[i] !== prevKey[i])
1292
- break;
1372
+ for (let i = 0; i < ID_SIZE; i++) {
1373
+ if (currKey[i] !== prevKey[i]) break;
1293
1374
  sharedPrefixBytes++;
1294
1375
  }
1295
1376
  return this._bound(curr.timestamp, curr.id.subarray(0, sharedPrefixBytes + 1));
1296
1377
  }
1297
1378
  }
1298
- }
1379
+ };
1299
1380
  function loadInputBuffer(inp) {
1300
- if (typeof inp === "string")
1301
- inp = hexToUint8Array(inp);
1302
- else if (__proto__ !== Uint8Array.prototype)
1303
- inp = new Uint8Array(inp);
1381
+ if (typeof inp === "string") inp = hexToUint8Array(inp);
1382
+ else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp);
1304
1383
  return inp;
1305
1384
  }
1306
1385
  function hexToUint8Array(h) {
1307
- if (h.startsWith("0x"))
1308
- h = h.substr(2);
1309
- if (h.length % 2 === 1)
1310
- throw Error("odd length of hex string");
1386
+ if (h.startsWith("0x")) h = h.substr(2);
1387
+ if (h.length % 2 === 1) throw Error("odd length of hex string");
1311
1388
  let arr = new Uint8Array(h.length / 2);
1312
- for (let i = 0;i < arr.length; i++)
1313
- arr[i] = parseInt(h.substr(i * 2, 2), 16);
1389
+ for (let i = 0; i < arr.length; i++) arr[i] = parseInt(h.substr(i * 2, 2), 16);
1314
1390
  return arr;
1315
1391
  }
1316
1392
  var uint8ArrayToHexLookupTable = new Array(256);
@@ -1333,28 +1409,24 @@ var uint8ArrayToHexLookupTable = new Array(256);
1333
1409
  "e",
1334
1410
  "f"
1335
1411
  ];
1336
- for (let i = 0;i < 256; i++) {
1412
+ for (let i = 0; i < 256; i++) {
1337
1413
  uint8ArrayToHexLookupTable[i] = hexAlphabet[i >>> 4 & 15] + hexAlphabet[i & 15];
1338
1414
  }
1339
1415
  }
1340
1416
  function uint8ArrayToHex(arr) {
1341
1417
  let out = "";
1342
- for (let i = 0, edx = arr.length;i < edx; i++) {
1418
+ for (let i = 0, edx = arr.length; i < edx; i++) {
1343
1419
  out += uint8ArrayToHexLookupTable[arr[i]];
1344
1420
  }
1345
1421
  return out;
1346
1422
  }
1347
1423
  function compareUint8Array(a, b) {
1348
- for (let i = 0;i < a.byteLength; i++) {
1349
- if (a[i] < b[i])
1350
- return -1;
1351
- if (a[i] > b[i])
1352
- return 1;
1353
- }
1354
- if (a.byteLength > b.byteLength)
1355
- return 1;
1356
- if (a.byteLength < b.byteLength)
1357
- return -1;
1424
+ for (let i = 0; i < a.byteLength; i++) {
1425
+ if (a[i] < b[i]) return -1;
1426
+ if (a[i] > b[i]) return 1;
1427
+ }
1428
+ if (a.byteLength > b.byteLength) return 1;
1429
+ if (a.byteLength < b.byteLength) return -1;
1358
1430
  return 0;
1359
1431
  }
1360
1432
  function itemCompare(a, b) {
@@ -1370,7 +1442,7 @@ import { GiftWrap } from "nostr-tools/kinds";
1370
1442
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1371
1443
  return Effect7.gen(function* () {
1372
1444
  const allGiftWraps = yield* storage.getAllGiftWraps();
1373
- const storageVector = new NegentropyStorageVector;
1445
+ const storageVector = new NegentropyStorageVector();
1374
1446
  for (const gw of allGiftWraps) {
1375
1447
  storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1376
1448
  }
@@ -1394,8 +1466,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1394
1466
  let currentMsg = initialMsg;
1395
1467
  while (currentMsg !== null) {
1396
1468
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1397
- if (response.msgHex === null)
1398
- break;
1469
+ if (response.msgHex === null) break;
1399
1470
  const reconcileResult = yield* Effect7.try({
1400
1471
  try: () => neg.reconcile(response.msgHex),
1401
1472
  catch: (e) => new SyncError({
@@ -1405,14 +1476,17 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1405
1476
  })
1406
1477
  });
1407
1478
  const [nextMsg, haveIds, needIds] = reconcileResult;
1408
- for (const id of haveIds)
1409
- allHaveIds.push(id);
1410
- for (const id of needIds)
1411
- allNeedIds.push(id);
1479
+ for (const id of haveIds) allHaveIds.push(id);
1480
+ for (const id of needIds) allNeedIds.push(id);
1412
1481
  currentMsg = nextMsg;
1413
1482
  }
1483
+ yield* Effect7.logDebug("Negentropy reconciliation complete", {
1484
+ relay: relayUrl,
1485
+ have: allHaveIds.length,
1486
+ need: allNeedIds.length
1487
+ });
1414
1488
  return { haveIds: allHaveIds, needIds: allNeedIds };
1415
- });
1489
+ }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1416
1490
  }
1417
1491
 
1418
1492
  // src/db/key-rotation.ts
@@ -1451,12 +1525,11 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1451
1525
  kind: 1,
1452
1526
  content: JSON.stringify(rotationData),
1453
1527
  tags: [["d", `_system:rotation:${epochId}`]],
1454
- created_at: Math.floor(Date.now() / 1000)
1528
+ created_at: Math.floor(Date.now() / 1e3)
1455
1529
  };
1456
1530
  const wrappedEvents = [];
1457
1531
  for (const memberPubkey of remainingMemberPubkeys) {
1458
- if (memberPubkey === senderPublicKey)
1459
- continue;
1532
+ if (memberPubkey === senderPublicKey) continue;
1460
1533
  const wrapped = wrapEvent(rumor, senderPrivateKey, memberPubkey);
1461
1534
  wrappedEvents.push(wrapped);
1462
1535
  }
@@ -1469,7 +1542,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1469
1542
  kind: 1,
1470
1543
  content: JSON.stringify(removalData),
1471
1544
  tags: [["d", `_system:removed:${epochId}`]],
1472
- created_at: Math.floor(Date.now() / 1000)
1545
+ created_at: Math.floor(Date.now() / 1e3)
1473
1546
  };
1474
1547
  const removalNotices = [];
1475
1548
  for (const removedPubkey of removedMemberPubkeys) {
@@ -1479,8 +1552,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1479
1552
  return { epoch, wrappedEvents, removalNotices };
1480
1553
  }
1481
1554
  function parseRotationEvent(content, dTag) {
1482
- if (!dTag.startsWith("_system:rotation:"))
1483
- return Option4.none();
1555
+ if (!dTag.startsWith("_system:rotation:")) return Option4.none();
1484
1556
  try {
1485
1557
  return Option4.some(decodeRotationData(content));
1486
1558
  } catch {
@@ -1488,8 +1560,7 @@ function parseRotationEvent(content, dTag) {
1488
1560
  }
1489
1561
  }
1490
1562
  function parseRemovalNotice(content, dTag) {
1491
- if (!dTag.startsWith("_system:removed:"))
1492
- return Option4.none();
1563
+ if (!dTag.startsWith("_system:removed:")) return Option4.none();
1493
1564
  try {
1494
1565
  return Option4.some(decodeRemovalNotice(content));
1495
1566
  } catch {
@@ -1498,7 +1569,8 @@ function parseRemovalNotice(content, dTag) {
1498
1569
  }
1499
1570
 
1500
1571
  // src/sync/sync-service.ts
1501
- function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1572
+ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, logLevel, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1573
+ const logLayer = Layer.succeed(References2.MinimumLogLevel, logLevel);
1502
1574
  const getSubscriptionPubKeys = () => {
1503
1575
  return getAllPublicKeys(epochStore);
1504
1576
  };
@@ -1508,64 +1580,79 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1508
1580
  kind: "create"
1509
1581
  });
1510
1582
  const forkHandled = (effect) => {
1511
- Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.forkIn(scope)));
1583
+ Effect8.runFork(
1584
+ effect.pipe(
1585
+ Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
1586
+ Effect8.ignore,
1587
+ Effect8.provide(logLayer),
1588
+ Effect8.forkIn(scope)
1589
+ )
1590
+ );
1512
1591
  };
1513
1592
  let autoFlushActive = false;
1514
1593
  const autoFlushEffect = Effect8.gen(function* () {
1515
1594
  const size = yield* publishQueue.size();
1516
- if (size === 0)
1517
- return;
1595
+ if (size === 0) return;
1518
1596
  yield* syncStatus.set("syncing");
1519
1597
  yield* publishQueue.flush(relayUrls);
1520
1598
  const remaining = yield* publishQueue.size();
1521
- if (remaining > 0)
1522
- yield* Effect8.fail("pending");
1523
- }).pipe(Effect8.ensuring(syncStatus.set("idle")), Effect8.retry({ schedule: Schedule.exponential(5000).pipe(Schedule.jittered), times: 10 }), Effect8.ignore);
1599
+ if (remaining > 0) yield* Effect8.fail("pending");
1600
+ }).pipe(
1601
+ Effect8.ensuring(syncStatus.set("idle")),
1602
+ Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1603
+ Effect8.ignore
1604
+ );
1524
1605
  const scheduleAutoFlush = () => {
1525
- if (autoFlushActive)
1526
- return;
1606
+ if (autoFlushActive) return;
1527
1607
  autoFlushActive = true;
1528
- forkHandled(autoFlushEffect.pipe(Effect8.ensuring(Effect8.sync(() => {
1529
- autoFlushActive = false;
1530
- }))));
1608
+ forkHandled(
1609
+ autoFlushEffect.pipe(
1610
+ Effect8.ensuring(
1611
+ Effect8.sync(() => {
1612
+ autoFlushActive = false;
1613
+ })
1614
+ )
1615
+ )
1616
+ );
1531
1617
  };
1532
1618
  const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1533
1619
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1534
- if (!memberRecord)
1535
- return false;
1620
+ if (!memberRecord) return false;
1536
1621
  return !!memberRecord.removedAt;
1537
1622
  });
1538
1623
  const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1539
1624
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1540
- if (existing)
1541
- return null;
1625
+ if (existing) return null;
1542
1626
  const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
1543
1627
  if (unwrapResult._tag === "Failure") {
1544
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1628
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1545
1629
  return null;
1546
1630
  }
1547
1631
  const rumor = unwrapResult.success;
1548
1632
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1549
1633
  if (!dTag) {
1550
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1634
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1551
1635
  return null;
1552
1636
  }
1553
1637
  const colonIdx = dTag.indexOf(":");
1554
1638
  if (colonIdx === -1) {
1555
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1639
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1556
1640
  return null;
1557
1641
  }
1558
1642
  const collectionName = dTag.substring(0, colonIdx);
1559
1643
  const recordId = dTag.substring(colonIdx + 1);
1560
1644
  const retention = knownCollections.get(collectionName);
1561
- if (retention === undefined) {
1562
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1645
+ if (retention === void 0) {
1646
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1563
1647
  return null;
1564
1648
  }
1565
1649
  if (rumor.pubkey) {
1566
1650
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1567
1651
  if (reject) {
1568
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1652
+ yield* Effect8.logWarning("Rejected write from removed member", {
1653
+ author: rumor.pubkey.slice(0, 12)
1654
+ });
1655
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1569
1656
  return null;
1570
1657
  }
1571
1658
  }
@@ -1573,14 +1660,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1573
1660
  let kind = "u";
1574
1661
  const parsed = yield* Effect8.try({
1575
1662
  try: () => JSON.parse(rumor.content),
1576
- catch: () => {
1577
- return;
1578
- }
1579
- }).pipe(Effect8.orElseSucceed(() => {
1580
- return;
1581
- }));
1582
- if (parsed === undefined) {
1583
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1663
+ catch: () => void 0
1664
+ }).pipe(Effect8.orElseSucceed(() => void 0));
1665
+ if (parsed === void 0) {
1666
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1584
1667
  return null;
1585
1668
  }
1586
1669
  if (parsed === null || parsed._deleted) {
@@ -1588,18 +1671,19 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1588
1671
  } else {
1589
1672
  data = parsed;
1590
1673
  }
1591
- const author = rumor.pubkey || undefined;
1674
+ const author = rumor.pubkey || void 0;
1592
1675
  const event = {
1593
1676
  id: rumor.id,
1594
1677
  collection: collectionName,
1595
1678
  recordId,
1596
1679
  kind,
1597
1680
  data,
1598
- createdAt: rumor.created_at * 1000,
1681
+ createdAt: rumor.created_at * 1e3,
1599
1682
  author
1600
1683
  };
1601
1684
  yield* storage.putGiftWrap({
1602
1685
  id: remoteGw.id,
1686
+ event: remoteGw,
1603
1687
  createdAt: remoteGw.created_at
1604
1688
  });
1605
1689
  yield* storage.putEvent(event);
@@ -1607,6 +1691,12 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1607
1691
  if (didApply && (kind === "u" || kind === "d")) {
1608
1692
  yield* pruneEvents(storage, collectionName, recordId, retention);
1609
1693
  }
1694
+ yield* Effect8.logDebug("Processed gift wrap", {
1695
+ collection: collectionName,
1696
+ recordId,
1697
+ kind,
1698
+ author: author?.slice(0, 12)
1699
+ });
1610
1700
  if (author && onNewAuthor) {
1611
1701
  onNewAuthor(author);
1612
1702
  }
@@ -1619,32 +1709,34 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1619
1709
  }
1620
1710
  });
1621
1711
  const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1622
- const unwrapResult = yield* Effect8.result(Effect8.try({
1623
- try: () => unwrapEvent(remoteGw, personalPrivateKey),
1624
- catch: (e) => new CryptoError({
1625
- message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
1626
- cause: e
1712
+ const unwrapResult = yield* Effect8.result(
1713
+ Effect8.try({
1714
+ try: () => unwrapEvent(remoteGw, personalPrivateKey),
1715
+ catch: (e) => new CryptoError({
1716
+ message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
1717
+ cause: e
1718
+ })
1627
1719
  })
1628
- }));
1629
- if (unwrapResult._tag === "Failure")
1630
- return false;
1720
+ );
1721
+ if (unwrapResult._tag === "Failure") return false;
1631
1722
  const rumor = unwrapResult.success;
1632
1723
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1633
- if (!dTag)
1634
- return false;
1724
+ if (!dTag) return false;
1635
1725
  const removalNoticeOpt = parseRemovalNotice(rumor.content, dTag);
1636
1726
  if (Option5.isSome(removalNoticeOpt)) {
1637
- if (onRemoved)
1638
- onRemoved(removalNoticeOpt.value);
1727
+ if (onRemoved) onRemoved(removalNoticeOpt.value);
1639
1728
  return true;
1640
1729
  }
1641
1730
  const rotationDataOpt = parseRotationEvent(rumor.content, dTag);
1642
- if (Option5.isNone(rotationDataOpt))
1643
- return false;
1731
+ if (Option5.isNone(rotationDataOpt)) return false;
1644
1732
  const rotationData = rotationDataOpt.value;
1645
- if (epochStore.epochs.has(rotationData.epochId))
1646
- return false;
1647
- const epoch = createEpochKey(rotationData.epochId, rotationData.epochKey, rumor.pubkey || "", rotationData.parentEpoch);
1733
+ if (epochStore.epochs.has(rotationData.epochId)) return false;
1734
+ const epoch = createEpochKey(
1735
+ rotationData.epochId,
1736
+ rotationData.epochKey,
1737
+ rumor.pubkey || "",
1738
+ rotationData.parentEpoch
1739
+ );
1648
1740
  addEpoch(epochStore, epoch);
1649
1741
  epochStore.currentEpochId = epoch.id;
1650
1742
  let membersChanged = false;
@@ -1664,75 +1756,158 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1664
1756
  membersChanged = true;
1665
1757
  }
1666
1758
  }
1667
- if (membersChanged && onMembersChanged)
1668
- onMembersChanged();
1759
+ if (membersChanged && onMembersChanged) onMembersChanged();
1669
1760
  yield* handle.addEpochSubscription(epoch.publicKey);
1670
1761
  return true;
1671
1762
  });
1672
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(relayUrls, (url) => Effect8.gen(function* () {
1673
- yield* relay.subscribe(filter, url, (event) => {
1674
- forkHandled(onEvent(event));
1675
- }).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1676
- }), { discard: true });
1763
+ const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
1764
+ relayUrls,
1765
+ (url) => Effect8.gen(function* () {
1766
+ yield* relay.subscribe(filter, url, (event) => {
1767
+ forkHandled(onEvent(event));
1768
+ }).pipe(
1769
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1770
+ Effect8.ignore
1771
+ );
1772
+ }),
1773
+ { discard: true }
1774
+ );
1677
1775
  const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1678
- const reconcileResult = yield* Effect8.result(reconcileWithRelay(storage, relay, url, Array.from(pubKeys)));
1776
+ yield* Effect8.logDebug("Syncing relay", { relay: url });
1777
+ const reconcileResult = yield* Effect8.result(
1778
+ reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
1779
+ );
1679
1780
  if (reconcileResult._tag === "Failure") {
1680
1781
  onSyncError?.(reconcileResult.failure);
1681
1782
  return;
1682
1783
  }
1683
1784
  const { haveIds, needIds } = reconcileResult.success;
1785
+ yield* Effect8.logDebug("Relay reconciliation result", {
1786
+ relay: url,
1787
+ need: needIds.length,
1788
+ have: haveIds.length
1789
+ });
1684
1790
  if (needIds.length > 0) {
1685
- const fetched = yield* relay.fetchEvents(needIds, url).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.orElseSucceed(() => []));
1791
+ const fetched = yield* relay.fetchEvents(needIds, url).pipe(
1792
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1793
+ Effect8.orElseSucceed(() => [])
1794
+ );
1686
1795
  const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
1687
- yield* Effect8.forEach(sorted, (remoteGw) => Effect8.gen(function* () {
1688
- const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
1689
- if (collection2)
1690
- changedCollections.add(collection2);
1691
- }), { discard: true });
1796
+ yield* Effect8.forEach(
1797
+ sorted,
1798
+ (remoteGw) => Effect8.gen(function* () {
1799
+ const collection2 = yield* processGiftWrap(remoteGw).pipe(
1800
+ Effect8.orElseSucceed(() => null)
1801
+ );
1802
+ if (collection2) changedCollections.add(collection2);
1803
+ }),
1804
+ { discard: true }
1805
+ );
1692
1806
  }
1693
1807
  if (haveIds.length > 0) {
1694
- yield* Effect8.forEach(haveIds, (id) => Effect8.gen(function* () {
1695
- const gw = yield* storage.getGiftWrap(id);
1696
- if (!gw?.event)
1697
- return;
1698
- yield* relay.publish(gw.event, [url]).pipe(Effect8.andThen(storage.stripGiftWrapBlob(id)), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1699
- }), { discard: true });
1700
- }
1701
- });
1808
+ yield* Effect8.forEach(
1809
+ haveIds,
1810
+ (id) => Effect8.gen(function* () {
1811
+ const gw = yield* storage.getGiftWrap(id);
1812
+ if (!gw?.event) return;
1813
+ yield* relay.publish(gw.event, [url]).pipe(
1814
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1815
+ Effect8.ignore
1816
+ );
1817
+ }),
1818
+ { discard: true }
1819
+ );
1820
+ }
1821
+ }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1822
+ let healingActive = false;
1823
+ const healingEffect = Effect8.gen(function* () {
1824
+ if (!healingActive) return;
1825
+ const status = yield* syncStatus.get();
1826
+ if (status === "syncing") return;
1827
+ yield* syncStatus.set("syncing");
1828
+ yield* Effect8.gen(function* () {
1829
+ const pubKeys = getSubscriptionPubKeys();
1830
+ const changedCollections = /* @__PURE__ */ new Set();
1831
+ yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1832
+ discard: true
1833
+ });
1834
+ if (changedCollections.size > 0) {
1835
+ yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1836
+ }
1837
+ }).pipe(Effect8.ensuring(syncStatus.set("idle")));
1838
+ }).pipe(Effect8.ignore);
1702
1839
  const handle = {
1703
1840
  sync: () => Effect8.gen(function* () {
1841
+ yield* Effect8.logInfo("Sync started");
1704
1842
  yield* syncStatus.set("syncing");
1705
1843
  yield* Ref3.set(watchCtx.replayingRef, true);
1706
- const changedCollections = new Set;
1844
+ const changedCollections = /* @__PURE__ */ new Set();
1707
1845
  yield* Effect8.gen(function* () {
1708
1846
  const pubKeys = getSubscriptionPubKeys();
1709
1847
  yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1710
1848
  discard: true
1711
1849
  });
1712
1850
  yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
1713
- }).pipe(Effect8.ensuring(Effect8.gen(function* () {
1714
- yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1715
- yield* syncStatus.set("idle");
1716
- })));
1717
- }),
1851
+ }).pipe(
1852
+ Effect8.ensuring(
1853
+ Effect8.gen(function* () {
1854
+ yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1855
+ yield* syncStatus.set("idle");
1856
+ })
1857
+ )
1858
+ );
1859
+ yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1860
+ }).pipe(Effect8.withLogSpan("tablinum.sync")),
1718
1861
  publishLocal: (giftWrap) => Effect8.gen(function* () {
1719
- if (!giftWrap.event)
1720
- return;
1721
- yield* relay.publish(giftWrap.event, relayUrls).pipe(Effect8.tapError(() => storage.putGiftWrap(giftWrap).pipe(Effect8.andThen(publishQueue.enqueue(giftWrap.id)), Effect8.andThen(Effect8.sync(() => scheduleAutoFlush())))), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1862
+ if (!giftWrap.event) return;
1863
+ yield* relay.publish(giftWrap.event, relayUrls).pipe(
1864
+ Effect8.tapError(
1865
+ () => storage.putGiftWrap(giftWrap).pipe(
1866
+ Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
1867
+ Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
1868
+ )
1869
+ ),
1870
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1871
+ Effect8.ignore
1872
+ );
1722
1873
  }),
1723
1874
  startSubscription: () => Effect8.gen(function* () {
1724
1875
  const pubKeys = getSubscriptionPubKeys();
1725
1876
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1726
1877
  if (!pubKeys.includes(personalPublicKey)) {
1727
- yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [personalPublicKey] }, (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid));
1878
+ yield* subscribeAcrossRelays(
1879
+ { kinds: [GiftWrap2], "#p": [personalPublicKey] },
1880
+ (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
1881
+ );
1728
1882
  }
1729
1883
  }),
1730
- addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap)
1884
+ addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap),
1885
+ startHealing: () => {
1886
+ if (healingActive) return;
1887
+ healingActive = true;
1888
+ forkHandled(
1889
+ Effect8.sleep(Duration.minutes(5)).pipe(
1890
+ Effect8.andThen(healingEffect),
1891
+ Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
1892
+ Effect8.ensuring(Effect8.sync(() => {
1893
+ healingActive = false;
1894
+ }))
1895
+ )
1896
+ );
1897
+ },
1898
+ stopHealing: () => {
1899
+ healingActive = false;
1900
+ }
1731
1901
  };
1732
- forkHandled(publishQueue.size().pipe(Effect8.flatMap((size) => Effect8.sync(() => {
1733
- if (size > 0)
1734
- scheduleAutoFlush();
1735
- }))));
1902
+ forkHandled(
1903
+ publishQueue.size().pipe(
1904
+ Effect8.flatMap(
1905
+ (size) => Effect8.sync(() => {
1906
+ if (size > 0) scheduleAutoFlush();
1907
+ })
1908
+ )
1909
+ )
1910
+ );
1736
1911
  return handle;
1737
1912
  }
1738
1913
 
@@ -1788,9 +1963,14 @@ var decodeAuthorProfile = Schema6.decodeUnknownEffect(Schema6.fromJsonString(Aut
1788
1963
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
1789
1964
  return Effect9.gen(function* () {
1790
1965
  for (const url of relayUrls) {
1791
- const result = yield* Effect9.result(relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url));
1966
+ const result = yield* Effect9.result(
1967
+ relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
1968
+ );
1792
1969
  if (result._tag === "Success" && result.success.length > 0) {
1793
- return yield* decodeAuthorProfile(result.success[0].content).pipe(Effect9.map(Option6.some), Effect9.orElseSucceed(() => Option6.none()));
1970
+ return yield* decodeAuthorProfile(result.success[0].content).pipe(
1971
+ Effect9.map(Option6.some),
1972
+ Effect9.orElseSucceed(() => Option6.none())
1973
+ );
1794
1974
  }
1795
1975
  }
1796
1976
  return Option6.none();
@@ -1799,48 +1979,47 @@ function fetchAuthorProfile(relay, relayUrls, pubkey) {
1799
1979
 
1800
1980
  // src/services/Identity.ts
1801
1981
  import { ServiceMap as ServiceMap3 } from "effect";
1802
-
1803
- class Identity extends ServiceMap3.Service()("tablinum/Identity") {
1804
- }
1982
+ var Identity = class extends ServiceMap3.Service()("tablinum/Identity") {
1983
+ };
1805
1984
 
1806
1985
  // src/services/EpochStore.ts
1807
1986
  import { ServiceMap as ServiceMap4 } from "effect";
1808
-
1809
- class EpochStore extends ServiceMap4.Service()("tablinum/EpochStore") {
1810
- }
1987
+ var EpochStore = class extends ServiceMap4.Service()(
1988
+ "tablinum/EpochStore"
1989
+ ) {
1990
+ };
1811
1991
 
1812
1992
  // src/services/Storage.ts
1813
1993
  import { ServiceMap as ServiceMap5 } from "effect";
1814
-
1815
- class Storage extends ServiceMap5.Service()("tablinum/Storage") {
1816
- }
1994
+ var Storage = class extends ServiceMap5.Service()("tablinum/Storage") {
1995
+ };
1817
1996
 
1818
1997
  // src/services/Relay.ts
1819
1998
  import { ServiceMap as ServiceMap6 } from "effect";
1820
-
1821
- class Relay extends ServiceMap6.Service()("tablinum/Relay") {
1822
- }
1999
+ var Relay = class extends ServiceMap6.Service()("tablinum/Relay") {
2000
+ };
1823
2001
 
1824
2002
  // src/services/GiftWrap.ts
1825
2003
  import { ServiceMap as ServiceMap7 } from "effect";
1826
-
1827
- class GiftWrap3 extends ServiceMap7.Service()("tablinum/GiftWrap") {
1828
- }
2004
+ var GiftWrap3 = class extends ServiceMap7.Service()("tablinum/GiftWrap") {
2005
+ };
1829
2006
 
1830
2007
  // src/services/PublishQueue.ts
1831
2008
  import { ServiceMap as ServiceMap8 } from "effect";
1832
-
1833
- class PublishQueue extends ServiceMap8.Service()("tablinum/PublishQueue") {
1834
- }
2009
+ var PublishQueue = class extends ServiceMap8.Service()(
2010
+ "tablinum/PublishQueue"
2011
+ ) {
2012
+ };
1835
2013
 
1836
2014
  // src/services/SyncStatus.ts
1837
2015
  import { ServiceMap as ServiceMap9 } from "effect";
1838
-
1839
- class SyncStatus extends ServiceMap9.Service()("tablinum/SyncStatus") {
1840
- }
2016
+ var SyncStatus = class extends ServiceMap9.Service()(
2017
+ "tablinum/SyncStatus"
2018
+ ) {
2019
+ };
1841
2020
 
1842
2021
  // src/layers/IdentityLive.ts
1843
- import { Effect as Effect11, Layer } from "effect";
2022
+ import { Effect as Effect11, Layer as Layer2 } from "effect";
1844
2023
  import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1845
2024
 
1846
2025
  // src/db/identity.ts
@@ -1878,47 +2057,128 @@ function createIdentity(suppliedKey) {
1878
2057
  }
1879
2058
 
1880
2059
  // src/layers/IdentityLive.ts
1881
- var IdentityLive = Layer.effect(Identity, Effect11.gen(function* () {
1882
- const config = yield* Config;
1883
- const storage = yield* Storage;
1884
- const idbKey = yield* storage.getMeta("identity_key");
1885
- const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : undefined);
1886
- const identity = yield* createIdentity(resolvedKey);
1887
- yield* storage.putMeta("identity_key", identity.exportKey());
1888
- return identity;
1889
- }));
2060
+ var IdentityLive = Layer2.effect(
2061
+ Identity,
2062
+ Effect11.gen(function* () {
2063
+ const config = yield* Config;
2064
+ const storage = yield* Storage;
2065
+ const idbKey = yield* storage.getMeta("identity_key");
2066
+ const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : void 0);
2067
+ const identity = yield* createIdentity(resolvedKey);
2068
+ yield* storage.putMeta("identity_key", identity.exportKey());
2069
+ yield* Effect11.logInfo("Identity loaded", {
2070
+ publicKey: identity.publicKey.slice(0, 12) + "...",
2071
+ source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
2072
+ });
2073
+ return identity;
2074
+ })
2075
+ );
1890
2076
 
1891
2077
  // src/layers/EpochStoreLive.ts
1892
- import { Effect as Effect12, Layer as Layer2, Option as Option7 } from "effect";
2078
+ import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
1893
2079
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
1894
2080
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
1895
- var EpochStoreLive = Layer2.effect(EpochStore, Effect12.gen(function* () {
1896
- const config = yield* Config;
1897
- const identity = yield* Identity;
1898
- const storage = yield* Storage;
1899
- const idbRaw = yield* storage.getMeta("epochs");
1900
- if (typeof idbRaw === "string") {
1901
- const idbStore = deserializeEpochStore(idbRaw);
1902
- if (Option7.isSome(idbStore)) {
1903
- return idbStore.value;
1904
- }
1905
- }
1906
- if (config.epochKeys && config.epochKeys.length > 0) {
1907
- const store2 = createEpochStoreFromInputs(config.epochKeys);
1908
- yield* storage.putMeta("epochs", stringifyEpochStore(store2));
1909
- return store2;
1910
- }
1911
- const store = createEpochStoreFromInputs([{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }], { createdBy: identity.publicKey });
1912
- yield* storage.putMeta("epochs", stringifyEpochStore(store));
1913
- return store;
1914
- }));
2081
+ var EpochStoreLive = Layer3.effect(
2082
+ EpochStore,
2083
+ Effect12.gen(function* () {
2084
+ const config = yield* Config;
2085
+ const identity = yield* Identity;
2086
+ const storage = yield* Storage;
2087
+ const idbRaw = yield* storage.getMeta("epochs");
2088
+ if (typeof idbRaw === "string") {
2089
+ const idbStore = deserializeEpochStore(idbRaw);
2090
+ if (Option7.isSome(idbStore)) {
2091
+ yield* Effect12.logInfo("Epoch store loaded", {
2092
+ source: "storage",
2093
+ epochs: idbStore.value.epochs.size
2094
+ });
2095
+ return idbStore.value;
2096
+ }
2097
+ }
2098
+ if (config.epochKeys && config.epochKeys.length > 0) {
2099
+ const store2 = createEpochStoreFromInputs(config.epochKeys);
2100
+ yield* storage.putMeta("epochs", stringifyEpochStore(store2));
2101
+ yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2102
+ return store2;
2103
+ }
2104
+ const store = createEpochStoreFromInputs(
2105
+ [{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
2106
+ { createdBy: identity.publicKey }
2107
+ );
2108
+ yield* storage.putMeta("epochs", stringifyEpochStore(store));
2109
+ yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2110
+ return store;
2111
+ })
2112
+ );
1915
2113
 
1916
2114
  // src/layers/StorageLive.ts
1917
- import { Effect as Effect14, Layer as Layer3 } from "effect";
2115
+ import { Effect as Effect14, Layer as Layer4 } from "effect";
1918
2116
 
1919
2117
  // src/storage/idb.ts
1920
2118
  import { Effect as Effect13 } from "effect";
1921
2119
  import { openDB } from "idb";
2120
+
2121
+ // src/sync/compact-event.ts
2122
+ import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2123
+ var VERSION = 1;
2124
+ var HEADER_SIZE = 133;
2125
+ function base64ToBytes(base64) {
2126
+ const binary = atob(base64);
2127
+ const bytes = new Uint8Array(binary.length);
2128
+ for (let i = 0; i < binary.length; i++) {
2129
+ bytes[i] = binary.charCodeAt(i);
2130
+ }
2131
+ return bytes;
2132
+ }
2133
+ function bytesToBase64(bytes) {
2134
+ let binary = "";
2135
+ for (let i = 0; i < bytes.length; i++) {
2136
+ binary += String.fromCharCode(bytes[i]);
2137
+ }
2138
+ return btoa(binary);
2139
+ }
2140
+ function packEvent(event) {
2141
+ const pubkey = hexToBytes4(event.pubkey);
2142
+ const sig = hexToBytes4(event.sig);
2143
+ const recipientTag = event.tags.find((t) => t[0] === "p");
2144
+ if (!recipientTag) throw new Error("Gift wrap missing #p tag");
2145
+ if (event.tags.some((t) => t[0] !== "p")) {
2146
+ throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
2147
+ }
2148
+ const recipient = hexToBytes4(recipientTag[1]);
2149
+ const createdAtBuf = new Uint8Array(4);
2150
+ new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
2151
+ const content = base64ToBytes(event.content);
2152
+ const result = new Uint8Array(HEADER_SIZE + content.length);
2153
+ result[0] = VERSION;
2154
+ result.set(pubkey, 1);
2155
+ result.set(sig, 33);
2156
+ result.set(recipient, 97);
2157
+ result.set(createdAtBuf, 129);
2158
+ result.set(content, HEADER_SIZE);
2159
+ return result;
2160
+ }
2161
+ function unpackEvent(id, compact) {
2162
+ const version = compact[0];
2163
+ if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
2164
+ const pubkey = bytesToHex4(compact.slice(1, 33));
2165
+ const sig = bytesToHex4(compact.slice(33, 97));
2166
+ const recipient = bytesToHex4(compact.slice(97, 129));
2167
+ const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
2168
+ const createdAt = dv.getUint32(0, false);
2169
+ const content = bytesToBase64(compact.slice(HEADER_SIZE));
2170
+ return {
2171
+ id,
2172
+ pubkey,
2173
+ sig,
2174
+ created_at: createdAt,
2175
+ kind: 1059,
2176
+ tags: [["p", recipient]],
2177
+ content
2178
+ };
2179
+ }
2180
+
2181
+ // src/storage/idb.ts
1922
2182
  var DB_NAME = "tablinum";
1923
2183
  function storeName(collection2) {
1924
2184
  return `col_${collection2}`;
@@ -1949,7 +2209,7 @@ function upgradeSchema(database, schema, tx) {
1949
2209
  if (!database.objectStoreNames.contains("giftwraps")) {
1950
2210
  database.createObjectStore("giftwraps", { keyPath: "id" });
1951
2211
  }
1952
- const expectedStores = new Set;
2212
+ const expectedStores = /* @__PURE__ */ new Set();
1953
2213
  for (const [, def] of Object.entries(schema)) {
1954
2214
  const sn = storeName(def.name);
1955
2215
  expectedStores.add(sn);
@@ -1963,12 +2223,10 @@ function upgradeSchema(database, schema, tx) {
1963
2223
  const existingIndices = new Set(Array.from(store.indexNames));
1964
2224
  const wantedIndices = new Set(def.indices ?? []);
1965
2225
  for (const idx of existingIndices) {
1966
- if (!wantedIndices.has(idx))
1967
- store.deleteIndex(idx);
2226
+ if (!wantedIndices.has(idx)) store.deleteIndex(idx);
1968
2227
  }
1969
2228
  for (const idx of wantedIndices) {
1970
- if (!existingIndices.has(idx))
1971
- store.createIndex(idx, idx);
2229
+ if (!existingIndices.has(idx)) store.createIndex(idx, idx);
1972
2230
  }
1973
2231
  }
1974
2232
  }
@@ -1983,6 +2241,13 @@ function openIDBStorage(dbName, schema) {
1983
2241
  return Effect13.gen(function* () {
1984
2242
  const name = dbName ?? DB_NAME;
1985
2243
  const schemaSig = computeSchemaSig(schema);
2244
+ if (typeof indexedDB === "undefined") {
2245
+ return yield* Effect13.fail(
2246
+ new StorageError({
2247
+ message: "IndexedDB is not available in this environment"
2248
+ })
2249
+ );
2250
+ }
1986
2251
  const probeDb = yield* Effect13.tryPromise({
1987
2252
  try: () => openDB(name),
1988
2253
  catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
@@ -1993,7 +2258,7 @@ function openIDBStorage(dbName, schema) {
1993
2258
  const storedSig = yield* Effect13.tryPromise({
1994
2259
  try: () => probeDb.get("_meta", "schema_sig"),
1995
2260
  catch: () => new StorageError({ message: "Failed to read schema meta" })
1996
- }).pipe(Effect13.catch(() => Effect13.succeed(undefined)));
2261
+ }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
1997
2262
  needsUpgrade = storedSig !== schemaSig;
1998
2263
  }
1999
2264
  probeDb.close();
@@ -2010,9 +2275,7 @@ function openIDBStorage(dbName, schema) {
2010
2275
  });
2011
2276
  yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2012
2277
  const handle = {
2013
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => {
2014
- return;
2015
- })),
2278
+ putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2016
2279
  getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2017
2280
  getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2018
2281
  countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
@@ -2032,29 +2295,52 @@ function openIDBStorage(dbName, schema) {
2032
2295
  }
2033
2296
  return results;
2034
2297
  }),
2035
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => {
2036
- return;
2037
- })),
2298
+ putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2038
2299
  getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2039
2300
  getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2040
- getEventsByRecord: (collection2, recordId) => wrap("getEventsByRecord", () => db.getAllFromIndex("events", "by-record", [collection2, recordId])),
2041
- putGiftWrap: (gw) => wrap("putGiftWrap", () => db.put("giftwraps", gw).then(() => {
2042
- return;
2043
- })),
2044
- getGiftWrap: (id) => wrap("getGiftWrap", () => db.get("giftwraps", id)),
2045
- getAllGiftWraps: () => wrap("getAllGiftWraps", () => db.getAll("giftwraps")),
2046
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => {
2047
- return;
2048
- })),
2049
- stripGiftWrapBlob: (id) => wrap("stripGiftWrapBlob", async () => {
2050
- const existing = await db.get("giftwraps", id);
2051
- if (existing) {
2052
- await db.put("giftwraps", { id: existing.id, createdAt: existing.createdAt });
2301
+ getEventsByRecord: (collection2, recordId) => wrap(
2302
+ "getEventsByRecord",
2303
+ () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
2304
+ ),
2305
+ putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
2306
+ if (gw.event) {
2307
+ const compact = packEvent(gw.event);
2308
+ await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
2309
+ } else {
2310
+ await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
2053
2311
  }
2054
2312
  }),
2055
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => {
2056
- return;
2057
- })),
2313
+ getGiftWrap: (id) => wrap("getGiftWrap", async () => {
2314
+ const raw = await db.get("giftwraps", id);
2315
+ if (!raw) return void 0;
2316
+ if (raw.compact) {
2317
+ return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
2318
+ }
2319
+ if (raw.event) {
2320
+ const compact = packEvent(raw.event);
2321
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2322
+ return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
2323
+ }
2324
+ return { id: raw.id, createdAt: raw.createdAt };
2325
+ }),
2326
+ getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
2327
+ const raws = await db.getAll("giftwraps");
2328
+ const results = [];
2329
+ for (const raw of raws) {
2330
+ if (raw.compact) {
2331
+ results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
2332
+ } else if (raw.event) {
2333
+ const compact = packEvent(raw.event);
2334
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2335
+ results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
2336
+ } else {
2337
+ results.push({ id: raw.id, createdAt: raw.createdAt });
2338
+ }
2339
+ }
2340
+ return results;
2341
+ }),
2342
+ deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
2343
+ deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
2058
2344
  stripEventData: (id) => wrap("stripEventData", async () => {
2059
2345
  const existing = await db.get("events", id);
2060
2346
  if (existing) {
@@ -2062,9 +2348,7 @@ function openIDBStorage(dbName, schema) {
2062
2348
  }
2063
2349
  }),
2064
2350
  getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2065
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => {
2066
- return;
2067
- })),
2351
+ putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2068
2352
  close: () => Effect13.sync(() => db.close())
2069
2353
  };
2070
2354
  return handle;
@@ -2072,16 +2356,21 @@ function openIDBStorage(dbName, schema) {
2072
2356
  }
2073
2357
 
2074
2358
  // src/layers/StorageLive.ts
2075
- var StorageLive = Layer3.effect(Storage, Effect14.gen(function* () {
2076
- const config = yield* Config;
2077
- return yield* openIDBStorage(config.dbName, {
2078
- ...config.schema,
2079
- _members: membersCollectionDef
2080
- });
2081
- }));
2359
+ var StorageLive = Layer4.effect(
2360
+ Storage,
2361
+ Effect14.gen(function* () {
2362
+ const config = yield* Config;
2363
+ const handle = yield* openIDBStorage(config.dbName, {
2364
+ ...config.schema,
2365
+ _members: membersCollectionDef
2366
+ });
2367
+ yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2368
+ return handle;
2369
+ })
2370
+ );
2082
2371
 
2083
2372
  // src/layers/RelayLive.ts
2084
- import { Layer as Layer4 } from "effect";
2373
+ import { Layer as Layer5 } from "effect";
2085
2374
 
2086
2375
  // src/sync/relay.ts
2087
2376
  import { Effect as Effect15, Option as Option8, Schema as Schema7, ScopedCache, Scope as Scope3 } from "effect";
@@ -2092,32 +2381,41 @@ var NegMessageFrameSchema = Schema7.Tuple([
2092
2381
  Schema7.String
2093
2382
  ]);
2094
2383
  var NegErrorFrameSchema = Schema7.Tuple([Schema7.Literal("NEG-ERR"), Schema7.String, Schema7.String]);
2095
- var decodeNegFrame = Schema7.decodeUnknownEffect(Schema7.fromJsonString(Schema7.Union([NegMessageFrameSchema, NegErrorFrameSchema])));
2384
+ var decodeNegFrame = Schema7.decodeUnknownEffect(
2385
+ Schema7.fromJsonString(Schema7.Union([NegMessageFrameSchema, NegErrorFrameSchema]))
2386
+ );
2096
2387
  function parseNegMessageFrame(data) {
2097
- return Effect15.runSync(decodeNegFrame(data).pipe(Effect15.map(Option8.some), Effect15.orElseSucceed(() => Option8.none())));
2388
+ return Effect15.runSync(
2389
+ decodeNegFrame(data).pipe(
2390
+ Effect15.map(Option8.some),
2391
+ Effect15.orElseSucceed(() => Option8.none())
2392
+ )
2393
+ );
2098
2394
  }
2099
2395
  function createRelayHandle() {
2100
2396
  return Effect15.gen(function* () {
2101
2397
  const relayScope = yield* Effect15.scope;
2102
2398
  const connections = yield* ScopedCache.make({
2103
2399
  capacity: 64,
2104
- lookup: (url) => Effect15.acquireRelease(Effect15.tryPromise({
2105
- try: () => Relay2.connect(url),
2106
- catch: (e) => new RelayError({
2107
- message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2108
- url,
2109
- cause: e
2400
+ lookup: (url) => Effect15.acquireRelease(
2401
+ Effect15.tryPromise({
2402
+ try: () => Relay2.connect(url),
2403
+ catch: (e) => new RelayError({
2404
+ message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2405
+ url,
2406
+ cause: e
2407
+ })
2408
+ }),
2409
+ (relay) => Effect15.sync(() => {
2410
+ relay.close();
2110
2411
  })
2111
- }), (relay) => Effect15.sync(() => {
2112
- relay.close();
2113
- }))
2412
+ )
2114
2413
  });
2115
- const connectedUrls = new Set;
2116
- const statusListeners = new Set;
2414
+ const connectedUrls = /* @__PURE__ */ new Set();
2415
+ const statusListeners = /* @__PURE__ */ new Set();
2117
2416
  const notifyStatus = () => {
2118
2417
  const status = { connectedUrls: [...connectedUrls] };
2119
- for (const listener of statusListeners)
2120
- listener(status);
2418
+ for (const listener of statusListeners) listener(status);
2121
2419
  };
2122
2420
  const markConnected = (url) => {
2123
2421
  if (!connectedUrls.has(url)) {
@@ -2131,66 +2429,98 @@ function createRelayHandle() {
2131
2429
  notifyStatus();
2132
2430
  }
2133
2431
  };
2134
- const getRelay = (url) => ScopedCache.get(connections, url).pipe(Effect15.flatMap((relay) => relay.connected === false ? ScopedCache.invalidate(connections, url).pipe(Effect15.andThen(ScopedCache.get(connections, url))) : Effect15.succeed(relay)));
2135
- const withRelay = (url, run) => getRelay(url).pipe(Effect15.tap(() => Effect15.sync(() => markConnected(url))), Effect15.flatMap((relay) => run(relay)), Effect15.tapError(() => ScopedCache.invalidate(connections, url).pipe(Effect15.tap(() => Effect15.sync(() => markDisconnected(url))))));
2136
- const collectEvents = (url, filters) => withRelay(url, (relay) => Effect15.callback((resume) => {
2137
- const events = [];
2138
- let settled = false;
2139
- let timer;
2140
- let sub;
2141
- const cleanup = () => {
2142
- settled = true;
2143
- if (timer !== undefined) {
2144
- clearTimeout(timer);
2145
- timer = undefined;
2146
- }
2147
- sub?.close();
2148
- sub = undefined;
2149
- };
2150
- const fail = (e) => resume(Effect15.fail(new RelayError({
2151
- message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2152
- url,
2153
- cause: e
2154
- })));
2155
- try {
2156
- sub = relay.subscribe([...filters], {
2157
- onevent(evt) {
2158
- if (!settled) {
2159
- events.push(evt);
2432
+ const getRelay = (url) => ScopedCache.get(connections, url).pipe(
2433
+ Effect15.flatMap(
2434
+ (relay) => relay.connected === false ? ScopedCache.invalidate(connections, url).pipe(
2435
+ Effect15.andThen(ScopedCache.get(connections, url))
2436
+ ) : Effect15.succeed(relay)
2437
+ )
2438
+ );
2439
+ const withRelay = (url, run) => getRelay(url).pipe(
2440
+ Effect15.tap(() => Effect15.sync(() => markConnected(url))),
2441
+ Effect15.flatMap((relay) => run(relay)),
2442
+ Effect15.tapError(
2443
+ () => ScopedCache.invalidate(connections, url).pipe(
2444
+ Effect15.tap(() => Effect15.sync(() => markDisconnected(url)))
2445
+ )
2446
+ )
2447
+ );
2448
+ const collectEvents = (url, filters) => withRelay(
2449
+ url,
2450
+ (relay) => Effect15.callback((resume) => {
2451
+ const events = [];
2452
+ let settled = false;
2453
+ let timer;
2454
+ let sub;
2455
+ const cleanup = () => {
2456
+ settled = true;
2457
+ if (timer !== void 0) {
2458
+ clearTimeout(timer);
2459
+ timer = void 0;
2460
+ }
2461
+ sub?.close();
2462
+ sub = void 0;
2463
+ };
2464
+ const fail = (e) => resume(
2465
+ Effect15.fail(
2466
+ new RelayError({
2467
+ message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2468
+ url,
2469
+ cause: e
2470
+ })
2471
+ )
2472
+ );
2473
+ try {
2474
+ sub = relay.subscribe([...filters], {
2475
+ onevent(evt) {
2476
+ if (!settled) {
2477
+ events.push(evt);
2478
+ }
2479
+ },
2480
+ oneose() {
2481
+ if (settled) return;
2482
+ cleanup();
2483
+ resume(Effect15.succeed(events));
2160
2484
  }
2161
- },
2162
- oneose() {
2163
- if (settled)
2164
- return;
2485
+ });
2486
+ timer = setTimeout(() => {
2487
+ if (settled) return;
2165
2488
  cleanup();
2166
2489
  resume(Effect15.succeed(events));
2167
- }
2168
- });
2169
- timer = setTimeout(() => {
2170
- if (settled)
2171
- return;
2490
+ }, 1e4);
2491
+ } catch (e) {
2172
2492
  cleanup();
2173
- resume(Effect15.succeed(events));
2174
- }, 1e4);
2175
- } catch (e) {
2176
- cleanup();
2177
- fail(e);
2178
- }
2179
- return Effect15.sync(cleanup);
2180
- }));
2493
+ fail(e);
2494
+ }
2495
+ return Effect15.sync(cleanup);
2496
+ })
2497
+ );
2181
2498
  return {
2182
2499
  publish: (event, urls) => Effect15.gen(function* () {
2183
- const results = yield* Effect15.forEach(urls, (url) => Effect15.result(withRelay(url, (relay) => Effect15.tryPromise({
2184
- try: () => relay.publish(event),
2185
- catch: (e) => new RelayError({
2186
- message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2187
- url,
2188
- cause: e
2189
- })
2190
- }).pipe(Effect15.timeoutOrElse({
2191
- duration: "10 seconds",
2192
- onTimeout: () => Effect15.fail(new RelayError({ message: `Publish to ${url} timed out`, url }))
2193
- })))), { concurrency: "unbounded" });
2500
+ const results = yield* Effect15.forEach(
2501
+ urls,
2502
+ (url) => Effect15.result(
2503
+ withRelay(
2504
+ url,
2505
+ (relay) => Effect15.tryPromise({
2506
+ try: () => relay.publish(event),
2507
+ catch: (e) => new RelayError({
2508
+ message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2509
+ url,
2510
+ cause: e
2511
+ })
2512
+ }).pipe(
2513
+ Effect15.timeoutOrElse({
2514
+ duration: "10 seconds",
2515
+ onTimeout: () => Effect15.fail(
2516
+ new RelayError({ message: `Publish to ${url} timed out`, url })
2517
+ )
2518
+ })
2519
+ )
2520
+ )
2521
+ ),
2522
+ { concurrency: "unbounded" }
2523
+ );
2194
2524
  const failures = results.filter((r) => r._tag === "Failure");
2195
2525
  if (failures.length === urls.length && urls.length > 0) {
2196
2526
  return yield* new RelayError({
@@ -2199,90 +2529,104 @@ function createRelayHandle() {
2199
2529
  }
2200
2530
  }),
2201
2531
  fetchEvents: (ids, url) => Effect15.gen(function* () {
2202
- if (ids.length === 0)
2203
- return [];
2532
+ if (ids.length === 0) return [];
2204
2533
  return yield* collectEvents(url, [{ ids }]);
2205
2534
  }),
2206
2535
  fetchByFilter: (filter, url) => collectEvents(url, [filter]),
2207
- subscribe: (filter, url, onEvent) => withRelay(url, (relay) => Effect15.acquireRelease(Effect15.try({
2208
- try: () => relay.subscribe([filter], {
2209
- onevent(evt) {
2210
- onEvent(evt);
2211
- },
2212
- oneose() {}
2213
- }),
2214
- catch: (e) => new RelayError({
2215
- message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2216
- url,
2217
- cause: e
2218
- })
2219
- }), (sub) => Effect15.sync(() => {
2220
- sub.close();
2221
- })).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)),
2222
- sendNegMsg: (url, subId, filter, msgHex) => withRelay(url, (relay) => Effect15.callback((resume) => {
2223
- let settled = false;
2224
- let timer;
2225
- let sub;
2226
- let ws;
2227
- const cleanup = () => {
2228
- settled = true;
2229
- if (timer !== undefined) {
2230
- clearTimeout(timer);
2231
- timer = undefined;
2232
- }
2233
- sub?.close();
2234
- sub = undefined;
2235
- ws?.removeEventListener("message", handler);
2236
- ws = undefined;
2237
- };
2238
- const fail = (e) => resume(Effect15.fail(new RelayError({
2239
- message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2240
- url,
2241
- cause: e
2242
- })));
2243
- const handler = (msg) => {
2244
- if (settled || typeof msg.data !== "string")
2245
- return;
2246
- const frameOpt = parseNegMessageFrame(msg.data);
2247
- if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId)
2248
- return;
2249
- const frame = frameOpt.value;
2250
- cleanup();
2251
- if (frame[0] === "NEG-MSG") {
2252
- resume(Effect15.succeed({
2253
- msgHex: frame[2],
2254
- haveIds: [],
2255
- needIds: []
2256
- }));
2257
- return;
2258
- }
2259
- fail(new Error(`NEG-ERR: ${frame[2]}`));
2260
- };
2261
- try {
2262
- sub = relay.subscribe([filter], {
2263
- onevent() {},
2264
- oneose() {}
2265
- });
2266
- ws = relay._ws || relay.ws;
2267
- if (!ws) {
2536
+ subscribe: (filter, url, onEvent) => withRelay(
2537
+ url,
2538
+ (relay) => Effect15.acquireRelease(
2539
+ Effect15.try({
2540
+ try: () => relay.subscribe([filter], {
2541
+ onevent(evt) {
2542
+ onEvent(evt);
2543
+ },
2544
+ oneose() {
2545
+ }
2546
+ }),
2547
+ catch: (e) => new RelayError({
2548
+ message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2549
+ url,
2550
+ cause: e
2551
+ })
2552
+ }),
2553
+ (sub) => Effect15.sync(() => {
2554
+ sub.close();
2555
+ })
2556
+ ).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)
2557
+ ),
2558
+ sendNegMsg: (url, subId, filter, msgHex) => withRelay(
2559
+ url,
2560
+ (relay) => Effect15.callback((resume) => {
2561
+ let settled = false;
2562
+ let timer;
2563
+ let sub;
2564
+ let ws;
2565
+ const cleanup = () => {
2566
+ settled = true;
2567
+ if (timer !== void 0) {
2568
+ clearTimeout(timer);
2569
+ timer = void 0;
2570
+ }
2571
+ sub?.close();
2572
+ sub = void 0;
2573
+ ws?.removeEventListener("message", handler);
2574
+ ws = void 0;
2575
+ };
2576
+ const fail = (e) => resume(
2577
+ Effect15.fail(
2578
+ new RelayError({
2579
+ message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2580
+ url,
2581
+ cause: e
2582
+ })
2583
+ )
2584
+ );
2585
+ const handler = (msg) => {
2586
+ if (settled || typeof msg.data !== "string") return;
2587
+ const frameOpt = parseNegMessageFrame(msg.data);
2588
+ if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId) return;
2589
+ const frame = frameOpt.value;
2268
2590
  cleanup();
2269
- fail(new Error("Cannot access relay WebSocket"));
2270
- return Effect15.succeed(undefined);
2271
- }
2272
- timer = setTimeout(() => {
2273
- if (settled)
2591
+ if (frame[0] === "NEG-MSG") {
2592
+ resume(
2593
+ Effect15.succeed({
2594
+ msgHex: frame[2],
2595
+ haveIds: [],
2596
+ needIds: []
2597
+ })
2598
+ );
2274
2599
  return;
2600
+ }
2601
+ fail(new Error(`NEG-ERR: ${frame[2]}`));
2602
+ };
2603
+ try {
2604
+ sub = relay.subscribe([filter], {
2605
+ onevent() {
2606
+ },
2607
+ oneose() {
2608
+ }
2609
+ });
2610
+ ws = relay._ws || relay.ws;
2611
+ if (!ws) {
2612
+ cleanup();
2613
+ fail(new Error("Cannot access relay WebSocket"));
2614
+ return Effect15.succeed(void 0);
2615
+ }
2616
+ timer = setTimeout(() => {
2617
+ if (settled) return;
2618
+ cleanup();
2619
+ fail(new Error("NIP-77 negotiation timeout"));
2620
+ }, 3e4);
2621
+ ws.addEventListener("message", handler);
2622
+ ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
2623
+ } catch (e) {
2275
2624
  cleanup();
2276
- fail(new Error("NIP-77 negotiation timeout"));
2277
- }, 30000);
2278
- ws.addEventListener("message", handler);
2279
- ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
2280
- } catch (e) {
2281
- cleanup();
2282
- fail(e);
2283
- }
2284
- return Effect15.sync(cleanup);
2285
- })),
2625
+ fail(e);
2626
+ }
2627
+ return Effect15.sync(cleanup);
2628
+ })
2629
+ ),
2286
2630
  closeAll: () => ScopedCache.invalidateAll(connections),
2287
2631
  getStatus: () => ({ connectedUrls: [...connectedUrls] }),
2288
2632
  subscribeStatus: (callback) => {
@@ -2294,10 +2638,10 @@ function createRelayHandle() {
2294
2638
  }
2295
2639
 
2296
2640
  // src/layers/RelayLive.ts
2297
- var RelayLive = Layer4.effect(Relay, createRelayHandle());
2641
+ var RelayLive = Layer5.effect(Relay, createRelayHandle());
2298
2642
 
2299
2643
  // src/layers/GiftWrapLive.ts
2300
- import { Effect as Effect17, Layer as Layer5 } from "effect";
2644
+ import { Effect as Effect17, Layer as Layer6 } from "effect";
2301
2645
 
2302
2646
  // src/sync/gift-wrap.ts
2303
2647
  import { Effect as Effect16 } from "effect";
@@ -2334,14 +2678,17 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
2334
2678
  }
2335
2679
 
2336
2680
  // src/layers/GiftWrapLive.ts
2337
- var GiftWrapLive = Layer5.effect(GiftWrap3, Effect17.gen(function* () {
2338
- const identity = yield* Identity;
2339
- const epochStore = yield* EpochStore;
2340
- return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2341
- }));
2681
+ var GiftWrapLive = Layer6.effect(
2682
+ GiftWrap3,
2683
+ Effect17.gen(function* () {
2684
+ const identity = yield* Identity;
2685
+ const epochStore = yield* EpochStore;
2686
+ return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2687
+ })
2688
+ );
2342
2689
 
2343
2690
  // src/layers/PublishQueueLive.ts
2344
- import { Effect as Effect19, Layer as Layer6 } from "effect";
2691
+ import { Effect as Effect19, Layer as Layer7 } from "effect";
2345
2692
 
2346
2693
  // src/sync/publish-queue.ts
2347
2694
  import { Effect as Effect18, Ref as Ref4 } from "effect";
@@ -2352,12 +2699,11 @@ function persist(storage, pending) {
2352
2699
  function createPublishQueue(storage, relay) {
2353
2700
  return Effect18.gen(function* () {
2354
2701
  const stored = yield* storage.getMeta(META_KEY);
2355
- const initial = Array.isArray(stored) ? new Set(stored) : new Set;
2702
+ const initial = Array.isArray(stored) ? new Set(stored) : /* @__PURE__ */ new Set();
2356
2703
  const pendingRef = yield* Ref4.make(initial);
2357
- const listeners = new Set;
2704
+ const listeners = /* @__PURE__ */ new Set();
2358
2705
  const notify = (pending) => {
2359
- for (const listener of listeners)
2360
- listener(pending.size);
2706
+ for (const listener of listeners) listener(pending.size);
2361
2707
  };
2362
2708
  return {
2363
2709
  enqueue: (eventId) => Effect18.gen(function* () {
@@ -2371,13 +2717,11 @@ function createPublishQueue(storage, relay) {
2371
2717
  }),
2372
2718
  flush: (relayUrls) => Effect18.gen(function* () {
2373
2719
  const pending = yield* Ref4.get(pendingRef);
2374
- if (pending.size === 0)
2375
- return;
2376
- const succeeded = new Set;
2720
+ if (pending.size === 0) return;
2721
+ const succeeded = /* @__PURE__ */ new Set();
2377
2722
  let consecutiveFailures = 0;
2378
2723
  for (const eventId of pending) {
2379
- if (consecutiveFailures >= 3)
2380
- break;
2724
+ if (consecutiveFailures >= 3) break;
2381
2725
  const gw = yield* storage.getGiftWrap(eventId);
2382
2726
  if (!gw || !gw.event) {
2383
2727
  succeeded.add(eventId);
@@ -2387,7 +2731,6 @@ function createPublishQueue(storage, relay) {
2387
2731
  const result = yield* Effect18.result(relay.publish(gw.event, relayUrls));
2388
2732
  if (result._tag === "Success") {
2389
2733
  succeeded.add(eventId);
2390
- yield* storage.stripGiftWrapBlob(eventId);
2391
2734
  consecutiveFailures = 0;
2392
2735
  } else {
2393
2736
  consecutiveFailures++;
@@ -2415,27 +2758,29 @@ function createPublishQueue(storage, relay) {
2415
2758
  }
2416
2759
 
2417
2760
  // src/layers/PublishQueueLive.ts
2418
- var PublishQueueLive = Layer6.effect(PublishQueue, Effect19.gen(function* () {
2419
- const storage = yield* Storage;
2420
- const relay = yield* Relay;
2421
- return yield* createPublishQueue(storage, relay);
2422
- }));
2761
+ var PublishQueueLive = Layer7.effect(
2762
+ PublishQueue,
2763
+ Effect19.gen(function* () {
2764
+ const storage = yield* Storage;
2765
+ const relay = yield* Relay;
2766
+ return yield* createPublishQueue(storage, relay);
2767
+ })
2768
+ );
2423
2769
 
2424
2770
  // src/layers/SyncStatusLive.ts
2425
- import { Layer as Layer7 } from "effect";
2771
+ import { Layer as Layer8 } from "effect";
2426
2772
 
2427
2773
  // src/sync/sync-status.ts
2428
2774
  import { Effect as Effect20, SubscriptionRef } from "effect";
2429
2775
  function createSyncStatusHandle() {
2430
2776
  return Effect20.gen(function* () {
2431
2777
  const ref = yield* SubscriptionRef.make("idle");
2432
- const listeners = new Set;
2778
+ const listeners = /* @__PURE__ */ new Set();
2433
2779
  return {
2434
2780
  get: () => SubscriptionRef.get(ref),
2435
2781
  set: (status) => Effect20.gen(function* () {
2436
2782
  yield* SubscriptionRef.set(ref, status);
2437
- for (const listener of listeners)
2438
- listener(status);
2783
+ for (const listener of listeners) listener(status);
2439
2784
  }),
2440
2785
  subscribe: (callback) => {
2441
2786
  listeners.add(callback);
@@ -2446,12 +2791,11 @@ function createSyncStatusHandle() {
2446
2791
  }
2447
2792
 
2448
2793
  // src/layers/SyncStatusLive.ts
2449
- var SyncStatusLive = Layer7.effect(SyncStatus, createSyncStatusHandle());
2794
+ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
2450
2795
 
2451
2796
  // src/layers/TablinumLive.ts
2452
2797
  function reportSyncError(onSyncError, error) {
2453
- if (!onSyncError)
2454
- return;
2798
+ if (!onSyncError) return;
2455
2799
  onSyncError(error instanceof Error ? error : new Error(String(error)));
2456
2800
  }
2457
2801
  function mapMemberRecord(record) {
@@ -2459,239 +2803,365 @@ function mapMemberRecord(record) {
2459
2803
  id: record.id,
2460
2804
  addedAt: record.addedAt,
2461
2805
  addedInEpoch: record.addedInEpoch,
2462
- ...record.name !== undefined ? { name: record.name } : {},
2463
- ...record.picture !== undefined ? { picture: record.picture } : {},
2464
- ...record.about !== undefined ? { about: record.about } : {},
2465
- ...record.nip05 !== undefined ? { nip05: record.nip05 } : {},
2466
- ...record.removedAt !== undefined ? { removedAt: record.removedAt } : {},
2467
- ...record.removedInEpoch !== undefined ? { removedInEpoch: record.removedInEpoch } : {}
2806
+ ...record.name !== void 0 ? { name: record.name } : {},
2807
+ ...record.picture !== void 0 ? { picture: record.picture } : {},
2808
+ ...record.about !== void 0 ? { about: record.about } : {},
2809
+ ...record.nip05 !== void 0 ? { nip05: record.nip05 } : {},
2810
+ ...record.removedAt !== void 0 ? { removedAt: record.removedAt } : {},
2811
+ ...record.removedInEpoch !== void 0 ? { removedInEpoch: record.removedInEpoch } : {}
2468
2812
  };
2469
2813
  }
2470
- var IdentityWithDeps = IdentityLive.pipe(Layer8.provide(StorageLive));
2471
- var EpochStoreWithDeps = EpochStoreLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(StorageLive));
2472
- var GiftWrapWithDeps = GiftWrapLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(EpochStoreWithDeps));
2473
- var PublishQueueWithDeps = PublishQueueLive.pipe(Layer8.provide(StorageLive), Layer8.provide(RelayLive));
2474
- var AllServicesLive = Layer8.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2475
- var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2476
- const config = yield* Config;
2477
- const identity = yield* Identity;
2478
- const epochStore = yield* EpochStore;
2479
- const storage = yield* Storage;
2480
- const relay = yield* Relay;
2481
- const giftWrap = yield* GiftWrap3;
2482
- const publishQueue = yield* PublishQueue;
2483
- const syncStatus = yield* SyncStatus;
2484
- const scope = yield* Effect21.scope;
2485
- const pubsub = yield* PubSub2.unbounded();
2486
- const replayingRef = yield* Ref5.make(false);
2487
- const closedRef = yield* Ref5.make(false);
2488
- const watchCtx = { pubsub, replayingRef };
2489
- const schemaEntries = Object.entries(config.schema);
2490
- const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2491
- const knownCollections = new Map(allSchemaEntries.map(([, def]) => [def.name, def.eventRetention]));
2492
- let notifyAuthor;
2493
- const syncHandle = createSyncHandle(storage, giftWrap, relay, publishQueue, syncStatus, watchCtx, config.relays, knownCollections, epochStore, identity.privateKey, identity.publicKey, scope, config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : undefined, (pubkey) => notifyAuthor?.(pubkey), config.onRemoved, config.onMembersChanged);
2494
- const onWrite = (event) => Effect21.gen(function* () {
2495
- const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2496
- const dTag = `${event.collection}:${event.recordId}`;
2497
- const wrapResult = yield* Effect21.result(giftWrap.wrap({
2498
- kind: 1,
2499
- content,
2500
- tags: [["d", dTag]],
2501
- created_at: Math.floor(event.createdAt / 1000)
2502
- }));
2503
- if (wrapResult._tag === "Failure") {
2504
- reportSyncError(config.onSyncError, wrapResult.failure);
2505
- return;
2506
- }
2507
- const gw = wrapResult.success;
2508
- yield* storage.putGiftWrap({ id: gw.id, createdAt: gw.created_at });
2509
- yield* Effect21.forkIn(Effect21.gen(function* () {
2510
- const publishResult = yield* Effect21.result(syncHandle.publishLocal({
2511
- id: gw.id,
2512
- event: gw,
2513
- createdAt: gw.created_at
2514
- }));
2515
- if (publishResult._tag === "Failure") {
2516
- reportSyncError(config.onSyncError, publishResult.failure);
2814
+ var IdentityWithDeps = IdentityLive.pipe(Layer9.provide(StorageLive));
2815
+ var EpochStoreWithDeps = EpochStoreLive.pipe(
2816
+ Layer9.provide(IdentityWithDeps),
2817
+ Layer9.provide(StorageLive)
2818
+ );
2819
+ var GiftWrapWithDeps = GiftWrapLive.pipe(
2820
+ Layer9.provide(IdentityWithDeps),
2821
+ Layer9.provide(EpochStoreWithDeps)
2822
+ );
2823
+ var PublishQueueWithDeps = PublishQueueLive.pipe(
2824
+ Layer9.provide(StorageLive),
2825
+ Layer9.provide(RelayLive)
2826
+ );
2827
+ var AllServicesLive = Layer9.mergeAll(
2828
+ IdentityWithDeps,
2829
+ EpochStoreWithDeps,
2830
+ StorageLive,
2831
+ RelayLive,
2832
+ GiftWrapWithDeps,
2833
+ PublishQueueWithDeps,
2834
+ SyncStatusLive
2835
+ );
2836
+ var TablinumLive = Layer9.effect(
2837
+ Tablinum,
2838
+ Effect21.gen(function* () {
2839
+ const config = yield* Config;
2840
+ const identity = yield* Identity;
2841
+ const epochStore = yield* EpochStore;
2842
+ const storage = yield* Storage;
2843
+ const relay = yield* Relay;
2844
+ const giftWrap = yield* GiftWrap3;
2845
+ const publishQueue = yield* PublishQueue;
2846
+ const syncStatus = yield* SyncStatus;
2847
+ const scope = yield* Effect21.scope;
2848
+ const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2849
+ const pubsub = yield* PubSub2.unbounded();
2850
+ const replayingRef = yield* Ref5.make(false);
2851
+ const closedRef = yield* Ref5.make(false);
2852
+ const watchCtx = { pubsub, replayingRef };
2853
+ const schemaEntries = Object.entries(config.schema);
2854
+ const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2855
+ const knownCollections = new Map(
2856
+ allSchemaEntries.map(([, def]) => [def.name, def.eventRetention])
2857
+ );
2858
+ let notifyAuthor;
2859
+ const syncHandle = createSyncHandle(
2860
+ storage,
2861
+ giftWrap,
2862
+ relay,
2863
+ publishQueue,
2864
+ syncStatus,
2865
+ watchCtx,
2866
+ config.relays,
2867
+ knownCollections,
2868
+ epochStore,
2869
+ identity.privateKey,
2870
+ identity.publicKey,
2871
+ scope,
2872
+ config.logLevel,
2873
+ config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : void 0,
2874
+ (pubkey) => notifyAuthor?.(pubkey),
2875
+ config.onRemoved,
2876
+ config.onMembersChanged
2877
+ );
2878
+ const onWrite = (event) => Effect21.gen(function* () {
2879
+ const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2880
+ const dTag = `${event.collection}:${event.recordId}`;
2881
+ const wrapResult = yield* Effect21.result(
2882
+ giftWrap.wrap({
2883
+ kind: 1,
2884
+ content,
2885
+ tags: [["d", dTag]],
2886
+ created_at: Math.floor(event.createdAt / 1e3)
2887
+ })
2888
+ );
2889
+ if (wrapResult._tag === "Failure") {
2890
+ reportSyncError(config.onSyncError, wrapResult.failure);
2891
+ return;
2517
2892
  }
2518
- }), scope);
2519
- });
2520
- const knownAuthors = new Set;
2521
- const putMemberRecord = (record) => Effect21.gen(function* () {
2522
- const existing = yield* storage.getRecord("_members", record.id);
2523
- const event = {
2524
- id: uuidv7(),
2525
- collection: "_members",
2526
- recordId: record.id,
2527
- kind: existing ? "u" : "c",
2528
- data: record,
2529
- createdAt: Date.now(),
2530
- author: identity.publicKey
2531
- };
2532
- yield* storage.putEvent(event);
2533
- yield* applyEvent(storage, event);
2534
- yield* onWrite(event);
2535
- yield* notifyChange(watchCtx, {
2536
- collection: "_members",
2537
- recordId: record.id,
2538
- kind: existing ? "update" : "create"
2893
+ const gw = wrapResult.success;
2894
+ yield* storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
2895
+ yield* Effect21.forkIn(
2896
+ Effect21.gen(function* () {
2897
+ const publishResult = yield* Effect21.result(
2898
+ syncHandle.publishLocal({
2899
+ id: gw.id,
2900
+ event: gw,
2901
+ createdAt: gw.created_at
2902
+ })
2903
+ );
2904
+ if (publishResult._tag === "Failure") {
2905
+ reportSyncError(config.onSyncError, publishResult.failure);
2906
+ }
2907
+ }),
2908
+ scope
2909
+ );
2539
2910
  });
2540
- config.onMembersChanged?.();
2541
- });
2542
- notifyAuthor = (pubkey) => {
2543
- if (knownAuthors.has(pubkey))
2544
- return;
2545
- knownAuthors.add(pubkey);
2546
- Effect21.runFork(Effect21.gen(function* () {
2547
- const existing = yield* storage.getRecord("_members", pubkey);
2548
- if (!existing) {
2549
- yield* putMemberRecord({
2550
- id: pubkey,
2551
- addedAt: Date.now(),
2552
- addedInEpoch: getCurrentEpoch(epochStore).id
2553
- });
2554
- }
2555
- const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none())));
2556
- if (Option9.isSome(profileOpt)) {
2557
- const current = yield* storage.getRecord("_members", pubkey);
2558
- if (current) {
2559
- yield* storage.putRecord("_members", {
2560
- ...current,
2561
- ...profileOpt.value
2562
- });
2563
- yield* notifyChange(watchCtx, {
2564
- collection: "_members",
2565
- recordId: pubkey,
2566
- kind: "update"
2567
- });
2568
- config.onMembersChanged?.();
2569
- }
2570
- }
2571
- }).pipe(Effect21.ignore, Effect21.forkIn(scope)));
2572
- };
2573
- const handles = new Map;
2574
- for (const [, def] of allSchemaEntries) {
2575
- const validator = buildValidator(def.name, def);
2576
- const partialValidator = buildPartialValidator(def.name, def);
2577
- const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite);
2578
- handles.set(def.name, handle);
2579
- }
2580
- yield* syncHandle.startSubscription();
2581
- const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2582
- if (!selfMember) {
2583
- yield* putMemberRecord({
2584
- id: identity.publicKey,
2585
- addedAt: Date.now(),
2586
- addedInEpoch: getCurrentEpoch(epochStore).id
2911
+ const knownAuthors = /* @__PURE__ */ new Set();
2912
+ const putMemberRecord = (record) => Effect21.gen(function* () {
2913
+ const existing = yield* storage.getRecord("_members", record.id);
2914
+ const event = {
2915
+ id: uuidv7(),
2916
+ collection: "_members",
2917
+ recordId: record.id,
2918
+ kind: existing ? "u" : "c",
2919
+ data: record,
2920
+ createdAt: Date.now(),
2921
+ author: identity.publicKey
2922
+ };
2923
+ yield* storage.putEvent(event);
2924
+ yield* applyEvent(storage, event);
2925
+ yield* onWrite(event);
2926
+ yield* notifyChange(watchCtx, {
2927
+ collection: "_members",
2928
+ recordId: record.id,
2929
+ kind: existing ? "update" : "create"
2930
+ });
2931
+ config.onMembersChanged?.();
2587
2932
  });
2588
- }
2589
- const ensureOpen = (effect) => Effect21.gen(function* () {
2590
- if (yield* Ref5.get(closedRef)) {
2591
- return yield* new StorageError({ message: "Database is closed" });
2592
- }
2593
- return yield* effect;
2594
- });
2595
- const ensureSyncOpen = (effect) => Effect21.gen(function* () {
2596
- if (yield* Ref5.get(closedRef)) {
2597
- return yield* new SyncError({ message: "Database is closed", phase: "init" });
2598
- }
2599
- return yield* effect;
2600
- });
2601
- const dbHandle = {
2602
- collection: (name) => {
2603
- const handle = handles.get(name);
2604
- if (!handle)
2605
- throw new Error(`Collection "${name}" not found in schema`);
2606
- return handle;
2607
- },
2608
- publicKey: identity.publicKey,
2609
- members: handles.get("_members"),
2610
- exportKey: () => identity.exportKey(),
2611
- exportInvite: () => ({
2612
- epochKeys: [...exportEpochKeys(epochStore)],
2613
- relays: [...config.relays],
2614
- dbName: config.dbName
2615
- }),
2616
- close: () => Effect21.gen(function* () {
2617
- if (yield* Ref5.get(closedRef))
2618
- return;
2619
- yield* Ref5.set(closedRef, true);
2620
- yield* Scope4.close(scope, Exit.void);
2621
- }),
2622
- rebuild: () => ensureOpen(rebuild(storage, allSchemaEntries.map(([, def]) => def.name))),
2623
- sync: () => ensureSyncOpen(syncHandle.sync()),
2624
- getSyncStatus: () => syncStatus.get(),
2625
- subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
2626
- pendingCount: () => publishQueue.size(),
2627
- subscribePendingCount: (callback) => publishQueue.subscribe(callback),
2628
- getRelayStatus: () => relay.getStatus(),
2629
- subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
2630
- addMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2631
- const existing = yield* storage.getRecord("_members", pubkey);
2632
- if (existing && !existing.removedAt)
2633
- return;
2933
+ notifyAuthor = (pubkey) => {
2934
+ if (knownAuthors.has(pubkey)) return;
2935
+ knownAuthors.add(pubkey);
2936
+ Effect21.runFork(
2937
+ Effect21.gen(function* () {
2938
+ const existing = yield* storage.getRecord("_members", pubkey);
2939
+ if (!existing) {
2940
+ yield* putMemberRecord({
2941
+ id: pubkey,
2942
+ addedAt: Date.now(),
2943
+ addedInEpoch: getCurrentEpoch(epochStore).id
2944
+ });
2945
+ }
2946
+ const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(
2947
+ Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none()))
2948
+ );
2949
+ if (Option9.isSome(profileOpt)) {
2950
+ const current = yield* storage.getRecord("_members", pubkey);
2951
+ if (current) {
2952
+ yield* storage.putRecord("_members", {
2953
+ ...current,
2954
+ ...profileOpt.value
2955
+ });
2956
+ yield* notifyChange(watchCtx, {
2957
+ collection: "_members",
2958
+ recordId: pubkey,
2959
+ kind: "update"
2960
+ });
2961
+ config.onMembersChanged?.();
2962
+ }
2963
+ }
2964
+ }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope))
2965
+ );
2966
+ };
2967
+ const handles = /* @__PURE__ */ new Map();
2968
+ for (const [, def] of allSchemaEntries) {
2969
+ const validator = buildValidator(def.name, def);
2970
+ const partialValidator = buildPartialValidator(def.name, def);
2971
+ const handle = createCollectionHandle(
2972
+ def,
2973
+ storage,
2974
+ watchCtx,
2975
+ validator,
2976
+ partialValidator,
2977
+ uuidv7,
2978
+ identity.publicKey,
2979
+ onWrite,
2980
+ config.logLevel
2981
+ );
2982
+ handles.set(def.name, handle);
2983
+ }
2984
+ yield* syncHandle.startSubscription();
2985
+ yield* Effect21.logInfo("Tablinum ready", {
2986
+ dbName: config.dbName,
2987
+ collections: schemaEntries.map(([name]) => name),
2988
+ relays: config.relays
2989
+ });
2990
+ const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2991
+ if (!selfMember) {
2634
2992
  yield* putMemberRecord({
2635
- id: pubkey,
2993
+ id: identity.publicKey,
2636
2994
  addedAt: Date.now(),
2637
- addedInEpoch: getCurrentEpoch(epochStore).id,
2638
- ...existing ? { removedAt: undefined, removedInEpoch: undefined } : {}
2639
- });
2640
- })),
2641
- removeMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2642
- const allMembers = yield* storage.getAllRecords("_members");
2643
- const activeMembers = allMembers.filter((member) => !member.removedAt && member.id !== pubkey);
2644
- const activePubkeys = activeMembers.map((member) => member.id);
2645
- const result = createRotation(epochStore, identity.privateKey, identity.publicKey, activePubkeys, [pubkey]);
2646
- addEpoch(epochStore, result.epoch);
2647
- epochStore.currentEpochId = result.epoch.id;
2648
- yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
2649
- const memberRecord = yield* storage.getRecord("_members", pubkey);
2650
- yield* putMemberRecord({
2651
- ...memberRecord ?? {
2652
- id: pubkey,
2653
- addedAt: 0,
2654
- addedInEpoch: EpochId("epoch-0")
2655
- },
2656
- removedAt: Date.now(),
2657
- removedInEpoch: result.epoch.id
2995
+ addedInEpoch: getCurrentEpoch(epochStore).id
2658
2996
  });
2659
- yield* Effect21.forEach(result.wrappedEvents, (wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))), Effect21.ignore), { discard: true });
2660
- yield* Effect21.forEach(result.removalNotices, (notice) => relay.publish(notice, [...config.relays]).pipe(Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))), Effect21.ignore), { discard: true });
2661
- yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
2662
- })),
2663
- getMembers: () => ensureOpen(Effect21.gen(function* () {
2664
- const allRecords = yield* storage.getAllRecords("_members");
2665
- return allRecords.filter((record) => !record._d).map(mapMemberRecord);
2666
- })),
2667
- getProfile: () => ensureOpen(Effect21.gen(function* () {
2668
- const record = yield* storage.getRecord("_members", identity.publicKey);
2669
- if (!record)
2670
- return {};
2671
- const profile = {};
2672
- if (record.name !== undefined)
2673
- profile.name = record.name;
2674
- if (record.picture !== undefined)
2675
- profile.picture = record.picture;
2676
- if (record.about !== undefined)
2677
- profile.about = record.about;
2678
- if (record.nip05 !== undefined)
2679
- profile.nip05 = record.nip05;
2680
- return profile;
2681
- })),
2682
- setProfile: (profile) => ensureOpen(Effect21.gen(function* () {
2683
- const existing = yield* storage.getRecord("_members", identity.publicKey);
2684
- if (!existing) {
2685
- return yield* new ValidationError({ message: "Current user is not a member" });
2686
- }
2687
- const { _d, _u, _a, _e, ...memberFields } = existing;
2688
- yield* putMemberRecord({ ...memberFields, ...profile });
2689
- }))
2690
- };
2691
- return dbHandle;
2692
- })).pipe(Layer8.provide(AllServicesLive));
2997
+ }
2998
+ const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2999
+ const ensureOpen = (effect) => withLog(
3000
+ Effect21.gen(function* () {
3001
+ if (yield* Ref5.get(closedRef)) {
3002
+ return yield* new StorageError({ message: "Database is closed" });
3003
+ }
3004
+ return yield* effect;
3005
+ })
3006
+ );
3007
+ const ensureSyncOpen = (effect) => withLog(
3008
+ Effect21.gen(function* () {
3009
+ if (yield* Ref5.get(closedRef)) {
3010
+ return yield* new SyncError({ message: "Database is closed", phase: "init" });
3011
+ }
3012
+ return yield* effect;
3013
+ })
3014
+ );
3015
+ const dbHandle = {
3016
+ collection: (name) => {
3017
+ const handle = handles.get(name);
3018
+ if (!handle) throw new Error(`Collection "${name}" not found in schema`);
3019
+ return handle;
3020
+ },
3021
+ publicKey: identity.publicKey,
3022
+ members: handles.get("_members"),
3023
+ exportKey: () => identity.exportKey(),
3024
+ exportInvite: () => ({
3025
+ epochKeys: [...exportEpochKeys(epochStore)],
3026
+ relays: [...config.relays],
3027
+ dbName: config.dbName
3028
+ }),
3029
+ close: () => withLog(
3030
+ Effect21.gen(function* () {
3031
+ if (yield* Ref5.get(closedRef)) return;
3032
+ yield* Ref5.set(closedRef, true);
3033
+ syncHandle.stopHealing();
3034
+ yield* Scope4.close(scope, Exit.void);
3035
+ })
3036
+ ),
3037
+ rebuild: () => ensureOpen(
3038
+ rebuild(
3039
+ storage,
3040
+ allSchemaEntries.map(([, def]) => def.name)
3041
+ )
3042
+ ),
3043
+ sync: () => ensureSyncOpen(
3044
+ syncHandle.sync().pipe(
3045
+ Effect21.tap(
3046
+ () => Effect21.sync(() => {
3047
+ syncHandle.startHealing();
3048
+ })
3049
+ )
3050
+ )
3051
+ ),
3052
+ getSyncStatus: () => syncStatus.get(),
3053
+ subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
3054
+ pendingCount: () => publishQueue.size(),
3055
+ subscribePendingCount: (callback) => publishQueue.subscribe(callback),
3056
+ getRelayStatus: () => relay.getStatus(),
3057
+ subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
3058
+ addMember: (pubkey) => ensureOpen(
3059
+ Effect21.gen(function* () {
3060
+ const existing = yield* storage.getRecord("_members", pubkey);
3061
+ if (existing && !existing.removedAt) return;
3062
+ yield* putMemberRecord({
3063
+ id: pubkey,
3064
+ addedAt: Date.now(),
3065
+ addedInEpoch: getCurrentEpoch(epochStore).id,
3066
+ ...existing ? { removedAt: void 0, removedInEpoch: void 0 } : {}
3067
+ });
3068
+ })
3069
+ ),
3070
+ removeMember: (pubkey) => ensureOpen(
3071
+ Effect21.gen(function* () {
3072
+ const allMembers = yield* storage.getAllRecords("_members");
3073
+ const activeMembers = allMembers.filter(
3074
+ (member) => !member.removedAt && member.id !== pubkey
3075
+ );
3076
+ const activePubkeys = activeMembers.map((member) => member.id);
3077
+ const result = createRotation(
3078
+ epochStore,
3079
+ identity.privateKey,
3080
+ identity.publicKey,
3081
+ activePubkeys,
3082
+ [pubkey]
3083
+ );
3084
+ addEpoch(epochStore, result.epoch);
3085
+ epochStore.currentEpochId = result.epoch.id;
3086
+ yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
3087
+ const memberRecord = yield* storage.getRecord("_members", pubkey);
3088
+ yield* putMemberRecord({
3089
+ ...memberRecord ?? {
3090
+ id: pubkey,
3091
+ addedAt: 0,
3092
+ addedInEpoch: EpochId("epoch-0")
3093
+ },
3094
+ removedAt: Date.now(),
3095
+ removedInEpoch: result.epoch.id
3096
+ });
3097
+ yield* Effect21.forEach(
3098
+ result.wrappedEvents,
3099
+ (wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(
3100
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3101
+ Effect21.ignore
3102
+ ),
3103
+ { discard: true }
3104
+ );
3105
+ yield* Effect21.forEach(
3106
+ result.removalNotices,
3107
+ (notice) => relay.publish(notice, [...config.relays]).pipe(
3108
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3109
+ Effect21.ignore
3110
+ ),
3111
+ { discard: true }
3112
+ );
3113
+ yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
3114
+ })
3115
+ ),
3116
+ getMembers: () => ensureOpen(
3117
+ Effect21.gen(function* () {
3118
+ const allRecords = yield* storage.getAllRecords("_members");
3119
+ return allRecords.filter((record) => !record._d).map(mapMemberRecord);
3120
+ })
3121
+ ),
3122
+ getProfile: () => ensureOpen(
3123
+ Effect21.gen(function* () {
3124
+ const record = yield* storage.getRecord("_members", identity.publicKey);
3125
+ if (!record) return {};
3126
+ const profile = {};
3127
+ if (record.name !== void 0) profile.name = record.name;
3128
+ if (record.picture !== void 0) profile.picture = record.picture;
3129
+ if (record.about !== void 0) profile.about = record.about;
3130
+ if (record.nip05 !== void 0) profile.nip05 = record.nip05;
3131
+ return profile;
3132
+ })
3133
+ ),
3134
+ setProfile: (profile) => ensureOpen(
3135
+ Effect21.gen(function* () {
3136
+ const existing = yield* storage.getRecord("_members", identity.publicKey);
3137
+ if (!existing) {
3138
+ return yield* new ValidationError({ message: "Current user is not a member" });
3139
+ }
3140
+ const { _d, _u, _a, _e, ...memberFields } = existing;
3141
+ yield* putMemberRecord({ ...memberFields, ...profile });
3142
+ })
3143
+ )
3144
+ };
3145
+ return dbHandle;
3146
+ }).pipe(Effect21.withLogSpan("tablinum.init"))
3147
+ ).pipe(Layer9.provide(AllServicesLive));
2693
3148
 
2694
3149
  // src/db/create-tablinum.ts
3150
+ function resolveLogLevel(input) {
3151
+ if (input === void 0 || input === "none") return "None";
3152
+ switch (input) {
3153
+ case "debug":
3154
+ return "Debug";
3155
+ case "info":
3156
+ return "Info";
3157
+ case "warning":
3158
+ return "Warn";
3159
+ case "error":
3160
+ return "Error";
3161
+ default:
3162
+ return input;
3163
+ }
3164
+ }
2695
3165
  function validateConfig(config) {
2696
3166
  return Effect22.gen(function* () {
2697
3167
  if (Object.keys(config.schema).length === 0) {
@@ -2705,22 +3175,25 @@ function createTablinum(config) {
2705
3175
  return Effect22.gen(function* () {
2706
3176
  yield* validateConfig(config);
2707
3177
  const runtimeConfig = yield* resolveRuntimeConfig(config);
3178
+ const logLevel = resolveLogLevel(config.logLevel);
2708
3179
  const configValue = {
2709
3180
  ...runtimeConfig,
2710
3181
  schema: config.schema,
3182
+ logLevel,
2711
3183
  onSyncError: config.onSyncError,
2712
3184
  onRemoved: config.onRemoved,
2713
3185
  onMembersChanged: config.onMembersChanged
2714
3186
  };
2715
- const configLayer = Layer9.succeed(Config, configValue);
2716
- const fullLayer = TablinumLive.pipe(Layer9.provide(configLayer));
2717
- const ctx = yield* Layer9.build(fullLayer);
3187
+ const configLayer = Layer10.succeed(Config, configValue);
3188
+ const logLayer = Layer10.succeed(References4.MinimumLogLevel, logLevel);
3189
+ const fullLayer = TablinumLive.pipe(Layer10.provide(configLayer), Layer10.provide(logLayer));
3190
+ const ctx = yield* Layer10.build(fullLayer);
2718
3191
  return ServiceMap10.get(ctx, Tablinum);
2719
3192
  });
2720
3193
  }
2721
3194
 
2722
3195
  // src/svelte/collection.svelte.ts
2723
- import { Effect as Effect24, Fiber, Option as Option11, Stream as Stream4 } from "effect";
3196
+ import { Effect as Effect24, Fiber, Option as Option11, References as References5, Stream as Stream4 } from "effect";
2724
3197
 
2725
3198
  // src/svelte/deferred.ts
2726
3199
  function createDeferred() {
@@ -2735,14 +3208,12 @@ function createDeferred() {
2735
3208
  promise,
2736
3209
  settled: () => settled,
2737
3210
  resolve: (value) => {
2738
- if (settled)
2739
- return;
3211
+ if (settled) return;
2740
3212
  settled = true;
2741
3213
  resolvePromise(value);
2742
3214
  },
2743
3215
  reject: (reason) => {
2744
- if (settled)
2745
- return;
3216
+ if (settled) return;
2746
3217
  settled = true;
2747
3218
  rejectPromise(reason);
2748
3219
  }
@@ -2764,7 +3235,9 @@ function wrapQueryBuilder(getBuilder, touchVersion, ready) {
2764
3235
  },
2765
3236
  first: () => {
2766
3237
  touchVersion();
2767
- return ready.then(() => Effect23.runPromise(Effect23.map(getBuilder().first(), Option10.getOrNull)));
3238
+ return ready.then(
3239
+ () => Effect23.runPromise(Effect23.map(getBuilder().first(), Option10.getOrNull))
3240
+ );
2768
3241
  },
2769
3242
  count: () => {
2770
3243
  touchVersion();
@@ -2796,7 +3269,9 @@ function wrapOrderByBuilder(getBuilder, touchVersion, ready) {
2796
3269
  },
2797
3270
  first: () => {
2798
3271
  touchVersion();
2799
- return ready.then(() => Effect23.runPromise(Effect23.map(getBuilder().first(), Option10.getOrNull)));
3272
+ return ready.then(
3273
+ () => Effect23.runPromise(Effect23.map(getBuilder().first(), Option10.getOrNull))
3274
+ );
2800
3275
  },
2801
3276
  count: () => {
2802
3277
  touchVersion();
@@ -2806,21 +3281,24 @@ function wrapOrderByBuilder(getBuilder, touchVersion, ready) {
2806
3281
  }
2807
3282
 
2808
3283
  // src/svelte/collection.svelte.ts
2809
- class Collection {
3284
+ var Collection = class {
2810
3285
  error = $state(null);
2811
3286
  #handle = null;
2812
3287
  #ready = createDeferred();
2813
3288
  #version = $state(0);
2814
3289
  #watchAbort = null;
2815
3290
  #watchFiber = null;
2816
- _bind(handle) {
2817
- if (this.#handle)
2818
- return;
3291
+ #logLevel = "None";
3292
+ /** @internal */
3293
+ _bind(handle, logLevel = "None") {
3294
+ if (this.#handle) return;
2819
3295
  this.#handle = handle;
3296
+ this.#logLevel = logLevel;
2820
3297
  this.error = null;
2821
3298
  this.#settleReady();
2822
3299
  this.#startWatch();
2823
3300
  }
3301
+ /** @internal */
2824
3302
  _fail(err) {
2825
3303
  this.error = err;
2826
3304
  this.#settleReady(err);
@@ -2833,31 +3311,41 @@ class Collection {
2833
3311
  }
2834
3312
  }
2835
3313
  #startWatch() {
2836
- if (!this.#handle)
2837
- return;
2838
- const abort = new AbortController;
3314
+ if (!this.#handle) return;
3315
+ const abort = new AbortController();
2839
3316
  this.#watchAbort = abort;
2840
- this.#watchFiber = Effect24.runFork(Stream4.runForEach(this.#handle.watch(), (_records) => Effect24.sync(() => {
2841
- if (!abort.signal.aborted) {
2842
- this.#version++;
2843
- }
2844
- })).pipe(Effect24.catch((e) => Effect24.sync(() => {
2845
- if (!abort.signal.aborted) {
2846
- this.error = e instanceof Error ? e : new Error(String(e));
2847
- }
2848
- }))));
3317
+ this.#watchFiber = Effect24.runFork(
3318
+ Stream4.runForEach(
3319
+ this.#handle.watch(),
3320
+ (_records) => Effect24.sync(() => {
3321
+ if (!abort.signal.aborted) {
3322
+ this.#version++;
3323
+ }
3324
+ })
3325
+ ).pipe(
3326
+ Effect24.catch(
3327
+ (e) => Effect24.sync(() => {
3328
+ if (!abort.signal.aborted) {
3329
+ this.error = e instanceof Error ? e : new Error(String(e));
3330
+ }
3331
+ })
3332
+ ),
3333
+ Effect24.provideService(References5.MinimumLogLevel, this.#logLevel)
3334
+ )
3335
+ );
2849
3336
  }
2850
3337
  #touchVersion = () => {
2851
- this.#version;
3338
+ void this.#version;
2852
3339
  };
2853
3340
  #handleOrThrow = () => {
2854
- if (this.#handle)
2855
- return this.#handle;
3341
+ if (this.#handle) return this.#handle;
2856
3342
  throw this.error ?? new ClosedError({ message: "Collection is not ready" });
2857
3343
  };
2858
3344
  #run = async (getEffect) => {
2859
3345
  await this.#ready.promise;
2860
- return Effect24.runPromise(getEffect());
3346
+ return Effect24.runPromise(
3347
+ getEffect().pipe(Effect24.provideService(References5.MinimumLogLevel, this.#logLevel))
3348
+ );
2861
3349
  };
2862
3350
  add = (data) => {
2863
3351
  return this.#run(() => this.#handleOrThrow().add(data));
@@ -2876,7 +3364,11 @@ class Collection {
2876
3364
  return this.#run(() => this.#handleOrThrow().get(id));
2877
3365
  }
2878
3366
  this.#touchVersion();
2879
- return this.#run(() => Stream4.runHead(this.#handleOrThrow().watch()).pipe(Effect24.map((opt) => Option11.getOrElse(opt, () => []))));
3367
+ return this.#run(
3368
+ () => Stream4.runHead(this.#handleOrThrow().watch()).pipe(
3369
+ Effect24.map((opt) => Option11.getOrElse(opt, () => []))
3370
+ )
3371
+ );
2880
3372
  }
2881
3373
  first = () => {
2882
3374
  this.#touchVersion();
@@ -2887,11 +3379,20 @@ class Collection {
2887
3379
  return this.#run(() => this.#handleOrThrow().count());
2888
3380
  };
2889
3381
  where = (field2) => {
2890
- return wrapWhereClause(() => this.#handleOrThrow().where(field2), this.#touchVersion, this.#ready.promise);
3382
+ return wrapWhereClause(
3383
+ () => this.#handleOrThrow().where(field2),
3384
+ this.#touchVersion,
3385
+ this.#ready.promise
3386
+ );
2891
3387
  };
2892
3388
  orderBy = (field2) => {
2893
- return wrapOrderByBuilder(() => this.#handleOrThrow().orderBy(field2), this.#touchVersion, this.#ready.promise);
3389
+ return wrapOrderByBuilder(
3390
+ () => this.#handleOrThrow().orderBy(field2),
3391
+ this.#touchVersion,
3392
+ this.#ready.promise
3393
+ );
2894
3394
  };
3395
+ /** @internal */
2895
3396
  _destroy(reason = new ClosedError({ message: "Collection is closed" })) {
2896
3397
  if (this.#watchAbort) {
2897
3398
  this.#watchAbort.abort();
@@ -2905,10 +3406,10 @@ class Collection {
2905
3406
  this.error ??= reason;
2906
3407
  this.#settleReady(this.error);
2907
3408
  }
2908
- }
3409
+ };
2909
3410
 
2910
3411
  // src/svelte/tablinum.svelte.ts
2911
- class Tablinum2 {
3412
+ var Tablinum2 = class {
2912
3413
  status = $state("initializing");
2913
3414
  syncStatus = $state("idle");
2914
3415
  pendingCount = $state(0);
@@ -2917,15 +3418,17 @@ class Tablinum2 {
2917
3418
  ready;
2918
3419
  #handle = null;
2919
3420
  #scope = null;
2920
- #collections = new Map;
2921
- #members = new Collection;
3421
+ #collections = /* @__PURE__ */ new Map();
3422
+ #members = new Collection();
2922
3423
  #unsubscribeSyncStatus = null;
2923
3424
  #unsubscribePendingCount = null;
2924
3425
  #unsubscribeRelayStatus = null;
2925
3426
  #closed = false;
2926
3427
  #readyState = createDeferred();
3428
+ #logLevel;
2927
3429
  constructor(config) {
2928
3430
  this.ready = this.#readyState.promise;
3431
+ this.#logLevel = resolveLogLevel(config.logLevel);
2929
3432
  this.#init(config);
2930
3433
  }
2931
3434
  #settleReady(err) {
@@ -2936,25 +3439,29 @@ class Tablinum2 {
2936
3439
  }
2937
3440
  }
2938
3441
  #bindCollections(handle) {
2939
- this.#members._bind(handle.members);
3442
+ this.#members._bind(handle.members, this.#logLevel);
2940
3443
  for (const [name, collection2] of this.#collections) {
2941
- collection2._bind(handle.collection(name));
3444
+ collection2._bind(handle.collection(name), this.#logLevel);
2942
3445
  }
2943
3446
  }
2944
3447
  #runHandleEffect = async (run) => {
2945
3448
  const handle = this.#requireReady();
2946
3449
  try {
2947
3450
  this.error = null;
2948
- return await Effect25.runPromise(run(handle));
3451
+ return await Effect25.runPromise(
3452
+ run(handle).pipe(Effect25.provideService(References6.MinimumLogLevel, this.#logLevel))
3453
+ );
2949
3454
  } catch (e) {
2950
3455
  this.error = e instanceof Error ? e : new Error(String(e));
2951
3456
  throw this.error;
2952
3457
  }
2953
3458
  };
2954
- async#init(config) {
3459
+ async #init(config) {
2955
3460
  const scope = Effect25.runSync(Scope6.make());
2956
3461
  try {
2957
- const handle = await Effect25.runPromise(createTablinum(config).pipe(Effect25.provideService(Scope6.Scope, scope)));
3462
+ const handle = await Effect25.runPromise(
3463
+ createTablinum(config).pipe(Effect25.provideService(Scope6.Scope, scope))
3464
+ );
2958
3465
  if (this.#closed) {
2959
3466
  await Effect25.runPromise(Scope6.close(scope, Exit2.void));
2960
3467
  this.#scope = null;
@@ -2977,7 +3484,8 @@ class Tablinum2 {
2977
3484
  this.status = "ready";
2978
3485
  this.#settleReady();
2979
3486
  } catch (e) {
2980
- await Effect25.runPromise(Scope6.close(scope, Exit2.fail(e))).catch(() => {});
3487
+ await Effect25.runPromise(Scope6.close(scope, Exit2.fail(e))).catch(() => {
3488
+ });
2981
3489
  const err = e instanceof Error ? e : new Error(String(e));
2982
3490
  this.error = err;
2983
3491
  this.status = "error";
@@ -3000,10 +3508,10 @@ class Tablinum2 {
3000
3508
  }
3001
3509
  let col = this.#collections.get(name);
3002
3510
  if (!col) {
3003
- col = new Collection;
3511
+ col = new Collection();
3004
3512
  this.#collections.set(name, col);
3005
3513
  if (this.#handle) {
3006
- col._bind(this.#handle.collection(name));
3514
+ col._bind(this.#handle.collection(name), this.#logLevel);
3007
3515
  }
3008
3516
  }
3009
3517
  return col;
@@ -3021,8 +3529,7 @@ class Tablinum2 {
3021
3529
  return this.#requireReady().exportInvite();
3022
3530
  }
3023
3531
  close = async () => {
3024
- if (this.#closed)
3025
- return;
3532
+ if (this.#closed) return;
3026
3533
  this.#closed = true;
3027
3534
  const closeError = new ClosedError({
3028
3535
  message: this.#readyState.settled() ? "Tablinum is closed" : "Tablinum was closed before initialization completed"
@@ -3040,8 +3547,7 @@ class Tablinum2 {
3040
3547
  this.#unsubscribeRelayStatus = null;
3041
3548
  }
3042
3549
  this.#members._destroy(closeError);
3043
- for (const col of this.#collections.values())
3044
- col._destroy(closeError);
3550
+ for (const col of this.#collections.values()) col._destroy(closeError);
3045
3551
  this.#collections.clear();
3046
3552
  const handle = this.#handle;
3047
3553
  const scope = this.#scope;
@@ -3064,19 +3570,19 @@ class Tablinum2 {
3064
3570
  getMembers = async () => this.#runHandleEffect((handle) => handle.getMembers());
3065
3571
  getProfile = async () => this.#runHandleEffect((handle) => handle.getProfile());
3066
3572
  setProfile = async (profile) => this.#runHandleEffect((handle) => handle.setProfile(profile));
3067
- }
3573
+ };
3068
3574
  export {
3069
- field,
3070
- encodeInvite,
3071
- decodeInvite,
3072
- collection,
3073
- ValidationError,
3074
- Tablinum2 as Tablinum,
3075
- SyncError,
3076
- StorageError,
3077
- RelayError,
3078
- NotFoundError,
3079
- CryptoError,
3575
+ ClosedError,
3080
3576
  Collection,
3081
- ClosedError
3577
+ CryptoError,
3578
+ NotFoundError,
3579
+ RelayError,
3580
+ StorageError,
3581
+ SyncError,
3582
+ Tablinum2 as Tablinum,
3583
+ ValidationError,
3584
+ collection,
3585
+ decodeInvite,
3586
+ encodeInvite,
3587
+ field
3082
3588
  };