@happyvertical/smrt-core 0.51.10 → 0.51.14

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.
Files changed (93) hide show
  1. package/agents/change-feed.md +82 -0
  2. package/agents/generators.md +3 -1
  3. package/agents/schema-paths.md +23 -6
  4. package/dist/change-feed-sensitivity.d.ts +155 -0
  5. package/dist/change-feed-sensitivity.d.ts.map +1 -0
  6. package/dist/change-feed-sensitivity.js +139 -0
  7. package/dist/change-feed-sensitivity.js.map +1 -0
  8. package/dist/change-feed.d.ts +37 -10
  9. package/dist/change-feed.d.ts.map +1 -1
  10. package/dist/change-feed.js +128 -12
  11. package/dist/change-feed.js.map +1 -1
  12. package/dist/change-signals.d.ts.map +1 -1
  13. package/dist/change-signals.js +3 -0
  14. package/dist/change-signals.js.map +1 -1
  15. package/dist/class.d.ts +1 -1
  16. package/dist/class.d.ts.map +1 -1
  17. package/dist/class.js +4 -2
  18. package/dist/class.js.map +1 -1
  19. package/dist/decorators/index.d.ts +19 -0
  20. package/dist/decorators/index.d.ts.map +1 -1
  21. package/dist/decorators/index.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -2
  25. package/dist/manifest/static-manifest.js +1 -1
  26. package/dist/manifest/static-manifest.js.map +1 -1
  27. package/dist/manifest/store.js +1 -1
  28. package/dist/manifest.json +1 -1
  29. package/dist/migrations/differ.d.ts +64 -5
  30. package/dist/migrations/differ.d.ts.map +1 -1
  31. package/dist/migrations/differ.js +274 -37
  32. package/dist/migrations/differ.js.map +1 -1
  33. package/dist/migrations/index.d.ts +1 -1
  34. package/dist/migrations/index.d.ts.map +1 -1
  35. package/dist/migrations/index.js +2 -2
  36. package/dist/migrations/sqlite-rebuild.d.ts +41 -0
  37. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
  38. package/dist/migrations/sqlite-rebuild.js +68 -13
  39. package/dist/migrations/sqlite-rebuild.js.map +1 -1
  40. package/dist/migrations.js +2 -2
  41. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js +1 -1
  42. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  43. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js +1 -1
  44. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  45. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js +1 -1
  46. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  47. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js +1 -1
  48. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  49. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js +1 -1
  50. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/index.js.map +1 -1
  51. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js +1 -1
  52. package/dist/node_modules/.pnpm/{minimatch@10.2.5 → minimatch@10.2.6}/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  53. package/dist/registry/class-registration.d.ts +1 -1
  54. package/dist/registry/class-registration.d.ts.map +1 -1
  55. package/dist/registry/class-registration.js +50 -4
  56. package/dist/registry/class-registration.js.map +1 -1
  57. package/dist/registry/db-cache-key.d.ts +47 -0
  58. package/dist/registry/db-cache-key.d.ts.map +1 -0
  59. package/dist/registry/db-cache-key.js +201 -0
  60. package/dist/registry/db-cache-key.js.map +1 -0
  61. package/dist/registry/schema-builder.d.ts.map +1 -1
  62. package/dist/registry/schema-builder.js +6 -1
  63. package/dist/registry/schema-builder.js.map +1 -1
  64. package/dist/registry/types.d.ts +43 -0
  65. package/dist/registry/types.d.ts.map +1 -1
  66. package/dist/registry.d.ts +17 -1
  67. package/dist/registry.d.ts.map +1 -1
  68. package/dist/registry.js +20 -8
  69. package/dist/registry.js.map +1 -1
  70. package/dist/scanner/types.d.ts +2 -0
  71. package/dist/scanner/types.d.ts.map +1 -1
  72. package/dist/scanner/types.js.map +1 -1
  73. package/dist/schema/backfill.d.ts +16 -0
  74. package/dist/schema/backfill.d.ts.map +1 -0
  75. package/dist/schema/backfill.js +26 -0
  76. package/dist/schema/backfill.js.map +1 -0
  77. package/dist/schema/generator.d.ts.map +1 -1
  78. package/dist/schema/generator.js +3 -0
  79. package/dist/schema/generator.js.map +1 -1
  80. package/dist/schema/types.d.ts +20 -1
  81. package/dist/schema/types.d.ts.map +1 -1
  82. package/dist/smrt-knowledge.json +21 -21
  83. package/dist/vite-plugin/dev-plane-route.js +1 -1
  84. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  85. package/dist/vite-plugin/index.js +1 -1
  86. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  87. package/dist/vite-plugin/sveltekit-generator.js +54 -7
  88. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  89. package/dist/vite-plugin/sync-apply-route.d.ts +7 -0
  90. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  91. package/dist/vite-plugin/sync-apply-route.js +50 -3
  92. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  93. package/package.json +23 -23
@@ -1,3 +1,4 @@
1
+ import { isChangeFeedSensitiveTable } from "./change-feed-sensitivity.js";
1
2
  import { PROCESS_ID, getNotifications, resolveDbCacheKey } from "./collection-cache.js";
2
3
  import { toSafeInteger } from "./utils/safe-integer.js";
3
4
  import { createLogger } from "@happyvertical/logger";
@@ -157,6 +158,7 @@ function tryReserveChangeSignalSubscriberSlot(db, maxSubscribers) {
157
158
  * never fail the user's write.
158
159
  */
159
160
  function publishChangeSignal(db, signal) {
161
+ if (isChangeFeedSensitiveTable(signal.table)) return;
160
162
  deliverLocally(resolveDbCacheKey(db), signal);
161
163
  broadcastChangeSignal(db, signal);
162
164
  }
@@ -167,6 +169,7 @@ function publishChangeSignal(db, signal) {
167
169
  * signals both flow through here — the single delivery path.
168
170
  */
169
171
  function deliverLocally(dbKey, signal) {
172
+ if (isChangeFeedSensitiveTable(signal.table)) return;
170
173
  const set = localListeners.get(dbKey);
171
174
  if (!set || set.size === 0) return;
172
175
  for (const listener of [...set]) try {
@@ -1 +1 @@
1
- {"version":3,"file":"change-signals.js","names":[],"sources":["../src/change-signals.ts"],"sourcesContent":["/**\n * Change-signal bus — the live push spine for the generated `_events` SSE\n * route (issue #1763, parent PRD #1755).\n *\n * The change feed (#1758) is a durable, cursor-addressable log; this bus is\n * its ephemeral companion. Every framework `save()`/`delete()` that appends a\n * feed row also publishes a coarse {@link ChangeSignal} here, which fans out\n * synchronously to in-process subscribers (the SSE controllers of connected\n * `_events` clients) and, when the database adapter exposes a notification\n * capability, to peer replicas over the same channel. Absence of that\n * capability degrades gracefully — no cross-replica push, never an error, and\n * it never blocks the write.\n *\n * ## What a signal carries (and deliberately does not)\n *\n * A signal is `{ table, operation, rowId, tenantId, seq }` — never any row\n * payload. Authorization stays entirely on the read path: a subscriber learns\n * *that* something changed and its cursor (`seq`), then re-reads through the\n * authorized collection routes to catch up. This is why the bus can broadcast\n * a tenant's writes to peer replicas without leaking data across a trust\n * boundary. The `seq` is the same monotonic cursor dimension the change feed\n * allocates, so a reconnecting client can resume via `getChangesSince`.\n *\n * ## Delivery model\n *\n * - Local delivery is a synchronous fan-out ({@link deliverLocally}) into each\n * listener callback, wrapped in a try/catch **per listener** so one throwing\n * listener (e.g. a closed SSE controller's `enqueue`) never blocks the\n * others. There is no per-subscriber queue — backpressure is delegated to\n * each platform `ReadableStream`.\n * - Cross-replica delivery reuses the same {@link deliverLocally} helper on\n * receipt, so locally-published and peer-received signals travel one code\n * path. Notifications this process published are skipped by `source` id\n * (echo-avoidance), exactly as the collection cache does.\n *\n * This mirrors `collection-cache.ts`'s notify/listen structure; study that\n * module for the shared cross-process conventions (`resolveDbCacheKey`,\n * `getNotifications`, the lazy listener and finally-retract).\n *\n * ## Known gaps\n *\n * - **Subscriber cap lives at the route boundary**: the bus tracks subscribers,\n * but generated `_events` routes decide whether to reject a new connection\n * before opening a stream (#1860).\n * - **Raw-SQL writes are invisible**: signals originate from the framework\n * write path (same accepted gap as the #1758 feed and #1498 cache). A\n * `bumpChangeFeed` escape-hatch write appends a feed row but does not\n * publish a signal.\n * - **Caller-managed transactions signal late, not early** (#2649): a\n * PostgreSQL append made inside a caller transaction that has already\n * written is *staged*, so it has no sequence to signal with at\n * `afterSave`/`afterDelete` time. Its signal is published instead by the\n * drain that sequences it, which is the first post-commit moment — closing\n * the older hazard where a pre-commit signal could describe a write a later\n * rollback undid, and where the rolled-back seq was reused by the next\n * append. The cost is latency: live subscribers see a transaction-wrapped\n * write one drain after it commits, and on engines without staging\n * (SQLite, DuckDB) the pre-commit caveat still applies. The autocommit\n * default path — save()/delete() as independent statements — is exact and\n * unchanged. Clients reconcile via cursor catch-up either way, so\n * convergence holds.\n *\n * @see https://github.com/happyvertical/smrt/issues/1763\n * @packageDocumentation\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n getNotifications,\n PROCESS_ID,\n resolveDbCacheKey,\n} from './collection-cache.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Operation carried by a change signal. Kept as a local string union — this\n * module deliberately does NOT import `./change-feed.js` (which imports this\n * one via the writer) so there is no import cycle. It must stay in sync with\n * `ChangeOperation` in `change-feed.ts`.\n */\nexport type ChangeSignalOperation = 'create' | 'update' | 'delete';\n\n/**\n * A coarse change notification. Carries no row payload by design.\n */\nexport interface ChangeSignal {\n // NEVER add row-payload fields — #1763 AC: authorization stays on the read path\n /** Physical table the change happened in. */\n table: string;\n /** What happened (`'delete'` doubles as a tombstone signal). */\n operation: ChangeSignalOperation;\n /** Primary key of the changed row, or `null` for table-level changes. */\n rowId: string | null;\n /** Tenant the changed row belongs to, or `null` for global rows. */\n tenantId: string | null;\n /**\n * The change feed sequence allocated for this change — the cursor a\n * reconnecting client resumes from via `getChangesSince`.\n */\n seq: number;\n}\n\n/** A subscriber invoked synchronously for every locally-visible signal. */\nexport type ChangeSignalListener = (signal: ChangeSignal) => void;\n\n/**\n * Notification channel for cross-replica change-signal broadcasts. A distinct\n * channel from the collection cache's — the two buses carry different payloads\n * and evolve independently.\n */\nexport const CHANGE_SIGNAL_CHANNEL = 'smrt_change_signals';\n\n/**\n * dbKey → local listeners. Keyed via `resolveDbCacheKey` so `:memory:` and\n * URL-less handles are scoped per instance (never cross-deliver between two\n * independent in-memory databases).\n */\nconst localListeners = new Map<string, Set<ChangeSignalListener>>();\n\n/**\n * dbKey -> pending subscriber slots claimed by `_events` before a stream has\n * reached its `start()` callback. This closes the check-then-subscribe race for\n * concurrent connection opens: capacity considers active listeners plus these\n * in-flight claims.\n */\nconst reservedListenerSlots = new Map<string, number>();\n\ninterface ListenerHandle {\n iterator: AsyncIterator<unknown> | null;\n stopped: boolean;\n}\n\n/** dbKey → background cross-replica listener handle. */\nconst crossReplicaListeners = new Map<string, ListenerHandle>();\n\n/** dbKeys we already warned about for a missing notification capability. */\nconst warnedNoNotifications = new Set<string>();\n\n/**\n * Subscribe to change signals for a database.\n *\n * Registers `listener` for the database's signal scope and, on the first\n * subscriber for that scope, lazily starts the cross-replica listener (a no-op\n * when the adapter has no notification capability). Returns an unsubscribe\n * function that removes the listener and, when the scope's last subscriber\n * leaves, retracts the cross-replica listener so its refcount reaches 0.\n *\n * Delivery is synchronous: `listener` is invoked from the write path (or the\n * cross-replica loop) inside a per-listener try/catch, so it must not assume\n * an active request or tenant context — capture what it needs at subscribe\n * time.\n */\nexport function subscribeToChangeSignals(\n db: DatabaseInterface,\n listener: ChangeSignalListener,\n): () => void {\n const dbKey = resolveDbCacheKey(db);\n\n let set = localListeners.get(dbKey);\n if (!set) {\n set = new Set();\n localListeners.set(dbKey, set);\n }\n set.add(listener);\n\n // Lazily start the cross-replica listener on first interest for this scope.\n ensureChangeSignalListener(db);\n\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n const current = localListeners.get(dbKey);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) {\n localListeners.delete(dbKey);\n // Last local subscriber gone — retract the cross-replica listener so its\n // refcount reaches 0 (mirrors collection-cache's finally-retract).\n retractChangeSignalListener(dbKey);\n }\n };\n}\n\n/**\n * Atomically reserve one local subscriber slot for a database scope.\n *\n * The `_events` route calls this before returning a streaming response so\n * concurrent opens cannot all pass a stale count and exceed the configured cap.\n * The returned release function is idempotent; callers must release it when the\n * stream either converts the reservation into a real subscription or tears down\n * before subscribing. `maxSubscribers === null` means unlimited and returns a\n * no-op reservation.\n */\nexport function tryReserveChangeSignalSubscriberSlot(\n db: DatabaseInterface,\n maxSubscribers: number | null,\n): (() => void) | null {\n if (maxSubscribers === null) {\n return () => {};\n }\n\n const dbKey = resolveDbCacheKey(db);\n if (changeSignalSubscriberCountForKey(dbKey) >= maxSubscribers) {\n return null;\n }\n\n reservedListenerSlots.set(dbKey, (reservedListenerSlots.get(dbKey) ?? 0) + 1);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const current = reservedListenerSlots.get(dbKey) ?? 0;\n if (current <= 1) {\n reservedListenerSlots.delete(dbKey);\n } else {\n reservedListenerSlots.set(dbKey, current - 1);\n }\n };\n}\n\n/**\n * Publish a change signal: synchronous local fan-out, then fire-and-forget\n * cross-replica broadcast. Never throws to the caller — a signal problem must\n * never fail the user's write.\n */\nexport function publishChangeSignal(\n db: DatabaseInterface,\n signal: ChangeSignal,\n): void {\n const dbKey = resolveDbCacheKey(db);\n deliverLocally(dbKey, signal);\n // Fire-and-forget: broadcast failures are swallowed inside broadcast.\n void broadcastChangeSignal(db, signal);\n}\n\n/**\n * Synchronous local fan-out to every subscriber for a scope. Each listener is\n * wrapped in its own try/catch so one throwing listener (a closed SSE\n * controller) never blocks the rest. Locally-published and peer-received\n * signals both flow through here — the single delivery path.\n */\nfunction deliverLocally(dbKey: string, signal: ChangeSignal): void {\n const set = localListeners.get(dbKey);\n if (!set || set.size === 0) return;\n // Snapshot so a listener that unsubscribes during delivery can't mutate the\n // set mid-iteration.\n for (const listener of [...set]) {\n try {\n listener(signal);\n } catch (error) {\n logger.warn('Change signal: a subscriber threw during local delivery', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n\n/**\n * Broadcast a signal to peer replicas over the adapter's notification\n * capability. Resolves normally (never throws) whether or not a capability\n * exists — a missing capability warns once per scope and is not an error.\n *\n * Fire-and-forget from the write path: a broadcast failure must never fail the\n * write that triggered it.\n */\nexport async function broadcastChangeSignal(\n db: DatabaseInterface,\n signal: ChangeSignal,\n): Promise<void> {\n const notifications = getNotifications(db);\n const dbKey = resolveDbCacheKey(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey);\n return;\n }\n try {\n await notifications.notify(CHANGE_SIGNAL_CHANNEL, {\n ...signal,\n source: PROCESS_ID,\n });\n } catch (error) {\n logger.warn(\n `Change signal: failed to broadcast a signal for '${signal.table}'`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n}\n\n/**\n * Ensure a background listener consumes cross-replica broadcasts for this\n * database and delivers them locally. Started lazily by the first subscriber\n * for a scope; a no-op (with a one-time warning) when the adapter exposes no\n * notification capability. Notifications this process published are skipped by\n * `source` id (echo-avoidance): the local fan-out already delivered them.\n */\nfunction ensureChangeSignalListener(db: DatabaseInterface): void {\n const dbKey = resolveDbCacheKey(db);\n if (crossReplicaListeners.has(dbKey)) return;\n\n const notifications = getNotifications(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey);\n return;\n }\n\n const handle: ListenerHandle = { iterator: null, stopped: false };\n crossReplicaListeners.set(dbKey, handle);\n\n void (async () => {\n try {\n const iterable = notifications.listen(CHANGE_SIGNAL_CHANNEL);\n const iterator = iterable[Symbol.asyncIterator]();\n handle.iterator = iterator;\n\n while (!handle.stopped) {\n const { value, done } = await iterator.next();\n if (done || handle.stopped) break;\n\n const notification = value as { payload?: unknown };\n const payload: unknown =\n typeof notification?.payload === 'string'\n ? safeParse(notification.payload)\n : notification?.payload;\n\n if (!payload || typeof payload !== 'object') continue;\n const record = payload as Record<string, unknown>;\n // Echo-avoidance: skip signals this process published (already\n // delivered locally on the write path).\n if (record.source === PROCESS_ID) continue;\n\n const signal = toSignal(record);\n if (!signal) continue;\n deliverLocally(dbKey, signal);\n }\n } catch (error) {\n if (!handle.stopped) {\n logger.warn(\n 'Change signal: cross-replica listener terminated unexpectedly; ' +\n 'peer signals are no longer delivered for this database (local ' +\n 'delivery and cursor catch-up still work)',\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n } finally {\n // Only retract our own handle — a concurrent restart may have installed\n // a replacement; deleting unconditionally would orphan it.\n if (crossReplicaListeners.get(dbKey) === handle) {\n crossReplicaListeners.delete(dbKey);\n }\n }\n })();\n}\n\n/** Retract the cross-replica listener for a scope (last subscriber left). */\nfunction retractChangeSignalListener(dbKey: string): void {\n const handle = crossReplicaListeners.get(dbKey);\n if (!handle) return;\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n crossReplicaListeners.delete(dbKey);\n}\n\n/**\n * Stop all cross-replica change-signal listeners. Used by tests and during\n * shutdown; safe to call when none are active.\n */\nexport function stopChangeSignalListeners(): void {\n for (const handle of crossReplicaListeners.values()) {\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n }\n crossReplicaListeners.clear();\n warnedNoNotifications.clear();\n}\n\n/**\n * Clear all local subscribers, stop cross-replica listeners, and reset the\n * no-capability warning dedup. Call in test setup for isolation between files.\n */\nexport function resetChangeSignals(): void {\n localListeners.clear();\n reservedListenerSlots.clear();\n stopChangeSignalListeners();\n}\n\n/**\n * Number of active or reserved local subscribers for a database scope.\n *\n * Used by the `_events` route boundary to enforce the per-process subscriber\n * cap (#1860), and by integration tests to assert teardown — a leaked SSE\n * subscription or stale reservation would keep this above 0 after a client\n * disconnects.\n */\nexport function changeSignalSubscriberCount(db: DatabaseInterface): number {\n return changeSignalSubscriberCountForKey(resolveDbCacheKey(db));\n}\n\nfunction changeSignalSubscriberCountForKey(dbKey: string): number {\n return (\n (localListeners.get(dbKey)?.size ?? 0) +\n (reservedListenerSlots.get(dbKey) ?? 0)\n );\n}\n\nfunction warnOnceNoNotifications(dbKey: string): void {\n if (warnedNoNotifications.has(dbKey)) return;\n warnedNoNotifications.add(dbKey);\n logger.warn(\n 'Change signal: the database adapter exposes no notification capability, ' +\n 'so change signals are delivered in-process only. Cross-replica live ' +\n 'updates require an adapter with notifications (e.g. Postgres ' +\n 'LISTEN/NOTIFY); subscribers on other replicas fall back to cursor ' +\n 'polling.',\n );\n}\n\n/**\n * Coerce a received notification payload into a {@link ChangeSignal}, or\n * `undefined` if it is malformed. Guards the cross-replica path against\n * garbage on the channel.\n */\nfunction toSignal(record: Record<string, unknown>): ChangeSignal | undefined {\n const { table, operation, rowId, tenantId, seq } = record;\n if (typeof table !== 'string' || !table) return undefined;\n if (\n operation !== 'create' &&\n operation !== 'update' &&\n operation !== 'delete'\n ) {\n return undefined;\n }\n let normalizedSeq: number;\n try {\n normalizedSeq = toSafeInteger(seq ?? 0, 'Change-signal sequence');\n } catch {\n return undefined;\n }\n return {\n table,\n operation,\n rowId: typeof rowId === 'string' ? rowId : null,\n tenantId: typeof tenantId === 'string' ? tenantId : null,\n seq: normalizedSeq,\n };\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-signals.js","names":[],"sources":["../src/change-signals.ts"],"sourcesContent":["/**\n * Change-signal bus — the live push spine for the generated `_events` SSE\n * route (issue #1763, parent PRD #1755).\n *\n * The change feed (#1758) is a durable, cursor-addressable log; this bus is\n * its ephemeral companion. Every framework `save()`/`delete()` that appends a\n * feed row also publishes a coarse {@link ChangeSignal} here, which fans out\n * synchronously to in-process subscribers (the SSE controllers of connected\n * `_events` clients) and, when the database adapter exposes a notification\n * capability, to peer replicas over the same channel. Absence of that\n * capability degrades gracefully — no cross-replica push, never an error, and\n * it never blocks the write.\n *\n * ## What a signal carries (and deliberately does not)\n *\n * A signal is `{ table, operation, rowId, tenantId, seq }` — never any row\n * payload. Authorization stays entirely on the read path: a subscriber learns\n * *that* something changed and its cursor (`seq`), then re-reads through the\n * authorized collection routes to catch up. This is why the bus can broadcast\n * a tenant's writes to peer replicas without leaking data across a trust\n * boundary. The `seq` is the same monotonic cursor dimension the change feed\n * allocates, so a reconnecting client can resume via `getChangesSince`.\n *\n * ## Delivery model\n *\n * - Local delivery is a synchronous fan-out ({@link deliverLocally}) into each\n * listener callback, wrapped in a try/catch **per listener** so one throwing\n * listener (e.g. a closed SSE controller's `enqueue`) never blocks the\n * others. There is no per-subscriber queue — backpressure is delegated to\n * each platform `ReadableStream`.\n * - Cross-replica delivery reuses the same {@link deliverLocally} helper on\n * receipt, so locally-published and peer-received signals travel one code\n * path. Notifications this process published are skipped by `source` id\n * (echo-avoidance), exactly as the collection cache does.\n *\n * This mirrors `collection-cache.ts`'s notify/listen structure; study that\n * module for the shared cross-process conventions (`resolveDbCacheKey`,\n * `getNotifications`, the lazy listener and finally-retract).\n *\n * ## Known gaps\n *\n * - **Subscriber cap lives at the route boundary**: the bus tracks subscribers,\n * but generated `_events` routes decide whether to reject a new connection\n * before opening a stream (#1860).\n * - **Raw-SQL writes are invisible**: signals originate from the framework\n * write path (same accepted gap as the #1758 feed and #1498 cache). A\n * `bumpChangeFeed` escape-hatch write appends a feed row but does not\n * publish a signal.\n * - **Caller-managed transactions signal late, not early** (#2649): a\n * PostgreSQL append made inside a caller transaction that has already\n * written is *staged*, so it has no sequence to signal with at\n * `afterSave`/`afterDelete` time. Its signal is published instead by the\n * drain that sequences it, which is the first post-commit moment — closing\n * the older hazard where a pre-commit signal could describe a write a later\n * rollback undid, and where the rolled-back seq was reused by the next\n * append. The cost is latency: live subscribers see a transaction-wrapped\n * write one drain after it commits, and on engines without staging\n * (SQLite, DuckDB) the pre-commit caveat still applies. The autocommit\n * default path — save()/delete() as independent statements — is exact and\n * unchanged. Clients reconcile via cursor catch-up either way, so\n * convergence holds.\n *\n * @see https://github.com/happyvertical/smrt/issues/1763\n * @packageDocumentation\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { isChangeFeedSensitiveTable } from './change-feed-sensitivity.js';\nimport {\n getNotifications,\n PROCESS_ID,\n resolveDbCacheKey,\n} from './collection-cache.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Operation carried by a change signal. Kept as a local string union — this\n * module deliberately does NOT import `./change-feed.js` (which imports this\n * one via the writer) so there is no import cycle. It must stay in sync with\n * `ChangeOperation` in `change-feed.ts`.\n */\nexport type ChangeSignalOperation = 'create' | 'update' | 'delete';\n\n/**\n * A coarse change notification. Carries no row payload by design.\n */\nexport interface ChangeSignal {\n // NEVER add row-payload fields — #1763 AC: authorization stays on the read path\n /** Physical table the change happened in. */\n table: string;\n /** What happened (`'delete'` doubles as a tombstone signal). */\n operation: ChangeSignalOperation;\n /** Primary key of the changed row, or `null` for table-level changes. */\n rowId: string | null;\n /** Tenant the changed row belongs to, or `null` for global rows. */\n tenantId: string | null;\n /**\n * The change feed sequence allocated for this change — the cursor a\n * reconnecting client resumes from via `getChangesSince`.\n */\n seq: number;\n}\n\n/** A subscriber invoked synchronously for every locally-visible signal. */\nexport type ChangeSignalListener = (signal: ChangeSignal) => void;\n\n/**\n * Notification channel for cross-replica change-signal broadcasts. A distinct\n * channel from the collection cache's — the two buses carry different payloads\n * and evolve independently.\n */\nexport const CHANGE_SIGNAL_CHANNEL = 'smrt_change_signals';\n\n/**\n * dbKey → local listeners. Keyed via `resolveDbCacheKey` so `:memory:` and\n * URL-less handles are scoped per instance (never cross-deliver between two\n * independent in-memory databases).\n */\nconst localListeners = new Map<string, Set<ChangeSignalListener>>();\n\n/**\n * dbKey -> pending subscriber slots claimed by `_events` before a stream has\n * reached its `start()` callback. This closes the check-then-subscribe race for\n * concurrent connection opens: capacity considers active listeners plus these\n * in-flight claims.\n */\nconst reservedListenerSlots = new Map<string, number>();\n\ninterface ListenerHandle {\n iterator: AsyncIterator<unknown> | null;\n stopped: boolean;\n}\n\n/** dbKey → background cross-replica listener handle. */\nconst crossReplicaListeners = new Map<string, ListenerHandle>();\n\n/** dbKeys we already warned about for a missing notification capability. */\nconst warnedNoNotifications = new Set<string>();\n\n/**\n * Subscribe to change signals for a database.\n *\n * Registers `listener` for the database's signal scope and, on the first\n * subscriber for that scope, lazily starts the cross-replica listener (a no-op\n * when the adapter has no notification capability). Returns an unsubscribe\n * function that removes the listener and, when the scope's last subscriber\n * leaves, retracts the cross-replica listener so its refcount reaches 0.\n *\n * Delivery is synchronous: `listener` is invoked from the write path (or the\n * cross-replica loop) inside a per-listener try/catch, so it must not assume\n * an active request or tenant context — capture what it needs at subscribe\n * time.\n */\nexport function subscribeToChangeSignals(\n db: DatabaseInterface,\n listener: ChangeSignalListener,\n): () => void {\n const dbKey = resolveDbCacheKey(db);\n\n let set = localListeners.get(dbKey);\n if (!set) {\n set = new Set();\n localListeners.set(dbKey, set);\n }\n set.add(listener);\n\n // Lazily start the cross-replica listener on first interest for this scope.\n ensureChangeSignalListener(db);\n\n let unsubscribed = false;\n return () => {\n if (unsubscribed) return;\n unsubscribed = true;\n const current = localListeners.get(dbKey);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) {\n localListeners.delete(dbKey);\n // Last local subscriber gone — retract the cross-replica listener so its\n // refcount reaches 0 (mirrors collection-cache's finally-retract).\n retractChangeSignalListener(dbKey);\n }\n };\n}\n\n/**\n * Atomically reserve one local subscriber slot for a database scope.\n *\n * The `_events` route calls this before returning a streaming response so\n * concurrent opens cannot all pass a stale count and exceed the configured cap.\n * The returned release function is idempotent; callers must release it when the\n * stream either converts the reservation into a real subscription or tears down\n * before subscribing. `maxSubscribers === null` means unlimited and returns a\n * no-op reservation.\n */\nexport function tryReserveChangeSignalSubscriberSlot(\n db: DatabaseInterface,\n maxSubscribers: number | null,\n): (() => void) | null {\n if (maxSubscribers === null) {\n return () => {};\n }\n\n const dbKey = resolveDbCacheKey(db);\n if (changeSignalSubscriberCountForKey(dbKey) >= maxSubscribers) {\n return null;\n }\n\n reservedListenerSlots.set(dbKey, (reservedListenerSlots.get(dbKey) ?? 0) + 1);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const current = reservedListenerSlots.get(dbKey) ?? 0;\n if (current <= 1) {\n reservedListenerSlots.delete(dbKey);\n } else {\n reservedListenerSlots.set(dbKey, current - 1);\n }\n };\n}\n\n/**\n * Publish a change signal: synchronous local fan-out, then fire-and-forget\n * cross-replica broadcast. Never throws to the caller — a signal problem must\n * never fail the user's write.\n */\nexport function publishChangeSignal(\n db: DatabaseInterface,\n signal: ChangeSignal,\n): void {\n // Refuse before the broadcast too, not only inside `deliverLocally` (#2937).\n // A process on this build can drain PostgreSQL rows an OLDER build staged,\n // and would then NOTIFY `{table: 'sessions', rowId}` onto the shared channel\n // — putting the credential on the wire and handing it to old-build peers\n // that forward it to their SSE clients. Those peers leak on their own\n // account; this build must not be what feeds them.\n if (isChangeFeedSensitiveTable(signal.table)) return;\n const dbKey = resolveDbCacheKey(db);\n deliverLocally(dbKey, signal);\n // Fire-and-forget: broadcast failures are swallowed inside broadcast.\n void broadcastChangeSignal(db, signal);\n}\n\n/**\n * Synchronous local fan-out to every subscriber for a scope. Each listener is\n * wrapped in its own try/catch so one throwing listener (a closed SSE\n * controller) never blocks the rest. Locally-published and peer-received\n * signals both flow through here — the single delivery path.\n */\nfunction deliverLocally(dbKey: string, signal: ChangeSignal): void {\n // The SSE frame names `{table, operation, rowId}`, and for a credential\n // table the rowId IS the credential (#2937). Every delivery — locally\n // published and peer-received alike — passes through here, so this is the\n // one place that also covers a peer replica still running a build whose\n // write path had no such guard.\n if (isChangeFeedSensitiveTable(signal.table)) return;\n const set = localListeners.get(dbKey);\n if (!set || set.size === 0) return;\n // Snapshot so a listener that unsubscribes during delivery can't mutate the\n // set mid-iteration.\n for (const listener of [...set]) {\n try {\n listener(signal);\n } catch (error) {\n logger.warn('Change signal: a subscriber threw during local delivery', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n\n/**\n * Broadcast a signal to peer replicas over the adapter's notification\n * capability. Resolves normally (never throws) whether or not a capability\n * exists — a missing capability warns once per scope and is not an error.\n *\n * Fire-and-forget from the write path: a broadcast failure must never fail the\n * write that triggered it.\n */\nexport async function broadcastChangeSignal(\n db: DatabaseInterface,\n signal: ChangeSignal,\n): Promise<void> {\n const notifications = getNotifications(db);\n const dbKey = resolveDbCacheKey(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey);\n return;\n }\n try {\n await notifications.notify(CHANGE_SIGNAL_CHANNEL, {\n ...signal,\n source: PROCESS_ID,\n });\n } catch (error) {\n logger.warn(\n `Change signal: failed to broadcast a signal for '${signal.table}'`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n}\n\n/**\n * Ensure a background listener consumes cross-replica broadcasts for this\n * database and delivers them locally. Started lazily by the first subscriber\n * for a scope; a no-op (with a one-time warning) when the adapter exposes no\n * notification capability. Notifications this process published are skipped by\n * `source` id (echo-avoidance): the local fan-out already delivered them.\n */\nfunction ensureChangeSignalListener(db: DatabaseInterface): void {\n const dbKey = resolveDbCacheKey(db);\n if (crossReplicaListeners.has(dbKey)) return;\n\n const notifications = getNotifications(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey);\n return;\n }\n\n const handle: ListenerHandle = { iterator: null, stopped: false };\n crossReplicaListeners.set(dbKey, handle);\n\n void (async () => {\n try {\n const iterable = notifications.listen(CHANGE_SIGNAL_CHANNEL);\n const iterator = iterable[Symbol.asyncIterator]();\n handle.iterator = iterator;\n\n while (!handle.stopped) {\n const { value, done } = await iterator.next();\n if (done || handle.stopped) break;\n\n const notification = value as { payload?: unknown };\n const payload: unknown =\n typeof notification?.payload === 'string'\n ? safeParse(notification.payload)\n : notification?.payload;\n\n if (!payload || typeof payload !== 'object') continue;\n const record = payload as Record<string, unknown>;\n // Echo-avoidance: skip signals this process published (already\n // delivered locally on the write path).\n if (record.source === PROCESS_ID) continue;\n\n const signal = toSignal(record);\n if (!signal) continue;\n deliverLocally(dbKey, signal);\n }\n } catch (error) {\n if (!handle.stopped) {\n logger.warn(\n 'Change signal: cross-replica listener terminated unexpectedly; ' +\n 'peer signals are no longer delivered for this database (local ' +\n 'delivery and cursor catch-up still work)',\n { error: error instanceof Error ? error.message : String(error) },\n );\n }\n } finally {\n // Only retract our own handle — a concurrent restart may have installed\n // a replacement; deleting unconditionally would orphan it.\n if (crossReplicaListeners.get(dbKey) === handle) {\n crossReplicaListeners.delete(dbKey);\n }\n }\n })();\n}\n\n/** Retract the cross-replica listener for a scope (last subscriber left). */\nfunction retractChangeSignalListener(dbKey: string): void {\n const handle = crossReplicaListeners.get(dbKey);\n if (!handle) return;\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n crossReplicaListeners.delete(dbKey);\n}\n\n/**\n * Stop all cross-replica change-signal listeners. Used by tests and during\n * shutdown; safe to call when none are active.\n */\nexport function stopChangeSignalListeners(): void {\n for (const handle of crossReplicaListeners.values()) {\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n }\n crossReplicaListeners.clear();\n warnedNoNotifications.clear();\n}\n\n/**\n * Clear all local subscribers, stop cross-replica listeners, and reset the\n * no-capability warning dedup. Call in test setup for isolation between files.\n */\nexport function resetChangeSignals(): void {\n localListeners.clear();\n reservedListenerSlots.clear();\n stopChangeSignalListeners();\n}\n\n/**\n * Number of active or reserved local subscribers for a database scope.\n *\n * Used by the `_events` route boundary to enforce the per-process subscriber\n * cap (#1860), and by integration tests to assert teardown — a leaked SSE\n * subscription or stale reservation would keep this above 0 after a client\n * disconnects.\n */\nexport function changeSignalSubscriberCount(db: DatabaseInterface): number {\n return changeSignalSubscriberCountForKey(resolveDbCacheKey(db));\n}\n\nfunction changeSignalSubscriberCountForKey(dbKey: string): number {\n return (\n (localListeners.get(dbKey)?.size ?? 0) +\n (reservedListenerSlots.get(dbKey) ?? 0)\n );\n}\n\nfunction warnOnceNoNotifications(dbKey: string): void {\n if (warnedNoNotifications.has(dbKey)) return;\n warnedNoNotifications.add(dbKey);\n logger.warn(\n 'Change signal: the database adapter exposes no notification capability, ' +\n 'so change signals are delivered in-process only. Cross-replica live ' +\n 'updates require an adapter with notifications (e.g. Postgres ' +\n 'LISTEN/NOTIFY); subscribers on other replicas fall back to cursor ' +\n 'polling.',\n );\n}\n\n/**\n * Coerce a received notification payload into a {@link ChangeSignal}, or\n * `undefined` if it is malformed. Guards the cross-replica path against\n * garbage on the channel.\n */\nfunction toSignal(record: Record<string, unknown>): ChangeSignal | undefined {\n const { table, operation, rowId, tenantId, seq } = record;\n if (typeof table !== 'string' || !table) return undefined;\n if (\n operation !== 'create' &&\n operation !== 'update' &&\n operation !== 'delete'\n ) {\n return undefined;\n }\n let normalizedSeq: number;\n try {\n normalizedSeq = toSafeInteger(seq ?? 0, 'Change-signal sequence');\n } catch {\n return undefined;\n }\n return {\n table,\n operation,\n rowId: typeof rowId === 'string' ? rowId : null,\n tenantId: typeof tenantId === 'string' ? tenantId : null,\n seq: normalizedSeq,\n };\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
package/dist/class.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AIClientOptions, AIClient } from '@happyvertical/ai';
1
+ import { AIClient, AIClientOptions } from '@happyvertical/ai';
2
2
  import { FilesystemAdapter, FilesystemAdapterOptions } from '@happyvertical/files';
3
3
  import { LoggerConfig } from '@happyvertical/logger';
4
4
  import { AiUsageListOptions, AiUsageSnapshot, AiUsageStats, AiUsageSummaryOptions, SignalAdapter, SmrtAiUsageRecord } from '@happyvertical/smrt-types';
@@ -1 +1 @@
1
- {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../src/class.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../src/class.ts"],"names":[],"mappings":"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"}
package/dist/class.js CHANGED
@@ -12,7 +12,6 @@ import { ensureDeferredSystemTableCompatibility, tableExists } from "./system/co
12
12
  import { ensureSystemTables } from "./system/bootstrap.js";
13
13
  import { createLogger } from "@happyvertical/logger";
14
14
  import { getDatabase } from "@happyvertical/sql";
15
- import { getAI } from "@happyvertical/ai";
16
15
  //#region src/class.ts
17
16
  /**
18
17
  * `_smrt_migrations.version` suffix marking that the deferred (manifest-created)
@@ -330,7 +329,10 @@ var SmrtClass = class SmrtClass {
330
329
  if (typeof existingOnUsage === "function") await existingOnUsage(event);
331
330
  await this.handleAiUsageCallback(event, aiConfig, usageConfig);
332
331
  };
333
- if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) this._ai = await getAI(aiConfig);
332
+ if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) {
333
+ const { getAI } = await import("@happyvertical/ai");
334
+ this._ai = await getAI(aiConfig);
335
+ }
334
336
  }
335
337
  }
336
338
  await this.initializeSignals();
package/dist/class.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"class.js","names":[],"sources":["../src/class.ts"],"sourcesContent":["import type { AIClientOptions } from '@happyvertical/ai';\nimport { type AIClient, getAI } from '@happyvertical/ai';\nimport type {\n FilesystemAdapter,\n FilesystemAdapterOptions,\n} from '@happyvertical/files';\nimport { createLogger, type LoggerConfig } from '@happyvertical/logger';\nimport type {\n AiTokenUsage,\n AiUsageHandler,\n AiUsageListOptions,\n AiUsageSnapshot,\n AiUsageStats,\n AiUsageSummaryOptions,\n SignalAdapter,\n SmrtAiUsageEvent,\n SmrtAiUsageRecord,\n} from '@happyvertical/smrt-types';\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport { registerChangeFeedWriter } from './change-feed.js';\nimport type {\n AIConfig,\n AiUsageConfig,\n GlobalSignalConfig,\n MetricsConfig,\n PubSubConfig,\n} from './config.js';\nimport { config } from './config.js';\nimport type { DatabaseConfig } from './database.js';\nimport { createFilesystemAdapter } from './filesystem-loader.js';\nimport { applyPostgresRuntimeTimeouts } from './postgres-timeouts.js';\nimport { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport { ensureSystemTables as ensureFrameworkSystemTables } from './system/bootstrap.js';\nimport {\n ensureDeferredSystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { SMRT_SCHEMA_VERSION } from './system/schema.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\n/**\n * `_smrt_migrations.version` suffix marking that the deferred (manifest-created)\n * system tables have been through their compatibility pass (issue #2376).\n */\nconst DEFERRED_COMPATIBILITY_VERSION_SUFFIX = '+deferred-compat';\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ninterface ResolvedAiUsageConfig {\n enabled: boolean;\n persist: boolean;\n estimateCosts: boolean;\n costRates?: Record<string, { input: number; output: number }>;\n handlers: AiUsageHandler[];\n}\n\ntype AiUsageFilterOptions = Pick<\n AiUsageListOptions,\n | 'since'\n | 'until'\n | 'provider'\n | 'model'\n | 'operation'\n | 'className'\n | 'tenantId'\n>;\n\ninterface AiUsageWhereClause {\n conditions: string[];\n params: unknown[];\n nextParamIndex: number;\n}\n\nfunction firstString(...candidates: unknown[]): string | undefined {\n return candidates.find((candidate): candidate is string => {\n return typeof candidate === 'string';\n });\n}\n\nfunction firstNumber(...candidates: unknown[]): number | undefined {\n return candidates.find((candidate): candidate is number => {\n return typeof candidate === 'number';\n });\n}\n\nfunction getDatabaseUrl(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseWithConfig;\n return db.url || dbWithConfig.config?.url || '';\n}\n\nfunction getDatabaseTypeHint(\n config: DatabaseConfig | undefined,\n): string | undefined {\n if (!config || typeof config === 'string') {\n return undefined;\n }\n\n const configWithType = config as DatabaseWithConfig & {\n client?: unknown;\n };\n\n if (typeof configWithType.type === 'string') {\n return configWithType.type;\n }\n\n if (typeof configWithType.config?.type === 'string') {\n return configWithType.config.type;\n }\n\n if ('query' in configWithType && typeof configWithType.query === 'function') {\n return undefined;\n }\n\n if ('client' in configWithType && configWithType.client) {\n return 'postgres';\n }\n\n return undefined;\n}\n\nfunction normalizeIncomingAiUsageTokens(\n value: unknown,\n): AiTokenUsage | undefined {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n\n const usage = value as Record<string, unknown>;\n const promptTokens =\n typeof usage.promptTokens === 'number'\n ? usage.promptTokens\n : typeof usage.inputTokens === 'number'\n ? usage.inputTokens\n : undefined;\n const completionTokens =\n typeof usage.completionTokens === 'number'\n ? usage.completionTokens\n : typeof usage.outputTokens === 'number'\n ? usage.outputTokens\n : undefined;\n const totalTokens =\n typeof usage.totalTokens === 'number'\n ? usage.totalTokens\n : promptTokens !== undefined || completionTokens !== undefined\n ? (promptTokens ?? 0) + (completionTokens ?? 0)\n : undefined;\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction hydratePersistedAiUsageTokens(row: {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n total_tokens?: unknown;\n}): AiTokenUsage | undefined {\n const promptTokens =\n row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : toSafeInteger(row.prompt_tokens, 'AI usage prompt tokens');\n const completionTokens =\n row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : toSafeInteger(row.completion_tokens, 'AI usage completion tokens');\n const totalTokens =\n row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : toSafeInteger(row.total_tokens, 'AI usage total tokens');\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction normalizeAiUsageTags(\n value: unknown,\n): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const tags: Record<string, string> = {};\n for (const [key, tagValue] of Object.entries(value)) {\n if (tagValue === undefined || tagValue === null) continue;\n tags[key] = String(tagValue);\n }\n\n return Object.keys(tags).length > 0 ? tags : undefined;\n}\n\nfunction parseAiUsageTags(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n try {\n return normalizeAiUsageTags(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeAiUsageTimestamp(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!Number.isNaN(date.getTime())) {\n return date;\n }\n }\n\n return new Date();\n}\n\nfunction getQueryRows(\n result: Awaited<ReturnType<DatabaseInterface['query']>>,\n): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] }).rows ?? []);\n}\n\nfunction buildAiUsageWhereClause(\n options: AiUsageFilterOptions,\n): AiUsageWhereClause {\n const conditions: string[] = [];\n const params: unknown[] = [];\n let nextParamIndex = 1;\n\n if (options.since) {\n conditions.push(`created_at >= $${nextParamIndex++}`);\n params.push(options.since.toISOString());\n }\n\n if (options.until) {\n conditions.push(`created_at <= $${nextParamIndex++}`);\n params.push(options.until.toISOString());\n }\n\n if (options.provider) {\n conditions.push(`provider = $${nextParamIndex++}`);\n params.push(options.provider);\n }\n\n if (options.model) {\n conditions.push(`model = $${nextParamIndex++}`);\n params.push(options.model);\n }\n\n if (options.operation) {\n conditions.push(`operation = $${nextParamIndex++}`);\n params.push(options.operation);\n }\n\n if (options.className) {\n conditions.push(`class_name = $${nextParamIndex++}`);\n params.push(options.className);\n }\n\n if (options.tenantId === null) {\n conditions.push(`tenant_id IS NULL`);\n } else if (options.tenantId) {\n conditions.push(`tenant_id = $${nextParamIndex++}`);\n params.push(options.tenantId);\n }\n\n return {\n conditions,\n params,\n nextParamIndex,\n };\n}\n\n/**\n * Configuration options for the SmrtClass\n */\nexport interface SmrtClassOptions {\n /**\n * Optional custom class name override\n */\n _className?: string;\n\n /**\n * Database configuration - unified approach matching @happyvertical/sql\n *\n * Supports three formats:\n * - String shortcut: 'products.db' (auto-detects database type)\n * - Config object: { type: 'sqlite', url: 'products.db' }\n * - DatabaseInterface instance: await getDatabase(...)\n *\n * @see DatabaseConfig for type definition\n */\n db?: DatabaseConfig;\n\n /**\n * Alias for db option - for backward compatibility with documentation\n *\n * @deprecated Use 'db' instead. This alias exists for backward compatibility.\n */\n persistence?: DatabaseConfig;\n\n /**\n * Filesystem adapter configuration options\n */\n fs?: FilesystemAdapterOptions;\n\n /**\n * AI client configuration options or instance\n */\n ai?: AIClientOptions | AIClient;\n\n /**\n * AI usage tracking configuration (overrides global defaults)\n */\n usage?: AiUsageConfig;\n\n /**\n * Logging configuration (overrides global default)\n */\n logging?: LoggerConfig;\n\n /**\n * Metrics configuration (overrides global default)\n */\n metrics?: MetricsConfig;\n\n /**\n * Pub/Sub configuration (overrides global default)\n */\n pubsub?: PubSubConfig;\n\n /**\n * Sanitization configuration (overrides global default)\n */\n sanitization?: import('./config.js').GlobalSignalConfig['sanitization'];\n\n /**\n * Custom signal configuration (overrides global default)\n */\n signals?: {\n /** Shared signal bus instance */\n bus?: SignalBus;\n /** Additional custom adapters */\n adapters?: SignalAdapter[];\n };\n\n /**\n * Internal flag to reuse an already initialized DatabaseInterface instance.\n *\n * Skips database resolution and system-table setup during lightweight hydration.\n * @internal\n */\n _reuseInitializedDb?: boolean;\n\n /**\n * Internal flag to defer runtime-only services such as signals and AI setup.\n *\n * Lightweight hydration paths set this so plain query reads avoid per-row\n * runtime bootstrap costs.\n * @internal\n */\n _deferRuntimeInitialization?: boolean;\n}\n\n/**\n * Foundation class providing core functionality for the SMRT framework\n *\n * SmrtClass provides unified access to database, filesystem, and AI client\n * interfaces. It serves as the foundation for all other classes in the\n * SMRT framework.\n */\nexport class SmrtClass {\n /**\n * AI client instance for interacting with AI models\n */\n protected _ai!: AIClient;\n\n /**\n * Filesystem adapter for file operations\n */\n protected _fs!: FilesystemAdapter;\n\n /**\n * Database interface for data persistence\n */\n protected _db!: DatabaseInterface;\n private _dbEngineHint?: string;\n\n /**\n * Class name used for identification\n */\n protected _className!: string;\n\n /**\n * Signal bus for method execution tracking\n */\n protected _signalBus?: SignalBus;\n\n /**\n * Adapters registered by this instance (for cleanup)\n */\n private _registeredAdapters: SignalAdapter[] = [];\n\n /**\n * In-memory AI usage collector for quick inspection.\n */\n private _aiUsageCollector?: AiUsageCollector;\n\n /**\n * Registered AI usage handlers for this instance.\n */\n private _aiUsageHandlers: AiUsageHandler[] = [];\n\n /**\n * Tracks whether optional runtime services (signals, AI, fs) are ready.\n */\n private _runtimeServicesInitialized = false;\n\n /**\n * Shared in-flight runtime initialization promise for single-flight setup.\n */\n private _runtimeServicesInitPromise?: Promise<void>;\n\n /**\n * Configuration options provided to the class\n */\n public options: SmrtClassOptions;\n\n /**\n * Track which databases have had system tables initialized\n * - WeakSet for :memory: databases (URL not unique, track by instance)\n * - Set<string> for all others (URL is unique identifier)\n */\n private static _systemTablesInitialized = new WeakSet<DatabaseInterface>();\n private static _systemTablesInitializedByUrl = new Set<string>();\n\n /**\n * Creates a new SmrtClass instance\n *\n * @param options - Configuration options for database, filesystem, and AI clients\n */\n constructor(options: SmrtClassOptions = {}) {\n this.options = options;\n this._className = this.constructor.name;\n }\n\n /**\n * Determines whether this class requires a database to function\n *\n * Override this method in subclasses that require database access\n * to enable early validation during initialization.\n *\n * @returns True if database is required, false otherwise\n * @example\n * ```typescript\n * class MyDataModel extends SmrtClass {\n * protected requiresDatabase(): boolean {\n * return true; // This class needs database access\n * }\n * }\n * ```\n */\n protected requiresDatabase(): boolean {\n return false; // Base class doesn't require database by default\n }\n\n /**\n * Initializes database, filesystem, and AI client connections\n *\n * This method sets up all required services based on the provided options.\n * It should be called before using any of the service interfaces.\n *\n * @returns Promise that resolves to this instance for chaining\n * @throws {Error} If database is required but not provided in options\n */\n protected async initialize(): Promise<this> {\n await this.initializeCoreResources();\n\n if (!this.options._deferRuntimeInitialization) {\n await this.initializeRuntimeServices();\n }\n\n return this;\n }\n\n /**\n * Initialize core resources required for ORM behavior.\n *\n * This setup is shared by both full runtime initialization and lightweight\n * query hydration. Hydrated objects reuse an existing DB connection and skip\n * repeated system-table checks.\n */\n protected async initializeCoreResources(): Promise<void> {\n // Framework init hook for the change feed (#1758): make sure the writer\n // interceptor observes every save/delete before any instance can write.\n // Idempotent and cheap when already registered.\n registerChangeFeedWriter();\n\n // Map persistence to db for backward compatibility\n if (this.options.persistence && !this.options.db) {\n this.options.db = this.options.persistence;\n }\n\n // Validate database configuration if required\n if (this.requiresDatabase() && !this.options.db) {\n throw new Error(\n `${this._className} requires a database configuration. ` +\n `Please provide 'db' in options: { db: { url: '...' } } or { db: 'database.db' }`,\n );\n }\n\n if (this.options.db) {\n this._dbEngineHint = getDatabaseTypeHint(this.options.db);\n\n if (\n this.options._reuseInitializedDb &&\n typeof this.options.db === 'object' &&\n 'query' in this.options.db\n ) {\n this._db = this.options.db as DatabaseInterface;\n this.options.db = this._db;\n } else {\n // Handle four db config formats (in implementation order):\n // 1. String URL: 'products.db' (shortcut)\n // 2. DatabaseInterface instance: already initialized db (has 'query' method)\n // 3. Config with client: { type: 'postgres', client: pgPool } (SvelteKit pattern)\n // 4. Config object: { type: 'sqlite', url: 'products.db' }\n if (typeof this.options.db === 'string') {\n // Format 1: String shortcut - let getDatabase auto-detect type from URL\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const isMemoryDb = this.options.db === ':memory:';\n // PostgreSQL URLs pick up the runtime timeout bounds, and the dbid is\n // derived from the bounded URL so the same rewrite at every call site\n // still resolves to one shared pool (#2377).\n const bounded = applyPostgresRuntimeTimeouts({\n url: this.options.db,\n });\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else if ('query' in this.options.db) {\n // Format 2: Already a DatabaseInterface instance - return as-is\n this._db = this.options.db as DatabaseInterface;\n } else if ('client' in this.options.db && this.options.db.client) {\n // Format 3: Config with pre-created client (e.g., from SvelteKit's $env-based connection)\n // Pass the client to getDatabase which will use it instead of creating a new connection\n const dbConfig = this.options.db as {\n type?: string;\n client: unknown;\n url?: string;\n };\n // `client` is a runtime-only property the postgres adapter reads\n // but the public `getDatabase` option union does not model, so the\n // literal is routed through the function's own parameter type.\n this._db = await getDatabase({\n type: dbConfig.type || 'postgres',\n client: dbConfig.client,\n url: dbConfig.url,\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else {\n // Format 4: Config object - pass to getDatabase (handles all types uniformly)\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const dbConfig = this.options.db as { url?: string; type?: string };\n const dbUrl = dbConfig.url || 'memory';\n const isMemoryDb = dbUrl === ':memory:' || dbUrl === 'memory';\n // Keep the config's own key set: an object with no `url` must stay\n // that way so the adapter applies its own default (#2377).\n const bounded = applyPostgresRuntimeTimeouts({ ...this.options.db });\n // The loose config-object variant of `DatabaseConfig` carries an\n // index signature that the closed `getDatabase` option union does\n // not accept structurally, so route through its parameter type.\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url ?? dbUrl}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n }\n\n /**\n * INTENTIONAL MUTATION: After resolving the database config,\n * we replace options.db with the actual DatabaseInterface instance.\n * This enables child objects to share the same connection via:\n *\n * const child = new ChildObject({ db: parent.options.db });\n *\n * Without this, passing this.options to getCollection() would use the config object\n * which causes a NEW db instance to be created, losing data isolation.\n *\n * See issue #567 for context on why this pattern is necessary.\n */\n this.options.db = this._db;\n\n await this.ensureSystemTables();\n }\n }\n }\n\n /**\n * Initialize optional runtime services that are not required for plain ORM reads.\n */\n protected async initializeRuntimeServices(): Promise<void> {\n if (this._runtimeServicesInitialized) {\n return;\n }\n\n if (!this._runtimeServicesInitPromise) {\n this._runtimeServicesInitPromise = (async () => {\n if (this.options.fs && !this._fs) {\n // Acquired through the boundary in filesystem-loader.ts so the\n // @happyvertical/files SDK (S3/googleapis) never enters\n // provider-neutral consumer bundles (#1977/#1979).\n this._fs = await createFilesystemAdapter(this.options.fs);\n }\n\n // Initialize AI client with environment variable support\n // Priority: instance options > env vars > global config > defaults\n const globalConfig = config.toJSON();\n const usageConfig = this.mergeAiUsageConfig(globalConfig);\n this.initializeAiUsageHandlers(usageConfig);\n\n if (\n !this._ai &&\n (this.options.ai || globalConfig.ai || process.env.SMRT_AI_PROVIDER)\n ) {\n // Check if options.ai is already a client-like object with embed method\n // This allows passing mock AI clients for testing\n const aiOption = this.options.ai as\n | Record<string, unknown>\n | undefined;\n if (\n aiOption &&\n typeof aiOption === 'object' &&\n typeof aiOption.embed === 'function' &&\n !aiOption.provider\n ) {\n this._ai = aiOption as unknown as AIClient;\n } else {\n // CC-8 follow-up: ideally this would route through\n // `@happyvertical/smrt-config` for sanitization parity (e.g. via\n // `getPackageConfig('ai', ...)`), but smrt-config currently only\n // merges file-based config + runtime overrides — it does NOT\n // read from `process.env` with a typed prefix/schema. Until\n // smrt-config grows an env-loader (or wraps `loadEnvConfig`),\n // we continue to use the underlying utility directly. Tracked\n // alongside the CC-8 audit on issue #1199.\n const { loadEnvConfig } = await import('@happyvertical/utils');\n\n // Start with global defaults\n const baseConfig = globalConfig.ai || {};\n\n // Merge with instance options (takes priority over global)\n const userConfig = { ...baseConfig, ...this.options.ai };\n\n // Load environment variables and merge (user options take priority).\n // `AIConfig` carries an index signature, so provider-specific keys\n // (`type`, `onUsage`, `defaultModel`, …) read back as `unknown`.\n const aiConfig = loadEnvConfig<AIConfig>(userConfig, {\n packageName: 'ai',\n prefix: 'SMRT',\n schema: {\n provider: 'string',\n model: 'string',\n apiKey: 'string',\n timeout: 'number',\n maxRetries: 'number',\n temperature: 'number',\n maxTokens: 'number',\n },\n });\n\n const existingOnUsage =\n aiConfig.onUsage ??\n (userConfig as Record<string, unknown>).onUsage ??\n undefined;\n aiConfig.onUsage = async (event: unknown) => {\n if (typeof existingOnUsage === 'function') {\n await (existingOnUsage as (usageEvent: unknown) => unknown)(\n event,\n );\n }\n await this.handleAiUsageCallback(event, aiConfig, usageConfig);\n };\n\n // Only initialize if we have a provider configured\n if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) {\n // Use getAI() factory to support all AI providers (OpenAI, Anthropic, Gemini, etc.)\n // getAI() returns AIInterface, which we narrow to AIClient for\n // backward compatibility. The index-signature `AIConfig` is routed\n // through getAI's own parameter type rather than the closed union.\n this._ai = (await getAI(\n aiConfig as unknown as Parameters<typeof getAI>[0],\n )) as unknown as AIClient;\n }\n }\n }\n\n await this.initializeSignals();\n this._runtimeServicesInitialized = true;\n })();\n }\n\n try {\n await this._runtimeServicesInitPromise;\n } finally {\n this._runtimeServicesInitPromise = undefined;\n }\n }\n\n /**\n * Ensure deferred runtime services are ready before using them.\n */\n protected async ensureRuntimeServicesInitialized(): Promise<void> {\n if (!this._runtimeServicesInitialized) {\n await this.initializeRuntimeServices();\n }\n }\n\n /**\n * Resolve the AI client, initializing deferred runtime services on demand.\n */\n protected async getAiClient(): Promise<AIClient> {\n await this.ensureRuntimeServicesInitialized();\n\n if (!this._ai) {\n throw new Error(\n `${this._className} does not have an AI client configured. ` +\n `Provide 'ai' in options or configure a global SMRT AI provider.`,\n );\n }\n\n return this._ai;\n }\n\n /**\n * Resolve the AI client if one is configured, otherwise return undefined.\n */\n protected async getOptionalAiClient(): Promise<AIClient | undefined> {\n await this.ensureRuntimeServicesInitialized();\n return this._ai;\n }\n\n private async isSystemSchemaVersionApplied(\n db: DatabaseInterface,\n version: string,\n ): Promise<boolean> {\n const engine = detectEngine(getDatabaseUrl(db), this._dbEngineHint);\n\n if (\n engine === 'postgres' &&\n !(await tableExists(db, '_smrt_migrations', this._dbEngineHint))\n ) {\n return false;\n }\n\n try {\n const versionParam = engine === 'postgres' ? '$1' : '?';\n const rows = await db.query(\n `SELECT 1 FROM _smrt_migrations WHERE version = ${versionParam} LIMIT 1`,\n version,\n );\n return getQueryRows(rows).length > 0;\n } catch (error) {\n if (engine === 'postgres') {\n throw error;\n }\n\n return false;\n }\n }\n\n /**\n * Ensure SMRT system tables exist in the database\n *\n * System tables use the _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_schema_migrations / _smrt_backfills: Migration and backfill ledgers\n * - _smrt_embeddings: Embedding vectors for semantic search\n * - _smrt_dispatch / _smrt_dispatch_subscriptions: Inter-agent dispatch queue\n * - _smrt_ai_usage: AI usage telemetry\n * - _smrt_changes: Append-only change feed\n *\n * Note that the _smrt_ prefix alone does NOT mean \"system table\" — ~25\n * `@smrt()` domain tables (feature flags, prompt overrides, subscription\n * plans, …) carry it too and are created by `db:migrate`, not here. The\n * framework's own list is `SYSTEM_TABLE_NAMES` in\n * `schema/system-table-shapes.ts`, derived from this DDL.\n *\n * This method is idempotent and safe to call multiple times.\n * Tables are only created once per database connection.\n */\n private async ensureSystemTables(): Promise<void> {\n if (!this._db) return;\n\n const dbUrl = getDatabaseUrl(this._db);\n\n // Some databases share URLs but are different instances:\n // - :memory: databases (SQLite/DuckDB in-memory)\n // - JSON databases (may have undefined or shared URLs)\n // - Any database with undefined URL\n // Use WeakSet for instance tracking, Set<string> for URL tracking\n const dbConstructorName = this._db.constructor?.name || '';\n const isMemoryDb = dbUrl === ':memory:';\n const isJsonDb = dbConstructorName.toLowerCase().includes('json');\n const hasUndefinedUrl = !dbUrl;\n const useInstanceTracking = isMemoryDb || isJsonDb || hasUndefinedUrl;\n\n if (useInstanceTracking) {\n // Check WeakSet for databases that may share URLs (track by instance)\n if (SmrtClass._systemTablesInitialized.has(this._db)) {\n return;\n }\n } else {\n // Check Set<string> for URL-based databases (track by URL)\n if (SmrtClass._systemTablesInitializedByUrl.has(dbUrl)) {\n return;\n }\n }\n\n try {\n await ensureFrameworkSystemTables(this._db, this._dbEngineHint);\n await this.settleDeferredSystemTableCompatibility(this._db);\n await this.ensureNativeVectorStorage();\n\n // Mark as initialized using appropriate tracking mechanism\n if (useInstanceTracking) {\n SmrtClass._systemTablesInitialized.add(this._db);\n } else {\n SmrtClass._systemTablesInitializedByUrl.add(dbUrl);\n }\n } catch (error) {\n // DO NOT SWALLOW ERRORS - fail loudly so we know what's wrong\n const dbInfo = this._db.constructor?.name || 'unknown database';\n throw new Error(\n `Failed to create system tables for ${dbInfo}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Run the compatibility pass for the system tables `db:migrate` creates, then\n * stamp its own marker in `_smrt_migrations` (issue #2376).\n *\n * Deliberately runs OUTSIDE the bootstrap lock/transaction, and deliberately\n * cannot fail startup:\n *\n * - `_smrt_jobs` / `_smrt_job_events` are created by `db:migrate` from the\n * jobs manifest, which on a fresh install runs AFTER the framework's first\n * bootstrap. Gating this pass on {@link SMRT_SCHEMA_VERSION} would mean the\n * version gets stamped while the tables are still absent and the pass never\n * runs on that database again. It therefore carries its own marker, which\n * is written only once every deferred table exists and has been upgraded.\n * - Its statements are ALTER TABLE / CREATE INDEX against tables the\n * framework does not own. Inside the PostgreSQL bootstrap transaction one\n * failure (a permissions problem, say) would abort the transaction and roll\n * back system-table creation with it. Outside, a failure is a logged\n * warning and the next process start retries — the pass is idempotent and\n * already tolerates concurrent creators.\n *\n * Until the tables exist this costs two catalog probes per process per\n * database; afterwards the marker short-circuits it.\n */\n private async settleDeferredSystemTableCompatibility(\n db: DatabaseInterface,\n ): Promise<void> {\n const marker = `${SMRT_SCHEMA_VERSION}${DEFERRED_COMPATIBILITY_VERSION_SUFFIX}`;\n\n try {\n if (await this.isSystemSchemaVersionApplied(db, marker)) {\n return;\n }\n\n const { settled } = await ensureDeferredSystemTableCompatibility(\n db,\n this._dbEngineHint,\n );\n if (!settled) {\n return;\n }\n\n const id = crypto.randomUUID();\n const description = 'Deferred SMRT system table compatibility';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${marker}, ${description})\n ON CONFLICT(version) DO NOTHING\n `;\n } catch (error) {\n logger.warn(\n `[smrt] Deferred system table compatibility did not complete; retrying on the next start: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n private async ensureNativeVectorStorage(): Promise<void> {\n if (!this._db) {\n return;\n }\n\n try {\n const { ObjectRegistry } = await import('./registry.js');\n const embeddingConfig = ObjectRegistry.getProjectEmbeddingConfig();\n if (embeddingConfig?.storage === 'native') {\n const { EmbeddingStorage } = await import('./embeddings/storage.js');\n const vector = this._db.vector;\n if (vector) {\n const dimensions = embeddingConfig.dimensions || 768;\n await EmbeddingStorage.ensureVectorStorage(\n this._db,\n dimensions,\n vector,\n );\n } else {\n logger.warn(\n '[smrt] Embedding storage set to \"native\" but database has no vector capability. Falling back to JSON storage.',\n );\n }\n }\n } catch (error) {\n // Don't fail system table initialization for vector setup errors\n logger.warn(\n `[smrt] Failed to initialize vector storage: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Access system tables through standard database interface\n * System tables use _smrt_ prefix to avoid conflicts with user tables\n */\n protected get systemDb(): DatabaseInterface {\n return this._db;\n }\n\n /**\n * Return the database engine hint captured from the caller's configuration.\n * Adapters may not expose their original type after construction (notably\n * in-memory DuckDB/JSON connections), so query helpers can combine this\n * hint with their public connection capabilities.\n */\n protected getDatabaseEngineHint(): string | undefined {\n return this._dbEngineHint;\n }\n\n /**\n * Initialize signal bus and adapters\n *\n * Merges global configuration with instance-specific overrides.\n * Registers built-in and custom adapters based on configuration.\n */\n private async initializeSignals(): Promise<void> {\n const globalConfig = config.toJSON();\n const effectiveConfig = this.mergeSignalConfig(globalConfig);\n\n // If a shared bus is provided, always use it (don't create new adapters)\n if (this.options.signals?.bus) {\n this._signalBus = this.options.signals.bus;\n return;\n }\n\n // Otherwise, check if we should initialize signals based on config\n if (!this.shouldInitializeSignals(effectiveConfig)) {\n return;\n }\n\n this._signalBus = new SignalBus({\n sanitization: effectiveConfig.sanitization,\n });\n await this.registerAdapters(effectiveConfig);\n }\n\n /**\n * Merge global and instance signal configuration\n *\n * Instance configuration takes priority over global defaults.\n *\n * @param globalConfig - Global configuration from smrt.configure()\n * @returns Merged configuration\n */\n private mergeSignalConfig(\n globalConfig: GlobalSignalConfig,\n ): GlobalSignalConfig {\n return {\n logging: this.options.logging ?? globalConfig.logging,\n metrics: this.options.metrics ?? globalConfig.metrics,\n pubsub: this.options.pubsub ?? globalConfig.pubsub,\n usage: this.mergeAiUsageConfig(globalConfig),\n sanitization: this.options.sanitization ?? globalConfig.sanitization,\n signals: {\n bus: this.options.signals?.bus ?? globalConfig.signals?.bus,\n adapters: [\n ...(globalConfig.signals?.adapters ?? []),\n ...(this.options.signals?.adapters ?? []),\n ],\n },\n };\n }\n\n /**\n * Check if signals should be initialized\n *\n * Signals are initialized if any adapter is configured.\n *\n * @param config - Effective signal configuration\n * @returns True if signals should be initialized\n */\n private shouldInitializeSignals(config: GlobalSignalConfig): boolean {\n return !!(\n config.logging !== false ||\n config.metrics?.enabled ||\n config.pubsub?.enabled ||\n config.signals?.adapters?.length\n );\n }\n\n /**\n * Register signal adapters based on configuration\n *\n * @param config - Effective signal configuration\n */\n private async registerAdapters(config: GlobalSignalConfig): Promise<void> {\n if (!this._signalBus) return;\n\n // Logging adapter (default: enabled with console)\n if (config.logging !== false) {\n const { createLogger, LoggerAdapter } = await import(\n '@happyvertical/logger'\n );\n const logger = createLogger(config.logging ?? true);\n const adapter = new LoggerAdapter(logger);\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Metrics adapter (default: disabled)\n if (config.metrics?.enabled) {\n const { MetricsAdapter } = await import('./adapters/metrics.js');\n const adapter = new MetricsAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Pub/Sub adapter (default: disabled)\n if (config.pubsub?.enabled) {\n const { PubSubAdapter } = await import('./adapters/pubsub.js');\n const adapter = new PubSubAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Custom adapters\n if (config.signals?.adapters) {\n for (const adapter of config.signals.adapters) {\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n }\n }\n\n /**\n * Gets the filesystem adapter instance\n */\n get fs() {\n return this._fs;\n }\n\n /**\n * Gets the database interface instance\n */\n get db() {\n // Throw helpful error if database is accessed before initialization\n if (!this._db) {\n throw new Error(\n `Database accessed before initialization. ` +\n `Please call await instance.initialize() before accessing the database.`,\n );\n }\n return this._db;\n }\n\n /**\n * Temporarily bind this initialized instance to another database interface.\n *\n * This is intended for transaction-scoped work where a caller must ensure\n * that every nested SMRT read and write uses the transaction database passed\n * to its callback. The original database is restored whether the callback\n * succeeds or throws. Do not use the same instance concurrently while it is\n * temporarily bound.\n */\n async withDatabase<T>(\n db: DatabaseInterface,\n operation: (bound: this) => Promise<T>,\n ): Promise<T> {\n // Read through the public getter so an uninitialized instance fails with\n // the normal, actionable SmrtClass error before any state is mutated.\n const previousDb = this.db;\n const previousOptionDb = this.options.db;\n this._db = db;\n this.options.db = db;\n try {\n return await operation(this);\n } finally {\n this._db = previousDb;\n this.options.db = previousOptionDb;\n }\n }\n\n /**\n * Gets the AI client instance\n */\n get ai() {\n return this._ai;\n }\n\n /**\n * Get the in-memory AI usage snapshot for this instance.\n */\n getAiUsageSnapshot(): AiUsageSnapshot | undefined {\n return this._aiUsageCollector?.getSnapshot();\n }\n\n /**\n * Reset the in-memory AI usage collector.\n */\n resetAiUsage(): void {\n this._aiUsageCollector?.reset();\n }\n\n /**\n * List persisted AI usage records.\n */\n async listAiUsage(\n options: AiUsageListOptions = {},\n ): Promise<SmrtAiUsageRecord[]> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n let paramIndex = params.length + 1;\n\n let sql = 'SELECT * FROM _smrt_ai_usage';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ` ORDER BY ${\n options.orderBy === 'timestamp ASC' ? 'created_at ASC' : 'created_at DESC'\n }`;\n\n if (options.limit !== undefined) {\n sql += ` LIMIT $${paramIndex++}`;\n params.push(options.limit);\n }\n\n if (options.offset !== undefined) {\n sql += ` OFFSET $${paramIndex++}`;\n params.push(options.offset);\n }\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n\n return rows.map((row) => ({\n id: String(row.id),\n provider: String(row.provider),\n model: String(row.model),\n operation: String(row.operation),\n usage: hydratePersistedAiUsageTokens(row),\n estimatedCost:\n row.estimated_cost === null || row.estimated_cost === undefined\n ? undefined\n : Number(row.estimated_cost),\n duration: toSafeInteger(row.duration ?? 0, 'AI usage duration'),\n className:\n row.class_name === null || row.class_name === undefined\n ? undefined\n : String(row.class_name),\n tenantId:\n row.tenant_id === undefined\n ? undefined\n : (row.tenant_id as string | null),\n tags: parseAiUsageTags(row.tags),\n timestamp: normalizeAiUsageTimestamp(row.created_at),\n }));\n }\n\n /**\n * Summarize persisted AI usage records by a grouping dimension.\n */\n async summarizeAiUsage(\n options: AiUsageSummaryOptions = {},\n ): Promise<Record<string, AiUsageStats>> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const groupBy = options.groupBy ?? 'model';\n const bucketExpression =\n groupBy === 'provider'\n ? `provider`\n : groupBy === 'model'\n ? `provider || ':' || model`\n : groupBy === 'class'\n ? `COALESCE(class_name, 'unknown')`\n : groupBy === 'tenant'\n ? `COALESCE(tenant_id, 'global')`\n : groupBy === 'operation'\n ? `operation`\n : `substr(CAST(created_at AS TEXT), 1, 10)`;\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n\n let sql = `\n SELECT ${bucketExpression} AS bucket,\n COUNT(*) AS call_count,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(duration), 0) AS total_duration,\n COALESCE(SUM(estimated_cost), 0) AS estimated_cost,\n MAX(created_at) AS last_used\n FROM _smrt_ai_usage\n `;\n\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ' GROUP BY bucket ORDER BY bucket ASC';\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n const summary: Record<string, AiUsageStats> = {};\n\n for (const row of rows) {\n const bucket = String(row.bucket);\n summary[bucket] = {\n callCount: toSafeInteger(row.call_count ?? 0, 'AI usage call count'),\n promptTokens: toSafeInteger(\n row.prompt_tokens ?? 0,\n 'AI usage prompt-token total',\n ),\n completionTokens: toSafeInteger(\n row.completion_tokens ?? 0,\n 'AI usage completion-token total',\n ),\n totalTokens: toSafeInteger(\n row.total_tokens ?? 0,\n 'AI usage token total',\n ),\n totalDuration: toSafeInteger(\n row.total_duration ?? 0,\n 'AI usage duration total',\n ),\n estimatedCost: Number(row.estimated_cost ?? 0),\n lastUsed: row.last_used ? new Date(String(row.last_used)).getTime() : 0,\n };\n }\n\n return summary;\n }\n\n /**\n * Gets the signal bus instance\n *\n * @returns Signal bus if signals are enabled, undefined otherwise\n */\n get signalBus(): SignalBus | undefined {\n return this._signalBus;\n }\n\n /**\n * Cleanup method to prevent memory leaks\n *\n * Unregisters all adapters from the signal bus that were registered\n * by this instance. Call this when the SmrtClass instance is no longer\n * needed to prevent memory leaks.\n *\n * @example\n * ```typescript\n * const product = new Product({ name: 'Widget' });\n * await product.initialize();\n * // ... use product ...\n * product.destroy(); // Clean up when done\n * ```\n */\n destroy(): void {\n // Only unregister adapters if we own the bus (not shared)\n if (this._signalBus && !this.options.signals?.bus) {\n for (const adapter of this._registeredAdapters) {\n this._signalBus.unregister(adapter);\n }\n this._registeredAdapters = [];\n }\n\n // TODO: If SmrtClass grows a broader async teardown lifecycle, move\n // AI usage handler cleanup there alongside other connection-bound state.\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n }\n\n private mergeAiUsageConfig(\n globalConfig: GlobalSignalConfig,\n ): ResolvedAiUsageConfig {\n const globalUsage = globalConfig.usage ?? {};\n const instanceUsage = this.options.usage ?? {};\n\n return {\n enabled: instanceUsage.enabled ?? globalUsage.enabled ?? true,\n persist: instanceUsage.persist ?? globalUsage.persist ?? true,\n estimateCosts:\n instanceUsage.estimateCosts ?? globalUsage.estimateCosts ?? true,\n costRates: {\n ...(globalUsage.costRates ?? {}),\n ...(instanceUsage.costRates ?? {}),\n },\n handlers: [\n ...(globalUsage.handlers ?? []),\n ...(instanceUsage.handlers ?? []),\n ],\n };\n }\n\n private initializeAiUsageHandlers(config: ResolvedAiUsageConfig): void {\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n\n if (!config.enabled) {\n return;\n }\n\n this._aiUsageCollector = new AiUsageCollector();\n this._aiUsageHandlers.push(this._aiUsageCollector);\n\n if (config.persist && this._db) {\n this._aiUsageHandlers.push(new AiUsagePersistenceHandler(this._db));\n }\n\n this._aiUsageHandlers.push(...config.handlers);\n }\n\n private async handleAiUsageCallback(\n event: unknown,\n aiConfig: Record<string, unknown>,\n usageConfig: ResolvedAiUsageConfig,\n ): Promise<void> {\n if (!usageConfig.enabled || this._aiUsageHandlers.length === 0) {\n return;\n }\n\n const normalizedEvent = this.normalizeAiUsageEvent(event, aiConfig);\n if (!normalizedEvent) {\n return;\n }\n\n if (usageConfig.estimateCosts) {\n normalizedEvent.estimatedCost = estimateAiUsageCost(\n normalizedEvent.provider,\n normalizedEvent.model,\n normalizedEvent.usage,\n usageConfig.costRates,\n );\n }\n\n const results = await Promise.allSettled(\n this._aiUsageHandlers.map((handler) => handler.handle(normalizedEvent)),\n );\n\n for (const result of results) {\n if (result.status === 'rejected') {\n logger.warn(\n `[smrt] AI usage handler failed for ${normalizedEvent.provider}:${normalizedEvent.model}: ${\n result.reason instanceof Error\n ? result.reason.message\n : String(result.reason)\n }`,\n );\n }\n }\n }\n\n private normalizeAiUsageEvent(\n event: unknown,\n aiConfig: Record<string, unknown>,\n ): SmrtAiUsageEvent | undefined {\n const raw = (event ?? {}) as Record<string, unknown>;\n const provider = firstString(\n raw.provider,\n raw.type,\n aiConfig.provider,\n aiConfig.type,\n );\n const model = firstString(\n raw.model,\n raw.defaultModel,\n aiConfig.model,\n aiConfig.defaultModel,\n );\n const operation =\n firstString(raw.operation, raw.kind, raw.method) ?? 'unknown';\n const usage =\n normalizeIncomingAiUsageTokens(raw.usage) ??\n normalizeIncomingAiUsageTokens(raw.tokenUsage) ??\n normalizeIncomingAiUsageTokens({\n promptTokens: raw.promptTokens,\n completionTokens: raw.completionTokens,\n totalTokens: raw.totalTokens,\n });\n\n if (!provider || !model) {\n return undefined;\n }\n\n const duration =\n firstNumber(raw.duration, raw.durationMs, raw.latency) ?? 0;\n const tenantId =\n 'tenantId' in this &&\n (this as { tenantId?: string | null }).tenantId !== undefined\n ? ((this as { tenantId?: string | null }).tenantId ?? null)\n : undefined;\n\n return {\n provider,\n model,\n operation,\n usage,\n duration,\n timestamp: normalizeAiUsageTimestamp(raw.timestamp),\n tags: normalizeAiUsageTags(raw.tags),\n className: this._className,\n tenantId,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"class.js","names":[],"sources":["../src/class.ts"],"sourcesContent":["import type { AIClient, AIClientOptions } from '@happyvertical/ai';\nimport type {\n FilesystemAdapter,\n FilesystemAdapterOptions,\n} from '@happyvertical/files';\nimport { createLogger, type LoggerConfig } from '@happyvertical/logger';\nimport type {\n AiTokenUsage,\n AiUsageHandler,\n AiUsageListOptions,\n AiUsageSnapshot,\n AiUsageStats,\n AiUsageSummaryOptions,\n SignalAdapter,\n SmrtAiUsageEvent,\n SmrtAiUsageRecord,\n} from '@happyvertical/smrt-types';\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport { registerChangeFeedWriter } from './change-feed.js';\nimport type {\n AIConfig,\n AiUsageConfig,\n GlobalSignalConfig,\n MetricsConfig,\n PubSubConfig,\n} from './config.js';\nimport { config } from './config.js';\nimport type { DatabaseConfig } from './database.js';\nimport { createFilesystemAdapter } from './filesystem-loader.js';\nimport { applyPostgresRuntimeTimeouts } from './postgres-timeouts.js';\nimport { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport { ensureSystemTables as ensureFrameworkSystemTables } from './system/bootstrap.js';\nimport {\n ensureDeferredSystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { SMRT_SCHEMA_VERSION } from './system/schema.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\n/**\n * `_smrt_migrations.version` suffix marking that the deferred (manifest-created)\n * system tables have been through their compatibility pass (issue #2376).\n */\nconst DEFERRED_COMPATIBILITY_VERSION_SUFFIX = '+deferred-compat';\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ninterface ResolvedAiUsageConfig {\n enabled: boolean;\n persist: boolean;\n estimateCosts: boolean;\n costRates?: Record<string, { input: number; output: number }>;\n handlers: AiUsageHandler[];\n}\n\ntype AiUsageFilterOptions = Pick<\n AiUsageListOptions,\n | 'since'\n | 'until'\n | 'provider'\n | 'model'\n | 'operation'\n | 'className'\n | 'tenantId'\n>;\n\ninterface AiUsageWhereClause {\n conditions: string[];\n params: unknown[];\n nextParamIndex: number;\n}\n\nfunction firstString(...candidates: unknown[]): string | undefined {\n return candidates.find((candidate): candidate is string => {\n return typeof candidate === 'string';\n });\n}\n\nfunction firstNumber(...candidates: unknown[]): number | undefined {\n return candidates.find((candidate): candidate is number => {\n return typeof candidate === 'number';\n });\n}\n\nfunction getDatabaseUrl(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseWithConfig;\n return db.url || dbWithConfig.config?.url || '';\n}\n\nfunction getDatabaseTypeHint(\n config: DatabaseConfig | undefined,\n): string | undefined {\n if (!config || typeof config === 'string') {\n return undefined;\n }\n\n const configWithType = config as DatabaseWithConfig & {\n client?: unknown;\n };\n\n if (typeof configWithType.type === 'string') {\n return configWithType.type;\n }\n\n if (typeof configWithType.config?.type === 'string') {\n return configWithType.config.type;\n }\n\n if ('query' in configWithType && typeof configWithType.query === 'function') {\n return undefined;\n }\n\n if ('client' in configWithType && configWithType.client) {\n return 'postgres';\n }\n\n return undefined;\n}\n\nfunction normalizeIncomingAiUsageTokens(\n value: unknown,\n): AiTokenUsage | undefined {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n\n const usage = value as Record<string, unknown>;\n const promptTokens =\n typeof usage.promptTokens === 'number'\n ? usage.promptTokens\n : typeof usage.inputTokens === 'number'\n ? usage.inputTokens\n : undefined;\n const completionTokens =\n typeof usage.completionTokens === 'number'\n ? usage.completionTokens\n : typeof usage.outputTokens === 'number'\n ? usage.outputTokens\n : undefined;\n const totalTokens =\n typeof usage.totalTokens === 'number'\n ? usage.totalTokens\n : promptTokens !== undefined || completionTokens !== undefined\n ? (promptTokens ?? 0) + (completionTokens ?? 0)\n : undefined;\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction hydratePersistedAiUsageTokens(row: {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n total_tokens?: unknown;\n}): AiTokenUsage | undefined {\n const promptTokens =\n row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : toSafeInteger(row.prompt_tokens, 'AI usage prompt tokens');\n const completionTokens =\n row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : toSafeInteger(row.completion_tokens, 'AI usage completion tokens');\n const totalTokens =\n row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : toSafeInteger(row.total_tokens, 'AI usage total tokens');\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction normalizeAiUsageTags(\n value: unknown,\n): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const tags: Record<string, string> = {};\n for (const [key, tagValue] of Object.entries(value)) {\n if (tagValue === undefined || tagValue === null) continue;\n tags[key] = String(tagValue);\n }\n\n return Object.keys(tags).length > 0 ? tags : undefined;\n}\n\nfunction parseAiUsageTags(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n try {\n return normalizeAiUsageTags(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeAiUsageTimestamp(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!Number.isNaN(date.getTime())) {\n return date;\n }\n }\n\n return new Date();\n}\n\nfunction getQueryRows(\n result: Awaited<ReturnType<DatabaseInterface['query']>>,\n): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] }).rows ?? []);\n}\n\nfunction buildAiUsageWhereClause(\n options: AiUsageFilterOptions,\n): AiUsageWhereClause {\n const conditions: string[] = [];\n const params: unknown[] = [];\n let nextParamIndex = 1;\n\n if (options.since) {\n conditions.push(`created_at >= $${nextParamIndex++}`);\n params.push(options.since.toISOString());\n }\n\n if (options.until) {\n conditions.push(`created_at <= $${nextParamIndex++}`);\n params.push(options.until.toISOString());\n }\n\n if (options.provider) {\n conditions.push(`provider = $${nextParamIndex++}`);\n params.push(options.provider);\n }\n\n if (options.model) {\n conditions.push(`model = $${nextParamIndex++}`);\n params.push(options.model);\n }\n\n if (options.operation) {\n conditions.push(`operation = $${nextParamIndex++}`);\n params.push(options.operation);\n }\n\n if (options.className) {\n conditions.push(`class_name = $${nextParamIndex++}`);\n params.push(options.className);\n }\n\n if (options.tenantId === null) {\n conditions.push(`tenant_id IS NULL`);\n } else if (options.tenantId) {\n conditions.push(`tenant_id = $${nextParamIndex++}`);\n params.push(options.tenantId);\n }\n\n return {\n conditions,\n params,\n nextParamIndex,\n };\n}\n\n/**\n * Configuration options for the SmrtClass\n */\nexport interface SmrtClassOptions {\n /**\n * Optional custom class name override\n */\n _className?: string;\n\n /**\n * Database configuration - unified approach matching @happyvertical/sql\n *\n * Supports three formats:\n * - String shortcut: 'products.db' (auto-detects database type)\n * - Config object: { type: 'sqlite', url: 'products.db' }\n * - DatabaseInterface instance: await getDatabase(...)\n *\n * @see DatabaseConfig for type definition\n */\n db?: DatabaseConfig;\n\n /**\n * Alias for db option - for backward compatibility with documentation\n *\n * @deprecated Use 'db' instead. This alias exists for backward compatibility.\n */\n persistence?: DatabaseConfig;\n\n /**\n * Filesystem adapter configuration options\n */\n fs?: FilesystemAdapterOptions;\n\n /**\n * AI client configuration options or instance\n */\n ai?: AIClientOptions | AIClient;\n\n /**\n * AI usage tracking configuration (overrides global defaults)\n */\n usage?: AiUsageConfig;\n\n /**\n * Logging configuration (overrides global default)\n */\n logging?: LoggerConfig;\n\n /**\n * Metrics configuration (overrides global default)\n */\n metrics?: MetricsConfig;\n\n /**\n * Pub/Sub configuration (overrides global default)\n */\n pubsub?: PubSubConfig;\n\n /**\n * Sanitization configuration (overrides global default)\n */\n sanitization?: import('./config.js').GlobalSignalConfig['sanitization'];\n\n /**\n * Custom signal configuration (overrides global default)\n */\n signals?: {\n /** Shared signal bus instance */\n bus?: SignalBus;\n /** Additional custom adapters */\n adapters?: SignalAdapter[];\n };\n\n /**\n * Internal flag to reuse an already initialized DatabaseInterface instance.\n *\n * Skips database resolution and system-table setup during lightweight hydration.\n * @internal\n */\n _reuseInitializedDb?: boolean;\n\n /**\n * Internal flag to defer runtime-only services such as signals and AI setup.\n *\n * Lightweight hydration paths set this so plain query reads avoid per-row\n * runtime bootstrap costs.\n * @internal\n */\n _deferRuntimeInitialization?: boolean;\n}\n\n/**\n * Foundation class providing core functionality for the SMRT framework\n *\n * SmrtClass provides unified access to database, filesystem, and AI client\n * interfaces. It serves as the foundation for all other classes in the\n * SMRT framework.\n */\nexport class SmrtClass {\n /**\n * AI client instance for interacting with AI models\n */\n protected _ai!: AIClient;\n\n /**\n * Filesystem adapter for file operations\n */\n protected _fs!: FilesystemAdapter;\n\n /**\n * Database interface for data persistence\n */\n protected _db!: DatabaseInterface;\n private _dbEngineHint?: string;\n\n /**\n * Class name used for identification\n */\n protected _className!: string;\n\n /**\n * Signal bus for method execution tracking\n */\n protected _signalBus?: SignalBus;\n\n /**\n * Adapters registered by this instance (for cleanup)\n */\n private _registeredAdapters: SignalAdapter[] = [];\n\n /**\n * In-memory AI usage collector for quick inspection.\n */\n private _aiUsageCollector?: AiUsageCollector;\n\n /**\n * Registered AI usage handlers for this instance.\n */\n private _aiUsageHandlers: AiUsageHandler[] = [];\n\n /**\n * Tracks whether optional runtime services (signals, AI, fs) are ready.\n */\n private _runtimeServicesInitialized = false;\n\n /**\n * Shared in-flight runtime initialization promise for single-flight setup.\n */\n private _runtimeServicesInitPromise?: Promise<void>;\n\n /**\n * Configuration options provided to the class\n */\n public options: SmrtClassOptions;\n\n /**\n * Track which databases have had system tables initialized\n * - WeakSet for :memory: databases (URL not unique, track by instance)\n * - Set<string> for all others (URL is unique identifier)\n */\n private static _systemTablesInitialized = new WeakSet<DatabaseInterface>();\n private static _systemTablesInitializedByUrl = new Set<string>();\n\n /**\n * Creates a new SmrtClass instance\n *\n * @param options - Configuration options for database, filesystem, and AI clients\n */\n constructor(options: SmrtClassOptions = {}) {\n this.options = options;\n this._className = this.constructor.name;\n }\n\n /**\n * Determines whether this class requires a database to function\n *\n * Override this method in subclasses that require database access\n * to enable early validation during initialization.\n *\n * @returns True if database is required, false otherwise\n * @example\n * ```typescript\n * class MyDataModel extends SmrtClass {\n * protected requiresDatabase(): boolean {\n * return true; // This class needs database access\n * }\n * }\n * ```\n */\n protected requiresDatabase(): boolean {\n return false; // Base class doesn't require database by default\n }\n\n /**\n * Initializes database, filesystem, and AI client connections\n *\n * This method sets up all required services based on the provided options.\n * It should be called before using any of the service interfaces.\n *\n * @returns Promise that resolves to this instance for chaining\n * @throws {Error} If database is required but not provided in options\n */\n protected async initialize(): Promise<this> {\n await this.initializeCoreResources();\n\n if (!this.options._deferRuntimeInitialization) {\n await this.initializeRuntimeServices();\n }\n\n return this;\n }\n\n /**\n * Initialize core resources required for ORM behavior.\n *\n * This setup is shared by both full runtime initialization and lightweight\n * query hydration. Hydrated objects reuse an existing DB connection and skip\n * repeated system-table checks.\n */\n protected async initializeCoreResources(): Promise<void> {\n // Framework init hook for the change feed (#1758): make sure the writer\n // interceptor observes every save/delete before any instance can write.\n // Idempotent and cheap when already registered.\n registerChangeFeedWriter();\n\n // Map persistence to db for backward compatibility\n if (this.options.persistence && !this.options.db) {\n this.options.db = this.options.persistence;\n }\n\n // Validate database configuration if required\n if (this.requiresDatabase() && !this.options.db) {\n throw new Error(\n `${this._className} requires a database configuration. ` +\n `Please provide 'db' in options: { db: { url: '...' } } or { db: 'database.db' }`,\n );\n }\n\n if (this.options.db) {\n this._dbEngineHint = getDatabaseTypeHint(this.options.db);\n\n if (\n this.options._reuseInitializedDb &&\n typeof this.options.db === 'object' &&\n 'query' in this.options.db\n ) {\n this._db = this.options.db as DatabaseInterface;\n this.options.db = this._db;\n } else {\n // Handle four db config formats (in implementation order):\n // 1. String URL: 'products.db' (shortcut)\n // 2. DatabaseInterface instance: already initialized db (has 'query' method)\n // 3. Config with client: { type: 'postgres', client: pgPool } (SvelteKit pattern)\n // 4. Config object: { type: 'sqlite', url: 'products.db' }\n if (typeof this.options.db === 'string') {\n // Format 1: String shortcut - let getDatabase auto-detect type from URL\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const isMemoryDb = this.options.db === ':memory:';\n // PostgreSQL URLs pick up the runtime timeout bounds, and the dbid is\n // derived from the bounded URL so the same rewrite at every call site\n // still resolves to one shared pool (#2377).\n const bounded = applyPostgresRuntimeTimeouts({\n url: this.options.db,\n });\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else if ('query' in this.options.db) {\n // Format 2: Already a DatabaseInterface instance - return as-is\n this._db = this.options.db as DatabaseInterface;\n } else if ('client' in this.options.db && this.options.db.client) {\n // Format 3: Config with pre-created client (e.g., from SvelteKit's $env-based connection)\n // Pass the client to getDatabase which will use it instead of creating a new connection\n const dbConfig = this.options.db as {\n type?: string;\n client: unknown;\n url?: string;\n };\n // `client` is a runtime-only property the postgres adapter reads\n // but the public `getDatabase` option union does not model, so the\n // literal is routed through the function's own parameter type.\n this._db = await getDatabase({\n type: dbConfig.type || 'postgres',\n client: dbConfig.client,\n url: dbConfig.url,\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else {\n // Format 4: Config object - pass to getDatabase (handles all types uniformly)\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const dbConfig = this.options.db as { url?: string; type?: string };\n const dbUrl = dbConfig.url || 'memory';\n const isMemoryDb = dbUrl === ':memory:' || dbUrl === 'memory';\n // Keep the config's own key set: an object with no `url` must stay\n // that way so the adapter applies its own default (#2377).\n const bounded = applyPostgresRuntimeTimeouts({ ...this.options.db });\n // The loose config-object variant of `DatabaseConfig` carries an\n // index signature that the closed `getDatabase` option union does\n // not accept structurally, so route through its parameter type.\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url ?? dbUrl}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n }\n\n /**\n * INTENTIONAL MUTATION: After resolving the database config,\n * we replace options.db with the actual DatabaseInterface instance.\n * This enables child objects to share the same connection via:\n *\n * const child = new ChildObject({ db: parent.options.db });\n *\n * Without this, passing this.options to getCollection() would use the config object\n * which causes a NEW db instance to be created, losing data isolation.\n *\n * See issue #567 for context on why this pattern is necessary.\n */\n this.options.db = this._db;\n\n await this.ensureSystemTables();\n }\n }\n }\n\n /**\n * Initialize optional runtime services that are not required for plain ORM reads.\n */\n protected async initializeRuntimeServices(): Promise<void> {\n if (this._runtimeServicesInitialized) {\n return;\n }\n\n if (!this._runtimeServicesInitPromise) {\n this._runtimeServicesInitPromise = (async () => {\n if (this.options.fs && !this._fs) {\n // Acquired through the boundary in filesystem-loader.ts so the\n // @happyvertical/files SDK (S3/googleapis) never enters\n // provider-neutral consumer bundles (#1977/#1979).\n this._fs = await createFilesystemAdapter(this.options.fs);\n }\n\n // Initialize AI client with environment variable support\n // Priority: instance options > env vars > global config > defaults\n const globalConfig = config.toJSON();\n const usageConfig = this.mergeAiUsageConfig(globalConfig);\n this.initializeAiUsageHandlers(usageConfig);\n\n if (\n !this._ai &&\n (this.options.ai || globalConfig.ai || process.env.SMRT_AI_PROVIDER)\n ) {\n // Check if options.ai is already a client-like object with embed method\n // This allows passing mock AI clients for testing\n const aiOption = this.options.ai as\n | Record<string, unknown>\n | undefined;\n if (\n aiOption &&\n typeof aiOption === 'object' &&\n typeof aiOption.embed === 'function' &&\n !aiOption.provider\n ) {\n this._ai = aiOption as unknown as AIClient;\n } else {\n // CC-8 follow-up: ideally this would route through\n // `@happyvertical/smrt-config` for sanitization parity (e.g. via\n // `getPackageConfig('ai', ...)`), but smrt-config currently only\n // merges file-based config + runtime overrides — it does NOT\n // read from `process.env` with a typed prefix/schema. Until\n // smrt-config grows an env-loader (or wraps `loadEnvConfig`),\n // we continue to use the underlying utility directly. Tracked\n // alongside the CC-8 audit on issue #1199.\n const { loadEnvConfig } = await import('@happyvertical/utils');\n\n // Start with global defaults\n const baseConfig = globalConfig.ai || {};\n\n // Merge with instance options (takes priority over global)\n const userConfig = { ...baseConfig, ...this.options.ai };\n\n // Load environment variables and merge (user options take priority).\n // `AIConfig` carries an index signature, so provider-specific keys\n // (`type`, `onUsage`, `defaultModel`, …) read back as `unknown`.\n const aiConfig = loadEnvConfig<AIConfig>(userConfig, {\n packageName: 'ai',\n prefix: 'SMRT',\n schema: {\n provider: 'string',\n model: 'string',\n apiKey: 'string',\n timeout: 'number',\n maxRetries: 'number',\n temperature: 'number',\n maxTokens: 'number',\n },\n });\n\n const existingOnUsage =\n aiConfig.onUsage ??\n (userConfig as Record<string, unknown>).onUsage ??\n undefined;\n aiConfig.onUsage = async (event: unknown) => {\n if (typeof existingOnUsage === 'function') {\n await (existingOnUsage as (usageEvent: unknown) => unknown)(\n event,\n );\n }\n await this.handleAiUsageCallback(event, aiConfig, usageConfig);\n };\n\n // Only initialize if we have a provider configured\n if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) {\n // Use the getAI() factory (loaded lazily to keep the\n // `@happyvertical/ai` module graph off smrt-core's cold-start\n // import path — see issue #2894) to support all AI providers\n // (OpenAI, Anthropic, Gemini, etc.). getAI() returns\n // AIInterface, which we narrow to AIClient for backward\n // compatibility. The index-signature `AIConfig` is routed\n // through getAI's own parameter type rather than the closed\n // union.\n const { getAI } = await import('@happyvertical/ai');\n this._ai = (await getAI(\n aiConfig as unknown as Parameters<typeof getAI>[0],\n )) as unknown as AIClient;\n }\n }\n }\n\n await this.initializeSignals();\n this._runtimeServicesInitialized = true;\n })();\n }\n\n try {\n await this._runtimeServicesInitPromise;\n } finally {\n this._runtimeServicesInitPromise = undefined;\n }\n }\n\n /**\n * Ensure deferred runtime services are ready before using them.\n */\n protected async ensureRuntimeServicesInitialized(): Promise<void> {\n if (!this._runtimeServicesInitialized) {\n await this.initializeRuntimeServices();\n }\n }\n\n /**\n * Resolve the AI client, initializing deferred runtime services on demand.\n */\n protected async getAiClient(): Promise<AIClient> {\n await this.ensureRuntimeServicesInitialized();\n\n if (!this._ai) {\n throw new Error(\n `${this._className} does not have an AI client configured. ` +\n `Provide 'ai' in options or configure a global SMRT AI provider.`,\n );\n }\n\n return this._ai;\n }\n\n /**\n * Resolve the AI client if one is configured, otherwise return undefined.\n */\n protected async getOptionalAiClient(): Promise<AIClient | undefined> {\n await this.ensureRuntimeServicesInitialized();\n return this._ai;\n }\n\n private async isSystemSchemaVersionApplied(\n db: DatabaseInterface,\n version: string,\n ): Promise<boolean> {\n const engine = detectEngine(getDatabaseUrl(db), this._dbEngineHint);\n\n if (\n engine === 'postgres' &&\n !(await tableExists(db, '_smrt_migrations', this._dbEngineHint))\n ) {\n return false;\n }\n\n try {\n const versionParam = engine === 'postgres' ? '$1' : '?';\n const rows = await db.query(\n `SELECT 1 FROM _smrt_migrations WHERE version = ${versionParam} LIMIT 1`,\n version,\n );\n return getQueryRows(rows).length > 0;\n } catch (error) {\n if (engine === 'postgres') {\n throw error;\n }\n\n return false;\n }\n }\n\n /**\n * Ensure SMRT system tables exist in the database\n *\n * System tables use the _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_schema_migrations / _smrt_backfills: Migration and backfill ledgers\n * - _smrt_embeddings: Embedding vectors for semantic search\n * - _smrt_dispatch / _smrt_dispatch_subscriptions: Inter-agent dispatch queue\n * - _smrt_ai_usage: AI usage telemetry\n * - _smrt_changes: Append-only change feed\n *\n * Note that the _smrt_ prefix alone does NOT mean \"system table\" — ~25\n * `@smrt()` domain tables (feature flags, prompt overrides, subscription\n * plans, …) carry it too and are created by `db:migrate`, not here. The\n * framework's own list is `SYSTEM_TABLE_NAMES` in\n * `schema/system-table-shapes.ts`, derived from this DDL.\n *\n * This method is idempotent and safe to call multiple times.\n * Tables are only created once per database connection.\n */\n private async ensureSystemTables(): Promise<void> {\n if (!this._db) return;\n\n const dbUrl = getDatabaseUrl(this._db);\n\n // Some databases share URLs but are different instances:\n // - :memory: databases (SQLite/DuckDB in-memory)\n // - JSON databases (may have undefined or shared URLs)\n // - Any database with undefined URL\n // Use WeakSet for instance tracking, Set<string> for URL tracking\n const dbConstructorName = this._db.constructor?.name || '';\n const isMemoryDb = dbUrl === ':memory:';\n const isJsonDb = dbConstructorName.toLowerCase().includes('json');\n const hasUndefinedUrl = !dbUrl;\n const useInstanceTracking = isMemoryDb || isJsonDb || hasUndefinedUrl;\n\n if (useInstanceTracking) {\n // Check WeakSet for databases that may share URLs (track by instance)\n if (SmrtClass._systemTablesInitialized.has(this._db)) {\n return;\n }\n } else {\n // Check Set<string> for URL-based databases (track by URL)\n if (SmrtClass._systemTablesInitializedByUrl.has(dbUrl)) {\n return;\n }\n }\n\n try {\n await ensureFrameworkSystemTables(this._db, this._dbEngineHint);\n await this.settleDeferredSystemTableCompatibility(this._db);\n await this.ensureNativeVectorStorage();\n\n // Mark as initialized using appropriate tracking mechanism\n if (useInstanceTracking) {\n SmrtClass._systemTablesInitialized.add(this._db);\n } else {\n SmrtClass._systemTablesInitializedByUrl.add(dbUrl);\n }\n } catch (error) {\n // DO NOT SWALLOW ERRORS - fail loudly so we know what's wrong\n const dbInfo = this._db.constructor?.name || 'unknown database';\n throw new Error(\n `Failed to create system tables for ${dbInfo}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Run the compatibility pass for the system tables `db:migrate` creates, then\n * stamp its own marker in `_smrt_migrations` (issue #2376).\n *\n * Deliberately runs OUTSIDE the bootstrap lock/transaction, and deliberately\n * cannot fail startup:\n *\n * - `_smrt_jobs` / `_smrt_job_events` are created by `db:migrate` from the\n * jobs manifest, which on a fresh install runs AFTER the framework's first\n * bootstrap. Gating this pass on {@link SMRT_SCHEMA_VERSION} would mean the\n * version gets stamped while the tables are still absent and the pass never\n * runs on that database again. It therefore carries its own marker, which\n * is written only once every deferred table exists and has been upgraded.\n * - Its statements are ALTER TABLE / CREATE INDEX against tables the\n * framework does not own. Inside the PostgreSQL bootstrap transaction one\n * failure (a permissions problem, say) would abort the transaction and roll\n * back system-table creation with it. Outside, a failure is a logged\n * warning and the next process start retries — the pass is idempotent and\n * already tolerates concurrent creators.\n *\n * Until the tables exist this costs two catalog probes per process per\n * database; afterwards the marker short-circuits it.\n */\n private async settleDeferredSystemTableCompatibility(\n db: DatabaseInterface,\n ): Promise<void> {\n const marker = `${SMRT_SCHEMA_VERSION}${DEFERRED_COMPATIBILITY_VERSION_SUFFIX}`;\n\n try {\n if (await this.isSystemSchemaVersionApplied(db, marker)) {\n return;\n }\n\n const { settled } = await ensureDeferredSystemTableCompatibility(\n db,\n this._dbEngineHint,\n );\n if (!settled) {\n return;\n }\n\n const id = crypto.randomUUID();\n const description = 'Deferred SMRT system table compatibility';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${marker}, ${description})\n ON CONFLICT(version) DO NOTHING\n `;\n } catch (error) {\n logger.warn(\n `[smrt] Deferred system table compatibility did not complete; retrying on the next start: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n private async ensureNativeVectorStorage(): Promise<void> {\n if (!this._db) {\n return;\n }\n\n try {\n const { ObjectRegistry } = await import('./registry.js');\n const embeddingConfig = ObjectRegistry.getProjectEmbeddingConfig();\n if (embeddingConfig?.storage === 'native') {\n const { EmbeddingStorage } = await import('./embeddings/storage.js');\n const vector = this._db.vector;\n if (vector) {\n const dimensions = embeddingConfig.dimensions || 768;\n await EmbeddingStorage.ensureVectorStorage(\n this._db,\n dimensions,\n vector,\n );\n } else {\n logger.warn(\n '[smrt] Embedding storage set to \"native\" but database has no vector capability. Falling back to JSON storage.',\n );\n }\n }\n } catch (error) {\n // Don't fail system table initialization for vector setup errors\n logger.warn(\n `[smrt] Failed to initialize vector storage: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Access system tables through standard database interface\n * System tables use _smrt_ prefix to avoid conflicts with user tables\n */\n protected get systemDb(): DatabaseInterface {\n return this._db;\n }\n\n /**\n * Return the database engine hint captured from the caller's configuration.\n * Adapters may not expose their original type after construction (notably\n * in-memory DuckDB/JSON connections), so query helpers can combine this\n * hint with their public connection capabilities.\n */\n protected getDatabaseEngineHint(): string | undefined {\n return this._dbEngineHint;\n }\n\n /**\n * Initialize signal bus and adapters\n *\n * Merges global configuration with instance-specific overrides.\n * Registers built-in and custom adapters based on configuration.\n */\n private async initializeSignals(): Promise<void> {\n const globalConfig = config.toJSON();\n const effectiveConfig = this.mergeSignalConfig(globalConfig);\n\n // If a shared bus is provided, always use it (don't create new adapters)\n if (this.options.signals?.bus) {\n this._signalBus = this.options.signals.bus;\n return;\n }\n\n // Otherwise, check if we should initialize signals based on config\n if (!this.shouldInitializeSignals(effectiveConfig)) {\n return;\n }\n\n this._signalBus = new SignalBus({\n sanitization: effectiveConfig.sanitization,\n });\n await this.registerAdapters(effectiveConfig);\n }\n\n /**\n * Merge global and instance signal configuration\n *\n * Instance configuration takes priority over global defaults.\n *\n * @param globalConfig - Global configuration from smrt.configure()\n * @returns Merged configuration\n */\n private mergeSignalConfig(\n globalConfig: GlobalSignalConfig,\n ): GlobalSignalConfig {\n return {\n logging: this.options.logging ?? globalConfig.logging,\n metrics: this.options.metrics ?? globalConfig.metrics,\n pubsub: this.options.pubsub ?? globalConfig.pubsub,\n usage: this.mergeAiUsageConfig(globalConfig),\n sanitization: this.options.sanitization ?? globalConfig.sanitization,\n signals: {\n bus: this.options.signals?.bus ?? globalConfig.signals?.bus,\n adapters: [\n ...(globalConfig.signals?.adapters ?? []),\n ...(this.options.signals?.adapters ?? []),\n ],\n },\n };\n }\n\n /**\n * Check if signals should be initialized\n *\n * Signals are initialized if any adapter is configured.\n *\n * @param config - Effective signal configuration\n * @returns True if signals should be initialized\n */\n private shouldInitializeSignals(config: GlobalSignalConfig): boolean {\n return !!(\n config.logging !== false ||\n config.metrics?.enabled ||\n config.pubsub?.enabled ||\n config.signals?.adapters?.length\n );\n }\n\n /**\n * Register signal adapters based on configuration\n *\n * @param config - Effective signal configuration\n */\n private async registerAdapters(config: GlobalSignalConfig): Promise<void> {\n if (!this._signalBus) return;\n\n // Logging adapter (default: enabled with console)\n if (config.logging !== false) {\n const { createLogger, LoggerAdapter } = await import(\n '@happyvertical/logger'\n );\n const logger = createLogger(config.logging ?? true);\n const adapter = new LoggerAdapter(logger);\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Metrics adapter (default: disabled)\n if (config.metrics?.enabled) {\n const { MetricsAdapter } = await import('./adapters/metrics.js');\n const adapter = new MetricsAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Pub/Sub adapter (default: disabled)\n if (config.pubsub?.enabled) {\n const { PubSubAdapter } = await import('./adapters/pubsub.js');\n const adapter = new PubSubAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Custom adapters\n if (config.signals?.adapters) {\n for (const adapter of config.signals.adapters) {\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n }\n }\n\n /**\n * Gets the filesystem adapter instance\n */\n get fs() {\n return this._fs;\n }\n\n /**\n * Gets the database interface instance\n */\n get db() {\n // Throw helpful error if database is accessed before initialization\n if (!this._db) {\n throw new Error(\n `Database accessed before initialization. ` +\n `Please call await instance.initialize() before accessing the database.`,\n );\n }\n return this._db;\n }\n\n /**\n * Temporarily bind this initialized instance to another database interface.\n *\n * This is intended for transaction-scoped work where a caller must ensure\n * that every nested SMRT read and write uses the transaction database passed\n * to its callback. The original database is restored whether the callback\n * succeeds or throws. Do not use the same instance concurrently while it is\n * temporarily bound.\n */\n async withDatabase<T>(\n db: DatabaseInterface,\n operation: (bound: this) => Promise<T>,\n ): Promise<T> {\n // Read through the public getter so an uninitialized instance fails with\n // the normal, actionable SmrtClass error before any state is mutated.\n const previousDb = this.db;\n const previousOptionDb = this.options.db;\n this._db = db;\n this.options.db = db;\n try {\n return await operation(this);\n } finally {\n this._db = previousDb;\n this.options.db = previousOptionDb;\n }\n }\n\n /**\n * Gets the AI client instance\n */\n get ai() {\n return this._ai;\n }\n\n /**\n * Get the in-memory AI usage snapshot for this instance.\n */\n getAiUsageSnapshot(): AiUsageSnapshot | undefined {\n return this._aiUsageCollector?.getSnapshot();\n }\n\n /**\n * Reset the in-memory AI usage collector.\n */\n resetAiUsage(): void {\n this._aiUsageCollector?.reset();\n }\n\n /**\n * List persisted AI usage records.\n */\n async listAiUsage(\n options: AiUsageListOptions = {},\n ): Promise<SmrtAiUsageRecord[]> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n let paramIndex = params.length + 1;\n\n let sql = 'SELECT * FROM _smrt_ai_usage';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ` ORDER BY ${\n options.orderBy === 'timestamp ASC' ? 'created_at ASC' : 'created_at DESC'\n }`;\n\n if (options.limit !== undefined) {\n sql += ` LIMIT $${paramIndex++}`;\n params.push(options.limit);\n }\n\n if (options.offset !== undefined) {\n sql += ` OFFSET $${paramIndex++}`;\n params.push(options.offset);\n }\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n\n return rows.map((row) => ({\n id: String(row.id),\n provider: String(row.provider),\n model: String(row.model),\n operation: String(row.operation),\n usage: hydratePersistedAiUsageTokens(row),\n estimatedCost:\n row.estimated_cost === null || row.estimated_cost === undefined\n ? undefined\n : Number(row.estimated_cost),\n duration: toSafeInteger(row.duration ?? 0, 'AI usage duration'),\n className:\n row.class_name === null || row.class_name === undefined\n ? undefined\n : String(row.class_name),\n tenantId:\n row.tenant_id === undefined\n ? undefined\n : (row.tenant_id as string | null),\n tags: parseAiUsageTags(row.tags),\n timestamp: normalizeAiUsageTimestamp(row.created_at),\n }));\n }\n\n /**\n * Summarize persisted AI usage records by a grouping dimension.\n */\n async summarizeAiUsage(\n options: AiUsageSummaryOptions = {},\n ): Promise<Record<string, AiUsageStats>> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const groupBy = options.groupBy ?? 'model';\n const bucketExpression =\n groupBy === 'provider'\n ? `provider`\n : groupBy === 'model'\n ? `provider || ':' || model`\n : groupBy === 'class'\n ? `COALESCE(class_name, 'unknown')`\n : groupBy === 'tenant'\n ? `COALESCE(tenant_id, 'global')`\n : groupBy === 'operation'\n ? `operation`\n : `substr(CAST(created_at AS TEXT), 1, 10)`;\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n\n let sql = `\n SELECT ${bucketExpression} AS bucket,\n COUNT(*) AS call_count,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(duration), 0) AS total_duration,\n COALESCE(SUM(estimated_cost), 0) AS estimated_cost,\n MAX(created_at) AS last_used\n FROM _smrt_ai_usage\n `;\n\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ' GROUP BY bucket ORDER BY bucket ASC';\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n const summary: Record<string, AiUsageStats> = {};\n\n for (const row of rows) {\n const bucket = String(row.bucket);\n summary[bucket] = {\n callCount: toSafeInteger(row.call_count ?? 0, 'AI usage call count'),\n promptTokens: toSafeInteger(\n row.prompt_tokens ?? 0,\n 'AI usage prompt-token total',\n ),\n completionTokens: toSafeInteger(\n row.completion_tokens ?? 0,\n 'AI usage completion-token total',\n ),\n totalTokens: toSafeInteger(\n row.total_tokens ?? 0,\n 'AI usage token total',\n ),\n totalDuration: toSafeInteger(\n row.total_duration ?? 0,\n 'AI usage duration total',\n ),\n estimatedCost: Number(row.estimated_cost ?? 0),\n lastUsed: row.last_used ? new Date(String(row.last_used)).getTime() : 0,\n };\n }\n\n return summary;\n }\n\n /**\n * Gets the signal bus instance\n *\n * @returns Signal bus if signals are enabled, undefined otherwise\n */\n get signalBus(): SignalBus | undefined {\n return this._signalBus;\n }\n\n /**\n * Cleanup method to prevent memory leaks\n *\n * Unregisters all adapters from the signal bus that were registered\n * by this instance. Call this when the SmrtClass instance is no longer\n * needed to prevent memory leaks.\n *\n * @example\n * ```typescript\n * const product = new Product({ name: 'Widget' });\n * await product.initialize();\n * // ... use product ...\n * product.destroy(); // Clean up when done\n * ```\n */\n destroy(): void {\n // Only unregister adapters if we own the bus (not shared)\n if (this._signalBus && !this.options.signals?.bus) {\n for (const adapter of this._registeredAdapters) {\n this._signalBus.unregister(adapter);\n }\n this._registeredAdapters = [];\n }\n\n // TODO: If SmrtClass grows a broader async teardown lifecycle, move\n // AI usage handler cleanup there alongside other connection-bound state.\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n }\n\n private mergeAiUsageConfig(\n globalConfig: GlobalSignalConfig,\n ): ResolvedAiUsageConfig {\n const globalUsage = globalConfig.usage ?? {};\n const instanceUsage = this.options.usage ?? {};\n\n return {\n enabled: instanceUsage.enabled ?? globalUsage.enabled ?? true,\n persist: instanceUsage.persist ?? globalUsage.persist ?? true,\n estimateCosts:\n instanceUsage.estimateCosts ?? globalUsage.estimateCosts ?? true,\n costRates: {\n ...(globalUsage.costRates ?? {}),\n ...(instanceUsage.costRates ?? {}),\n },\n handlers: [\n ...(globalUsage.handlers ?? []),\n ...(instanceUsage.handlers ?? []),\n ],\n };\n }\n\n private initializeAiUsageHandlers(config: ResolvedAiUsageConfig): void {\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n\n if (!config.enabled) {\n return;\n }\n\n this._aiUsageCollector = new AiUsageCollector();\n this._aiUsageHandlers.push(this._aiUsageCollector);\n\n if (config.persist && this._db) {\n this._aiUsageHandlers.push(new AiUsagePersistenceHandler(this._db));\n }\n\n this._aiUsageHandlers.push(...config.handlers);\n }\n\n private async handleAiUsageCallback(\n event: unknown,\n aiConfig: Record<string, unknown>,\n usageConfig: ResolvedAiUsageConfig,\n ): Promise<void> {\n if (!usageConfig.enabled || this._aiUsageHandlers.length === 0) {\n return;\n }\n\n const normalizedEvent = this.normalizeAiUsageEvent(event, aiConfig);\n if (!normalizedEvent) {\n return;\n }\n\n if (usageConfig.estimateCosts) {\n normalizedEvent.estimatedCost = estimateAiUsageCost(\n normalizedEvent.provider,\n normalizedEvent.model,\n normalizedEvent.usage,\n usageConfig.costRates,\n );\n }\n\n const results = await Promise.allSettled(\n this._aiUsageHandlers.map((handler) => handler.handle(normalizedEvent)),\n );\n\n for (const result of results) {\n if (result.status === 'rejected') {\n logger.warn(\n `[smrt] AI usage handler failed for ${normalizedEvent.provider}:${normalizedEvent.model}: ${\n result.reason instanceof Error\n ? result.reason.message\n : String(result.reason)\n }`,\n );\n }\n }\n }\n\n private normalizeAiUsageEvent(\n event: unknown,\n aiConfig: Record<string, unknown>,\n ): SmrtAiUsageEvent | undefined {\n const raw = (event ?? {}) as Record<string, unknown>;\n const provider = firstString(\n raw.provider,\n raw.type,\n aiConfig.provider,\n aiConfig.type,\n );\n const model = firstString(\n raw.model,\n raw.defaultModel,\n aiConfig.model,\n aiConfig.defaultModel,\n );\n const operation =\n firstString(raw.operation, raw.kind, raw.method) ?? 'unknown';\n const usage =\n normalizeIncomingAiUsageTokens(raw.usage) ??\n normalizeIncomingAiUsageTokens(raw.tokenUsage) ??\n normalizeIncomingAiUsageTokens({\n promptTokens: raw.promptTokens,\n completionTokens: raw.completionTokens,\n totalTokens: raw.totalTokens,\n });\n\n if (!provider || !model) {\n return undefined;\n }\n\n const duration =\n firstNumber(raw.duration, raw.durationMs, raw.latency) ?? 0;\n const tenantId =\n 'tenantId' in this &&\n (this as { tenantId?: string | null }).tenantId !== undefined\n ? ((this as { tenantId?: string | null }).tenantId ?? null)\n : undefined;\n\n return {\n provider,\n model,\n operation,\n usage,\n duration,\n timestamp: normalizeAiUsageTimestamp(raw.timestamp),\n tags: normalizeAiUsageTags(raw.tags),\n className: this._className,\n tenantId,\n };\n }\n}\n"],"mappings":"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"}
@@ -41,6 +41,25 @@ export interface FieldOptions {
41
41
  required?: boolean;
42
42
  /** Default value for the field */
43
43
  default?: unknown;
44
+ /**
45
+ * Per-row SQL expression `db:migrate` uses to fill existing rows when this
46
+ * field is added as a required column (no `default`) to a table that
47
+ * already has rows, or when an existing nullable column with NULLs becomes
48
+ * required (#3008). Evaluated once per row inside the migration
49
+ * transaction, before NOT NULL and any unique index are enforced, so it
50
+ * can derive a distinct value from the row's other columns:
51
+ *
52
+ * ```typescript
53
+ * @field({ required: true, maxLength: 80, backfill: "'closed:' || id" })
54
+ * openKey: string = '';
55
+ * ```
56
+ *
57
+ * Must be a string literal (the build-time scanner reads it from source).
58
+ * Write it in SQL that every engine you run accepts (`||`, `CASE`,
59
+ * `COALESCE`, `CAST`); columns are the table's snake_case names. It is not
60
+ * a default: the model must still supply a value on every insert.
61
+ */
62
+ backfill?: string;
44
63
  /** Whether the field is unique */
45
64
  unique?: boolean;
46
65
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACX,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,2BAA2B,EAKjC,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAgBxD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,SAAS,GACT,SAAS,GACT,SAAS,GACT,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,MAAM,SAAS,GACjB,kBAAkB,GAClB,MAAM,GACN,YAAY,GACZ,iBAAiB,GACjB,WAAW,GACX,YAAY,CAAC;AAEjB,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,EAAE,CAAC,EAAE,YAAY,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,YAAY;IAC5D,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,IAAI,CAAC,EAAE,YAAY,GAAG,iBAAiB,GAAG,WAAW,GAAG,YAAY,CAAC;IACrE;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC;IAC7D;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EACP,OAAO,GACP;QACE;;;;;;;WAOG;QACH,OAAO,EAAE,KAAK,CAAC,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;KAC3D,CAAC;CACP;AAED;;GAEG;AACH,MAAM,WAAW,sBACf,SAAQ,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1D;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAyFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,KAAK,CACnB,OAAO,GAAE,YAAY,GAAG,mBAAmB,GAAG,gBAAqB,GAkB7D,2BAA2B,CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,wBAAgB,UAAU,CACxB,YAAY,EAAE,MAAM,GAAG,QAAQ,EAC/B,OAAO,GAAE,IAAI,CAAC,wBAAwB,EAAE,SAAS,CAAM,GA2BjD,2BAA2B,CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,sBAA2B,GAsB9B,2BAA2B,CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,MAAM,GAAG,QAAQ,EAC/B,OAAO,GAAE,IAAI,CAAC,wBAAwB,EAAE,SAAS,CAAM,GA4BjD,2BAA2B,CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CACxB,YAAY,EAAE,MAAM,GAAG,QAAQ,EAC/B,OAAO,GAAE,IAAI,CAAC,wBAAwB,EAAE,SAAS,CAAM,GA4BjD,2BAA2B,CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,YAAiB,GAoBvC,2BAA2B,CAClC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAE3B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAE1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IAEpB,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAqB1C,yBAAyB,CAChC"}