tablinum 0.5.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,29 +212,24 @@ 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
234
  import { Effect as Effect25, Exit as Exit2, References as References6, Scope as Scope6 } from "effect";
221
235
 
@@ -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
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,15 +486,20 @@ 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
 
@@ -475,16 +510,14 @@ import { Effect as Effect6, Option as Option3, References } from "effect";
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
  }
@@ -723,8 +797,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
723
797
  const commitEvent = (event) => Effect6.gen(function* () {
724
798
  yield* storage.putEvent(event);
725
799
  yield* applyEvent(storage, event);
726
- if (onWrite)
727
- yield* onWrite(event);
800
+ if (onWrite) yield* onWrite(event);
728
801
  yield* notifyChange(watchCtx, {
729
802
  collection: collectionName,
730
803
  recordId: event.recordId,
@@ -732,70 +805,84 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
732
805
  });
733
806
  });
734
807
  const handle = {
735
- add: (data) => withLog(Effect6.gen(function* () {
736
- const id = uuidv7();
737
- const fullRecord = { id, ...data };
738
- yield* validator(fullRecord);
739
- const event = {
740
- id: makeEventId(),
741
- collection: collectionName,
742
- recordId: id,
743
- kind: "c",
744
- data: fullRecord,
745
- createdAt: Date.now(),
746
- author: localAuthor
747
- };
748
- yield* commitEvent(event);
749
- yield* Effect6.logDebug("Record added", { collection: collectionName, recordId: id, data: fullRecord });
750
- return id;
751
- })),
752
- update: (id, data) => withLog(Effect6.gen(function* () {
753
- const existing = yield* storage.getRecord(collectionName, id);
754
- if (!existing || existing._d) {
755
- 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(),
756
815
  collection: collectionName,
757
- 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
758
827
  });
759
- }
760
- yield* partialValidator(data);
761
- const { _d, _u, _a, _e, ...existingFields } = existing;
762
- const merged = { ...existingFields, ...data, id };
763
- yield* validator(merged);
764
- const diff = deepDiff(existingFields, merged);
765
- const event = {
766
- id: makeEventId(),
767
- collection: collectionName,
768
- recordId: id,
769
- kind: "u",
770
- data: diff ?? { id },
771
- createdAt: Date.now(),
772
- author: localAuthor
773
- };
774
- yield* commitEvent(event);
775
- yield* Effect6.logDebug("Record updated", { collection: collectionName, recordId: id, data: diff });
776
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
777
- })),
778
- delete: (id) => withLog(Effect6.gen(function* () {
779
- const existing = yield* storage.getRecord(collectionName, id);
780
- if (!existing || existing._d) {
781
- 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(),
782
847
  collection: collectionName,
783
- 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
784
859
  });
785
- }
786
- const event = {
787
- id: makeEventId(),
788
- collection: collectionName,
789
- recordId: id,
790
- kind: "d",
791
- data: null,
792
- createdAt: Date.now(),
793
- author: localAuthor
794
- };
795
- yield* commitEvent(event);
796
- yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
797
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
798
- })),
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
+ ),
799
886
  undo: (id) => Effect6.gen(function* () {
800
887
  const existing = yield* storage.getRecord(collectionName, id);
801
888
  if (!existing) {
@@ -836,15 +923,29 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
836
923
  return found ? Option3.some(mapRecord(found)) : Option3.none();
837
924
  }),
838
925
  count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
839
- watch: () => watchCollection(watchCtx, storage, collectionName, undefined, mapRecord),
840
- where: (fieldName) => createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord),
841
- 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
+ )
842
943
  };
843
944
  return handle;
844
945
  }
845
946
 
846
947
  // src/sync/sync-service.ts
847
- import { Effect as Effect8, Layer, Option as Option5, References as References2, 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";
848
949
  import { unwrapEvent } from "nostr-tools/nip59";
849
950
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
850
951
 
@@ -860,8 +961,7 @@ var Mode = {
860
961
  Fingerprint: 1,
861
962
  IdList: 2
862
963
  };
863
-
864
- class WrappedBuffer {
964
+ var WrappedBuffer = class {
865
965
  constructor(buffer) {
866
966
  this._raw = new Uint8Array(buffer || 512);
867
967
  this.length = buffer ? buffer.length : 0;
@@ -873,10 +973,8 @@ class WrappedBuffer {
873
973
  return this._raw.byteLength;
874
974
  }
875
975
  extend(buf) {
876
- if (buf._raw)
877
- buf = buf.unwrap();
878
- if (typeof buf.length !== "number")
879
- throw Error("bad length");
976
+ if (buf._raw) buf = buf.unwrap();
977
+ if (typeof buf.length !== "number") throw Error("bad length");
880
978
  const targetSize = buf.length + this.length;
881
979
  if (this.capacity < targetSize) {
882
980
  const oldRaw = this._raw;
@@ -899,42 +997,36 @@ class WrappedBuffer {
899
997
  this.length -= n;
900
998
  return firstSubarray;
901
999
  }
902
- }
1000
+ };
903
1001
  function decodeVarInt(buf) {
904
1002
  let res = 0;
905
- while (true) {
906
- if (buf.length === 0)
907
- throw Error("parse ends prematurely");
1003
+ while (1) {
1004
+ if (buf.length === 0) throw Error("parse ends prematurely");
908
1005
  let byte = buf.shift();
909
1006
  res = res << 7 | byte & 127;
910
- if ((byte & 128) === 0)
911
- break;
1007
+ if ((byte & 128) === 0) break;
912
1008
  }
913
1009
  return res;
914
1010
  }
915
1011
  function encodeVarInt(n) {
916
- if (n === 0)
917
- return new WrappedBuffer([0]);
1012
+ if (n === 0) return new WrappedBuffer([0]);
918
1013
  let o = [];
919
1014
  while (n !== 0) {
920
1015
  o.push(n & 127);
921
1016
  n >>>= 7;
922
1017
  }
923
1018
  o.reverse();
924
- for (let i = 0;i < o.length - 1; i++)
925
- o[i] |= 128;
1019
+ for (let i = 0; i < o.length - 1; i++) o[i] |= 128;
926
1020
  return new WrappedBuffer(o);
927
1021
  }
928
1022
  function getByte(buf) {
929
1023
  return getBytes(buf, 1)[0];
930
1024
  }
931
1025
  function getBytes(buf, n) {
932
- if (buf.length < n)
933
- throw Error("parse ends prematurely");
1026
+ if (buf.length < n) throw Error("parse ends prematurely");
934
1027
  return buf.shiftN(n);
935
1028
  }
936
-
937
- class Accumulator {
1029
+ var Accumulator = class {
938
1030
  constructor() {
939
1031
  this.setToZero();
940
1032
  if (typeof window === "undefined") {
@@ -951,15 +1043,14 @@ class Accumulator {
951
1043
  let currCarry = 0, nextCarry = 0;
952
1044
  let p = new DataView(this.buf.buffer);
953
1045
  let po = new DataView(otherBuf.buffer);
954
- for (let i = 0;i < 8; i++) {
1046
+ for (let i = 0; i < 8; i++) {
955
1047
  let offset = i * 4;
956
1048
  let orig = p.getUint32(offset, true);
957
1049
  let otherV = po.getUint32(offset, true);
958
1050
  let next = orig;
959
1051
  next += currCarry;
960
1052
  next += otherV;
961
- if (next > 4294967295)
962
- nextCarry = 1;
1053
+ if (next > 4294967295) nextCarry = 1;
963
1054
  p.setUint32(offset, next & 4294967295, true);
964
1055
  currCarry = nextCarry;
965
1056
  nextCarry = 0;
@@ -967,7 +1058,7 @@ class Accumulator {
967
1058
  }
968
1059
  negate() {
969
1060
  let p = new DataView(this.buf.buffer);
970
- for (let i = 0;i < 8; i++) {
1061
+ for (let i = 0; i < 8; i++) {
971
1062
  let offset = i * 4;
972
1063
  p.setUint32(offset, ~p.getUint32(offset, true));
973
1064
  }
@@ -976,33 +1067,29 @@ class Accumulator {
976
1067
  this.add(one);
977
1068
  }
978
1069
  async getFingerprint(n) {
979
- let input = new WrappedBuffer;
1070
+ let input = new WrappedBuffer();
980
1071
  input.extend(this.buf);
981
1072
  input.extend(encodeVarInt(n));
982
1073
  let hash = await this.sha256(input.unwrap());
983
1074
  return hash.subarray(0, FINGERPRINT_SIZE);
984
1075
  }
985
- }
986
-
987
- class NegentropyStorageVector {
1076
+ };
1077
+ var NegentropyStorageVector = class {
988
1078
  constructor() {
989
1079
  this.items = [];
990
1080
  this.sealed = false;
991
1081
  }
992
1082
  insert(timestamp, id) {
993
- if (this.sealed)
994
- throw Error("already sealed");
1083
+ if (this.sealed) throw Error("already sealed");
995
1084
  id = loadInputBuffer(id);
996
- if (id.byteLength !== ID_SIZE)
997
- throw Error("bad id size for added item");
1085
+ if (id.byteLength !== ID_SIZE) throw Error("bad id size for added item");
998
1086
  this.items.push({ timestamp, id });
999
1087
  }
1000
1088
  seal() {
1001
- if (this.sealed)
1002
- throw Error("already sealed");
1089
+ if (this.sealed) throw Error("already sealed");
1003
1090
  this.sealed = true;
1004
1091
  this.items.sort(itemCompare);
1005
- for (let i = 1;i < this.items.length; i++) {
1092
+ for (let i = 1; i < this.items.length; i++) {
1006
1093
  if (itemCompare(this.items[i - 1], this.items[i]) === 0)
1007
1094
  throw Error("duplicate item inserted");
1008
1095
  }
@@ -1016,16 +1103,14 @@ class NegentropyStorageVector {
1016
1103
  }
1017
1104
  getItem(i) {
1018
1105
  this._checkSealed();
1019
- if (i >= this.items.length)
1020
- throw Error("out of range");
1106
+ if (i >= this.items.length) throw Error("out of range");
1021
1107
  return this.items[i];
1022
1108
  }
1023
1109
  iterate(begin, end, cb) {
1024
1110
  this._checkSealed();
1025
1111
  this._checkBounds(begin, end);
1026
- for (let i = begin;i < end; ++i) {
1027
- if (!cb(this.items[i], i))
1028
- break;
1112
+ for (let i = begin; i < end; ++i) {
1113
+ if (!cb(this.items[i], i)) break;
1029
1114
  }
1030
1115
  }
1031
1116
  findLowerBound(begin, end, bound) {
@@ -1034,7 +1119,7 @@ class NegentropyStorageVector {
1034
1119
  return this._binarySearch(this.items, begin, end, (a) => itemCompare(a, bound) < 0);
1035
1120
  }
1036
1121
  async fingerprint(begin, end) {
1037
- let out = new Accumulator;
1122
+ let out = new Accumulator();
1038
1123
  out.setToZero();
1039
1124
  this.iterate(begin, end, (item, i) => {
1040
1125
  out.add(item.id);
@@ -1043,12 +1128,10 @@ class NegentropyStorageVector {
1043
1128
  return await out.getFingerprint(end - begin);
1044
1129
  }
1045
1130
  _checkSealed() {
1046
- if (!this.sealed)
1047
- throw Error("not sealed");
1131
+ if (!this.sealed) throw Error("not sealed");
1048
1132
  }
1049
1133
  _checkBounds(begin, end) {
1050
- if (begin > end || end > this.items.length)
1051
- throw Error("bad range");
1134
+ if (begin > end || end > this.items.length) throw Error("bad range");
1052
1135
  }
1053
1136
  _binarySearch(arr, first, last, cmp) {
1054
1137
  let count = last - first;
@@ -1065,12 +1148,10 @@ class NegentropyStorageVector {
1065
1148
  }
1066
1149
  return first;
1067
1150
  }
1068
- }
1069
-
1070
- class Negentropy {
1151
+ };
1152
+ var Negentropy = class {
1071
1153
  constructor(storage, frameSizeLimit = 0) {
1072
- if (frameSizeLimit !== 0 && frameSizeLimit < 4096)
1073
- throw Error("frameSizeLimit too small");
1154
+ if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small");
1074
1155
  this.storage = storage;
1075
1156
  this.frameSizeLimit = frameSizeLimit;
1076
1157
  this.lastTimestampIn = 0;
@@ -1080,10 +1161,9 @@ class Negentropy {
1080
1161
  return { timestamp, id: id ? id : new Uint8Array(0) };
1081
1162
  }
1082
1163
  async initiate() {
1083
- if (this.isInitiator)
1084
- throw Error("already initiated");
1164
+ if (this.isInitiator) throw Error("already initiated");
1085
1165
  this.isInitiator = true;
1086
- let output = new WrappedBuffer;
1166
+ let output = new WrappedBuffer();
1087
1167
  output.extend([PROTOCOL_VERSION]);
1088
1168
  await this.splitRange(0, this.storage.size(), this._bound(Number.MAX_VALUE), output);
1089
1169
  return this._renderOutput(output);
@@ -1095,23 +1175,24 @@ class Negentropy {
1095
1175
  let haveIds = [], needIds = [];
1096
1176
  query = new WrappedBuffer(loadInputBuffer(query));
1097
1177
  this.lastTimestampIn = this.lastTimestampOut = 0;
1098
- let fullOutput = new WrappedBuffer;
1178
+ let fullOutput = new WrappedBuffer();
1099
1179
  fullOutput.extend([PROTOCOL_VERSION]);
1100
1180
  let protocolVersion = getByte(query);
1101
1181
  if (protocolVersion < 96 || protocolVersion > 111)
1102
1182
  throw Error("invalid negentropy protocol version byte");
1103
1183
  if (protocolVersion !== PROTOCOL_VERSION) {
1104
1184
  if (this.isInitiator)
1105
- throw Error("unsupported negentropy protocol version requested: " + (protocolVersion - 96));
1106
- else
1107
- 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];
1108
1189
  }
1109
1190
  let storageSize = this.storage.size();
1110
1191
  let prevBound = this._bound(0);
1111
1192
  let prevIndex = 0;
1112
1193
  let skip = false;
1113
1194
  while (query.length !== 0) {
1114
- let o = new WrappedBuffer;
1195
+ let o = new WrappedBuffer();
1115
1196
  let doSkip = () => {
1116
1197
  if (skip) {
1117
1198
  skip = false;
@@ -1137,10 +1218,9 @@ class Negentropy {
1137
1218
  } else if (mode === Mode.IdList) {
1138
1219
  let numIds = decodeVarInt(query);
1139
1220
  let theirElems = {};
1140
- for (let i = 0;i < numIds; i++) {
1221
+ for (let i = 0; i < numIds; i++) {
1141
1222
  let e = getBytes(query, ID_SIZE);
1142
- if (this.isInitiator)
1143
- theirElems[e] = e;
1223
+ if (this.isInitiator) theirElems[e] = e;
1144
1224
  }
1145
1225
  if (this.isInitiator) {
1146
1226
  skip = true;
@@ -1159,7 +1239,7 @@ class Negentropy {
1159
1239
  }
1160
1240
  } else {
1161
1241
  doSkip();
1162
- let responseIds = new WrappedBuffer;
1242
+ let responseIds = new WrappedBuffer();
1163
1243
  let numResponseIds = 0;
1164
1244
  let endBound = currBound;
1165
1245
  this.storage.iterate(lower, upper, (item, index) => {
@@ -1177,7 +1257,7 @@ class Negentropy {
1177
1257
  o.extend(encodeVarInt(numResponseIds));
1178
1258
  o.extend(responseIds);
1179
1259
  fullOutput.extend(o);
1180
- o = new WrappedBuffer;
1260
+ o = new WrappedBuffer();
1181
1261
  }
1182
1262
  } else {
1183
1263
  throw Error("unexpected mode");
@@ -1215,7 +1295,7 @@ class Negentropy {
1215
1295
  let itemsPerBucket = Math.floor(numElems / buckets);
1216
1296
  let bucketsWithExtra = numElems % buckets;
1217
1297
  let curr = lower;
1218
- for (let i = 0;i < buckets; i++) {
1298
+ for (let i = 0; i < buckets; i++) {
1219
1299
  let bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);
1220
1300
  let ourFingerprint = await this.storage.fingerprint(curr, curr + bucketSize);
1221
1301
  curr += bucketSize;
@@ -1225,10 +1305,8 @@ class Negentropy {
1225
1305
  } else {
1226
1306
  let prevItem, currItem;
1227
1307
  this.storage.iterate(curr - 1, curr + 1, (item, index) => {
1228
- if (index === curr - 1)
1229
- prevItem = item;
1230
- else
1231
- currItem = item;
1308
+ if (index === curr - 1) prevItem = item;
1309
+ else currItem = item;
1232
1310
  return true;
1233
1311
  });
1234
1312
  nextBound = this.getMinimalBound(prevItem, currItem);
@@ -1241,13 +1319,13 @@ class Negentropy {
1241
1319
  }
1242
1320
  _renderOutput(o) {
1243
1321
  o = o.unwrap();
1244
- if (!this.wantUint8ArrayOutput)
1245
- o = uint8ArrayToHex(o);
1322
+ if (!this.wantUint8ArrayOutput) o = uint8ArrayToHex(o);
1246
1323
  return o;
1247
1324
  }
1248
1325
  exceededFrameSizeLimit(n) {
1249
1326
  return this.frameSizeLimit && n > this.frameSizeLimit - 200;
1250
1327
  }
1328
+ // Decoding
1251
1329
  decodeTimestampIn(encoded) {
1252
1330
  let timestamp = decodeVarInt(encoded);
1253
1331
  timestamp = timestamp === 0 ? Number.MAX_VALUE : timestamp - 1;
@@ -1262,11 +1340,11 @@ class Negentropy {
1262
1340
  decodeBound(encoded) {
1263
1341
  let timestamp = this.decodeTimestampIn(encoded);
1264
1342
  let len = decodeVarInt(encoded);
1265
- if (len > ID_SIZE)
1266
- throw Error("bound key too long");
1343
+ if (len > ID_SIZE) throw Error("bound key too long");
1267
1344
  let id = getBytes(encoded, len);
1268
1345
  return { timestamp, id };
1269
1346
  }
1347
+ // Encoding
1270
1348
  encodeTimestampOut(timestamp) {
1271
1349
  if (timestamp === Number.MAX_VALUE) {
1272
1350
  this.lastTimestampOut = Number.MAX_VALUE;
@@ -1278,7 +1356,7 @@ class Negentropy {
1278
1356
  return encodeVarInt(timestamp + 1);
1279
1357
  }
1280
1358
  encodeBound(key) {
1281
- let output = new WrappedBuffer;
1359
+ let output = new WrappedBuffer();
1282
1360
  output.extend(this.encodeTimestampOut(key.timestamp));
1283
1361
  output.extend(encodeVarInt(key.id.length));
1284
1362
  output.extend(key.id);
@@ -1291,30 +1369,24 @@ class Negentropy {
1291
1369
  let sharedPrefixBytes = 0;
1292
1370
  let currKey = curr.id;
1293
1371
  let prevKey = prev.id;
1294
- for (let i = 0;i < ID_SIZE; i++) {
1295
- if (currKey[i] !== prevKey[i])
1296
- break;
1372
+ for (let i = 0; i < ID_SIZE; i++) {
1373
+ if (currKey[i] !== prevKey[i]) break;
1297
1374
  sharedPrefixBytes++;
1298
1375
  }
1299
1376
  return this._bound(curr.timestamp, curr.id.subarray(0, sharedPrefixBytes + 1));
1300
1377
  }
1301
1378
  }
1302
- }
1379
+ };
1303
1380
  function loadInputBuffer(inp) {
1304
- if (typeof inp === "string")
1305
- inp = hexToUint8Array(inp);
1306
- else if (__proto__ !== Uint8Array.prototype)
1307
- inp = new Uint8Array(inp);
1381
+ if (typeof inp === "string") inp = hexToUint8Array(inp);
1382
+ else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp);
1308
1383
  return inp;
1309
1384
  }
1310
1385
  function hexToUint8Array(h) {
1311
- if (h.startsWith("0x"))
1312
- h = h.substr(2);
1313
- if (h.length % 2 === 1)
1314
- 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");
1315
1388
  let arr = new Uint8Array(h.length / 2);
1316
- for (let i = 0;i < arr.length; i++)
1317
- 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);
1318
1390
  return arr;
1319
1391
  }
1320
1392
  var uint8ArrayToHexLookupTable = new Array(256);
@@ -1337,28 +1409,24 @@ var uint8ArrayToHexLookupTable = new Array(256);
1337
1409
  "e",
1338
1410
  "f"
1339
1411
  ];
1340
- for (let i = 0;i < 256; i++) {
1412
+ for (let i = 0; i < 256; i++) {
1341
1413
  uint8ArrayToHexLookupTable[i] = hexAlphabet[i >>> 4 & 15] + hexAlphabet[i & 15];
1342
1414
  }
1343
1415
  }
1344
1416
  function uint8ArrayToHex(arr) {
1345
1417
  let out = "";
1346
- for (let i = 0, edx = arr.length;i < edx; i++) {
1418
+ for (let i = 0, edx = arr.length; i < edx; i++) {
1347
1419
  out += uint8ArrayToHexLookupTable[arr[i]];
1348
1420
  }
1349
1421
  return out;
1350
1422
  }
1351
1423
  function compareUint8Array(a, b) {
1352
- for (let i = 0;i < a.byteLength; i++) {
1353
- if (a[i] < b[i])
1354
- return -1;
1355
- if (a[i] > b[i])
1356
- return 1;
1357
- }
1358
- if (a.byteLength > b.byteLength)
1359
- return 1;
1360
- if (a.byteLength < b.byteLength)
1361
- 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;
1362
1430
  return 0;
1363
1431
  }
1364
1432
  function itemCompare(a, b) {
@@ -1374,7 +1442,7 @@ import { GiftWrap } from "nostr-tools/kinds";
1374
1442
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1375
1443
  return Effect7.gen(function* () {
1376
1444
  const allGiftWraps = yield* storage.getAllGiftWraps();
1377
- const storageVector = new NegentropyStorageVector;
1445
+ const storageVector = new NegentropyStorageVector();
1378
1446
  for (const gw of allGiftWraps) {
1379
1447
  storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1380
1448
  }
@@ -1398,8 +1466,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1398
1466
  let currentMsg = initialMsg;
1399
1467
  while (currentMsg !== null) {
1400
1468
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1401
- if (response.msgHex === null)
1402
- break;
1469
+ if (response.msgHex === null) break;
1403
1470
  const reconcileResult = yield* Effect7.try({
1404
1471
  try: () => neg.reconcile(response.msgHex),
1405
1472
  catch: (e) => new SyncError({
@@ -1409,10 +1476,8 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1409
1476
  })
1410
1477
  });
1411
1478
  const [nextMsg, haveIds, needIds] = reconcileResult;
1412
- for (const id of haveIds)
1413
- allHaveIds.push(id);
1414
- for (const id of needIds)
1415
- allNeedIds.push(id);
1479
+ for (const id of haveIds) allHaveIds.push(id);
1480
+ for (const id of needIds) allNeedIds.push(id);
1416
1481
  currentMsg = nextMsg;
1417
1482
  }
1418
1483
  yield* Effect7.logDebug("Negentropy reconciliation complete", {
@@ -1460,12 +1525,11 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1460
1525
  kind: 1,
1461
1526
  content: JSON.stringify(rotationData),
1462
1527
  tags: [["d", `_system:rotation:${epochId}`]],
1463
- created_at: Math.floor(Date.now() / 1000)
1528
+ created_at: Math.floor(Date.now() / 1e3)
1464
1529
  };
1465
1530
  const wrappedEvents = [];
1466
1531
  for (const memberPubkey of remainingMemberPubkeys) {
1467
- if (memberPubkey === senderPublicKey)
1468
- continue;
1532
+ if (memberPubkey === senderPublicKey) continue;
1469
1533
  const wrapped = wrapEvent(rumor, senderPrivateKey, memberPubkey);
1470
1534
  wrappedEvents.push(wrapped);
1471
1535
  }
@@ -1478,7 +1542,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1478
1542
  kind: 1,
1479
1543
  content: JSON.stringify(removalData),
1480
1544
  tags: [["d", `_system:removed:${epochId}`]],
1481
- created_at: Math.floor(Date.now() / 1000)
1545
+ created_at: Math.floor(Date.now() / 1e3)
1482
1546
  };
1483
1547
  const removalNotices = [];
1484
1548
  for (const removedPubkey of removedMemberPubkeys) {
@@ -1488,8 +1552,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1488
1552
  return { epoch, wrappedEvents, removalNotices };
1489
1553
  }
1490
1554
  function parseRotationEvent(content, dTag) {
1491
- if (!dTag.startsWith("_system:rotation:"))
1492
- return Option4.none();
1555
+ if (!dTag.startsWith("_system:rotation:")) return Option4.none();
1493
1556
  try {
1494
1557
  return Option4.some(decodeRotationData(content));
1495
1558
  } catch {
@@ -1497,8 +1560,7 @@ function parseRotationEvent(content, dTag) {
1497
1560
  }
1498
1561
  }
1499
1562
  function parseRemovalNotice(content, dTag) {
1500
- if (!dTag.startsWith("_system:removed:"))
1501
- return Option4.none();
1563
+ if (!dTag.startsWith("_system:removed:")) return Option4.none();
1502
1564
  try {
1503
1565
  return Option4.some(decodeRemovalNotice(content));
1504
1566
  } catch {
@@ -1518,65 +1580,79 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1518
1580
  kind: "create"
1519
1581
  });
1520
1582
  const forkHandled = (effect) => {
1521
- Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.provide(logLayer), 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
+ );
1522
1591
  };
1523
1592
  let autoFlushActive = false;
1524
1593
  const autoFlushEffect = Effect8.gen(function* () {
1525
1594
  const size = yield* publishQueue.size();
1526
- if (size === 0)
1527
- return;
1595
+ if (size === 0) return;
1528
1596
  yield* syncStatus.set("syncing");
1529
1597
  yield* publishQueue.flush(relayUrls);
1530
1598
  const remaining = yield* publishQueue.size();
1531
- if (remaining > 0)
1532
- yield* Effect8.fail("pending");
1533
- }).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
+ );
1534
1605
  const scheduleAutoFlush = () => {
1535
- if (autoFlushActive)
1536
- return;
1606
+ if (autoFlushActive) return;
1537
1607
  autoFlushActive = true;
1538
- forkHandled(autoFlushEffect.pipe(Effect8.ensuring(Effect8.sync(() => {
1539
- autoFlushActive = false;
1540
- }))));
1608
+ forkHandled(
1609
+ autoFlushEffect.pipe(
1610
+ Effect8.ensuring(
1611
+ Effect8.sync(() => {
1612
+ autoFlushActive = false;
1613
+ })
1614
+ )
1615
+ )
1616
+ );
1541
1617
  };
1542
1618
  const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1543
1619
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1544
- if (!memberRecord)
1545
- return false;
1620
+ if (!memberRecord) return false;
1546
1621
  return !!memberRecord.removedAt;
1547
1622
  });
1548
1623
  const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1549
1624
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1550
- if (existing)
1551
- return null;
1625
+ if (existing) return null;
1552
1626
  const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
1553
1627
  if (unwrapResult._tag === "Failure") {
1554
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1628
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1555
1629
  return null;
1556
1630
  }
1557
1631
  const rumor = unwrapResult.success;
1558
1632
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1559
1633
  if (!dTag) {
1560
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1634
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1561
1635
  return null;
1562
1636
  }
1563
1637
  const colonIdx = dTag.indexOf(":");
1564
1638
  if (colonIdx === -1) {
1565
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1639
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1566
1640
  return null;
1567
1641
  }
1568
1642
  const collectionName = dTag.substring(0, colonIdx);
1569
1643
  const recordId = dTag.substring(colonIdx + 1);
1570
1644
  const retention = knownCollections.get(collectionName);
1571
- if (retention === undefined) {
1572
- 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 });
1573
1647
  return null;
1574
1648
  }
1575
1649
  if (rumor.pubkey) {
1576
1650
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1577
1651
  if (reject) {
1578
- yield* Effect8.logWarning("Rejected write from removed member", { author: rumor.pubkey.slice(0, 12) });
1579
- 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 });
1580
1656
  return null;
1581
1657
  }
1582
1658
  }
@@ -1584,14 +1660,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1584
1660
  let kind = "u";
1585
1661
  const parsed = yield* Effect8.try({
1586
1662
  try: () => JSON.parse(rumor.content),
1587
- catch: () => {
1588
- return;
1589
- }
1590
- }).pipe(Effect8.orElseSucceed(() => {
1591
- return;
1592
- }));
1593
- if (parsed === undefined) {
1594
- 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 });
1595
1667
  return null;
1596
1668
  }
1597
1669
  if (parsed === null || parsed._deleted) {
@@ -1599,18 +1671,19 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1599
1671
  } else {
1600
1672
  data = parsed;
1601
1673
  }
1602
- const author = rumor.pubkey || undefined;
1674
+ const author = rumor.pubkey || void 0;
1603
1675
  const event = {
1604
1676
  id: rumor.id,
1605
1677
  collection: collectionName,
1606
1678
  recordId,
1607
1679
  kind,
1608
1680
  data,
1609
- createdAt: rumor.created_at * 1000,
1681
+ createdAt: rumor.created_at * 1e3,
1610
1682
  author
1611
1683
  };
1612
1684
  yield* storage.putGiftWrap({
1613
1685
  id: remoteGw.id,
1686
+ event: remoteGw,
1614
1687
  createdAt: remoteGw.created_at
1615
1688
  });
1616
1689
  yield* storage.putEvent(event);
@@ -1618,7 +1691,12 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1618
1691
  if (didApply && (kind === "u" || kind === "d")) {
1619
1692
  yield* pruneEvents(storage, collectionName, recordId, retention);
1620
1693
  }
1621
- yield* Effect8.logDebug("Processed gift wrap", { collection: collectionName, recordId, kind, author: author?.slice(0, 12) });
1694
+ yield* Effect8.logDebug("Processed gift wrap", {
1695
+ collection: collectionName,
1696
+ recordId,
1697
+ kind,
1698
+ author: author?.slice(0, 12)
1699
+ });
1622
1700
  if (author && onNewAuthor) {
1623
1701
  onNewAuthor(author);
1624
1702
  }
@@ -1631,32 +1709,34 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1631
1709
  }
1632
1710
  });
1633
1711
  const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1634
- const unwrapResult = yield* Effect8.result(Effect8.try({
1635
- try: () => unwrapEvent(remoteGw, personalPrivateKey),
1636
- catch: (e) => new CryptoError({
1637
- message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
1638
- 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
+ })
1639
1719
  })
1640
- }));
1641
- if (unwrapResult._tag === "Failure")
1642
- return false;
1720
+ );
1721
+ if (unwrapResult._tag === "Failure") return false;
1643
1722
  const rumor = unwrapResult.success;
1644
1723
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1645
- if (!dTag)
1646
- return false;
1724
+ if (!dTag) return false;
1647
1725
  const removalNoticeOpt = parseRemovalNotice(rumor.content, dTag);
1648
1726
  if (Option5.isSome(removalNoticeOpt)) {
1649
- if (onRemoved)
1650
- onRemoved(removalNoticeOpt.value);
1727
+ if (onRemoved) onRemoved(removalNoticeOpt.value);
1651
1728
  return true;
1652
1729
  }
1653
1730
  const rotationDataOpt = parseRotationEvent(rumor.content, dTag);
1654
- if (Option5.isNone(rotationDataOpt))
1655
- return false;
1731
+ if (Option5.isNone(rotationDataOpt)) return false;
1656
1732
  const rotationData = rotationDataOpt.value;
1657
- if (epochStore.epochs.has(rotationData.epochId))
1658
- return false;
1659
- 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
+ );
1660
1740
  addEpoch(epochStore, epoch);
1661
1741
  epochStore.currentEpochId = epoch.id;
1662
1742
  let membersChanged = false;
@@ -1676,79 +1756,158 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1676
1756
  membersChanged = true;
1677
1757
  }
1678
1758
  }
1679
- if (membersChanged && onMembersChanged)
1680
- onMembersChanged();
1759
+ if (membersChanged && onMembersChanged) onMembersChanged();
1681
1760
  yield* handle.addEpochSubscription(epoch.publicKey);
1682
1761
  return true;
1683
1762
  });
1684
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(relayUrls, (url) => Effect8.gen(function* () {
1685
- yield* relay.subscribe(filter, url, (event) => {
1686
- forkHandled(onEvent(event));
1687
- }).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1688
- }), { 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
+ );
1689
1775
  const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1690
1776
  yield* Effect8.logDebug("Syncing relay", { relay: url });
1691
- const reconcileResult = yield* Effect8.result(reconcileWithRelay(storage, relay, url, Array.from(pubKeys)));
1777
+ const reconcileResult = yield* Effect8.result(
1778
+ reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
1779
+ );
1692
1780
  if (reconcileResult._tag === "Failure") {
1693
1781
  onSyncError?.(reconcileResult.failure);
1694
1782
  return;
1695
1783
  }
1696
1784
  const { haveIds, needIds } = reconcileResult.success;
1697
- yield* Effect8.logDebug("Relay reconciliation result", { relay: url, need: needIds.length, have: haveIds.length });
1785
+ yield* Effect8.logDebug("Relay reconciliation result", {
1786
+ relay: url,
1787
+ need: needIds.length,
1788
+ have: haveIds.length
1789
+ });
1698
1790
  if (needIds.length > 0) {
1699
- 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
+ );
1700
1795
  const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
1701
- yield* Effect8.forEach(sorted, (remoteGw) => Effect8.gen(function* () {
1702
- const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
1703
- if (collection2)
1704
- changedCollections.add(collection2);
1705
- }), { 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
+ );
1706
1806
  }
1707
1807
  if (haveIds.length > 0) {
1708
- yield* Effect8.forEach(haveIds, (id) => Effect8.gen(function* () {
1709
- const gw = yield* storage.getGiftWrap(id);
1710
- if (!gw?.event)
1711
- return;
1712
- yield* relay.publish(gw.event, [url]).pipe(Effect8.andThen(storage.stripGiftWrapBlob(id)), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1713
- }), { discard: true });
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
+ );
1714
1820
  }
1715
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);
1716
1839
  const handle = {
1717
1840
  sync: () => Effect8.gen(function* () {
1718
1841
  yield* Effect8.logInfo("Sync started");
1719
1842
  yield* syncStatus.set("syncing");
1720
1843
  yield* Ref3.set(watchCtx.replayingRef, true);
1721
- const changedCollections = new Set;
1844
+ const changedCollections = /* @__PURE__ */ new Set();
1722
1845
  yield* Effect8.gen(function* () {
1723
1846
  const pubKeys = getSubscriptionPubKeys();
1724
1847
  yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1725
1848
  discard: true
1726
1849
  });
1727
1850
  yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
1728
- }).pipe(Effect8.ensuring(Effect8.gen(function* () {
1729
- yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1730
- yield* syncStatus.set("idle");
1731
- })));
1851
+ }).pipe(
1852
+ Effect8.ensuring(
1853
+ Effect8.gen(function* () {
1854
+ yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1855
+ yield* syncStatus.set("idle");
1856
+ })
1857
+ )
1858
+ );
1732
1859
  yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1733
1860
  }).pipe(Effect8.withLogSpan("tablinum.sync")),
1734
1861
  publishLocal: (giftWrap) => Effect8.gen(function* () {
1735
- if (!giftWrap.event)
1736
- return;
1737
- 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
+ );
1738
1873
  }),
1739
1874
  startSubscription: () => Effect8.gen(function* () {
1740
1875
  const pubKeys = getSubscriptionPubKeys();
1741
1876
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1742
1877
  if (!pubKeys.includes(personalPublicKey)) {
1743
- 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
+ );
1744
1882
  }
1745
1883
  }),
1746
- 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
+ }
1747
1901
  };
1748
- forkHandled(publishQueue.size().pipe(Effect8.flatMap((size) => Effect8.sync(() => {
1749
- if (size > 0)
1750
- scheduleAutoFlush();
1751
- }))));
1902
+ forkHandled(
1903
+ publishQueue.size().pipe(
1904
+ Effect8.flatMap(
1905
+ (size) => Effect8.sync(() => {
1906
+ if (size > 0) scheduleAutoFlush();
1907
+ })
1908
+ )
1909
+ )
1910
+ );
1752
1911
  return handle;
1753
1912
  }
1754
1913
 
@@ -1804,9 +1963,14 @@ var decodeAuthorProfile = Schema6.decodeUnknownEffect(Schema6.fromJsonString(Aut
1804
1963
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
1805
1964
  return Effect9.gen(function* () {
1806
1965
  for (const url of relayUrls) {
1807
- 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
+ );
1808
1969
  if (result._tag === "Success" && result.success.length > 0) {
1809
- 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
+ );
1810
1974
  }
1811
1975
  }
1812
1976
  return Option6.none();
@@ -1815,45 +1979,44 @@ function fetchAuthorProfile(relay, relayUrls, pubkey) {
1815
1979
 
1816
1980
  // src/services/Identity.ts
1817
1981
  import { ServiceMap as ServiceMap3 } from "effect";
1818
-
1819
- class Identity extends ServiceMap3.Service()("tablinum/Identity") {
1820
- }
1982
+ var Identity = class extends ServiceMap3.Service()("tablinum/Identity") {
1983
+ };
1821
1984
 
1822
1985
  // src/services/EpochStore.ts
1823
1986
  import { ServiceMap as ServiceMap4 } from "effect";
1824
-
1825
- class EpochStore extends ServiceMap4.Service()("tablinum/EpochStore") {
1826
- }
1987
+ var EpochStore = class extends ServiceMap4.Service()(
1988
+ "tablinum/EpochStore"
1989
+ ) {
1990
+ };
1827
1991
 
1828
1992
  // src/services/Storage.ts
1829
1993
  import { ServiceMap as ServiceMap5 } from "effect";
1830
-
1831
- class Storage extends ServiceMap5.Service()("tablinum/Storage") {
1832
- }
1994
+ var Storage = class extends ServiceMap5.Service()("tablinum/Storage") {
1995
+ };
1833
1996
 
1834
1997
  // src/services/Relay.ts
1835
1998
  import { ServiceMap as ServiceMap6 } from "effect";
1836
-
1837
- class Relay extends ServiceMap6.Service()("tablinum/Relay") {
1838
- }
1999
+ var Relay = class extends ServiceMap6.Service()("tablinum/Relay") {
2000
+ };
1839
2001
 
1840
2002
  // src/services/GiftWrap.ts
1841
2003
  import { ServiceMap as ServiceMap7 } from "effect";
1842
-
1843
- class GiftWrap3 extends ServiceMap7.Service()("tablinum/GiftWrap") {
1844
- }
2004
+ var GiftWrap3 = class extends ServiceMap7.Service()("tablinum/GiftWrap") {
2005
+ };
1845
2006
 
1846
2007
  // src/services/PublishQueue.ts
1847
2008
  import { ServiceMap as ServiceMap8 } from "effect";
1848
-
1849
- class PublishQueue extends ServiceMap8.Service()("tablinum/PublishQueue") {
1850
- }
2009
+ var PublishQueue = class extends ServiceMap8.Service()(
2010
+ "tablinum/PublishQueue"
2011
+ ) {
2012
+ };
1851
2013
 
1852
2014
  // src/services/SyncStatus.ts
1853
2015
  import { ServiceMap as ServiceMap9 } from "effect";
1854
-
1855
- class SyncStatus extends ServiceMap9.Service()("tablinum/SyncStatus") {
1856
- }
2016
+ var SyncStatus = class extends ServiceMap9.Service()(
2017
+ "tablinum/SyncStatus"
2018
+ ) {
2019
+ };
1857
2020
 
1858
2021
  // src/layers/IdentityLive.ts
1859
2022
  import { Effect as Effect11, Layer as Layer2 } from "effect";
@@ -1894,47 +2057,59 @@ function createIdentity(suppliedKey) {
1894
2057
  }
1895
2058
 
1896
2059
  // src/layers/IdentityLive.ts
1897
- var IdentityLive = Layer2.effect(Identity, Effect11.gen(function* () {
1898
- const config = yield* Config;
1899
- const storage = yield* Storage;
1900
- const idbKey = yield* storage.getMeta("identity_key");
1901
- const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : undefined);
1902
- const identity = yield* createIdentity(resolvedKey);
1903
- yield* storage.putMeta("identity_key", identity.exportKey());
1904
- yield* Effect11.logInfo("Identity loaded", {
1905
- publicKey: identity.publicKey.slice(0, 12) + "...",
1906
- source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
1907
- });
1908
- return identity;
1909
- }));
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
+ );
1910
2076
 
1911
2077
  // src/layers/EpochStoreLive.ts
1912
2078
  import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
1913
2079
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
1914
2080
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
1915
- var EpochStoreLive = Layer3.effect(EpochStore, Effect12.gen(function* () {
1916
- const config = yield* Config;
1917
- const identity = yield* Identity;
1918
- const storage = yield* Storage;
1919
- const idbRaw = yield* storage.getMeta("epochs");
1920
- if (typeof idbRaw === "string") {
1921
- const idbStore = deserializeEpochStore(idbRaw);
1922
- if (Option7.isSome(idbStore)) {
1923
- yield* Effect12.logInfo("Epoch store loaded", { source: "storage", epochs: idbStore.value.epochs.size });
1924
- return idbStore.value;
1925
- }
1926
- }
1927
- if (config.epochKeys && config.epochKeys.length > 0) {
1928
- const store2 = createEpochStoreFromInputs(config.epochKeys);
1929
- yield* storage.putMeta("epochs", stringifyEpochStore(store2));
1930
- yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
1931
- return store2;
1932
- }
1933
- const store = createEpochStoreFromInputs([{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }], { createdBy: identity.publicKey });
1934
- yield* storage.putMeta("epochs", stringifyEpochStore(store));
1935
- yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
1936
- return store;
1937
- }));
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
+ );
1938
2113
 
1939
2114
  // src/layers/StorageLive.ts
1940
2115
  import { Effect as Effect14, Layer as Layer4 } from "effect";
@@ -1942,6 +2117,68 @@ import { Effect as Effect14, Layer as Layer4 } from "effect";
1942
2117
  // src/storage/idb.ts
1943
2118
  import { Effect as Effect13 } from "effect";
1944
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
1945
2182
  var DB_NAME = "tablinum";
1946
2183
  function storeName(collection2) {
1947
2184
  return `col_${collection2}`;
@@ -1972,7 +2209,7 @@ function upgradeSchema(database, schema, tx) {
1972
2209
  if (!database.objectStoreNames.contains("giftwraps")) {
1973
2210
  database.createObjectStore("giftwraps", { keyPath: "id" });
1974
2211
  }
1975
- const expectedStores = new Set;
2212
+ const expectedStores = /* @__PURE__ */ new Set();
1976
2213
  for (const [, def] of Object.entries(schema)) {
1977
2214
  const sn = storeName(def.name);
1978
2215
  expectedStores.add(sn);
@@ -1986,12 +2223,10 @@ function upgradeSchema(database, schema, tx) {
1986
2223
  const existingIndices = new Set(Array.from(store.indexNames));
1987
2224
  const wantedIndices = new Set(def.indices ?? []);
1988
2225
  for (const idx of existingIndices) {
1989
- if (!wantedIndices.has(idx))
1990
- store.deleteIndex(idx);
2226
+ if (!wantedIndices.has(idx)) store.deleteIndex(idx);
1991
2227
  }
1992
2228
  for (const idx of wantedIndices) {
1993
- if (!existingIndices.has(idx))
1994
- store.createIndex(idx, idx);
2229
+ if (!existingIndices.has(idx)) store.createIndex(idx, idx);
1995
2230
  }
1996
2231
  }
1997
2232
  }
@@ -2006,6 +2241,13 @@ function openIDBStorage(dbName, schema) {
2006
2241
  return Effect13.gen(function* () {
2007
2242
  const name = dbName ?? DB_NAME;
2008
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
+ }
2009
2251
  const probeDb = yield* Effect13.tryPromise({
2010
2252
  try: () => openDB(name),
2011
2253
  catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
@@ -2016,7 +2258,7 @@ function openIDBStorage(dbName, schema) {
2016
2258
  const storedSig = yield* Effect13.tryPromise({
2017
2259
  try: () => probeDb.get("_meta", "schema_sig"),
2018
2260
  catch: () => new StorageError({ message: "Failed to read schema meta" })
2019
- }).pipe(Effect13.catch(() => Effect13.succeed(undefined)));
2261
+ }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
2020
2262
  needsUpgrade = storedSig !== schemaSig;
2021
2263
  }
2022
2264
  probeDb.close();
@@ -2033,9 +2275,7 @@ function openIDBStorage(dbName, schema) {
2033
2275
  });
2034
2276
  yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2035
2277
  const handle = {
2036
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => {
2037
- return;
2038
- })),
2278
+ putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2039
2279
  getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2040
2280
  getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2041
2281
  countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
@@ -2055,29 +2295,52 @@ function openIDBStorage(dbName, schema) {
2055
2295
  }
2056
2296
  return results;
2057
2297
  }),
2058
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => {
2059
- return;
2060
- })),
2298
+ putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2061
2299
  getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2062
2300
  getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2063
- getEventsByRecord: (collection2, recordId) => wrap("getEventsByRecord", () => db.getAllFromIndex("events", "by-record", [collection2, recordId])),
2064
- putGiftWrap: (gw) => wrap("putGiftWrap", () => db.put("giftwraps", gw).then(() => {
2065
- return;
2066
- })),
2067
- getGiftWrap: (id) => wrap("getGiftWrap", () => db.get("giftwraps", id)),
2068
- getAllGiftWraps: () => wrap("getAllGiftWraps", () => db.getAll("giftwraps")),
2069
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => {
2070
- return;
2071
- })),
2072
- stripGiftWrapBlob: (id) => wrap("stripGiftWrapBlob", async () => {
2073
- const existing = await db.get("giftwraps", id);
2074
- if (existing) {
2075
- 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 });
2076
2311
  }
2077
2312
  }),
2078
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => {
2079
- return;
2080
- })),
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)),
2081
2344
  stripEventData: (id) => wrap("stripEventData", async () => {
2082
2345
  const existing = await db.get("events", id);
2083
2346
  if (existing) {
@@ -2085,9 +2348,7 @@ function openIDBStorage(dbName, schema) {
2085
2348
  }
2086
2349
  }),
2087
2350
  getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2088
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => {
2089
- return;
2090
- })),
2351
+ putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2091
2352
  close: () => Effect13.sync(() => db.close())
2092
2353
  };
2093
2354
  return handle;
@@ -2095,15 +2356,18 @@ function openIDBStorage(dbName, schema) {
2095
2356
  }
2096
2357
 
2097
2358
  // src/layers/StorageLive.ts
2098
- var StorageLive = Layer4.effect(Storage, Effect14.gen(function* () {
2099
- const config = yield* Config;
2100
- const handle = yield* openIDBStorage(config.dbName, {
2101
- ...config.schema,
2102
- _members: membersCollectionDef
2103
- });
2104
- yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2105
- return handle;
2106
- }));
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
+ );
2107
2371
 
2108
2372
  // src/layers/RelayLive.ts
2109
2373
  import { Layer as Layer5 } from "effect";
@@ -2117,32 +2381,41 @@ var NegMessageFrameSchema = Schema7.Tuple([
2117
2381
  Schema7.String
2118
2382
  ]);
2119
2383
  var NegErrorFrameSchema = Schema7.Tuple([Schema7.Literal("NEG-ERR"), Schema7.String, Schema7.String]);
2120
- var decodeNegFrame = Schema7.decodeUnknownEffect(Schema7.fromJsonString(Schema7.Union([NegMessageFrameSchema, NegErrorFrameSchema])));
2384
+ var decodeNegFrame = Schema7.decodeUnknownEffect(
2385
+ Schema7.fromJsonString(Schema7.Union([NegMessageFrameSchema, NegErrorFrameSchema]))
2386
+ );
2121
2387
  function parseNegMessageFrame(data) {
2122
- 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
+ );
2123
2394
  }
2124
2395
  function createRelayHandle() {
2125
2396
  return Effect15.gen(function* () {
2126
2397
  const relayScope = yield* Effect15.scope;
2127
2398
  const connections = yield* ScopedCache.make({
2128
2399
  capacity: 64,
2129
- lookup: (url) => Effect15.acquireRelease(Effect15.tryPromise({
2130
- try: () => Relay2.connect(url),
2131
- catch: (e) => new RelayError({
2132
- message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2133
- url,
2134
- 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();
2135
2411
  })
2136
- }), (relay) => Effect15.sync(() => {
2137
- relay.close();
2138
- }))
2412
+ )
2139
2413
  });
2140
- const connectedUrls = new Set;
2141
- const statusListeners = new Set;
2414
+ const connectedUrls = /* @__PURE__ */ new Set();
2415
+ const statusListeners = /* @__PURE__ */ new Set();
2142
2416
  const notifyStatus = () => {
2143
2417
  const status = { connectedUrls: [...connectedUrls] };
2144
- for (const listener of statusListeners)
2145
- listener(status);
2418
+ for (const listener of statusListeners) listener(status);
2146
2419
  };
2147
2420
  const markConnected = (url) => {
2148
2421
  if (!connectedUrls.has(url)) {
@@ -2156,66 +2429,98 @@ function createRelayHandle() {
2156
2429
  notifyStatus();
2157
2430
  }
2158
2431
  };
2159
- 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)));
2160
- 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))))));
2161
- const collectEvents = (url, filters) => withRelay(url, (relay) => Effect15.callback((resume) => {
2162
- const events = [];
2163
- let settled = false;
2164
- let timer;
2165
- let sub;
2166
- const cleanup = () => {
2167
- settled = true;
2168
- if (timer !== undefined) {
2169
- clearTimeout(timer);
2170
- timer = undefined;
2171
- }
2172
- sub?.close();
2173
- sub = undefined;
2174
- };
2175
- const fail = (e) => resume(Effect15.fail(new RelayError({
2176
- message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2177
- url,
2178
- cause: e
2179
- })));
2180
- try {
2181
- sub = relay.subscribe([...filters], {
2182
- onevent(evt) {
2183
- if (!settled) {
2184
- 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));
2185
2484
  }
2186
- },
2187
- oneose() {
2188
- if (settled)
2189
- return;
2485
+ });
2486
+ timer = setTimeout(() => {
2487
+ if (settled) return;
2190
2488
  cleanup();
2191
2489
  resume(Effect15.succeed(events));
2192
- }
2193
- });
2194
- timer = setTimeout(() => {
2195
- if (settled)
2196
- return;
2490
+ }, 1e4);
2491
+ } catch (e) {
2197
2492
  cleanup();
2198
- resume(Effect15.succeed(events));
2199
- }, 1e4);
2200
- } catch (e) {
2201
- cleanup();
2202
- fail(e);
2203
- }
2204
- return Effect15.sync(cleanup);
2205
- }));
2493
+ fail(e);
2494
+ }
2495
+ return Effect15.sync(cleanup);
2496
+ })
2497
+ );
2206
2498
  return {
2207
2499
  publish: (event, urls) => Effect15.gen(function* () {
2208
- const results = yield* Effect15.forEach(urls, (url) => Effect15.result(withRelay(url, (relay) => Effect15.tryPromise({
2209
- try: () => relay.publish(event),
2210
- catch: (e) => new RelayError({
2211
- message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2212
- url,
2213
- cause: e
2214
- })
2215
- }).pipe(Effect15.timeoutOrElse({
2216
- duration: "10 seconds",
2217
- onTimeout: () => Effect15.fail(new RelayError({ message: `Publish to ${url} timed out`, url }))
2218
- })))), { 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
+ );
2219
2524
  const failures = results.filter((r) => r._tag === "Failure");
2220
2525
  if (failures.length === urls.length && urls.length > 0) {
2221
2526
  return yield* new RelayError({
@@ -2224,90 +2529,104 @@ function createRelayHandle() {
2224
2529
  }
2225
2530
  }),
2226
2531
  fetchEvents: (ids, url) => Effect15.gen(function* () {
2227
- if (ids.length === 0)
2228
- return [];
2532
+ if (ids.length === 0) return [];
2229
2533
  return yield* collectEvents(url, [{ ids }]);
2230
2534
  }),
2231
2535
  fetchByFilter: (filter, url) => collectEvents(url, [filter]),
2232
- subscribe: (filter, url, onEvent) => withRelay(url, (relay) => Effect15.acquireRelease(Effect15.try({
2233
- try: () => relay.subscribe([filter], {
2234
- onevent(evt) {
2235
- onEvent(evt);
2236
- },
2237
- oneose() {}
2238
- }),
2239
- catch: (e) => new RelayError({
2240
- message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2241
- url,
2242
- cause: e
2243
- })
2244
- }), (sub) => Effect15.sync(() => {
2245
- sub.close();
2246
- })).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)),
2247
- sendNegMsg: (url, subId, filter, msgHex) => withRelay(url, (relay) => Effect15.callback((resume) => {
2248
- let settled = false;
2249
- let timer;
2250
- let sub;
2251
- let ws;
2252
- const cleanup = () => {
2253
- settled = true;
2254
- if (timer !== undefined) {
2255
- clearTimeout(timer);
2256
- timer = undefined;
2257
- }
2258
- sub?.close();
2259
- sub = undefined;
2260
- ws?.removeEventListener("message", handler);
2261
- ws = undefined;
2262
- };
2263
- const fail = (e) => resume(Effect15.fail(new RelayError({
2264
- message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2265
- url,
2266
- cause: e
2267
- })));
2268
- const handler = (msg) => {
2269
- if (settled || typeof msg.data !== "string")
2270
- return;
2271
- const frameOpt = parseNegMessageFrame(msg.data);
2272
- if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId)
2273
- return;
2274
- const frame = frameOpt.value;
2275
- cleanup();
2276
- if (frame[0] === "NEG-MSG") {
2277
- resume(Effect15.succeed({
2278
- msgHex: frame[2],
2279
- haveIds: [],
2280
- needIds: []
2281
- }));
2282
- return;
2283
- }
2284
- fail(new Error(`NEG-ERR: ${frame[2]}`));
2285
- };
2286
- try {
2287
- sub = relay.subscribe([filter], {
2288
- onevent() {},
2289
- oneose() {}
2290
- });
2291
- ws = relay._ws || relay.ws;
2292
- 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;
2293
2590
  cleanup();
2294
- fail(new Error("Cannot access relay WebSocket"));
2295
- return Effect15.succeed(undefined);
2296
- }
2297
- timer = setTimeout(() => {
2298
- if (settled)
2591
+ if (frame[0] === "NEG-MSG") {
2592
+ resume(
2593
+ Effect15.succeed({
2594
+ msgHex: frame[2],
2595
+ haveIds: [],
2596
+ needIds: []
2597
+ })
2598
+ );
2299
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) {
2300
2624
  cleanup();
2301
- fail(new Error("NIP-77 negotiation timeout"));
2302
- }, 30000);
2303
- ws.addEventListener("message", handler);
2304
- ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
2305
- } catch (e) {
2306
- cleanup();
2307
- fail(e);
2308
- }
2309
- return Effect15.sync(cleanup);
2310
- })),
2625
+ fail(e);
2626
+ }
2627
+ return Effect15.sync(cleanup);
2628
+ })
2629
+ ),
2311
2630
  closeAll: () => ScopedCache.invalidateAll(connections),
2312
2631
  getStatus: () => ({ connectedUrls: [...connectedUrls] }),
2313
2632
  subscribeStatus: (callback) => {
@@ -2359,11 +2678,14 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
2359
2678
  }
2360
2679
 
2361
2680
  // src/layers/GiftWrapLive.ts
2362
- var GiftWrapLive = Layer6.effect(GiftWrap3, Effect17.gen(function* () {
2363
- const identity = yield* Identity;
2364
- const epochStore = yield* EpochStore;
2365
- return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2366
- }));
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
+ );
2367
2689
 
2368
2690
  // src/layers/PublishQueueLive.ts
2369
2691
  import { Effect as Effect19, Layer as Layer7 } from "effect";
@@ -2377,12 +2699,11 @@ function persist(storage, pending) {
2377
2699
  function createPublishQueue(storage, relay) {
2378
2700
  return Effect18.gen(function* () {
2379
2701
  const stored = yield* storage.getMeta(META_KEY);
2380
- const initial = Array.isArray(stored) ? new Set(stored) : new Set;
2702
+ const initial = Array.isArray(stored) ? new Set(stored) : /* @__PURE__ */ new Set();
2381
2703
  const pendingRef = yield* Ref4.make(initial);
2382
- const listeners = new Set;
2704
+ const listeners = /* @__PURE__ */ new Set();
2383
2705
  const notify = (pending) => {
2384
- for (const listener of listeners)
2385
- listener(pending.size);
2706
+ for (const listener of listeners) listener(pending.size);
2386
2707
  };
2387
2708
  return {
2388
2709
  enqueue: (eventId) => Effect18.gen(function* () {
@@ -2396,13 +2717,11 @@ function createPublishQueue(storage, relay) {
2396
2717
  }),
2397
2718
  flush: (relayUrls) => Effect18.gen(function* () {
2398
2719
  const pending = yield* Ref4.get(pendingRef);
2399
- if (pending.size === 0)
2400
- return;
2401
- const succeeded = new Set;
2720
+ if (pending.size === 0) return;
2721
+ const succeeded = /* @__PURE__ */ new Set();
2402
2722
  let consecutiveFailures = 0;
2403
2723
  for (const eventId of pending) {
2404
- if (consecutiveFailures >= 3)
2405
- break;
2724
+ if (consecutiveFailures >= 3) break;
2406
2725
  const gw = yield* storage.getGiftWrap(eventId);
2407
2726
  if (!gw || !gw.event) {
2408
2727
  succeeded.add(eventId);
@@ -2412,7 +2731,6 @@ function createPublishQueue(storage, relay) {
2412
2731
  const result = yield* Effect18.result(relay.publish(gw.event, relayUrls));
2413
2732
  if (result._tag === "Success") {
2414
2733
  succeeded.add(eventId);
2415
- yield* storage.stripGiftWrapBlob(eventId);
2416
2734
  consecutiveFailures = 0;
2417
2735
  } else {
2418
2736
  consecutiveFailures++;
@@ -2440,11 +2758,14 @@ function createPublishQueue(storage, relay) {
2440
2758
  }
2441
2759
 
2442
2760
  // src/layers/PublishQueueLive.ts
2443
- var PublishQueueLive = Layer7.effect(PublishQueue, Effect19.gen(function* () {
2444
- const storage = yield* Storage;
2445
- const relay = yield* Relay;
2446
- return yield* createPublishQueue(storage, relay);
2447
- }));
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
+ );
2448
2769
 
2449
2770
  // src/layers/SyncStatusLive.ts
2450
2771
  import { Layer as Layer8 } from "effect";
@@ -2454,13 +2775,12 @@ import { Effect as Effect20, SubscriptionRef } from "effect";
2454
2775
  function createSyncStatusHandle() {
2455
2776
  return Effect20.gen(function* () {
2456
2777
  const ref = yield* SubscriptionRef.make("idle");
2457
- const listeners = new Set;
2778
+ const listeners = /* @__PURE__ */ new Set();
2458
2779
  return {
2459
2780
  get: () => SubscriptionRef.get(ref),
2460
2781
  set: (status) => Effect20.gen(function* () {
2461
2782
  yield* SubscriptionRef.set(ref, status);
2462
- for (const listener of listeners)
2463
- listener(status);
2783
+ for (const listener of listeners) listener(status);
2464
2784
  }),
2465
2785
  subscribe: (callback) => {
2466
2786
  listeners.add(callback);
@@ -2475,8 +2795,7 @@ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
2475
2795
 
2476
2796
  // src/layers/TablinumLive.ts
2477
2797
  function reportSyncError(onSyncError, error) {
2478
- if (!onSyncError)
2479
- return;
2798
+ if (!onSyncError) return;
2480
2799
  onSyncError(error instanceof Error ? error : new Error(String(error)));
2481
2800
  }
2482
2801
  function mapMemberRecord(record) {
@@ -2484,249 +2803,352 @@ function mapMemberRecord(record) {
2484
2803
  id: record.id,
2485
2804
  addedAt: record.addedAt,
2486
2805
  addedInEpoch: record.addedInEpoch,
2487
- ...record.name !== undefined ? { name: record.name } : {},
2488
- ...record.picture !== undefined ? { picture: record.picture } : {},
2489
- ...record.about !== undefined ? { about: record.about } : {},
2490
- ...record.nip05 !== undefined ? { nip05: record.nip05 } : {},
2491
- ...record.removedAt !== undefined ? { removedAt: record.removedAt } : {},
2492
- ...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 } : {}
2493
2812
  };
2494
2813
  }
2495
2814
  var IdentityWithDeps = IdentityLive.pipe(Layer9.provide(StorageLive));
2496
- var EpochStoreWithDeps = EpochStoreLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(StorageLive));
2497
- var GiftWrapWithDeps = GiftWrapLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(EpochStoreWithDeps));
2498
- var PublishQueueWithDeps = PublishQueueLive.pipe(Layer9.provide(StorageLive), Layer9.provide(RelayLive));
2499
- var AllServicesLive = Layer9.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2500
- var TablinumLive = Layer9.effect(Tablinum, Effect21.gen(function* () {
2501
- const config = yield* Config;
2502
- const identity = yield* Identity;
2503
- const epochStore = yield* EpochStore;
2504
- const storage = yield* Storage;
2505
- const relay = yield* Relay;
2506
- const giftWrap = yield* GiftWrap3;
2507
- const publishQueue = yield* PublishQueue;
2508
- const syncStatus = yield* SyncStatus;
2509
- const scope = yield* Effect21.scope;
2510
- const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2511
- const pubsub = yield* PubSub2.unbounded();
2512
- const replayingRef = yield* Ref5.make(false);
2513
- const closedRef = yield* Ref5.make(false);
2514
- const watchCtx = { pubsub, replayingRef };
2515
- const schemaEntries = Object.entries(config.schema);
2516
- const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2517
- const knownCollections = new Map(allSchemaEntries.map(([, def]) => [def.name, def.eventRetention]));
2518
- let notifyAuthor;
2519
- const syncHandle = createSyncHandle(storage, giftWrap, relay, publishQueue, syncStatus, watchCtx, config.relays, knownCollections, epochStore, identity.privateKey, identity.publicKey, scope, config.logLevel, config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : undefined, (pubkey) => notifyAuthor?.(pubkey), config.onRemoved, config.onMembersChanged);
2520
- const onWrite = (event) => Effect21.gen(function* () {
2521
- const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2522
- const dTag = `${event.collection}:${event.recordId}`;
2523
- const wrapResult = yield* Effect21.result(giftWrap.wrap({
2524
- kind: 1,
2525
- content,
2526
- tags: [["d", dTag]],
2527
- created_at: Math.floor(event.createdAt / 1000)
2528
- }));
2529
- if (wrapResult._tag === "Failure") {
2530
- reportSyncError(config.onSyncError, wrapResult.failure);
2531
- return;
2532
- }
2533
- const gw = wrapResult.success;
2534
- yield* storage.putGiftWrap({ id: gw.id, createdAt: gw.created_at });
2535
- yield* Effect21.forkIn(Effect21.gen(function* () {
2536
- const publishResult = yield* Effect21.result(syncHandle.publishLocal({
2537
- id: gw.id,
2538
- event: gw,
2539
- createdAt: gw.created_at
2540
- }));
2541
- if (publishResult._tag === "Failure") {
2542
- reportSyncError(config.onSyncError, publishResult.failure);
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;
2543
2892
  }
2544
- }), scope);
2545
- });
2546
- const knownAuthors = new Set;
2547
- const putMemberRecord = (record) => Effect21.gen(function* () {
2548
- const existing = yield* storage.getRecord("_members", record.id);
2549
- const event = {
2550
- id: uuidv7(),
2551
- collection: "_members",
2552
- recordId: record.id,
2553
- kind: existing ? "u" : "c",
2554
- data: record,
2555
- createdAt: Date.now(),
2556
- author: identity.publicKey
2557
- };
2558
- yield* storage.putEvent(event);
2559
- yield* applyEvent(storage, event);
2560
- yield* onWrite(event);
2561
- yield* notifyChange(watchCtx, {
2562
- collection: "_members",
2563
- recordId: record.id,
2564
- 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
+ );
2565
2910
  });
2566
- config.onMembersChanged?.();
2567
- });
2568
- notifyAuthor = (pubkey) => {
2569
- if (knownAuthors.has(pubkey))
2570
- return;
2571
- knownAuthors.add(pubkey);
2572
- Effect21.runFork(Effect21.gen(function* () {
2573
- const existing = yield* storage.getRecord("_members", pubkey);
2574
- if (!existing) {
2575
- yield* putMemberRecord({
2576
- id: pubkey,
2577
- addedAt: Date.now(),
2578
- addedInEpoch: getCurrentEpoch(epochStore).id
2579
- });
2580
- }
2581
- const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none())));
2582
- if (Option9.isSome(profileOpt)) {
2583
- const current = yield* storage.getRecord("_members", pubkey);
2584
- if (current) {
2585
- yield* storage.putRecord("_members", {
2586
- ...current,
2587
- ...profileOpt.value
2588
- });
2589
- yield* notifyChange(watchCtx, {
2590
- collection: "_members",
2591
- recordId: pubkey,
2592
- kind: "update"
2593
- });
2594
- config.onMembersChanged?.();
2595
- }
2596
- }
2597
- }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope)));
2598
- };
2599
- const handles = new Map;
2600
- for (const [, def] of allSchemaEntries) {
2601
- const validator = buildValidator(def.name, def);
2602
- const partialValidator = buildPartialValidator(def.name, def);
2603
- const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite, config.logLevel);
2604
- handles.set(def.name, handle);
2605
- }
2606
- yield* syncHandle.startSubscription();
2607
- yield* Effect21.logInfo("Tablinum ready", {
2608
- dbName: config.dbName,
2609
- collections: schemaEntries.map(([name]) => name),
2610
- relays: config.relays
2611
- });
2612
- const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2613
- if (!selfMember) {
2614
- yield* putMemberRecord({
2615
- id: identity.publicKey,
2616
- addedAt: Date.now(),
2617
- 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?.();
2618
2932
  });
2619
- }
2620
- const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2621
- const ensureOpen = (effect) => withLog(Effect21.gen(function* () {
2622
- if (yield* Ref5.get(closedRef)) {
2623
- return yield* new StorageError({ message: "Database is closed" });
2624
- }
2625
- return yield* effect;
2626
- }));
2627
- const ensureSyncOpen = (effect) => withLog(Effect21.gen(function* () {
2628
- if (yield* Ref5.get(closedRef)) {
2629
- return yield* new SyncError({ message: "Database is closed", phase: "init" });
2630
- }
2631
- return yield* effect;
2632
- }));
2633
- const dbHandle = {
2634
- collection: (name) => {
2635
- const handle = handles.get(name);
2636
- if (!handle)
2637
- throw new Error(`Collection "${name}" not found in schema`);
2638
- return handle;
2639
- },
2640
- publicKey: identity.publicKey,
2641
- members: handles.get("_members"),
2642
- exportKey: () => identity.exportKey(),
2643
- exportInvite: () => ({
2644
- epochKeys: [...exportEpochKeys(epochStore)],
2645
- relays: [...config.relays],
2646
- dbName: config.dbName
2647
- }),
2648
- close: () => withLog(Effect21.gen(function* () {
2649
- if (yield* Ref5.get(closedRef))
2650
- return;
2651
- yield* Ref5.set(closedRef, true);
2652
- yield* Scope4.close(scope, Exit.void);
2653
- })),
2654
- rebuild: () => ensureOpen(rebuild(storage, allSchemaEntries.map(([, def]) => def.name))),
2655
- sync: () => ensureSyncOpen(syncHandle.sync()),
2656
- getSyncStatus: () => syncStatus.get(),
2657
- subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
2658
- pendingCount: () => publishQueue.size(),
2659
- subscribePendingCount: (callback) => publishQueue.subscribe(callback),
2660
- getRelayStatus: () => relay.getStatus(),
2661
- subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
2662
- addMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2663
- const existing = yield* storage.getRecord("_members", pubkey);
2664
- if (existing && !existing.removedAt)
2665
- 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) {
2666
2992
  yield* putMemberRecord({
2667
- id: pubkey,
2993
+ id: identity.publicKey,
2668
2994
  addedAt: Date.now(),
2669
- addedInEpoch: getCurrentEpoch(epochStore).id,
2670
- ...existing ? { removedAt: undefined, removedInEpoch: undefined } : {}
2671
- });
2672
- })),
2673
- removeMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2674
- const allMembers = yield* storage.getAllRecords("_members");
2675
- const activeMembers = allMembers.filter((member) => !member.removedAt && member.id !== pubkey);
2676
- const activePubkeys = activeMembers.map((member) => member.id);
2677
- const result = createRotation(epochStore, identity.privateKey, identity.publicKey, activePubkeys, [pubkey]);
2678
- addEpoch(epochStore, result.epoch);
2679
- epochStore.currentEpochId = result.epoch.id;
2680
- yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
2681
- const memberRecord = yield* storage.getRecord("_members", pubkey);
2682
- yield* putMemberRecord({
2683
- ...memberRecord ?? {
2684
- id: pubkey,
2685
- addedAt: 0,
2686
- addedInEpoch: EpochId("epoch-0")
2687
- },
2688
- removedAt: Date.now(),
2689
- removedInEpoch: result.epoch.id
2995
+ addedInEpoch: getCurrentEpoch(epochStore).id
2690
2996
  });
2691
- 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 });
2692
- 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 });
2693
- yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
2694
- })),
2695
- getMembers: () => ensureOpen(Effect21.gen(function* () {
2696
- const allRecords = yield* storage.getAllRecords("_members");
2697
- return allRecords.filter((record) => !record._d).map(mapMemberRecord);
2698
- })),
2699
- getProfile: () => ensureOpen(Effect21.gen(function* () {
2700
- const record = yield* storage.getRecord("_members", identity.publicKey);
2701
- if (!record)
2702
- return {};
2703
- const profile = {};
2704
- if (record.name !== undefined)
2705
- profile.name = record.name;
2706
- if (record.picture !== undefined)
2707
- profile.picture = record.picture;
2708
- if (record.about !== undefined)
2709
- profile.about = record.about;
2710
- if (record.nip05 !== undefined)
2711
- profile.nip05 = record.nip05;
2712
- return profile;
2713
- })),
2714
- setProfile: (profile) => ensureOpen(Effect21.gen(function* () {
2715
- const existing = yield* storage.getRecord("_members", identity.publicKey);
2716
- if (!existing) {
2717
- return yield* new ValidationError({ message: "Current user is not a member" });
2718
- }
2719
- const { _d, _u, _a, _e, ...memberFields } = existing;
2720
- yield* putMemberRecord({ ...memberFields, ...profile });
2721
- }))
2722
- };
2723
- return dbHandle;
2724
- }).pipe(Effect21.withLogSpan("tablinum.init"))).pipe(Layer9.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));
2725
3148
 
2726
3149
  // src/db/create-tablinum.ts
2727
3150
  function resolveLogLevel(input) {
2728
- if (input === undefined || input === "none")
2729
- return "None";
3151
+ if (input === void 0 || input === "none") return "None";
2730
3152
  switch (input) {
2731
3153
  case "debug":
2732
3154
  return "Debug";
@@ -2786,14 +3208,12 @@ function createDeferred() {
2786
3208
  promise,
2787
3209
  settled: () => settled,
2788
3210
  resolve: (value) => {
2789
- if (settled)
2790
- return;
3211
+ if (settled) return;
2791
3212
  settled = true;
2792
3213
  resolvePromise(value);
2793
3214
  },
2794
3215
  reject: (reason) => {
2795
- if (settled)
2796
- return;
3216
+ if (settled) return;
2797
3217
  settled = true;
2798
3218
  rejectPromise(reason);
2799
3219
  }
@@ -2815,7 +3235,9 @@ function wrapQueryBuilder(getBuilder, touchVersion, ready) {
2815
3235
  },
2816
3236
  first: () => {
2817
3237
  touchVersion();
2818
- 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
+ );
2819
3241
  },
2820
3242
  count: () => {
2821
3243
  touchVersion();
@@ -2847,7 +3269,9 @@ function wrapOrderByBuilder(getBuilder, touchVersion, ready) {
2847
3269
  },
2848
3270
  first: () => {
2849
3271
  touchVersion();
2850
- 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
+ );
2851
3275
  },
2852
3276
  count: () => {
2853
3277
  touchVersion();
@@ -2857,7 +3281,7 @@ function wrapOrderByBuilder(getBuilder, touchVersion, ready) {
2857
3281
  }
2858
3282
 
2859
3283
  // src/svelte/collection.svelte.ts
2860
- class Collection {
3284
+ var Collection = class {
2861
3285
  error = $state(null);
2862
3286
  #handle = null;
2863
3287
  #ready = createDeferred();
@@ -2865,15 +3289,16 @@ class Collection {
2865
3289
  #watchAbort = null;
2866
3290
  #watchFiber = null;
2867
3291
  #logLevel = "None";
3292
+ /** @internal */
2868
3293
  _bind(handle, logLevel = "None") {
2869
- if (this.#handle)
2870
- return;
3294
+ if (this.#handle) return;
2871
3295
  this.#handle = handle;
2872
3296
  this.#logLevel = logLevel;
2873
3297
  this.error = null;
2874
3298
  this.#settleReady();
2875
3299
  this.#startWatch();
2876
3300
  }
3301
+ /** @internal */
2877
3302
  _fail(err) {
2878
3303
  this.error = err;
2879
3304
  this.#settleReady(err);
@@ -2886,31 +3311,41 @@ class Collection {
2886
3311
  }
2887
3312
  }
2888
3313
  #startWatch() {
2889
- if (!this.#handle)
2890
- return;
2891
- const abort = new AbortController;
3314
+ if (!this.#handle) return;
3315
+ const abort = new AbortController();
2892
3316
  this.#watchAbort = abort;
2893
- this.#watchFiber = Effect24.runFork(Stream4.runForEach(this.#handle.watch(), (_records) => Effect24.sync(() => {
2894
- if (!abort.signal.aborted) {
2895
- this.#version++;
2896
- }
2897
- })).pipe(Effect24.catch((e) => Effect24.sync(() => {
2898
- if (!abort.signal.aborted) {
2899
- this.error = e instanceof Error ? e : new Error(String(e));
2900
- }
2901
- })), Effect24.provideService(References5.MinimumLogLevel, this.#logLevel)));
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
+ );
2902
3336
  }
2903
3337
  #touchVersion = () => {
2904
- this.#version;
3338
+ void this.#version;
2905
3339
  };
2906
3340
  #handleOrThrow = () => {
2907
- if (this.#handle)
2908
- return this.#handle;
3341
+ if (this.#handle) return this.#handle;
2909
3342
  throw this.error ?? new ClosedError({ message: "Collection is not ready" });
2910
3343
  };
2911
3344
  #run = async (getEffect) => {
2912
3345
  await this.#ready.promise;
2913
- return Effect24.runPromise(getEffect().pipe(Effect24.provideService(References5.MinimumLogLevel, this.#logLevel)));
3346
+ return Effect24.runPromise(
3347
+ getEffect().pipe(Effect24.provideService(References5.MinimumLogLevel, this.#logLevel))
3348
+ );
2914
3349
  };
2915
3350
  add = (data) => {
2916
3351
  return this.#run(() => this.#handleOrThrow().add(data));
@@ -2929,7 +3364,11 @@ class Collection {
2929
3364
  return this.#run(() => this.#handleOrThrow().get(id));
2930
3365
  }
2931
3366
  this.#touchVersion();
2932
- 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
+ );
2933
3372
  }
2934
3373
  first = () => {
2935
3374
  this.#touchVersion();
@@ -2940,11 +3379,20 @@ class Collection {
2940
3379
  return this.#run(() => this.#handleOrThrow().count());
2941
3380
  };
2942
3381
  where = (field2) => {
2943
- 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
+ );
2944
3387
  };
2945
3388
  orderBy = (field2) => {
2946
- 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
+ );
2947
3394
  };
3395
+ /** @internal */
2948
3396
  _destroy(reason = new ClosedError({ message: "Collection is closed" })) {
2949
3397
  if (this.#watchAbort) {
2950
3398
  this.#watchAbort.abort();
@@ -2958,10 +3406,10 @@ class Collection {
2958
3406
  this.error ??= reason;
2959
3407
  this.#settleReady(this.error);
2960
3408
  }
2961
- }
3409
+ };
2962
3410
 
2963
3411
  // src/svelte/tablinum.svelte.ts
2964
- class Tablinum2 {
3412
+ var Tablinum2 = class {
2965
3413
  status = $state("initializing");
2966
3414
  syncStatus = $state("idle");
2967
3415
  pendingCount = $state(0);
@@ -2970,8 +3418,8 @@ class Tablinum2 {
2970
3418
  ready;
2971
3419
  #handle = null;
2972
3420
  #scope = null;
2973
- #collections = new Map;
2974
- #members = new Collection;
3421
+ #collections = /* @__PURE__ */ new Map();
3422
+ #members = new Collection();
2975
3423
  #unsubscribeSyncStatus = null;
2976
3424
  #unsubscribePendingCount = null;
2977
3425
  #unsubscribeRelayStatus = null;
@@ -3000,16 +3448,20 @@ class Tablinum2 {
3000
3448
  const handle = this.#requireReady();
3001
3449
  try {
3002
3450
  this.error = null;
3003
- return await Effect25.runPromise(run(handle).pipe(Effect25.provideService(References6.MinimumLogLevel, this.#logLevel)));
3451
+ return await Effect25.runPromise(
3452
+ run(handle).pipe(Effect25.provideService(References6.MinimumLogLevel, this.#logLevel))
3453
+ );
3004
3454
  } catch (e) {
3005
3455
  this.error = e instanceof Error ? e : new Error(String(e));
3006
3456
  throw this.error;
3007
3457
  }
3008
3458
  };
3009
- async#init(config) {
3459
+ async #init(config) {
3010
3460
  const scope = Effect25.runSync(Scope6.make());
3011
3461
  try {
3012
- 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
+ );
3013
3465
  if (this.#closed) {
3014
3466
  await Effect25.runPromise(Scope6.close(scope, Exit2.void));
3015
3467
  this.#scope = null;
@@ -3032,7 +3484,8 @@ class Tablinum2 {
3032
3484
  this.status = "ready";
3033
3485
  this.#settleReady();
3034
3486
  } catch (e) {
3035
- await Effect25.runPromise(Scope6.close(scope, Exit2.fail(e))).catch(() => {});
3487
+ await Effect25.runPromise(Scope6.close(scope, Exit2.fail(e))).catch(() => {
3488
+ });
3036
3489
  const err = e instanceof Error ? e : new Error(String(e));
3037
3490
  this.error = err;
3038
3491
  this.status = "error";
@@ -3055,7 +3508,7 @@ class Tablinum2 {
3055
3508
  }
3056
3509
  let col = this.#collections.get(name);
3057
3510
  if (!col) {
3058
- col = new Collection;
3511
+ col = new Collection();
3059
3512
  this.#collections.set(name, col);
3060
3513
  if (this.#handle) {
3061
3514
  col._bind(this.#handle.collection(name), this.#logLevel);
@@ -3076,8 +3529,7 @@ class Tablinum2 {
3076
3529
  return this.#requireReady().exportInvite();
3077
3530
  }
3078
3531
  close = async () => {
3079
- if (this.#closed)
3080
- return;
3532
+ if (this.#closed) return;
3081
3533
  this.#closed = true;
3082
3534
  const closeError = new ClosedError({
3083
3535
  message: this.#readyState.settled() ? "Tablinum is closed" : "Tablinum was closed before initialization completed"
@@ -3095,8 +3547,7 @@ class Tablinum2 {
3095
3547
  this.#unsubscribeRelayStatus = null;
3096
3548
  }
3097
3549
  this.#members._destroy(closeError);
3098
- for (const col of this.#collections.values())
3099
- col._destroy(closeError);
3550
+ for (const col of this.#collections.values()) col._destroy(closeError);
3100
3551
  this.#collections.clear();
3101
3552
  const handle = this.#handle;
3102
3553
  const scope = this.#scope;
@@ -3119,19 +3570,19 @@ class Tablinum2 {
3119
3570
  getMembers = async () => this.#runHandleEffect((handle) => handle.getMembers());
3120
3571
  getProfile = async () => this.#runHandleEffect((handle) => handle.getProfile());
3121
3572
  setProfile = async (profile) => this.#runHandleEffect((handle) => handle.setProfile(profile));
3122
- }
3573
+ };
3123
3574
  export {
3124
- field,
3125
- encodeInvite,
3126
- decodeInvite,
3127
- collection,
3128
- ValidationError,
3129
- Tablinum2 as Tablinum,
3130
- SyncError,
3131
- StorageError,
3132
- RelayError,
3133
- NotFoundError,
3134
- CryptoError,
3575
+ ClosedError,
3135
3576
  Collection,
3136
- 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
3137
3588
  };