@happyvertical/smrt-core 0.57.46 → 0.57.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -3
- package/agents/browser-entry.md +16 -0
- package/agents/build-knowledge.md +58 -14
- package/agents/change-feed.md +20 -0
- package/agents/generators.md +69 -3
- package/agents/schema-paths.md +82 -0
- package/dist/_virtual/_rolldown/runtime.js +22 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -2
- package/dist/cascade.d.ts +46 -0
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +96 -7
- package/dist/cascade.js.map +1 -1
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +7 -4
- package/dist/change-feed.js.map +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +3 -3
- package/dist/class.js.map +1 -1
- package/dist/consumer-plugin/index.d.ts +13 -0
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +69 -13
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/consumer-plugin.js +2 -2
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +3 -3
- package/dist/database.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +1 -4
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/dispatch/index.d.ts +1 -1
- package/dist/dispatch/index.d.ts.map +1 -1
- package/dist/dispatch/types.d.ts +22 -3
- package/dist/dispatch/types.d.ts.map +1 -1
- package/dist/generators/changes-route.d.ts.map +1 -1
- package/dist/generators/changes-route.js +2 -2
- package/dist/generators/changes-route.js.map +1 -1
- package/dist/generators/custom-action.d.ts +11 -0
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +23 -4
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/lazy-config.d.ts +120 -3
- package/dist/lazy-config.d.ts.map +1 -1
- package/dist/lazy-config.js +81 -10
- package/dist/lazy-config.js.map +1 -1
- package/dist/manifest/generator.d.ts +8 -2
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +6 -5
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/index.d.ts +1 -0
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +2 -1
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/local-manifest-reader.d.ts +13 -2
- package/dist/manifest/local-manifest-reader.d.ts.map +1 -1
- package/dist/manifest/local-manifest-reader.js +38 -6
- package/dist/manifest/local-manifest-reader.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts +9 -8
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +67 -27
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/object-declarations.d.ts +116 -0
- package/dist/manifest/object-declarations.d.ts.map +1 -0
- package/dist/manifest/object-declarations.js +161 -0
- package/dist/manifest/object-declarations.js.map +1 -0
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +5 -3
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.d.ts.map +1 -1
- package/dist/manifest/store.js +18 -2
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest/test-artifacts.d.ts +57 -0
- package/dist/manifest/test-artifacts.d.ts.map +1 -0
- package/dist/manifest/test-artifacts.js +111 -0
- package/dist/manifest/test-artifacts.js.map +1 -0
- package/dist/manifest.js +2 -1
- package/dist/manifest.json +5 -3
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +11 -3
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -1
- package/dist/migrations/qualified-name-aliases.d.ts.map +1 -1
- package/dist/migrations/qualified-name-aliases.js +1 -1
- package/dist/migrations/qualified-name-aliases.js.map +1 -1
- package/dist/migrations.js +3 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +2 -2
- package/dist/object.js.map +1 -1
- package/dist/postgres-permissions.d.ts.map +1 -1
- package/dist/postgres-permissions.js +4 -3
- package/dist/postgres-permissions.js.map +1 -1
- package/dist/postgres-timeouts.d.ts +30 -3
- package/dist/postgres-timeouts.d.ts.map +1 -1
- package/dist/postgres-timeouts.js +39 -2
- package/dist/postgres-timeouts.js.map +1 -1
- package/dist/prebuild/index.d.ts.map +1 -1
- package/dist/prebuild/index.js +14 -11
- package/dist/prebuild/index.js.map +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +24 -2
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/collection-exposure.d.ts +27 -0
- package/dist/registry/collection-exposure.d.ts.map +1 -1
- package/dist/registry/collection-exposure.js +74 -1
- package/dist/registry/collection-exposure.js.map +1 -1
- package/dist/registry/collection-resolution.d.ts.map +1 -1
- package/dist/registry/collection-resolution.js +1 -0
- package/dist/registry/collection-resolution.js.map +1 -1
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +5 -0
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +8 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +17 -4
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +12 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +2 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +30 -10
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +10 -8
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +29 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/generator.d.ts +32 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +59 -0
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +4 -2
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/scripts/browser-unbundled-dev.mjs +90 -0
- package/dist/scripts/generate-test-manifest.js +9 -49
- package/dist/smrt-knowledge.json +16 -16
- package/dist/system/compatibility.d.ts.map +1 -1
- package/dist/system/compatibility.js +12 -4
- package/dist/system/compatibility.js.map +1 -1
- package/dist/system/schema.d.ts +70 -8
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +231 -24
- package/dist/system/schema.js.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +14 -2
- package/dist/testing/database.js.map +1 -1
- package/dist/tools/tool-generator.d.ts.map +1 -1
- package/dist/tools/tool-generator.js +2 -2
- package/dist/tools/tool-generator.js.map +1 -1
- package/dist/utils/import-workspace-module.d.ts.map +1 -1
- package/dist/utils/import-workspace-module.js +23 -0
- package/dist/utils/import-workspace-module.js.map +1 -1
- package/dist/utils/naming.js +5 -3
- package/dist/utils/naming.js.map +1 -1
- package/dist/vendor/pluralize/pluralize.js +421 -0
- package/dist/vendor/pluralize/pluralize.js.map +1 -0
- package/dist/vite-plugin/api-client-entries.d.ts +5 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +11 -6
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/consumer-import.d.ts +28 -0
- package/dist/vite-plugin/consumer-import.d.ts.map +1 -0
- package/dist/vite-plugin/consumer-import.js +42 -0
- package/dist/vite-plugin/consumer-import.js.map +1 -0
- package/dist/vite-plugin/dev-plane-route.d.ts +6 -1
- package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
- package/dist/vite-plugin/dev-plane-route.js +8 -9
- package/dist/vite-plugin/dev-plane-route.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +12 -9
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/resources-route.d.ts.map +1 -1
- package/dist/vite-plugin/resources-route.js +3 -8
- package/dist/vite-plugin/resources-route.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +7 -11
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts +16 -0
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +48 -6
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/package.json +12 -12
package/dist/database.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDatabase } from "./host.js";
|
|
2
|
-
import { applyPostgresRuntimeTimeouts } from "./postgres-timeouts.js";
|
|
2
|
+
import { applyPostgresRuntimeTimeouts, resolveRuntimePoolDbid } from "./postgres-timeouts.js";
|
|
3
3
|
//#region src/database.ts
|
|
4
4
|
/**
|
|
5
5
|
* Type guard to check if a value is a DatabaseInterface instance
|
|
@@ -56,7 +56,7 @@ async function resolveDatabase(config, options = {}) {
|
|
|
56
56
|
return getDatabase({
|
|
57
57
|
...bounded,
|
|
58
58
|
schemas,
|
|
59
|
-
...isMemoryDb ? {} : { dbid: dbid ??
|
|
59
|
+
...isMemoryDb ? {} : { dbid: dbid ?? resolveRuntimePoolDbid(bounded) }
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
const canUseMemory = !config.type || config.type === "sqlite";
|
|
@@ -69,7 +69,7 @@ async function resolveDatabase(config, options = {}) {
|
|
|
69
69
|
return getDatabase({
|
|
70
70
|
...bounded,
|
|
71
71
|
schemas,
|
|
72
|
-
...isMemoryDb ? {} : { dbid: dbid ??
|
|
72
|
+
...isMemoryDb ? {} : { dbid: dbid ?? resolveRuntimePoolDbid(bounded) }
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
//#endregion
|
package/dist/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","names":[],"sources":["../src/database.ts"],"sourcesContent":["/**\n * Database configuration and resolution utilities\n *\n * This module provides a unified type for database configuration options\n * and a utility function to resolve any config format to a DatabaseInterface.\n *\n * @module\n */\n\nimport type { DatabaseInterface, SchemasOption } from '@happyvertical/sql';\nimport { getDatabase } from './host.js';\nimport {\n applyPostgresRuntimeTimeouts,\n type PostgresTimeoutConfig,\n} from './postgres-timeouts.js';\n\n/**\n * Unified type for all database configuration formats\n *\n * Supports three formats:\n * - **String**: Connection URL (e.g., 'products.db', ':memory:', 'postgres://...')\n * - **Config object**: Full configuration with type, url, and options\n * - **DatabaseInterface**: Pre-initialized database instance\n *\n * @example\n * ```typescript\n * // String shortcut\n * const config: DatabaseConfig = 'products.db';\n *\n * // Config object\n * const config: DatabaseConfig = {\n * type: 'sqlite',\n * url: 'products.db',\n * authToken: 'token'\n * };\n *\n * // Pre-initialized instance\n * const db = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * const config: DatabaseConfig = db;\n * ```\n */\nexport type DatabaseConfig =\n | string\n | {\n url?: string;\n type?: 'sqlite' | 'postgres' | 'duckdb' | 'json';\n authToken?: string;\n /**\n * Runtime PostgreSQL timeouts for this connection (#2377). Ignored by\n * every other engine.\n *\n * @see {@link PostgresTimeoutConfig}\n */\n timeouts?: PostgresTimeoutConfig;\n [key: string]: unknown;\n }\n | DatabaseInterface;\n\n/**\n * Canonical declaration of the request-scoped database global.\n *\n * The value is installed by whichever package owns request scoping (today\n * `@happyvertical/smrt-users`, via its session permission context) and is read\n * by the SvelteKit runtime config that `smrt-core`'s vite plugin generates into\n * consumer apps. Both sides used to `declare global` this name independently\n * with different types — `DatabaseConfig` here, a package-private\n * `QueryableDatabase` there — and TypeScript requires merged `var` declarations\n * to be *identical*, not merely compatible. Consumers whose program contained\n * both then failed to type-check (#2342).\n *\n * `smrt-core` owns the declaration because it is the base package every other\n * SMRT package and consumer already depends on. Writers should keep assigning a\n * live `DatabaseInterface`; it satisfies `DatabaseConfig` through that arm of\n * the union. Never redeclare this global elsewhere.\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtGetRequestScopedDatabase:\n | (() => DatabaseConfig | undefined)\n | undefined;\n}\n\n/**\n * Type guard to check if a value is a DatabaseInterface instance\n *\n * @param value - Value to check\n * @returns True if value has query() and close() methods\n *\n * @example\n * ```typescript\n * if (isDatabaseInterface(config)) {\n * // config is DatabaseInterface\n * await config.query('SELECT 1');\n * }\n * ```\n */\nexport function isDatabaseInterface(\n value: unknown,\n): value is DatabaseInterface {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'query' in value &&\n typeof (value as { query: unknown }).query === 'function'\n );\n}\n\n/**\n * Options for resolving a database configuration\n */\nexport interface ResolveDatabaseOptions {\n /**\n * Cache key for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n */\n dbid?: string;\n\n /**\n * Optional pre-generated schemas to pass to the database adapter.\n *\n * Intended for explicit tooling and test utilities that bootstrap schema\n * ahead of runtime. Core runtime no longer passes these automatically.\n */\n schemas?: SchemasOption;\n}\n\n/**\n * Resolve any DatabaseConfig format to a DatabaseInterface instance\n *\n * This utility function normalizes the three config formats:\n * 1. **String URL**: Passed to getDatabase() with auto-detected type\n * 2. **Config object**: Passed to getDatabase() directly\n * 3. **DatabaseInterface**: Returned as-is\n *\n * @param config - Database configuration in any supported format\n * @param options - Resolution options (dbid for caching, optional explicit schemas)\n * @returns Promise resolving to a DatabaseInterface instance\n *\n * @example\n * ```typescript\n * // String URL\n * const db = await resolveDatabase('products.db');\n *\n * // Config object\n * const db = await resolveDatabase({\n * type: 'postgres',\n * url: 'postgres://localhost/mydb'\n * });\n *\n * // Pre-initialized instance (returned as-is)\n * const existingDb = await getDatabase({ url: ':memory:' });\n * const db = await resolveDatabase(existingDb);\n * console.log(db === existingDb); // true\n * ```\n */\nexport async function resolveDatabase(\n config: DatabaseConfig,\n options: ResolveDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const { dbid, schemas } = options;\n\n // Already a DatabaseInterface instance - return as-is\n if (isDatabaseInterface(config)) {\n return config;\n }\n\n // String URL shortcut\n if (typeof config === 'string') {\n const isMemoryDb = config === ':memory:';\n //
|
|
1
|
+
{"version":3,"file":"database.js","names":[],"sources":["../src/database.ts"],"sourcesContent":["/**\n * Database configuration and resolution utilities\n *\n * This module provides a unified type for database configuration options\n * and a utility function to resolve any config format to a DatabaseInterface.\n *\n * @module\n */\n\nimport type { DatabaseInterface, SchemasOption } from '@happyvertical/sql';\nimport { getDatabase } from './host.js';\nimport {\n applyPostgresRuntimeTimeouts,\n type PostgresTimeoutConfig,\n resolveRuntimePoolDbid,\n} from './postgres-timeouts.js';\n\n/**\n * Unified type for all database configuration formats\n *\n * Supports three formats:\n * - **String**: Connection URL (e.g., 'products.db', ':memory:', 'postgres://...')\n * - **Config object**: Full configuration with type, url, and options\n * - **DatabaseInterface**: Pre-initialized database instance\n *\n * @example\n * ```typescript\n * // String shortcut\n * const config: DatabaseConfig = 'products.db';\n *\n * // Config object\n * const config: DatabaseConfig = {\n * type: 'sqlite',\n * url: 'products.db',\n * authToken: 'token'\n * };\n *\n * // Pre-initialized instance\n * const db = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * const config: DatabaseConfig = db;\n * ```\n */\nexport type DatabaseConfig =\n | string\n | {\n url?: string;\n type?: 'sqlite' | 'postgres' | 'duckdb' | 'json';\n authToken?: string;\n /**\n * Runtime PostgreSQL timeouts for this connection (#2377). Ignored by\n * every other engine.\n *\n * @see {@link PostgresTimeoutConfig}\n */\n timeouts?: PostgresTimeoutConfig;\n [key: string]: unknown;\n }\n | DatabaseInterface;\n\n/**\n * Canonical declaration of the request-scoped database global.\n *\n * The value is installed by whichever package owns request scoping (today\n * `@happyvertical/smrt-users`, via its session permission context) and is read\n * by the SvelteKit runtime config that `smrt-core`'s vite plugin generates into\n * consumer apps. Both sides used to `declare global` this name independently\n * with different types — `DatabaseConfig` here, a package-private\n * `QueryableDatabase` there — and TypeScript requires merged `var` declarations\n * to be *identical*, not merely compatible. Consumers whose program contained\n * both then failed to type-check (#2342).\n *\n * `smrt-core` owns the declaration because it is the base package every other\n * SMRT package and consumer already depends on. Writers should keep assigning a\n * live `DatabaseInterface`; it satisfies `DatabaseConfig` through that arm of\n * the union. Never redeclare this global elsewhere.\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtGetRequestScopedDatabase:\n | (() => DatabaseConfig | undefined)\n | undefined;\n}\n\n/**\n * Type guard to check if a value is a DatabaseInterface instance\n *\n * @param value - Value to check\n * @returns True if value has query() and close() methods\n *\n * @example\n * ```typescript\n * if (isDatabaseInterface(config)) {\n * // config is DatabaseInterface\n * await config.query('SELECT 1');\n * }\n * ```\n */\nexport function isDatabaseInterface(\n value: unknown,\n): value is DatabaseInterface {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'query' in value &&\n typeof (value as { query: unknown }).query === 'function'\n );\n}\n\n/**\n * Options for resolving a database configuration\n */\nexport interface ResolveDatabaseOptions {\n /**\n * Cache key for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n */\n dbid?: string;\n\n /**\n * Optional pre-generated schemas to pass to the database adapter.\n *\n * Intended for explicit tooling and test utilities that bootstrap schema\n * ahead of runtime. Core runtime no longer passes these automatically.\n */\n schemas?: SchemasOption;\n}\n\n/**\n * Resolve any DatabaseConfig format to a DatabaseInterface instance\n *\n * This utility function normalizes the three config formats:\n * 1. **String URL**: Passed to getDatabase() with auto-detected type\n * 2. **Config object**: Passed to getDatabase() directly\n * 3. **DatabaseInterface**: Returned as-is\n *\n * @param config - Database configuration in any supported format\n * @param options - Resolution options (dbid for caching, optional explicit schemas)\n * @returns Promise resolving to a DatabaseInterface instance\n *\n * @example\n * ```typescript\n * // String URL\n * const db = await resolveDatabase('products.db');\n *\n * // Config object\n * const db = await resolveDatabase({\n * type: 'postgres',\n * url: 'postgres://localhost/mydb'\n * });\n *\n * // Pre-initialized instance (returned as-is)\n * const existingDb = await getDatabase({ url: ':memory:' });\n * const db = await resolveDatabase(existingDb);\n * console.log(db === existingDb); // true\n * ```\n */\nexport async function resolveDatabase(\n config: DatabaseConfig,\n options: ResolveDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const { dbid, schemas } = options;\n\n // Already a DatabaseInterface instance - return as-is\n if (isDatabaseInterface(config)) {\n return config;\n }\n\n // String URL shortcut\n if (typeof config === 'string') {\n const isMemoryDb = config === ':memory:';\n // Pool identity follows the *bounded* configuration so configurations that\n // differ in their PostgreSQL timeouts (#2377) or pool options (#3046)\n // cannot share one pool.\n const bounded = applyPostgresRuntimeTimeouts({ url: config });\n return getDatabase({\n ...bounded,\n schemas,\n ...(isMemoryDb ? {} : { dbid: dbid ?? resolveRuntimePoolDbid(bounded) }),\n } as Parameters<typeof getDatabase>[0]);\n }\n\n // Config object\n // Only default to :memory: for SQLite (or unspecified type which defaults to SQLite)\n // Other adapters (json, postgres, duckdb) require explicit URLs\n const canUseMemory = !config.type || config.type === 'sqlite';\n const dbUrl = config.url || (canUseMemory ? ':memory:' : '');\n const isMemoryDb = dbUrl === ':memory:';\n const bounded = applyPostgresRuntimeTimeouts({ ...config, url: dbUrl });\n // `config` is the loosely-typed config-object variant of `DatabaseConfig`\n // (it carries an open `[key: string]: unknown` index for adapter-specific\n // options). Cast the merged options to `getDatabase`'s own parameter type at\n // this boundary; the adapter validates the concrete shape at runtime.\n return getDatabase({\n ...bounded,\n schemas,\n // An explicit `options.dbid` wins, then a `dbid` on the config itself,\n // then the shared rule: none for PostgreSQL (the SDK keys the pool on its\n // effective connection and pool options), `smrt:<url>` elsewhere (#3046).\n ...(isMemoryDb ? {} : { dbid: dbid ?? resolveRuntimePoolDbid(bounded) }),\n } as Parameters<typeof getDatabase>[0]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiGA,SAAgB,oBACd,OAC4B;CAC5B,OACE,UAAU,QACV,OAAO,UAAU,YACjB,WAAW,SACX,OAAQ,MAA6B,UAAU;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,gBACpB,QACA,UAAkC,CAAC,GACP;CAC5B,MAAM,EAAE,MAAM,YAAY;CAG1B,IAAI,oBAAoB,MAAM,GAC5B,OAAO;CAIT,IAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,aAAa,WAAW;EAI9B,MAAM,UAAU,6BAA6B,EAAE,KAAK,OAAO,CAAC;EAC5D,OAAO,YAAY;GACjB,GAAG;GACH;GACA,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,QAAQ,uBAAuB,OAAO,EAAE;EACxE,CAAsC;CACxC;CAKA,MAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS;CACrD,MAAM,QAAQ,OAAO,QAAQ,eAAe,aAAa;CACzD,MAAM,aAAa,UAAU;CAC7B,MAAM,UAAU,6BAA6B;EAAE,GAAG;EAAQ,KAAK;CAAM,CAAC;CAKtE,OAAO,YAAY;EACjB,GAAG;EACH;EAIA,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,QAAQ,uBAAuB,OAAO,EAAE;CACxE,CAAsC;AACxC"}
|
|
@@ -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;AAkB5D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAKxE,OAAO,KAAK,EACV,kBAAkB,EAElB,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;IAkFjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAgBlE;;;;;;;;OAQG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAU/C;;OAEG;IACG,iBAAiB,CACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkBlC;;OAEG;IACH,OAAO,CAAC,cAAc;CAqCvB;AAiCD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,
|
|
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;AAkB5D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAKxE,OAAO,KAAK,EACV,kBAAkB,EAElB,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;IAkFjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAgBlE;;;;;;;;OAQG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAU/C;;OAEG;IACG,iBAAiB,CACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkBlC;;OAEG;IACH,OAAO,CAAC,cAAc;CAqCvB;AAiCD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,CAmDtB"}
|
package/dist/dispatch/bus.js
CHANGED
|
@@ -547,10 +547,7 @@ async function createDispatchBus(options = {}) {
|
|
|
547
547
|
let db;
|
|
548
548
|
if (typeof dbConfig === "string") db = await withStartupRetry(() => getDatabase(applyPostgresRuntimeTimeouts({ url: dbConfig })), options.startupRetry);
|
|
549
549
|
else if ("query" in dbConfig) db = dbConfig;
|
|
550
|
-
else if ("type" in dbConfig && "url" in dbConfig) db = await withStartupRetry(() => getDatabase(applyPostgresRuntimeTimeouts({
|
|
551
|
-
type: dbConfig.type,
|
|
552
|
-
url: dbConfig.url
|
|
553
|
-
})), options.startupRetry);
|
|
550
|
+
else if ("type" in dbConfig && "url" in dbConfig) db = await withStartupRetry(() => getDatabase(applyPostgresRuntimeTimeouts({ ...dbConfig })), options.startupRetry);
|
|
554
551
|
else throw new Error("Invalid database configuration for DispatchBus");
|
|
555
552
|
const bus = new DispatchBus(db);
|
|
556
553
|
await withStartupRetry(() => bus.initialize(), options.startupRetry);
|
package/dist/dispatch/bus.js.map
CHANGED
|
@@ -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 { isTransientConnectionError } from '../db-errors.js';\nimport { getDatabase } from '../host.js';\nimport { applyPostgresRuntimeTimeouts } from '../postgres-timeouts.js';\nimport { runSerializedAgainstSystemTableBootstrap } from '../system/bootstrap.js';\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 DispatchBusStartupRetry,\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 // `_smrt_dispatch`/`_smrt_dispatch_subscriptions` are framework-owned\n // system tables (part of `ALL_SYSTEM_TABLES`), but this standalone\n // initializer provisions/verifies them independently of a full\n // `ensureSystemTables()` bootstrap. Without serialization, concurrent\n // `DispatchBus` instances against the same already-warmed PostgreSQL\n // database (e.g. multiple `Suasor`/`Agent` instantiations, or concurrent\n // test files sharing one database) race on unguarded DDL — proven to\n // throw `duplicate key value violates unique constraint\n // \"pg_type_typname_nsp_index\"` on concurrent `CREATE TABLE IF NOT\n // EXISTS` (#2861). Fresh DDL always creates timezone-aware columns (see\n // `getSystemTableDDLForEngine()`), so this race does not itself produce a\n // legacy-typed column; a stale `assertPostgresSystemTimestampsCurrent()`\n // guard requires a *separate* concurrent actor to have put some\n // `_smrt_*` table into a legacy-typed state (e.g. a test deliberately\n // simulating a pre-migration database), which this lock does not by\n // itself rule out — see the issue for that residual risk. Running this\n // under the same advisory lock `ensureSystemTables()` uses makes every\n // writer of these tables mutually exclusive instead of merely\n // idempotent, which is what the proven race requires.\n //\n // Resolve the engine from `this.db` — the caller-supplied handle — once,\n // up front. `runSerializedAgainstSystemTableBootstrap()` hands the\n // callback a *different* handle on PostgreSQL (the lock-holding\n // transaction it opens), and that handle is not guaranteed to carry the\n // `url`/`type` (or `config.url`/`config.type`) fields engine detection\n // reads (see the `db.config?.url`-style adapters `getDatabaseEngine()`\n // already supports elsewhere in this package). Recomputing the engine\n // on the transaction handle can therefore silently disagree with the\n // outer gate that just decided `postgres` — `detectEngine('', undefined)`\n // falls back to `sqlite`, which would create the dispatch tables with\n // `TIMESTAMP` instead of `TIMESTAMPTZ` and skip the PostgreSQL timestamp\n // guard entirely. Threading the one resolved `engine` through — as\n // `ensureSystemTables()`/`bootstrapSystemTables()` already thread\n // `typeHint` — keeps the inner detection bound to the outer decision.\n const engine = getDatabaseEngine(this.db);\n await runSerializedAgainstSystemTableBootstrap(\n this.db,\n engine,\n async (tx) => {\n // Create dispatch tables if they don't exist\n const dispatchExists = await DispatchCollection.tableExists(tx, engine);\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 tx.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(tx, engine);\n await ensureDispatchSystemTableCompatibility(tx, engine);\n\n const subsExists = await DispatchSubscriptionCollection.tableExists(\n tx,\n engine,\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 tx.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(tx, engine);\n await ensureDispatchSubscriptionsSystemTableCompatibility(tx, engine);\n },\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 only. Global (NULL-tenant)\n * dispatches are system-only on reads (#3301): a global row's payload (for\n * example a delivery emitted outside tenant context) may carry another\n * tenant's recipients/subjects. Processing (`process`) still lets a tenant\n * worker claim global work.\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 // `includeGlobal: false` — global (NULL-tenant) rows are system-only on\n // this inspection read (#3301).\n return DispatchCollection.list(\n this.db,\n {\n ...safeOptions,\n tenantScope: resolveDispatchTenantScope(),\n },\n false,\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, or a global\n * (NULL-tenant) dispatch, by id (#3301), and when tenancy is on with no\n * active tenant only global (NULL) dispatches are returned. Returns `null`\n * 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 false,\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 * Run one startup step, retrying it with exponential backoff while it fails\n * with a transient connection error (#3657). Anything else, or the last\n * attempt's failure, is thrown as is.\n */\nasync function withStartupRetry<T>(\n step: () => Promise<T>,\n retry: DispatchBusStartupRetry | false | undefined,\n): Promise<T> {\n const attempts =\n retry === false ? 1 : Math.max(1, Math.floor(retry?.attempts ?? 5));\n const maxDelayMs = retry ? (retry.maxDelayMs ?? 5000) : 5000;\n let delayMs = retry ? (retry.initialDelayMs ?? 500) : 500;\n for (let attempt = 1; ; attempt++) {\n try {\n return await step();\n } catch (error) {\n if (attempt >= attempts || !isTransientConnectionError(error)) {\n throw error;\n }\n const wait = Math.min(delayMs, maxDelayMs);\n logger.warn(\n `DispatchBus: transient connection failure during startup (attempt ${attempt} of ${attempts}); retrying in ${wait} ms`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n await new Promise((resolve) => setTimeout(resolve, wait));\n delayMs *= 2;\n }\n }\n}\n\n/**\n * Create a DispatchBus instance\n *\n * Opening the database and `initialize()` are retried on a transient\n * connection failure; see {@link DispatchBusOptions.startupRetry}.\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 // `DispatchBusOptions.db` does not admit a bare string, but this branch is\n // live code and must not be the one unbounded pool a reader finds while\n // auditing this file (#2377).\n db = await withStartupRetry(\n () =>\n getDatabase(\n applyPostgresRuntimeTimeouts({\n url: dbConfig,\n }) as Parameters<typeof getDatabase>[0],\n ),\n options.startupRetry,\n );\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 // Bounded like every other runtime pool so a dispatch-bus connection is\n // not the one unbounded PostgreSQL client in the process (#2377).\n db = await withStartupRetry(\n () =>\n getDatabase(\n applyPostgresRuntimeTimeouts({\n type: dbConfig.type as 'sqlite' | 'postgres' | 'duckdb',\n url: dbConfig.url,\n }) as Parameters<typeof getDatabase>[0],\n ),\n options.startupRetry,\n );\n } else {\n throw new Error('Invalid database configuration for DispatchBus');\n }\n\n const bus = new DispatchBus(db);\n // initialize() sets `initialized` only on success and its DDL is idempotent\n // and serialized, so a retry after a dropped connection starts it over.\n await withStartupRetry(() => bus.initialize(), options.startupRetry);\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 { isTransientConnectionError } from '../db-errors.js';\nimport { getDatabase } from '../host.js';\nimport { applyPostgresRuntimeTimeouts } from '../postgres-timeouts.js';\nimport { runSerializedAgainstSystemTableBootstrap } from '../system/bootstrap.js';\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 DispatchBusStartupRetry,\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 // `_smrt_dispatch`/`_smrt_dispatch_subscriptions` are framework-owned\n // system tables (part of `ALL_SYSTEM_TABLES`), but this standalone\n // initializer provisions/verifies them independently of a full\n // `ensureSystemTables()` bootstrap. Without serialization, concurrent\n // `DispatchBus` instances against the same already-warmed PostgreSQL\n // database (e.g. multiple `Suasor`/`Agent` instantiations, or concurrent\n // test files sharing one database) race on unguarded DDL — proven to\n // throw `duplicate key value violates unique constraint\n // \"pg_type_typname_nsp_index\"` on concurrent `CREATE TABLE IF NOT\n // EXISTS` (#2861). Fresh DDL always creates timezone-aware columns (see\n // `getSystemTableDDLForEngine()`), so this race does not itself produce a\n // legacy-typed column; a stale `assertPostgresSystemTimestampsCurrent()`\n // guard requires a *separate* concurrent actor to have put some\n // `_smrt_*` table into a legacy-typed state (e.g. a test deliberately\n // simulating a pre-migration database), which this lock does not by\n // itself rule out — see the issue for that residual risk. Running this\n // under the same advisory lock `ensureSystemTables()` uses makes every\n // writer of these tables mutually exclusive instead of merely\n // idempotent, which is what the proven race requires.\n //\n // Resolve the engine from `this.db` — the caller-supplied handle — once,\n // up front. `runSerializedAgainstSystemTableBootstrap()` hands the\n // callback a *different* handle on PostgreSQL (the lock-holding\n // transaction it opens), and that handle is not guaranteed to carry the\n // `url`/`type` (or `config.url`/`config.type`) fields engine detection\n // reads (see the `db.config?.url`-style adapters `getDatabaseEngine()`\n // already supports elsewhere in this package). Recomputing the engine\n // on the transaction handle can therefore silently disagree with the\n // outer gate that just decided `postgres` — `detectEngine('', undefined)`\n // falls back to `sqlite`, which would create the dispatch tables with\n // `TIMESTAMP` instead of `TIMESTAMPTZ` and skip the PostgreSQL timestamp\n // guard entirely. Threading the one resolved `engine` through — as\n // `ensureSystemTables()`/`bootstrapSystemTables()` already thread\n // `typeHint` — keeps the inner detection bound to the outer decision.\n const engine = getDatabaseEngine(this.db);\n await runSerializedAgainstSystemTableBootstrap(\n this.db,\n engine,\n async (tx) => {\n // Create dispatch tables if they don't exist\n const dispatchExists = await DispatchCollection.tableExists(tx, engine);\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 tx.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(tx, engine);\n await ensureDispatchSystemTableCompatibility(tx, engine);\n\n const subsExists = await DispatchSubscriptionCollection.tableExists(\n tx,\n engine,\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 tx.query(stmt);\n }\n }\n await assertPostgresSystemTimestampsCurrent(tx, engine);\n await ensureDispatchSubscriptionsSystemTableCompatibility(tx, engine);\n },\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 only. Global (NULL-tenant)\n * dispatches are system-only on reads (#3301): a global row's payload (for\n * example a delivery emitted outside tenant context) may carry another\n * tenant's recipients/subjects. Processing (`process`) still lets a tenant\n * worker claim global work.\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 // `includeGlobal: false` — global (NULL-tenant) rows are system-only on\n // this inspection read (#3301).\n return DispatchCollection.list(\n this.db,\n {\n ...safeOptions,\n tenantScope: resolveDispatchTenantScope(),\n },\n false,\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, or a global\n * (NULL-tenant) dispatch, by id (#3301), and when tenancy is on with no\n * active tenant only global (NULL) dispatches are returned. Returns `null`\n * 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 false,\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 * Run one startup step, retrying it with exponential backoff while it fails\n * with a transient connection error (#3657). Anything else, or the last\n * attempt's failure, is thrown as is.\n */\nasync function withStartupRetry<T>(\n step: () => Promise<T>,\n retry: DispatchBusStartupRetry | false | undefined,\n): Promise<T> {\n const attempts =\n retry === false ? 1 : Math.max(1, Math.floor(retry?.attempts ?? 5));\n const maxDelayMs = retry ? (retry.maxDelayMs ?? 5000) : 5000;\n let delayMs = retry ? (retry.initialDelayMs ?? 500) : 500;\n for (let attempt = 1; ; attempt++) {\n try {\n return await step();\n } catch (error) {\n if (attempt >= attempts || !isTransientConnectionError(error)) {\n throw error;\n }\n const wait = Math.min(delayMs, maxDelayMs);\n logger.warn(\n `DispatchBus: transient connection failure during startup (attempt ${attempt} of ${attempts}); retrying in ${wait} ms`,\n { error: error instanceof Error ? error.message : String(error) },\n );\n await new Promise((resolve) => setTimeout(resolve, wait));\n delayMs *= 2;\n }\n }\n}\n\n/**\n * Create a DispatchBus instance\n *\n * Opening the database and `initialize()` are retried on a transient\n * connection failure; see {@link DispatchBusOptions.startupRetry}.\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 // `DispatchBusOptions.db` does not admit a bare string, but this branch is\n // live code and must not be the one unbounded pool a reader finds while\n // auditing this file (#2377).\n db = await withStartupRetry(\n () =>\n getDatabase(\n applyPostgresRuntimeTimeouts({\n url: dbConfig,\n }) as Parameters<typeof getDatabase>[0],\n ),\n options.startupRetry,\n );\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. Bounded like every other runtime pool so a\n // dispatch-bus connection is not the one unbounded PostgreSQL client in\n // the process (#2377), and forwarded whole: rebuilding `{ type, url }`\n // dropped `max`, the pool timeouts, and any `dbid`, so a bus built from\n // the application's config opened its own default-sized pool (#3046).\n db = await withStartupRetry(\n () =>\n getDatabase(\n applyPostgresRuntimeTimeouts({\n ...dbConfig,\n }) as Parameters<typeof getDatabase>[0],\n ),\n options.startupRetry,\n );\n } else {\n throw new Error('Invalid database configuration for DispatchBus');\n }\n\n const bus = new DispatchBus(db);\n // initialize() sets `initialized` only on success and its DDL is idempotent\n // and serialized, so a retry after a dropped connection starts it over.\n await withStartupRetry(() => bus.initialize(), options.startupRetry);\n\n return bus;\n}\n"],"mappings":"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"}
|
package/dist/dispatch/index.d.ts
CHANGED
|
@@ -43,5 +43,5 @@ export { DispatchSubscriptionCollection } from './collections/DispatchSubscripti
|
|
|
43
43
|
export { Dispatch, type DispatchData } from './models/Dispatch.js';
|
|
44
44
|
export { DispatchSubscription, type DispatchSubscriptionData, } from './models/DispatchSubscription.js';
|
|
45
45
|
export { type DispatchTenantResolver, type DispatchTenantScope, isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver, } from './tenant-resolver.js';
|
|
46
|
-
export type { DispatchBusOptions, DispatchBusStartupRetry, DispatchCleanupOptions, DispatchCleanupResult, DispatchEmitOptions, DispatchHandler, DispatchListOptions, DispatchMetadata, DispatchProcessOptions, DispatchRetryOptions, DispatchStatus, DispatchSubscribeOptions, } from './types.js';
|
|
46
|
+
export type { DispatchBusOptions, DispatchBusPoolOptions, DispatchBusStartupRetry, DispatchCleanupOptions, DispatchCleanupResult, DispatchEmitOptions, DispatchHandler, DispatchListOptions, DispatchMetadata, DispatchProcessOptions, DispatchRetryOptions, DispatchStatus, DispatchSubscribeOptions, } from './types.js';
|
|
47
47
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dispatch/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAExF,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dispatch/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAExF,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAG9B,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/dist/dispatch/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides inter-agent communication through persistent dispatch messages.
|
|
5
5
|
*/
|
|
6
6
|
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
7
|
+
import type { PostgresTimeoutConfig } from '../postgres-timeouts.js';
|
|
7
8
|
import type { DispatchTenantScope } from './tenant-resolver.js';
|
|
8
9
|
/**
|
|
9
10
|
* Dispatch message status
|
|
@@ -145,6 +146,24 @@ export interface DispatchCleanupResult {
|
|
|
145
146
|
/** Number of failed dispatches deleted */
|
|
146
147
|
failedDeleted: number;
|
|
147
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Pool and connection options a {@link DispatchBusOptions} database config may
|
|
151
|
+
* carry. They are forwarded to `getDatabase()` exactly as given, so a bus
|
|
152
|
+
* built from a config honors the same pool cap and timeouts as the rest of the
|
|
153
|
+
* application instead of opening its own default-sized pool (#3046).
|
|
154
|
+
*/
|
|
155
|
+
export interface DispatchBusPoolOptions {
|
|
156
|
+
/** Caller-owned pool cache identity, forwarded as given. */
|
|
157
|
+
dbid?: string;
|
|
158
|
+
/** Maximum PostgreSQL pool clients. */
|
|
159
|
+
max?: number;
|
|
160
|
+
/** PostgreSQL pool acquisition timeout, in milliseconds. */
|
|
161
|
+
connectionTimeoutMillis?: number;
|
|
162
|
+
/** PostgreSQL idle-client timeout, in milliseconds. */
|
|
163
|
+
idleTimeoutMillis?: number;
|
|
164
|
+
/** Runtime PostgreSQL timeouts (#2377). */
|
|
165
|
+
timeouts?: PostgresTimeoutConfig;
|
|
166
|
+
}
|
|
148
167
|
/**
|
|
149
168
|
* Options for creating a DispatchBus
|
|
150
169
|
*/
|
|
@@ -165,15 +184,15 @@ export interface DispatchBusOptions {
|
|
|
165
184
|
* DuckDB, and JSON adapters are unaffected — only the PostgreSQL path
|
|
166
185
|
* needs the lock.
|
|
167
186
|
*/
|
|
168
|
-
db?: DatabaseInterface | {
|
|
187
|
+
db?: DatabaseInterface | ({
|
|
169
188
|
type: 'sqlite' | 'postgres' | 'json' | 'duckdb';
|
|
170
189
|
url: string;
|
|
171
|
-
};
|
|
190
|
+
} & DispatchBusPoolOptions);
|
|
172
191
|
/** Alias for db - persistence layer configuration */
|
|
173
192
|
persistence?: {
|
|
174
193
|
type: 'sql' | 'json' | 'duckdb';
|
|
175
194
|
url: string;
|
|
176
|
-
};
|
|
195
|
+
} & DispatchBusPoolOptions;
|
|
177
196
|
/**
|
|
178
197
|
* Bounded retry of the startup bootstrap (opening the database and
|
|
179
198
|
* `initialize()`) on a transient connection failure, such as PostgreSQL or
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dispatch/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,SAAS,EAAE,IAAI,CAAC;IAChB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uBAAuB;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;OAeG;IACH,EAAE,CAAC,EACC,iBAAiB,GACjB;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dispatch/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changes-route.d.ts","sourceRoot":"","sources":["../../src/generators/changes-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"changes-route.d.ts","sourceRoot":"","sources":["../../src/generators/changes-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAW5D;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,KACX,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,gFAAgF;IAChF,EAAE,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,aAAa,CAAC;AAQpD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAwC5B;AAaD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAuEnB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureChangeFeedTable } from "../change-feed.js";
|
|
2
|
-
import { applyPostgresRuntimeTimeouts } from "../postgres-timeouts.js";
|
|
2
|
+
import { applyPostgresRuntimeTimeouts, resolveRuntimePoolDbid } from "../postgres-timeouts.js";
|
|
3
3
|
import { getAuthorizedTenantScopedChangesSince } from "../change-feed-authz.js";
|
|
4
4
|
import { createLogger } from "@happyvertical/logger";
|
|
5
5
|
import { getDatabase } from "@happyvertical/sql";
|
|
@@ -71,7 +71,7 @@ function resolveChangesDb(dbOption) {
|
|
|
71
71
|
const bounded = applyPostgresRuntimeTimeouts({ url: dbOption });
|
|
72
72
|
resolved = getDatabase({
|
|
73
73
|
...bounded,
|
|
74
|
-
...isMemoryDb ? {} : { dbid:
|
|
74
|
+
...isMemoryDb ? {} : { dbid: resolveRuntimePoolDbid(bounded) }
|
|
75
75
|
});
|
|
76
76
|
resolvedUrlDbs.set(dbOption, resolved);
|
|
77
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changes-route.js","names":[],"sources":["../../src/generators/changes-route.ts"],"sourcesContent":["/**\n * Generated `_changes` HTTP route for the change feed (issue #1758).\n *\n * Handles `GET {basePath}/_changes` in the REST generator: an auth-guarded,\n * tenant-scoped cursor read over the `_smrt_changes` log. This module keeps\n * the feed logic out of `rest.ts` (which only registers the path) so\n * sibling generator changes stay conflict-free.\n *\n * Contract (part of the client/mobile sync contract, PRD #1755):\n * - `GET {basePath}/_changes?since=<cursor>&tables=<a,b>&limit=<n>` returns\n * `{ changes, cursor, resyncRequired?, resyncCursor? }` — see\n * `getChangesSince` for the exact cursor guarantee (strictly monotonic;\n * reads miss no committed changes under concurrent writers).\n * `resyncRequired: true` — served as HTTP 200, it is protocol state rather\n * than an error — means the cursor cannot be served incrementally (pruned\n * out of the retained window, or foreign/reset) and the client must\n * re-fetch its data in full, then resume from `resyncCursor`.\n * - **Auth is fail-closed** (#1540 posture): the route requires the\n * generator's `authMiddleware`. Without one configured, every request is\n * refused with 401 — the feed spans all tables, so per-model\n * `api: { public }` opt-outs deliberately do NOT apply to it.\n * - **Tenant scoping** follows the active tenant context through the same\n * dependency-inversion hook the DispatchBus uses: with tenancy enabled, a\n * request only ever sees its own tenant's changes plus global rows; with\n * tenancy enabled but no active tenant, only global rows (fail-closed).\n * - **Table/row authorization** (#3020): the read additionally applies the\n * consumer-supplied `authorizeChangeFeed`/`isChangeFeedEntryVisible` hooks,\n * if registered (`change-feed-authz.ts`), via\n * `getAuthorizedTenantScopedChangesSince()`. REST has no `locals`, so the\n * hooks receive `locals: undefined` and the `authMiddleware`-processed\n * `Request` as `request` — a REST-hosting consumer identifies the principal\n * from `request`. Unregistered, behavior is unchanged from pre-#3020.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensureChangeFeedTable } from '../change-feed.js';\nimport { getAuthorizedTenantScopedChangesSince } from '../change-feed-authz.js';\nimport { applyPostgresRuntimeTimeouts } from '../postgres-timeouts.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Structural copy of `APIConfig['authMiddleware']` (from `rest.ts`) so this\n * module never imports the generator back (keeps the dependency one-way).\n *\n * Exported so the sibling `_events` route (#1763) reuses the exact same\n * fail-closed auth contract without redeclaring it.\n */\nexport type ChangesAuthMiddleware = (\n objectName: string,\n action: string,\n) => (req: Request) => Promise<Request | Response>;\n\nexport interface ChangesRouteOptions {\n /** The generator's configured auth middleware, if any. */\n authMiddleware?: ChangesAuthMiddleware;\n /** The generator's `APIContext.db` (instance, config object, or URL string). */\n db?: unknown;\n}\n\n/**\n * Pseudo object name passed to the auth middleware for the feed route, so\n * middlewares can recognize and specially authorize it if they want to.\n */\nexport const CHANGES_ROUTE_OBJECT_NAME = '_changes';\n\n// Resolve the APIContext db option once per distinct option value so every\n// request reads the same underlying database (getDatabase() would otherwise\n// mint a fresh instance per call for config objects / `:memory:` URLs).\nconst resolvedInstanceDbs = new WeakMap<object, Promise<DatabaseInterface>>();\nconst resolvedUrlDbs = new Map<string, Promise<DatabaseInterface>>();\n\n/**\n * Resolve the generator's `db` option (instance, config object, or URL string)\n * to a single shared {@link DatabaseInterface} per distinct option value.\n *\n * Exported so the sibling `_events` route (#1763) resolves its database\n * identically — sharing this WeakMap/Map means both routes read the same\n * underlying handle for a given option, which matters for `:memory:` databases\n * where a fresh `getDatabase()` per call would mint a separate database.\n */\nexport function resolveChangesDb(\n dbOption: unknown,\n): Promise<DatabaseInterface> {\n if (dbOption && typeof dbOption === 'object') {\n if (\n 'query' in dbOption &&\n typeof (dbOption as { query?: unknown }).query === 'function'\n ) {\n return Promise.resolve(dbOption as DatabaseInterface);\n }\n let resolved = resolvedInstanceDbs.get(dbOption);\n if (!resolved) {\n resolved = getDatabase(\n applyPostgresRuntimeTimeouts({\n ...(dbOption as Record<string, unknown>),\n }) as Parameters<typeof getDatabase>[0],\n ) as Promise<DatabaseInterface>;\n resolvedInstanceDbs.set(dbOption, resolved);\n }\n return resolved;\n }\n\n if (typeof dbOption === 'string' && dbOption) {\n let resolved = resolvedUrlDbs.get(dbOption);\n if (!resolved) {\n // Match SmrtClass's connection-sharing convention for file-backed URLs,\n // including the runtime PostgreSQL timeout bounds and the dbid derived\n // from the bounded URL (#2377).\n const isMemoryDb = dbOption === ':memory:';\n const bounded = applyPostgresRuntimeTimeouts({ url: dbOption });\n resolved = getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url}` }),\n } as Parameters<typeof getDatabase>[0]) as Promise<DatabaseInterface>;\n resolvedUrlDbs.set(dbOption, resolved);\n }\n return resolved;\n }\n\n return Promise.reject(\n new Error('The _changes route requires a database in APIContext'),\n );\n}\n\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction errorResponse(status: number, message: string): Response {\n return jsonResponse({ error: message }, status);\n}\n\n/**\n * Handle a request against the generated `_changes` route.\n *\n * Returns 401 when no auth middleware is configured (fail-closed) or the\n * middleware rejects; 405 for non-GET methods; 400 for malformed `since`/\n * `limit`; 503 when the generator has no database; otherwise a\n * `{ changes, cursor }` page scoped to the active tenant context.\n */\nexport async function handleChangesRoute(\n req: Request,\n options: ChangesRouteOptions,\n): Promise<Response> {\n if (req.method !== 'GET') {\n return errorResponse(405, 'Method not allowed');\n }\n\n // Fail-closed (#1540): the change feed spans every table, so it is never\n // public — an auth middleware must be configured and must pass. The action\n // is the lowercased HTTP method, matching the generator's other call sites.\n if (!options.authMiddleware) {\n return errorResponse(401, 'Authentication required');\n }\n const authCheck = options.authMiddleware(\n CHANGES_ROUTE_OBJECT_NAME,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult;\n }\n\n if (options.db == null) {\n return errorResponse(\n 503,\n 'Change feed unavailable: no database configured for the API generator',\n );\n }\n\n const url = new URL(authResult.url);\n const since = Number(url.searchParams.get('since') ?? '0');\n if (!Number.isFinite(since) || since < 0) {\n return errorResponse(400, \"'since' must be a non-negative number\");\n }\n\n let limit: number | undefined;\n const limitParam = url.searchParams.get('limit');\n if (limitParam !== null) {\n limit = Number(limitParam);\n if (!Number.isFinite(limit) || limit < 1) {\n return errorResponse(400, \"'limit' must be a positive number\");\n }\n }\n\n const tablesParam = url.searchParams.get('tables');\n const tables = tablesParam\n ? tablesParam\n .split(',')\n .map((table) => table.trim())\n .filter(Boolean)\n : undefined;\n\n try {\n const db = await resolveChangesDb(options.db);\n // System tables are runtime-ensured; a raw handle passed straight to the\n // generator may not have gone through framework init yet.\n await ensureChangeFeedTable(db);\n const page = await getAuthorizedTenantScopedChangesSince(db, {\n since,\n tables,\n limit,\n // REST has no `locals`; the authorization hooks (#3020) identify the\n // principal from the authMiddleware-processed request instead.\n locals: undefined,\n request: authResult,\n });\n return jsonResponse(page);\n } catch (error) {\n logger.error('Change feed route failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n return errorResponse(500, 'Internal server error');\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;AAyB7C,IAAa,4BAA4B;AAKzC,IAAM,sCAAsB,IAAI,QAA4C;AAC5E,IAAM,iCAAiB,IAAI,IAAwC;;;;;;;;;;AAWnE,SAAgB,iBACd,UAC4B;CAC5B,IAAI,YAAY,OAAO,aAAa,UAAU;EAC5C,IACE,WAAW,YACX,OAAQ,SAAiC,UAAU,YAEnD,OAAO,QAAQ,QAAQ,QAA6B;EAEtD,IAAI,WAAW,oBAAoB,IAAI,QAAQ;EAC/C,IAAI,CAAC,UAAU;GACb,WAAW,YACT,6BAA6B,EAC3B,GAAI,SACN,CAAC,CACH;GACA,oBAAoB,IAAI,UAAU,QAAQ;EAC5C;EACA,OAAO;CACT;CAEA,IAAI,OAAO,aAAa,YAAY,UAAU;EAC5C,IAAI,WAAW,eAAe,IAAI,QAAQ;EAC1C,IAAI,CAAC,UAAU;GAIb,MAAM,aAAa,aAAa;GAChC,MAAM,UAAU,6BAA6B,EAAE,KAAK,SAAS,CAAC;GAC9D,WAAW,YAAY;IACrB,GAAG;IACH,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAQ,MAAM;GACtD,CAAsC;GACtC,eAAe,IAAI,UAAU,QAAQ;EACvC;EACA,OAAO;CACT;CAEA,OAAO,QAAQ,uBACb,IAAI,MAAM,sDAAsD,CAClE;AACF;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;CAC3D,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH;AAEA,SAAS,cAAc,QAAgB,SAA2B;CAChE,OAAO,aAAa,EAAE,OAAO,QAAQ,GAAG,MAAM;AAChD;;;;;;;;;AAUA,eAAsB,mBACpB,KACA,SACmB;CACnB,IAAI,IAAI,WAAW,OACjB,OAAO,cAAc,KAAK,oBAAoB;CAMhD,IAAI,CAAC,QAAQ,gBACX,OAAO,cAAc,KAAK,yBAAyB;CAMrD,MAAM,aAAa,MAJD,QAAQ,eACxB,2BACA,IAAI,OAAO,YAAY,CAEA,CAAA,CAAU,GAAG;CACtC,IAAI,sBAAsB,UACxB,OAAO;CAGT,IAAI,QAAQ,MAAM,MAChB,OAAO,cACL,KACA,uEACF;CAGF,MAAM,MAAM,IAAI,IAAI,WAAW,GAAG;CAClC,MAAM,QAAQ,OAAO,IAAI,aAAa,IAAI,OAAO,KAAK,GAAG;CACzD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,OAAO,cAAc,KAAK,uCAAuC;CAGnE,IAAI;CACJ,MAAM,aAAa,IAAI,aAAa,IAAI,OAAO;CAC/C,IAAI,eAAe,MAAM;EACvB,QAAQ,OAAO,UAAU;EACzB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,OAAO,cAAc,KAAK,mCAAmC;CAEjE;CAEA,MAAM,cAAc,IAAI,aAAa,IAAI,QAAQ;CACjD,MAAM,SAAS,cACX,YACG,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,IACjB,KAAA;CAEJ,IAAI;EACF,MAAM,KAAK,MAAM,iBAAiB,QAAQ,EAAE;EAG5C,MAAM,sBAAsB,EAAE;EAU9B,OAAO,aAAa,MATD,sCAAsC,IAAI;GAC3D;GACA;GACA;GAGA,QAAQ,KAAA;GACR,SAAS;EACX,CAAC,CACuB;CAC1B,SAAS,OAAO;EACd,OAAO,MAAM,4BAA4B,EACvC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;EACD,OAAO,cAAc,KAAK,uBAAuB;CACnD;AACF"}
|
|
1
|
+
{"version":3,"file":"changes-route.js","names":[],"sources":["../../src/generators/changes-route.ts"],"sourcesContent":["/**\n * Generated `_changes` HTTP route for the change feed (issue #1758).\n *\n * Handles `GET {basePath}/_changes` in the REST generator: an auth-guarded,\n * tenant-scoped cursor read over the `_smrt_changes` log. This module keeps\n * the feed logic out of `rest.ts` (which only registers the path) so\n * sibling generator changes stay conflict-free.\n *\n * Contract (part of the client/mobile sync contract, PRD #1755):\n * - `GET {basePath}/_changes?since=<cursor>&tables=<a,b>&limit=<n>` returns\n * `{ changes, cursor, resyncRequired?, resyncCursor? }` — see\n * `getChangesSince` for the exact cursor guarantee (strictly monotonic;\n * reads miss no committed changes under concurrent writers).\n * `resyncRequired: true` — served as HTTP 200, it is protocol state rather\n * than an error — means the cursor cannot be served incrementally (pruned\n * out of the retained window, or foreign/reset) and the client must\n * re-fetch its data in full, then resume from `resyncCursor`.\n * - **Auth is fail-closed** (#1540 posture): the route requires the\n * generator's `authMiddleware`. Without one configured, every request is\n * refused with 401 — the feed spans all tables, so per-model\n * `api: { public }` opt-outs deliberately do NOT apply to it.\n * - **Tenant scoping** follows the active tenant context through the same\n * dependency-inversion hook the DispatchBus uses: with tenancy enabled, a\n * request only ever sees its own tenant's changes plus global rows; with\n * tenancy enabled but no active tenant, only global rows (fail-closed).\n * - **Table/row authorization** (#3020): the read additionally applies the\n * consumer-supplied `authorizeChangeFeed`/`isChangeFeedEntryVisible` hooks,\n * if registered (`change-feed-authz.ts`), via\n * `getAuthorizedTenantScopedChangesSince()`. REST has no `locals`, so the\n * hooks receive `locals: undefined` and the `authMiddleware`-processed\n * `Request` as `request` — a REST-hosting consumer identifies the principal\n * from `request`. Unregistered, behavior is unchanged from pre-#3020.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensureChangeFeedTable } from '../change-feed.js';\nimport { getAuthorizedTenantScopedChangesSince } from '../change-feed-authz.js';\nimport {\n applyPostgresRuntimeTimeouts,\n resolveRuntimePoolDbid,\n} from '../postgres-timeouts.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Structural copy of `APIConfig['authMiddleware']` (from `rest.ts`) so this\n * module never imports the generator back (keeps the dependency one-way).\n *\n * Exported so the sibling `_events` route (#1763) reuses the exact same\n * fail-closed auth contract without redeclaring it.\n */\nexport type ChangesAuthMiddleware = (\n objectName: string,\n action: string,\n) => (req: Request) => Promise<Request | Response>;\n\nexport interface ChangesRouteOptions {\n /** The generator's configured auth middleware, if any. */\n authMiddleware?: ChangesAuthMiddleware;\n /** The generator's `APIContext.db` (instance, config object, or URL string). */\n db?: unknown;\n}\n\n/**\n * Pseudo object name passed to the auth middleware for the feed route, so\n * middlewares can recognize and specially authorize it if they want to.\n */\nexport const CHANGES_ROUTE_OBJECT_NAME = '_changes';\n\n// Resolve the APIContext db option once per distinct option value so every\n// request reads the same underlying database (getDatabase() would otherwise\n// mint a fresh instance per call for config objects / `:memory:` URLs).\nconst resolvedInstanceDbs = new WeakMap<object, Promise<DatabaseInterface>>();\nconst resolvedUrlDbs = new Map<string, Promise<DatabaseInterface>>();\n\n/**\n * Resolve the generator's `db` option (instance, config object, or URL string)\n * to a single shared {@link DatabaseInterface} per distinct option value.\n *\n * Exported so the sibling `_events` route (#1763) resolves its database\n * identically — sharing this WeakMap/Map means both routes read the same\n * underlying handle for a given option, which matters for `:memory:` databases\n * where a fresh `getDatabase()` per call would mint a separate database.\n */\nexport function resolveChangesDb(\n dbOption: unknown,\n): Promise<DatabaseInterface> {\n if (dbOption && typeof dbOption === 'object') {\n if (\n 'query' in dbOption &&\n typeof (dbOption as { query?: unknown }).query === 'function'\n ) {\n return Promise.resolve(dbOption as DatabaseInterface);\n }\n let resolved = resolvedInstanceDbs.get(dbOption);\n if (!resolved) {\n resolved = getDatabase(\n applyPostgresRuntimeTimeouts({\n ...(dbOption as Record<string, unknown>),\n }) as Parameters<typeof getDatabase>[0],\n ) as Promise<DatabaseInterface>;\n resolvedInstanceDbs.set(dbOption, resolved);\n }\n return resolved;\n }\n\n if (typeof dbOption === 'string' && dbOption) {\n let resolved = resolvedUrlDbs.get(dbOption);\n if (!resolved) {\n // Match SmrtClass's connection-sharing convention for file-backed URLs,\n // including the runtime PostgreSQL timeout bounds (#2377) and the shared\n // pool-identity rule (#3046).\n const isMemoryDb = dbOption === ':memory:';\n const bounded = applyPostgresRuntimeTimeouts({ url: dbOption });\n resolved = getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: resolveRuntimePoolDbid(bounded) }),\n } as Parameters<typeof getDatabase>[0]) as Promise<DatabaseInterface>;\n resolvedUrlDbs.set(dbOption, resolved);\n }\n return resolved;\n }\n\n return Promise.reject(\n new Error('The _changes route requires a database in APIContext'),\n );\n}\n\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nfunction errorResponse(status: number, message: string): Response {\n return jsonResponse({ error: message }, status);\n}\n\n/**\n * Handle a request against the generated `_changes` route.\n *\n * Returns 401 when no auth middleware is configured (fail-closed) or the\n * middleware rejects; 405 for non-GET methods; 400 for malformed `since`/\n * `limit`; 503 when the generator has no database; otherwise a\n * `{ changes, cursor }` page scoped to the active tenant context.\n */\nexport async function handleChangesRoute(\n req: Request,\n options: ChangesRouteOptions,\n): Promise<Response> {\n if (req.method !== 'GET') {\n return errorResponse(405, 'Method not allowed');\n }\n\n // Fail-closed (#1540): the change feed spans every table, so it is never\n // public — an auth middleware must be configured and must pass. The action\n // is the lowercased HTTP method, matching the generator's other call sites.\n if (!options.authMiddleware) {\n return errorResponse(401, 'Authentication required');\n }\n const authCheck = options.authMiddleware(\n CHANGES_ROUTE_OBJECT_NAME,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult;\n }\n\n if (options.db == null) {\n return errorResponse(\n 503,\n 'Change feed unavailable: no database configured for the API generator',\n );\n }\n\n const url = new URL(authResult.url);\n const since = Number(url.searchParams.get('since') ?? '0');\n if (!Number.isFinite(since) || since < 0) {\n return errorResponse(400, \"'since' must be a non-negative number\");\n }\n\n let limit: number | undefined;\n const limitParam = url.searchParams.get('limit');\n if (limitParam !== null) {\n limit = Number(limitParam);\n if (!Number.isFinite(limit) || limit < 1) {\n return errorResponse(400, \"'limit' must be a positive number\");\n }\n }\n\n const tablesParam = url.searchParams.get('tables');\n const tables = tablesParam\n ? tablesParam\n .split(',')\n .map((table) => table.trim())\n .filter(Boolean)\n : undefined;\n\n try {\n const db = await resolveChangesDb(options.db);\n // System tables are runtime-ensured; a raw handle passed straight to the\n // generator may not have gone through framework init yet.\n await ensureChangeFeedTable(db);\n const page = await getAuthorizedTenantScopedChangesSince(db, {\n since,\n tables,\n limit,\n // REST has no `locals`; the authorization hooks (#3020) identify the\n // principal from the authMiddleware-processed request instead.\n locals: undefined,\n request: authResult,\n });\n return jsonResponse(page);\n } catch (error) {\n logger.error('Change feed route failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n return errorResponse(500, 'Internal server error');\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;;;;;AAyB7C,IAAa,4BAA4B;AAKzC,IAAM,sCAAsB,IAAI,QAA4C;AAC5E,IAAM,iCAAiB,IAAI,IAAwC;;;;;;;;;;AAWnE,SAAgB,iBACd,UAC4B;CAC5B,IAAI,YAAY,OAAO,aAAa,UAAU;EAC5C,IACE,WAAW,YACX,OAAQ,SAAiC,UAAU,YAEnD,OAAO,QAAQ,QAAQ,QAA6B;EAEtD,IAAI,WAAW,oBAAoB,IAAI,QAAQ;EAC/C,IAAI,CAAC,UAAU;GACb,WAAW,YACT,6BAA6B,EAC3B,GAAI,SACN,CAAC,CACH;GACA,oBAAoB,IAAI,UAAU,QAAQ;EAC5C;EACA,OAAO;CACT;CAEA,IAAI,OAAO,aAAa,YAAY,UAAU;EAC5C,IAAI,WAAW,eAAe,IAAI,QAAQ;EAC1C,IAAI,CAAC,UAAU;GAIb,MAAM,aAAa,aAAa;GAChC,MAAM,UAAU,6BAA6B,EAAE,KAAK,SAAS,CAAC;GAC9D,WAAW,YAAY;IACrB,GAAG;IACH,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,uBAAuB,OAAO,EAAE;GAChE,CAAsC;GACtC,eAAe,IAAI,UAAU,QAAQ;EACvC;EACA,OAAO;CACT;CAEA,OAAO,QAAQ,uBACb,IAAI,MAAM,sDAAsD,CAClE;AACF;AAEA,SAAS,aAAa,MAAe,SAAS,KAAe;CAC3D,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH;AAEA,SAAS,cAAc,QAAgB,SAA2B;CAChE,OAAO,aAAa,EAAE,OAAO,QAAQ,GAAG,MAAM;AAChD;;;;;;;;;AAUA,eAAsB,mBACpB,KACA,SACmB;CACnB,IAAI,IAAI,WAAW,OACjB,OAAO,cAAc,KAAK,oBAAoB;CAMhD,IAAI,CAAC,QAAQ,gBACX,OAAO,cAAc,KAAK,yBAAyB;CAMrD,MAAM,aAAa,MAJD,QAAQ,eACxB,2BACA,IAAI,OAAO,YAAY,CAEA,CAAA,CAAU,GAAG;CACtC,IAAI,sBAAsB,UACxB,OAAO;CAGT,IAAI,QAAQ,MAAM,MAChB,OAAO,cACL,KACA,uEACF;CAGF,MAAM,MAAM,IAAI,IAAI,WAAW,GAAG;CAClC,MAAM,QAAQ,OAAO,IAAI,aAAa,IAAI,OAAO,KAAK,GAAG;CACzD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,OAAO,cAAc,KAAK,uCAAuC;CAGnE,IAAI;CACJ,MAAM,aAAa,IAAI,aAAa,IAAI,OAAO;CAC/C,IAAI,eAAe,MAAM;EACvB,QAAQ,OAAO,UAAU;EACzB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,OAAO,cAAc,KAAK,mCAAmC;CAEjE;CAEA,MAAM,cAAc,IAAI,aAAa,IAAI,QAAQ;CACjD,MAAM,SAAS,cACX,YACG,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,IACjB,KAAA;CAEJ,IAAI;EACF,MAAM,KAAK,MAAM,iBAAiB,QAAQ,EAAE;EAG5C,MAAM,sBAAsB,EAAE;EAU9B,OAAO,aAAa,MATD,sCAAsC,IAAI;GAC3D;GACA;GACA;GAGA,QAAQ,KAAA;GACR,SAAS;EACX,CAAC,CACuB;CAC1B,SAAS,OAAO;EACd,OAAO,MAAM,4BAA4B,EACvC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;EACD,OAAO,cAAc,KAAK,uBAAuB;CACnD;AACF"}
|
|
@@ -322,6 +322,17 @@ export declare function resolveDeclaredScopeMismatch(options: {
|
|
|
322
322
|
/** The receiver-derived scope, as the emitter computed it. */
|
|
323
323
|
effectiveScope: CustomActionScope;
|
|
324
324
|
}): CustomActionScope | undefined;
|
|
325
|
+
/**
|
|
326
|
+
* The type a parameter accepts on the wire: the scanner's same-module alias
|
|
327
|
+
* target when the declared type is an alias name, else the declared type
|
|
328
|
+
* (#4327). Core's custom-action schema (MCP/WebMCP), invocation coercion and
|
|
329
|
+
* SvelteKit route decoders read this rather than `parameter.type`; the CLI
|
|
330
|
+
* generator (`packages/cli`) still reads the authored name.
|
|
331
|
+
*/
|
|
332
|
+
export declare function declaredParameterType(parameter: {
|
|
333
|
+
type?: string | undefined;
|
|
334
|
+
resolvedType?: string | undefined;
|
|
335
|
+
}): string;
|
|
325
336
|
/** Build the custom-action portion of an MCP/WebMCP JSON Schema. */
|
|
326
337
|
export declare function buildCustomActionInputSchema(metadata: CustomActionMetadata): JsonSchema;
|
|
327
338
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-action.d.ts","sourceRoot":"","sources":["../../src/generators/custom-action.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"custom-action.d.ts","sourceRoot":"","sources":["../../src/generators/custom-action.ts"],"names":[],"mappings":"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"}
|
|
@@ -337,6 +337,25 @@ function resolveDeclaredScopeMismatch(options) {
|
|
|
337
337
|
if (!declared || declared === options.effectiveScope) return void 0;
|
|
338
338
|
return declared;
|
|
339
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* The type a parameter accepts on the wire: the scanner's same-module alias
|
|
342
|
+
* target when the declared type is an alias name, else the declared type
|
|
343
|
+
* (#4327). Core's custom-action schema (MCP/WebMCP), invocation coercion and
|
|
344
|
+
* SvelteKit route decoders read this rather than `parameter.type`; the CLI
|
|
345
|
+
* generator (`packages/cli`) still reads the authored name.
|
|
346
|
+
*/
|
|
347
|
+
function declaredParameterType(parameter) {
|
|
348
|
+
return parameter.resolvedType ?? parameter.type ?? "any";
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* JSON Schema for one custom-action parameter. A cyclic alias is advertised
|
|
352
|
+
* without a type constraint instead of the unknown-name `string` fallback,
|
|
353
|
+
* which would reject the value the action actually needs (#4327).
|
|
354
|
+
*/
|
|
355
|
+
function customActionParameterSchema(parameter) {
|
|
356
|
+
if (parameter.aliasUnresolved) return { description: `Unresolved TypeScript type alias: ${parameter.type}` };
|
|
357
|
+
return convertTypeToJsonSchema(declaredParameterType(parameter));
|
|
358
|
+
}
|
|
340
359
|
/** Build the custom-action portion of an MCP/WebMCP JSON Schema. */
|
|
341
360
|
function buildCustomActionInputSchema(metadata) {
|
|
342
361
|
const properties = {};
|
|
@@ -356,7 +375,7 @@ function buildCustomActionInputSchema(metadata) {
|
|
|
356
375
|
else if (metadata.parameters.length === 1 && metadata.parameters[0]?.name === "options") {
|
|
357
376
|
const parameter = metadata.parameters[0];
|
|
358
377
|
properties.options = {
|
|
359
|
-
...
|
|
378
|
+
...customActionParameterSchema(parameter),
|
|
360
379
|
description: "Options for the custom action",
|
|
361
380
|
...parameter.default !== void 0 ? { default: parameter.default } : {}
|
|
362
381
|
};
|
|
@@ -364,7 +383,7 @@ function buildCustomActionInputSchema(metadata) {
|
|
|
364
383
|
} else for (const parameter of metadata.parameters) {
|
|
365
384
|
const inputName = customActionParameterInputName(metadata, parameter.name);
|
|
366
385
|
properties[inputName] = {
|
|
367
|
-
...
|
|
386
|
+
...customActionParameterSchema(parameter),
|
|
368
387
|
...parameter.default !== void 0 ? { default: parameter.default } : {}
|
|
369
388
|
};
|
|
370
389
|
if (!parameter.optional) required.push(inputName);
|
|
@@ -385,7 +404,7 @@ function buildCustomActionInvocationArgs(metadata, args) {
|
|
|
385
404
|
if (!metadata.parameters) return [isRecord(options) && Object.keys(options).length > 0 ? options : directArgs];
|
|
386
405
|
if (metadata.parameters.length === 0) return [];
|
|
387
406
|
if (metadata.parameters.length === 1 && metadata.parameters[0]?.name === "options") return [options];
|
|
388
|
-
return metadata.parameters.map((parameter) => coerceCustomActionArgument(args[customActionParameterInputName(metadata, parameter.name)], parameter
|
|
407
|
+
return metadata.parameters.map((parameter) => coerceCustomActionArgument(args[customActionParameterInputName(metadata, parameter.name)], declaredParameterType(parameter)));
|
|
389
408
|
}
|
|
390
409
|
/** Stable MCP `_meta` member shared with the app discovery contract (#2181). */
|
|
391
410
|
var SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY = "io.happyvertical/smrt";
|
|
@@ -1105,6 +1124,6 @@ function declaredTypeAcceptsDate(declaredType) {
|
|
|
1105
1124
|
return branches.length > 0 && branches.every((branch) => branch === "Date");
|
|
1106
1125
|
}
|
|
1107
1126
|
//#endregion
|
|
1108
|
-
export { CRUD_OPERATIONS, FRAMEWORK_LIFECYCLE_METHOD_NAMES, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, classifyMethodWireability, classifyParameterWireability, coerceCustomActionArgument, createClassNamePredicate, createManifestClassNamePredicate, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, isCrudOperation, isCrudToolAction, isFrameworkLifecycleMethod, normalizeCustomActionFailure, queryStringDecoderFor, readMethodDecoratorConfig, resolveApiMethodExposure, resolveCustomActionMetadata, resolveCustomActionNames, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber };
|
|
1127
|
+
export { CRUD_OPERATIONS, FRAMEWORK_LIFECYCLE_METHOD_NAMES, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, classifyMethodWireability, classifyParameterWireability, coerceCustomActionArgument, createClassNamePredicate, createManifestClassNamePredicate, customActionParameterInputName, declaredParameterType, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, isCrudOperation, isCrudToolAction, isFrameworkLifecycleMethod, normalizeCustomActionFailure, queryStringDecoderFor, readMethodDecoratorConfig, resolveApiMethodExposure, resolveCustomActionMetadata, resolveCustomActionNames, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber };
|
|
1109
1128
|
|
|
1110
1129
|
//# sourceMappingURL=custom-action.js.map
|