tablinum 0.4.0 → 0.5.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/README.md CHANGED
@@ -198,6 +198,66 @@ Use it in a component:
198
198
  - **`db.status`** tracks initialization and terminal state; **`db.syncStatus`** tracks sync activity
199
199
  - **`createTablinum(config)`** still exists as a convenience and resolves once `db.ready` completes
200
200
 
201
+ ## Logging
202
+
203
+ Tablinum uses [Effect's built-in logging](https://effect.website/docs/observability/logging/) under the hood. By default, logging is completely silent. Set `logLevel` in your config to enable it:
204
+
205
+ ```typescript
206
+ const db = yield* createTablinum({
207
+ schema,
208
+ relays: ["wss://relay.example.com"],
209
+ logLevel: "debug", // "debug" | "info" | "warning" | "error" | "none"
210
+ });
211
+ ```
212
+
213
+ Wire it to an environment variable for easy toggling:
214
+
215
+ ```typescript
216
+ // Effect API
217
+ createTablinum({
218
+ schema,
219
+ relays: ["wss://relay.example.com"],
220
+ logLevel: import.meta.env.VITE_LOG_LEVEL ?? "none",
221
+ });
222
+
223
+ // Svelte API
224
+ new Tablinum({
225
+ schema,
226
+ relays: ["wss://relay.example.com"],
227
+ logLevel: import.meta.env.VITE_LOG_LEVEL ?? "none",
228
+ });
229
+ ```
230
+
231
+ ### Log levels
232
+
233
+ | Level | What you see |
234
+ |-------|-------------|
235
+ | `"none"` | Nothing (default) |
236
+ | `"error"` | Unrecoverable failures |
237
+ | `"warning"` | Recoverable issues (e.g. rejected writes from removed members) |
238
+ | `"info"` | Lifecycle milestones — storage opened, identity loaded, sync started/complete |
239
+ | `"debug"` | Everything above plus CRUD operations (with record data), relay reconciliation details, gift wrap processing |
240
+
241
+ ### Log spans
242
+
243
+ Key operations include timing spans that appear automatically in log output:
244
+
245
+ ```
246
+ [11:50:31] INFO (#1) tablinum.init=13ms: Tablinum ready { ... }
247
+ [11:50:41] INFO (#1) tablinum.sync=520ms: Sync complete { changed: ["todos"] }
248
+ ```
249
+
250
+ Spans: `tablinum.init`, `tablinum.sync`, `tablinum.syncRelay`, `tablinum.negentropy`.
251
+
252
+ ### Using Effect's LogLevel type
253
+
254
+ Power users can also pass Effect's `LogLevel` type directly:
255
+
256
+ ```typescript
257
+ import { LogLevel } from "tablinum";
258
+ createTablinum({ ..., logLevel: LogLevel.Debug });
259
+ ```
260
+
201
261
  ## How it works
202
262
 
203
263
  Tablinum is built on [Effect](https://effect.website) and stores all data locally in [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).
@@ -1,4 +1,5 @@
1
1
  import { Effect, Option, Stream } from "effect";
2
+ import type { LogLevel } from "effect";
2
3
  import type { CollectionDef, CollectionFields } from "../schema/collection.ts";
3
4
  import type { InferRecord } from "../schema/types.ts";
4
5
  import type { RecordValidator, PartialValidator } from "../schema/validate.ts";
@@ -20,4 +21,4 @@ export interface CollectionHandle<C extends CollectionDef<CollectionFields>> {
20
21
  readonly orderBy: (field: string & keyof Omit<InferRecord<C>, "id">) => QueryBuilder<InferRecord<C>>;
21
22
  }
22
23
  export type OnWriteCallback = (event: StoredEvent) => Effect.Effect<void, StorageError>;
23
- export declare function createCollectionHandle<C extends CollectionDef<CollectionFields>>(def: C, storage: IDBStorageHandle, watchCtx: WatchContext, validator: RecordValidator<C["fields"]>, partialValidator: PartialValidator<C["fields"]>, makeEventId: () => string, localAuthor?: string, onWrite?: OnWriteCallback): CollectionHandle<C>;
24
+ export declare function createCollectionHandle<C extends CollectionDef<CollectionFields>>(def: C, storage: IDBStorageHandle, watchCtx: WatchContext, validator: RecordValidator<C["fields"]>, partialValidator: PartialValidator<C["fields"]>, makeEventId: () => string, localAuthor?: string, onWrite?: OnWriteCallback, logLevel?: LogLevel.LogLevel): CollectionHandle<C>;
@@ -1,14 +1,18 @@
1
1
  import { Effect, Scope } from "effect";
2
+ import type { LogLevel } from "effect";
2
3
  import type { SchemaConfig } from "../schema/types.ts";
3
4
  import type { DatabaseHandle } from "./database-handle.ts";
4
5
  import type { EpochKeyInput } from "./epoch.ts";
5
6
  import { CryptoError, StorageError, ValidationError } from "../errors.ts";
7
+ export type TablinumLogLevel = "debug" | "info" | "warning" | "error" | "none" | LogLevel.LogLevel;
8
+ export declare function resolveLogLevel(input: TablinumLogLevel | undefined): LogLevel.LogLevel;
6
9
  export interface TablinumConfig<S extends SchemaConfig> {
7
10
  readonly schema: S;
8
11
  readonly relays: readonly string[];
9
12
  readonly privateKey?: Uint8Array | undefined;
10
13
  readonly epochKeys?: ReadonlyArray<EpochKeyInput> | undefined;
11
14
  readonly dbName?: string | undefined;
15
+ readonly logLevel?: TablinumLogLevel | undefined;
12
16
  readonly onSyncError?: ((error: Error) => void) | undefined;
13
17
  readonly onRemoved?: ((info: {
14
18
  epochId: string;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ export type { CollectionDef, CollectionFields } from "./schema/collection.ts";
4
4
  export type { FieldDef, FieldKind } from "./schema/field.ts";
5
5
  export type { InferRecord, SchemaConfig } from "./schema/types.ts";
6
6
  export { createTablinum } from "./db/create-tablinum.ts";
7
- export type { TablinumConfig } from "./db/create-tablinum.ts";
7
+ export type { TablinumConfig, TablinumLogLevel } from "./db/create-tablinum.ts";
8
+ export { LogLevel } from "effect";
8
9
  export type { DatabaseHandle, SyncStatus } from "./db/database-handle.ts";
9
10
  export { encodeInvite, decodeInvite } from "./db/invite.ts";
10
11
  export type { Invite } from "./db/invite.ts";
package/dist/index.js CHANGED
@@ -54,7 +54,7 @@ function collection(name, fields, options) {
54
54
  return { _tag: "CollectionDef", name, fields, indices, eventRetention };
55
55
  }
56
56
  // src/db/create-tablinum.ts
57
- import { Effect as Effect22, Layer as Layer9, ServiceMap as ServiceMap10 } from "effect";
57
+ import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
58
58
 
59
59
  // src/db/runtime-config.ts
60
60
  import { Effect, Schema as Schema2 } from "effect";
@@ -220,7 +220,7 @@ class Tablinum extends ServiceMap2.Service()("tablinum/Tablinum") {
220
220
  }
221
221
 
222
222
  // src/layers/TablinumLive.ts
223
- import { Effect as Effect21, Exit, Layer as Layer8, Option as Option9, PubSub as PubSub2, Ref as Ref5, Scope as Scope4 } from "effect";
223
+ 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
224
 
225
225
  // src/crud/watch.ts
226
226
  import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
@@ -434,7 +434,7 @@ function buildPartialValidator(collectionName, def) {
434
434
  }
435
435
 
436
436
  // src/crud/collection-handle.ts
437
- import { Effect as Effect6, Option as Option3 } from "effect";
437
+ import { Effect as Effect6, Option as Option3, References } from "effect";
438
438
 
439
439
  // src/utils/uuid.ts
440
440
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -682,8 +682,9 @@ function mapRecord(record) {
682
682
  const { _d, _u, _a, _e, ...fields } = record;
683
683
  return fields;
684
684
  }
685
- function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite) {
685
+ function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
686
686
  const collectionName = def.name;
687
+ const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
687
688
  const commitEvent = (event) => Effect6.gen(function* () {
688
689
  yield* storage.putEvent(event);
689
690
  yield* applyEvent(storage, event);
@@ -696,7 +697,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
696
697
  });
697
698
  });
698
699
  const handle = {
699
- add: (data) => Effect6.gen(function* () {
700
+ add: (data) => withLog(Effect6.gen(function* () {
700
701
  const id = uuidv7();
701
702
  const fullRecord = { id, ...data };
702
703
  yield* validator(fullRecord);
@@ -710,9 +711,10 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
710
711
  author: localAuthor
711
712
  };
712
713
  yield* commitEvent(event);
714
+ yield* Effect6.logDebug("Record added", { collection: collectionName, recordId: id, data: fullRecord });
713
715
  return id;
714
- }),
715
- update: (id, data) => Effect6.gen(function* () {
716
+ })),
717
+ update: (id, data) => withLog(Effect6.gen(function* () {
716
718
  const existing = yield* storage.getRecord(collectionName, id);
717
719
  if (!existing || existing._d) {
718
720
  return yield* new NotFoundError({
@@ -735,9 +737,10 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
735
737
  author: localAuthor
736
738
  };
737
739
  yield* commitEvent(event);
740
+ yield* Effect6.logDebug("Record updated", { collection: collectionName, recordId: id, data: diff });
738
741
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
739
- }),
740
- delete: (id) => Effect6.gen(function* () {
742
+ })),
743
+ delete: (id) => withLog(Effect6.gen(function* () {
741
744
  const existing = yield* storage.getRecord(collectionName, id);
742
745
  if (!existing || existing._d) {
743
746
  return yield* new NotFoundError({
@@ -755,8 +758,9 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
755
758
  author: localAuthor
756
759
  };
757
760
  yield* commitEvent(event);
761
+ yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
758
762
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
759
- }),
763
+ })),
760
764
  undo: (id) => Effect6.gen(function* () {
761
765
  const existing = yield* storage.getRecord(collectionName, id);
762
766
  if (!existing) {
@@ -805,7 +809,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
805
809
  }
806
810
 
807
811
  // src/sync/sync-service.ts
808
- import { Effect as Effect8, Option as Option5, Ref as Ref3, Schedule } from "effect";
812
+ import { Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
809
813
  import { unwrapEvent } from "nostr-tools/nip59";
810
814
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
811
815
 
@@ -1376,8 +1380,13 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1376
1380
  allNeedIds.push(id);
1377
1381
  currentMsg = nextMsg;
1378
1382
  }
1383
+ yield* Effect7.logDebug("Negentropy reconciliation complete", {
1384
+ relay: relayUrl,
1385
+ have: allHaveIds.length,
1386
+ need: allNeedIds.length
1387
+ });
1379
1388
  return { haveIds: allHaveIds, needIds: allNeedIds };
1380
- });
1389
+ }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1381
1390
  }
1382
1391
 
1383
1392
  // src/db/key-rotation.ts
@@ -1463,7 +1472,8 @@ function parseRemovalNotice(content, dTag) {
1463
1472
  }
1464
1473
 
1465
1474
  // src/sync/sync-service.ts
1466
- function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1475
+ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, logLevel, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1476
+ const logLayer = Layer.succeed(References2.MinimumLogLevel, logLevel);
1467
1477
  const getSubscriptionPubKeys = () => {
1468
1478
  return getAllPublicKeys(epochStore);
1469
1479
  };
@@ -1473,7 +1483,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1473
1483
  kind: "create"
1474
1484
  });
1475
1485
  const forkHandled = (effect) => {
1476
- Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.forkIn(scope)));
1486
+ Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.provide(logLayer), Effect8.forkIn(scope)));
1477
1487
  };
1478
1488
  let autoFlushActive = false;
1479
1489
  const autoFlushEffect = Effect8.gen(function* () {
@@ -1530,6 +1540,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1530
1540
  if (rumor.pubkey) {
1531
1541
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1532
1542
  if (reject) {
1543
+ yield* Effect8.logWarning("Rejected write from removed member", { author: rumor.pubkey.slice(0, 12) });
1533
1544
  yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1534
1545
  return null;
1535
1546
  }
@@ -1572,6 +1583,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1572
1583
  if (didApply && (kind === "u" || kind === "d")) {
1573
1584
  yield* pruneEvents(storage, collectionName, recordId, retention);
1574
1585
  }
1586
+ yield* Effect8.logDebug("Processed gift wrap", { collection: collectionName, recordId, kind, author: author?.slice(0, 12) });
1575
1587
  if (author && onNewAuthor) {
1576
1588
  onNewAuthor(author);
1577
1589
  }
@@ -1640,12 +1652,14 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1640
1652
  }).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1641
1653
  }), { discard: true });
1642
1654
  const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1655
+ yield* Effect8.logDebug("Syncing relay", { relay: url });
1643
1656
  const reconcileResult = yield* Effect8.result(reconcileWithRelay(storage, relay, url, Array.from(pubKeys)));
1644
1657
  if (reconcileResult._tag === "Failure") {
1645
1658
  onSyncError?.(reconcileResult.failure);
1646
1659
  return;
1647
1660
  }
1648
1661
  const { haveIds, needIds } = reconcileResult.success;
1662
+ yield* Effect8.logDebug("Relay reconciliation result", { relay: url, need: needIds.length, have: haveIds.length });
1649
1663
  if (needIds.length > 0) {
1650
1664
  const fetched = yield* relay.fetchEvents(needIds, url).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.orElseSucceed(() => []));
1651
1665
  const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
@@ -1663,9 +1677,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1663
1677
  yield* relay.publish(gw.event, [url]).pipe(Effect8.andThen(storage.stripGiftWrapBlob(id)), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1664
1678
  }), { discard: true });
1665
1679
  }
1666
- });
1680
+ }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1667
1681
  const handle = {
1668
1682
  sync: () => Effect8.gen(function* () {
1683
+ yield* Effect8.logInfo("Sync started");
1669
1684
  yield* syncStatus.set("syncing");
1670
1685
  yield* Ref3.set(watchCtx.replayingRef, true);
1671
1686
  const changedCollections = new Set;
@@ -1679,7 +1694,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1679
1694
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1680
1695
  yield* syncStatus.set("idle");
1681
1696
  })));
1682
- }),
1697
+ yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1698
+ }).pipe(Effect8.withLogSpan("tablinum.sync")),
1683
1699
  publishLocal: (giftWrap) => Effect8.gen(function* () {
1684
1700
  if (!giftWrap.event)
1685
1701
  return;
@@ -1805,7 +1821,7 @@ class SyncStatus extends ServiceMap9.Service()("tablinum/SyncStatus") {
1805
1821
  }
1806
1822
 
1807
1823
  // src/layers/IdentityLive.ts
1808
- import { Effect as Effect11, Layer } from "effect";
1824
+ import { Effect as Effect11, Layer as Layer2 } from "effect";
1809
1825
  import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1810
1826
 
1811
1827
  // src/db/identity.ts
@@ -1843,21 +1859,25 @@ function createIdentity(suppliedKey) {
1843
1859
  }
1844
1860
 
1845
1861
  // src/layers/IdentityLive.ts
1846
- var IdentityLive = Layer.effect(Identity, Effect11.gen(function* () {
1862
+ var IdentityLive = Layer2.effect(Identity, Effect11.gen(function* () {
1847
1863
  const config = yield* Config;
1848
1864
  const storage = yield* Storage;
1849
1865
  const idbKey = yield* storage.getMeta("identity_key");
1850
1866
  const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : undefined);
1851
1867
  const identity = yield* createIdentity(resolvedKey);
1852
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
+ });
1853
1873
  return identity;
1854
1874
  }));
1855
1875
 
1856
1876
  // src/layers/EpochStoreLive.ts
1857
- import { Effect as Effect12, Layer as Layer2, Option as Option7 } from "effect";
1877
+ import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
1858
1878
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
1859
1879
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
1860
- var EpochStoreLive = Layer2.effect(EpochStore, Effect12.gen(function* () {
1880
+ var EpochStoreLive = Layer3.effect(EpochStore, Effect12.gen(function* () {
1861
1881
  const config = yield* Config;
1862
1882
  const identity = yield* Identity;
1863
1883
  const storage = yield* Storage;
@@ -1865,21 +1885,24 @@ var EpochStoreLive = Layer2.effect(EpochStore, Effect12.gen(function* () {
1865
1885
  if (typeof idbRaw === "string") {
1866
1886
  const idbStore = deserializeEpochStore(idbRaw);
1867
1887
  if (Option7.isSome(idbStore)) {
1888
+ yield* Effect12.logInfo("Epoch store loaded", { source: "storage", epochs: idbStore.value.epochs.size });
1868
1889
  return idbStore.value;
1869
1890
  }
1870
1891
  }
1871
1892
  if (config.epochKeys && config.epochKeys.length > 0) {
1872
1893
  const store2 = createEpochStoreFromInputs(config.epochKeys);
1873
1894
  yield* storage.putMeta("epochs", stringifyEpochStore(store2));
1895
+ yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
1874
1896
  return store2;
1875
1897
  }
1876
1898
  const store = createEpochStoreFromInputs([{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }], { createdBy: identity.publicKey });
1877
1899
  yield* storage.putMeta("epochs", stringifyEpochStore(store));
1900
+ yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
1878
1901
  return store;
1879
1902
  }));
1880
1903
 
1881
1904
  // src/layers/StorageLive.ts
1882
- import { Effect as Effect14, Layer as Layer3 } from "effect";
1905
+ import { Effect as Effect14, Layer as Layer4 } from "effect";
1883
1906
 
1884
1907
  // src/storage/idb.ts
1885
1908
  import { Effect as Effect13 } from "effect";
@@ -2037,16 +2060,18 @@ function openIDBStorage(dbName, schema) {
2037
2060
  }
2038
2061
 
2039
2062
  // src/layers/StorageLive.ts
2040
- var StorageLive = Layer3.effect(Storage, Effect14.gen(function* () {
2063
+ var StorageLive = Layer4.effect(Storage, Effect14.gen(function* () {
2041
2064
  const config = yield* Config;
2042
- return yield* openIDBStorage(config.dbName, {
2065
+ const handle = yield* openIDBStorage(config.dbName, {
2043
2066
  ...config.schema,
2044
2067
  _members: membersCollectionDef
2045
2068
  });
2069
+ yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2070
+ return handle;
2046
2071
  }));
2047
2072
 
2048
2073
  // src/layers/RelayLive.ts
2049
- import { Layer as Layer4 } from "effect";
2074
+ import { Layer as Layer5 } from "effect";
2050
2075
 
2051
2076
  // src/sync/relay.ts
2052
2077
  import { Effect as Effect15, Option as Option8, Schema as Schema6, ScopedCache, Scope as Scope3 } from "effect";
@@ -2259,10 +2284,10 @@ function createRelayHandle() {
2259
2284
  }
2260
2285
 
2261
2286
  // src/layers/RelayLive.ts
2262
- var RelayLive = Layer4.effect(Relay, createRelayHandle());
2287
+ var RelayLive = Layer5.effect(Relay, createRelayHandle());
2263
2288
 
2264
2289
  // src/layers/GiftWrapLive.ts
2265
- import { Effect as Effect17, Layer as Layer5 } from "effect";
2290
+ import { Effect as Effect17, Layer as Layer6 } from "effect";
2266
2291
 
2267
2292
  // src/sync/gift-wrap.ts
2268
2293
  import { Effect as Effect16 } from "effect";
@@ -2299,14 +2324,14 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
2299
2324
  }
2300
2325
 
2301
2326
  // src/layers/GiftWrapLive.ts
2302
- var GiftWrapLive = Layer5.effect(GiftWrap3, Effect17.gen(function* () {
2327
+ var GiftWrapLive = Layer6.effect(GiftWrap3, Effect17.gen(function* () {
2303
2328
  const identity = yield* Identity;
2304
2329
  const epochStore = yield* EpochStore;
2305
2330
  return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2306
2331
  }));
2307
2332
 
2308
2333
  // src/layers/PublishQueueLive.ts
2309
- import { Effect as Effect19, Layer as Layer6 } from "effect";
2334
+ import { Effect as Effect19, Layer as Layer7 } from "effect";
2310
2335
 
2311
2336
  // src/sync/publish-queue.ts
2312
2337
  import { Effect as Effect18, Ref as Ref4 } from "effect";
@@ -2380,14 +2405,14 @@ function createPublishQueue(storage, relay) {
2380
2405
  }
2381
2406
 
2382
2407
  // src/layers/PublishQueueLive.ts
2383
- var PublishQueueLive = Layer6.effect(PublishQueue, Effect19.gen(function* () {
2408
+ var PublishQueueLive = Layer7.effect(PublishQueue, Effect19.gen(function* () {
2384
2409
  const storage = yield* Storage;
2385
2410
  const relay = yield* Relay;
2386
2411
  return yield* createPublishQueue(storage, relay);
2387
2412
  }));
2388
2413
 
2389
2414
  // src/layers/SyncStatusLive.ts
2390
- import { Layer as Layer7 } from "effect";
2415
+ import { Layer as Layer8 } from "effect";
2391
2416
 
2392
2417
  // src/sync/sync-status.ts
2393
2418
  import { Effect as Effect20, SubscriptionRef } from "effect";
@@ -2411,7 +2436,7 @@ function createSyncStatusHandle() {
2411
2436
  }
2412
2437
 
2413
2438
  // src/layers/SyncStatusLive.ts
2414
- var SyncStatusLive = Layer7.effect(SyncStatus, createSyncStatusHandle());
2439
+ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
2415
2440
 
2416
2441
  // src/layers/TablinumLive.ts
2417
2442
  function reportSyncError(onSyncError, error) {
@@ -2432,12 +2457,12 @@ function mapMemberRecord(record) {
2432
2457
  ...record.removedInEpoch !== undefined ? { removedInEpoch: record.removedInEpoch } : {}
2433
2458
  };
2434
2459
  }
2435
- var IdentityWithDeps = IdentityLive.pipe(Layer8.provide(StorageLive));
2436
- var EpochStoreWithDeps = EpochStoreLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(StorageLive));
2437
- var GiftWrapWithDeps = GiftWrapLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(EpochStoreWithDeps));
2438
- var PublishQueueWithDeps = PublishQueueLive.pipe(Layer8.provide(StorageLive), Layer8.provide(RelayLive));
2439
- var AllServicesLive = Layer8.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2440
- var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2460
+ 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* () {
2441
2466
  const config = yield* Config;
2442
2467
  const identity = yield* Identity;
2443
2468
  const epochStore = yield* EpochStore;
@@ -2447,6 +2472,7 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2447
2472
  const publishQueue = yield* PublishQueue;
2448
2473
  const syncStatus = yield* SyncStatus;
2449
2474
  const scope = yield* Effect21.scope;
2475
+ const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2450
2476
  const pubsub = yield* PubSub2.unbounded();
2451
2477
  const replayingRef = yield* Ref5.make(false);
2452
2478
  const closedRef = yield* Ref5.make(false);
@@ -2455,7 +2481,7 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2455
2481
  const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2456
2482
  const knownCollections = new Map(allSchemaEntries.map(([, def]) => [def.name, def.eventRetention]));
2457
2483
  let notifyAuthor;
2458
- const syncHandle = createSyncHandle(storage, giftWrap, relay, publishQueue, syncStatus, watchCtx, config.relays, knownCollections, epochStore, identity.privateKey, identity.publicKey, scope, config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : undefined, (pubkey) => notifyAuthor?.(pubkey), config.onRemoved, config.onMembersChanged);
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);
2459
2485
  const onWrite = (event) => Effect21.gen(function* () {
2460
2486
  const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2461
2487
  const dTag = `${event.collection}:${event.recordId}`;
@@ -2533,16 +2559,21 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2533
2559
  config.onMembersChanged?.();
2534
2560
  }
2535
2561
  }
2536
- }).pipe(Effect21.ignore, Effect21.forkIn(scope)));
2562
+ }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope)));
2537
2563
  };
2538
2564
  const handles = new Map;
2539
2565
  for (const [, def] of allSchemaEntries) {
2540
2566
  const validator = buildValidator(def.name, def);
2541
2567
  const partialValidator = buildPartialValidator(def.name, def);
2542
- const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite);
2568
+ const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite, config.logLevel);
2543
2569
  handles.set(def.name, handle);
2544
2570
  }
2545
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
+ });
2546
2577
  const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2547
2578
  if (!selfMember) {
2548
2579
  yield* putMemberRecord({
@@ -2551,18 +2582,19 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2551
2582
  addedInEpoch: getCurrentEpoch(epochStore).id
2552
2583
  });
2553
2584
  }
2554
- const ensureOpen = (effect) => Effect21.gen(function* () {
2585
+ const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2586
+ const ensureOpen = (effect) => withLog(Effect21.gen(function* () {
2555
2587
  if (yield* Ref5.get(closedRef)) {
2556
2588
  return yield* new StorageError({ message: "Database is closed" });
2557
2589
  }
2558
2590
  return yield* effect;
2559
- });
2560
- const ensureSyncOpen = (effect) => Effect21.gen(function* () {
2591
+ }));
2592
+ const ensureSyncOpen = (effect) => withLog(Effect21.gen(function* () {
2561
2593
  if (yield* Ref5.get(closedRef)) {
2562
2594
  return yield* new SyncError({ message: "Database is closed", phase: "init" });
2563
2595
  }
2564
2596
  return yield* effect;
2565
- });
2597
+ }));
2566
2598
  const dbHandle = {
2567
2599
  collection: (name) => {
2568
2600
  const handle = handles.get(name);
@@ -2578,12 +2610,12 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2578
2610
  relays: [...config.relays],
2579
2611
  dbName: config.dbName
2580
2612
  }),
2581
- close: () => Effect21.gen(function* () {
2613
+ close: () => withLog(Effect21.gen(function* () {
2582
2614
  if (yield* Ref5.get(closedRef))
2583
2615
  return;
2584
2616
  yield* Ref5.set(closedRef, true);
2585
2617
  yield* Scope4.close(scope, Exit.void);
2586
- }),
2618
+ })),
2587
2619
  rebuild: () => ensureOpen(rebuild(storage, allSchemaEntries.map(([, def]) => def.name))),
2588
2620
  sync: () => ensureSyncOpen(syncHandle.sync()),
2589
2621
  getSyncStatus: () => syncStatus.get(),
@@ -2654,9 +2686,25 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2654
2686
  }))
2655
2687
  };
2656
2688
  return dbHandle;
2657
- })).pipe(Layer8.provide(AllServicesLive));
2689
+ }).pipe(Effect21.withLogSpan("tablinum.init"))).pipe(Layer9.provide(AllServicesLive));
2658
2690
 
2659
2691
  // src/db/create-tablinum.ts
2692
+ function resolveLogLevel(input) {
2693
+ if (input === undefined || input === "none")
2694
+ return "None";
2695
+ switch (input) {
2696
+ case "debug":
2697
+ return "Debug";
2698
+ case "info":
2699
+ return "Info";
2700
+ case "warning":
2701
+ return "Warn";
2702
+ case "error":
2703
+ return "Error";
2704
+ default:
2705
+ return input;
2706
+ }
2707
+ }
2660
2708
  function validateConfig(config) {
2661
2709
  return Effect22.gen(function* () {
2662
2710
  if (Object.keys(config.schema).length === 0) {
@@ -2670,19 +2718,26 @@ function createTablinum(config) {
2670
2718
  return Effect22.gen(function* () {
2671
2719
  yield* validateConfig(config);
2672
2720
  const runtimeConfig = yield* resolveRuntimeConfig(config);
2721
+ const logLevel = resolveLogLevel(config.logLevel);
2673
2722
  const configValue = {
2674
2723
  ...runtimeConfig,
2675
2724
  schema: config.schema,
2725
+ logLevel,
2676
2726
  onSyncError: config.onSyncError,
2677
2727
  onRemoved: config.onRemoved,
2678
2728
  onMembersChanged: config.onMembersChanged
2679
2729
  };
2680
- const configLayer = Layer9.succeed(Config, configValue);
2681
- const fullLayer = TablinumLive.pipe(Layer9.provide(configLayer));
2682
- const ctx = yield* Layer9.build(fullLayer);
2730
+ const configLayer = Layer10.succeed(Config, configValue);
2731
+ const logLayer = Layer10.succeed(References4.MinimumLogLevel, logLevel);
2732
+ const fullLayer = TablinumLive.pipe(Layer10.provide(configLayer), Layer10.provide(logLayer));
2733
+ const ctx = yield* Layer10.build(fullLayer);
2683
2734
  return ServiceMap10.get(ctx, Tablinum);
2684
2735
  });
2685
2736
  }
2737
+
2738
+ // src/index.ts
2739
+ import { LogLevel } from "effect";
2740
+
2686
2741
  // src/db/invite.ts
2687
2742
  import { Schema as Schema7 } from "effect";
2688
2743
  var InviteSchema = Schema7.Struct({
@@ -2728,6 +2783,7 @@ export {
2728
2783
  StorageError,
2729
2784
  RelayError,
2730
2785
  NotFoundError,
2786
+ LogLevel,
2731
2787
  EpochId,
2732
2788
  DatabaseName,
2733
2789
  CryptoError,
@@ -1,8 +1,10 @@
1
1
  import { ServiceMap } from "effect";
2
+ import type { LogLevel } from "effect";
2
3
  import type { ResolvedRuntimeConfig } from "../db/runtime-config.ts";
3
4
  import type { SchemaConfig } from "../schema/types.ts";
4
5
  export interface TablinumConfigShape extends ResolvedRuntimeConfig {
5
6
  readonly schema: SchemaConfig;
7
+ readonly logLevel: LogLevel.LogLevel;
6
8
  readonly onSyncError?: ((error: Error) => void) | undefined;
7
9
  readonly onRemoved?: ((info: {
8
10
  epochId: string;
@@ -1,3 +1,4 @@
1
+ import type { LogLevel } from "effect";
1
2
  import type { CollectionDef, CollectionFields } from "../schema/collection.ts";
2
3
  import type { InferRecord } from "../schema/types.ts";
3
4
  import type { CollectionHandle } from "../crud/collection-handle.ts";
@@ -6,7 +7,7 @@ export declare class Collection<C extends CollectionDef<CollectionFields>> {
6
7
  #private;
7
8
  error: Error | null;
8
9
  /** @internal */
9
- _bind(handle: CollectionHandle<C>): void;
10
+ _bind(handle: CollectionHandle<C>, logLevel?: LogLevel.LogLevel): void;
10
11
  /** @internal */
11
12
  _fail(err: Error): void;
12
13
  add: (data: Omit<InferRecord<C>, "id">) => Promise<string>;
@@ -217,10 +217,10 @@ class NotFoundError extends Data.TaggedError("NotFoundError") {
217
217
  class ClosedError extends Data.TaggedError("ClosedError") {
218
218
  }
219
219
  // src/svelte/tablinum.svelte.ts
220
- import { Effect as Effect25, Exit as Exit2, Scope as Scope6 } from "effect";
220
+ import { Effect as Effect25, Exit as Exit2, References as References6, Scope as Scope6 } from "effect";
221
221
 
222
222
  // src/db/create-tablinum.ts
223
- import { Effect as Effect22, Layer as Layer9, ServiceMap as ServiceMap10 } from "effect";
223
+ import { Effect as Effect22, Layer as Layer10, References as References4, ServiceMap as ServiceMap10 } from "effect";
224
224
 
225
225
  // src/db/runtime-config.ts
226
226
  import { Effect, Schema as Schema3 } from "effect";
@@ -255,7 +255,7 @@ class Tablinum extends ServiceMap2.Service()("tablinum/Tablinum") {
255
255
  }
256
256
 
257
257
  // src/layers/TablinumLive.ts
258
- import { Effect as Effect21, Exit, Layer as Layer8, Option as Option9, PubSub as PubSub2, Ref as Ref5, Scope as Scope4 } from "effect";
258
+ import { Effect as Effect21, Exit, Layer as Layer9, Option as Option9, PubSub as PubSub2, References as References3, Ref as Ref5, Scope as Scope4 } from "effect";
259
259
 
260
260
  // src/crud/watch.ts
261
261
  import { Effect as Effect2, PubSub, Ref, Stream } from "effect";
@@ -469,7 +469,7 @@ function buildPartialValidator(collectionName, def) {
469
469
  }
470
470
 
471
471
  // src/crud/collection-handle.ts
472
- import { Effect as Effect6, Option as Option3 } from "effect";
472
+ import { Effect as Effect6, Option as Option3, References } from "effect";
473
473
 
474
474
  // src/utils/uuid.ts
475
475
  var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -717,8 +717,9 @@ function mapRecord(record) {
717
717
  const { _d, _u, _a, _e, ...fields } = record;
718
718
  return fields;
719
719
  }
720
- function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite) {
720
+ function createCollectionHandle(def, storage, watchCtx, validator, partialValidator, makeEventId, localAuthor, onWrite, logLevel = "None") {
721
721
  const collectionName = def.name;
722
+ const withLog = (effect) => Effect6.provideService(effect, References.MinimumLogLevel, logLevel);
722
723
  const commitEvent = (event) => Effect6.gen(function* () {
723
724
  yield* storage.putEvent(event);
724
725
  yield* applyEvent(storage, event);
@@ -731,7 +732,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
731
732
  });
732
733
  });
733
734
  const handle = {
734
- add: (data) => Effect6.gen(function* () {
735
+ add: (data) => withLog(Effect6.gen(function* () {
735
736
  const id = uuidv7();
736
737
  const fullRecord = { id, ...data };
737
738
  yield* validator(fullRecord);
@@ -745,9 +746,10 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
745
746
  author: localAuthor
746
747
  };
747
748
  yield* commitEvent(event);
749
+ yield* Effect6.logDebug("Record added", { collection: collectionName, recordId: id, data: fullRecord });
748
750
  return id;
749
- }),
750
- update: (id, data) => Effect6.gen(function* () {
751
+ })),
752
+ update: (id, data) => withLog(Effect6.gen(function* () {
751
753
  const existing = yield* storage.getRecord(collectionName, id);
752
754
  if (!existing || existing._d) {
753
755
  return yield* new NotFoundError({
@@ -770,9 +772,10 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
770
772
  author: localAuthor
771
773
  };
772
774
  yield* commitEvent(event);
775
+ yield* Effect6.logDebug("Record updated", { collection: collectionName, recordId: id, data: diff });
773
776
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
774
- }),
775
- delete: (id) => Effect6.gen(function* () {
777
+ })),
778
+ delete: (id) => withLog(Effect6.gen(function* () {
776
779
  const existing = yield* storage.getRecord(collectionName, id);
777
780
  if (!existing || existing._d) {
778
781
  return yield* new NotFoundError({
@@ -790,8 +793,9 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
790
793
  author: localAuthor
791
794
  };
792
795
  yield* commitEvent(event);
796
+ yield* Effect6.logDebug("Record deleted", { collection: collectionName, recordId: id });
793
797
  yield* pruneEvents(storage, collectionName, id, def.eventRetention);
794
- }),
798
+ })),
795
799
  undo: (id) => Effect6.gen(function* () {
796
800
  const existing = yield* storage.getRecord(collectionName, id);
797
801
  if (!existing) {
@@ -840,7 +844,7 @@ function createCollectionHandle(def, storage, watchCtx, validator, partialValida
840
844
  }
841
845
 
842
846
  // src/sync/sync-service.ts
843
- import { Effect as Effect8, Option as Option5, Ref as Ref3, Schedule } from "effect";
847
+ import { Effect as Effect8, Layer, Option as Option5, References as References2, Ref as Ref3, Schedule } from "effect";
844
848
  import { unwrapEvent } from "nostr-tools/nip59";
845
849
  import { GiftWrap as GiftWrap2 } from "nostr-tools/kinds";
846
850
 
@@ -1411,8 +1415,13 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1411
1415
  allNeedIds.push(id);
1412
1416
  currentMsg = nextMsg;
1413
1417
  }
1418
+ yield* Effect7.logDebug("Negentropy reconciliation complete", {
1419
+ relay: relayUrl,
1420
+ have: allHaveIds.length,
1421
+ need: allNeedIds.length
1422
+ });
1414
1423
  return { haveIds: allHaveIds, needIds: allNeedIds };
1415
- });
1424
+ }).pipe(Effect7.withLogSpan("tablinum.negentropy"));
1416
1425
  }
1417
1426
 
1418
1427
  // src/db/key-rotation.ts
@@ -1498,7 +1507,8 @@ function parseRemovalNotice(content, dTag) {
1498
1507
  }
1499
1508
 
1500
1509
  // src/sync/sync-service.ts
1501
- function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1510
+ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStatus, watchCtx, relayUrls, knownCollections, epochStore, personalPrivateKey, personalPublicKey, scope, logLevel, onSyncError, onNewAuthor, onRemoved, onMembersChanged) {
1511
+ const logLayer = Layer.succeed(References2.MinimumLogLevel, logLevel);
1502
1512
  const getSubscriptionPubKeys = () => {
1503
1513
  return getAllPublicKeys(epochStore);
1504
1514
  };
@@ -1508,7 +1518,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1508
1518
  kind: "create"
1509
1519
  });
1510
1520
  const forkHandled = (effect) => {
1511
- Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.forkIn(scope)));
1521
+ Effect8.runFork(effect.pipe(Effect8.tapError((e) => Effect8.sync(() => onSyncError?.(e))), Effect8.ignore, Effect8.provide(logLayer), Effect8.forkIn(scope)));
1512
1522
  };
1513
1523
  let autoFlushActive = false;
1514
1524
  const autoFlushEffect = Effect8.gen(function* () {
@@ -1565,6 +1575,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1565
1575
  if (rumor.pubkey) {
1566
1576
  const reject = yield* shouldRejectWrite(rumor.pubkey);
1567
1577
  if (reject) {
1578
+ yield* Effect8.logWarning("Rejected write from removed member", { author: rumor.pubkey.slice(0, 12) });
1568
1579
  yield* storage.putGiftWrap({ id: remoteGw.id, createdAt: remoteGw.created_at });
1569
1580
  return null;
1570
1581
  }
@@ -1607,6 +1618,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1607
1618
  if (didApply && (kind === "u" || kind === "d")) {
1608
1619
  yield* pruneEvents(storage, collectionName, recordId, retention);
1609
1620
  }
1621
+ yield* Effect8.logDebug("Processed gift wrap", { collection: collectionName, recordId, kind, author: author?.slice(0, 12) });
1610
1622
  if (author && onNewAuthor) {
1611
1623
  onNewAuthor(author);
1612
1624
  }
@@ -1675,12 +1687,14 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1675
1687
  }).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1676
1688
  }), { discard: true });
1677
1689
  const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1690
+ yield* Effect8.logDebug("Syncing relay", { relay: url });
1678
1691
  const reconcileResult = yield* Effect8.result(reconcileWithRelay(storage, relay, url, Array.from(pubKeys)));
1679
1692
  if (reconcileResult._tag === "Failure") {
1680
1693
  onSyncError?.(reconcileResult.failure);
1681
1694
  return;
1682
1695
  }
1683
1696
  const { haveIds, needIds } = reconcileResult.success;
1697
+ yield* Effect8.logDebug("Relay reconciliation result", { relay: url, need: needIds.length, have: haveIds.length });
1684
1698
  if (needIds.length > 0) {
1685
1699
  const fetched = yield* relay.fetchEvents(needIds, url).pipe(Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.orElseSucceed(() => []));
1686
1700
  const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
@@ -1698,9 +1712,10 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1698
1712
  yield* relay.publish(gw.event, [url]).pipe(Effect8.andThen(storage.stripGiftWrapBlob(id)), Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))), Effect8.ignore);
1699
1713
  }), { discard: true });
1700
1714
  }
1701
- });
1715
+ }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1702
1716
  const handle = {
1703
1717
  sync: () => Effect8.gen(function* () {
1718
+ yield* Effect8.logInfo("Sync started");
1704
1719
  yield* syncStatus.set("syncing");
1705
1720
  yield* Ref3.set(watchCtx.replayingRef, true);
1706
1721
  const changedCollections = new Set;
@@ -1714,7 +1729,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1714
1729
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1715
1730
  yield* syncStatus.set("idle");
1716
1731
  })));
1717
- }),
1732
+ yield* Effect8.logInfo("Sync complete", { changed: [...changedCollections] });
1733
+ }).pipe(Effect8.withLogSpan("tablinum.sync")),
1718
1734
  publishLocal: (giftWrap) => Effect8.gen(function* () {
1719
1735
  if (!giftWrap.event)
1720
1736
  return;
@@ -1840,7 +1856,7 @@ class SyncStatus extends ServiceMap9.Service()("tablinum/SyncStatus") {
1840
1856
  }
1841
1857
 
1842
1858
  // src/layers/IdentityLive.ts
1843
- import { Effect as Effect11, Layer } from "effect";
1859
+ import { Effect as Effect11, Layer as Layer2 } from "effect";
1844
1860
  import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1845
1861
 
1846
1862
  // src/db/identity.ts
@@ -1878,21 +1894,25 @@ function createIdentity(suppliedKey) {
1878
1894
  }
1879
1895
 
1880
1896
  // src/layers/IdentityLive.ts
1881
- var IdentityLive = Layer.effect(Identity, Effect11.gen(function* () {
1897
+ var IdentityLive = Layer2.effect(Identity, Effect11.gen(function* () {
1882
1898
  const config = yield* Config;
1883
1899
  const storage = yield* Storage;
1884
1900
  const idbKey = yield* storage.getMeta("identity_key");
1885
1901
  const resolvedKey = config.privateKey ?? (typeof idbKey === "string" && idbKey.length === 64 ? hexToBytes3(idbKey) : undefined);
1886
1902
  const identity = yield* createIdentity(resolvedKey);
1887
1903
  yield* storage.putMeta("identity_key", identity.exportKey());
1904
+ yield* Effect11.logInfo("Identity loaded", {
1905
+ publicKey: identity.publicKey.slice(0, 12) + "...",
1906
+ source: config.privateKey ? "config" : resolvedKey ? "storage" : "generated"
1907
+ });
1888
1908
  return identity;
1889
1909
  }));
1890
1910
 
1891
1911
  // src/layers/EpochStoreLive.ts
1892
- import { Effect as Effect12, Layer as Layer2, Option as Option7 } from "effect";
1912
+ import { Effect as Effect12, Layer as Layer3, Option as Option7 } from "effect";
1893
1913
  import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
1894
1914
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
1895
- var EpochStoreLive = Layer2.effect(EpochStore, Effect12.gen(function* () {
1915
+ var EpochStoreLive = Layer3.effect(EpochStore, Effect12.gen(function* () {
1896
1916
  const config = yield* Config;
1897
1917
  const identity = yield* Identity;
1898
1918
  const storage = yield* Storage;
@@ -1900,21 +1920,24 @@ var EpochStoreLive = Layer2.effect(EpochStore, Effect12.gen(function* () {
1900
1920
  if (typeof idbRaw === "string") {
1901
1921
  const idbStore = deserializeEpochStore(idbRaw);
1902
1922
  if (Option7.isSome(idbStore)) {
1923
+ yield* Effect12.logInfo("Epoch store loaded", { source: "storage", epochs: idbStore.value.epochs.size });
1903
1924
  return idbStore.value;
1904
1925
  }
1905
1926
  }
1906
1927
  if (config.epochKeys && config.epochKeys.length > 0) {
1907
1928
  const store2 = createEpochStoreFromInputs(config.epochKeys);
1908
1929
  yield* storage.putMeta("epochs", stringifyEpochStore(store2));
1930
+ yield* Effect12.logInfo("Epoch store loaded", { source: "config", epochs: store2.epochs.size });
1909
1931
  return store2;
1910
1932
  }
1911
1933
  const store = createEpochStoreFromInputs([{ epochId: EpochId("epoch-0"), key: bytesToHex3(generateSecretKey2()) }], { createdBy: identity.publicKey });
1912
1934
  yield* storage.putMeta("epochs", stringifyEpochStore(store));
1935
+ yield* Effect12.logInfo("Epoch store loaded", { source: "generated", epochs: store.epochs.size });
1913
1936
  return store;
1914
1937
  }));
1915
1938
 
1916
1939
  // src/layers/StorageLive.ts
1917
- import { Effect as Effect14, Layer as Layer3 } from "effect";
1940
+ import { Effect as Effect14, Layer as Layer4 } from "effect";
1918
1941
 
1919
1942
  // src/storage/idb.ts
1920
1943
  import { Effect as Effect13 } from "effect";
@@ -2072,16 +2095,18 @@ function openIDBStorage(dbName, schema) {
2072
2095
  }
2073
2096
 
2074
2097
  // src/layers/StorageLive.ts
2075
- var StorageLive = Layer3.effect(Storage, Effect14.gen(function* () {
2098
+ var StorageLive = Layer4.effect(Storage, Effect14.gen(function* () {
2076
2099
  const config = yield* Config;
2077
- return yield* openIDBStorage(config.dbName, {
2100
+ const handle = yield* openIDBStorage(config.dbName, {
2078
2101
  ...config.schema,
2079
2102
  _members: membersCollectionDef
2080
2103
  });
2104
+ yield* Effect14.logInfo("Storage opened", { dbName: config.dbName });
2105
+ return handle;
2081
2106
  }));
2082
2107
 
2083
2108
  // src/layers/RelayLive.ts
2084
- import { Layer as Layer4 } from "effect";
2109
+ import { Layer as Layer5 } from "effect";
2085
2110
 
2086
2111
  // src/sync/relay.ts
2087
2112
  import { Effect as Effect15, Option as Option8, Schema as Schema7, ScopedCache, Scope as Scope3 } from "effect";
@@ -2294,10 +2319,10 @@ function createRelayHandle() {
2294
2319
  }
2295
2320
 
2296
2321
  // src/layers/RelayLive.ts
2297
- var RelayLive = Layer4.effect(Relay, createRelayHandle());
2322
+ var RelayLive = Layer5.effect(Relay, createRelayHandle());
2298
2323
 
2299
2324
  // src/layers/GiftWrapLive.ts
2300
- import { Effect as Effect17, Layer as Layer5 } from "effect";
2325
+ import { Effect as Effect17, Layer as Layer6 } from "effect";
2301
2326
 
2302
2327
  // src/sync/gift-wrap.ts
2303
2328
  import { Effect as Effect16 } from "effect";
@@ -2334,14 +2359,14 @@ function createEpochGiftWrapHandle(senderPrivateKey, epochStore) {
2334
2359
  }
2335
2360
 
2336
2361
  // src/layers/GiftWrapLive.ts
2337
- var GiftWrapLive = Layer5.effect(GiftWrap3, Effect17.gen(function* () {
2362
+ var GiftWrapLive = Layer6.effect(GiftWrap3, Effect17.gen(function* () {
2338
2363
  const identity = yield* Identity;
2339
2364
  const epochStore = yield* EpochStore;
2340
2365
  return createEpochGiftWrapHandle(identity.privateKey, epochStore);
2341
2366
  }));
2342
2367
 
2343
2368
  // src/layers/PublishQueueLive.ts
2344
- import { Effect as Effect19, Layer as Layer6 } from "effect";
2369
+ import { Effect as Effect19, Layer as Layer7 } from "effect";
2345
2370
 
2346
2371
  // src/sync/publish-queue.ts
2347
2372
  import { Effect as Effect18, Ref as Ref4 } from "effect";
@@ -2415,14 +2440,14 @@ function createPublishQueue(storage, relay) {
2415
2440
  }
2416
2441
 
2417
2442
  // src/layers/PublishQueueLive.ts
2418
- var PublishQueueLive = Layer6.effect(PublishQueue, Effect19.gen(function* () {
2443
+ var PublishQueueLive = Layer7.effect(PublishQueue, Effect19.gen(function* () {
2419
2444
  const storage = yield* Storage;
2420
2445
  const relay = yield* Relay;
2421
2446
  return yield* createPublishQueue(storage, relay);
2422
2447
  }));
2423
2448
 
2424
2449
  // src/layers/SyncStatusLive.ts
2425
- import { Layer as Layer7 } from "effect";
2450
+ import { Layer as Layer8 } from "effect";
2426
2451
 
2427
2452
  // src/sync/sync-status.ts
2428
2453
  import { Effect as Effect20, SubscriptionRef } from "effect";
@@ -2446,7 +2471,7 @@ function createSyncStatusHandle() {
2446
2471
  }
2447
2472
 
2448
2473
  // src/layers/SyncStatusLive.ts
2449
- var SyncStatusLive = Layer7.effect(SyncStatus, createSyncStatusHandle());
2474
+ var SyncStatusLive = Layer8.effect(SyncStatus, createSyncStatusHandle());
2450
2475
 
2451
2476
  // src/layers/TablinumLive.ts
2452
2477
  function reportSyncError(onSyncError, error) {
@@ -2467,12 +2492,12 @@ function mapMemberRecord(record) {
2467
2492
  ...record.removedInEpoch !== undefined ? { removedInEpoch: record.removedInEpoch } : {}
2468
2493
  };
2469
2494
  }
2470
- var IdentityWithDeps = IdentityLive.pipe(Layer8.provide(StorageLive));
2471
- var EpochStoreWithDeps = EpochStoreLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(StorageLive));
2472
- var GiftWrapWithDeps = GiftWrapLive.pipe(Layer8.provide(IdentityWithDeps), Layer8.provide(EpochStoreWithDeps));
2473
- var PublishQueueWithDeps = PublishQueueLive.pipe(Layer8.provide(StorageLive), Layer8.provide(RelayLive));
2474
- var AllServicesLive = Layer8.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2475
- var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2495
+ var IdentityWithDeps = IdentityLive.pipe(Layer9.provide(StorageLive));
2496
+ var EpochStoreWithDeps = EpochStoreLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(StorageLive));
2497
+ var GiftWrapWithDeps = GiftWrapLive.pipe(Layer9.provide(IdentityWithDeps), Layer9.provide(EpochStoreWithDeps));
2498
+ var PublishQueueWithDeps = PublishQueueLive.pipe(Layer9.provide(StorageLive), Layer9.provide(RelayLive));
2499
+ var AllServicesLive = Layer9.mergeAll(IdentityWithDeps, EpochStoreWithDeps, StorageLive, RelayLive, GiftWrapWithDeps, PublishQueueWithDeps, SyncStatusLive);
2500
+ var TablinumLive = Layer9.effect(Tablinum, Effect21.gen(function* () {
2476
2501
  const config = yield* Config;
2477
2502
  const identity = yield* Identity;
2478
2503
  const epochStore = yield* EpochStore;
@@ -2482,6 +2507,7 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2482
2507
  const publishQueue = yield* PublishQueue;
2483
2508
  const syncStatus = yield* SyncStatus;
2484
2509
  const scope = yield* Effect21.scope;
2510
+ const logLayer = Layer9.succeed(References3.MinimumLogLevel, config.logLevel);
2485
2511
  const pubsub = yield* PubSub2.unbounded();
2486
2512
  const replayingRef = yield* Ref5.make(false);
2487
2513
  const closedRef = yield* Ref5.make(false);
@@ -2490,7 +2516,7 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2490
2516
  const allSchemaEntries = [...schemaEntries, ["_members", membersCollectionDef]];
2491
2517
  const knownCollections = new Map(allSchemaEntries.map(([, def]) => [def.name, def.eventRetention]));
2492
2518
  let notifyAuthor;
2493
- const syncHandle = createSyncHandle(storage, giftWrap, relay, publishQueue, syncStatus, watchCtx, config.relays, knownCollections, epochStore, identity.privateKey, identity.publicKey, scope, config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : undefined, (pubkey) => notifyAuthor?.(pubkey), config.onRemoved, config.onMembersChanged);
2519
+ const syncHandle = createSyncHandle(storage, giftWrap, relay, publishQueue, syncStatus, watchCtx, config.relays, knownCollections, epochStore, identity.privateKey, identity.publicKey, scope, config.logLevel, config.onSyncError ? (error) => reportSyncError(config.onSyncError, error) : undefined, (pubkey) => notifyAuthor?.(pubkey), config.onRemoved, config.onMembersChanged);
2494
2520
  const onWrite = (event) => Effect21.gen(function* () {
2495
2521
  const content = event.kind === "d" ? JSON.stringify(null) : JSON.stringify(event.data);
2496
2522
  const dTag = `${event.collection}:${event.recordId}`;
@@ -2568,16 +2594,21 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2568
2594
  config.onMembersChanged?.();
2569
2595
  }
2570
2596
  }
2571
- }).pipe(Effect21.ignore, Effect21.forkIn(scope)));
2597
+ }).pipe(Effect21.ignore, Effect21.provide(logLayer), Effect21.forkIn(scope)));
2572
2598
  };
2573
2599
  const handles = new Map;
2574
2600
  for (const [, def] of allSchemaEntries) {
2575
2601
  const validator = buildValidator(def.name, def);
2576
2602
  const partialValidator = buildPartialValidator(def.name, def);
2577
- const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite);
2603
+ const handle = createCollectionHandle(def, storage, watchCtx, validator, partialValidator, uuidv7, identity.publicKey, onWrite, config.logLevel);
2578
2604
  handles.set(def.name, handle);
2579
2605
  }
2580
2606
  yield* syncHandle.startSubscription();
2607
+ yield* Effect21.logInfo("Tablinum ready", {
2608
+ dbName: config.dbName,
2609
+ collections: schemaEntries.map(([name]) => name),
2610
+ relays: config.relays
2611
+ });
2581
2612
  const selfMember = yield* storage.getRecord("_members", identity.publicKey);
2582
2613
  if (!selfMember) {
2583
2614
  yield* putMemberRecord({
@@ -2586,18 +2617,19 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2586
2617
  addedInEpoch: getCurrentEpoch(epochStore).id
2587
2618
  });
2588
2619
  }
2589
- const ensureOpen = (effect) => Effect21.gen(function* () {
2620
+ const withLog = (effect) => Effect21.provideService(effect, References3.MinimumLogLevel, config.logLevel);
2621
+ const ensureOpen = (effect) => withLog(Effect21.gen(function* () {
2590
2622
  if (yield* Ref5.get(closedRef)) {
2591
2623
  return yield* new StorageError({ message: "Database is closed" });
2592
2624
  }
2593
2625
  return yield* effect;
2594
- });
2595
- const ensureSyncOpen = (effect) => Effect21.gen(function* () {
2626
+ }));
2627
+ const ensureSyncOpen = (effect) => withLog(Effect21.gen(function* () {
2596
2628
  if (yield* Ref5.get(closedRef)) {
2597
2629
  return yield* new SyncError({ message: "Database is closed", phase: "init" });
2598
2630
  }
2599
2631
  return yield* effect;
2600
- });
2632
+ }));
2601
2633
  const dbHandle = {
2602
2634
  collection: (name) => {
2603
2635
  const handle = handles.get(name);
@@ -2613,12 +2645,12 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2613
2645
  relays: [...config.relays],
2614
2646
  dbName: config.dbName
2615
2647
  }),
2616
- close: () => Effect21.gen(function* () {
2648
+ close: () => withLog(Effect21.gen(function* () {
2617
2649
  if (yield* Ref5.get(closedRef))
2618
2650
  return;
2619
2651
  yield* Ref5.set(closedRef, true);
2620
2652
  yield* Scope4.close(scope, Exit.void);
2621
- }),
2653
+ })),
2622
2654
  rebuild: () => ensureOpen(rebuild(storage, allSchemaEntries.map(([, def]) => def.name))),
2623
2655
  sync: () => ensureSyncOpen(syncHandle.sync()),
2624
2656
  getSyncStatus: () => syncStatus.get(),
@@ -2689,9 +2721,25 @@ var TablinumLive = Layer8.effect(Tablinum, Effect21.gen(function* () {
2689
2721
  }))
2690
2722
  };
2691
2723
  return dbHandle;
2692
- })).pipe(Layer8.provide(AllServicesLive));
2724
+ }).pipe(Effect21.withLogSpan("tablinum.init"))).pipe(Layer9.provide(AllServicesLive));
2693
2725
 
2694
2726
  // src/db/create-tablinum.ts
2727
+ function resolveLogLevel(input) {
2728
+ if (input === undefined || input === "none")
2729
+ return "None";
2730
+ switch (input) {
2731
+ case "debug":
2732
+ return "Debug";
2733
+ case "info":
2734
+ return "Info";
2735
+ case "warning":
2736
+ return "Warn";
2737
+ case "error":
2738
+ return "Error";
2739
+ default:
2740
+ return input;
2741
+ }
2742
+ }
2695
2743
  function validateConfig(config) {
2696
2744
  return Effect22.gen(function* () {
2697
2745
  if (Object.keys(config.schema).length === 0) {
@@ -2705,22 +2753,25 @@ function createTablinum(config) {
2705
2753
  return Effect22.gen(function* () {
2706
2754
  yield* validateConfig(config);
2707
2755
  const runtimeConfig = yield* resolveRuntimeConfig(config);
2756
+ const logLevel = resolveLogLevel(config.logLevel);
2708
2757
  const configValue = {
2709
2758
  ...runtimeConfig,
2710
2759
  schema: config.schema,
2760
+ logLevel,
2711
2761
  onSyncError: config.onSyncError,
2712
2762
  onRemoved: config.onRemoved,
2713
2763
  onMembersChanged: config.onMembersChanged
2714
2764
  };
2715
- const configLayer = Layer9.succeed(Config, configValue);
2716
- const fullLayer = TablinumLive.pipe(Layer9.provide(configLayer));
2717
- const ctx = yield* Layer9.build(fullLayer);
2765
+ const configLayer = Layer10.succeed(Config, configValue);
2766
+ const logLayer = Layer10.succeed(References4.MinimumLogLevel, logLevel);
2767
+ const fullLayer = TablinumLive.pipe(Layer10.provide(configLayer), Layer10.provide(logLayer));
2768
+ const ctx = yield* Layer10.build(fullLayer);
2718
2769
  return ServiceMap10.get(ctx, Tablinum);
2719
2770
  });
2720
2771
  }
2721
2772
 
2722
2773
  // src/svelte/collection.svelte.ts
2723
- import { Effect as Effect24, Fiber, Option as Option11, Stream as Stream4 } from "effect";
2774
+ import { Effect as Effect24, Fiber, Option as Option11, References as References5, Stream as Stream4 } from "effect";
2724
2775
 
2725
2776
  // src/svelte/deferred.ts
2726
2777
  function createDeferred() {
@@ -2813,10 +2864,12 @@ class Collection {
2813
2864
  #version = $state(0);
2814
2865
  #watchAbort = null;
2815
2866
  #watchFiber = null;
2816
- _bind(handle) {
2867
+ #logLevel = "None";
2868
+ _bind(handle, logLevel = "None") {
2817
2869
  if (this.#handle)
2818
2870
  return;
2819
2871
  this.#handle = handle;
2872
+ this.#logLevel = logLevel;
2820
2873
  this.error = null;
2821
2874
  this.#settleReady();
2822
2875
  this.#startWatch();
@@ -2845,7 +2898,7 @@ class Collection {
2845
2898
  if (!abort.signal.aborted) {
2846
2899
  this.error = e instanceof Error ? e : new Error(String(e));
2847
2900
  }
2848
- }))));
2901
+ })), Effect24.provideService(References5.MinimumLogLevel, this.#logLevel)));
2849
2902
  }
2850
2903
  #touchVersion = () => {
2851
2904
  this.#version;
@@ -2857,7 +2910,7 @@ class Collection {
2857
2910
  };
2858
2911
  #run = async (getEffect) => {
2859
2912
  await this.#ready.promise;
2860
- return Effect24.runPromise(getEffect());
2913
+ return Effect24.runPromise(getEffect().pipe(Effect24.provideService(References5.MinimumLogLevel, this.#logLevel)));
2861
2914
  };
2862
2915
  add = (data) => {
2863
2916
  return this.#run(() => this.#handleOrThrow().add(data));
@@ -2924,8 +2977,10 @@ class Tablinum2 {
2924
2977
  #unsubscribeRelayStatus = null;
2925
2978
  #closed = false;
2926
2979
  #readyState = createDeferred();
2980
+ #logLevel;
2927
2981
  constructor(config) {
2928
2982
  this.ready = this.#readyState.promise;
2983
+ this.#logLevel = resolveLogLevel(config.logLevel);
2929
2984
  this.#init(config);
2930
2985
  }
2931
2986
  #settleReady(err) {
@@ -2936,16 +2991,16 @@ class Tablinum2 {
2936
2991
  }
2937
2992
  }
2938
2993
  #bindCollections(handle) {
2939
- this.#members._bind(handle.members);
2994
+ this.#members._bind(handle.members, this.#logLevel);
2940
2995
  for (const [name, collection2] of this.#collections) {
2941
- collection2._bind(handle.collection(name));
2996
+ collection2._bind(handle.collection(name), this.#logLevel);
2942
2997
  }
2943
2998
  }
2944
2999
  #runHandleEffect = async (run) => {
2945
3000
  const handle = this.#requireReady();
2946
3001
  try {
2947
3002
  this.error = null;
2948
- return await Effect25.runPromise(run(handle));
3003
+ return await Effect25.runPromise(run(handle).pipe(Effect25.provideService(References6.MinimumLogLevel, this.#logLevel)));
2949
3004
  } catch (e) {
2950
3005
  this.error = e instanceof Error ? e : new Error(String(e));
2951
3006
  throw this.error;
@@ -3003,7 +3058,7 @@ class Tablinum2 {
3003
3058
  col = new Collection;
3004
3059
  this.#collections.set(name, col);
3005
3060
  if (this.#handle) {
3006
- col._bind(this.#handle.collection(name));
3061
+ col._bind(this.#handle.collection(name), this.#logLevel);
3007
3062
  }
3008
3063
  }
3009
3064
  return col;
@@ -1,4 +1,5 @@
1
1
  import { Effect, Scope } from "effect";
2
+ import type { LogLevel } from "effect";
2
3
  import type { IDBStorageHandle, StoredGiftWrap } from "../storage/idb.ts";
3
4
  import type { GiftWrapHandle } from "./gift-wrap.ts";
4
5
  import type { RelayHandle } from "./relay.ts";
@@ -14,4 +15,4 @@ export interface SyncHandle {
14
15
  readonly startSubscription: () => Effect.Effect<void>;
15
16
  readonly addEpochSubscription: (publicKey: string) => Effect.Effect<void>;
16
17
  }
17
- export declare function createSyncHandle(storage: IDBStorageHandle, giftWrapHandle: GiftWrapHandle, relay: RelayHandle, publishQueue: PublishQueueHandle, syncStatus: SyncStatusHandle, watchCtx: WatchContext, relayUrls: readonly string[], knownCollections: ReadonlyMap<string, number>, epochStore: EpochStore, personalPrivateKey: Uint8Array, personalPublicKey: string, scope: Scope.Scope, onSyncError?: ((error: unknown) => void) | undefined, onNewAuthor?: ((pubkey: string) => void) | undefined, onRemoved?: ((notice: RemovalNotice) => void) | undefined, onMembersChanged?: (() => void) | undefined): SyncHandle;
18
+ export declare function createSyncHandle(storage: IDBStorageHandle, giftWrapHandle: GiftWrapHandle, relay: RelayHandle, publishQueue: PublishQueueHandle, syncStatus: SyncStatusHandle, watchCtx: WatchContext, relayUrls: readonly string[], knownCollections: ReadonlyMap<string, number>, epochStore: EpochStore, personalPrivateKey: Uint8Array, personalPublicKey: string, scope: Scope.Scope, logLevel: LogLevel.LogLevel, onSyncError?: ((error: unknown) => void) | undefined, onNewAuthor?: ((pubkey: string) => void) | undefined, onRemoved?: ((notice: RemovalNotice) => void) | undefined, onMembersChanged?: (() => void) | undefined): SyncHandle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tablinum",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kevmodrome/tablinum.git"