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.
package/dist/index.js CHANGED
@@ -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/create-tablinum.ts
57
60
  import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
58
61
 
@@ -61,27 +64,20 @@ import { Effect, Schema as Schema2 } from "effect";
61
64
 
62
65
  // src/errors.ts
63
66
  import { Data } from "effect";
64
-
65
- class ValidationError extends Data.TaggedError("ValidationError") {
66
- }
67
-
68
- class StorageError extends Data.TaggedError("StorageError") {
69
- }
70
-
71
- class CryptoError extends Data.TaggedError("CryptoError") {
72
- }
73
-
74
- class RelayError extends Data.TaggedError("RelayError") {
75
- }
76
-
77
- class SyncError extends Data.TaggedError("SyncError") {
78
- }
79
-
80
- class NotFoundError extends Data.TaggedError("NotFoundError") {
81
- }
82
-
83
- class ClosedError extends Data.TaggedError("ClosedError") {
84
- }
67
+ var ValidationError = class extends Data.TaggedError("ValidationError") {
68
+ };
69
+ var StorageError = class extends Data.TaggedError("StorageError") {
70
+ };
71
+ var CryptoError = class extends Data.TaggedError("CryptoError") {
72
+ };
73
+ var RelayError = class extends Data.TaggedError("RelayError") {
74
+ };
75
+ var SyncError = class extends Data.TaggedError("SyncError") {
76
+ };
77
+ var NotFoundError = class extends Data.TaggedError("NotFoundError") {
78
+ };
79
+ var ClosedError = class extends Data.TaggedError("ClosedError") {
80
+ };
85
81
 
86
82
  // src/db/epoch.ts
87
83
  import { Option, Schema } from "effect";
@@ -109,15 +105,17 @@ var PersistedEpochStoreSchema = Schema.Struct({
109
105
  epochs: Schema.Array(PersistedEpochSchema),
110
106
  currentEpochId: Schema.String
111
107
  });
112
- var decodePersistedEpochStore = Schema.decodeUnknownSync(Schema.fromJsonString(PersistedEpochStoreSchema));
108
+ var decodePersistedEpochStore = Schema.decodeUnknownSync(
109
+ Schema.fromJsonString(PersistedEpochStoreSchema)
110
+ );
113
111
  function createEpochKey(id, privateKeyHex, createdBy, parentEpoch) {
114
112
  const publicKey = getPublicKey(hexToBytes(privateKeyHex));
115
113
  const base = { id, privateKey: privateKeyHex, publicKey, createdBy };
116
- return parentEpoch !== undefined ? { ...base, parentEpoch } : base;
114
+ return parentEpoch !== void 0 ? { ...base, parentEpoch } : base;
117
115
  }
118
116
  function createEpochStore(initialEpoch) {
119
- const epochs = new Map;
120
- const keysByPublicKey = new Map;
117
+ const epochs = /* @__PURE__ */ new Map();
118
+ const keysByPublicKey = /* @__PURE__ */ new Map();
121
119
  epochs.set(initialEpoch.id, initialEpoch);
122
120
  keysByPublicKey.set(initialEpoch.publicKey, hexToBytes(initialEpoch.privateKey));
123
121
  return { epochs, keysByPublicKey, currentEpochId: initialEpoch.id };
@@ -127,7 +125,14 @@ function addEpoch(store, epoch) {
127
125
  store.keysByPublicKey.set(epoch.publicKey, hexToBytes(epoch.privateKey));
128
126
  }
129
127
  function hydrateEpochStore(snapshot) {
130
- const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map((epoch) => createEpochKey(EpochId(epoch.id), epoch.privateKey, epoch.createdBy, epoch.parentEpoch !== undefined ? EpochId(epoch.parentEpoch) : undefined));
128
+ const [firstEpoch, ...remainingEpochs] = snapshot.epochs.map(
129
+ (epoch) => createEpochKey(
130
+ EpochId(epoch.id),
131
+ epoch.privateKey,
132
+ epoch.createdBy,
133
+ epoch.parentEpoch !== void 0 ? EpochId(epoch.parentEpoch) : void 0
134
+ )
135
+ );
131
136
  if (!firstEpoch) {
132
137
  throw new Error("Epoch snapshot must contain at least one epoch");
133
138
  }
@@ -143,9 +148,16 @@ function createEpochStoreFromInputs(epochKeys, options = {}) {
143
148
  throw new Error("Epoch input must contain at least one key");
144
149
  }
145
150
  const createdBy = options.createdBy ?? "";
146
- const epochs = epochKeys.map((epochKey, index) => createEpochKey(epochKey.epochId, epochKey.key, createdBy, index > 0 ? epochKeys[index - 1].epochId : undefined));
151
+ const epochs = epochKeys.map(
152
+ (epochKey, index) => createEpochKey(
153
+ epochKey.epochId,
154
+ epochKey.key,
155
+ createdBy,
156
+ index > 0 ? epochKeys[index - 1].epochId : void 0
157
+ )
158
+ );
147
159
  const store = createEpochStore(epochs[0]);
148
- for (let i = 1;i < epochs.length; i++) {
160
+ for (let i = 1; i < epochs.length; i++) {
149
161
  addEpoch(store, epochs[i]);
150
162
  }
151
163
  store.currentEpochId = epochs[epochs.length - 1].id;
@@ -172,7 +184,7 @@ function serializeEpochStore(store) {
172
184
  id: epoch.id,
173
185
  privateKey: epoch.privateKey,
174
186
  createdBy: epoch.createdBy,
175
- ...epoch.parentEpoch !== undefined ? { parentEpoch: epoch.parentEpoch } : {}
187
+ ...epoch.parentEpoch !== void 0 ? { parentEpoch: epoch.parentEpoch } : {}
176
188
  })),
177
189
  currentEpochId: store.currentEpochId
178
190
  };
@@ -197,49 +209,61 @@ var RuntimeConfigSchema = Schema2.Struct({
197
209
  epochKeys: Schema2.optional(Schema2.Array(EpochKeyInputSchema))
198
210
  });
199
211
  function resolveRuntimeConfig(source) {
200
- return Schema2.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(Effect.map((config) => ({
201
- relays: [...config.relays],
202
- privateKey: config.privateKey,
203
- epochKeys: config.epochKeys?.map((ek) => ({ epochId: EpochId(ek.epochId), key: ek.key })),
204
- dbName: DatabaseName(config.dbName ?? "tablinum")
205
- })), Effect.mapError((error) => new ValidationError({
206
- message: `Invalid Tablinum configuration: ${error.message}`
207
- })));
212
+ return Schema2.decodeUnknownEffect(RuntimeConfigSchema)(source).pipe(
213
+ Effect.map((config) => ({
214
+ relays: [...config.relays],
215
+ privateKey: config.privateKey,
216
+ epochKeys: config.epochKeys?.map((ek) => ({ epochId: EpochId(ek.epochId), key: ek.key })),
217
+ dbName: DatabaseName(config.dbName ?? "tablinum")
218
+ })),
219
+ Effect.mapError(
220
+ (error) => new ValidationError({
221
+ message: `Invalid Tablinum configuration: ${error.message}`
222
+ })
223
+ )
224
+ );
208
225
  }
209
226
 
210
227
  // src/services/Config.ts
211
228
  import { ServiceMap } from "effect";
212
-
213
- class Config extends ServiceMap.Service()("tablinum/Config") {
214
- }
229
+ var Config = class extends ServiceMap.Service()("tablinum/Config") {
230
+ };
215
231
 
216
232
  // src/services/Tablinum.ts
217
233
  import { ServiceMap as ServiceMap2 } from "effect";
218
-
219
- class Tablinum extends ServiceMap2.Service()("tablinum/Tablinum") {
220
- }
234
+ var Tablinum = class extends ServiceMap2.Service()(
235
+ "tablinum/Tablinum"
236
+ ) {
237
+ };
221
238
 
222
239
  // src/layers/TablinumLive.ts
223
240
  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";
224
241
 
225
242
  // src/crud/watch.ts
226
243
  import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
227
- function watchCollection(ctx, storage, collectionName, filter, mapRecord) {
244
+ function watchCollection(ctx, storage, collectionName, filter, mapRecord2) {
228
245
  const query = () => Effect2.map(storage.getAllRecords(collectionName), (all) => {
229
246
  const filtered = all.filter((r) => !r._d && (filter ? filter(r) : true));
230
- return mapRecord ? filtered.map(mapRecord) : filtered;
247
+ return mapRecord2 ? filtered.map(mapRecord2) : filtered;
231
248
  });
232
- const changes = Stream.fromPubSub(ctx.pubsub).pipe(Stream.filter((event) => event.collection === collectionName), Stream.mapEffect(() => Effect2.gen(function* () {
233
- const replaying = yield* Ref.get(ctx.replayingRef);
234
- if (replaying)
235
- return;
236
- return yield* query();
237
- })), Stream.filter((result) => result !== undefined));
238
- return Stream.unwrap(Effect2.gen(function* () {
239
- yield* Effect2.sleep(0);
240
- const initial = yield* query();
241
- return Stream.concat(Stream.make(initial), changes);
242
- }));
249
+ const changes = Stream.fromPubSub(ctx.pubsub).pipe(
250
+ Stream.filter((event) => event.collection === collectionName),
251
+ Stream.mapEffect(
252
+ () => Effect2.gen(function* () {
253
+ const replaying = yield* Ref.get(ctx.replayingRef);
254
+ if (replaying) return void 0;
255
+ return yield* query();
256
+ })
257
+ ),
258
+ Stream.filter((result) => result !== void 0)
259
+ );
260
+ return Stream.unwrap(
261
+ Effect2.gen(function* () {
262
+ yield* Effect2.sleep(0);
263
+ const initial = yield* query();
264
+ return Stream.concat(Stream.make(initial), changes);
265
+ })
266
+ );
243
267
  }
244
268
  function notifyChange(ctx, event) {
245
269
  return PubSub.publish(ctx.pubsub, event).pipe(Effect2.asVoid);
@@ -262,12 +286,9 @@ import { Effect as Effect3 } from "effect";
262
286
 
263
287
  // src/storage/lww.ts
264
288
  function resolveWinner(existing, incoming) {
265
- if (existing === null)
266
- return incoming;
267
- if (incoming.createdAt > existing.createdAt)
268
- return incoming;
269
- if (incoming.createdAt < existing.createdAt)
270
- return existing;
289
+ if (existing === null) return incoming;
290
+ if (incoming.createdAt > existing.createdAt) return incoming;
291
+ if (incoming.createdAt < existing.createdAt) return existing;
271
292
  return incoming.id < existing.id ? incoming : existing;
272
293
  }
273
294
 
@@ -334,8 +355,7 @@ function applyEvent(storage, event) {
334
355
  };
335
356
  const incomingMeta = { id: event.id, createdAt: event.createdAt };
336
357
  const winner = resolveWinner(existingMeta, incomingMeta);
337
- if (winner.id !== event.id)
338
- return false;
358
+ if (winner.id !== event.id) return false;
339
359
  }
340
360
  if (existing && event.kind === "u") {
341
361
  yield* storage.putRecord(event.collection, deepMerge(existing, buildRecord(event)));
@@ -351,10 +371,11 @@ function rebuild(storage, collections) {
351
371
  yield* storage.clearRecords(col);
352
372
  }
353
373
  const allEvents = yield* storage.getAllEvents();
354
- const sorted = [...allEvents].sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1));
374
+ const sorted = [...allEvents].sort(
375
+ (a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1)
376
+ );
355
377
  for (const event of sorted) {
356
- if (event.data === null && event.kind !== "d")
357
- continue;
378
+ if (event.data === null && event.kind !== "d") continue;
358
379
  yield* applyEvent(storage, event);
359
380
  }
360
381
  });
@@ -407,9 +428,16 @@ function buildStructSchema(def, options = {}) {
407
428
  }
408
429
  function buildValidator(collectionName, def) {
409
430
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { includeId: true }));
410
- return (input) => decode(input).pipe(Effect4.map((result) => result), Effect4.mapError((e) => new ValidationError({
411
- message: `Validation failed for collection "${collectionName}": ${e.message}`
412
- })));
431
+ return (input) => decode(input).pipe(
432
+ Effect4.map(
433
+ (result) => result
434
+ ),
435
+ Effect4.mapError(
436
+ (e) => new ValidationError({
437
+ message: `Validation failed for collection "${collectionName}": ${e.message}`
438
+ })
439
+ )
440
+ );
413
441
  }
414
442
  function buildPartialValidator(collectionName, def) {
415
443
  const decode = Schema3.decodeUnknownEffect(buildStructSchema(def, { allOptional: true }));
@@ -421,15 +449,20 @@ function buildPartialValidator(collectionName, def) {
421
449
  }
422
450
  const record = input;
423
451
  const unknownField = Object.keys(record).find((key) => !Object.hasOwn(def.fields, key));
424
- if (unknownField !== undefined) {
452
+ if (unknownField !== void 0) {
425
453
  return yield* new ValidationError({
426
454
  message: `Unknown field "${unknownField}" in collection "${collectionName}"`,
427
455
  field: unknownField
428
456
  });
429
457
  }
430
- return yield* decode(record).pipe(Effect4.map((result) => result), Effect4.mapError((e) => new ValidationError({
431
- message: `Validation failed for collection "${collectionName}": ${e.message}`
432
- })));
458
+ return yield* decode(record).pipe(
459
+ Effect4.map((result) => result),
460
+ Effect4.mapError(
461
+ (e) => new ValidationError({
462
+ message: `Validation failed for collection "${collectionName}": ${e.message}`
463
+ })
464
+ )
465
+ );
433
466
  });
434
467
  }
435
468
 
@@ -440,16 +473,14 @@ import { Effect as Effect6, Option as Option3, References } from "effect";
440
473
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
441
474
  function toBase64url(bytes) {
442
475
  let result = "";
443
- for (let i = 0;i < bytes.length; i += 3) {
476
+ for (let i = 0; i < bytes.length; i += 3) {
444
477
  const b0 = bytes[i];
445
478
  const b1 = bytes[i + 1] ?? 0;
446
479
  const b2 = bytes[i + 2] ?? 0;
447
480
  result += alphabet[b0 >> 2];
448
481
  result += alphabet[(b0 & 3) << 4 | b1 >> 4];
449
- if (i + 1 < bytes.length)
450
- result += alphabet[(b1 & 15) << 2 | b2 >> 6];
451
- if (i + 2 < bytes.length)
452
- result += alphabet[b2 & 63];
482
+ if (i + 1 < bytes.length) result += alphabet[(b1 & 15) << 2 | b2 >> 6];
483
+ if (i + 2 < bytes.length) result += alphabet[b2 & 63];
453
484
  }
454
485
  return result;
455
486
  }
@@ -494,16 +525,28 @@ function executeQuery(ctx, plan) {
494
525
  if (plan.indexQuery && ctx.def.indices.includes(plan.indexQuery.field)) {
495
526
  if (plan.indexQuery.type === "value") {
496
527
  results = [
497
- ...yield* ctx.storage.getByIndex(ctx.collectionName, plan.indexQuery.field, plan.indexQuery.range)
528
+ ...yield* ctx.storage.getByIndex(
529
+ ctx.collectionName,
530
+ plan.indexQuery.field,
531
+ plan.indexQuery.range
532
+ )
498
533
  ];
499
534
  } else {
500
535
  results = [
501
- ...yield* ctx.storage.getByIndexRange(ctx.collectionName, plan.indexQuery.field, plan.indexQuery.range)
536
+ ...yield* ctx.storage.getByIndexRange(
537
+ ctx.collectionName,
538
+ plan.indexQuery.field,
539
+ plan.indexQuery.range
540
+ )
502
541
  ];
503
542
  }
504
543
  } else if (plan.orderBy && ctx.def.indices.includes(plan.orderBy.field) && plan.filters.length === 0) {
505
544
  results = [
506
- ...yield* ctx.storage.getAllSorted(ctx.collectionName, plan.orderBy.field, plan.orderBy.direction === "desc" ? "prev" : "next")
545
+ ...yield* ctx.storage.getAllSorted(
546
+ ctx.collectionName,
547
+ plan.orderBy.field,
548
+ plan.orderBy.direction === "desc" ? "prev" : "next"
549
+ )
507
550
  ];
508
551
  } else {
509
552
  results = [...yield* ctx.storage.getAllRecords(ctx.collectionName)];
@@ -527,7 +570,7 @@ function executeQuery(ctx, plan) {
527
570
  if (plan.offset) {
528
571
  results = results.slice(plan.offset);
529
572
  }
530
- if (plan.limit !== null && plan.limit !== undefined) {
573
+ if (plan.limit !== null && plan.limit !== void 0) {
531
574
  results = results.slice(0, plan.limit);
532
575
  }
533
576
  return results.map(ctx.mapRecord);
@@ -535,16 +578,23 @@ function executeQuery(ctx, plan) {
535
578
  }
536
579
  function watchQuery(ctx, plan) {
537
580
  const query = () => executeQuery(ctx, plan);
538
- const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(Stream2.filter((event) => event.collection === ctx.collectionName), Stream2.mapEffect(() => Effect5.gen(function* () {
539
- const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
540
- if (replaying)
541
- return;
542
- return yield* query();
543
- })), Stream2.filter((result) => result !== undefined));
544
- return Stream2.unwrap(Effect5.gen(function* () {
545
- const initial = yield* query();
546
- return Stream2.concat(Stream2.make(initial), changes);
547
- }));
581
+ const changes = Stream2.fromPubSub(ctx.watchCtx.pubsub).pipe(
582
+ Stream2.filter((event) => event.collection === ctx.collectionName),
583
+ Stream2.mapEffect(
584
+ () => Effect5.gen(function* () {
585
+ const replaying = yield* Ref2.get(ctx.watchCtx.replayingRef);
586
+ if (replaying) return void 0;
587
+ return yield* query();
588
+ })
589
+ ),
590
+ Stream2.filter((result) => result !== void 0)
591
+ );
592
+ return Stream2.unwrap(
593
+ Effect5.gen(function* () {
594
+ const initial = yield* query();
595
+ return Stream2.concat(Stream2.make(initial), changes);
596
+ })
597
+ );
548
598
  }
549
599
  function makeQueryBuilder(ctx, plan) {
550
600
  return {
@@ -566,15 +616,18 @@ function makeQueryBuilder(ctx, plan) {
566
616
  offset: (n) => makeQueryBuilder(ctx, { ...plan, offset: n }),
567
617
  limit: (n) => makeQueryBuilder(ctx, { ...plan, limit: n }),
568
618
  get: () => executeQuery(ctx, plan),
569
- first: () => Effect5.map(executeQuery(ctx, { ...plan, limit: 1 }), (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()),
619
+ first: () => Effect5.map(
620
+ executeQuery(ctx, { ...plan, limit: 1 }),
621
+ (results) => results.length > 0 ? Option2.some(results[0]) : Option2.none()
622
+ ),
570
623
  count: () => Effect5.map(executeQuery(ctx, plan), (results) => results.length),
571
624
  watch: () => watchQuery(ctx, plan)
572
625
  };
573
626
  }
574
- function createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord) {
575
- const ctx = { storage, watchCtx, collectionName, def, mapRecord };
627
+ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
628
+ const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
576
629
  const fieldDef = def.fields[fieldName];
577
- const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !== undefined && fieldDef.kind !== "boolean";
630
+ const isIndexed = def.indices.includes(fieldName) && fieldDef !== null && fieldDef !== void 0 && fieldDef.kind !== "boolean";
578
631
  const withFilter = (filterFn, indexQuery) => {
579
632
  const plan = {
580
633
  ...emptyPlan(),
@@ -585,30 +638,51 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
585
638
  return makeQueryBuilder(ctx, plan);
586
639
  };
587
640
  return {
588
- equals: (value) => withFilter((r) => r[fieldName] === value, isIndexed ? { field: fieldName, range: value, type: "value" } : undefined),
589
- above: (value) => withFilter((r) => r[fieldName] > value, isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, true), type: "range" } : undefined),
590
- aboveOrEqual: (value) => withFilter((r) => r[fieldName] >= value, isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, false), type: "range" } : undefined),
591
- below: (value) => withFilter((r) => r[fieldName] < value, isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, true), type: "range" } : undefined),
592
- belowOrEqual: (value) => withFilter((r) => r[fieldName] <= value, isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, false), type: "range" } : undefined),
641
+ equals: (value) => withFilter(
642
+ (r) => r[fieldName] === value,
643
+ isIndexed ? { field: fieldName, range: value, type: "value" } : void 0
644
+ ),
645
+ above: (value) => withFilter(
646
+ (r) => r[fieldName] > value,
647
+ isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, true), type: "range" } : void 0
648
+ ),
649
+ aboveOrEqual: (value) => withFilter(
650
+ (r) => r[fieldName] >= value,
651
+ isIndexed ? { field: fieldName, range: IDBKeyRange.lowerBound(value, false), type: "range" } : void 0
652
+ ),
653
+ below: (value) => withFilter(
654
+ (r) => r[fieldName] < value,
655
+ isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, true), type: "range" } : void 0
656
+ ),
657
+ belowOrEqual: (value) => withFilter(
658
+ (r) => r[fieldName] <= value,
659
+ isIndexed ? { field: fieldName, range: IDBKeyRange.upperBound(value, false), type: "range" } : void 0
660
+ ),
593
661
  between: (lower, upper, options) => {
594
662
  const includeLower = options?.includeLower ?? true;
595
663
  const includeUpper = options?.includeUpper ?? true;
596
- return withFilter((r) => {
597
- const v = r[fieldName];
598
- const aboveLower = includeLower ? v >= lower : v > lower;
599
- const belowUpper = includeUpper ? v <= upper : v < upper;
600
- return aboveLower && belowUpper;
601
- }, isIndexed ? {
664
+ return withFilter(
665
+ (r) => {
666
+ const v = r[fieldName];
667
+ const aboveLower = includeLower ? v >= lower : v > lower;
668
+ const belowUpper = includeUpper ? v <= upper : v < upper;
669
+ return aboveLower && belowUpper;
670
+ },
671
+ isIndexed ? {
672
+ field: fieldName,
673
+ range: IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper),
674
+ type: "range"
675
+ } : void 0
676
+ );
677
+ },
678
+ startsWith: (prefix) => withFilter(
679
+ (r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix),
680
+ isIndexed ? {
602
681
  field: fieldName,
603
- range: IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper),
682
+ range: IDBKeyRange.bound(prefix, prefix + "\uFFFF", false, false),
604
683
  type: "range"
605
- } : undefined);
606
- },
607
- startsWith: (prefix) => withFilter((r) => typeof r[fieldName] === "string" && r[fieldName].startsWith(prefix), isIndexed ? {
608
- field: fieldName,
609
- range: IDBKeyRange.bound(prefix, prefix + "￿", false, false),
610
- type: "range"
611
- } : undefined),
684
+ } : void 0
685
+ ),
612
686
  anyOf: (values) => {
613
687
  const set = new Set(values);
614
688
  return withFilter((r) => set.has(r[fieldName]));
@@ -619,8 +693,8 @@ function createWhereClause(storage, watchCtx, collectionName, def, fieldName, ma
619
693
  }
620
694
  };
621
695
  }
622
- function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord) {
623
- const ctx = { storage, watchCtx, collectionName, def, mapRecord };
696
+ function createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord2) {
697
+ const ctx = { storage, watchCtx, collectionName, def, mapRecord: mapRecord2 };
624
698
  const plan = {
625
699
  ...emptyPlan(),
626
700
  orderBy: { field: fieldName, direction: "asc" }
@@ -645,7 +719,7 @@ function replayState(recordId, events, stopAtId) {
645
719
  state = deepMerge(state, e.data);
646
720
  }
647
721
  }
648
- if (stopAtId !== undefined && e.id === stopAtId) {
722
+ if (stopAtId !== void 0 && e.id === stopAtId) {
649
723
  break;
650
724
  }
651
725
  }
@@ -663,8 +737,7 @@ function promoteToSnapshot(storage, collection2, recordId, target, allSorted) {
663
737
  function pruneEvents(storage, collection2, recordId, retention) {
664
738
  return Effect6.gen(function* () {
665
739
  const events = yield* storage.getEventsByRecord(collection2, recordId);
666
- if (events.length <= retention)
667
- return;
740
+ if (events.length <= retention) return;
668
741
  const sorted = [...events].sort((a, b) => b.createdAt - a.createdAt || (a.id < b.id ? 1 : -1));
669
742
  const retained = sorted.slice(0, retention);
670
743
  const toStrip = sorted.slice(retention);
@@ -673,8 +746,7 @@ function pruneEvents(storage, collection2, recordId, retention) {
673
746
  yield* promoteToSnapshot(storage, collection2, recordId, oldestRetained, sorted);
674
747
  }
675
748
  for (const e of toStrip) {
676
- if (e.data !== null)
677
- yield* storage.stripEventData(e.id);
749
+ if (e.data !== null) yield* storage.stripEventData(e.id);
678
750
  }
679
751
  });
680
752
  }
@@ -688,8 +760,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
688
760
  const commitEvent = (event) => Effect6.gen(function* () {
689
761
  yield* storage.putEvent(event);
690
762
  yield* applyEvent(storage, event);
691
- if (onWrite)
692
- yield* onWrite(event);
763
+ if (onWrite) yield* onWrite(event);
693
764
  yield* notifyChange(watchCtx, {
694
765
  collection: collectionName,
695
766
  recordId: event.recordId,
@@ -697,70 +768,84 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
697
768
  });
698
769
  });
699
770
  const handle = {
700
- add: (data) => withLog(Effect6.gen(function* () {
701
- const id = uuidv7();
702
- const fullRecord = { id, ...data };
703
- yield* validator(fullRecord);
704
- const event = {
705
- id: makeEventId(),
706
- collection: collectionName,
707
- recordId: id,
708
- kind: "c",
709
- data: fullRecord,
710
- createdAt: Date.now(),
711
- author: localAuthor
712
- };
713
- yield* commitEvent(event);
714
- yield* Effect6.logDebug("Record added", { collection: collectionName, recordId: id, data: fullRecord });
715
- return id;
716
- })),
717
- update: (id, data) => withLog(Effect6.gen(function* () {
718
- const existing = yield* storage.getRecord(collectionName, id);
719
- if (!existing || existing._d) {
720
- return yield* new NotFoundError({
771
+ add: (data) => withLog(
772
+ Effect6.gen(function* () {
773
+ const id = uuidv7();
774
+ const fullRecord = { id, ...data };
775
+ yield* validator(fullRecord);
776
+ const event = {
777
+ id: makeEventId(),
721
778
  collection: collectionName,
722
- id
779
+ recordId: id,
780
+ kind: "c",
781
+ data: fullRecord,
782
+ createdAt: Date.now(),
783
+ author: localAuthor
784
+ };
785
+ yield* commitEvent(event);
786
+ yield* Effect6.logDebug("Record added", {
787
+ collection: collectionName,
788
+ recordId: id,
789
+ data: fullRecord
723
790
  });
724
- }
725
- yield* partialValidator(data);
726
- const { _d, _u, _a, _e, ...existingFields } = existing;
727
- const merged = { ...existingFields, ...data, id };
728
- yield* validator(merged);
729
- const diff = deepDiff(existingFields, merged);
730
- const event = {
731
- id: makeEventId(),
732
- collection: collectionName,
733
- recordId: id,
734
- kind: "u",
735
- data: diff ?? { id },
736
- createdAt: Date.now(),
737
- author: localAuthor
738
- };
739
- yield* commitEvent(event);
740
- yield* Effect6.logDebug("Record updated", { collection: collectionName, recordId: id, data: diff });
741
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
742
- })),
743
- delete: (id) => withLog(Effect6.gen(function* () {
744
- const existing = yield* storage.getRecord(collectionName, id);
745
- if (!existing || existing._d) {
746
- return yield* new NotFoundError({
791
+ return id;
792
+ })
793
+ ),
794
+ update: (id, data) => withLog(
795
+ Effect6.gen(function* () {
796
+ const existing = yield* storage.getRecord(collectionName, id);
797
+ if (!existing || existing._d) {
798
+ return yield* new NotFoundError({
799
+ collection: collectionName,
800
+ id
801
+ });
802
+ }
803
+ yield* partialValidator(data);
804
+ const { _d, _u, _a, _e, ...existingFields } = existing;
805
+ const merged = { ...existingFields, ...data, id };
806
+ yield* validator(merged);
807
+ const diff = deepDiff(existingFields, merged);
808
+ const event = {
809
+ id: makeEventId(),
747
810
  collection: collectionName,
748
- id
811
+ recordId: id,
812
+ kind: "u",
813
+ data: diff ?? { id },
814
+ createdAt: Date.now(),
815
+ author: localAuthor
816
+ };
817
+ yield* commitEvent(event);
818
+ yield* Effect6.logDebug("Record updated", {
819
+ collection: collectionName,
820
+ recordId: id,
821
+ data: diff
749
822
  });
750
- }
751
- const event = {
752
- id: makeEventId(),
753
- collection: collectionName,
754
- recordId: id,
755
- kind: "d",
756
- data: null,
757
- createdAt: Date.now(),
758
- author: localAuthor
759
- };
760
- yield* commitEvent(event);
761
- yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
762
- yield* pruneEvents(storage, collectionName, id, def.eventRetention);
763
- })),
823
+ yield* pruneEvents(storage, collectionName, id, def.eventRetention);
824
+ })
825
+ ),
826
+ delete: (id) => withLog(
827
+ Effect6.gen(function* () {
828
+ const existing = yield* storage.getRecord(collectionName, id);
829
+ if (!existing || existing._d) {
830
+ return yield* new NotFoundError({
831
+ collection: collectionName,
832
+ id
833
+ });
834
+ }
835
+ const event = {
836
+ id: makeEventId(),
837
+ collection: collectionName,
838
+ recordId: id,
839
+ kind: "d",
840
+ data: null,
841
+ createdAt: Date.now(),
842
+ author: localAuthor
843
+ };
844
+ yield* commitEvent(event);
845
+ yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
846
+ yield* pruneEvents(storage, collectionName, id, def.eventRetention);
847
+ })
848
+ ),
764
849
  undo: (id) => Effect6.gen(function* () {
765
850
  const existing = yield* storage.getRecord(collectionName, id);
766
851
  if (!existing) {
@@ -801,15 +886,29 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
801
886
  return found ? Option3.some(mapRecord(found)) : Option3.none();
802
887
  }),
803
888
  count: () => Effect6.map(storage.getAllRecords(collectionName), (all) => all.filter((r) => !r._d).length),
804
- watch: () => watchCollection(watchCtx, storage, collectionName, undefined, mapRecord),
805
- where: (fieldName) => createWhereClause(storage, watchCtx, collectionName, def, fieldName, mapRecord),
806
- orderBy: (fieldName) => createOrderByBuilder(storage, watchCtx, collectionName, def, fieldName, mapRecord)
889
+ watch: () => watchCollection(watchCtx, storage, collectionName, void 0, mapRecord),
890
+ where: (fieldName) => createWhereClause(
891
+ storage,
892
+ watchCtx,
893
+ collectionName,
894
+ def,
895
+ fieldName,
896
+ mapRecord
897
+ ),
898
+ orderBy: (fieldName) => createOrderByBuilder(
899
+ storage,
900
+ watchCtx,
901
+ collectionName,
902
+ def,
903
+ fieldName,
904
+ mapRecord
905
+ )
807
906
  };
808
907
  return handle;
809
908
  }
810
909
 
811
910
  // src/sync/sync-service.ts
812
- import { Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
911
+ import { Duration, Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
813
912
  import { unwrapEvent } from "nostr-tools/nip59";
814
913
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
815
914
 
@@ -825,8 +924,7 @@ var Mode = {
825
924
  Fingerprint: 1,
826
925
  IdList: 2
827
926
  };
828
-
829
- class WrappedBuffer {
927
+ var WrappedBuffer = class {
830
928
  constructor(buffer) {
831
929
  this._raw = new Uint8Array(buffer || 512);
832
930
  this.length = buffer ? buffer.length : 0;
@@ -838,10 +936,8 @@ class WrappedBuffer {
838
936
  return this._raw.byteLength;
839
937
  }
840
938
  extend(buf) {
841
- if (buf._raw)
842
- buf = buf.unwrap();
843
- if (typeof buf.length !== "number")
844
- throw Error("bad length");
939
+ if (buf._raw) buf = buf.unwrap();
940
+ if (typeof buf.length !== "number") throw Error("bad length");
845
941
  const targetSize = buf.length + this.length;
846
942
  if (this.capacity < targetSize) {
847
943
  const oldRaw = this._raw;
@@ -864,42 +960,36 @@ class WrappedBuffer {
864
960
  this.length -= n;
865
961
  return firstSubarray;
866
962
  }
867
- }
963
+ };
868
964
  function decodeVarInt(buf) {
869
965
  let res = 0;
870
- while (true) {
871
- if (buf.length === 0)
872
- throw Error("parse ends prematurely");
966
+ while (1) {
967
+ if (buf.length === 0) throw Error("parse ends prematurely");
873
968
  let byte = buf.shift();
874
969
  res = res << 7 | byte & 127;
875
- if ((byte & 128) === 0)
876
- break;
970
+ if ((byte & 128) === 0) break;
877
971
  }
878
972
  return res;
879
973
  }
880
974
  function encodeVarInt(n) {
881
- if (n === 0)
882
- return new WrappedBuffer([0]);
975
+ if (n === 0) return new WrappedBuffer([0]);
883
976
  let o = [];
884
977
  while (n !== 0) {
885
978
  o.push(n & 127);
886
979
  n >>>= 7;
887
980
  }
888
981
  o.reverse();
889
- for (let i = 0;i < o.length - 1; i++)
890
- o[i] |= 128;
982
+ for (let i = 0; i < o.length - 1; i++) o[i] |= 128;
891
983
  return new WrappedBuffer(o);
892
984
  }
893
985
  function getByte(buf) {
894
986
  return getBytes(buf, 1)[0];
895
987
  }
896
988
  function getBytes(buf, n) {
897
- if (buf.length < n)
898
- throw Error("parse ends prematurely");
989
+ if (buf.length < n) throw Error("parse ends prematurely");
899
990
  return buf.shiftN(n);
900
991
  }
901
-
902
- class Accumulator {
992
+ var Accumulator = class {
903
993
  constructor() {
904
994
  this.setToZero();
905
995
  if (typeof window === "undefined") {
@@ -916,15 +1006,14 @@ class Accumulator {
916
1006
  let currCarry = 0, nextCarry = 0;
917
1007
  let p = new DataView(this.buf.buffer);
918
1008
  let po = new DataView(otherBuf.buffer);
919
- for (let i = 0;i < 8; i++) {
1009
+ for (let i = 0; i < 8; i++) {
920
1010
  let offset = i * 4;
921
1011
  let orig = p.getUint32(offset, true);
922
1012
  let otherV = po.getUint32(offset, true);
923
1013
  let next = orig;
924
1014
  next += currCarry;
925
1015
  next += otherV;
926
- if (next > 4294967295)
927
- nextCarry = 1;
1016
+ if (next > 4294967295) nextCarry = 1;
928
1017
  p.setUint32(offset, next & 4294967295, true);
929
1018
  currCarry = nextCarry;
930
1019
  nextCarry = 0;
@@ -932,7 +1021,7 @@ class Accumulator {
932
1021
  }
933
1022
  negate() {
934
1023
  let p = new DataView(this.buf.buffer);
935
- for (let i = 0;i < 8; i++) {
1024
+ for (let i = 0; i < 8; i++) {
936
1025
  let offset = i * 4;
937
1026
  p.setUint32(offset, ~p.getUint32(offset, true));
938
1027
  }
@@ -941,33 +1030,29 @@ class Accumulator {
941
1030
  this.add(one);
942
1031
  }
943
1032
  async getFingerprint(n) {
944
- let input = new WrappedBuffer;
1033
+ let input = new WrappedBuffer();
945
1034
  input.extend(this.buf);
946
1035
  input.extend(encodeVarInt(n));
947
1036
  let hash = await this.sha256(input.unwrap());
948
1037
  return hash.subarray(0, FINGERPRINT_SIZE);
949
1038
  }
950
- }
951
-
952
- class NegentropyStorageVector {
1039
+ };
1040
+ var NegentropyStorageVector = class {
953
1041
  constructor() {
954
1042
  this.items = [];
955
1043
  this.sealed = false;
956
1044
  }
957
1045
  insert(timestamp, id) {
958
- if (this.sealed)
959
- throw Error("already sealed");
1046
+ if (this.sealed) throw Error("already sealed");
960
1047
  id = loadInputBuffer(id);
961
- if (id.byteLength !== ID_SIZE)
962
- throw Error("bad id size for added item");
1048
+ if (id.byteLength !== ID_SIZE) throw Error("bad id size for added item");
963
1049
  this.items.push({ timestamp, id });
964
1050
  }
965
1051
  seal() {
966
- if (this.sealed)
967
- throw Error("already sealed");
1052
+ if (this.sealed) throw Error("already sealed");
968
1053
  this.sealed = true;
969
1054
  this.items.sort(itemCompare);
970
- for (let i = 1;i < this.items.length; i++) {
1055
+ for (let i = 1; i < this.items.length; i++) {
971
1056
  if (itemCompare(this.items[i - 1], this.items[i]) === 0)
972
1057
  throw Error("duplicate item inserted");
973
1058
  }
@@ -981,16 +1066,14 @@ class NegentropyStorageVector {
981
1066
  }
982
1067
  getItem(i) {
983
1068
  this._checkSealed();
984
- if (i >= this.items.length)
985
- throw Error("out of range");
1069
+ if (i >= this.items.length) throw Error("out of range");
986
1070
  return this.items[i];
987
1071
  }
988
1072
  iterate(begin, end, cb) {
989
1073
  this._checkSealed();
990
1074
  this._checkBounds(begin, end);
991
- for (let i = begin;i < end; ++i) {
992
- if (!cb(this.items[i], i))
993
- break;
1075
+ for (let i = begin; i < end; ++i) {
1076
+ if (!cb(this.items[i], i)) break;
994
1077
  }
995
1078
  }
996
1079
  findLowerBound(begin, end, bound) {
@@ -999,7 +1082,7 @@ class NegentropyStorageVector {
999
1082
  return this._binarySearch(this.items, begin, end, (a) => itemCompare(a, bound) < 0);
1000
1083
  }
1001
1084
  async fingerprint(begin, end) {
1002
- let out = new Accumulator;
1085
+ let out = new Accumulator();
1003
1086
  out.setToZero();
1004
1087
  this.iterate(begin, end, (item, i) => {
1005
1088
  out.add(item.id);
@@ -1008,12 +1091,10 @@ class NegentropyStorageVector {
1008
1091
  return await out.getFingerprint(end - begin);
1009
1092
  }
1010
1093
  _checkSealed() {
1011
- if (!this.sealed)
1012
- throw Error("not sealed");
1094
+ if (!this.sealed) throw Error("not sealed");
1013
1095
  }
1014
1096
  _checkBounds(begin, end) {
1015
- if (begin > end || end > this.items.length)
1016
- throw Error("bad range");
1097
+ if (begin > end || end > this.items.length) throw Error("bad range");
1017
1098
  }
1018
1099
  _binarySearch(arr, first, last, cmp) {
1019
1100
  let count = last - first;
@@ -1030,12 +1111,10 @@ class NegentropyStorageVector {
1030
1111
  }
1031
1112
  return first;
1032
1113
  }
1033
- }
1034
-
1035
- class Negentropy {
1114
+ };
1115
+ var Negentropy = class {
1036
1116
  constructor(storage, frameSizeLimit = 0) {
1037
- if (frameSizeLimit !== 0 && frameSizeLimit < 4096)
1038
- throw Error("frameSizeLimit too small");
1117
+ if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small");
1039
1118
  this.storage = storage;
1040
1119
  this.frameSizeLimit = frameSizeLimit;
1041
1120
  this.lastTimestampIn = 0;
@@ -1045,10 +1124,9 @@ class Negentropy {
1045
1124
  return { timestamp, id: id ? id : new Uint8Array(0) };
1046
1125
  }
1047
1126
  async initiate() {
1048
- if (this.isInitiator)
1049
- throw Error("already initiated");
1127
+ if (this.isInitiator) throw Error("already initiated");
1050
1128
  this.isInitiator = true;
1051
- let output = new WrappedBuffer;
1129
+ let output = new WrappedBuffer();
1052
1130
  output.extend([PROTOCOL_VERSION]);
1053
1131
  await this.splitRange(0, this.storage.size(), this._bound(Number.MAX_VALUE), output);
1054
1132
  return this._renderOutput(output);
@@ -1060,23 +1138,24 @@ class Negentropy {
1060
1138
  let haveIds = [], needIds = [];
1061
1139
  query = new WrappedBuffer(loadInputBuffer(query));
1062
1140
  this.lastTimestampIn = this.lastTimestampOut = 0;
1063
- let fullOutput = new WrappedBuffer;
1141
+ let fullOutput = new WrappedBuffer();
1064
1142
  fullOutput.extend([PROTOCOL_VERSION]);
1065
1143
  let protocolVersion = getByte(query);
1066
1144
  if (protocolVersion < 96 || protocolVersion > 111)
1067
1145
  throw Error("invalid negentropy protocol version byte");
1068
1146
  if (protocolVersion !== PROTOCOL_VERSION) {
1069
1147
  if (this.isInitiator)
1070
- throw Error("unsupported negentropy protocol version requested: " + (protocolVersion - 96));
1071
- else
1072
- return [this._renderOutput(fullOutput), haveIds, needIds];
1148
+ throw Error(
1149
+ "unsupported negentropy protocol version requested: " + (protocolVersion - 96)
1150
+ );
1151
+ else return [this._renderOutput(fullOutput), haveIds, needIds];
1073
1152
  }
1074
1153
  let storageSize = this.storage.size();
1075
1154
  let prevBound = this._bound(0);
1076
1155
  let prevIndex = 0;
1077
1156
  let skip = false;
1078
1157
  while (query.length !== 0) {
1079
- let o = new WrappedBuffer;
1158
+ let o = new WrappedBuffer();
1080
1159
  let doSkip = () => {
1081
1160
  if (skip) {
1082
1161
  skip = false;
@@ -1102,10 +1181,9 @@ class Negentropy {
1102
1181
  } else if (mode === Mode.IdList) {
1103
1182
  let numIds = decodeVarInt(query);
1104
1183
  let theirElems = {};
1105
- for (let i = 0;i < numIds; i++) {
1184
+ for (let i = 0; i < numIds; i++) {
1106
1185
  let e = getBytes(query, ID_SIZE);
1107
- if (this.isInitiator)
1108
- theirElems[e] = e;
1186
+ if (this.isInitiator) theirElems[e] = e;
1109
1187
  }
1110
1188
  if (this.isInitiator) {
1111
1189
  skip = true;
@@ -1124,7 +1202,7 @@ class Negentropy {
1124
1202
  }
1125
1203
  } else {
1126
1204
  doSkip();
1127
- let responseIds = new WrappedBuffer;
1205
+ let responseIds = new WrappedBuffer();
1128
1206
  let numResponseIds = 0;
1129
1207
  let endBound = currBound;
1130
1208
  this.storage.iterate(lower, upper, (item, index) => {
@@ -1142,7 +1220,7 @@ class Negentropy {
1142
1220
  o.extend(encodeVarInt(numResponseIds));
1143
1221
  o.extend(responseIds);
1144
1222
  fullOutput.extend(o);
1145
- o = new WrappedBuffer;
1223
+ o = new WrappedBuffer();
1146
1224
  }
1147
1225
  } else {
1148
1226
  throw Error("unexpected mode");
@@ -1180,7 +1258,7 @@ class Negentropy {
1180
1258
  let itemsPerBucket = Math.floor(numElems / buckets);
1181
1259
  let bucketsWithExtra = numElems % buckets;
1182
1260
  let curr = lower;
1183
- for (let i = 0;i < buckets; i++) {
1261
+ for (let i = 0; i < buckets; i++) {
1184
1262
  let bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);
1185
1263
  let ourFingerprint = await this.storage.fingerprint(curr, curr + bucketSize);
1186
1264
  curr += bucketSize;
@@ -1190,10 +1268,8 @@ class Negentropy {
1190
1268
  } else {
1191
1269
  let prevItem, currItem;
1192
1270
  this.storage.iterate(curr - 1, curr + 1, (item, index) => {
1193
- if (index === curr - 1)
1194
- prevItem = item;
1195
- else
1196
- currItem = item;
1271
+ if (index === curr - 1) prevItem = item;
1272
+ else currItem = item;
1197
1273
  return true;
1198
1274
  });
1199
1275
  nextBound = this.getMinimalBound(prevItem, currItem);
@@ -1206,13 +1282,13 @@ class Negentropy {
1206
1282
  }
1207
1283
  _renderOutput(o) {
1208
1284
  o = o.unwrap();
1209
- if (!this.wantUint8ArrayOutput)
1210
- o = uint8ArrayToHex(o);
1285
+ if (!this.wantUint8ArrayOutput) o = uint8ArrayToHex(o);
1211
1286
  return o;
1212
1287
  }
1213
1288
  exceededFrameSizeLimit(n) {
1214
1289
  return this.frameSizeLimit && n > this.frameSizeLimit - 200;
1215
1290
  }
1291
+ // Decoding
1216
1292
  decodeTimestampIn(encoded) {
1217
1293
  let timestamp = decodeVarInt(encoded);
1218
1294
  timestamp = timestamp === 0 ? Number.MAX_VALUE : timestamp - 1;
@@ -1227,11 +1303,11 @@ class Negentropy {
1227
1303
  decodeBound(encoded) {
1228
1304
  let timestamp = this.decodeTimestampIn(encoded);
1229
1305
  let len = decodeVarInt(encoded);
1230
- if (len > ID_SIZE)
1231
- throw Error("bound key too long");
1306
+ if (len > ID_SIZE) throw Error("bound key too long");
1232
1307
  let id = getBytes(encoded, len);
1233
1308
  return { timestamp, id };
1234
1309
  }
1310
+ // Encoding
1235
1311
  encodeTimestampOut(timestamp) {
1236
1312
  if (timestamp === Number.MAX_VALUE) {
1237
1313
  this.lastTimestampOut = Number.MAX_VALUE;
@@ -1243,7 +1319,7 @@ class Negentropy {
1243
1319
  return encodeVarInt(timestamp + 1);
1244
1320
  }
1245
1321
  encodeBound(key) {
1246
- let output = new WrappedBuffer;
1322
+ let output = new WrappedBuffer();
1247
1323
  output.extend(this.encodeTimestampOut(key.timestamp));
1248
1324
  output.extend(encodeVarInt(key.id.length));
1249
1325
  output.extend(key.id);
@@ -1256,30 +1332,24 @@ class Negentropy {
1256
1332
  let sharedPrefixBytes = 0;
1257
1333
  let currKey = curr.id;
1258
1334
  let prevKey = prev.id;
1259
- for (let i = 0;i < ID_SIZE; i++) {
1260
- if (currKey[i] !== prevKey[i])
1261
- break;
1335
+ for (let i = 0; i < ID_SIZE; i++) {
1336
+ if (currKey[i] !== prevKey[i]) break;
1262
1337
  sharedPrefixBytes++;
1263
1338
  }
1264
1339
  return this._bound(curr.timestamp, curr.id.subarray(0, sharedPrefixBytes + 1));
1265
1340
  }
1266
1341
  }
1267
- }
1342
+ };
1268
1343
  function loadInputBuffer(inp) {
1269
- if (typeof inp === "string")
1270
- inp = hexToUint8Array(inp);
1271
- else if (__proto__ !== Uint8Array.prototype)
1272
- inp = new Uint8Array(inp);
1344
+ if (typeof inp === "string") inp = hexToUint8Array(inp);
1345
+ else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp);
1273
1346
  return inp;
1274
1347
  }
1275
1348
  function hexToUint8Array(h) {
1276
- if (h.startsWith("0x"))
1277
- h = h.substr(2);
1278
- if (h.length % 2 === 1)
1279
- throw Error("odd length of hex string");
1349
+ if (h.startsWith("0x")) h = h.substr(2);
1350
+ if (h.length % 2 === 1) throw Error("odd length of hex string");
1280
1351
  let arr = new Uint8Array(h.length / 2);
1281
- for (let i = 0;i < arr.length; i++)
1282
- arr[i] = parseInt(h.substr(i * 2, 2), 16);
1352
+ for (let i = 0; i < arr.length; i++) arr[i] = parseInt(h.substr(i * 2, 2), 16);
1283
1353
  return arr;
1284
1354
  }
1285
1355
  var uint8ArrayToHexLookupTable = new Array(256);
@@ -1302,28 +1372,24 @@ var uint8ArrayToHexLookupTable = new Array(256);
1302
1372
  "e",
1303
1373
  "f"
1304
1374
  ];
1305
- for (let i = 0;i < 256; i++) {
1375
+ for (let i = 0; i < 256; i++) {
1306
1376
  uint8ArrayToHexLookupTable[i] = hexAlphabet[i >>> 4 & 15] + hexAlphabet[i & 15];
1307
1377
  }
1308
1378
  }
1309
1379
  function uint8ArrayToHex(arr) {
1310
1380
  let out = "";
1311
- for (let i = 0, edx = arr.length;i < edx; i++) {
1381
+ for (let i = 0, edx = arr.length; i < edx; i++) {
1312
1382
  out += uint8ArrayToHexLookupTable[arr[i]];
1313
1383
  }
1314
1384
  return out;
1315
1385
  }
1316
1386
  function compareUint8Array(a, b) {
1317
- for (let i = 0;i < a.byteLength; i++) {
1318
- if (a[i] < b[i])
1319
- return -1;
1320
- if (a[i] > b[i])
1321
- return 1;
1322
- }
1323
- if (a.byteLength > b.byteLength)
1324
- return 1;
1325
- if (a.byteLength < b.byteLength)
1326
- return -1;
1387
+ for (let i = 0; i < a.byteLength; i++) {
1388
+ if (a[i] < b[i]) return -1;
1389
+ if (a[i] > b[i]) return 1;
1390
+ }
1391
+ if (a.byteLength > b.byteLength) return 1;
1392
+ if (a.byteLength < b.byteLength) return -1;
1327
1393
  return 0;
1328
1394
  }
1329
1395
  function itemCompare(a, b) {
@@ -1339,7 +1405,7 @@ import { GiftWrap } from "nostr-tools/kinds";
1339
1405
  function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1340
1406
  return Effect7.gen(function* () {
1341
1407
  const allGiftWraps = yield* storage.getAllGiftWraps();
1342
- const storageVector = new NegentropyStorageVector;
1408
+ const storageVector = new NegentropyStorageVector();
1343
1409
  for (const gw of allGiftWraps) {
1344
1410
  storageVector.insert(gw.createdAt, hexToBytes2(gw.id));
1345
1411
  }
@@ -1363,8 +1429,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1363
1429
  let currentMsg = initialMsg;
1364
1430
  while (currentMsg !== null) {
1365
1431
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1366
- if (response.msgHex === null)
1367
- break;
1432
+ if (response.msgHex === null) break;
1368
1433
  const reconcileResult = yield* Effect7.try({
1369
1434
  try: () => neg.reconcile(response.msgHex),
1370
1435
  catch: (e) => new SyncError({
@@ -1374,10 +1439,8 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1374
1439
  })
1375
1440
  });
1376
1441
  const [nextMsg, haveIds, needIds] = reconcileResult;
1377
- for (const id of haveIds)
1378
- allHaveIds.push(id);
1379
- for (const id of needIds)
1380
- allNeedIds.push(id);
1442
+ for (const id of haveIds) allHaveIds.push(id);
1443
+ for (const id of needIds) allNeedIds.push(id);
1381
1444
  currentMsg = nextMsg;
1382
1445
  }
1383
1446
  yield* Effect7.logDebug("Negentropy reconciliation complete", {
@@ -1425,12 +1488,11 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1425
1488
  kind: 1,
1426
1489
  content: JSON.stringify(rotationData),
1427
1490
  tags: [["d", `_system:rotation:${epochId}`]],
1428
- created_at: Math.floor(Date.now() / 1000)
1491
+ created_at: Math.floor(Date.now() / 1e3)
1429
1492
  };
1430
1493
  const wrappedEvents = [];
1431
1494
  for (const memberPubkey of remainingMemberPubkeys) {
1432
- if (memberPubkey === senderPublicKey)
1433
- continue;
1495
+ if (memberPubkey === senderPublicKey) continue;
1434
1496
  const wrapped = wrapEvent(rumor, senderPrivateKey, memberPubkey);
1435
1497
  wrappedEvents.push(wrapped);
1436
1498
  }
@@ -1443,7 +1505,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1443
1505
  kind: 1,
1444
1506
  content: JSON.stringify(removalData),
1445
1507
  tags: [["d", `_system:removed:${epochId}`]],
1446
- created_at: Math.floor(Date.now() / 1000)
1508
+ created_at: Math.floor(Date.now() / 1e3)
1447
1509
  };
1448
1510
  const removalNotices = [];
1449
1511
  for (const removedPubkey of removedMemberPubkeys) {
@@ -1453,8 +1515,7 @@ function createRotation(epochStore, senderPrivateKey, senderPublicKey, remaining
1453
1515
  return { epoch, wrappedEvents, removalNotices };
1454
1516
  }
1455
1517
  function parseRotationEvent(content, dTag) {
1456
- if (!dTag.startsWith("_system:rotation:"))
1457
- return Option4.none();
1518
+ if (!dTag.startsWith("_system:rotation:")) return Option4.none();
1458
1519
  try {
1459
1520
  return Option4.some(decodeRotationData(content));
1460
1521
  } catch {
@@ -1462,8 +1523,7 @@ function parseRotationEvent(content, dTag) {
1462
1523
  }
1463
1524
  }
1464
1525
  function parseRemovalNotice(content, dTag) {
1465
- if (!dTag.startsWith("_system:removed:"))
1466
- return Option4.none();
1526
+ if (!dTag.startsWith("_system:removed:")) return Option4.none();
1467
1527
  try {
1468
1528
  return Option4.some(decodeRemovalNotice(content));
1469
1529
  } catch {
@@ -1483,65 +1543,79 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1483
1543
  kind: "create"
1484
1544
  });
1485
1545
  const forkHandled = (effect) => {
1486
- Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.provide(logLayer), Effect8.forkIn(scope)));
1546
+ Effect8.runFork(
1547
+ effect.pipe(
1548
+ Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))),
1549
+ Effect8.ignore,
1550
+ Effect8.provide(logLayer),
1551
+ Effect8.forkIn(scope)
1552
+ )
1553
+ );
1487
1554
  };
1488
1555
  let autoFlushActive = false;
1489
1556
  const autoFlushEffect = Effect8.gen(function* () {
1490
1557
  const size = yield* publishQueue.size();
1491
- if (size === 0)
1492
- return;
1558
+ if (size === 0) return;
1493
1559
  yield* syncStatus.set("syncing");
1494
1560
  yield* publishQueue.flush(relayUrls);
1495
1561
  const remaining = yield* publishQueue.size();
1496
- if (remaining > 0)
1497
- yield* Effect8.fail("pending");
1498
- }).pipe(Effect8.ensuring(syncStatus.set("idle")), Effect8.retry({ schedule: Schedule.exponential(5000).pipe(Schedule.jittered), times: 10 }), Effect8.ignore);
1562
+ if (remaining > 0) yield* Effect8.fail("pending");
1563
+ }).pipe(
1564
+ Effect8.ensuring(syncStatus.set("idle")),
1565
+ Effect8.retry({ schedule: Schedule.exponential(5e3).pipe(Schedule.jittered), times: 10 }),
1566
+ Effect8.ignore
1567
+ );
1499
1568
  const scheduleAutoFlush = () => {
1500
- if (autoFlushActive)
1501
- return;
1569
+ if (autoFlushActive) return;
1502
1570
  autoFlushActive = true;
1503
- forkHandled(autoFlushEffect.pipe(Effect8.ensuring(Effect8.sync(() => {
1504
- autoFlushActive = false;
1505
- }))));
1571
+ forkHandled(
1572
+ autoFlushEffect.pipe(
1573
+ Effect8.ensuring(
1574
+ Effect8.sync(() => {
1575
+ autoFlushActive = false;
1576
+ })
1577
+ )
1578
+ )
1579
+ );
1506
1580
  };
1507
1581
  const shouldRejectWrite = (authorPubkey) => Effect8.gen(function* () {
1508
1582
  const memberRecord = yield* storage.getRecord("_members", authorPubkey);
1509
- if (!memberRecord)
1510
- return false;
1583
+ if (!memberRecord) return false;
1511
1584
  return !!memberRecord.removedAt;
1512
1585
  });
1513
1586
  const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1514
1587
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1515
- if (existing)
1516
- return null;
1588
+ if (existing) return null;
1517
1589
  const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
1518
1590
  if (unwrapResult._tag === "Failure") {
1519
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1591
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1520
1592
  return null;
1521
1593
  }
1522
1594
  const rumor = unwrapResult.success;
1523
1595
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1524
1596
  if (!dTag) {
1525
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1597
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1526
1598
  return null;
1527
1599
  }
1528
1600
  const colonIdx = dTag.indexOf(":");
1529
1601
  if (colonIdx === -1) {
1530
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1602
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1531
1603
  return null;
1532
1604
  }
1533
1605
  const collectionName = dTag.substring(0, colonIdx);
1534
1606
  const recordId = dTag.substring(colonIdx + 1);
1535
1607
  const retention = knownCollections.get(collectionName);
1536
- if (retention === undefined) {
1537
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1608
+ if (retention === void 0) {
1609
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1538
1610
  return null;
1539
1611
  }
1540
1612
  if (rumor.pubkey) {
1541
1613
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1542
1614
  if (reject) {
1543
- yield* Effect8.logWarning("Rejected write from removed member", { author: rumor.pubkey.slice(0, 12) });
1544
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1615
+ yield* Effect8.logWarning("Rejected write from removed member", {
1616
+ author: rumor.pubkey.slice(0, 12)
1617
+ });
1618
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1545
1619
  return null;
1546
1620
  }
1547
1621
  }
@@ -1549,14 +1623,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1549
1623
  let kind = "u";
1550
1624
  const parsed = yield* Effect8.try({
1551
1625
  try: () => JSON.parse(rumor.content),
1552
- catch: () => {
1553
- return;
1554
- }
1555
- }).pipe(Effect8.orElseSucceed(() => {
1556
- return;
1557
- }));
1558
- if (parsed === undefined) {
1559
- yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1626
+ catch: () => void 0
1627
+ }).pipe(Effect8.orElseSucceed(() => void 0));
1628
+ if (parsed === void 0) {
1629
+ yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1560
1630
  return null;
1561
1631
  }
1562
1632
  if (parsed === null || parsed._deleted) {
@@ -1564,18 +1634,19 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1564
1634
  } else {
1565
1635
  data = parsed;
1566
1636
  }
1567
- const author = rumor.pubkey || undefined;
1637
+ const author = rumor.pubkey || void 0;
1568
1638
  const event = {
1569
1639
  id: rumor.id,
1570
1640
  collection: collectionName,
1571
1641
  recordId,
1572
1642
  kind,
1573
1643
  data,
1574
- createdAt: rumor.created_at * 1000,
1644
+ createdAt: rumor.created_at * 1e3,
1575
1645
  author
1576
1646
  };
1577
1647
  yield* storage.putGiftWrap({
1578
1648
  id: remoteGw.id,
1649
+ event: remoteGw,
1579
1650
  createdAt: remoteGw.created_at
1580
1651
  });
1581
1652
  yield* storage.putEvent(event);
@@ -1583,7 +1654,12 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1583
1654
  if (didApply && (kind === "u" || kind === "d")) {
1584
1655
  yield* pruneEvents(storage, collectionName, recordId, retention);
1585
1656
  }
1586
- yield* Effect8.logDebug("Processed gift wrap", { collection: collectionName, recordId, kind, author: author?.slice(0, 12) });
1657
+ yield* Effect8.logDebug("Processed gift wrap", {
1658
+ collection: collectionName,
1659
+ recordId,
1660
+ kind,
1661
+ author: author?.slice(0, 12)
1662
+ });
1587
1663
  if (author && onNewAuthor) {
1588
1664
  onNewAuthor(author);
1589
1665
  }
@@ -1596,32 +1672,34 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1596
1672
  }
1597
1673
  });
1598
1674
  const processRotationGiftWrap = (remoteGw) => Effect8.gen(function* () {
1599
- const unwrapResult = yield* Effect8.result(Effect8.try({
1600
- try: () => unwrapEvent(remoteGw, personalPrivateKey),
1601
- catch: (e) => new CryptoError({
1602
- message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
1603
- cause: e
1675
+ const unwrapResult = yield* Effect8.result(
1676
+ Effect8.try({
1677
+ try: () => unwrapEvent(remoteGw, personalPrivateKey),
1678
+ catch: (e) => new CryptoError({
1679
+ message: `Rotation unwrap failed: ${e instanceof Error ? e.message : String(e)}`,
1680
+ cause: e
1681
+ })
1604
1682
  })
1605
- }));
1606
- if (unwrapResult._tag === "Failure")
1607
- return false;
1683
+ );
1684
+ if (unwrapResult._tag === "Failure") return false;
1608
1685
  const rumor = unwrapResult.success;
1609
1686
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1610
- if (!dTag)
1611
- return false;
1687
+ if (!dTag) return false;
1612
1688
  const removalNoticeOpt = parseRemovalNotice(rumor.content, dTag);
1613
1689
  if (Option5.isSome(removalNoticeOpt)) {
1614
- if (onRemoved)
1615
- onRemoved(removalNoticeOpt.value);
1690
+ if (onRemoved) onRemoved(removalNoticeOpt.value);
1616
1691
  return true;
1617
1692
  }
1618
1693
  const rotationDataOpt = parseRotationEvent(rumor.content, dTag);
1619
- if (Option5.isNone(rotationDataOpt))
1620
- return false;
1694
+ if (Option5.isNone(rotationDataOpt)) return false;
1621
1695
  const rotationData = rotationDataOpt.value;
1622
- if (epochStore.epochs.has(rotationData.epochId))
1623
- return false;
1624
- const epoch = createEpochKey(rotationData.epochId, rotationData.epochKey, rumor.pubkey || "", rotationData.parentEpoch);
1696
+ if (epochStore.epochs.has(rotationData.epochId)) return false;
1697
+ const epoch = createEpochKey(
1698
+ rotationData.epochId,
1699
+ rotationData.epochKey,
1700
+ rumor.pubkey || "",
1701
+ rotationData.parentEpoch
1702
+ );
1625
1703
  addEpoch(epochStore, epoch);
1626
1704
  epochStore.currentEpochId = epoch.id;
1627
1705
  let membersChanged = false;
@@ -1641,79 +1719,158 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1641
1719
  membersChanged = true;
1642
1720
  }
1643
1721
  }
1644
- if (membersChanged && onMembersChanged)
1645
- onMembersChanged();
1722
+ if (membersChanged && onMembersChanged) onMembersChanged();
1646
1723
  yield* handle.addEpochSubscription(epoch.publicKey);
1647
1724
  return true;
1648
1725
  });
1649
- const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(relayUrls, (url) => Effect8.gen(function* () {
1650
- yield* relay.subscribe(filter, url, (event) => {
1651
- forkHandled(onEvent(event));
1652
- }).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1653
- }), { discard: true });
1726
+ const subscribeAcrossRelays = (filter, onEvent) => Effect8.forEach(
1727
+ relayUrls,
1728
+ (url) => Effect8.gen(function* () {
1729
+ yield* relay.subscribe(filter, url, (event) => {
1730
+ forkHandled(onEvent(event));
1731
+ }).pipe(
1732
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1733
+ Effect8.ignore
1734
+ );
1735
+ }),
1736
+ { discard: true }
1737
+ );
1654
1738
  const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1655
1739
  yield* Effect8.logDebug("Syncing relay", { relay: url });
1656
- const reconcileResult = yield* Effect8.result(reconcileWithRelay(storage, relay, url, Array.from(pubKeys)));
1740
+ const reconcileResult = yield* Effect8.result(
1741
+ reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
1742
+ );
1657
1743
  if (reconcileResult._tag === "Failure") {
1658
1744
  onSyncError?.(reconcileResult.failure);
1659
1745
  return;
1660
1746
  }
1661
1747
  const { haveIds, needIds } = reconcileResult.success;
1662
- yield* Effect8.logDebug("Relay reconciliation result", { relay: url, need: needIds.length, have: haveIds.length });
1748
+ yield* Effect8.logDebug("Relay reconciliation result", {
1749
+ relay: url,
1750
+ need: needIds.length,
1751
+ have: haveIds.length
1752
+ });
1663
1753
  if (needIds.length > 0) {
1664
- const fetched = yield* relay.fetchEvents(needIds, url).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.orElseSucceed(() => []));
1754
+ const fetched = yield* relay.fetchEvents(needIds, url).pipe(
1755
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1756
+ Effect8.orElseSucceed(() => [])
1757
+ );
1665
1758
  const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
1666
- yield* Effect8.forEach(sorted, (remoteGw) => Effect8.gen(function* () {
1667
- const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
1668
- if (collection2)
1669
- changedCollections.add(collection2);
1670
- }), { discard: true });
1759
+ yield* Effect8.forEach(
1760
+ sorted,
1761
+ (remoteGw) => Effect8.gen(function* () {
1762
+ const collection2 = yield* processGiftWrap(remoteGw).pipe(
1763
+ Effect8.orElseSucceed(() => null)
1764
+ );
1765
+ if (collection2) changedCollections.add(collection2);
1766
+ }),
1767
+ { discard: true }
1768
+ );
1671
1769
  }
1672
1770
  if (haveIds.length > 0) {
1673
- yield* Effect8.forEach(haveIds, (id) => Effect8.gen(function* () {
1674
- const gw = yield* storage.getGiftWrap(id);
1675
- if (!gw?.event)
1676
- return;
1677
- yield* relay.publish(gw.event, [url]).pipe(Effect8.andThen(storage.stripGiftWrapBlob(id)), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1678
- }), { discard: true });
1771
+ yield* Effect8.forEach(
1772
+ haveIds,
1773
+ (id) => Effect8.gen(function* () {
1774
+ const gw = yield* storage.getGiftWrap(id);
1775
+ if (!gw?.event) return;
1776
+ yield* relay.publish(gw.event, [url]).pipe(
1777
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1778
+ Effect8.ignore
1779
+ );
1780
+ }),
1781
+ { discard: true }
1782
+ );
1679
1783
  }
1680
1784
  }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1785
+ let healingActive = false;
1786
+ const healingEffect = Effect8.gen(function* () {
1787
+ if (!healingActive) return;
1788
+ const status = yield* syncStatus.get();
1789
+ if (status === "syncing") return;
1790
+ yield* syncStatus.set("syncing");
1791
+ yield* Effect8.gen(function* () {
1792
+ const pubKeys = getSubscriptionPubKeys();
1793
+ const changedCollections = /* @__PURE__ */ new Set();
1794
+ yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1795
+ discard: true
1796
+ });
1797
+ if (changedCollections.size > 0) {
1798
+ yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1799
+ }
1800
+ }).pipe(Effect8.ensuring(syncStatus.set("idle")));
1801
+ }).pipe(Effect8.ignore);
1681
1802
  const handle = {
1682
1803
  sync: () => Effect8.gen(function* () {
1683
1804
  yield* Effect8.logInfo("Sync started");
1684
1805
  yield* syncStatus.set("syncing");
1685
1806
  yield* Ref3.set(watchCtx.replayingRef, true);
1686
- const changedCollections = new Set;
1807
+ const changedCollections = /* @__PURE__ */ new Set();
1687
1808
  yield* Effect8.gen(function* () {
1688
1809
  const pubKeys = getSubscriptionPubKeys();
1689
1810
  yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1690
1811
  discard: true
1691
1812
  });
1692
1813
  yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
1693
- }).pipe(Effect8.ensuring(Effect8.gen(function* () {
1694
- yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1695
- yield* syncStatus.set("idle");
1696
- })));
1814
+ }).pipe(
1815
+ Effect8.ensuring(
1816
+ Effect8.gen(function* () {
1817
+ yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1818
+ yield* syncStatus.set("idle");
1819
+ })
1820
+ )
1821
+ );
1697
1822
  yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1698
1823
  }).pipe(Effect8.withLogSpan("tablinum.sync")),
1699
1824
  publishLocal: (giftWrap) => Effect8.gen(function* () {
1700
- if (!giftWrap.event)
1701
- return;
1702
- 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);
1825
+ if (!giftWrap.event) return;
1826
+ yield* relay.publish(giftWrap.event, relayUrls).pipe(
1827
+ Effect8.tapError(
1828
+ () => storage.putGiftWrap(giftWrap).pipe(
1829
+ Effect8.andThen(publishQueue.enqueue(giftWrap.id)),
1830
+ Effect8.andThen(Effect8.sync(() => scheduleAutoFlush()))
1831
+ )
1832
+ ),
1833
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1834
+ Effect8.ignore
1835
+ );
1703
1836
  }),
1704
1837
  startSubscription: () => Effect8.gen(function* () {
1705
1838
  const pubKeys = getSubscriptionPubKeys();
1706
1839
  yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": pubKeys }, processRealtimeGiftWrap);
1707
1840
  if (!pubKeys.includes(personalPublicKey)) {
1708
- yield* subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [personalPublicKey] }, (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid));
1841
+ yield* subscribeAcrossRelays(
1842
+ { kinds: [GiftWrap2], "#p": [personalPublicKey] },
1843
+ (event) => Effect8.result(processRotationGiftWrap(event)).pipe(Effect8.asVoid)
1844
+ );
1709
1845
  }
1710
1846
  }),
1711
- addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap)
1847
+ addEpochSubscription: (publicKey) => subscribeAcrossRelays({ kinds: [GiftWrap2], "#p": [publicKey] }, processRealtimeGiftWrap),
1848
+ startHealing: () => {
1849
+ if (healingActive) return;
1850
+ healingActive = true;
1851
+ forkHandled(
1852
+ Effect8.sleep(Duration.minutes(5)).pipe(
1853
+ Effect8.andThen(healingEffect),
1854
+ Effect8.repeat(Schedule.spaced(Duration.minutes(5))),
1855
+ Effect8.ensuring(Effect8.sync(() => {
1856
+ healingActive = false;
1857
+ }))
1858
+ )
1859
+ );
1860
+ },
1861
+ stopHealing: () => {
1862
+ healingActive = false;
1863
+ }
1712
1864
  };
1713
- forkHandled(publishQueue.size().pipe(Effect8.flatMap((size) => Effect8.sync(() => {
1714
- if (size > 0)
1715
- scheduleAutoFlush();
1716
- }))));
1865
+ forkHandled(
1866
+ publishQueue.size().pipe(
1867
+ Effect8.flatMap(
1868
+ (size) => Effect8.sync(() => {
1869
+ if (size > 0) scheduleAutoFlush();
1870
+ })
1871
+ )
1872
+ )
1873
+ );
1717
1874
  return handle;
1718
1875
  }
1719
1876
 
@@ -1769,9 +1926,14 @@ var decodeAuthorProfile = Schema5.decodeUnknownEffect(Schema5.fromJsonString(Aut
1769
1926
  function fetchAuthorProfile(relay, relayUrls, pubkey) {
1770
1927
  return Effect9.gen(function* () {
1771
1928
  for (const url of relayUrls) {
1772
- const result = yield* Effect9.result(relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url));
1929
+ const result = yield* Effect9.result(
1930
+ relay.fetchByFilter({ kinds: [0], authors: [pubkey], limit: 1 }, url)
1931
+ );
1773
1932
  if (result._tag === "Success" && result.success.length > 0) {
1774
- return yield* decodeAuthorProfile(result.success[0].content).pipe(Effect9.map(Option6.some), Effect9.orElseSucceed(() => Option6.none()));
1933
+ return yield* decodeAuthorProfile(result.success[0].content).pipe(
1934
+ Effect9.map(Option6.some),
1935
+ Effect9.orElseSucceed(() => Option6.none())
1936
+ );
1775
1937
  }
1776
1938
  }
1777
1939
  return Option6.none();
@@ -1780,45 +1942,44 @@ function fetchAuthorProfile(relay, relayUrls, pubkey) {
1780
1942
 
1781
1943
  // src/services/Identity.ts
1782
1944
  import { ServiceMap as ServiceMap3 } from "effect";
1783
-
1784
- class Identity extends ServiceMap3.Service()("tablinum/Identity") {
1785
- }
1945
+ var Identity = class extends ServiceMap3.Service()("tablinum/Identity") {
1946
+ };
1786
1947
 
1787
1948
  // src/services/EpochStore.ts
1788
1949
  import { ServiceMap as ServiceMap4 } from "effect";
1789
-
1790
- class EpochStore extends ServiceMap4.Service()("tablinum/EpochStore") {
1791
- }
1950
+ var EpochStore = class extends ServiceMap4.Service()(
1951
+ "tablinum/EpochStore"
1952
+ ) {
1953
+ };
1792
1954
 
1793
1955
  // src/services/Storage.ts
1794
1956
  import { ServiceMap as ServiceMap5 } from "effect";
1795
-
1796
- class Storage extends ServiceMap5.Service()("tablinum/Storage") {
1797
- }
1957
+ var Storage = class extends ServiceMap5.Service()("tablinum/Storage") {
1958
+ };
1798
1959
 
1799
1960
  // src/services/Relay.ts
1800
1961
  import { ServiceMap as ServiceMap6 } from "effect";
1801
-
1802
- class Relay extends ServiceMap6.Service()("tablinum/Relay") {
1803
- }
1962
+ var Relay = class extends ServiceMap6.Service()("tablinum/Relay") {
1963
+ };
1804
1964
 
1805
1965
  // src/services/GiftWrap.ts
1806
1966
  import { ServiceMap as ServiceMap7 } from "effect";
1807
-
1808
- class GiftWrap3 extends ServiceMap7.Service()("tablinum/GiftWrap") {
1809
- }
1967
+ var GiftWrap3 = class extends ServiceMap7.Service()("tablinum/GiftWrap") {
1968
+ };
1810
1969
 
1811
1970
  // src/services/PublishQueue.ts
1812
1971
  import { ServiceMap as ServiceMap8 } from "effect";
1813
-
1814
- class PublishQueue extends ServiceMap8.Service()("tablinum/PublishQueue") {
1815
- }
1972
+ var PublishQueue = class extends ServiceMap8.Service()(
1973
+ "tablinum/PublishQueue"
1974
+ ) {
1975
+ };
1816
1976
 
1817
1977
  // src/services/SyncStatus.ts
1818
1978
  import { ServiceMap as ServiceMap9 } from "effect";
1819
-
1820
- class SyncStatus extends ServiceMap9.Service()("tablinum/SyncStatus") {
1821
- }
1979
+ var SyncStatus = class extends ServiceMap9.Service()(
1980
+ "tablinum/SyncStatus"
1981
+ ) {
1982
+ };
1822
1983
 
1823
1984
  // src/layers/IdentityLive.ts
1824
1985
  import { Effect as Effect11, Layer as Layer2 } from "effect";
@@ -1859,47 +2020,59 @@ function createIdentity(suppliedKey) {
1859
2020
  }
1860
2021
 
1861
2022
  // src/layers/IdentityLive.ts
1862
- var IdentityLive = Layer2.effect(Identity, Effect11.gen(function* () {
1863
- const config = yield* Config;
1864
- const storage = yield* Storage;
1865
- const idbKey = yield* storage.getMeta("identity_key");
1866
- const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : undefined);
1867
- const identity = yield* createIdentity(resolvedKey);
1868
- yield* storage.putMeta("identity_key", identity.exportKey());
1869
- yield* Effect11.logInfo("Identity loaded", {
1870
- publicKey: identity.publicKey.slice(0, 12) + "...",
1871
- source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
1872
- });
1873
- return identity;
1874
- }));
2023
+ var IdentityLive = Layer2.effect(
2024
+ Identity,
2025
+ Effect11.gen(function* () {
2026
+ const config = yield* Config;
2027
+ const storage = yield* Storage;
2028
+ const idbKey = yield* storage.getMeta("identity_key");
2029
+ const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : void 0);
2030
+ const identity = yield* createIdentity(resolvedKey);
2031
+ yield* storage.putMeta("identity_key", identity.exportKey());
2032
+ yield* Effect11.logInfo("Identity loaded", {
2033
+ publicKey: identity.publicKey.slice(0, 12) + "...",
2034
+ source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
2035
+ });
2036
+ return identity;
2037
+ })
2038
+ );
1875
2039
 
1876
2040
  // src/layers/EpochStoreLive.ts
1877
2041
  import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
1878
2042
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
1879
2043
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
1880
- var EpochStoreLive = Layer3.effect(EpochStore, Effect12.gen(function* () {
1881
- const config = yield* Config;
1882
- const identity = yield* Identity;
1883
- const storage = yield* Storage;
1884
- const idbRaw = yield* storage.getMeta("epochs");
1885
- if (typeof idbRaw === "string") {
1886
- const idbStore = deserializeEpochStore(idbRaw);
1887
- if (Option7.isSome(idbStore)) {
1888
- yield* Effect12.logInfo("Epoch store loaded", { source: "storage", epochs: idbStore.value.epochs.size });
1889
- return idbStore.value;
1890
- }
1891
- }
1892
- if (config.epochKeys && config.epochKeys.length > 0) {
1893
- const store2 = createEpochStoreFromInputs(config.epochKeys);
1894
- yield* storage.putMeta("epochs", stringifyEpochStore(store2));
1895
- yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
1896
- return store2;
1897
- }
1898
- const store = createEpochStoreFromInputs([{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }], { createdBy: identity.publicKey });
1899
- yield* storage.putMeta("epochs", stringifyEpochStore(store));
1900
- yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
1901
- return store;
1902
- }));
2044
+ var EpochStoreLive = Layer3.effect(
2045
+ EpochStore,
2046
+ Effect12.gen(function* () {
2047
+ const config = yield* Config;
2048
+ const identity = yield* Identity;
2049
+ const storage = yield* Storage;
2050
+ const idbRaw = yield* storage.getMeta("epochs");
2051
+ if (typeof idbRaw === "string") {
2052
+ const idbStore = deserializeEpochStore(idbRaw);
2053
+ if (Option7.isSome(idbStore)) {
2054
+ yield* Effect12.logInfo("Epoch store loaded", {
2055
+ source: "storage",
2056
+ epochs: idbStore.value.epochs.size
2057
+ });
2058
+ return idbStore.value;
2059
+ }
2060
+ }
2061
+ if (config.epochKeys && config.epochKeys.length > 0) {
2062
+ const store2 = createEpochStoreFromInputs(config.epochKeys);
2063
+ yield* storage.putMeta("epochs", stringifyEpochStore(store2));
2064
+ yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
2065
+ return store2;
2066
+ }
2067
+ const store = createEpochStoreFromInputs(
2068
+ [{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }],
2069
+ { createdBy: identity.publicKey }
2070
+ );
2071
+ yield* storage.putMeta("epochs", stringifyEpochStore(store));
2072
+ yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
2073
+ return store;
2074
+ })
2075
+ );
1903
2076
 
1904
2077
  // src/layers/StorageLive.ts
1905
2078
  import { Effect as Effect14, Layer as Layer4 } from "effect";
@@ -1907,6 +2080,68 @@ import { Effect as Effect14, Layer as Layer4 } from "effect";
1907
2080
  // src/storage/idb.ts
1908
2081
  import { Effect as Effect13 } from "effect";
1909
2082
  import { openDB } from "idb";
2083
+
2084
+ // src/sync/compact-event.ts
2085
+ import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes4 } from "@noble/hashes/utils.js";
2086
+ var VERSION = 1;
2087
+ var HEADER_SIZE = 133;
2088
+ function base64ToBytes(base64) {
2089
+ const binary = atob(base64);
2090
+ const bytes = new Uint8Array(binary.length);
2091
+ for (let i = 0; i < binary.length; i++) {
2092
+ bytes[i] = binary.charCodeAt(i);
2093
+ }
2094
+ return bytes;
2095
+ }
2096
+ function bytesToBase64(bytes) {
2097
+ let binary = "";
2098
+ for (let i = 0; i < bytes.length; i++) {
2099
+ binary += String.fromCharCode(bytes[i]);
2100
+ }
2101
+ return btoa(binary);
2102
+ }
2103
+ function packEvent(event) {
2104
+ const pubkey = hexToBytes4(event.pubkey);
2105
+ const sig = hexToBytes4(event.sig);
2106
+ const recipientTag = event.tags.find((t) => t[0] === "p");
2107
+ if (!recipientTag) throw new Error("Gift wrap missing #p tag");
2108
+ if (event.tags.some((t) => t[0] !== "p")) {
2109
+ throw new Error("Gift wrap has unexpected non-p tags; compact encoding would lose them");
2110
+ }
2111
+ const recipient = hexToBytes4(recipientTag[1]);
2112
+ const createdAtBuf = new Uint8Array(4);
2113
+ new DataView(createdAtBuf.buffer).setUint32(0, event.created_at, false);
2114
+ const content = base64ToBytes(event.content);
2115
+ const result = new Uint8Array(HEADER_SIZE + content.length);
2116
+ result[0] = VERSION;
2117
+ result.set(pubkey, 1);
2118
+ result.set(sig, 33);
2119
+ result.set(recipient, 97);
2120
+ result.set(createdAtBuf, 129);
2121
+ result.set(content, HEADER_SIZE);
2122
+ return result;
2123
+ }
2124
+ function unpackEvent(id, compact) {
2125
+ const version = compact[0];
2126
+ if (version !== VERSION) throw new Error(`Unknown compact event version: ${version}`);
2127
+ const pubkey = bytesToHex4(compact.slice(1, 33));
2128
+ const sig = bytesToHex4(compact.slice(33, 97));
2129
+ const recipient = bytesToHex4(compact.slice(97, 129));
2130
+ const dv = new DataView(compact.buffer, compact.byteOffset + 129, 4);
2131
+ const createdAt = dv.getUint32(0, false);
2132
+ const content = bytesToBase64(compact.slice(HEADER_SIZE));
2133
+ return {
2134
+ id,
2135
+ pubkey,
2136
+ sig,
2137
+ created_at: createdAt,
2138
+ kind: 1059,
2139
+ tags: [["p", recipient]],
2140
+ content
2141
+ };
2142
+ }
2143
+
2144
+ // src/storage/idb.ts
1910
2145
  var DB_NAME = "tablinum";
1911
2146
  function storeName(collection2) {
1912
2147
  return `col_${collection2}`;
@@ -1937,7 +2172,7 @@ function upgradeSchema(database, schema, tx) {
1937
2172
  if (!database.objectStoreNames.contains("giftwraps")) {
1938
2173
  database.createObjectStore("giftwraps", { keyPath: "id" });
1939
2174
  }
1940
- const expectedStores = new Set;
2175
+ const expectedStores = /* @__PURE__ */ new Set();
1941
2176
  for (const [, def] of Object.entries(schema)) {
1942
2177
  const sn = storeName(def.name);
1943
2178
  expectedStores.add(sn);
@@ -1951,12 +2186,10 @@ function upgradeSchema(database, schema, tx) {
1951
2186
  const existingIndices = new Set(Array.from(store.indexNames));
1952
2187
  const wantedIndices = new Set(def.indices ?? []);
1953
2188
  for (const idx of existingIndices) {
1954
- if (!wantedIndices.has(idx))
1955
- store.deleteIndex(idx);
2189
+ if (!wantedIndices.has(idx)) store.deleteIndex(idx);
1956
2190
  }
1957
2191
  for (const idx of wantedIndices) {
1958
- if (!existingIndices.has(idx))
1959
- store.createIndex(idx, idx);
2192
+ if (!existingIndices.has(idx)) store.createIndex(idx, idx);
1960
2193
  }
1961
2194
  }
1962
2195
  }
@@ -1971,6 +2204,13 @@ function openIDBStorage(dbName, schema) {
1971
2204
  return Effect13.gen(function* () {
1972
2205
  const name = dbName ?? DB_NAME;
1973
2206
  const schemaSig = computeSchemaSig(schema);
2207
+ if (typeof indexedDB === "undefined") {
2208
+ return yield* Effect13.fail(
2209
+ new StorageError({
2210
+ message: "IndexedDB is not available in this environment"
2211
+ })
2212
+ );
2213
+ }
1974
2214
  const probeDb = yield* Effect13.tryPromise({
1975
2215
  try: () => openDB(name),
1976
2216
  catch: (e) => new StorageError({ message: "Failed to open IndexedDB", cause: e })
@@ -1981,7 +2221,7 @@ function openIDBStorage(dbName, schema) {
1981
2221
  const storedSig = yield* Effect13.tryPromise({
1982
2222
  try: () => probeDb.get("_meta", "schema_sig"),
1983
2223
  catch: () => new StorageError({ message: "Failed to read schema meta" })
1984
- }).pipe(Effect13.catch(() => Effect13.succeed(undefined)));
2224
+ }).pipe(Effect13.catch(() => Effect13.succeed(void 0)));
1985
2225
  needsUpgrade = storedSig !== schemaSig;
1986
2226
  }
1987
2227
  probeDb.close();
@@ -1998,9 +2238,7 @@ function openIDBStorage(dbName, schema) {
1998
2238
  });
1999
2239
  yield* Effect13.addFinalizer(() => Effect13.sync(() => db.close()));
2000
2240
  const handle = {
2001
- putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => {
2002
- return;
2003
- })),
2241
+ putRecord: (collection2, record) => wrap("putRecord", () => db.put(storeName(collection2), record).then(() => void 0)),
2004
2242
  getRecord: (collection2, id) => wrap("getRecord", () => db.get(storeName(collection2), id)),
2005
2243
  getAllRecords: (collection2) => wrap("getAllRecords", () => db.getAll(storeName(collection2))),
2006
2244
  countRecords: (collection2) => wrap("countRecords", () => db.count(storeName(collection2))),
@@ -2020,29 +2258,52 @@ function openIDBStorage(dbName, schema) {
2020
2258
  }
2021
2259
  return results;
2022
2260
  }),
2023
- putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => {
2024
- return;
2025
- })),
2261
+ putEvent: (event) => wrap("putEvent", () => db.put("events", event).then(() => void 0)),
2026
2262
  getEvent: (id) => wrap("getEvent", () => db.get("events", id)),
2027
2263
  getAllEvents: () => wrap("getAllEvents", () => db.getAll("events")),
2028
- getEventsByRecord: (collection2, recordId) => wrap("getEventsByRecord", () => db.getAllFromIndex("events", "by-record", [collection2, recordId])),
2029
- putGiftWrap: (gw) => wrap("putGiftWrap", () => db.put("giftwraps", gw).then(() => {
2030
- return;
2031
- })),
2032
- getGiftWrap: (id) => wrap("getGiftWrap", () => db.get("giftwraps", id)),
2033
- getAllGiftWraps: () => wrap("getAllGiftWraps", () => db.getAll("giftwraps")),
2034
- deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => {
2035
- return;
2036
- })),
2037
- stripGiftWrapBlob: (id) => wrap("stripGiftWrapBlob", async () => {
2038
- const existing = await db.get("giftwraps", id);
2039
- if (existing) {
2040
- await db.put("giftwraps", { id: existing.id, createdAt: existing.createdAt });
2264
+ getEventsByRecord: (collection2, recordId) => wrap(
2265
+ "getEventsByRecord",
2266
+ () => db.getAllFromIndex("events", "by-record", [collection2, recordId])
2267
+ ),
2268
+ putGiftWrap: (gw) => wrap("putGiftWrap", async () => {
2269
+ if (gw.event) {
2270
+ const compact = packEvent(gw.event);
2271
+ await db.put("giftwraps", { id: gw.id, compact, createdAt: gw.createdAt });
2272
+ } else {
2273
+ await db.put("giftwraps", { id: gw.id, createdAt: gw.createdAt });
2041
2274
  }
2042
2275
  }),
2043
- deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => {
2044
- return;
2045
- })),
2276
+ getGiftWrap: (id) => wrap("getGiftWrap", async () => {
2277
+ const raw = await db.get("giftwraps", id);
2278
+ if (!raw) return void 0;
2279
+ if (raw.compact) {
2280
+ return { id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt };
2281
+ }
2282
+ if (raw.event) {
2283
+ const compact = packEvent(raw.event);
2284
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2285
+ return { id: raw.id, event: raw.event, createdAt: raw.createdAt };
2286
+ }
2287
+ return { id: raw.id, createdAt: raw.createdAt };
2288
+ }),
2289
+ getAllGiftWraps: () => wrap("getAllGiftWraps", async () => {
2290
+ const raws = await db.getAll("giftwraps");
2291
+ const results = [];
2292
+ for (const raw of raws) {
2293
+ if (raw.compact) {
2294
+ results.push({ id: raw.id, event: unpackEvent(raw.id, raw.compact), createdAt: raw.createdAt });
2295
+ } else if (raw.event) {
2296
+ const compact = packEvent(raw.event);
2297
+ await db.put("giftwraps", { id: raw.id, compact, createdAt: raw.createdAt });
2298
+ results.push({ id: raw.id, event: raw.event, createdAt: raw.createdAt });
2299
+ } else {
2300
+ results.push({ id: raw.id, createdAt: raw.createdAt });
2301
+ }
2302
+ }
2303
+ return results;
2304
+ }),
2305
+ deleteGiftWrap: (id) => wrap("deleteGiftWrap", () => db.delete("giftwraps", id).then(() => void 0)),
2306
+ deleteEvent: (id) => wrap("deleteEvent", () => db.delete("events", id).then(() => void 0)),
2046
2307
  stripEventData: (id) => wrap("stripEventData", async () => {
2047
2308
  const existing = await db.get("events", id);
2048
2309
  if (existing) {
@@ -2050,9 +2311,7 @@ function openIDBStorage(dbName, schema) {
2050
2311
  }
2051
2312
  }),
2052
2313
  getMeta: (key) => wrap("getMeta", () => db.get("_meta", key)),
2053
- putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => {
2054
- return;
2055
- })),
2314
+ putMeta: (key, value) => wrap("putMeta", () => db.put("_meta", value, key).then(() => void 0)),
2056
2315
  close: () => Effect13.sync(() => db.close())
2057
2316
  };
2058
2317
  return handle;
@@ -2060,15 +2319,18 @@ function openIDBStorage(dbName, schema) {
2060
2319
  }
2061
2320
 
2062
2321
  // src/layers/StorageLive.ts
2063
- var StorageLive = Layer4.effect(Storage, Effect14.gen(function* () {
2064
- const config = yield* Config;
2065
- const handle = yield* openIDBStorage(config.dbName, {
2066
- ...config.schema,
2067
- _members: membersCollectionDef
2068
- });
2069
- yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2070
- return handle;
2071
- }));
2322
+ var StorageLive = Layer4.effect(
2323
+ Storage,
2324
+ Effect14.gen(function* () {
2325
+ const config = yield* Config;
2326
+ const handle = yield* openIDBStorage(config.dbName, {
2327
+ ...config.schema,
2328
+ _members: membersCollectionDef
2329
+ });
2330
+ yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2331
+ return handle;
2332
+ })
2333
+ );
2072
2334
 
2073
2335
  // src/layers/RelayLive.ts
2074
2336
  import { Layer as Layer5 } from "effect";
@@ -2082,32 +2344,41 @@ var NegMessageFrameSchema = Schema6.Tuple([
2082
2344
  Schema6.String
2083
2345
  ]);
2084
2346
  var NegErrorFrameSchema = Schema6.Tuple([Schema6.Literal("NEG-ERR"), Schema6.String, Schema6.String]);
2085
- var decodeNegFrame = Schema6.decodeUnknownEffect(Schema6.fromJsonString(Schema6.Union([NegMessageFrameSchema, NegErrorFrameSchema])));
2347
+ var decodeNegFrame = Schema6.decodeUnknownEffect(
2348
+ Schema6.fromJsonString(Schema6.Union([NegMessageFrameSchema, NegErrorFrameSchema]))
2349
+ );
2086
2350
  function parseNegMessageFrame(data) {
2087
- return Effect15.runSync(decodeNegFrame(data).pipe(Effect15.map(Option8.some), Effect15.orElseSucceed(() => Option8.none())));
2351
+ return Effect15.runSync(
2352
+ decodeNegFrame(data).pipe(
2353
+ Effect15.map(Option8.some),
2354
+ Effect15.orElseSucceed(() => Option8.none())
2355
+ )
2356
+ );
2088
2357
  }
2089
2358
  function createRelayHandle() {
2090
2359
  return Effect15.gen(function* () {
2091
2360
  const relayScope = yield* Effect15.scope;
2092
2361
  const connections = yield* ScopedCache.make({
2093
2362
  capacity: 64,
2094
- lookup: (url) => Effect15.acquireRelease(Effect15.tryPromise({
2095
- try: () => Relay2.connect(url),
2096
- catch: (e) => new RelayError({
2097
- message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2098
- url,
2099
- cause: e
2363
+ lookup: (url) => Effect15.acquireRelease(
2364
+ Effect15.tryPromise({
2365
+ try: () => Relay2.connect(url),
2366
+ catch: (e) => new RelayError({
2367
+ message: `Connect to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2368
+ url,
2369
+ cause: e
2370
+ })
2371
+ }),
2372
+ (relay) => Effect15.sync(() => {
2373
+ relay.close();
2100
2374
  })
2101
- }), (relay) => Effect15.sync(() => {
2102
- relay.close();
2103
- }))
2375
+ )
2104
2376
  });
2105
- const connectedUrls = new Set;
2106
- const statusListeners = new Set;
2377
+ const connectedUrls = /* @__PURE__ */ new Set();
2378
+ const statusListeners = /* @__PURE__ */ new Set();
2107
2379
  const notifyStatus = () => {
2108
2380
  const status = { connectedUrls: [...connectedUrls] };
2109
- for (const listener of statusListeners)
2110
- listener(status);
2381
+ for (const listener of statusListeners) listener(status);
2111
2382
  };
2112
2383
  const markConnected = (url) => {
2113
2384
  if (!connectedUrls.has(url)) {
@@ -2121,66 +2392,98 @@ function createRelayHandle() {
2121
2392
  notifyStatus();
2122
2393
  }
2123
2394
  };
2124
- 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)));
2125
- 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))))));
2126
- const collectEvents = (url, filters) => withRelay(url, (relay) => Effect15.callback((resume) => {
2127
- const events = [];
2128
- let settled = false;
2129
- let timer;
2130
- let sub;
2131
- const cleanup = () => {
2132
- settled = true;
2133
- if (timer !== undefined) {
2134
- clearTimeout(timer);
2135
- timer = undefined;
2136
- }
2137
- sub?.close();
2138
- sub = undefined;
2139
- };
2140
- const fail = (e) => resume(Effect15.fail(new RelayError({
2141
- message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2142
- url,
2143
- cause: e
2144
- })));
2145
- try {
2146
- sub = relay.subscribe([...filters], {
2147
- onevent(evt) {
2148
- if (!settled) {
2149
- events.push(evt);
2395
+ const getRelay = (url) => ScopedCache.get(connections, url).pipe(
2396
+ Effect15.flatMap(
2397
+ (relay) => relay.connected === false ? ScopedCache.invalidate(connections, url).pipe(
2398
+ Effect15.andThen(ScopedCache.get(connections, url))
2399
+ ) : Effect15.succeed(relay)
2400
+ )
2401
+ );
2402
+ const withRelay = (url, run) => getRelay(url).pipe(
2403
+ Effect15.tap(() => Effect15.sync(() => markConnected(url))),
2404
+ Effect15.flatMap((relay) => run(relay)),
2405
+ Effect15.tapError(
2406
+ () => ScopedCache.invalidate(connections, url).pipe(
2407
+ Effect15.tap(() => Effect15.sync(() => markDisconnected(url)))
2408
+ )
2409
+ )
2410
+ );
2411
+ const collectEvents = (url, filters) => withRelay(
2412
+ url,
2413
+ (relay) => Effect15.callback((resume) => {
2414
+ const events = [];
2415
+ let settled = false;
2416
+ let timer;
2417
+ let sub;
2418
+ const cleanup = () => {
2419
+ settled = true;
2420
+ if (timer !== void 0) {
2421
+ clearTimeout(timer);
2422
+ timer = void 0;
2423
+ }
2424
+ sub?.close();
2425
+ sub = void 0;
2426
+ };
2427
+ const fail = (e) => resume(
2428
+ Effect15.fail(
2429
+ new RelayError({
2430
+ message: `Fetch from ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2431
+ url,
2432
+ cause: e
2433
+ })
2434
+ )
2435
+ );
2436
+ try {
2437
+ sub = relay.subscribe([...filters], {
2438
+ onevent(evt) {
2439
+ if (!settled) {
2440
+ events.push(evt);
2441
+ }
2442
+ },
2443
+ oneose() {
2444
+ if (settled) return;
2445
+ cleanup();
2446
+ resume(Effect15.succeed(events));
2150
2447
  }
2151
- },
2152
- oneose() {
2153
- if (settled)
2154
- return;
2448
+ });
2449
+ timer = setTimeout(() => {
2450
+ if (settled) return;
2155
2451
  cleanup();
2156
2452
  resume(Effect15.succeed(events));
2157
- }
2158
- });
2159
- timer = setTimeout(() => {
2160
- if (settled)
2161
- return;
2453
+ }, 1e4);
2454
+ } catch (e) {
2162
2455
  cleanup();
2163
- resume(Effect15.succeed(events));
2164
- }, 1e4);
2165
- } catch (e) {
2166
- cleanup();
2167
- fail(e);
2168
- }
2169
- return Effect15.sync(cleanup);
2170
- }));
2456
+ fail(e);
2457
+ }
2458
+ return Effect15.sync(cleanup);
2459
+ })
2460
+ );
2171
2461
  return {
2172
2462
  publish: (event, urls) => Effect15.gen(function* () {
2173
- const results = yield* Effect15.forEach(urls, (url) => Effect15.result(withRelay(url, (relay) => Effect15.tryPromise({
2174
- try: () => relay.publish(event),
2175
- catch: (e) => new RelayError({
2176
- message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2177
- url,
2178
- cause: e
2179
- })
2180
- }).pipe(Effect15.timeoutOrElse({
2181
- duration: "10 seconds",
2182
- onTimeout: () => Effect15.fail(new RelayError({ message: `Publish to ${url} timed out`, url }))
2183
- })))), { concurrency: "unbounded" });
2463
+ const results = yield* Effect15.forEach(
2464
+ urls,
2465
+ (url) => Effect15.result(
2466
+ withRelay(
2467
+ url,
2468
+ (relay) => Effect15.tryPromise({
2469
+ try: () => relay.publish(event),
2470
+ catch: (e) => new RelayError({
2471
+ message: `Publish to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2472
+ url,
2473
+ cause: e
2474
+ })
2475
+ }).pipe(
2476
+ Effect15.timeoutOrElse({
2477
+ duration: "10 seconds",
2478
+ onTimeout: () => Effect15.fail(
2479
+ new RelayError({ message: `Publish to ${url} timed out`, url })
2480
+ )
2481
+ })
2482
+ )
2483
+ )
2484
+ ),
2485
+ { concurrency: "unbounded" }
2486
+ );
2184
2487
  const failures = results.filter((r) => r._tag === "Failure");
2185
2488
  if (failures.length === urls.length && urls.length > 0) {
2186
2489
  return yield* new RelayError({
@@ -2189,90 +2492,104 @@ function createRelayHandle() {
2189
2492
  }
2190
2493
  }),
2191
2494
  fetchEvents: (ids, url) => Effect15.gen(function* () {
2192
- if (ids.length === 0)
2193
- return [];
2495
+ if (ids.length === 0) return [];
2194
2496
  return yield* collectEvents(url, [{ ids }]);
2195
2497
  }),
2196
2498
  fetchByFilter: (filter, url) => collectEvents(url, [filter]),
2197
- subscribe: (filter, url, onEvent) => withRelay(url, (relay) => Effect15.acquireRelease(Effect15.try({
2198
- try: () => relay.subscribe([filter], {
2199
- onevent(evt) {
2200
- onEvent(evt);
2201
- },
2202
- oneose() {}
2203
- }),
2204
- catch: (e) => new RelayError({
2205
- message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2206
- url,
2207
- cause: e
2208
- })
2209
- }), (sub) => Effect15.sync(() => {
2210
- sub.close();
2211
- })).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)),
2212
- sendNegMsg: (url, subId, filter, msgHex) => withRelay(url, (relay) => Effect15.callback((resume) => {
2213
- let settled = false;
2214
- let timer;
2215
- let sub;
2216
- let ws;
2217
- const cleanup = () => {
2218
- settled = true;
2219
- if (timer !== undefined) {
2220
- clearTimeout(timer);
2221
- timer = undefined;
2222
- }
2223
- sub?.close();
2224
- sub = undefined;
2225
- ws?.removeEventListener("message", handler);
2226
- ws = undefined;
2227
- };
2228
- const fail = (e) => resume(Effect15.fail(new RelayError({
2229
- message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2230
- url,
2231
- cause: e
2232
- })));
2233
- const handler = (msg) => {
2234
- if (settled || typeof msg.data !== "string")
2235
- return;
2236
- const frameOpt = parseNegMessageFrame(msg.data);
2237
- if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId)
2238
- return;
2239
- const frame = frameOpt.value;
2240
- cleanup();
2241
- if (frame[0] === "NEG-MSG") {
2242
- resume(Effect15.succeed({
2243
- msgHex: frame[2],
2244
- haveIds: [],
2245
- needIds: []
2246
- }));
2247
- return;
2248
- }
2249
- fail(new Error(`NEG-ERR: ${frame[2]}`));
2250
- };
2251
- try {
2252
- sub = relay.subscribe([filter], {
2253
- onevent() {},
2254
- oneose() {}
2255
- });
2256
- ws = relay._ws || relay.ws;
2257
- if (!ws) {
2499
+ subscribe: (filter, url, onEvent) => withRelay(
2500
+ url,
2501
+ (relay) => Effect15.acquireRelease(
2502
+ Effect15.try({
2503
+ try: () => relay.subscribe([filter], {
2504
+ onevent(evt) {
2505
+ onEvent(evt);
2506
+ },
2507
+ oneose() {
2508
+ }
2509
+ }),
2510
+ catch: (e) => new RelayError({
2511
+ message: `Subscribe to ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2512
+ url,
2513
+ cause: e
2514
+ })
2515
+ }),
2516
+ (sub) => Effect15.sync(() => {
2517
+ sub.close();
2518
+ })
2519
+ ).pipe(Effect15.provideService(Scope3.Scope, relayScope), Effect15.asVoid)
2520
+ ),
2521
+ sendNegMsg: (url, subId, filter, msgHex) => withRelay(
2522
+ url,
2523
+ (relay) => Effect15.callback((resume) => {
2524
+ let settled = false;
2525
+ let timer;
2526
+ let sub;
2527
+ let ws;
2528
+ const cleanup = () => {
2529
+ settled = true;
2530
+ if (timer !== void 0) {
2531
+ clearTimeout(timer);
2532
+ timer = void 0;
2533
+ }
2534
+ sub?.close();
2535
+ sub = void 0;
2536
+ ws?.removeEventListener("message", handler);
2537
+ ws = void 0;
2538
+ };
2539
+ const fail = (e) => resume(
2540
+ Effect15.fail(
2541
+ new RelayError({
2542
+ message: `NIP-77 negotiation with ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
2543
+ url,
2544
+ cause: e
2545
+ })
2546
+ )
2547
+ );
2548
+ const handler = (msg) => {
2549
+ if (settled || typeof msg.data !== "string") return;
2550
+ const frameOpt = parseNegMessageFrame(msg.data);
2551
+ if (Option8.isNone(frameOpt) || frameOpt.value[1] !== subId) return;
2552
+ const frame = frameOpt.value;
2258
2553
  cleanup();
2259
- fail(new Error("Cannot access relay WebSocket"));
2260
- return Effect15.succeed(undefined);
2261
- }
2262
- timer = setTimeout(() => {
2263
- if (settled)
2554
+ if (frame[0] === "NEG-MSG") {
2555
+ resume(
2556
+ Effect15.succeed({
2557
+ msgHex: frame[2],
2558
+ haveIds: [],
2559
+ needIds: []
2560
+ })
2561
+ );
2264
2562
  return;
2563
+ }
2564
+ fail(new Error(`NEG-ERR: ${frame[2]}`));
2565
+ };
2566
+ try {
2567
+ sub = relay.subscribe([filter], {
2568
+ onevent() {
2569
+ },
2570
+ oneose() {
2571
+ }
2572
+ });
2573
+ ws = relay._ws || relay.ws;
2574
+ if (!ws) {
2575
+ cleanup();
2576
+ fail(new Error("Cannot access relay WebSocket"));
2577
+ return Effect15.succeed(void 0);
2578
+ }
2579
+ timer = setTimeout(() => {
2580
+ if (settled) return;
2581
+ cleanup();
2582
+ fail(new Error("NIP-77 negotiation timeout"));
2583
+ }, 3e4);
2584
+ ws.addEventListener("message", handler);
2585
+ ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
2586
+ } catch (e) {
2265
2587
  cleanup();
2266
- fail(new Error("NIP-77 negotiation timeout"));
2267
- }, 30000);
2268
- ws.addEventListener("message", handler);
2269
- ws.send(JSON.stringify(["NEG-OPEN", subId, filter, msgHex]));
2270
- } catch (e) {
2271
- cleanup();
2272
- fail(e);
2273
- }
2274
- return Effect15.sync(cleanup);
2275
- })),
2588
+ fail(e);
2589
+ }
2590
+ return Effect15.sync(cleanup);
2591
+ })
2592
+ ),
2276
2593
  closeAll: () => ScopedCache.invalidateAll(connections),
2277
2594
  getStatus: () => ({ connectedUrls: [...connectedUrls] }),
2278
2595
  subscribeStatus: (callback) => {
@@ -2324,11 +2641,14 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
2324
2641
  }
2325
2642
 
2326
2643
  // src/layers/GiftWrapLive.ts
2327
- var GiftWrapLive = Layer6.effect(GiftWrap3, Effect17.gen(function* () {
2328
- const identity = yield* Identity;
2329
- const epochStore = yield* EpochStore;
2330
- return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2331
- }));
2644
+ var GiftWrapLive = Layer6.effect(
2645
+ GiftWrap3,
2646
+ Effect17.gen(function* () {
2647
+ const identity = yield* Identity;
2648
+ const epochStore = yield* EpochStore;
2649
+ return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2650
+ })
2651
+ );
2332
2652
 
2333
2653
  // src/layers/PublishQueueLive.ts
2334
2654
  import { Effect as Effect19, Layer as Layer7 } from "effect";
@@ -2342,12 +2662,11 @@ function persist(storage, pending) {
2342
2662
  function createPublishQueue(storage, relay) {
2343
2663
  return Effect18.gen(function* () {
2344
2664
  const stored = yield* storage.getMeta(META_KEY);
2345
- const initial = Array.isArray(stored) ? new Set(stored) : new Set;
2665
+ const initial = Array.isArray(stored) ? new Set(stored) : /* @__PURE__ */ new Set();
2346
2666
  const pendingRef = yield* Ref4.make(initial);
2347
- const listeners = new Set;
2667
+ const listeners = /* @__PURE__ */ new Set();
2348
2668
  const notify = (pending) => {
2349
- for (const listener of listeners)
2350
- listener(pending.size);
2669
+ for (const listener of listeners) listener(pending.size);
2351
2670
  };
2352
2671
  return {
2353
2672
  enqueue: (eventId) => Effect18.gen(function* () {
@@ -2361,13 +2680,11 @@ function createPublishQueue(storage, relay) {
2361
2680
  }),
2362
2681
  flush: (relayUrls) => Effect18.gen(function* () {
2363
2682
  const pending = yield* Ref4.get(pendingRef);
2364
- if (pending.size === 0)
2365
- return;
2366
- const succeeded = new Set;
2683
+ if (pending.size === 0) return;
2684
+ const succeeded = /* @__PURE__ */ new Set();
2367
2685
  let consecutiveFailures = 0;
2368
2686
  for (const eventId of pending) {
2369
- if (consecutiveFailures >= 3)
2370
- break;
2687
+ if (consecutiveFailures >= 3) break;
2371
2688
  const gw = yield* storage.getGiftWrap(eventId);
2372
2689
  if (!gw || !gw.event) {
2373
2690
  succeeded.add(eventId);
@@ -2377,7 +2694,6 @@ function createPublishQueue(storage, relay) {
2377
2694
  const result = yield* Effect18.result(relay.publish(gw.event, relayUrls));
2378
2695
  if (result._tag === "Success") {
2379
2696
  succeeded.add(eventId);
2380
- yield* storage.stripGiftWrapBlob(eventId);
2381
2697
  consecutiveFailures = 0;
2382
2698
  } else {
2383
2699
  consecutiveFailures++;
@@ -2405,11 +2721,14 @@ function createPublishQueue(storage, relay) {
2405
2721
  }
2406
2722
 
2407
2723
  // src/layers/PublishQueueLive.ts
2408
- var PublishQueueLive = Layer7.effect(PublishQueue, Effect19.gen(function* () {
2409
- const storage = yield* Storage;
2410
- const relay = yield* Relay;
2411
- return yield* createPublishQueue(storage, relay);
2412
- }));
2724
+ var PublishQueueLive = Layer7.effect(
2725
+ PublishQueue,
2726
+ Effect19.gen(function* () {
2727
+ const storage = yield* Storage;
2728
+ const relay = yield* Relay;
2729
+ return yield* createPublishQueue(storage, relay);
2730
+ })
2731
+ );
2413
2732
 
2414
2733
  // src/layers/SyncStatusLive.ts
2415
2734
  import { Layer as Layer8 } from "effect";
@@ -2419,13 +2738,12 @@ import { Effect as Effect20, SubscriptionRef } from "effect";
2419
2738
  function createSyncStatusHandle() {
2420
2739
  return Effect20.gen(function* () {
2421
2740
  const ref = yield* SubscriptionRef.make("idle");
2422
- const listeners = new Set;
2741
+ const listeners = /* @__PURE__ */ new Set();
2423
2742
  return {
2424
2743
  get: () => SubscriptionRef.get(ref),
2425
2744
  set: (status) => Effect20.gen(function* () {
2426
2745
  yield* SubscriptionRef.set(ref, status);
2427
- for (const listener of listeners)
2428
- listener(status);
2746
+ for (const listener of listeners) listener(status);
2429
2747
  }),
2430
2748
  subscribe: (callback) => {
2431
2749
  listeners.add(callback);
@@ -2440,8 +2758,7 @@ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
2440
2758
 
2441
2759
  // src/layers/TablinumLive.ts
2442
2760
  function reportSyncError(onSyncError, error) {
2443
- if (!onSyncError)
2444
- return;
2761
+ if (!onSyncError) return;
2445
2762
  onSyncError(error instanceof Error ? error : new Error(String(error)));
2446
2763
  }
2447
2764
  function mapMemberRecord(record) {
@@ -2449,249 +2766,352 @@ function mapMemberRecord(record) {
2449
2766
  id: record.id,
2450
2767
  addedAt: record.addedAt,
2451
2768
  addedInEpoch: record.addedInEpoch,
2452
- ...record.name !== undefined ? { name: record.name } : {},
2453
- ...record.picture !== undefined ? { picture: record.picture } : {},
2454
- ...record.about !== undefined ? { about: record.about } : {},
2455
- ...record.nip05 !== undefined ? { nip05: record.nip05 } : {},
2456
- ...record.removedAt !== undefined ? { removedAt: record.removedAt } : {},
2457
- ...record.removedInEpoch !== undefined ? { removedInEpoch: record.removedInEpoch } : {}
2769
+ ...record.name !== void 0 ? { name: record.name } : {},
2770
+ ...record.picture !== void 0 ? { picture: record.picture } : {},
2771
+ ...record.about !== void 0 ? { about: record.about } : {},
2772
+ ...record.nip05 !== void 0 ? { nip05: record.nip05 } : {},
2773
+ ...record.removedAt !== void 0 ? { removedAt: record.removedAt } : {},
2774
+ ...record.removedInEpoch !== void 0 ? { removedInEpoch: record.removedInEpoch } : {}
2458
2775
  };
2459
2776
  }
2460
2777
  var IdentityWithDeps = IdentityLive.pipe(Layer9.provide(StorageLive));
2461
- var EpochStoreWithDeps = EpochStoreLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(StorageLive));
2462
- var GiftWrapWithDeps = GiftWrapLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(EpochStoreWithDeps));
2463
- var PublishQueueWithDeps = PublishQueueLive.pipe(Layer9.provide(StorageLive), Layer9.provide(RelayLive));
2464
- var AllServicesLive = Layer9.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2465
- var TablinumLive = Layer9.effect(Tablinum, Effect21.gen(function* () {
2466
- const config = yield* Config;
2467
- const identity = yield* Identity;
2468
- const epochStore = yield* EpochStore;
2469
- const storage = yield* Storage;
2470
- const relay = yield* Relay;
2471
- const giftWrap = yield* GiftWrap3;
2472
- const publishQueue = yield* PublishQueue;
2473
- const syncStatus = yield* SyncStatus;
2474
- const scope = yield* Effect21.scope;
2475
- const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2476
- const pubsub = yield* PubSub2.unbounded();
2477
- const replayingRef = yield* Ref5.make(false);
2478
- const closedRef = yield* Ref5.make(false);
2479
- const watchCtx = { pubsub, replayingRef };
2480
- const schemaEntries = Object.entries(config.schema);
2481
- const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2482
- const knownCollections = new Map(allSchemaEntries.map(([, def]) => [def.name, def.eventRetention]));
2483
- let notifyAuthor;
2484
- 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);
2485
- const onWrite = (event) => Effect21.gen(function* () {
2486
- const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2487
- const dTag = `${event.collection}:${event.recordId}`;
2488
- const wrapResult = yield* Effect21.result(giftWrap.wrap({
2489
- kind: 1,
2490
- content,
2491
- tags: [["d", dTag]],
2492
- created_at: Math.floor(event.createdAt / 1000)
2493
- }));
2494
- if (wrapResult._tag === "Failure") {
2495
- reportSyncError(config.onSyncError, wrapResult.failure);
2496
- return;
2497
- }
2498
- const gw = wrapResult.success;
2499
- yield* storage.putGiftWrap({ id: gw.id, createdAt: gw.created_at });
2500
- yield* Effect21.forkIn(Effect21.gen(function* () {
2501
- const publishResult = yield* Effect21.result(syncHandle.publishLocal({
2502
- id: gw.id,
2503
- event: gw,
2504
- createdAt: gw.created_at
2505
- }));
2506
- if (publishResult._tag === "Failure") {
2507
- reportSyncError(config.onSyncError, publishResult.failure);
2778
+ var EpochStoreWithDeps = EpochStoreLive.pipe(
2779
+ Layer9.provide(IdentityWithDeps),
2780
+ Layer9.provide(StorageLive)
2781
+ );
2782
+ var GiftWrapWithDeps = GiftWrapLive.pipe(
2783
+ Layer9.provide(IdentityWithDeps),
2784
+ Layer9.provide(EpochStoreWithDeps)
2785
+ );
2786
+ var PublishQueueWithDeps = PublishQueueLive.pipe(
2787
+ Layer9.provide(StorageLive),
2788
+ Layer9.provide(RelayLive)
2789
+ );
2790
+ var AllServicesLive = Layer9.mergeAll(
2791
+ IdentityWithDeps,
2792
+ EpochStoreWithDeps,
2793
+ StorageLive,
2794
+ RelayLive,
2795
+ GiftWrapWithDeps,
2796
+ PublishQueueWithDeps,
2797
+ SyncStatusLive
2798
+ );
2799
+ var TablinumLive = Layer9.effect(
2800
+ Tablinum,
2801
+ Effect21.gen(function* () {
2802
+ const config = yield* Config;
2803
+ const identity = yield* Identity;
2804
+ const epochStore = yield* EpochStore;
2805
+ const storage = yield* Storage;
2806
+ const relay = yield* Relay;
2807
+ const giftWrap = yield* GiftWrap3;
2808
+ const publishQueue = yield* PublishQueue;
2809
+ const syncStatus = yield* SyncStatus;
2810
+ const scope = yield* Effect21.scope;
2811
+ const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2812
+ const pubsub = yield* PubSub2.unbounded();
2813
+ const replayingRef = yield* Ref5.make(false);
2814
+ const closedRef = yield* Ref5.make(false);
2815
+ const watchCtx = { pubsub, replayingRef };
2816
+ const schemaEntries = Object.entries(config.schema);
2817
+ const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2818
+ const knownCollections = new Map(
2819
+ allSchemaEntries.map(([, def]) => [def.name, def.eventRetention])
2820
+ );
2821
+ let notifyAuthor;
2822
+ const syncHandle = createSyncHandle(
2823
+ storage,
2824
+ giftWrap,
2825
+ relay,
2826
+ publishQueue,
2827
+ syncStatus,
2828
+ watchCtx,
2829
+ config.relays,
2830
+ knownCollections,
2831
+ epochStore,
2832
+ identity.privateKey,
2833
+ identity.publicKey,
2834
+ scope,
2835
+ config.logLevel,
2836
+ config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : void 0,
2837
+ (pubkey) => notifyAuthor?.(pubkey),
2838
+ config.onRemoved,
2839
+ config.onMembersChanged
2840
+ );
2841
+ const onWrite = (event) => Effect21.gen(function* () {
2842
+ const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2843
+ const dTag = `${event.collection}:${event.recordId}`;
2844
+ const wrapResult = yield* Effect21.result(
2845
+ giftWrap.wrap({
2846
+ kind: 1,
2847
+ content,
2848
+ tags: [["d", dTag]],
2849
+ created_at: Math.floor(event.createdAt / 1e3)
2850
+ })
2851
+ );
2852
+ if (wrapResult._tag === "Failure") {
2853
+ reportSyncError(config.onSyncError, wrapResult.failure);
2854
+ return;
2508
2855
  }
2509
- }), scope);
2510
- });
2511
- const knownAuthors = new Set;
2512
- const putMemberRecord = (record) => Effect21.gen(function* () {
2513
- const existing = yield* storage.getRecord("_members", record.id);
2514
- const event = {
2515
- id: uuidv7(),
2516
- collection: "_members",
2517
- recordId: record.id,
2518
- kind: existing ? "u" : "c",
2519
- data: record,
2520
- createdAt: Date.now(),
2521
- author: identity.publicKey
2522
- };
2523
- yield* storage.putEvent(event);
2524
- yield* applyEvent(storage, event);
2525
- yield* onWrite(event);
2526
- yield* notifyChange(watchCtx, {
2527
- collection: "_members",
2528
- recordId: record.id,
2529
- kind: existing ? "update" : "create"
2856
+ const gw = wrapResult.success;
2857
+ yield* storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
2858
+ yield* Effect21.forkIn(
2859
+ Effect21.gen(function* () {
2860
+ const publishResult = yield* Effect21.result(
2861
+ syncHandle.publishLocal({
2862
+ id: gw.id,
2863
+ event: gw,
2864
+ createdAt: gw.created_at
2865
+ })
2866
+ );
2867
+ if (publishResult._tag === "Failure") {
2868
+ reportSyncError(config.onSyncError, publishResult.failure);
2869
+ }
2870
+ }),
2871
+ scope
2872
+ );
2530
2873
  });
2531
- config.onMembersChanged?.();
2532
- });
2533
- notifyAuthor = (pubkey) => {
2534
- if (knownAuthors.has(pubkey))
2535
- return;
2536
- knownAuthors.add(pubkey);
2537
- Effect21.runFork(Effect21.gen(function* () {
2538
- const existing = yield* storage.getRecord("_members", pubkey);
2539
- if (!existing) {
2540
- yield* putMemberRecord({
2541
- id: pubkey,
2542
- addedAt: Date.now(),
2543
- addedInEpoch: getCurrentEpoch(epochStore).id
2544
- });
2545
- }
2546
- const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none())));
2547
- if (Option9.isSome(profileOpt)) {
2548
- const current = yield* storage.getRecord("_members", pubkey);
2549
- if (current) {
2550
- yield* storage.putRecord("_members", {
2551
- ...current,
2552
- ...profileOpt.value
2553
- });
2554
- yield* notifyChange(watchCtx, {
2555
- collection: "_members",
2556
- recordId: pubkey,
2557
- kind: "update"
2558
- });
2559
- config.onMembersChanged?.();
2560
- }
2561
- }
2562
- }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope)));
2563
- };
2564
- const handles = new Map;
2565
- for (const [, def] of allSchemaEntries) {
2566
- const validator = buildValidator(def.name, def);
2567
- const partialValidator = buildPartialValidator(def.name, def);
2568
- const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite, config.logLevel);
2569
- handles.set(def.name, handle);
2570
- }
2571
- yield* syncHandle.startSubscription();
2572
- yield* Effect21.logInfo("Tablinum ready", {
2573
- dbName: config.dbName,
2574
- collections: schemaEntries.map(([name]) => name),
2575
- relays: config.relays
2576
- });
2577
- const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2578
- if (!selfMember) {
2579
- yield* putMemberRecord({
2580
- id: identity.publicKey,
2581
- addedAt: Date.now(),
2582
- addedInEpoch: getCurrentEpoch(epochStore).id
2874
+ const knownAuthors = /* @__PURE__ */ new Set();
2875
+ const putMemberRecord = (record) => Effect21.gen(function* () {
2876
+ const existing = yield* storage.getRecord("_members", record.id);
2877
+ const event = {
2878
+ id: uuidv7(),
2879
+ collection: "_members",
2880
+ recordId: record.id,
2881
+ kind: existing ? "u" : "c",
2882
+ data: record,
2883
+ createdAt: Date.now(),
2884
+ author: identity.publicKey
2885
+ };
2886
+ yield* storage.putEvent(event);
2887
+ yield* applyEvent(storage, event);
2888
+ yield* onWrite(event);
2889
+ yield* notifyChange(watchCtx, {
2890
+ collection: "_members",
2891
+ recordId: record.id,
2892
+ kind: existing ? "update" : "create"
2893
+ });
2894
+ config.onMembersChanged?.();
2583
2895
  });
2584
- }
2585
- const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2586
- const ensureOpen = (effect) => withLog(Effect21.gen(function* () {
2587
- if (yield* Ref5.get(closedRef)) {
2588
- return yield* new StorageError({ message: "Database is closed" });
2589
- }
2590
- return yield* effect;
2591
- }));
2592
- const ensureSyncOpen = (effect) => withLog(Effect21.gen(function* () {
2593
- if (yield* Ref5.get(closedRef)) {
2594
- return yield* new SyncError({ message: "Database is closed", phase: "init" });
2595
- }
2596
- return yield* effect;
2597
- }));
2598
- const dbHandle = {
2599
- collection: (name) => {
2600
- const handle = handles.get(name);
2601
- if (!handle)
2602
- throw new Error(`Collection "${name}" not found in schema`);
2603
- return handle;
2604
- },
2605
- publicKey: identity.publicKey,
2606
- members: handles.get("_members"),
2607
- exportKey: () => identity.exportKey(),
2608
- exportInvite: () => ({
2609
- epochKeys: [...exportEpochKeys(epochStore)],
2610
- relays: [...config.relays],
2611
- dbName: config.dbName
2612
- }),
2613
- close: () => withLog(Effect21.gen(function* () {
2614
- if (yield* Ref5.get(closedRef))
2615
- return;
2616
- yield* Ref5.set(closedRef, true);
2617
- yield* Scope4.close(scope, Exit.void);
2618
- })),
2619
- rebuild: () => ensureOpen(rebuild(storage, allSchemaEntries.map(([, def]) => def.name))),
2620
- sync: () => ensureSyncOpen(syncHandle.sync()),
2621
- getSyncStatus: () => syncStatus.get(),
2622
- subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
2623
- pendingCount: () => publishQueue.size(),
2624
- subscribePendingCount: (callback) => publishQueue.subscribe(callback),
2625
- getRelayStatus: () => relay.getStatus(),
2626
- subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
2627
- addMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2628
- const existing = yield* storage.getRecord("_members", pubkey);
2629
- if (existing && !existing.removedAt)
2630
- return;
2896
+ notifyAuthor = (pubkey) => {
2897
+ if (knownAuthors.has(pubkey)) return;
2898
+ knownAuthors.add(pubkey);
2899
+ Effect21.runFork(
2900
+ Effect21.gen(function* () {
2901
+ const existing = yield* storage.getRecord("_members", pubkey);
2902
+ if (!existing) {
2903
+ yield* putMemberRecord({
2904
+ id: pubkey,
2905
+ addedAt: Date.now(),
2906
+ addedInEpoch: getCurrentEpoch(epochStore).id
2907
+ });
2908
+ }
2909
+ const profileOpt = yield* fetchAuthorProfile(relay, config.relays, pubkey).pipe(
2910
+ Effect21.catchTag("RelayError", () => Effect21.succeed(Option9.none()))
2911
+ );
2912
+ if (Option9.isSome(profileOpt)) {
2913
+ const current = yield* storage.getRecord("_members", pubkey);
2914
+ if (current) {
2915
+ yield* storage.putRecord("_members", {
2916
+ ...current,
2917
+ ...profileOpt.value
2918
+ });
2919
+ yield* notifyChange(watchCtx, {
2920
+ collection: "_members",
2921
+ recordId: pubkey,
2922
+ kind: "update"
2923
+ });
2924
+ config.onMembersChanged?.();
2925
+ }
2926
+ }
2927
+ }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope))
2928
+ );
2929
+ };
2930
+ const handles = /* @__PURE__ */ new Map();
2931
+ for (const [, def] of allSchemaEntries) {
2932
+ const validator = buildValidator(def.name, def);
2933
+ const partialValidator = buildPartialValidator(def.name, def);
2934
+ const handle = createCollectionHandle(
2935
+ def,
2936
+ storage,
2937
+ watchCtx,
2938
+ validator,
2939
+ partialValidator,
2940
+ uuidv7,
2941
+ identity.publicKey,
2942
+ onWrite,
2943
+ config.logLevel
2944
+ );
2945
+ handles.set(def.name, handle);
2946
+ }
2947
+ yield* syncHandle.startSubscription();
2948
+ yield* Effect21.logInfo("Tablinum ready", {
2949
+ dbName: config.dbName,
2950
+ collections: schemaEntries.map(([name]) => name),
2951
+ relays: config.relays
2952
+ });
2953
+ const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2954
+ if (!selfMember) {
2631
2955
  yield* putMemberRecord({
2632
- id: pubkey,
2956
+ id: identity.publicKey,
2633
2957
  addedAt: Date.now(),
2634
- addedInEpoch: getCurrentEpoch(epochStore).id,
2635
- ...existing ? { removedAt: undefined, removedInEpoch: undefined } : {}
2958
+ addedInEpoch: getCurrentEpoch(epochStore).id
2636
2959
  });
2637
- })),
2638
- removeMember: (pubkey) => ensureOpen(Effect21.gen(function* () {
2639
- const allMembers = yield* storage.getAllRecords("_members");
2640
- const activeMembers = allMembers.filter((member) => !member.removedAt && member.id !== pubkey);
2641
- const activePubkeys = activeMembers.map((member) => member.id);
2642
- const result = createRotation(epochStore, identity.privateKey, identity.publicKey, activePubkeys, [pubkey]);
2643
- addEpoch(epochStore, result.epoch);
2644
- epochStore.currentEpochId = result.epoch.id;
2645
- yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
2646
- const memberRecord = yield* storage.getRecord("_members", pubkey);
2647
- yield* putMemberRecord({
2648
- ...memberRecord ?? {
2649
- id: pubkey,
2650
- addedAt: 0,
2651
- addedInEpoch: EpochId("epoch-0")
2652
- },
2653
- removedAt: Date.now(),
2654
- removedInEpoch: result.epoch.id
2655
- });
2656
- 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 });
2657
- 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 });
2658
- yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
2659
- })),
2660
- getMembers: () => ensureOpen(Effect21.gen(function* () {
2661
- const allRecords = yield* storage.getAllRecords("_members");
2662
- return allRecords.filter((record) => !record._d).map(mapMemberRecord);
2663
- })),
2664
- getProfile: () => ensureOpen(Effect21.gen(function* () {
2665
- const record = yield* storage.getRecord("_members", identity.publicKey);
2666
- if (!record)
2667
- return {};
2668
- const profile = {};
2669
- if (record.name !== undefined)
2670
- profile.name = record.name;
2671
- if (record.picture !== undefined)
2672
- profile.picture = record.picture;
2673
- if (record.about !== undefined)
2674
- profile.about = record.about;
2675
- if (record.nip05 !== undefined)
2676
- profile.nip05 = record.nip05;
2677
- return profile;
2678
- })),
2679
- setProfile: (profile) => ensureOpen(Effect21.gen(function* () {
2680
- const existing = yield* storage.getRecord("_members", identity.publicKey);
2681
- if (!existing) {
2682
- return yield* new ValidationError({ message: "Current user is not a member" });
2683
- }
2684
- const { _d, _u, _a, _e, ...memberFields } = existing;
2685
- yield* putMemberRecord({ ...memberFields, ...profile });
2686
- }))
2687
- };
2688
- return dbHandle;
2689
- }).pipe(Effect21.withLogSpan("tablinum.init"))).pipe(Layer9.provide(AllServicesLive));
2960
+ }
2961
+ const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2962
+ const ensureOpen = (effect) => withLog(
2963
+ Effect21.gen(function* () {
2964
+ if (yield* Ref5.get(closedRef)) {
2965
+ return yield* new StorageError({ message: "Database is closed" });
2966
+ }
2967
+ return yield* effect;
2968
+ })
2969
+ );
2970
+ const ensureSyncOpen = (effect) => withLog(
2971
+ Effect21.gen(function* () {
2972
+ if (yield* Ref5.get(closedRef)) {
2973
+ return yield* new SyncError({ message: "Database is closed", phase: "init" });
2974
+ }
2975
+ return yield* effect;
2976
+ })
2977
+ );
2978
+ const dbHandle = {
2979
+ collection: (name) => {
2980
+ const handle = handles.get(name);
2981
+ if (!handle) throw new Error(`Collection "${name}" not found in schema`);
2982
+ return handle;
2983
+ },
2984
+ publicKey: identity.publicKey,
2985
+ members: handles.get("_members"),
2986
+ exportKey: () => identity.exportKey(),
2987
+ exportInvite: () => ({
2988
+ epochKeys: [...exportEpochKeys(epochStore)],
2989
+ relays: [...config.relays],
2990
+ dbName: config.dbName
2991
+ }),
2992
+ close: () => withLog(
2993
+ Effect21.gen(function* () {
2994
+ if (yield* Ref5.get(closedRef)) return;
2995
+ yield* Ref5.set(closedRef, true);
2996
+ syncHandle.stopHealing();
2997
+ yield* Scope4.close(scope, Exit.void);
2998
+ })
2999
+ ),
3000
+ rebuild: () => ensureOpen(
3001
+ rebuild(
3002
+ storage,
3003
+ allSchemaEntries.map(([, def]) => def.name)
3004
+ )
3005
+ ),
3006
+ sync: () => ensureSyncOpen(
3007
+ syncHandle.sync().pipe(
3008
+ Effect21.tap(
3009
+ () => Effect21.sync(() => {
3010
+ syncHandle.startHealing();
3011
+ })
3012
+ )
3013
+ )
3014
+ ),
3015
+ getSyncStatus: () => syncStatus.get(),
3016
+ subscribeSyncStatus: (callback) => syncStatus.subscribe(callback),
3017
+ pendingCount: () => publishQueue.size(),
3018
+ subscribePendingCount: (callback) => publishQueue.subscribe(callback),
3019
+ getRelayStatus: () => relay.getStatus(),
3020
+ subscribeRelayStatus: (callback) => relay.subscribeStatus(callback),
3021
+ addMember: (pubkey) => ensureOpen(
3022
+ Effect21.gen(function* () {
3023
+ const existing = yield* storage.getRecord("_members", pubkey);
3024
+ if (existing && !existing.removedAt) return;
3025
+ yield* putMemberRecord({
3026
+ id: pubkey,
3027
+ addedAt: Date.now(),
3028
+ addedInEpoch: getCurrentEpoch(epochStore).id,
3029
+ ...existing ? { removedAt: void 0, removedInEpoch: void 0 } : {}
3030
+ });
3031
+ })
3032
+ ),
3033
+ removeMember: (pubkey) => ensureOpen(
3034
+ Effect21.gen(function* () {
3035
+ const allMembers = yield* storage.getAllRecords("_members");
3036
+ const activeMembers = allMembers.filter(
3037
+ (member) => !member.removedAt && member.id !== pubkey
3038
+ );
3039
+ const activePubkeys = activeMembers.map((member) => member.id);
3040
+ const result = createRotation(
3041
+ epochStore,
3042
+ identity.privateKey,
3043
+ identity.publicKey,
3044
+ activePubkeys,
3045
+ [pubkey]
3046
+ );
3047
+ addEpoch(epochStore, result.epoch);
3048
+ epochStore.currentEpochId = result.epoch.id;
3049
+ yield* storage.putMeta("epochs", stringifyEpochStore(epochStore));
3050
+ const memberRecord = yield* storage.getRecord("_members", pubkey);
3051
+ yield* putMemberRecord({
3052
+ ...memberRecord ?? {
3053
+ id: pubkey,
3054
+ addedAt: 0,
3055
+ addedInEpoch: EpochId("epoch-0")
3056
+ },
3057
+ removedAt: Date.now(),
3058
+ removedInEpoch: result.epoch.id
3059
+ });
3060
+ yield* Effect21.forEach(
3061
+ result.wrappedEvents,
3062
+ (wrappedEvent) => relay.publish(wrappedEvent, [...config.relays]).pipe(
3063
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3064
+ Effect21.ignore
3065
+ ),
3066
+ { discard: true }
3067
+ );
3068
+ yield* Effect21.forEach(
3069
+ result.removalNotices,
3070
+ (notice) => relay.publish(notice, [...config.relays]).pipe(
3071
+ Effect21.tapError((e) => Effect21.sync(() => reportSyncError(config.onSyncError, e))),
3072
+ Effect21.ignore
3073
+ ),
3074
+ { discard: true }
3075
+ );
3076
+ yield* syncHandle.addEpochSubscription(result.epoch.publicKey);
3077
+ })
3078
+ ),
3079
+ getMembers: () => ensureOpen(
3080
+ Effect21.gen(function* () {
3081
+ const allRecords = yield* storage.getAllRecords("_members");
3082
+ return allRecords.filter((record) => !record._d).map(mapMemberRecord);
3083
+ })
3084
+ ),
3085
+ getProfile: () => ensureOpen(
3086
+ Effect21.gen(function* () {
3087
+ const record = yield* storage.getRecord("_members", identity.publicKey);
3088
+ if (!record) return {};
3089
+ const profile = {};
3090
+ if (record.name !== void 0) profile.name = record.name;
3091
+ if (record.picture !== void 0) profile.picture = record.picture;
3092
+ if (record.about !== void 0) profile.about = record.about;
3093
+ if (record.nip05 !== void 0) profile.nip05 = record.nip05;
3094
+ return profile;
3095
+ })
3096
+ ),
3097
+ setProfile: (profile) => ensureOpen(
3098
+ Effect21.gen(function* () {
3099
+ const existing = yield* storage.getRecord("_members", identity.publicKey);
3100
+ if (!existing) {
3101
+ return yield* new ValidationError({ message: "Current user is not a member" });
3102
+ }
3103
+ const { _d, _u, _a, _e, ...memberFields } = existing;
3104
+ yield* putMemberRecord({ ...memberFields, ...profile });
3105
+ })
3106
+ )
3107
+ };
3108
+ return dbHandle;
3109
+ }).pipe(Effect21.withLogSpan("tablinum.init"))
3110
+ ).pipe(Layer9.provide(AllServicesLive));
2690
3111
 
2691
3112
  // src/db/create-tablinum.ts
2692
3113
  function resolveLogLevel(input) {
2693
- if (input === undefined || input === "none")
2694
- return "None";
3114
+ if (input === void 0 || input === "none") return "None";
2695
3115
  switch (input) {
2696
3116
  case "debug":
2697
3117
  return "Debug";
@@ -2772,20 +3192,20 @@ function decodeInvite(encoded) {
2772
3192
  }
2773
3193
  }
2774
3194
  export {
2775
- field,
2776
- encodeInvite,
2777
- decodeInvite,
2778
- createTablinum,
2779
- collection,
2780
- ValidationError,
2781
- TablinumLive,
2782
- SyncError,
2783
- StorageError,
2784
- RelayError,
2785
- NotFoundError,
2786
- LogLevel,
2787
- EpochId,
2788
- DatabaseName,
3195
+ ClosedError,
2789
3196
  CryptoError,
2790
- ClosedError
3197
+ DatabaseName,
3198
+ EpochId,
3199
+ LogLevel,
3200
+ NotFoundError,
3201
+ RelayError,
3202
+ StorageError,
3203
+ SyncError,
3204
+ TablinumLive,
3205
+ ValidationError,
3206
+ collection,
3207
+ createTablinum,
3208
+ decodeInvite,
3209
+ encodeInvite,
3210
+ field
2791
3211
  };