@happyvertical/smrt-core 0.40.14 → 0.40.16

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 (92) hide show
  1. package/README.md +85 -11
  2. package/dist/browser.js +2 -2
  3. package/dist/change-feed.d.ts.map +1 -1
  4. package/dist/change-feed.js +21 -4
  5. package/dist/change-feed.js.map +1 -1
  6. package/dist/class.d.ts.map +1 -1
  7. package/dist/class.js +5 -3
  8. package/dist/class.js.map +1 -1
  9. package/dist/dispatch/bus.d.ts.map +1 -1
  10. package/dist/dispatch/bus.js +11 -6
  11. package/dist/dispatch/bus.js.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/manifest/static-manifest.js +2 -2
  14. package/dist/manifest/static-manifest.js.map +1 -1
  15. package/dist/manifest/store.js +1 -1
  16. package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
  17. package/dist/manifest/test-manifest-stub.js +132 -2
  18. package/dist/manifest/test-manifest-stub.js.map +1 -1
  19. package/dist/manifest.json +2 -2
  20. package/dist/migrations/backfill-tracker.d.ts +1 -0
  21. package/dist/migrations/backfill-tracker.d.ts.map +1 -1
  22. package/dist/migrations/backfill-tracker.js +9 -3
  23. package/dist/migrations/backfill-tracker.js.map +1 -1
  24. package/dist/migrations/differ.d.ts +18 -0
  25. package/dist/migrations/differ.d.ts.map +1 -1
  26. package/dist/migrations/differ.js +28 -5
  27. package/dist/migrations/differ.js.map +1 -1
  28. package/dist/migrations/generator.d.ts +8 -0
  29. package/dist/migrations/generator.d.ts.map +1 -1
  30. package/dist/migrations/generator.js +5 -1
  31. package/dist/migrations/generator.js.map +1 -1
  32. package/dist/migrations/orchestrate.d.ts +10 -0
  33. package/dist/migrations/orchestrate.d.ts.map +1 -1
  34. package/dist/migrations/orchestrate.js +8 -2
  35. package/dist/migrations/orchestrate.js.map +1 -1
  36. package/dist/migrations/tracker.d.ts.map +1 -1
  37. package/dist/migrations/tracker.js +4 -2
  38. package/dist/migrations/tracker.js.map +1 -1
  39. package/dist/registry/schema-builder.d.ts +13 -6
  40. package/dist/registry/schema-builder.d.ts.map +1 -1
  41. package/dist/registry/schema-builder.js +54 -15
  42. package/dist/registry/schema-builder.js.map +1 -1
  43. package/dist/registry.d.ts +3 -2
  44. package/dist/registry.d.ts.map +1 -1
  45. package/dist/registry.js +4 -4
  46. package/dist/registry.js.map +1 -1
  47. package/dist/schema/code-generator.js +123 -0
  48. package/dist/schema/code-generator.js.map +1 -0
  49. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  50. package/dist/schema/ddl/base-strategy.js +2 -2
  51. package/dist/schema/ddl/base-strategy.js.map +1 -1
  52. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  53. package/dist/schema/ddl/duckdb-strategy.js +2 -3
  54. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  55. package/dist/schema/ddl/index.js +26 -2
  56. package/dist/schema/ddl/index.js.map +1 -1
  57. package/dist/schema/ddl/materialize-manifest.d.ts +36 -0
  58. package/dist/schema/ddl/materialize-manifest.d.ts.map +1 -0
  59. package/dist/schema/ddl/materialize-manifest.js +361 -0
  60. package/dist/schema/ddl/materialize-manifest.js.map +1 -0
  61. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  62. package/dist/schema/ddl/postgres-strategy.js +1 -1
  63. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  64. package/dist/schema/index.js +12 -0
  65. package/dist/schema/override-system.js +205 -0
  66. package/dist/schema/override-system.js.map +1 -0
  67. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  68. package/dist/schema/schema-aggregator.js +261 -0
  69. package/dist/schema/schema-aggregator.js.map +1 -0
  70. package/dist/schema/schema-manager.js +11 -1
  71. package/dist/schema/schema-manager.js.map +1 -1
  72. package/dist/schema/utils.d.ts +1 -0
  73. package/dist/schema/utils.d.ts.map +1 -1
  74. package/dist/schema/utils.js +2 -1
  75. package/dist/schema/utils.js.map +1 -1
  76. package/dist/schema.d.ts +2 -0
  77. package/dist/schema.d.ts.map +1 -0
  78. package/dist/schema.js +13 -0
  79. package/dist/smrt-knowledge.json +5 -4
  80. package/dist/system/compatibility.d.ts +25 -0
  81. package/dist/system/compatibility.d.ts.map +1 -1
  82. package/dist/system/compatibility.js +61 -1
  83. package/dist/system/compatibility.js.map +1 -1
  84. package/dist/system/index.js +2 -2
  85. package/dist/system/schema.d.ts +17 -9
  86. package/dist/system/schema.d.ts.map +1 -1
  87. package/dist/system/schema.js +25 -11
  88. package/dist/system/schema.js.map +1 -1
  89. package/dist/testing/database.d.ts.map +1 -1
  90. package/dist/testing/database.js +5 -2
  91. package/dist/testing/database.js.map +1 -1
  92. package/package.json +10 -5
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 {\n type DatabaseInterface,\n getDatabase,\n type TransactionHandle,\n} from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport {\n ensurePostgresChangeFeedAppendFunction,\n registerChangeFeedWriter,\n} 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 { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport {\n ensureLegacySystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { ALL_SYSTEM_TABLES, SMRT_SCHEMA_VERSION } from './system/schema.js';\n\nconst SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL =\n \"SELECT pg_advisory_xact_lock(hashtext('smrt'), hashtext('system-tables'))\";\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ntype TransactionCapableDatabase = DatabaseInterface & {\n transaction?: <T>(\n this: DatabaseInterface,\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\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 typeof row.prompt_tokens === 'number'\n ? row.prompt_tokens\n : row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : Number(row.prompt_tokens);\n const completionTokens =\n typeof row.completion_tokens === 'number'\n ? row.completion_tokens\n : row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : Number(row.completion_tokens);\n const totalTokens =\n typeof row.total_tokens === 'number'\n ? row.total_tokens\n : row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : Number(row.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 this._db = await getDatabase({\n url: this.options.db,\n ...(isMemoryDb ? {} : { dbid: `smrt:${this.options.db}` }),\n });\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 // 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 ...this.options.db,\n ...(isMemoryDb ? {} : { dbid: `smrt:${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 _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_registry: Object registry persistence\n * - _smrt_signals: Signal history/audit log\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 this.withSystemTableBootstrapLock((db) =>\n this.bootstrapSystemTables(db),\n );\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 private async withSystemTableBootstrapLock<T>(\n callback: (db: DatabaseInterface) => Promise<T>,\n ): Promise<T> {\n if (!this._db) {\n throw new Error('Database is not initialized');\n }\n\n const beginTransaction = this._db.beginTransaction;\n const transaction = (this._db as TransactionCapableDatabase).transaction;\n const shouldUsePostgresLock =\n detectEngine(getDatabaseUrl(this._db), this._dbEngineHint) === 'postgres';\n\n if (!shouldUsePostgresLock) {\n return callback(this._db);\n }\n\n if (typeof beginTransaction === 'function') {\n const tx = await beginTransaction.call(this._db);\n if (!tx) {\n throw new Error('Database transaction could not be started');\n }\n\n try {\n await tx.query(SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL);\n const result = await callback(tx);\n await tx.commit();\n return result;\n } catch (error) {\n await this.rollbackSystemTableBootstrap(tx);\n throw error;\n }\n }\n\n if (typeof transaction === 'function') {\n const runInTransaction = transaction.bind(this._db) as <R>(\n callback: (tx: DatabaseInterface) => Promise<R>,\n ) => Promise<R>;\n\n return runInTransaction(async (tx) => {\n await tx.query(SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL);\n return callback(tx);\n });\n }\n\n throw new Error(\n 'Postgres system table bootstrap requires a transaction-capable database adapter',\n );\n }\n\n private async rollbackSystemTableBootstrap(\n tx: TransactionHandle,\n ): Promise<void> {\n try {\n if (typeof tx.isActive !== 'function' || tx.isActive()) {\n await tx.rollback();\n }\n } catch (rollbackError) {\n logger.warn(\n `[smrt] Failed to rollback system table bootstrap transaction: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,\n );\n }\n }\n\n private async bootstrapSystemTables(db: DatabaseInterface): Promise<void> {\n // Fast path: check if system tables already exist by querying _smrt_migrations.\n // This avoids 29 sequential DDL round-trips on high-latency connections\n // (e.g. remote Postgres over Tailscale where each round-trip is ~650ms).\n // For Postgres, this runs after acquiring the advisory lock so concurrent\n // initializers can observe a completed bootstrap and skip replaying DDL.\n const version = SMRT_SCHEMA_VERSION;\n if (await this.isSystemSchemaVersionApplied(db, version)) {\n return;\n }\n\n // Older installs can have a subset of system columns already created.\n // Upgrade those tables before replaying idempotent DDL so index creation\n // does not fail on missing legacy columns.\n await ensureLegacySystemTableCompatibility(db, this._dbEngineHint);\n\n // Create all system tables\n // Split multi-statement SQL into individual statements to avoid race conditions\n // Each ALL_SYSTEM_TABLES entry contains CREATE TABLE + CREATE INDEX statements\n const allStatements: string[] = [];\n for (const multiStatementSQL of ALL_SYSTEM_TABLES) {\n // Split on semicolon, filter out empty statements\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() for system tables — they use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively in a single round-trip. The syncSchema()\n // approach does per-column existence checks (multiple round-trips per table)\n // which is unnecessary for framework-owned system tables and extremely slow\n // on high-latency connections (e.g. remote postgres over Tailscale).\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n await ensurePostgresChangeFeedAppendFunction(db, {\n typeHint: this._dbEngineHint,\n });\n\n // Record current schema version\n // Use ON CONFLICT for DuckDB compatibility (not INSERT OR IGNORE)\n const id = crypto.randomUUID();\n const description = 'Initial SMRT system tables';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${version}, ${description})\n ON CONFLICT(version) DO NOTHING\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 * 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 * 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: Number(row.duration ?? 0),\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: Number(row.call_count ?? 0),\n promptTokens: Number(row.prompt_tokens ?? 0),\n completionTokens: Number(row.completion_tokens ?? 0),\n totalTokens: Number(row.total_tokens ?? 0),\n totalDuration: Number(row.total_duration ?? 0),\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 { 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 {\n type DatabaseInterface,\n getDatabase,\n type TransactionHandle,\n} from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport {\n ensurePostgresChangeFeedAppendFunction,\n registerChangeFeedWriter,\n} 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 { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport {\n assertPostgresSystemTimestampsCurrent,\n ensureLegacySystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { getSystemTableDDL, SMRT_SCHEMA_VERSION } from './system/schema.js';\n\nconst SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL =\n \"SELECT pg_advisory_xact_lock(hashtext('smrt'), hashtext('system-tables'))\";\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ntype TransactionCapableDatabase = DatabaseInterface & {\n transaction?: <T>(\n this: DatabaseInterface,\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\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 typeof row.prompt_tokens === 'number'\n ? row.prompt_tokens\n : row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : Number(row.prompt_tokens);\n const completionTokens =\n typeof row.completion_tokens === 'number'\n ? row.completion_tokens\n : row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : Number(row.completion_tokens);\n const totalTokens =\n typeof row.total_tokens === 'number'\n ? row.total_tokens\n : row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : Number(row.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 this._db = await getDatabase({\n url: this.options.db,\n ...(isMemoryDb ? {} : { dbid: `smrt:${this.options.db}` }),\n });\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 // 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 ...this.options.db,\n ...(isMemoryDb ? {} : { dbid: `smrt:${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 _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_registry: Object registry persistence\n * - _smrt_signals: Signal history/audit log\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 this.withSystemTableBootstrapLock((db) =>\n this.bootstrapSystemTables(db),\n );\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 private async withSystemTableBootstrapLock<T>(\n callback: (db: DatabaseInterface) => Promise<T>,\n ): Promise<T> {\n if (!this._db) {\n throw new Error('Database is not initialized');\n }\n\n const beginTransaction = this._db.beginTransaction;\n const transaction = (this._db as TransactionCapableDatabase).transaction;\n const shouldUsePostgresLock =\n detectEngine(getDatabaseUrl(this._db), this._dbEngineHint) === 'postgres';\n\n if (!shouldUsePostgresLock) {\n return callback(this._db);\n }\n\n if (typeof beginTransaction === 'function') {\n const tx = await beginTransaction.call(this._db);\n if (!tx) {\n throw new Error('Database transaction could not be started');\n }\n\n try {\n await tx.query(SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL);\n const result = await callback(tx);\n await tx.commit();\n return result;\n } catch (error) {\n await this.rollbackSystemTableBootstrap(tx);\n throw error;\n }\n }\n\n if (typeof transaction === 'function') {\n const runInTransaction = transaction.bind(this._db) as <R>(\n callback: (tx: DatabaseInterface) => Promise<R>,\n ) => Promise<R>;\n\n return runInTransaction(async (tx) => {\n await tx.query(SYSTEM_TABLE_BOOTSTRAP_LOCK_SQL);\n return callback(tx);\n });\n }\n\n throw new Error(\n 'Postgres system table bootstrap requires a transaction-capable database adapter',\n );\n }\n\n private async rollbackSystemTableBootstrap(\n tx: TransactionHandle,\n ): Promise<void> {\n try {\n if (typeof tx.isActive !== 'function' || tx.isActive()) {\n await tx.rollback();\n }\n } catch (rollbackError) {\n logger.warn(\n `[smrt] Failed to rollback system table bootstrap transaction: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,\n );\n }\n }\n\n private async bootstrapSystemTables(db: DatabaseInterface): Promise<void> {\n // Fast path: check if system tables already exist by querying _smrt_migrations.\n // This avoids 29 sequential DDL round-trips on high-latency connections\n // (e.g. remote Postgres over Tailscale where each round-trip is ~650ms).\n // For Postgres, this runs after acquiring the advisory lock so concurrent\n // initializers can observe a completed bootstrap and skip replaying DDL.\n const version = SMRT_SCHEMA_VERSION;\n if (await this.isSystemSchemaVersionApplied(db, version)) {\n return;\n }\n\n // Older installs can have a subset of system columns already created.\n // Upgrade those tables before replaying idempotent DDL so index creation\n // does not fail on missing legacy columns.\n await ensureLegacySystemTableCompatibility(db, this._dbEngineHint);\n\n // Create all system tables\n // Split multi-statement SQL into individual statements to avoid race conditions\n // Each entry contains CREATE TABLE + CREATE INDEX statements.\n const engine = detectEngine(getDatabaseUrl(db), this._dbEngineHint);\n const allStatements: string[] = [];\n for (const multiStatementSQL of getSystemTableDDL(engine)) {\n // Split on semicolon, filter out empty statements\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() for system tables — they use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively in a single round-trip. The syncSchema()\n // approach does per-column existence checks (multiple round-trips per table)\n // which is unnecessary for framework-owned system tables and extremely slow\n // on high-latency connections (e.g. remote postgres over Tailscale).\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n await ensurePostgresChangeFeedAppendFunction(db, {\n typeHint: this._dbEngineHint,\n });\n\n // Never stamp the current version while a partial legacy installation\n // still has timezone-naive framework timestamps. The explicit migration\n // requires operator confirmation of historical UTC provenance.\n await assertPostgresSystemTimestampsCurrent(db, this._dbEngineHint);\n\n // Record current schema version\n // Use ON CONFLICT for DuckDB compatibility (not INSERT OR IGNORE)\n const id = crypto.randomUUID();\n const description = 'Initial SMRT system tables';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${version}, ${description})\n ON CONFLICT(version) DO NOTHING\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 * 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 * 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: Number(row.duration ?? 0),\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: Number(row.call_count ?? 0),\n promptTokens: Number(row.prompt_tokens ?? 0),\n completionTokens: Number(row.completion_tokens ?? 0),\n totalTokens: Number(row.total_tokens ?? 0),\n totalDuration: Number(row.total_duration ?? 0),\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 +1 @@
1
- {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../src/dispatch/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAY5D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAKxE,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EAEnB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAapB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,WAAW,CAAkB;IAErC;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAiB;IAE5D,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAEhD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAa7B;;OAEG;gBACS,EAAE,EAAE,iBAAiB;IAIjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoDG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,QAAQ,CAAC;IA0EpB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAenD;;;;;;OAMG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;IAcvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCjE;;;;;OAKG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,OAAO,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC;IAyIlB;;;;;;;OAOG;IACG,KAAK,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBhE;;;;;OAKG;IACG,OAAO,CACX,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC;IAYjC;;;;;;;;;;;;;;OAcG;IACG,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAUlE;;;;;;;OAOG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAS/C;;OAEG;IACG,iBAAiB,CACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkBlC;;OAEG;IACH,OAAO,CAAC,cAAc;CAqCvB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
1
+ {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../src/dispatch/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAe5D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAKxE,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EAEnB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAapB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,WAAW,CAAkB;IAErC;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAiB;IAE5D,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAEhD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAa7B;;OAEG;gBACS,EAAE,EAAE,iBAAiB;IAIjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAyCjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoDG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,QAAQ,CAAC;IA0EpB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAenD;;;;;;OAMG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;IAcvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCjE;;;;;OAKG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,OAAO,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC;IAyIlB;;;;;;;OAOG;IACG,KAAK,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBhE;;;;;OAKG;IACG,OAAO,CACX,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC;IAYjC;;;;;;;;;;;;;;OAcG;IACG,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAUlE;;;;;;;OAOG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAS/C;;OAEG;IACG,iBAAiB,CACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkBlC;;OAEG;IACH,OAAO,CAAC,cAAc;CAqCvB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
@@ -1,6 +1,6 @@
1
1
  import { resolveDispatchTenantId, resolveDispatchTenantScope } from "./tenant-resolver.js";
2
- import { CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE } from "../system/schema.js";
3
- import { ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility } from "../system/compatibility.js";
2
+ import { CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE, getSystemTableDDLForEngine } from "../system/schema.js";
3
+ import { assertPostgresSystemTimestampsCurrent, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, getDatabaseEngine } from "../system/compatibility.js";
4
4
  import { Dispatch } from "./models/Dispatch.js";
5
5
  import { DispatchCollection } from "./collections/Dispatches.js";
6
6
  import { DispatchSubscription } from "./models/DispatchSubscription.js";
@@ -117,14 +117,19 @@ var DispatchBus = class DispatchBus {
117
117
  */
118
118
  async initialize() {
119
119
  if (this.initialized) return;
120
+ const engine = getDatabaseEngine(this.db);
120
121
  if (!await DispatchCollection.tableExists(this.db)) {
121
- const statements = CREATE_SMRT_DISPATCH_TABLE.split(";").filter((s) => s.trim());
122
+ const statements = getSystemTableDDLForEngine(CREATE_SMRT_DISPATCH_TABLE, engine).split(";").filter((s) => s.trim());
122
123
  for (const stmt of statements) await this.db.query(stmt);
123
- } else await ensureDispatchSystemTableCompatibility(this.db);
124
+ }
125
+ await assertPostgresSystemTimestampsCurrent(this.db);
126
+ await ensureDispatchSystemTableCompatibility(this.db);
124
127
  if (!await DispatchSubscriptionCollection.tableExists(this.db)) {
125
- const statements = CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE.split(";").filter((s) => s.trim());
128
+ const statements = getSystemTableDDLForEngine(CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, engine).split(";").filter((s) => s.trim());
126
129
  for (const stmt of statements) await this.db.query(stmt);
127
- } else await ensureDispatchSubscriptionsSystemTableCompatibility(this.db);
130
+ }
131
+ await assertPostgresSystemTimestampsCurrent(this.db);
132
+ await ensureDispatchSubscriptionsSystemTableCompatibility(this.db);
128
133
  this.initialized = true;
129
134
  }
130
135
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bus.js","names":[],"sources":["../../src/dispatch/bus.ts"],"sourcesContent":["/**\n * DispatchBus - Central hub for inter-agent communication\n *\n * The DispatchBus provides both in-memory handlers and persistent subscriptions\n * for asynchronous agent-to-agent messaging.\n *\n * @example\n * ```typescript\n * const bus = await createDispatchBus({ db: { type: 'sqlite', url: 'app.db' } });\n *\n * // In-memory handler (immediate)\n * bus.on('campaign.completed', async (payload, metadata) => {\n * console.log(`Campaign ${payload.campaignId} completed`);\n * });\n *\n * // Persistent subscription (processed later)\n * await bus.subscribe({ signalType: 'campaign.*', subscriber: 'fiscus' });\n *\n * // Emit a dispatch\n * await bus.emit('campaign.completed', { campaignId: '123' }, { source: 'suasor' });\n *\n * // Process pending dispatches\n * await bus.process('fiscus');\n * ```\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport {\n ensureDispatchSubscriptionsSystemTableCompatibility,\n ensureDispatchSystemTableCompatibility,\n} from '../system/compatibility.js';\nimport {\n CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE,\n CREATE_SMRT_DISPATCH_TABLE,\n} from '../system/schema.js';\nimport { DispatchCollection } from './collections/Dispatches.js';\nimport { DispatchSubscriptionCollection } from './collections/DispatchSubscriptions.js';\nimport { Dispatch } from './models/Dispatch.js';\nimport { DispatchSubscription } from './models/DispatchSubscription.js';\nimport {\n resolveDispatchTenantId,\n resolveDispatchTenantScope,\n} from './tenant-resolver.js';\nimport type {\n DispatchBusOptions,\n DispatchCleanupOptions,\n DispatchCleanupResult,\n DispatchEmitOptions,\n DispatchHandler,\n DispatchListOptions,\n DispatchMetadata,\n DispatchProcessOptions,\n DispatchRetryOptions,\n DispatchSubscribeOptions,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Registered in-memory handler\n */\ninterface RegisteredHandler {\n pattern: string;\n handler: DispatchHandler;\n subscription: DispatchSubscription;\n}\n\n/**\n * Central hub for inter-agent messaging with both in-memory and persistent delivery.\n *\n * The `DispatchBus` combines two complementary delivery models:\n *\n * - **In-memory handlers** (`on(pattern, handler)`) — called synchronously (fire-and-forget)\n * when a dispatch is emitted. Fast, but lost on process restart.\n * - **Persistent subscriptions** (`subscribe({ signalType, subscriber })`) — stored in\n * `_smrt_dispatch_subscriptions` and processed later by calling `process(subscriber, handler)`.\n * Survive restarts; suitable for background workers and scheduled agents.\n *\n * Signal types support single-segment wildcards: `'campaign.*'` matches `'campaign.completed'`\n * and `'campaign.failed'`, but not `'campaign.phase.two'`.\n *\n * Dispatch lifecycle: `pending → processing → completed` (or `failed` on handler error).\n * Use `retry()` to reset failed dispatches back to `pending`.\n *\n * Create instances via `createDispatchBus()` — do not use `new DispatchBus()` directly\n * in application code.\n *\n * @example\n * ```typescript\n * const bus = await createDispatchBus({ db: myDb });\n *\n * // In-memory: immediate, fire-and-forget\n * bus.on('invoice.paid', async (payload) => {\n * console.log('Invoice paid:', payload.invoiceId);\n * });\n *\n * // Persistent: processed by 'notifications' agent on next run\n * await bus.subscribe({ signalType: 'invoice.*', subscriber: 'notifications' });\n *\n * // Emit (notifies in-memory handlers immediately, stores persistent dispatch)\n * await bus.emit('invoice.paid', { invoiceId: 'inv-001' }, { source: 'billing' });\n *\n * // Later, in the notifications agent:\n * await bus.process('notifications', async (payload) => {\n * await sendEmail(payload.invoiceId);\n * });\n * ```\n */\nexport class DispatchBus {\n private db: DatabaseInterface;\n private handlers: Map<string, RegisteredHandler[]> = new Map();\n private initialized: boolean = false;\n\n /**\n * Reserved subscriber name used internally for in-memory `on()` handlers.\n * Callers may not register persistent subscriptions under this name, nor\n * assert it as an emit `source`, so they cannot impersonate the in-memory\n * pseudo-subscriber (S5 #1398).\n */\n private static readonly RESERVED_SUBSCRIBER = '_in_memory_';\n\n /** Maximum stored length of a caller-asserted dispatch source label. */\n private static readonly MAX_SOURCE_LENGTH = 256;\n\n /**\n * Normalize a caller-asserted `source` into untrusted metadata.\n *\n * `source` is a declared label, not an authenticated identity, so it must not\n * be trusted for authorization. This caps its length and **rejects** the\n * reserved internal sentinel (`_in_memory_`) by throwing — the contract is\n * that the sentinel is not an accepted source, so a caller cannot quietly\n * impersonate the in-memory subscriber pseudo-source (S5 #1398). Empty/missing\n * values default to `'unknown'` (unchanged behavior).\n *\n * @throws Error if `source` is the reserved in-memory sentinel.\n */\n private static sanitizeSource(source: string | undefined): string {\n const raw = (source ?? '').trim();\n if (!raw) {\n return 'unknown';\n }\n if (raw === DispatchBus.RESERVED_SUBSCRIBER) {\n throw new Error(\n `DispatchBus.emit: \"${DispatchBus.RESERVED_SUBSCRIBER}\" is a reserved source and cannot be asserted`,\n );\n }\n return raw.slice(0, DispatchBus.MAX_SOURCE_LENGTH);\n }\n\n /**\n * Create a new DispatchBus (use createDispatchBus factory instead)\n */\n constructor(db: DatabaseInterface) {\n this.db = db;\n }\n\n /**\n * Initialize the dispatch tables\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Create dispatch tables if they don't exist\n const dispatchExists = await DispatchCollection.tableExists(this.db);\n if (!dispatchExists) {\n // Split the DDL into separate statements and execute each\n const statements = CREATE_SMRT_DISPATCH_TABLE.split(';').filter((s) =>\n s.trim(),\n );\n for (const stmt of statements) {\n await this.db.query(stmt);\n }\n } else {\n await ensureDispatchSystemTableCompatibility(this.db);\n }\n\n const subsExists = await DispatchSubscriptionCollection.tableExists(\n this.db,\n );\n if (!subsExists) {\n const statements = CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE.split(\n ';',\n ).filter((s) => s.trim());\n for (const stmt of statements) {\n await this.db.query(stmt);\n }\n } else {\n await ensureDispatchSubscriptionsSystemTableCompatibility(this.db);\n }\n\n this.initialized = true;\n }\n\n /**\n * Emits a dispatch message.\n *\n * Two things happen atomically (from the caller's perspective):\n * 1. One or more `Dispatch` rows are inserted with `status: 'pending'` into `_smrt_dispatch`.\n * 2. All matching in-memory handlers (registered via `on()`) are invoked\n * fire-and-forget (errors are caught and logged, not thrown back to the caller).\n *\n * Persistent subscriptions registered via `subscribe()` will see this dispatch\n * the next time `process(subscriber, handler)` is called for their subscriber name.\n *\n * **Delivery modes** affect how dispatches are created:\n * - `compete` subscribers share a single dispatch (`target_subscriber = NULL`).\n * First subscriber to `process()` claims it (at-most-once delivery).\n * - `fanout` subscribers each get their own dispatch copy (`target_subscriber` set\n * to the subscriber name), so each processes independently.\n *\n * If no subscriptions exist, one dispatch is created for future processing.\n *\n * **Security (S5 #1398):**\n * - `tenant_id` is derived **server-side** from the active tenant context and\n * is never read from caller options, so it cannot be spoofed. Subscribers in\n * a different tenant cannot see or claim this dispatch (see {@link process}).\n * When there is no active tenant context (system/global), `tenant_id` is\n * `NULL` and the dispatch is visible to all scopes — preserving pre-tenancy\n * behavior.\n * - `options.source` is **caller-asserted, untrusted metadata** — it is a\n * declared label only and must not be relied on as an authenticated emitter\n * identity. It is length-capped and the reserved internal sentinel\n * (`_in_memory_`) is rejected so a caller cannot impersonate the in-memory\n * subscriber pseudo-source.\n *\n * @param type - Signal type string, e.g. `'campaign.completed'` or `'invoice.paid'`\n * @param payload - Any JSON-serializable data to attach to the dispatch\n * @param options.source - Declared (untrusted) name of the emitting agent/component (default `'unknown'`)\n * @param options.sourceId - Optional ID of the specific emitting entity\n * @param options.metadata - Optional additional JSON metadata for the dispatch record\n * @returns A persisted `Dispatch` instance (the compete dispatch, or the first fanout copy if fanout-only)\n *\n * @example\n * ```typescript\n * const dispatch = await bus.emit(\n * 'campaign.completed',\n * { campaignId: 'cmp-001', impressions: 10_000 },\n * { source: 'suasor', sourceId: agentId },\n * );\n * console.log(dispatch.id); // UUID of the created dispatch record\n * ```\n *\n * @see {@link on} for in-memory handlers\n * @see {@link subscribe} for persistent subscriptions\n * @see {@link process} to consume pending dispatches\n */\n async emit(\n type: string,\n payload: unknown,\n options: DispatchEmitOptions = {},\n ): Promise<Dispatch> {\n await this.initialize();\n\n // Derive the tenant scope server-side from the active context. This is the\n // trust anchor for cross-tenant isolation (S5 #1398) — it is never taken\n // from caller options, so it cannot be spoofed. `undefined`/`null` means\n // \"no tenant context\" → tenant_id stays NULL (global dispatch).\n const tenantId = resolveDispatchTenantId() ?? null;\n\n // Treat the caller-asserted source as untrusted metadata.\n const source = DispatchBus.sanitizeSource(options.source);\n\n // Query subscriptions to determine if fan-out copies are needed.\n // This runs on every emit() — uses SQL-level exact matching with\n // in-memory fallback only for wildcard subscriptions to minimize I/O.\n // Scoped to the active tenant so emit only fans out to this tenant's\n // (and, for global emits, global) subscriptions (S5 #1398).\n const matchingSubs = await DispatchSubscriptionCollection.findBySignalType(\n this.db,\n type,\n resolveDispatchTenantScope(),\n );\n\n // Separate into compete and fanout subscribers\n const fanoutSubs = matchingSubs.filter((s) => s.delivery === 'fanout');\n const competeSubs = matchingSubs.filter((s) => s.delivery !== 'fanout');\n\n // Create the base dispatch record\n const dispatch = new Dispatch({\n type,\n source,\n source_id: options.sourceId || null,\n payload: JSON.stringify(payload || {}),\n metadata: JSON.stringify(options.metadata || {}),\n status: 'pending',\n correlation_id: options.correlationId || null,\n tenant_id: tenantId,\n });\n\n // Track first persisted dispatch to return to caller\n let returnDispatch = dispatch;\n\n // For fanout subscribers: create a per-subscriber dispatch copy\n for (const sub of fanoutSubs) {\n const fanoutDispatch = new Dispatch({\n type,\n source,\n source_id: options.sourceId || null,\n payload: JSON.stringify(payload || {}),\n metadata: JSON.stringify(options.metadata || {}),\n status: 'pending',\n target_subscriber: sub.subscriber,\n correlation_id: options.correlationId || null,\n tenant_id: tenantId,\n });\n await DispatchCollection.insert(this.db, fanoutDispatch);\n // If no compete dispatch will be persisted, return the first fanout copy\n if (competeSubs.length === 0 && matchingSubs.length > 0) {\n returnDispatch = fanoutDispatch;\n }\n }\n\n // For compete subscribers (or if no subscriptions found): insert the original\n // dispatch with target_subscriber = NULL so any compete subscriber can claim it\n if (competeSubs.length > 0 || matchingSubs.length === 0) {\n await DispatchCollection.insert(this.db, dispatch);\n }\n\n // Notify in-memory handlers immediately (fire-and-forget)\n this.notifyHandlers(type, payload, dispatch.getMetadata());\n\n return returnDispatch;\n }\n\n /**\n * Registers an in-memory handler for a signal type pattern.\n *\n * In-memory handlers are called immediately (fire-and-forget) during `emit()`.\n * They are stored only in memory and lost on process restart — use `subscribe()`\n * for durable, restart-safe subscriptions.\n *\n * Pattern matching supports a single-segment wildcard `*`:\n * - `'campaign.*'` matches `'campaign.completed'` and `'campaign.failed'`\n * - `'campaign.*'` does **not** match `'campaign.phase.two'`\n *\n * Multiple handlers may be registered for the same pattern — all will be called.\n *\n * @param pattern - Signal type pattern, optionally with a trailing `.*` wildcard\n * @param handler - Async or sync function to call with `(payload, metadata)`\n *\n * @example\n * ```typescript\n * bus.on('invoice.*', async (payload, metadata) => {\n * console.log(`Invoice event from ${metadata.source}:`, payload);\n * });\n * ```\n *\n * @see {@link off} to remove a handler\n * @see {@link subscribe} for persistent (restart-safe) subscriptions\n */\n on(pattern: string, handler: DispatchHandler): void {\n const subscription = new DispatchSubscription({\n signal_type: pattern,\n subscriber: '_in_memory_',\n handler: 'callback',\n });\n\n const registered: RegisteredHandler = { pattern, handler, subscription };\n\n if (!this.handlers.has(pattern)) {\n this.handlers.set(pattern, []);\n }\n this.handlers.get(pattern)?.push(registered);\n }\n\n /**\n * Remove an in-memory handler\n *\n * @param pattern - Signal type pattern\n * @param handler - Handler function to remove\n * @returns True if handler was found and removed\n */\n off(pattern: string, handler: DispatchHandler): boolean {\n const handlers = this.handlers.get(pattern);\n if (!handlers) return false;\n\n const index = handlers.findIndex((h) => h.handler === handler);\n if (index === -1) return false;\n\n handlers.splice(index, 1);\n if (handlers.length === 0) {\n this.handlers.delete(pattern);\n }\n return true;\n }\n\n /**\n * Creates or updates a persistent subscription in the database.\n *\n * Persistent subscriptions survive process restarts. When `process(subscriber, handler)`\n * is called later, all pending dispatches matching this subscriber's signal patterns\n * will be delivered.\n *\n * Calling `subscribe()` with the same `signalType`/`subscriber` pair is idempotent\n * (upsert) — it is safe to call on every agent startup.\n *\n * Set `delivery: 'fanout'` to give each subscriber their own dispatch copy.\n * Default is `'compete'` (at-most-once, first subscriber to process claims it).\n *\n * @param options.signalType - Signal type pattern to subscribe to (wildcards supported)\n * @param options.subscriber - Name that identifies this subscriber (e.g. agent class name)\n * @param options.handler - Optional method name on the subscriber to call (default `'handleDispatch'`)\n * @param options.delivery - `'compete'` (default) for at-most-once, `'fanout'` for per-subscriber copies\n * @param options.enabled - If `false`, subscription is created but disabled (default `true`)\n *\n * @example\n * ```typescript\n * // Subscribe on agent startup (idempotent)\n * await bus.subscribe({ signalType: 'campaign.*', subscriber: 'FiscusAgent' });\n *\n * // Fan-out: each subscriber gets their own dispatch copy\n * await bus.subscribe({\n * signalType: 'campaign.*',\n * subscriber: 'AuditorAgent',\n * delivery: 'fanout',\n * });\n *\n * // Later, process matching pending dispatches\n * await bus.process('FiscusAgent', async (payload, metadata) => { ... });\n * ```\n *\n * @see {@link process} to consume pending dispatches for this subscriber\n * @see {@link unsubscribe} to remove the subscription\n * @see {@link on} for non-persistent in-memory handling\n */\n async subscribe(options: DispatchSubscribeOptions): Promise<void> {\n await this.initialize();\n\n // Gate the subscriber namespace (S5 #1398): require a concrete subscriber\n // name and reject the reserved internal sentinel so callers cannot\n // register a persistent subscription that masquerades as the in-memory\n // pseudo-subscriber.\n const subscriber = (options.subscriber ?? '').trim();\n if (!subscriber) {\n throw new Error('DispatchBus.subscribe requires a non-empty subscriber');\n }\n if (subscriber === DispatchBus.RESERVED_SUBSCRIBER) {\n throw new Error(\n `DispatchBus.subscribe: \"${DispatchBus.RESERVED_SUBSCRIBER}\" is a reserved subscriber name`,\n );\n }\n const signalType = (options.signalType ?? '').trim();\n if (!signalType) {\n throw new Error('DispatchBus.subscribe requires a non-empty signalType');\n }\n\n const subscription = new DispatchSubscription({\n signal_type: signalType,\n subscriber,\n handler: options.handler || 'handleDispatch',\n delivery: options.delivery || 'compete',\n enabled: options.enabled !== false ? 1 : 0,\n // Server-derived tenant scope (never from caller options). NULL when there\n // is no active tenant context (global subscription).\n tenant_id: resolveDispatchTenantId() ?? null,\n });\n\n await DispatchSubscriptionCollection.upsert(this.db, subscription);\n }\n\n /**\n * Remove a persistent subscription\n *\n * @param signalType - Signal type pattern\n * @param subscriber - Subscriber name\n */\n async unsubscribe(signalType: string, subscriber: string): Promise<void> {\n await this.initialize();\n // Scoped to the active tenant so one tenant cannot remove another's\n // subscription (S5 #1398).\n await DispatchSubscriptionCollection.deleteByKey(\n this.db,\n signalType,\n subscriber,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * Processes pending dispatches for a named subscriber.\n *\n * For each matching `pending` dispatch:\n * 1. Sets status to `processing`\n * 2. Calls `handler(payload, metadata)`\n * 3. On success: sets status to `completed`\n * 4. On error: sets status to `failed` (stores the error message)\n *\n * Uses a wildcard-aware query strategy: subscriptions with `*` patterns fetch\n * all pending dispatches and filter in memory; exact-match subscriptions use\n * a direct SQL `IN` query for efficiency.\n *\n * @param subscriber - The subscriber name (must match a `subscribe()` call)\n * @param handler - Function to call for each pending dispatch\n * @param options.limit - Maximum dispatches to process in one call (default 100)\n * @param options.signalTypes - Optional filter to process only specific signal types\n * @returns Number of dispatches successfully processed (excludes failed)\n *\n * @example\n * ```typescript\n * const count = await bus.process('FiscusAgent', async (payload, metadata) => {\n * if (metadata.signalType === 'campaign.completed') {\n * await generateInvoice(payload.campaignId);\n * }\n * });\n * console.log(`Processed ${count} dispatches`);\n * ```\n *\n * @see {@link subscribe} to register the persistent subscription first\n * @see {@link retry} to reset failed dispatches back to pending\n */\n async process(\n subscriber: string,\n handler: DispatchHandler,\n options: DispatchProcessOptions = {},\n ): Promise<number> {\n await this.initialize();\n\n // Resolve the active tenant scope server-side (S5 #1398). This distinguishes\n // three states (see resolveDispatchTenantScope):\n // - tenancy off → no filter; system/global + non-tenant deployments behave\n // exactly as before.\n // - active tenant T → only T's dispatches plus global (NULL) dispatches are\n // claimable, so a subscriber in tenant A cannot see/claim tenant B's.\n // - tenancy on but no active tenant → fail-closed to global (NULL) rows\n // only; a missing context never leaks other tenants' dispatches.\n const tenantScope = resolveDispatchTenantScope();\n\n // Get subscriber's subscriptions, scoped to the active tenant so a tenant-A\n // processor only matches tenant-A (and, when global, global) subscriptions.\n const subscriptions = await DispatchSubscriptionCollection.findBySubscriber(\n this.db,\n subscriber,\n true,\n tenantScope,\n );\n\n if (subscriptions.length === 0) {\n return 0;\n }\n\n // Get signal types (including wildcards)\n const signalTypes = subscriptions.map((s) => s.signalType);\n\n // For non-wildcard subscriptions, we can query directly\n // For wildcards, we need to get all pending and filter\n const hasWildcards = signalTypes.some((t) => t.includes('*'));\n\n // Helper: check if a dispatch should be visible to this subscriber\n const isVisibleToSubscriber = (dispatch: Dispatch): boolean => {\n // Tenant isolation (defense in depth — the SQL queries below also filter).\n if (tenantScope.enforced) {\n if (tenantScope.tenantId === null) {\n // Tenancy on, no active tenant → only global (NULL) rows are visible.\n if (dispatch.tenantId !== null) {\n return false;\n }\n } else if (\n dispatch.tenantId !== null &&\n dispatch.tenantId !== tenantScope.tenantId\n ) {\n // Active tenant → that tenant's rows plus global (NULL) rows only.\n return false;\n }\n }\n if (dispatch.targetSubscriber === subscriber) {\n // Targeted at this subscriber (fanout copy) — always visible\n return true;\n }\n if (dispatch.targetSubscriber !== null) {\n // Targeted at a different subscriber — not visible\n return false;\n }\n // Null target (compete dispatch): only visible if this subscriber\n // has a compete subscription matching this dispatch type\n const matchingSub = subscriptions.find((sub) =>\n sub.matches(dispatch.type),\n );\n return matchingSub?.delivery === 'compete';\n };\n\n let pendingDispatches: Dispatch[];\n if (hasWildcards) {\n // Get all pending dispatches and filter by subscription patterns + target\n const allPending = await DispatchCollection.list(this.db, {\n status: 'pending',\n limit: options.limit || 100,\n tenantScope,\n });\n\n pendingDispatches = allPending.filter(\n (dispatch) =>\n subscriptions.some((sub) => sub.matches(dispatch.type)) &&\n isVisibleToSubscriber(dispatch),\n );\n } else {\n // Direct query for exact signal types (with target_subscriber filter)\n const raw = await DispatchCollection.findPending(\n this.db,\n signalTypes,\n options.limit || 100,\n subscriber,\n tenantScope,\n );\n // Post-filter for delivery mode correctness\n pendingDispatches = raw.filter(isVisibleToSubscriber);\n }\n\n // Filter by specific signal types if provided\n if (options.signalTypes && options.signalTypes.length > 0) {\n pendingDispatches = pendingDispatches.filter((d) =>\n options.signalTypes?.includes(d.type),\n );\n }\n\n let processed = 0;\n\n for (const dispatch of pendingDispatches) {\n // Atomically claim the dispatch (S5 #1398). The conditional UPDATE\n // (WHERE id = ? AND status = 'pending' [AND tenant predicate]) closes the\n // TOCTOU window: if a competing worker — or a system-scoped processor —\n // already moved this row out of 'pending', our claim affects no rows and\n // we skip it, so a compete/global dispatch is processed at most once.\n const claimed = await DispatchCollection.claim(\n this.db,\n dispatch,\n tenantScope,\n );\n if (!claimed) {\n continue;\n }\n\n try {\n // Invoke handler\n await handler(dispatch.payload, dispatch.getMetadata());\n\n // Mark as completed\n dispatch.markCompleted(subscriber);\n await DispatchCollection.update(this.db, dispatch);\n processed++;\n } catch (error) {\n // Mark as failed\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n dispatch.markFailed(errorMessage);\n await DispatchCollection.update(this.db, dispatch);\n }\n }\n\n return processed;\n }\n\n /**\n * Retry failed dispatches\n *\n * Resets failed dispatches to pending status so they can be processed again.\n *\n * @param options - Retry options\n * @returns Number of dispatches reset\n */\n async retry(options: DispatchRetryOptions = {}): Promise<number> {\n await this.initialize();\n\n // Tenant isolation (S5 #1398): only the active tenant's failed dispatches\n // (plus global ones) are eligible — a tenant's retry() must not reset\n // another tenant's rows. Scope is derived server-side; callers cannot widen\n // it.\n const retryable = await DispatchCollection.findRetryable(\n this.db,\n options,\n resolveDispatchTenantScope(),\n );\n\n for (const dispatch of retryable) {\n dispatch.resetForRetry();\n await DispatchCollection.update(this.db, dispatch);\n }\n\n return retryable.length;\n }\n\n /**\n * Clean up old dispatches\n *\n * @param options - Cleanup options\n * @returns Number of dispatches deleted\n */\n async cleanup(\n options: DispatchCleanupOptions = {},\n ): Promise<DispatchCleanupResult> {\n await this.initialize();\n // Tenant isolation (S5 #1398): only the active tenant's dispatches (plus\n // global ones) are deleted — a tenant's cleanup() must not delete another\n // tenant's rows. Scope is derived server-side; callers cannot widen it.\n return DispatchCollection.cleanup(\n this.db,\n options,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * Lists dispatches, scoped to the active tenant context (S5 #1398).\n *\n * The tenant scope is derived **server-side** from the active context and\n * **overrides any caller-supplied scope** — callers cannot select another\n * tenant or widen visibility to all tenants. Scoping rules:\n *\n * - tenancy off → no tenant filter (pre-tenancy behavior).\n * - active tenant T → that tenant's dispatches plus global (NULL) dispatches.\n * - tenancy on but no active tenant → global (NULL) dispatches only\n * (fail-closed; never all tenants).\n *\n * @param options - Non-tenant list filters (status/type/source/etc.). Any\n * `tenantScope` field is ignored and replaced with the server-derived one.\n */\n async list(options: DispatchListOptions = {}): Promise<Dispatch[]> {\n await this.initialize();\n // Strip any caller-supplied scope and inject the server-derived one.\n const { tenantScope: _ignoredCallerScope, ...safeOptions } = options;\n return DispatchCollection.list(this.db, {\n ...safeOptions,\n tenantScope: resolveDispatchTenantScope(),\n });\n }\n\n /**\n * Gets a dispatch by ID, enforcing the active tenant scope (S5 #1398).\n *\n * Applies the same server-derived tenant predicate as {@link list}: a\n * subscriber in tenant A cannot fetch tenant B's dispatch by id, and when\n * tenancy is on with no active tenant only global (NULL) dispatches are\n * returned. Returns `null` when the dispatch exists but is out of scope.\n */\n async get(id: string): Promise<Dispatch | null> {\n await this.initialize();\n return DispatchCollection.getScoped(\n this.db,\n id,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * List all subscriptions\n */\n async listSubscriptions(\n subscriber?: string,\n ): Promise<DispatchSubscription[]> {\n await this.initialize();\n\n // Scoped to the active tenant so listing never leaks another tenant's\n // subscriptions (S5 #1398).\n const tenantScope = resolveDispatchTenantScope();\n\n if (subscriber) {\n return DispatchSubscriptionCollection.findBySubscriber(\n this.db,\n subscriber,\n false,\n tenantScope,\n );\n }\n return DispatchSubscriptionCollection.list(this.db, false, tenantScope);\n }\n\n /**\n * Notify in-memory handlers (fire-and-forget)\n */\n private notifyHandlers(\n type: string,\n payload: unknown,\n metadata: DispatchMetadata,\n ): void {\n // Collect all matching handlers\n const matchingHandlers: RegisteredHandler[] = [];\n\n for (const [, handlers] of this.handlers) {\n for (const registered of handlers) {\n if (registered.subscription.matches(type)) {\n matchingHandlers.push(registered);\n }\n }\n }\n\n // Fire-and-forget - don't await\n for (const registered of matchingHandlers) {\n try {\n const result = registered.handler(payload, metadata);\n // Handle both sync and async handlers\n if (result && typeof result.catch === 'function') {\n void result.catch((error: unknown) => {\n logger.error(\n `DispatchBus: Handler for \"${registered.pattern}\" failed`,\n { error },\n );\n });\n }\n } catch (error) {\n logger.error(\n `DispatchBus: Handler for \"${registered.pattern}\" failed`,\n { error },\n );\n }\n }\n }\n}\n\n/**\n * Create a DispatchBus instance\n *\n * @param options - Bus configuration\n * @returns Initialized DispatchBus\n */\nexport async function createDispatchBus(\n options: DispatchBusOptions = {},\n): Promise<DispatchBus> {\n // Resolve database configuration\n const dbConfig = options.db || options.persistence;\n\n if (!dbConfig) {\n throw new Error('DispatchBus requires a database configuration');\n }\n\n // Get or create database interface\n let db: DatabaseInterface;\n if (typeof dbConfig === 'string') {\n db = await getDatabase(dbConfig);\n } else if ('query' in dbConfig) {\n // Already a DatabaseInterface (has query method)\n db = dbConfig as DatabaseInterface;\n } else if ('type' in dbConfig && 'url' in dbConfig) {\n // Database config object - use getDatabase with type and url\n db = await getDatabase({\n type: dbConfig.type as 'sqlite' | 'postgres' | 'duckdb',\n url: dbConfig.url,\n });\n } else {\n throw new Error('Invalid database configuration for DispatchBus');\n }\n\n const bus = new DispatchBus(db);\n await bus.initialize();\n\n return bus;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bus.js","names":[],"sources":["../../src/dispatch/bus.ts"],"sourcesContent":["/**\n * DispatchBus - Central hub for inter-agent communication\n *\n * The DispatchBus provides both in-memory handlers and persistent subscriptions\n * for asynchronous agent-to-agent messaging.\n *\n * @example\n * ```typescript\n * const bus = await createDispatchBus({ db: { type: 'sqlite', url: 'app.db' } });\n *\n * // In-memory handler (immediate)\n * bus.on('campaign.completed', async (payload, metadata) => {\n * console.log(`Campaign ${payload.campaignId} completed`);\n * });\n *\n * // Persistent subscription (processed later)\n * await bus.subscribe({ signalType: 'campaign.*', subscriber: 'fiscus' });\n *\n * // Emit a dispatch\n * await bus.emit('campaign.completed', { campaignId: '123' }, { source: 'suasor' });\n *\n * // Process pending dispatches\n * await bus.process('fiscus');\n * ```\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport {\n assertPostgresSystemTimestampsCurrent,\n ensureDispatchSubscriptionsSystemTableCompatibility,\n ensureDispatchSystemTableCompatibility,\n getDatabaseEngine,\n} from '../system/compatibility.js';\nimport {\n CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE,\n CREATE_SMRT_DISPATCH_TABLE,\n getSystemTableDDLForEngine,\n} from '../system/schema.js';\nimport { DispatchCollection } from './collections/Dispatches.js';\nimport { DispatchSubscriptionCollection } from './collections/DispatchSubscriptions.js';\nimport { Dispatch } from './models/Dispatch.js';\nimport { DispatchSubscription } from './models/DispatchSubscription.js';\nimport {\n resolveDispatchTenantId,\n resolveDispatchTenantScope,\n} from './tenant-resolver.js';\nimport type {\n DispatchBusOptions,\n DispatchCleanupOptions,\n DispatchCleanupResult,\n DispatchEmitOptions,\n DispatchHandler,\n DispatchListOptions,\n DispatchMetadata,\n DispatchProcessOptions,\n DispatchRetryOptions,\n DispatchSubscribeOptions,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Registered in-memory handler\n */\ninterface RegisteredHandler {\n pattern: string;\n handler: DispatchHandler;\n subscription: DispatchSubscription;\n}\n\n/**\n * Central hub for inter-agent messaging with both in-memory and persistent delivery.\n *\n * The `DispatchBus` combines two complementary delivery models:\n *\n * - **In-memory handlers** (`on(pattern, handler)`) — called synchronously (fire-and-forget)\n * when a dispatch is emitted. Fast, but lost on process restart.\n * - **Persistent subscriptions** (`subscribe({ signalType, subscriber })`) — stored in\n * `_smrt_dispatch_subscriptions` and processed later by calling `process(subscriber, handler)`.\n * Survive restarts; suitable for background workers and scheduled agents.\n *\n * Signal types support single-segment wildcards: `'campaign.*'` matches `'campaign.completed'`\n * and `'campaign.failed'`, but not `'campaign.phase.two'`.\n *\n * Dispatch lifecycle: `pending → processing → completed` (or `failed` on handler error).\n * Use `retry()` to reset failed dispatches back to `pending`.\n *\n * Create instances via `createDispatchBus()` — do not use `new DispatchBus()` directly\n * in application code.\n *\n * @example\n * ```typescript\n * const bus = await createDispatchBus({ db: myDb });\n *\n * // In-memory: immediate, fire-and-forget\n * bus.on('invoice.paid', async (payload) => {\n * console.log('Invoice paid:', payload.invoiceId);\n * });\n *\n * // Persistent: processed by 'notifications' agent on next run\n * await bus.subscribe({ signalType: 'invoice.*', subscriber: 'notifications' });\n *\n * // Emit (notifies in-memory handlers immediately, stores persistent dispatch)\n * await bus.emit('invoice.paid', { invoiceId: 'inv-001' }, { source: 'billing' });\n *\n * // Later, in the notifications agent:\n * await bus.process('notifications', async (payload) => {\n * await sendEmail(payload.invoiceId);\n * });\n * ```\n */\nexport class DispatchBus {\n private db: DatabaseInterface;\n private handlers: Map<string, RegisteredHandler[]> = new Map();\n private initialized: boolean = false;\n\n /**\n * Reserved subscriber name used internally for in-memory `on()` handlers.\n * Callers may not register persistent subscriptions under this name, nor\n * assert it as an emit `source`, so they cannot impersonate the in-memory\n * pseudo-subscriber (S5 #1398).\n */\n private static readonly RESERVED_SUBSCRIBER = '_in_memory_';\n\n /** Maximum stored length of a caller-asserted dispatch source label. */\n private static readonly MAX_SOURCE_LENGTH = 256;\n\n /**\n * Normalize a caller-asserted `source` into untrusted metadata.\n *\n * `source` is a declared label, not an authenticated identity, so it must not\n * be trusted for authorization. This caps its length and **rejects** the\n * reserved internal sentinel (`_in_memory_`) by throwing — the contract is\n * that the sentinel is not an accepted source, so a caller cannot quietly\n * impersonate the in-memory subscriber pseudo-source (S5 #1398). Empty/missing\n * values default to `'unknown'` (unchanged behavior).\n *\n * @throws Error if `source` is the reserved in-memory sentinel.\n */\n private static sanitizeSource(source: string | undefined): string {\n const raw = (source ?? '').trim();\n if (!raw) {\n return 'unknown';\n }\n if (raw === DispatchBus.RESERVED_SUBSCRIBER) {\n throw new Error(\n `DispatchBus.emit: \"${DispatchBus.RESERVED_SUBSCRIBER}\" is a reserved source and cannot be asserted`,\n );\n }\n return raw.slice(0, DispatchBus.MAX_SOURCE_LENGTH);\n }\n\n /**\n * Create a new DispatchBus (use createDispatchBus factory instead)\n */\n constructor(db: DatabaseInterface) {\n this.db = db;\n }\n\n /**\n * Initialize the dispatch tables\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n const engine = getDatabaseEngine(this.db);\n\n // Create dispatch tables if they don't exist\n const dispatchExists = await DispatchCollection.tableExists(this.db);\n if (!dispatchExists) {\n // Split the DDL into separate statements and execute each\n const statements = getSystemTableDDLForEngine(\n CREATE_SMRT_DISPATCH_TABLE,\n engine,\n )\n .split(';')\n .filter((s) => s.trim());\n for (const stmt of statements) {\n await this.db.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(this.db);\n await ensureDispatchSystemTableCompatibility(this.db);\n\n const subsExists = await DispatchSubscriptionCollection.tableExists(\n this.db,\n );\n if (!subsExists) {\n const statements = getSystemTableDDLForEngine(\n CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE,\n engine,\n )\n .split(';')\n .filter((s) => s.trim());\n for (const stmt of statements) {\n await this.db.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(this.db);\n await ensureDispatchSubscriptionsSystemTableCompatibility(this.db);\n\n this.initialized = true;\n }\n\n /**\n * Emits a dispatch message.\n *\n * Two things happen atomically (from the caller's perspective):\n * 1. One or more `Dispatch` rows are inserted with `status: 'pending'` into `_smrt_dispatch`.\n * 2. All matching in-memory handlers (registered via `on()`) are invoked\n * fire-and-forget (errors are caught and logged, not thrown back to the caller).\n *\n * Persistent subscriptions registered via `subscribe()` will see this dispatch\n * the next time `process(subscriber, handler)` is called for their subscriber name.\n *\n * **Delivery modes** affect how dispatches are created:\n * - `compete` subscribers share a single dispatch (`target_subscriber = NULL`).\n * First subscriber to `process()` claims it (at-most-once delivery).\n * - `fanout` subscribers each get their own dispatch copy (`target_subscriber` set\n * to the subscriber name), so each processes independently.\n *\n * If no subscriptions exist, one dispatch is created for future processing.\n *\n * **Security (S5 #1398):**\n * - `tenant_id` is derived **server-side** from the active tenant context and\n * is never read from caller options, so it cannot be spoofed. Subscribers in\n * a different tenant cannot see or claim this dispatch (see {@link process}).\n * When there is no active tenant context (system/global), `tenant_id` is\n * `NULL` and the dispatch is visible to all scopes — preserving pre-tenancy\n * behavior.\n * - `options.source` is **caller-asserted, untrusted metadata** — it is a\n * declared label only and must not be relied on as an authenticated emitter\n * identity. It is length-capped and the reserved internal sentinel\n * (`_in_memory_`) is rejected so a caller cannot impersonate the in-memory\n * subscriber pseudo-source.\n *\n * @param type - Signal type string, e.g. `'campaign.completed'` or `'invoice.paid'`\n * @param payload - Any JSON-serializable data to attach to the dispatch\n * @param options.source - Declared (untrusted) name of the emitting agent/component (default `'unknown'`)\n * @param options.sourceId - Optional ID of the specific emitting entity\n * @param options.metadata - Optional additional JSON metadata for the dispatch record\n * @returns A persisted `Dispatch` instance (the compete dispatch, or the first fanout copy if fanout-only)\n *\n * @example\n * ```typescript\n * const dispatch = await bus.emit(\n * 'campaign.completed',\n * { campaignId: 'cmp-001', impressions: 10_000 },\n * { source: 'suasor', sourceId: agentId },\n * );\n * console.log(dispatch.id); // UUID of the created dispatch record\n * ```\n *\n * @see {@link on} for in-memory handlers\n * @see {@link subscribe} for persistent subscriptions\n * @see {@link process} to consume pending dispatches\n */\n async emit(\n type: string,\n payload: unknown,\n options: DispatchEmitOptions = {},\n ): Promise<Dispatch> {\n await this.initialize();\n\n // Derive the tenant scope server-side from the active context. This is the\n // trust anchor for cross-tenant isolation (S5 #1398) — it is never taken\n // from caller options, so it cannot be spoofed. `undefined`/`null` means\n // \"no tenant context\" → tenant_id stays NULL (global dispatch).\n const tenantId = resolveDispatchTenantId() ?? null;\n\n // Treat the caller-asserted source as untrusted metadata.\n const source = DispatchBus.sanitizeSource(options.source);\n\n // Query subscriptions to determine if fan-out copies are needed.\n // This runs on every emit() — uses SQL-level exact matching with\n // in-memory fallback only for wildcard subscriptions to minimize I/O.\n // Scoped to the active tenant so emit only fans out to this tenant's\n // (and, for global emits, global) subscriptions (S5 #1398).\n const matchingSubs = await DispatchSubscriptionCollection.findBySignalType(\n this.db,\n type,\n resolveDispatchTenantScope(),\n );\n\n // Separate into compete and fanout subscribers\n const fanoutSubs = matchingSubs.filter((s) => s.delivery === 'fanout');\n const competeSubs = matchingSubs.filter((s) => s.delivery !== 'fanout');\n\n // Create the base dispatch record\n const dispatch = new Dispatch({\n type,\n source,\n source_id: options.sourceId || null,\n payload: JSON.stringify(payload || {}),\n metadata: JSON.stringify(options.metadata || {}),\n status: 'pending',\n correlation_id: options.correlationId || null,\n tenant_id: tenantId,\n });\n\n // Track first persisted dispatch to return to caller\n let returnDispatch = dispatch;\n\n // For fanout subscribers: create a per-subscriber dispatch copy\n for (const sub of fanoutSubs) {\n const fanoutDispatch = new Dispatch({\n type,\n source,\n source_id: options.sourceId || null,\n payload: JSON.stringify(payload || {}),\n metadata: JSON.stringify(options.metadata || {}),\n status: 'pending',\n target_subscriber: sub.subscriber,\n correlation_id: options.correlationId || null,\n tenant_id: tenantId,\n });\n await DispatchCollection.insert(this.db, fanoutDispatch);\n // If no compete dispatch will be persisted, return the first fanout copy\n if (competeSubs.length === 0 && matchingSubs.length > 0) {\n returnDispatch = fanoutDispatch;\n }\n }\n\n // For compete subscribers (or if no subscriptions found): insert the original\n // dispatch with target_subscriber = NULL so any compete subscriber can claim it\n if (competeSubs.length > 0 || matchingSubs.length === 0) {\n await DispatchCollection.insert(this.db, dispatch);\n }\n\n // Notify in-memory handlers immediately (fire-and-forget)\n this.notifyHandlers(type, payload, dispatch.getMetadata());\n\n return returnDispatch;\n }\n\n /**\n * Registers an in-memory handler for a signal type pattern.\n *\n * In-memory handlers are called immediately (fire-and-forget) during `emit()`.\n * They are stored only in memory and lost on process restart — use `subscribe()`\n * for durable, restart-safe subscriptions.\n *\n * Pattern matching supports a single-segment wildcard `*`:\n * - `'campaign.*'` matches `'campaign.completed'` and `'campaign.failed'`\n * - `'campaign.*'` does **not** match `'campaign.phase.two'`\n *\n * Multiple handlers may be registered for the same pattern — all will be called.\n *\n * @param pattern - Signal type pattern, optionally with a trailing `.*` wildcard\n * @param handler - Async or sync function to call with `(payload, metadata)`\n *\n * @example\n * ```typescript\n * bus.on('invoice.*', async (payload, metadata) => {\n * console.log(`Invoice event from ${metadata.source}:`, payload);\n * });\n * ```\n *\n * @see {@link off} to remove a handler\n * @see {@link subscribe} for persistent (restart-safe) subscriptions\n */\n on(pattern: string, handler: DispatchHandler): void {\n const subscription = new DispatchSubscription({\n signal_type: pattern,\n subscriber: '_in_memory_',\n handler: 'callback',\n });\n\n const registered: RegisteredHandler = { pattern, handler, subscription };\n\n if (!this.handlers.has(pattern)) {\n this.handlers.set(pattern, []);\n }\n this.handlers.get(pattern)?.push(registered);\n }\n\n /**\n * Remove an in-memory handler\n *\n * @param pattern - Signal type pattern\n * @param handler - Handler function to remove\n * @returns True if handler was found and removed\n */\n off(pattern: string, handler: DispatchHandler): boolean {\n const handlers = this.handlers.get(pattern);\n if (!handlers) return false;\n\n const index = handlers.findIndex((h) => h.handler === handler);\n if (index === -1) return false;\n\n handlers.splice(index, 1);\n if (handlers.length === 0) {\n this.handlers.delete(pattern);\n }\n return true;\n }\n\n /**\n * Creates or updates a persistent subscription in the database.\n *\n * Persistent subscriptions survive process restarts. When `process(subscriber, handler)`\n * is called later, all pending dispatches matching this subscriber's signal patterns\n * will be delivered.\n *\n * Calling `subscribe()` with the same `signalType`/`subscriber` pair is idempotent\n * (upsert) — it is safe to call on every agent startup.\n *\n * Set `delivery: 'fanout'` to give each subscriber their own dispatch copy.\n * Default is `'compete'` (at-most-once, first subscriber to process claims it).\n *\n * @param options.signalType - Signal type pattern to subscribe to (wildcards supported)\n * @param options.subscriber - Name that identifies this subscriber (e.g. agent class name)\n * @param options.handler - Optional method name on the subscriber to call (default `'handleDispatch'`)\n * @param options.delivery - `'compete'` (default) for at-most-once, `'fanout'` for per-subscriber copies\n * @param options.enabled - If `false`, subscription is created but disabled (default `true`)\n *\n * @example\n * ```typescript\n * // Subscribe on agent startup (idempotent)\n * await bus.subscribe({ signalType: 'campaign.*', subscriber: 'FiscusAgent' });\n *\n * // Fan-out: each subscriber gets their own dispatch copy\n * await bus.subscribe({\n * signalType: 'campaign.*',\n * subscriber: 'AuditorAgent',\n * delivery: 'fanout',\n * });\n *\n * // Later, process matching pending dispatches\n * await bus.process('FiscusAgent', async (payload, metadata) => { ... });\n * ```\n *\n * @see {@link process} to consume pending dispatches for this subscriber\n * @see {@link unsubscribe} to remove the subscription\n * @see {@link on} for non-persistent in-memory handling\n */\n async subscribe(options: DispatchSubscribeOptions): Promise<void> {\n await this.initialize();\n\n // Gate the subscriber namespace (S5 #1398): require a concrete subscriber\n // name and reject the reserved internal sentinel so callers cannot\n // register a persistent subscription that masquerades as the in-memory\n // pseudo-subscriber.\n const subscriber = (options.subscriber ?? '').trim();\n if (!subscriber) {\n throw new Error('DispatchBus.subscribe requires a non-empty subscriber');\n }\n if (subscriber === DispatchBus.RESERVED_SUBSCRIBER) {\n throw new Error(\n `DispatchBus.subscribe: \"${DispatchBus.RESERVED_SUBSCRIBER}\" is a reserved subscriber name`,\n );\n }\n const signalType = (options.signalType ?? '').trim();\n if (!signalType) {\n throw new Error('DispatchBus.subscribe requires a non-empty signalType');\n }\n\n const subscription = new DispatchSubscription({\n signal_type: signalType,\n subscriber,\n handler: options.handler || 'handleDispatch',\n delivery: options.delivery || 'compete',\n enabled: options.enabled !== false ? 1 : 0,\n // Server-derived tenant scope (never from caller options). NULL when there\n // is no active tenant context (global subscription).\n tenant_id: resolveDispatchTenantId() ?? null,\n });\n\n await DispatchSubscriptionCollection.upsert(this.db, subscription);\n }\n\n /**\n * Remove a persistent subscription\n *\n * @param signalType - Signal type pattern\n * @param subscriber - Subscriber name\n */\n async unsubscribe(signalType: string, subscriber: string): Promise<void> {\n await this.initialize();\n // Scoped to the active tenant so one tenant cannot remove another's\n // subscription (S5 #1398).\n await DispatchSubscriptionCollection.deleteByKey(\n this.db,\n signalType,\n subscriber,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * Processes pending dispatches for a named subscriber.\n *\n * For each matching `pending` dispatch:\n * 1. Sets status to `processing`\n * 2. Calls `handler(payload, metadata)`\n * 3. On success: sets status to `completed`\n * 4. On error: sets status to `failed` (stores the error message)\n *\n * Uses a wildcard-aware query strategy: subscriptions with `*` patterns fetch\n * all pending dispatches and filter in memory; exact-match subscriptions use\n * a direct SQL `IN` query for efficiency.\n *\n * @param subscriber - The subscriber name (must match a `subscribe()` call)\n * @param handler - Function to call for each pending dispatch\n * @param options.limit - Maximum dispatches to process in one call (default 100)\n * @param options.signalTypes - Optional filter to process only specific signal types\n * @returns Number of dispatches successfully processed (excludes failed)\n *\n * @example\n * ```typescript\n * const count = await bus.process('FiscusAgent', async (payload, metadata) => {\n * if (metadata.signalType === 'campaign.completed') {\n * await generateInvoice(payload.campaignId);\n * }\n * });\n * console.log(`Processed ${count} dispatches`);\n * ```\n *\n * @see {@link subscribe} to register the persistent subscription first\n * @see {@link retry} to reset failed dispatches back to pending\n */\n async process(\n subscriber: string,\n handler: DispatchHandler,\n options: DispatchProcessOptions = {},\n ): Promise<number> {\n await this.initialize();\n\n // Resolve the active tenant scope server-side (S5 #1398). This distinguishes\n // three states (see resolveDispatchTenantScope):\n // - tenancy off → no filter; system/global + non-tenant deployments behave\n // exactly as before.\n // - active tenant T → only T's dispatches plus global (NULL) dispatches are\n // claimable, so a subscriber in tenant A cannot see/claim tenant B's.\n // - tenancy on but no active tenant → fail-closed to global (NULL) rows\n // only; a missing context never leaks other tenants' dispatches.\n const tenantScope = resolveDispatchTenantScope();\n\n // Get subscriber's subscriptions, scoped to the active tenant so a tenant-A\n // processor only matches tenant-A (and, when global, global) subscriptions.\n const subscriptions = await DispatchSubscriptionCollection.findBySubscriber(\n this.db,\n subscriber,\n true,\n tenantScope,\n );\n\n if (subscriptions.length === 0) {\n return 0;\n }\n\n // Get signal types (including wildcards)\n const signalTypes = subscriptions.map((s) => s.signalType);\n\n // For non-wildcard subscriptions, we can query directly\n // For wildcards, we need to get all pending and filter\n const hasWildcards = signalTypes.some((t) => t.includes('*'));\n\n // Helper: check if a dispatch should be visible to this subscriber\n const isVisibleToSubscriber = (dispatch: Dispatch): boolean => {\n // Tenant isolation (defense in depth — the SQL queries below also filter).\n if (tenantScope.enforced) {\n if (tenantScope.tenantId === null) {\n // Tenancy on, no active tenant → only global (NULL) rows are visible.\n if (dispatch.tenantId !== null) {\n return false;\n }\n } else if (\n dispatch.tenantId !== null &&\n dispatch.tenantId !== tenantScope.tenantId\n ) {\n // Active tenant → that tenant's rows plus global (NULL) rows only.\n return false;\n }\n }\n if (dispatch.targetSubscriber === subscriber) {\n // Targeted at this subscriber (fanout copy) — always visible\n return true;\n }\n if (dispatch.targetSubscriber !== null) {\n // Targeted at a different subscriber — not visible\n return false;\n }\n // Null target (compete dispatch): only visible if this subscriber\n // has a compete subscription matching this dispatch type\n const matchingSub = subscriptions.find((sub) =>\n sub.matches(dispatch.type),\n );\n return matchingSub?.delivery === 'compete';\n };\n\n let pendingDispatches: Dispatch[];\n if (hasWildcards) {\n // Get all pending dispatches and filter by subscription patterns + target\n const allPending = await DispatchCollection.list(this.db, {\n status: 'pending',\n limit: options.limit || 100,\n tenantScope,\n });\n\n pendingDispatches = allPending.filter(\n (dispatch) =>\n subscriptions.some((sub) => sub.matches(dispatch.type)) &&\n isVisibleToSubscriber(dispatch),\n );\n } else {\n // Direct query for exact signal types (with target_subscriber filter)\n const raw = await DispatchCollection.findPending(\n this.db,\n signalTypes,\n options.limit || 100,\n subscriber,\n tenantScope,\n );\n // Post-filter for delivery mode correctness\n pendingDispatches = raw.filter(isVisibleToSubscriber);\n }\n\n // Filter by specific signal types if provided\n if (options.signalTypes && options.signalTypes.length > 0) {\n pendingDispatches = pendingDispatches.filter((d) =>\n options.signalTypes?.includes(d.type),\n );\n }\n\n let processed = 0;\n\n for (const dispatch of pendingDispatches) {\n // Atomically claim the dispatch (S5 #1398). The conditional UPDATE\n // (WHERE id = ? AND status = 'pending' [AND tenant predicate]) closes the\n // TOCTOU window: if a competing worker — or a system-scoped processor —\n // already moved this row out of 'pending', our claim affects no rows and\n // we skip it, so a compete/global dispatch is processed at most once.\n const claimed = await DispatchCollection.claim(\n this.db,\n dispatch,\n tenantScope,\n );\n if (!claimed) {\n continue;\n }\n\n try {\n // Invoke handler\n await handler(dispatch.payload, dispatch.getMetadata());\n\n // Mark as completed\n dispatch.markCompleted(subscriber);\n await DispatchCollection.update(this.db, dispatch);\n processed++;\n } catch (error) {\n // Mark as failed\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n dispatch.markFailed(errorMessage);\n await DispatchCollection.update(this.db, dispatch);\n }\n }\n\n return processed;\n }\n\n /**\n * Retry failed dispatches\n *\n * Resets failed dispatches to pending status so they can be processed again.\n *\n * @param options - Retry options\n * @returns Number of dispatches reset\n */\n async retry(options: DispatchRetryOptions = {}): Promise<number> {\n await this.initialize();\n\n // Tenant isolation (S5 #1398): only the active tenant's failed dispatches\n // (plus global ones) are eligible — a tenant's retry() must not reset\n // another tenant's rows. Scope is derived server-side; callers cannot widen\n // it.\n const retryable = await DispatchCollection.findRetryable(\n this.db,\n options,\n resolveDispatchTenantScope(),\n );\n\n for (const dispatch of retryable) {\n dispatch.resetForRetry();\n await DispatchCollection.update(this.db, dispatch);\n }\n\n return retryable.length;\n }\n\n /**\n * Clean up old dispatches\n *\n * @param options - Cleanup options\n * @returns Number of dispatches deleted\n */\n async cleanup(\n options: DispatchCleanupOptions = {},\n ): Promise<DispatchCleanupResult> {\n await this.initialize();\n // Tenant isolation (S5 #1398): only the active tenant's dispatches (plus\n // global ones) are deleted — a tenant's cleanup() must not delete another\n // tenant's rows. Scope is derived server-side; callers cannot widen it.\n return DispatchCollection.cleanup(\n this.db,\n options,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * Lists dispatches, scoped to the active tenant context (S5 #1398).\n *\n * The tenant scope is derived **server-side** from the active context and\n * **overrides any caller-supplied scope** — callers cannot select another\n * tenant or widen visibility to all tenants. Scoping rules:\n *\n * - tenancy off → no tenant filter (pre-tenancy behavior).\n * - active tenant T → that tenant's dispatches plus global (NULL) dispatches.\n * - tenancy on but no active tenant → global (NULL) dispatches only\n * (fail-closed; never all tenants).\n *\n * @param options - Non-tenant list filters (status/type/source/etc.). Any\n * `tenantScope` field is ignored and replaced with the server-derived one.\n */\n async list(options: DispatchListOptions = {}): Promise<Dispatch[]> {\n await this.initialize();\n // Strip any caller-supplied scope and inject the server-derived one.\n const { tenantScope: _ignoredCallerScope, ...safeOptions } = options;\n return DispatchCollection.list(this.db, {\n ...safeOptions,\n tenantScope: resolveDispatchTenantScope(),\n });\n }\n\n /**\n * Gets a dispatch by ID, enforcing the active tenant scope (S5 #1398).\n *\n * Applies the same server-derived tenant predicate as {@link list}: a\n * subscriber in tenant A cannot fetch tenant B's dispatch by id, and when\n * tenancy is on with no active tenant only global (NULL) dispatches are\n * returned. Returns `null` when the dispatch exists but is out of scope.\n */\n async get(id: string): Promise<Dispatch | null> {\n await this.initialize();\n return DispatchCollection.getScoped(\n this.db,\n id,\n resolveDispatchTenantScope(),\n );\n }\n\n /**\n * List all subscriptions\n */\n async listSubscriptions(\n subscriber?: string,\n ): Promise<DispatchSubscription[]> {\n await this.initialize();\n\n // Scoped to the active tenant so listing never leaks another tenant's\n // subscriptions (S5 #1398).\n const tenantScope = resolveDispatchTenantScope();\n\n if (subscriber) {\n return DispatchSubscriptionCollection.findBySubscriber(\n this.db,\n subscriber,\n false,\n tenantScope,\n );\n }\n return DispatchSubscriptionCollection.list(this.db, false, tenantScope);\n }\n\n /**\n * Notify in-memory handlers (fire-and-forget)\n */\n private notifyHandlers(\n type: string,\n payload: unknown,\n metadata: DispatchMetadata,\n ): void {\n // Collect all matching handlers\n const matchingHandlers: RegisteredHandler[] = [];\n\n for (const [, handlers] of this.handlers) {\n for (const registered of handlers) {\n if (registered.subscription.matches(type)) {\n matchingHandlers.push(registered);\n }\n }\n }\n\n // Fire-and-forget - don't await\n for (const registered of matchingHandlers) {\n try {\n const result = registered.handler(payload, metadata);\n // Handle both sync and async handlers\n if (result && typeof result.catch === 'function') {\n void result.catch((error: unknown) => {\n logger.error(\n `DispatchBus: Handler for \"${registered.pattern}\" failed`,\n { error },\n );\n });\n }\n } catch (error) {\n logger.error(\n `DispatchBus: Handler for \"${registered.pattern}\" failed`,\n { error },\n );\n }\n }\n }\n}\n\n/**\n * Create a DispatchBus instance\n *\n * @param options - Bus configuration\n * @returns Initialized DispatchBus\n */\nexport async function createDispatchBus(\n options: DispatchBusOptions = {},\n): Promise<DispatchBus> {\n // Resolve database configuration\n const dbConfig = options.db || options.persistence;\n\n if (!dbConfig) {\n throw new Error('DispatchBus requires a database configuration');\n }\n\n // Get or create database interface\n let db: DatabaseInterface;\n if (typeof dbConfig === 'string') {\n db = await getDatabase(dbConfig);\n } else if ('query' in dbConfig) {\n // Already a DatabaseInterface (has query method)\n db = dbConfig as DatabaseInterface;\n } else if ('type' in dbConfig && 'url' in dbConfig) {\n // Database config object - use getDatabase with type and url\n db = await getDatabase({\n type: dbConfig.type as 'sqlite' | 'postgres' | 'duckdb',\n url: dbConfig.url,\n });\n } else {\n throw new Error('Invalid database configuration for DispatchBus');\n }\n\n const bus = new DispatchBus(db);\n await bus.initialize();\n\n return bus;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoD7C,IAAa,cAAb,MAAa,YAAY;CACvB;CACA,2BAAqD,IAAI,IAAI;CAC7D,cAA+B;;;;;;;CAQ/B,OAAwB,sBAAsB;;CAG9C,OAAwB,oBAAoB;;;;;;;;;;;;;CAc5C,OAAe,eAAe,QAAoC;EAChE,MAAM,OAAO,UAAU,GAAA,CAAI,KAAK;EAChC,IAAI,CAAC,KACH,OAAO;EAET,IAAI,QAAQ,YAAY,qBACtB,MAAM,IAAI,MACR,sBAAsB,YAAY,oBAAoB,8CACxD;EAEF,OAAO,IAAI,MAAM,GAAG,YAAY,iBAAiB;CACnD;;;;CAKA,YAAY,IAAuB;EACjC,KAAK,KAAK;CACZ;;;;CAKA,MAAM,aAA4B;EAChC,IAAI,KAAK,aAAa;EACtB,MAAM,SAAS,kBAAkB,KAAK,EAAE;EAIxC,IAAI,CAAC,MADwB,mBAAmB,YAAY,KAAK,EAAE,GAC9C;GAEnB,MAAM,aAAa,2BACjB,4BACA,MACF,CAAC,CACE,MAAM,GAAG,CAAC,CACV,QAAQ,MAAM,EAAE,KAAK,CAAC;GACzB,KAAK,MAAM,QAAQ,YACjB,MAAM,KAAK,GAAG,MAAM,IAAI;EAE5B;EACA,MAAM,sCAAsC,KAAK,EAAE;EACnD,MAAM,uCAAuC,KAAK,EAAE;EAKpD,IAAI,CAAC,MAHoB,+BAA+B,YACtD,KAAK,EACP,GACiB;GACf,MAAM,aAAa,2BACjB,0CACA,MACF,CAAC,CACE,MAAM,GAAG,CAAC,CACV,QAAQ,MAAM,EAAE,KAAK,CAAC;GACzB,KAAK,MAAM,QAAQ,YACjB,MAAM,KAAK,GAAG,MAAM,IAAI;EAE5B;EACA,MAAM,sCAAsC,KAAK,EAAE;EACnD,MAAM,oDAAoD,KAAK,EAAE;EAEjE,KAAK,cAAc;CACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDA,MAAM,KACJ,MACA,SACA,UAA+B,CAAC,GACb;EACnB,MAAM,KAAK,WAAW;EAMtB,MAAM,WAAW,wBAAwB,KAAK;EAG9C,MAAM,SAAS,YAAY,eAAe,QAAQ,MAAM;EAOxD,MAAM,eAAe,MAAM,+BAA+B,iBACxD,KAAK,IACL,MACA,2BAA2B,CAC7B;EAGA,MAAM,aAAa,aAAa,QAAQ,MAAM,EAAE,aAAa,QAAQ;EACrE,MAAM,cAAc,aAAa,QAAQ,MAAM,EAAE,aAAa,QAAQ;EAGtE,MAAM,WAAW,IAAI,SAAS;GAC5B;GACA;GACA,WAAW,QAAQ,YAAY;GAC/B,SAAS,KAAK,UAAU,WAAW,CAAC,CAAC;GACrC,UAAU,KAAK,UAAU,QAAQ,YAAY,CAAC,CAAC;GAC/C,QAAQ;GACR,gBAAgB,QAAQ,iBAAiB;GACzC,WAAW;EACb,CAAC;EAGD,IAAI,iBAAiB;EAGrB,KAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,iBAAiB,IAAI,SAAS;IAClC;IACA;IACA,WAAW,QAAQ,YAAY;IAC/B,SAAS,KAAK,UAAU,WAAW,CAAC,CAAC;IACrC,UAAU,KAAK,UAAU,QAAQ,YAAY,CAAC,CAAC;IAC/C,QAAQ;IACR,mBAAmB,IAAI;IACvB,gBAAgB,QAAQ,iBAAiB;IACzC,WAAW;GACb,CAAC;GACD,MAAM,mBAAmB,OAAO,KAAK,IAAI,cAAc;GAEvD,IAAI,YAAY,WAAW,KAAK,aAAa,SAAS,GACpD,iBAAiB;EAErB;EAIA,IAAI,YAAY,SAAS,KAAK,aAAa,WAAW,GACpD,MAAM,mBAAmB,OAAO,KAAK,IAAI,QAAQ;EAInD,KAAK,eAAe,MAAM,SAAS,SAAS,YAAY,CAAC;EAEzD,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BA,GAAG,SAAiB,SAAgC;EAOlD,MAAM,aAAgC;GAAE;GAAS;GAAS,cAAA,IANjC,qBAAqB;IAC5C,aAAa;IACb,YAAY;IACZ,SAAS;GACX,CAE0D;EAAa;EAEvE,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,GAC5B,KAAK,SAAS,IAAI,SAAS,CAAC,CAAC;EAE/B,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,KAAK,UAAU;CAC7C;;;;;;;;CASA,IAAI,SAAiB,SAAmC;EACtD,MAAM,WAAW,KAAK,SAAS,IAAI,OAAO;EAC1C,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,QAAQ,SAAS,WAAW,MAAM,EAAE,YAAY,OAAO;EAC7D,IAAI,UAAU,IAAI,OAAO;EAEzB,SAAS,OAAO,OAAO,CAAC;EACxB,IAAI,SAAS,WAAW,GACtB,KAAK,SAAS,OAAO,OAAO;EAE9B,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCA,MAAM,UAAU,SAAkD;EAChE,MAAM,KAAK,WAAW;EAMtB,MAAM,cAAc,QAAQ,cAAc,GAAA,CAAI,KAAK;EACnD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,uDAAuD;EAEzE,IAAI,eAAe,YAAY,qBAC7B,MAAM,IAAI,MACR,2BAA2B,YAAY,oBAAoB,gCAC7D;EAEF,MAAM,cAAc,QAAQ,cAAc,GAAA,CAAI,KAAK;EACnD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,uDAAuD;EAGzE,MAAM,eAAe,IAAI,qBAAqB;GAC5C,aAAa;GACb;GACA,SAAS,QAAQ,WAAW;GAC5B,UAAU,QAAQ,YAAY;GAC9B,SAAS,QAAQ,YAAY,QAAQ,IAAI;GAGzC,WAAW,wBAAwB,KAAK;EAC1C,CAAC;EAED,MAAM,+BAA+B,OAAO,KAAK,IAAI,YAAY;CACnE;;;;;;;CAQA,MAAM,YAAY,YAAoB,YAAmC;EACvE,MAAM,KAAK,WAAW;EAGtB,MAAM,+BAA+B,YACnC,KAAK,IACL,YACA,YACA,2BAA2B,CAC7B;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCA,MAAM,QACJ,YACA,SACA,UAAkC,CAAC,GAClB;EACjB,MAAM,KAAK,WAAW;EAUtB,MAAM,cAAc,2BAA2B;EAI/C,MAAM,gBAAgB,MAAM,+BAA+B,iBACzD,KAAK,IACL,YACA,MACA,WACF;EAEA,IAAI,cAAc,WAAW,GAC3B,OAAO;EAIT,MAAM,cAAc,cAAc,KAAK,MAAM,EAAE,UAAU;EAIzD,MAAM,eAAe,YAAY,MAAM,MAAM,EAAE,SAAS,GAAG,CAAC;EAG5D,MAAM,yBAAyB,aAAgC;GAE7D,IAAI,YAAY;QACV,YAAY,aAAa;SAEvB,SAAS,aAAa,MACxB,OAAO;IAAA,OAEJ,IACL,SAAS,aAAa,QACtB,SAAS,aAAa,YAAY,UAGlC,OAAO;GAAA;GAGX,IAAI,SAAS,qBAAqB,YAEhC,OAAO;GAET,IAAI,SAAS,qBAAqB,MAEhC,OAAO;GAOT,OAHoB,cAAc,MAAM,QACtC,IAAI,QAAQ,SAAS,IAAI,CAEpB,CAAA,EAAa,aAAa;EACnC;EAEA,IAAI;EACJ,IAAI,cAQF,qBAAoB,MANK,mBAAmB,KAAK,KAAK,IAAI;GACxD,QAAQ;GACR,OAAO,QAAQ,SAAS;GACxB;EACF,CAAC,EAAA,CAE8B,QAC5B,aACC,cAAc,MAAM,QAAQ,IAAI,QAAQ,SAAS,IAAI,CAAC,KACtD,sBAAsB,QAAQ,CAClC;OAWA,qBAAoB,MARF,mBAAmB,YACnC,KAAK,IACL,aACA,QAAQ,SAAS,KACjB,YACA,WACF,EAAA,CAEwB,OAAO,qBAAqB;EAItD,IAAI,QAAQ,eAAe,QAAQ,YAAY,SAAS,GACtD,oBAAoB,kBAAkB,QAAQ,MAC5C,QAAQ,aAAa,SAAS,EAAE,IAAI,CACtC;EAGF,IAAI,YAAY;EAEhB,KAAK,MAAM,YAAY,mBAAmB;GAWxC,IAAI,CAAC,MALiB,mBAAmB,MACvC,KAAK,IACL,UACA,WACF,GAEE;GAGF,IAAI;IAEF,MAAM,QAAQ,SAAS,SAAS,SAAS,YAAY,CAAC;IAGtD,SAAS,cAAc,UAAU;IACjC,MAAM,mBAAmB,OAAO,KAAK,IAAI,QAAQ;IACjD;GACF,SAAS,OAAO;IAEd,MAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACvD,SAAS,WAAW,YAAY;IAChC,MAAM,mBAAmB,OAAO,KAAK,IAAI,QAAQ;GACnD;EACF;EAEA,OAAO;CACT;;;;;;;;;CAUA,MAAM,MAAM,UAAgC,CAAC,GAAoB;EAC/D,MAAM,KAAK,WAAW;EAMtB,MAAM,YAAY,MAAM,mBAAmB,cACzC,KAAK,IACL,SACA,2BAA2B,CAC7B;EAEA,KAAK,MAAM,YAAY,WAAW;GAChC,SAAS,cAAc;GACvB,MAAM,mBAAmB,OAAO,KAAK,IAAI,QAAQ;EACnD;EAEA,OAAO,UAAU;CACnB;;;;;;;CAQA,MAAM,QACJ,UAAkC,CAAC,GACH;EAChC,MAAM,KAAK,WAAW;EAItB,OAAO,mBAAmB,QACxB,KAAK,IACL,SACA,2BAA2B,CAC7B;CACF;;;;;;;;;;;;;;;;CAiBA,MAAM,KAAK,UAA+B,CAAC,GAAwB;EACjE,MAAM,KAAK,WAAW;EAEtB,MAAM,EAAE,aAAa,qBAAqB,GAAG,gBAAgB;EAC7D,OAAO,mBAAmB,KAAK,KAAK,IAAI;GACtC,GAAG;GACH,aAAa,2BAA2B;EAC1C,CAAC;CACH;;;;;;;;;CAUA,MAAM,IAAI,IAAsC;EAC9C,MAAM,KAAK,WAAW;EACtB,OAAO,mBAAmB,UACxB,KAAK,IACL,IACA,2BAA2B,CAC7B;CACF;;;;CAKA,MAAM,kBACJ,YACiC;EACjC,MAAM,KAAK,WAAW;EAItB,MAAM,cAAc,2BAA2B;EAE/C,IAAI,YACF,OAAO,+BAA+B,iBACpC,KAAK,IACL,YACA,OACA,WACF;EAEF,OAAO,+BAA+B,KAAK,KAAK,IAAI,OAAO,WAAW;CACxE;;;;CAKA,eACE,MACA,SACA,UACM;EAEN,MAAM,mBAAwC,CAAC;EAE/C,KAAK,MAAM,GAAG,aAAa,KAAK,UAC9B,KAAK,MAAM,cAAc,UACvB,IAAI,WAAW,aAAa,QAAQ,IAAI,GACtC,iBAAiB,KAAK,UAAU;EAMtC,KAAK,MAAM,cAAc,kBACvB,IAAI;GACF,MAAM,SAAS,WAAW,QAAQ,SAAS,QAAQ;GAEnD,IAAI,UAAU,OAAO,OAAO,UAAU,YACpC,OAAY,OAAO,UAAmB;IACpC,OAAO,MACL,6BAA6B,WAAW,QAAQ,WAChD,EAAE,MAAM,CACV;GACF,CAAC;EAEL,SAAS,OAAO;GACd,OAAO,MACL,6BAA6B,WAAW,QAAQ,WAChD,EAAE,MAAM,CACV;EACF;CAEJ;AACF;;;;;;;AAQA,eAAsB,kBACpB,UAA8B,CAAC,GACT;CAEtB,MAAM,WAAW,QAAQ,MAAM,QAAQ;CAEvC,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,+CAA+C;CAIjE,IAAI;CACJ,IAAI,OAAO,aAAa,UACtB,KAAK,MAAM,YAAY,QAAQ;MAC1B,IAAI,WAAW,UAEpB,KAAK;MACA,IAAI,UAAU,YAAY,SAAS,UAExC,KAAK,MAAM,YAAY;EACrB,MAAM,SAAS;EACf,KAAK,SAAS;CAChB,CAAC;MAED,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,MAAM,IAAI,YAAY,EAAE;CAC9B,MAAM,IAAI,WAAW;CAErB,OAAO;AACT"}
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { importOptionalDependency, registerOptionalDependency } from "./lazy-ext
15
15
  import { createFilesystemAdapter, registerFilesystemAdapterFactory } from "./filesystem-loader.js";
16
16
  import { SignalSanitizer } from "./signals/sanitizer.js";
17
17
  import { SignalBus } from "./signals/bus.js";
18
- import { ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, tableExists } from "./system/compatibility.js";
18
+ import { assertPostgresSystemTimestampsCurrent, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, getDatabaseEngine, migratePostgresSystemTimestamps, tableExists } from "./system/compatibility.js";
19
19
  import { SmrtClass } from "./class.js";
20
20
  import { DEFAULT_EMBEDDING_CONFIG, EmbeddingProvider } from "./embeddings/provider.js";
21
21
  import { CosineSimilarity } from "./embeddings/similarity.js";
@@ -71,4 +71,4 @@ import "./system/index.js";
71
71
  import { getTestDatabase } from "./testing/database.js";
72
72
  import "./tools/index.js";
73
73
  import { smrtPlugin } from "./vite-plugin/index.js";
74
- export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DEFAULT_LEARNING_CONFIG, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, LearningMemory, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, broadcastCacheInvalidation, buildChangeEventStream, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, changeEventSubscribersAtCapacity, childAccessorName, clone, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createFilesystemAdapter, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, importOptionalDependency, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, normalizeEventsMaxSubscribers, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, registerFilesystemAdapterFactory, registerOptionalDependency, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
74
+ export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DEFAULT_LEARNING_CONFIG, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, LearningMemory, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, assertPostgresSystemTimestampsCurrent, broadcastCacheInvalidation, buildChangeEventStream, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, changeEventSubscribersAtCapacity, childAccessorName, clone, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createFilesystemAdapter, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getDatabaseEngine, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, importOptionalDependency, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, migratePostgresSystemTimestamps, normalizeEventsMaxSubscribers, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, registerFilesystemAdapterFactory, registerOptionalDependency, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
@@ -1,9 +1,9 @@
1
1
  //#region src/manifest/static-manifest.ts
2
2
  var staticManifest = {
3
3
  "version": "1.0.0",
4
- "timestamp": 1784377914316,
4
+ "timestamp": 1784534099516,
5
5
  "packageName": "@happyvertical/smrt-core",
6
- "packageVersion": "0.40.14",
6
+ "packageVersion": "0.40.16",
7
7
  "objects": {
8
8
  "@happyvertical/smrt-core:SmrtClass": {
9
9
  "name": "smrtclass",