@happyvertical/smrt-core 0.55.24 → 0.56.1
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/collection-reads.md +6 -3
- package/agents/generators.md +53 -0
- package/agents/object-runtime.md +3 -0
- package/agents/recipes.md +7 -4
- package/dist/browser.d.ts +2 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -1
- package/dist/collection.d.ts +4 -0
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +19 -9
- package/dist/collection.js.map +1 -1
- package/dist/consumer-plugin/hosted-sti-collections.d.ts.map +1 -1
- package/dist/consumer-plugin/hosted-sti-collections.js +4 -2
- package/dist/consumer-plugin/hosted-sti-collections.js.map +1 -1
- package/dist/cookbook/engine/commands.d.ts.map +1 -1
- package/dist/cookbook/engine/commands.js +12 -0
- package/dist/cookbook/engine/commands.js.map +1 -1
- package/dist/cookbook/engine/defaults.d.ts.map +1 -1
- package/dist/cookbook/engine/defaults.js +8 -0
- package/dist/cookbook/engine/defaults.js.map +1 -1
- package/dist/cookbook/engine/document.d.ts +4 -0
- package/dist/cookbook/engine/document.d.ts.map +1 -1
- package/dist/cookbook/engine/document.js +11 -3
- package/dist/cookbook/engine/document.js.map +1 -1
- package/dist/cookbook/engine/engine.d.ts.map +1 -1
- package/dist/cookbook/engine/engine.js +3 -1
- package/dist/cookbook/engine/engine.js.map +1 -1
- package/dist/cookbook/engine/schemas.d.ts.map +1 -1
- package/dist/cookbook/engine/schemas.js +8 -0
- package/dist/cookbook/engine/schemas.js.map +1 -1
- package/dist/cookbook/engine/types.d.ts +5 -1
- package/dist/cookbook/engine/types.d.ts.map +1 -1
- package/dist/cookbook/engine/types.js.map +1 -1
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/generators/index.d.ts +1 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -1
- package/dist/generators/mcp-runtime-template.d.ts +3 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +30 -4
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +5 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +82 -9
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/preflight-route.d.ts.map +1 -1
- package/dist/generators/preflight-route.js +3 -1
- package/dist/generators/preflight-route.js.map +1 -1
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +5 -0
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/swagger.d.ts.map +1 -1
- package/dist/generators/swagger.js +3 -1
- package/dist/generators/swagger.js.map +1 -1
- package/dist/generators.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/manifest/browser-capability.generated.js +34 -5
- package/dist/manifest/browser-capability.generated.js.map +1 -1
- package/dist/manifest/index.js +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +9 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +9 -1
- package/dist/mcp-serializer.d.ts +38 -0
- package/dist/mcp-serializer.d.ts.map +1 -0
- package/dist/mcp-serializer.js +60 -0
- package/dist/mcp-serializer.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/index.js +34 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/index.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/errors.js +343 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/errors.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/get-format.js +109 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/get-format.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/package-json-reader.js +102 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/package-json-reader.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/resolve.js +673 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/resolve.js.map +1 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/utils.js +32 -0
- package/dist/node_modules/.pnpm/import-meta-resolve@4.2.0/node_modules/import-meta-resolve/lib/utils.js.map +1 -0
- package/dist/object.d.ts +2 -0
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +51 -0
- package/dist/object.js.map +1 -1
- package/dist/operation-permission.d.ts +10 -0
- package/dist/operation-permission.d.ts.map +1 -0
- package/dist/operation-permission.js +36 -0
- package/dist/operation-permission.js.map +1 -0
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +12 -0
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/types.d.ts +18 -3
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +47 -0
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +5 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/smrt-knowledge.json +18 -11
- package/dist/utils/import-project-vite.js +1 -1
- package/dist/utils/import-workspace-module.js +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +1 -0
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/dev-plane-route.js +1 -1
- package/dist/vite-plugin/import-build-aware.js +1 -1
- package/dist/vite-plugin/index.js +1 -1
- package/dist/vite-plugin/resources-route.js +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +0 -4
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +37 -25
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/package.json +16 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/decorators/index.ts"],"sourcesContent":["/**\n * Field decorators for SMRT objects\n *\n * Modern decorator-based API for defining SMRT object properties.\n * Properties are typed as primitives with decorator metadata.\n */\n\nimport type {\n CustomActionScope,\n ToolEffect,\n} from '../generators/custom-action.js';\nimport type { ApiHttpMethod } from '../registry/types.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldUIHints } from '../scanner/types.js';\nimport type { SQLDataType } from '../schema/types.js';\nimport {\n type CompatibleMethodDecorator,\n type CompatibleMethodDecoratorContext,\n type CompatiblePropertyDecorator,\n type CompatiblePropertyDecoratorContext,\n type LegacyPropertyDecoratorTarget,\n registerCompatibleFieldDecorator,\n registerCompatibleMethodDecorator,\n} from './compatibility.js';\n\nexport type { FieldUIHints } from '../scanner/types.js';\n\ntype CompatibleFieldDecoratorOptions = FieldOptions & {\n related?: string;\n relatedConstructor?: Function;\n};\n\nfunction registerFieldDecoratorForCompatibility(\n className: string,\n propertyKey: string,\n options: CompatibleFieldDecoratorOptions,\n ctor?: Function,\n): void {\n ObjectRegistry.registerFieldDecorator(className, propertyKey, options, ctor);\n}\n\n/**\n * Meta type wrapper for STI (Single Table Inheritance) meta fields\n *\n * Fields typed as Meta<T> are stored in the _meta_data JSONB column\n * rather than as direct table columns. Used for child-specific fields\n * in STI hierarchies.\n *\n * @example\n * ```typescript\n * @smrt({ tableStrategy: 'sti' })\n * class Event extends SmrtObject {\n * title: string = '';\n * }\n *\n * @smrt()\n * class Meeting extends Event {\n * // Stored in _meta_data JSONB column\n * roomNumber: Meta<string> = '';\n * attendees: Meta<string[]> = [];\n * }\n * ```\n */\nexport type Meta<T> = T;\n\n/**\n * Base field options\n */\nexport type PrimitiveFieldType =\n | 'text'\n | 'integer'\n | 'decimal'\n | 'boolean'\n | 'datetime'\n | 'json';\n\nexport type FieldType =\n | PrimitiveFieldType\n | 'meta'\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\nexport interface FieldOptions {\n /** Use this field in the natural primary key, as supported by schema generation. */\n primaryKey?: boolean;\n /** Explicit field type for runtime-only registration paths */\n type?: FieldType;\n /** Explicit SQL storage type when runtime and persistence contracts differ */\n sqlType?: SQLDataType;\n /** Whether the field is required */\n required?: boolean;\n /** Default value for the field */\n default?: unknown;\n /**\n * Per-row SQL expression `db:migrate` uses to fill existing rows when this\n * field is added as a required column (no `default`) to a table that\n * already has rows, or when an existing nullable column with NULLs becomes\n * required (#3008). Evaluated once per row inside the migration\n * transaction, before NOT NULL and any unique index are enforced, so it\n * can derive a distinct value from the row's other columns:\n *\n * ```typescript\n * @field({ required: true, maxLength: 80, backfill: \"'closed:' || id\" })\n * openKey: string = '';\n * ```\n *\n * Must be a string literal (the build-time scanner reads it from source).\n * Write it in SQL that every engine you run accepts (`||`, `CASE`,\n * `COALESCE`, `CAST`); columns are the table's snake_case names. It is not\n * a default: the model must still supply a value on every insert.\n */\n backfill?: string;\n /** Whether the field is unique */\n unique?: boolean;\n /**\n * When `true`, the schema emits a database index targeting this field.\n *\n * For regular (column-backed) fields the index is a plain column index.\n * For `@meta()` fields stored inside `_meta_data` JSONB, the index targets\n * the JSON path — `json_extract(_meta_data, '$.fieldName')` on SQLite,\n * `(_meta_data->>'fieldName')` on Postgres.\n *\n * Note that `collection.list({ where })` cannot currently reach a JSON-path\n * index: dot-notation keys such as `_meta_data.fieldName` are rejected,\n * because nothing rewrites them into the matching extraction expression\n * (#2276, tracked in #2282). The index is still emitted and still serves\n * hand-written SQL that spells the expression out; it just has no\n * collection-level query to accelerate yet.\n */\n indexed?: boolean;\n /** Whether the field is nullable */\n nullable?: boolean;\n /** Whether the field should be excluded from database */\n transient?: boolean;\n /**\n * Marks the field as sensitive (e.g. API secrets, credentials, tax IDs).\n *\n * Sensitive fields are still persisted to the database, but the framework:\n * - excludes them from `toPublicJSON()` (the serializer used by generated\n * REST/MCP/SvelteKit routes), so they never appear in API responses; and\n * - rejects them as `where`-clause filter keys, closing the\n * `?secret[like]=...` value-probing oracle.\n *\n * Use this for any column that holds a secret value that must never be\n * read back over a generated network surface.\n */\n sensitive?: boolean;\n /**\n * Marks the field as read-only over generated write surfaces.\n *\n * Read-only fields are stripped from the request body before\n * `create`/`update` in generated REST/MCP/SvelteKit routes, so callers\n * cannot mass-assign them. Server-side code can still set them directly.\n */\n readonly?: boolean;\n /**\n * Permission slug required to include this field in public/read responses.\n *\n * Fields with a read permission are fail-closed: generated serializers omit\n * them unless the caller's resolved permission set contains this slug.\n * `sensitive: true` still wins and omits the field for every caller.\n */\n readPermission?: string;\n /** Field description */\n description?: string;\n /**\n * Static UI hints for the field-policy rail (#2046, epic #2045).\n *\n * A pure presentation seed — carried in the manifest under the field's\n * `_meta.ui`, readable at runtime via `getAllFields()` at `field._meta.ui`,\n * and emitted to the browser in generated web-collection definitions. Has no\n * schema, persistence, or security effect; `sensitive`/`readPermission`\n * remain the security rail.\n */\n ui?: FieldUIHints;\n /**\n * Controls whether the field is included in JSON exports.\n * - `true`: Always exported (unless site explicitly excludes it)\n * - `false`: Never exported (cannot be overridden by site config)\n * - `undefined`: Uses site's fieldExportDefault setting\n */\n exported?: boolean;\n}\n\n/**\n * Options for text fields\n */\nexport interface TextFieldOptions extends FieldOptions {\n /** Minimum length for text fields */\n minLength?: number;\n /** Maximum length for text fields */\n maxLength?: number;\n /** Regex pattern for validation */\n pattern?: RegExp | string;\n}\n\n/**\n * Options for numeric fields\n */\nexport interface NumericFieldOptions extends FieldOptions {\n /** Minimum value */\n min?: number;\n /** Maximum value */\n max?: number;\n}\n\n/**\n * Options for relationship fields\n */\nexport interface RelationshipFieldOptions extends FieldOptions {\n /** Related class name */\n related?: string;\n /** Foreign key field name */\n foreignKey?: string;\n /** Through table for many-to-many */\n through?: string;\n /** Source-side join column override for many-to-many relationships. */\n sourceKey?: string;\n /** Target-side join column override for many-to-many relationships. */\n targetKey?: string;\n /** Relationship type */\n type?: 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';\n /**\n * Relationship role (#3718 record page): `items` on a `@foreignKey` /\n * `@crossPackageRef` marks the declaring model as the line items of the\n * target, so record pages show its rows as a line-items table with a\n * totals block. Emitted on the generated web relationship edge.\n */\n role?: 'items';\n /**\n * What happens to this row when the referenced object is deleted (#2371).\n *\n * Same-package references emit this DB-level action and `SmrtObject.delete()`\n * enforces the same policy in the application layer:\n *\n * - `'CASCADE'` — this row is deleted with the target.\n * - `'SET NULL'` — this column is set to `NULL`. The field must be nullable;\n * declaring it on a `required` field throws `ConfigurationError`.\n * - `'RESTRICT'` — deleting the target throws `DatabaseError` while any row\n * still points at it.\n *\n * When omitted, a column that is part of this class's `conflictColumns`\n * (junction and association rows, which are *identified* by the reference)\n * defaults to `CASCADE`; every other same-package column defaults to\n * immediate `NO ACTION`.\n * `@tenantId()` fields are the one exception: `smrt-tenancy` leads a\n * tenant-scoped class's default `conflictColumns` with the tenant column,\n * but that column scopes ownership rather than identifying the row, so it\n * is never defaulted to `CASCADE` — deleting a `Tenant` must not cascade\n * through every tenant-scoped table.\n *\n * Cascaded rows are removed set-based: their `beforeDelete`/`afterDelete`\n * hooks and interceptors do not run and no change-feed entry is written, the\n * same as a database-level `ON DELETE CASCADE`.\n */\n onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';\n /** Database action when the referenced key changes; defaults to CASCADE. */\n onUpdate?: RelationshipFieldOptions['onDelete'];\n /**\n * Set to `false` only for an app-side relationship whose target identifier\n * must deliberately survive without a database parent row (for example an\n * immutable audit/event record retained after its parent is pruned).\n *\n * The field remains a typed `foreignKey` relationship for loading and\n * indexes, but no physical DDL constraint, schema dependency, or app-side\n * delete action is emitted. The stored identifier is deliberately preserved\n * after the parent is deleted. Ordinary same-package relationships must leave\n * this enabled (the default).\n */\n constraint?:\n | boolean\n | {\n /**\n * Database engines on which SMRT emits the physical constraint.\n *\n * Relationship loading, UUID storage, indexes, and application-side\n * delete enforcement remain active on every engine. Use this narrow\n * allowlist only when an engine cannot faithfully enforce a supported\n * relationship shape (for example a DuckDB self-reference).\n */\n engines: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'>;\n };\n}\n\n/**\n * Options specific to cross-package references.\n */\nexport interface CrossPackageRefOptions\n extends Omit<RelationshipFieldOptions, 'related' | 'type'> {\n /**\n * Storage type for the referenced target id. Defaults to 'uuid'.\n *\n * Use 'text' only when the external target model declares\n * `@smrt({ idType: 'text' })`.\n */\n idType?: 'uuid' | 'text';\n\n /**\n * When `true`, the framework verifies the referenced object exists at save time.\n * Validation uses the target package's manifest (loaded on demand via\n * `ObjectRegistry.ensureManifestLoaded()`), so this requires the target manifest\n * to be discoverable at runtime.\n *\n * Empty/null values are always allowed (treated as \"no reference set\").\n *\n * Defaults to `false` — same behavior as a plain string field today.\n */\n validate?: boolean;\n}\n\n/**\n * Resolve a relationship decorator's target argument to a class name.\n *\n * Accepted target forms:\n * - `'Target'` — class name string; resolves lazily, immune to import cycles.\n * - `Target` — class constructor.\n * - `() => Target` — thunk (inline or a named `const`), invoked here to read\n * the name, so its target must already be initialized.\n *\n * A thunk's own `.name` is `''`, so reading `relatedClass.name` used to register\n * `related: ''` (issue #2379): the field kept `type: 'foreignKey'` but lost its\n * target, which silently dropped the relationship edge, `loadRelated()`, and any\n * FK-derived index. Thunks are therefore invoked at decoration time and an\n * unresolvable target throws with the string form as the remedy — an empty\n * `related` is never registered.\n *\n * Resolution runs inside the field-registration callback, which is the latest\n * point in the decorator lifecycle (after the class binding exists for legacy\n * decorators, and at `@smrt()` application time for standard decorators), so a\n * self-referential `() => Self` thunk resolves rather than hitting the TDZ.\n * A thunk pointing at a class declared LATER in the same module is still in\n * that class's temporal dead zone when the decorators of the earlier class run;\n * that now fails loudly, naming the string form, instead of silently\n * registering an empty target.\n */\nfunction resolveRelatedClassName(\n decoratorName: 'foreignKey' | 'oneToMany' | 'manyToMany',\n relatedClass: string | Function,\n className: string,\n propertyKey: string,\n): { related: string; relatedConstructor?: Function } {\n const where = `@${decoratorName}() on ${className}.${propertyKey}`;\n const remedy =\n `Pass the target class name as a string instead — ` +\n `\\`@${decoratorName}('Target')\\` resolves lazily and is immune to import cycles.`;\n\n if (typeof relatedClass === 'string') {\n const name = relatedClass.trim();\n if (!name) {\n throw new Error(\n `${where}: target class name is empty. Pass a class, a class name, or a \\`() => Target\\` thunk.`,\n );\n }\n return { related: name };\n }\n\n if (typeof relatedClass !== 'function') {\n throw new Error(\n `${where}: expected a class, a class name, or a \\`() => Target\\` thunk, received ${relatedClass === null ? 'null' : typeof relatedClass}. ${remedy}`,\n );\n }\n\n // Class/function reference — the common `@foreignKey(Target)` form. Arrow\n // functions have no `prototype`, so a *named* thunk (`const lazyTarget = () =>\n // Target`) is still routed to the thunk branch below instead of registering\n // the variable name as the target class.\n if (relatedClass.name && relatedClass.prototype !== undefined) {\n return { related: relatedClass.name, relatedConstructor: relatedClass };\n }\n\n // Thunk (`() => Target`, named or inline) — invoke it for the target.\n let resolved: unknown;\n try {\n resolved = (relatedClass as () => unknown)();\n } catch (error) {\n throw new Error(\n `${where}: the \\`() => Target\\` thunk threw while resolving its target (${\n error instanceof Error ? error.message : String(error)\n }). ${remedy}`,\n { cause: error },\n );\n }\n\n if (typeof resolved === 'function' && resolved.name) {\n return { related: resolved.name, relatedConstructor: resolved };\n }\n if (typeof resolved === 'string' && resolved.trim()) {\n return { related: resolved.trim() };\n }\n\n throw new Error(\n `${where}: the \\`() => Target\\` thunk resolved to ${\n resolved === null ? 'null' : typeof resolved\n } instead of a named class. ${remedy}`,\n );\n}\n\n/**\n * Marks a class property with validation constraints and metadata options.\n *\n * Use `@field()` when you need options beyond what plain TypeScript initializers\n * express — required validation, numeric ranges, string length limits, uniqueness,\n * or transient (non-persisted) computed properties.\n *\n * For plain persisted fields with no constraints, no decorator is needed: just\n * declare the property with a TypeScript initializer and the framework will infer\n * the column type from the default value (`0` → INTEGER, `0.0` → DECIMAL, `''` → TEXT).\n *\n * @param options - Field configuration options\n * @param options.required - If `true`, `save()` throws `ValidationError` when empty/null\n * @param options.unique - Enforces a UNIQUE database constraint\n * @param options.nullable - If `true`, the column accepts NULL (default depends on type)\n * @param options.transient - If `true`, the property is not persisted to the database\n * @param options.default - Default value applied at the database level\n * @param options.description - Human-readable description used in generated API docs\n * @param options.exported - Controls JSON export visibility (see `FieldOptions`)\n * @returns A TypeScript property decorator\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * @field({ required: true, maxLength: 100 })\n * name: string = '';\n *\n * @field({ min: 0 })\n * stock: number = 0;\n *\n * @field({ transient: true })\n * get displayPrice(): string { return `$${this.price.toFixed(2)}`; }\n * }\n * ```\n *\n * @see {@link meta} for STI child-specific fields stored in `_meta_data` JSON\n * @see {@link foreignKey} for typed relationship fields\n */\nexport function field(\n options: FieldOptions | NumericFieldOptions | TextFieldOptions = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n options,\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a many-to-one (foreign key) relationship to another `SmrtObject` class.\n *\n * The decorated property stores the UUID of the related object. At runtime, call\n * `instance.loadRelated('fieldName')` to lazy-load (and cache) the related object,\n * or pass `include: ['fieldName']` to `collection.list()` for batch eager loading.\n *\n * Cross-package rule: Use `@foreignKey()` only for same-package references.\n * Use `@crossPackageRef()` for cross-package relationships.\n *\n * A named database constraint is emitted on supported engines by default, and\n * the same action is enforced by `SmrtObject.delete()` before the engine sees\n * it. Exceptional archival/audit references that intentionally outlive their\n * parent may declare `{ constraint: false }`; they retain relationship loading\n * and indexing while deliberately preserving the identifier after parent\n * deletion and omitting physical DDL. The\n * decorator also enables `loadRelated()` and eager `include:` loading:\n *\n * ```typescript\n * @foreignKey(Order, { onDelete: 'CASCADE' }) // deleted with the order\n * orderId: string = '';\n *\n * @foreignKey(Customer, { onDelete: 'RESTRICT' }) // blocks the customer delete\n * customerId: string = '';\n * ```\n *\n * Without an `onDelete`, a column that is part of this class's\n * `conflictColumns` defaults to `CASCADE` (this is what cleans up junction\n * rows); any other column defaults to immediate `NO ACTION`.\n *\n * @param relatedClass - The target class constructor, its name as a string, or a\n * `() => Target` thunk. A thunk is **invoked at decoration time**, so its\n * target must already be initialized: a class from an already-evaluated module\n * or the decorated class itself. Use the string form for a class declared\n * later in the same module or reached through an import cycle — it is never\n * evaluated, so it cannot hit the temporal dead zone.\n * @param options - Optional field constraints (required, nullable, etc.)\n * @returns A TypeScript property decorator\n * @throws Error when the target cannot be resolved to a class name (an empty\n * string, a thunk that throws — including on an uninitialized target — or a\n * thunk returning an anonymous value)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Order extends SmrtObject {\n * // Same-package FK — enables loadRelated() and eager loading\n * @foreignKey(Customer)\n * customerId: string = '';\n *\n * // Self-reference: the class binding exists when its decorators run\n * @foreignKey(() => Order)\n * parentOrderId: string = '';\n *\n * // Declared later in this module — name string, never evaluated\n * @foreignKey('Invoice')\n * invoiceId: string = '';\n *\n * // Physical constraint only where the engine can enforce this shape;\n * // relationship loading and app-side delete policy remain cross-engine.\n * @foreignKey('Order', {\n * constraint: { engines: ['postgres', 'sqlite'] },\n * })\n * hierarchyParentId: string | null = null;\n * }\n *\n * // Cross-package: runtime relationship, index, and loading; no physical FK\n * @smrt()\n * class Post extends SmrtObject {\n * @crossPackageRef('@happyvertical/smrt-users:User')\n * authorId: string = '';\n * }\n * ```\n *\n * @see {@link oneToMany} for the inverse (parent) side of the relationship\n * @see SmrtObject.loadRelated for lazy-loading the related object at runtime\n */\nexport function foreignKey(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'foreignKey',\n ...resolveRelatedClassName(\n 'foreignKey',\n relatedClass,\n className,\n propertyKey,\n ),\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a cross-package foreign key reference.\n *\n * Use this for relationships that point to a `SmrtObject` in a *different* package\n * (e.g. `Customer.profileId` pointing at `@happyvertical/smrt-profiles:Profile`).\n *\n * Unlike same-package `@foreignKey()`, this emits **no** DDL `FOREIGN KEY`\n * constraint, keeping package schemas independently installable. The property is a\n * `UUID` column on PostgreSQL/DuckDB and `TEXT` on SQLite, matching the target's\n * id type; pass `{ idType: 'text' }` when the target declares\n * `@smrt({ idType: 'text' })`.\n *\n * What you get over a plain string field:\n * - The relationship is registered with the `ObjectRegistry`, so `loadRelated()`\n * and `Collection.list({ include })` can resolve it once the target package's\n * manifest is loaded.\n * - No physical database constraint is emitted; package schemas remain\n * independently installable.\n * - Optional save-time validation (`validate: true`) confirms the referenced\n * object exists, catching typos and stale IDs before they hit the database.\n * - `onDelete` is honoured by `SmrtObject.delete()` when the target package's\n * manifest is loaded in the same runtime.\n *\n * The `qualifiedName` is a fully-qualified class identifier in the form\n * `@package/scope:ClassName` — for example `@happyvertical/smrt-profiles:Profile`.\n *\n * @param qualifiedName - Qualified name of the target class\n * @param options - Optional field constraints and `validate` flag\n * @returns A TypeScript property decorator\n *\n * @example\n * ```typescript\n * @smrt()\n * class Customer extends SmrtObject {\n * @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n * profileId: string = '';\n *\n * // With save-time validation\n * @crossPackageRef('@happyvertical/smrt-profiles:Profile', { validate: true })\n * primaryContactId: string = '';\n * }\n * ```\n *\n * @see {@link foreignKey} for same-package relationships\n * @see SmrtObject.loadRelated for runtime resolution\n */\nexport function crossPackageRef(\n qualifiedName: string,\n options: CrossPackageRefOptions = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'crossPackageRef',\n related: qualifiedName,\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a one-to-many relationship from this object to a collection of related objects.\n *\n * The decorated property is `transient` — it is not persisted as a database column.\n * At runtime, call `instance.loadRelatedMany('fieldName')` to load the related objects,\n * or pass `include: ['fieldName']` to `collection.list()` for batch eager loading (issues\n * a single batched query for all instances instead of N individual queries).\n *\n * The inverse side (`@foreignKey`) must exist on the `relatedClass` pointing back to this\n * class. The framework discovers it automatically via `ObjectRegistry.getInverseRelationships()`.\n *\n * **Generated accessor (R10):** registering the class installs a consistent\n * `get<FieldName>()` instance method (e.g. `items` → `order.getItems()`) that\n * delegates to `loadRelatedMany('items')`. Generation is additive — a\n * hand-rolled method of the same name is never overwritten.\n *\n * **Disambiguation:** when `relatedClass` declares more than one `@foreignKey`\n * back to this class, pass `{ foreignKey: '<inverseFieldName>' }` so both\n * `loadRelatedMany` and the generated accessor resolve the intended inverse\n * side. Without it the first matching foreign key is used.\n *\n * **Delete behaviour is declared on the child, not here.** `@oneToMany` is a\n * transient read-side accessor; to have children removed with their parent,\n * put `onDelete: 'CASCADE'` on the inverse `@foreignKey` (#2371):\n *\n * ```typescript\n * class OrderItem extends SmrtObject {\n * @foreignKey(Order, { onDelete: 'CASCADE' })\n * orderId: string = '';\n * }\n * ```\n *\n * @param relatedClass - The class constructor of the child/related objects\n * @param options - Optional relationship options. `foreignKey` selects the\n * inverse foreign-key field on `relatedClass` when it has more than one.\n * @returns A TypeScript property decorator (sets `transient: true` automatically)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Order extends SmrtObject {\n * @oneToMany(OrderItem)\n * items: OrderItem[] = [];\n * }\n *\n * @smrt()\n * class OrderItem extends SmrtObject {\n * @foreignKey(Order)\n * orderId: string = '';\n * }\n *\n * const order = await orders.get({ id });\n * const items = await order.getItems(); // generated; === loadRelatedMany('items')\n * ```\n *\n * @example\n * ```typescript\n * // Multiple inverse foreign keys → disambiguate explicitly.\n * @smrt()\n * class Profile extends SmrtObject {\n * @oneToMany(ProfileRelationship, { foreignKey: 'fromProfileId' })\n * relationshipsFrom: ProfileRelationship[] = [];\n * @oneToMany(ProfileRelationship, { foreignKey: 'toProfileId' })\n * relationshipsTo: ProfileRelationship[] = [];\n * }\n * ```\n *\n * @see {@link foreignKey} for the many-to-one (child) side of the relationship\n * @see SmrtObject.loadRelatedMany for lazy-loading at runtime\n */\nexport function oneToMany(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'oneToMany',\n ...resolveRelatedClassName(\n 'oneToMany',\n relatedClass,\n className,\n propertyKey,\n ),\n transient: true, // Relationship fields are not database columns\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a many-to-many relationship between two `SmrtObject` classes via a join table.\n *\n * The decorated property is `transient` — it is not persisted as a database column.\n * The `through` option specifies the junction table name. The join table model must\n * be decorated with `@smrt({ conflictColumns: ['...', '...'] })` to use the natural\n * key columns for upsert operations.\n *\n * Runtime loading: call `instance.loadRelatedMany('field')` to lazy-load, or\n * pass `include: ['field']` to `collection.list()` for batched eager loading.\n *\n * @param relatedClass - The class constructor of the related objects\n * @param options - Relationship options; `through` specifies the junction table name\n * @returns A TypeScript property decorator (sets `transient: true` automatically)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * @manyToMany(Tag, { through: 'product_tags' })\n * tags: Tag[] = [];\n * }\n * ```\n *\n * @see {@link oneToMany} for one-to-many relationships\n */\nexport function manyToMany(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'manyToMany',\n ...resolveRelatedClassName(\n 'manyToMany',\n relatedClass,\n className,\n propertyKey,\n ),\n transient: true, // Relationship fields are not database columns\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Marks a field as a Single Table Inheritance (STI) meta field.\n *\n * Meta fields are stored in the `_meta_data` JSONB column on the shared STI\n * table rather than as dedicated table columns. Use this decorator for fields\n * that are specific to an STI child class and should not pollute the shared\n * table schema with child-specific columns.\n *\n * The `@smrt({ tableStrategy: 'sti' })` decorator must be set on the base class.\n * All child-specific fields should use `@meta()` (or the `Meta<T>` type alias).\n *\n * @param options - Standard field options (required, nullable, description, etc.)\n * @returns A TypeScript property decorator (registers field with `type: 'meta'`)\n *\n * @example\n * ```typescript\n * @smrt({ tableStrategy: 'sti' })\n * class Event extends SmrtObject {\n * title: string = ''; // shared column on events table\n * }\n *\n * @smrt()\n * class Meeting extends Event {\n * @meta()\n * roomNumber: string = ''; // stored in _meta_data JSON, not a column\n *\n * @meta({ required: true })\n * durationMinutes: number = 60;\n * }\n * ```\n *\n * @see {@link Meta} for the equivalent type alias approach\n * @see {@link field} for regular (non-STI) field declarations\n */\nexport function meta(options: FieldOptions = {}) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'meta', // Mark this field as a meta field for STI\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Options for {@link method}.\n *\n * Every option is optional, and an omitted one falls back to the class-level\n * `api.routes[methodName]` entry (or `ai.descriptions[methodName]` for\n * `description`) when one exists. The merge is FIELD BY FIELD, so adding\n * `@method({ description })` to a class that already declares a route verb and\n * path does not reset them (#2686).\n */\nexport interface MethodOptions {\n /**\n * Override the default routing decision.\n *\n * Public methods are routed by default when they are WIRE-ABLE: every\n * parameter can be built from a JSON request body or query string. Set\n * `false` to withhold a method the heuristic accepted, `true` to expose one\n * it rejected.\n *\n * `true` bypasses the heuristic and NOTHING else. It cannot manufacture a\n * receiver, undo `api: false`, cross an `include`/`exclude` boundary, reach a\n * non-public method, claim a CRUD verb, or hydrate a value the transport\n * cannot build — a model-instance parameter still arrives as whatever JSON\n * the caller sent.\n */\n expose?: boolean;\n\n /**\n * Why the method is withheld. Recorded in the knowledge artifact so\n * `smrt doctor` and agents read an explanation instead of silence.\n */\n reason?: string;\n\n /**\n * HTTP verb for the generated route. Defaults to `POST`.\n *\n * Named `httpMethod`, not `method`: RFC 9110 calls this the request\n * \"method\", so the protocol word is exactly the one that collides with the\n * decorator's own name, and `@method({ method: 'POST' })` stutters.\n * `httpMethod` is also already this framework's name for it — `ApiHttpMethod`\n * is carried across the whole discovery/invoke path.\n *\n * Migrates from `api.routes[methodName].method`.\n */\n httpMethod?: ApiHttpMethod;\n\n /**\n * Route path segment(s), used verbatim and split on `/`.\n * Defaults to the method name. Migrates from `api.routes[methodName].path`.\n */\n path?: string;\n\n /**\n * Declared receiver scope. Migrates from `api.routes[methodName].scope`.\n *\n * A DECLARATION checked against the method's executable receiver, not a\n * relocation: an instance method is item-scoped and a static (or\n * collection-class) method is collection-scoped, and no config can change\n * that. A contradicting value is reported at build time and ignored.\n */\n scope?: CustomActionScope;\n\n /**\n * Agent-visible effect classification. Omitted actions are treated as\n * `destructive`, so missing metadata can never widen browser capability\n * exposure. Migrates from `api.routes[methodName].effect`.\n */\n effect?: ToolEffect;\n\n /** Whether repeating the action with the same arguments is safe. */\n idempotent?: boolean;\n\n /** Whether the action may interact outside the SMRT application. */\n openWorld?: boolean;\n\n /**\n * Description used by AI/tool surfaces, overriding the method's JSDoc.\n * Migrates from `ai.descriptions[methodName]`.\n */\n description?: string;\n}\n\n/**\n * Refines how a method is exposed on generated surfaces.\n *\n * The method counterpart of {@link field}, and symmetric with it: a manifest\n * records exactly two collections per object, `fields` and `methods`. Neither\n * decorator DECLARES its member — a property is a field because it is a\n * property, and a method is a candidate action because it is public — both\n * REFINE what the framework already inferred. `@field()` refines an inferred\n * column type and constraints; `@method()` refines inferred exposure, its\n * route shape, and its tool semantics.\n *\n * The name is `@method()` rather than `@action()` because of the negative\n * case: withholding a method has to read correctly, and\n * `@action({ expose: false })` contradicts itself — declaring something an\n * action in order to say it is not one. \"Action\" keeps its established meaning\n * in the generators (a non-CRUD method exposed on a surface); this decorator\n * is how a method BECOMES one.\n *\n * Metadata only: the decorated method is not wrapped and keeps its identity,\n * so it stays directly callable.\n *\n * @example Shape the generated route\n * ```typescript\n * @method({ httpMethod: 'POST', path: 'reviews', effect: 'write' })\n * async runReview(options?: RunContentReviewOptions) { … }\n * ```\n *\n * @example Withhold a method the heuristic would have routed\n * ```typescript\n * @method({ expose: false, reason: 'callback registration, not a wire operation' })\n * static registerValidator(validator: ValidatorFunction) { … }\n * ```\n *\n * @example Force a route the heuristic rejected\n * ```typescript\n * // `expose: true` bypasses the heuristic only — this method must still\n * // accept whatever JSON the caller sends for `asset`.\n * @method({ expose: true })\n * async addAsset(asset: Asset) { … }\n * ```\n *\n * @see {@link field} for the property-side refinement decorator\n */\nexport function method(options: MethodOptions = {}) {\n return ((\n targetOrValue: unknown,\n propertyKeyOrContext: CompatibleMethodDecoratorContext<\n unknown,\n // biome-ignore lint/suspicious/noExplicitAny: mirrors the lib's own `ClassMethodDecoratorContext` constraint; see `AnyMethodOf` in `compatibility.ts`. S4 #1579.\n (this: unknown, ...args: any) => any\n >,\n ) => {\n registerCompatibleMethodDecorator(\n targetOrValue as LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext,\n (ctor, methodName, isStatic) => {\n ObjectRegistry.registerMethodDecorator(\n ctor,\n methodName,\n options,\n isStatic,\n );\n },\n );\n }) as CompatibleMethodDecorator;\n}\n"],"mappings":";;;AAgCA,SAAS,uCACP,WACA,aACA,SACA,MACM;CACN,eAAe,uBAAuB,WAAW,aAAa,SAAS,IAAI;AAC7E;;;;;;;;;;;;;;;;;;;;;;;;;;AA0SA,SAAS,wBACP,eACA,cACA,WACA,aACoD;CACpD,MAAM,QAAQ,IAAI,cAAc,QAAQ,UAAU,GAAG;CACrD,MAAM,SACJ,uDACM,cAAc;CAEtB,IAAI,OAAO,iBAAiB,UAAU;EACpC,MAAM,OAAO,aAAa,KAAK;EAC/B,IAAI,CAAC,MACH,MAAM,IAAI,MACR,GAAG,MAAM,uFACX;EAEF,OAAO,EAAE,SAAS,KAAK;CACzB;CAEA,IAAI,OAAO,iBAAiB,YAC1B,MAAM,IAAI,MACR,GAAG,MAAM,0EAA0E,iBAAiB,OAAO,SAAS,OAAO,aAAa,IAAI,QAC9I;CAOF,IAAI,aAAa,QAAQ,aAAa,cAAc,KAAA,GAClD,OAAO;EAAE,SAAS,aAAa;EAAM,oBAAoB;CAAa;CAIxE,IAAI;CACJ,IAAI;EACF,WAAY,aAA+B;CAC7C,SAAS,OAAO;EACd,MAAM,IAAI,MACR,GAAG,MAAM,iEACP,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EACtD,KAAK,UACN,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,IAAI,OAAO,aAAa,cAAc,SAAS,MAC7C,OAAO;EAAE,SAAS,SAAS;EAAM,oBAAoB;CAAS;CAEhE,IAAI,OAAO,aAAa,YAAY,SAAS,KAAK,GAChD,OAAO,EAAE,SAAS,SAAS,KAAK,EAAE;CAGpC,MAAM,IAAI,MACR,GAAG,MAAM,2CACP,aAAa,OAAO,SAAS,OAAO,SACrC,6BAA6B,QAChC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,MACd,UAAiE,CAAC,GAClE;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA,SACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,WACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,cACA,cACA,WACA,WACF;GACF,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,gBACd,eACA,UAAkC,CAAC,GACnC;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,SAAS;GACX,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,UACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,aACA,cACA,WACA,WACF;IACA,WAAW;GACb,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,WACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,cACA,cACA,WACA,WACF;IACA,WAAW;GACb,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,KAAK,UAAwB,CAAC,GAAG;CAC/C,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;GACR,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8HA,SAAgB,OAAO,UAAyB,CAAC,GAAG;CAClD,SACE,eACA,yBAKG;EACH,kCACE,eACA,uBACC,MAAM,YAAY,aAAa;GAC9B,eAAe,wBACb,MACA,YACA,SACA,QACF;EACF,CACF;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/decorators/index.ts"],"sourcesContent":["/**\n * Field decorators for SMRT objects\n *\n * Modern decorator-based API for defining SMRT object properties.\n * Properties are typed as primitives with decorator metadata.\n */\n\nimport type {\n CustomActionScope,\n ToolEffect,\n} from '../generators/custom-action.js';\nimport type { ApiHttpMethod } from '../registry/types.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldUIHints } from '../scanner/types.js';\nimport type { SQLDataType } from '../schema/types.js';\nimport {\n type CompatibleMethodDecorator,\n type CompatibleMethodDecoratorContext,\n type CompatiblePropertyDecorator,\n type CompatiblePropertyDecoratorContext,\n type LegacyPropertyDecoratorTarget,\n registerCompatibleFieldDecorator,\n registerCompatibleMethodDecorator,\n} from './compatibility.js';\n\nexport type { FieldUIHints } from '../scanner/types.js';\n\ntype CompatibleFieldDecoratorOptions = FieldOptions & {\n related?: string;\n relatedConstructor?: Function;\n};\n\nfunction registerFieldDecoratorForCompatibility(\n className: string,\n propertyKey: string,\n options: CompatibleFieldDecoratorOptions,\n ctor?: Function,\n): void {\n ObjectRegistry.registerFieldDecorator(className, propertyKey, options, ctor);\n}\n\n/**\n * Meta type wrapper for STI (Single Table Inheritance) meta fields\n *\n * Fields typed as Meta<T> are stored in the _meta_data JSONB column\n * rather than as direct table columns. Used for child-specific fields\n * in STI hierarchies.\n *\n * @example\n * ```typescript\n * @smrt({ tableStrategy: 'sti' })\n * class Event extends SmrtObject {\n * title: string = '';\n * }\n *\n * @smrt()\n * class Meeting extends Event {\n * // Stored in _meta_data JSONB column\n * roomNumber: Meta<string> = '';\n * attendees: Meta<string[]> = [];\n * }\n * ```\n */\nexport type Meta<T> = T;\n\n/**\n * Base field options\n */\nexport type PrimitiveFieldType =\n | 'text'\n | 'integer'\n | 'decimal'\n | 'boolean'\n | 'datetime'\n | 'json';\n\nexport type FieldType =\n | PrimitiveFieldType\n | 'meta'\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\nexport interface FieldOptions {\n /** Use this field in the natural primary key, as supported by schema generation. */\n primaryKey?: boolean;\n /** Explicit field type for runtime-only registration paths */\n type?: FieldType;\n /** Explicit SQL storage type when runtime and persistence contracts differ */\n sqlType?: SQLDataType;\n /** Whether the field is required */\n required?: boolean;\n /** Validate that an STI reference resolves to the declared subtype on save/load. */\n validateSubtype?: boolean;\n /** Default value for the field */\n default?: unknown;\n /**\n * Per-row SQL expression `db:migrate` uses to fill existing rows when this\n * field is added as a required column (no `default`) to a table that\n * already has rows, or when an existing nullable column with NULLs becomes\n * required (#3008). Evaluated once per row inside the migration\n * transaction, before NOT NULL and any unique index are enforced, so it\n * can derive a distinct value from the row's other columns:\n *\n * ```typescript\n * @field({ required: true, maxLength: 80, backfill: \"'closed:' || id\" })\n * openKey: string = '';\n * ```\n *\n * Must be a string literal (the build-time scanner reads it from source).\n * Write it in SQL that every engine you run accepts (`||`, `CASE`,\n * `COALESCE`, `CAST`); columns are the table's snake_case names. It is not\n * a default: the model must still supply a value on every insert.\n */\n backfill?: string;\n /** Whether the field is unique */\n unique?: boolean;\n /**\n * When `true`, the schema emits a database index targeting this field.\n *\n * For regular (column-backed) fields the index is a plain column index.\n * For `@meta()` fields stored inside `_meta_data` JSONB, the index targets\n * the JSON path — `json_extract(_meta_data, '$.fieldName')` on SQLite,\n * `(_meta_data->>'fieldName')` on Postgres.\n *\n * Note that `collection.list({ where })` cannot currently reach a JSON-path\n * index: dot-notation keys such as `_meta_data.fieldName` are rejected,\n * because nothing rewrites them into the matching extraction expression\n * (#2276, tracked in #2282). The index is still emitted and still serves\n * hand-written SQL that spells the expression out; it just has no\n * collection-level query to accelerate yet.\n */\n indexed?: boolean;\n /** Whether the field is nullable */\n nullable?: boolean;\n /** Whether the field should be excluded from database */\n transient?: boolean;\n /**\n * Marks the field as sensitive (e.g. API secrets, credentials, tax IDs).\n *\n * Sensitive fields are still persisted to the database, but the framework:\n * - excludes them from `toPublicJSON()` (the serializer used by generated\n * REST/MCP/SvelteKit routes), so they never appear in API responses; and\n * - rejects them as `where`-clause filter keys, closing the\n * `?secret[like]=...` value-probing oracle.\n *\n * Use this for any column that holds a secret value that must never be\n * read back over a generated network surface.\n */\n sensitive?: boolean;\n /**\n * Marks the field as read-only over generated write surfaces.\n *\n * Read-only fields are stripped from the request body before\n * `create`/`update` in generated REST/MCP/SvelteKit routes, so callers\n * cannot mass-assign them. Server-side code can still set them directly.\n */\n readonly?: boolean;\n /**\n * Permission slug required to include this field in public/read responses.\n *\n * Fields with a read permission are fail-closed: generated serializers omit\n * them unless the caller's resolved permission set contains this slug.\n * `sensitive: true` still wins and omits the field for every caller.\n */\n readPermission?: string;\n /** Field description */\n description?: string;\n /**\n * Static UI hints for the field-policy rail (#2046, epic #2045).\n *\n * A pure presentation seed — carried in the manifest under the field's\n * `_meta.ui`, readable at runtime via `getAllFields()` at `field._meta.ui`,\n * and emitted to the browser in generated web-collection definitions. Has no\n * schema, persistence, or security effect; `sensitive`/`readPermission`\n * remain the security rail.\n */\n ui?: FieldUIHints;\n /**\n * Controls whether the field is included in JSON exports.\n * - `true`: Always exported (unless site explicitly excludes it)\n * - `false`: Never exported (cannot be overridden by site config)\n * - `undefined`: Uses site's fieldExportDefault setting\n */\n exported?: boolean;\n}\n\n/**\n * Options for text fields\n */\nexport interface TextFieldOptions extends FieldOptions {\n /** Minimum length for text fields */\n minLength?: number;\n /** Maximum length for text fields */\n maxLength?: number;\n /** Regex pattern for validation */\n pattern?: RegExp | string;\n}\n\n/**\n * Options for numeric fields\n */\nexport interface NumericFieldOptions extends FieldOptions {\n /** Minimum value */\n min?: number;\n /** Maximum value */\n max?: number;\n}\n\n/**\n * Options for relationship fields\n */\nexport interface RelationshipFieldOptions extends FieldOptions {\n /** Related class name */\n related?: string;\n /** Foreign key field name */\n foreignKey?: string;\n /** Through table for many-to-many */\n through?: string;\n /** Source-side join column override for many-to-many relationships. */\n sourceKey?: string;\n /** Target-side join column override for many-to-many relationships. */\n targetKey?: string;\n /** Relationship type */\n type?: 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';\n /**\n * Relationship role (#3718 record page): `items` on a `@foreignKey` /\n * `@crossPackageRef` marks the declaring model as the line items of the\n * target, so record pages show its rows as a line-items table with a\n * totals block. Emitted on the generated web relationship edge.\n */\n role?: 'items';\n /**\n * What happens to this row when the referenced object is deleted (#2371).\n *\n * Same-package references emit this DB-level action and `SmrtObject.delete()`\n * enforces the same policy in the application layer:\n *\n * - `'CASCADE'` — this row is deleted with the target.\n * - `'SET NULL'` — this column is set to `NULL`. The field must be nullable;\n * declaring it on a `required` field throws `ConfigurationError`.\n * - `'RESTRICT'` — deleting the target throws `DatabaseError` while any row\n * still points at it.\n *\n * When omitted, a column that is part of this class's `conflictColumns`\n * (junction and association rows, which are *identified* by the reference)\n * defaults to `CASCADE`; every other same-package column defaults to\n * immediate `NO ACTION`.\n * `@tenantId()` fields are the one exception: `smrt-tenancy` leads a\n * tenant-scoped class's default `conflictColumns` with the tenant column,\n * but that column scopes ownership rather than identifying the row, so it\n * is never defaulted to `CASCADE` — deleting a `Tenant` must not cascade\n * through every tenant-scoped table.\n *\n * Cascaded rows are removed set-based: their `beforeDelete`/`afterDelete`\n * hooks and interceptors do not run and no change-feed entry is written, the\n * same as a database-level `ON DELETE CASCADE`.\n */\n onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';\n /** Database action when the referenced key changes; defaults to CASCADE. */\n onUpdate?: RelationshipFieldOptions['onDelete'];\n /**\n * Set to `false` only for an app-side relationship whose target identifier\n * must deliberately survive without a database parent row (for example an\n * immutable audit/event record retained after its parent is pruned).\n *\n * The field remains a typed `foreignKey` relationship for loading and\n * indexes, but no physical DDL constraint, schema dependency, or app-side\n * delete action is emitted. The stored identifier is deliberately preserved\n * after the parent is deleted. Ordinary same-package relationships must leave\n * this enabled (the default).\n */\n constraint?:\n | boolean\n | {\n /**\n * Database engines on which SMRT emits the physical constraint.\n *\n * Relationship loading, UUID storage, indexes, and application-side\n * delete enforcement remain active on every engine. Use this narrow\n * allowlist only when an engine cannot faithfully enforce a supported\n * relationship shape (for example a DuckDB self-reference).\n */\n engines: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'>;\n };\n}\n\n/**\n * Options specific to cross-package references.\n */\nexport interface CrossPackageRefOptions\n extends Omit<RelationshipFieldOptions, 'related' | 'type'> {\n /**\n * Storage type for the referenced target id. Defaults to 'uuid'.\n *\n * Use 'text' only when the external target model declares\n * `@smrt({ idType: 'text' })`.\n */\n idType?: 'uuid' | 'text';\n\n /**\n * When `true`, the framework verifies the referenced object exists at save time.\n * Validation uses the target package's manifest (loaded on demand via\n * `ObjectRegistry.ensureManifestLoaded()`), so this requires the target manifest\n * to be discoverable at runtime.\n *\n * Empty/null values are always allowed (treated as \"no reference set\").\n *\n * Defaults to `false` — same behavior as a plain string field today.\n */\n validate?: boolean;\n}\n\n/**\n * Resolve a relationship decorator's target argument to a class name.\n *\n * Accepted target forms:\n * - `'Target'` — class name string; resolves lazily, immune to import cycles.\n * - `Target` — class constructor.\n * - `() => Target` — thunk (inline or a named `const`), invoked here to read\n * the name, so its target must already be initialized.\n *\n * A thunk's own `.name` is `''`, so reading `relatedClass.name` used to register\n * `related: ''` (issue #2379): the field kept `type: 'foreignKey'` but lost its\n * target, which silently dropped the relationship edge, `loadRelated()`, and any\n * FK-derived index. Thunks are therefore invoked at decoration time and an\n * unresolvable target throws with the string form as the remedy — an empty\n * `related` is never registered.\n *\n * Resolution runs inside the field-registration callback, which is the latest\n * point in the decorator lifecycle (after the class binding exists for legacy\n * decorators, and at `@smrt()` application time for standard decorators), so a\n * self-referential `() => Self` thunk resolves rather than hitting the TDZ.\n * A thunk pointing at a class declared LATER in the same module is still in\n * that class's temporal dead zone when the decorators of the earlier class run;\n * that now fails loudly, naming the string form, instead of silently\n * registering an empty target.\n */\nfunction resolveRelatedClassName(\n decoratorName: 'foreignKey' | 'oneToMany' | 'manyToMany',\n relatedClass: string | Function,\n className: string,\n propertyKey: string,\n): { related: string; relatedConstructor?: Function } {\n const where = `@${decoratorName}() on ${className}.${propertyKey}`;\n const remedy =\n `Pass the target class name as a string instead — ` +\n `\\`@${decoratorName}('Target')\\` resolves lazily and is immune to import cycles.`;\n\n if (typeof relatedClass === 'string') {\n const name = relatedClass.trim();\n if (!name) {\n throw new Error(\n `${where}: target class name is empty. Pass a class, a class name, or a \\`() => Target\\` thunk.`,\n );\n }\n return { related: name };\n }\n\n if (typeof relatedClass !== 'function') {\n throw new Error(\n `${where}: expected a class, a class name, or a \\`() => Target\\` thunk, received ${relatedClass === null ? 'null' : typeof relatedClass}. ${remedy}`,\n );\n }\n\n // Class/function reference — the common `@foreignKey(Target)` form. Arrow\n // functions have no `prototype`, so a *named* thunk (`const lazyTarget = () =>\n // Target`) is still routed to the thunk branch below instead of registering\n // the variable name as the target class.\n if (relatedClass.name && relatedClass.prototype !== undefined) {\n return { related: relatedClass.name, relatedConstructor: relatedClass };\n }\n\n // Thunk (`() => Target`, named or inline) — invoke it for the target.\n let resolved: unknown;\n try {\n resolved = (relatedClass as () => unknown)();\n } catch (error) {\n throw new Error(\n `${where}: the \\`() => Target\\` thunk threw while resolving its target (${\n error instanceof Error ? error.message : String(error)\n }). ${remedy}`,\n { cause: error },\n );\n }\n\n if (typeof resolved === 'function' && resolved.name) {\n return { related: resolved.name, relatedConstructor: resolved };\n }\n if (typeof resolved === 'string' && resolved.trim()) {\n return { related: resolved.trim() };\n }\n\n throw new Error(\n `${where}: the \\`() => Target\\` thunk resolved to ${\n resolved === null ? 'null' : typeof resolved\n } instead of a named class. ${remedy}`,\n );\n}\n\n/**\n * Marks a class property with validation constraints and metadata options.\n *\n * Use `@field()` when you need options beyond what plain TypeScript initializers\n * express — required validation, numeric ranges, string length limits, uniqueness,\n * or transient (non-persisted) computed properties.\n *\n * For plain persisted fields with no constraints, no decorator is needed: just\n * declare the property with a TypeScript initializer and the framework will infer\n * the column type from the default value (`0` → INTEGER, `0.0` → DECIMAL, `''` → TEXT).\n *\n * @param options - Field configuration options\n * @param options.required - If `true`, `save()` throws `ValidationError` when empty/null\n * @param options.unique - Enforces a UNIQUE database constraint\n * @param options.nullable - If `true`, the column accepts NULL (default depends on type)\n * @param options.transient - If `true`, the property is not persisted to the database\n * @param options.default - Default value applied at the database level\n * @param options.description - Human-readable description used in generated API docs\n * @param options.exported - Controls JSON export visibility (see `FieldOptions`)\n * @returns A TypeScript property decorator\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * @field({ required: true, maxLength: 100 })\n * name: string = '';\n *\n * @field({ min: 0 })\n * stock: number = 0;\n *\n * @field({ transient: true })\n * get displayPrice(): string { return `$${this.price.toFixed(2)}`; }\n * }\n * ```\n *\n * @see {@link meta} for STI child-specific fields stored in `_meta_data` JSON\n * @see {@link foreignKey} for typed relationship fields\n */\nexport function field(\n options: FieldOptions | NumericFieldOptions | TextFieldOptions = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n options,\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a many-to-one (foreign key) relationship to another `SmrtObject` class.\n *\n * The decorated property stores the UUID of the related object. At runtime, call\n * `instance.loadRelated('fieldName')` to lazy-load (and cache) the related object,\n * or pass `include: ['fieldName']` to `collection.list()` for batch eager loading.\n *\n * Cross-package rule: Use `@foreignKey()` only for same-package references.\n * Use `@crossPackageRef()` for cross-package relationships.\n *\n * A named database constraint is emitted on supported engines by default, and\n * the same action is enforced by `SmrtObject.delete()` before the engine sees\n * it. Exceptional archival/audit references that intentionally outlive their\n * parent may declare `{ constraint: false }`; they retain relationship loading\n * and indexing while deliberately preserving the identifier after parent\n * deletion and omitting physical DDL. The\n * decorator also enables `loadRelated()` and eager `include:` loading:\n *\n * ```typescript\n * @foreignKey(Order, { onDelete: 'CASCADE' }) // deleted with the order\n * orderId: string = '';\n *\n * @foreignKey(Customer, { onDelete: 'RESTRICT' }) // blocks the customer delete\n * customerId: string = '';\n * ```\n *\n * Without an `onDelete`, a column that is part of this class's\n * `conflictColumns` defaults to `CASCADE` (this is what cleans up junction\n * rows); any other column defaults to immediate `NO ACTION`.\n *\n * @param relatedClass - The target class constructor, its name as a string, or a\n * `() => Target` thunk. A thunk is **invoked at decoration time**, so its\n * target must already be initialized: a class from an already-evaluated module\n * or the decorated class itself. Use the string form for a class declared\n * later in the same module or reached through an import cycle — it is never\n * evaluated, so it cannot hit the temporal dead zone.\n * @param options - Optional field constraints (required, nullable, etc.)\n * @returns A TypeScript property decorator\n * @throws Error when the target cannot be resolved to a class name (an empty\n * string, a thunk that throws — including on an uninitialized target — or a\n * thunk returning an anonymous value)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Order extends SmrtObject {\n * // Same-package FK — enables loadRelated() and eager loading\n * @foreignKey(Customer)\n * customerId: string = '';\n *\n * // Self-reference: the class binding exists when its decorators run\n * @foreignKey(() => Order)\n * parentOrderId: string = '';\n *\n * // Declared later in this module — name string, never evaluated\n * @foreignKey('Invoice')\n * invoiceId: string = '';\n *\n * // Physical constraint only where the engine can enforce this shape;\n * // relationship loading and app-side delete policy remain cross-engine.\n * @foreignKey('Order', {\n * constraint: { engines: ['postgres', 'sqlite'] },\n * })\n * hierarchyParentId: string | null = null;\n * }\n *\n * // Cross-package: runtime relationship, index, and loading; no physical FK\n * @smrt()\n * class Post extends SmrtObject {\n * @crossPackageRef('@happyvertical/smrt-users:User')\n * authorId: string = '';\n * }\n * ```\n *\n * @see {@link oneToMany} for the inverse (parent) side of the relationship\n * @see SmrtObject.loadRelated for lazy-loading the related object at runtime\n */\nexport function foreignKey(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'foreignKey',\n ...resolveRelatedClassName(\n 'foreignKey',\n relatedClass,\n className,\n propertyKey,\n ),\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a cross-package foreign key reference.\n *\n * Use this for relationships that point to a `SmrtObject` in a *different* package\n * (e.g. `Customer.profileId` pointing at `@happyvertical/smrt-profiles:Profile`).\n *\n * Unlike same-package `@foreignKey()`, this emits **no** DDL `FOREIGN KEY`\n * constraint, keeping package schemas independently installable. The property is a\n * `UUID` column on PostgreSQL/DuckDB and `TEXT` on SQLite, matching the target's\n * id type; pass `{ idType: 'text' }` when the target declares\n * `@smrt({ idType: 'text' })`.\n *\n * What you get over a plain string field:\n * - The relationship is registered with the `ObjectRegistry`, so `loadRelated()`\n * and `Collection.list({ include })` can resolve it once the target package's\n * manifest is loaded.\n * - No physical database constraint is emitted; package schemas remain\n * independently installable.\n * - Optional save-time validation (`validate: true`) confirms the referenced\n * object exists, catching typos and stale IDs before they hit the database.\n * - `onDelete` is honoured by `SmrtObject.delete()` when the target package's\n * manifest is loaded in the same runtime.\n *\n * The `qualifiedName` is a fully-qualified class identifier in the form\n * `@package/scope:ClassName` — for example `@happyvertical/smrt-profiles:Profile`.\n *\n * @param qualifiedName - Qualified name of the target class\n * @param options - Optional field constraints and `validate` flag\n * @returns A TypeScript property decorator\n *\n * @example\n * ```typescript\n * @smrt()\n * class Customer extends SmrtObject {\n * @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n * profileId: string = '';\n *\n * // With save-time validation\n * @crossPackageRef('@happyvertical/smrt-profiles:Profile', { validate: true })\n * primaryContactId: string = '';\n * }\n * ```\n *\n * @see {@link foreignKey} for same-package relationships\n * @see SmrtObject.loadRelated for runtime resolution\n */\nexport function crossPackageRef(\n qualifiedName: string,\n options: CrossPackageRefOptions = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'crossPackageRef',\n related: qualifiedName,\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a one-to-many relationship from this object to a collection of related objects.\n *\n * The decorated property is `transient` — it is not persisted as a database column.\n * At runtime, call `instance.loadRelatedMany('fieldName')` to load the related objects,\n * or pass `include: ['fieldName']` to `collection.list()` for batch eager loading (issues\n * a single batched query for all instances instead of N individual queries).\n *\n * The inverse side (`@foreignKey`) must exist on the `relatedClass` pointing back to this\n * class. The framework discovers it automatically via `ObjectRegistry.getInverseRelationships()`.\n *\n * **Generated accessor (R10):** registering the class installs a consistent\n * `get<FieldName>()` instance method (e.g. `items` → `order.getItems()`) that\n * delegates to `loadRelatedMany('items')`. Generation is additive — a\n * hand-rolled method of the same name is never overwritten.\n *\n * **Disambiguation:** when `relatedClass` declares more than one `@foreignKey`\n * back to this class, pass `{ foreignKey: '<inverseFieldName>' }` so both\n * `loadRelatedMany` and the generated accessor resolve the intended inverse\n * side. Without it the first matching foreign key is used.\n *\n * **Delete behaviour is declared on the child, not here.** `@oneToMany` is a\n * transient read-side accessor; to have children removed with their parent,\n * put `onDelete: 'CASCADE'` on the inverse `@foreignKey` (#2371):\n *\n * ```typescript\n * class OrderItem extends SmrtObject {\n * @foreignKey(Order, { onDelete: 'CASCADE' })\n * orderId: string = '';\n * }\n * ```\n *\n * @param relatedClass - The class constructor of the child/related objects\n * @param options - Optional relationship options. `foreignKey` selects the\n * inverse foreign-key field on `relatedClass` when it has more than one.\n * @returns A TypeScript property decorator (sets `transient: true` automatically)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Order extends SmrtObject {\n * @oneToMany(OrderItem)\n * items: OrderItem[] = [];\n * }\n *\n * @smrt()\n * class OrderItem extends SmrtObject {\n * @foreignKey(Order)\n * orderId: string = '';\n * }\n *\n * const order = await orders.get({ id });\n * const items = await order.getItems(); // generated; === loadRelatedMany('items')\n * ```\n *\n * @example\n * ```typescript\n * // Multiple inverse foreign keys → disambiguate explicitly.\n * @smrt()\n * class Profile extends SmrtObject {\n * @oneToMany(ProfileRelationship, { foreignKey: 'fromProfileId' })\n * relationshipsFrom: ProfileRelationship[] = [];\n * @oneToMany(ProfileRelationship, { foreignKey: 'toProfileId' })\n * relationshipsTo: ProfileRelationship[] = [];\n * }\n * ```\n *\n * @see {@link foreignKey} for the many-to-one (child) side of the relationship\n * @see SmrtObject.loadRelatedMany for lazy-loading at runtime\n */\nexport function oneToMany(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'oneToMany',\n ...resolveRelatedClassName(\n 'oneToMany',\n relatedClass,\n className,\n propertyKey,\n ),\n transient: true, // Relationship fields are not database columns\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Declares a many-to-many relationship between two `SmrtObject` classes via a join table.\n *\n * The decorated property is `transient` — it is not persisted as a database column.\n * The `through` option specifies the junction table name. The join table model must\n * be decorated with `@smrt({ conflictColumns: ['...', '...'] })` to use the natural\n * key columns for upsert operations.\n *\n * Runtime loading: call `instance.loadRelatedMany('field')` to lazy-load, or\n * pass `include: ['field']` to `collection.list()` for batched eager loading.\n *\n * @param relatedClass - The class constructor of the related objects\n * @param options - Relationship options; `through` specifies the junction table name\n * @returns A TypeScript property decorator (sets `transient: true` automatically)\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * @manyToMany(Tag, { through: 'product_tags' })\n * tags: Tag[] = [];\n * }\n * ```\n *\n * @see {@link oneToMany} for one-to-many relationships\n */\nexport function manyToMany(\n relatedClass: string | Function,\n options: Omit<RelationshipFieldOptions, 'related'> = {},\n) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'manyToMany',\n ...resolveRelatedClassName(\n 'manyToMany',\n relatedClass,\n className,\n propertyKey,\n ),\n transient: true, // Relationship fields are not database columns\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Marks a field as a Single Table Inheritance (STI) meta field.\n *\n * Meta fields are stored in the `_meta_data` JSONB column on the shared STI\n * table rather than as dedicated table columns. Use this decorator for fields\n * that are specific to an STI child class and should not pollute the shared\n * table schema with child-specific columns.\n *\n * The `@smrt({ tableStrategy: 'sti' })` decorator must be set on the base class.\n * All child-specific fields should use `@meta()` (or the `Meta<T>` type alias).\n *\n * @param options - Standard field options (required, nullable, description, etc.)\n * @returns A TypeScript property decorator (registers field with `type: 'meta'`)\n *\n * @example\n * ```typescript\n * @smrt({ tableStrategy: 'sti' })\n * class Event extends SmrtObject {\n * title: string = ''; // shared column on events table\n * }\n *\n * @smrt()\n * class Meeting extends Event {\n * @meta()\n * roomNumber: string = ''; // stored in _meta_data JSON, not a column\n *\n * @meta({ required: true })\n * durationMinutes: number = 60;\n * }\n * ```\n *\n * @see {@link Meta} for the equivalent type alias approach\n * @see {@link field} for regular (non-STI) field declarations\n */\nexport function meta(options: FieldOptions = {}) {\n return ((\n targetOrValue: LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext: CompatiblePropertyDecoratorContext<unknown, unknown>,\n ) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey, ctor) => {\n registerFieldDecoratorForCompatibility(\n className,\n propertyKey,\n {\n ...options,\n type: 'meta', // Mark this field as a meta field for STI\n },\n ctor,\n );\n },\n );\n }) as CompatiblePropertyDecorator;\n}\n\n/**\n * Options for {@link method}.\n *\n * Every option is optional, and an omitted one falls back to the class-level\n * `api.routes[methodName]` entry (or `ai.descriptions[methodName]` for\n * `description`) when one exists. The merge is FIELD BY FIELD, so adding\n * `@method({ description })` to a class that already declares a route verb and\n * path does not reset them (#2686).\n */\nexport interface MethodOptions {\n /**\n * Override the default routing decision.\n *\n * Public methods are routed by default when they are WIRE-ABLE: every\n * parameter can be built from a JSON request body or query string. Set\n * `false` to withhold a method the heuristic accepted, `true` to expose one\n * it rejected.\n *\n * `true` bypasses the heuristic and NOTHING else. It cannot manufacture a\n * receiver, undo `api: false`, cross an `include`/`exclude` boundary, reach a\n * non-public method, claim a CRUD verb, or hydrate a value the transport\n * cannot build — a model-instance parameter still arrives as whatever JSON\n * the caller sent.\n */\n expose?: boolean;\n\n /**\n * Why the method is withheld. Recorded in the knowledge artifact so\n * `smrt doctor` and agents read an explanation instead of silence.\n */\n reason?: string;\n\n /**\n * HTTP verb for the generated route. Defaults to `POST`.\n *\n * Named `httpMethod`, not `method`: RFC 9110 calls this the request\n * \"method\", so the protocol word is exactly the one that collides with the\n * decorator's own name, and `@method({ method: 'POST' })` stutters.\n * `httpMethod` is also already this framework's name for it — `ApiHttpMethod`\n * is carried across the whole discovery/invoke path.\n *\n * Migrates from `api.routes[methodName].method`.\n */\n httpMethod?: ApiHttpMethod;\n\n /**\n * Route path segment(s), used verbatim and split on `/`.\n * Defaults to the method name. Migrates from `api.routes[methodName].path`.\n */\n path?: string;\n\n /**\n * Declared receiver scope. Migrates from `api.routes[methodName].scope`.\n *\n * A DECLARATION checked against the method's executable receiver, not a\n * relocation: an instance method is item-scoped and a static (or\n * collection-class) method is collection-scoped, and no config can change\n * that. A contradicting value is reported at build time and ignored.\n */\n scope?: CustomActionScope;\n\n /**\n * Agent-visible effect classification. Omitted actions are treated as\n * `destructive`, so missing metadata can never widen browser capability\n * exposure. Migrates from `api.routes[methodName].effect`.\n */\n effect?: ToolEffect;\n\n /** Whether repeating the action with the same arguments is safe. */\n idempotent?: boolean;\n\n /** Whether the action may interact outside the SMRT application. */\n openWorld?: boolean;\n\n /**\n * Description used by AI/tool surfaces, overriding the method's JSDoc.\n * Migrates from `ai.descriptions[methodName]`.\n */\n description?: string;\n}\n\n/**\n * Refines how a method is exposed on generated surfaces.\n *\n * The method counterpart of {@link field}, and symmetric with it: a manifest\n * records exactly two collections per object, `fields` and `methods`. Neither\n * decorator DECLARES its member — a property is a field because it is a\n * property, and a method is a candidate action because it is public — both\n * REFINE what the framework already inferred. `@field()` refines an inferred\n * column type and constraints; `@method()` refines inferred exposure, its\n * route shape, and its tool semantics.\n *\n * The name is `@method()` rather than `@action()` because of the negative\n * case: withholding a method has to read correctly, and\n * `@action({ expose: false })` contradicts itself — declaring something an\n * action in order to say it is not one. \"Action\" keeps its established meaning\n * in the generators (a non-CRUD method exposed on a surface); this decorator\n * is how a method BECOMES one.\n *\n * Metadata only: the decorated method is not wrapped and keeps its identity,\n * so it stays directly callable.\n *\n * @example Shape the generated route\n * ```typescript\n * @method({ httpMethod: 'POST', path: 'reviews', effect: 'write' })\n * async runReview(options?: RunContentReviewOptions) { … }\n * ```\n *\n * @example Withhold a method the heuristic would have routed\n * ```typescript\n * @method({ expose: false, reason: 'callback registration, not a wire operation' })\n * static registerValidator(validator: ValidatorFunction) { … }\n * ```\n *\n * @example Force a route the heuristic rejected\n * ```typescript\n * // `expose: true` bypasses the heuristic only — this method must still\n * // accept whatever JSON the caller sends for `asset`.\n * @method({ expose: true })\n * async addAsset(asset: Asset) { … }\n * ```\n *\n * @see {@link field} for the property-side refinement decorator\n */\nexport function method(options: MethodOptions = {}) {\n return ((\n targetOrValue: unknown,\n propertyKeyOrContext: CompatibleMethodDecoratorContext<\n unknown,\n // biome-ignore lint/suspicious/noExplicitAny: mirrors the lib's own `ClassMethodDecoratorContext` constraint; see `AnyMethodOf` in `compatibility.ts`. S4 #1579.\n (this: unknown, ...args: any) => any\n >,\n ) => {\n registerCompatibleMethodDecorator(\n targetOrValue as LegacyPropertyDecoratorTarget | undefined,\n propertyKeyOrContext,\n (ctor, methodName, isStatic) => {\n ObjectRegistry.registerMethodDecorator(\n ctor,\n methodName,\n options,\n isStatic,\n );\n },\n );\n }) as CompatibleMethodDecorator;\n}\n"],"mappings":";;;AAgCA,SAAS,uCACP,WACA,aACA,SACA,MACM;CACN,eAAe,uBAAuB,WAAW,aAAa,SAAS,IAAI;AAC7E;;;;;;;;;;;;;;;;;;;;;;;;;;AA4SA,SAAS,wBACP,eACA,cACA,WACA,aACoD;CACpD,MAAM,QAAQ,IAAI,cAAc,QAAQ,UAAU,GAAG;CACrD,MAAM,SACJ,uDACM,cAAc;CAEtB,IAAI,OAAO,iBAAiB,UAAU;EACpC,MAAM,OAAO,aAAa,KAAK;EAC/B,IAAI,CAAC,MACH,MAAM,IAAI,MACR,GAAG,MAAM,uFACX;EAEF,OAAO,EAAE,SAAS,KAAK;CACzB;CAEA,IAAI,OAAO,iBAAiB,YAC1B,MAAM,IAAI,MACR,GAAG,MAAM,0EAA0E,iBAAiB,OAAO,SAAS,OAAO,aAAa,IAAI,QAC9I;CAOF,IAAI,aAAa,QAAQ,aAAa,cAAc,KAAA,GAClD,OAAO;EAAE,SAAS,aAAa;EAAM,oBAAoB;CAAa;CAIxE,IAAI;CACJ,IAAI;EACF,WAAY,aAA+B;CAC7C,SAAS,OAAO;EACd,MAAM,IAAI,MACR,GAAG,MAAM,iEACP,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EACtD,KAAK,UACN,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,IAAI,OAAO,aAAa,cAAc,SAAS,MAC7C,OAAO;EAAE,SAAS,SAAS;EAAM,oBAAoB;CAAS;CAEhE,IAAI,OAAO,aAAa,YAAY,SAAS,KAAK,GAChD,OAAO,EAAE,SAAS,SAAS,KAAK,EAAE;CAGpC,MAAM,IAAI,MACR,GAAG,MAAM,2CACP,aAAa,OAAO,SAAS,OAAO,SACrC,6BAA6B,QAChC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,MACd,UAAiE,CAAC,GAClE;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA,SACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,WACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,cACA,cACA,WACA,WACF;GACF,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,gBACd,eACA,UAAkC,CAAC,GACnC;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,SAAS;GACX,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,UACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,aACA,cACA,WACA,WACF;IACA,WAAW;GACb,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,WACd,cACA,UAAqD,CAAC,GACtD;CACA,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;IACN,GAAG,wBACD,cACA,cACA,WACA,WACF;IACA,WAAW;GACb,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,KAAK,UAAwB,CAAC,GAAG;CAC/C,SACE,eACA,yBACG;EACH,iCACE,eACA,uBACC,WAAW,aAAa,SAAS;GAChC,uCACE,WACA,aACA;IACE,GAAG;IACH,MAAM;GACR,GACA,IACF;EACF,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8HA,SAAgB,OAAO,UAAyB,CAAC,GAAG;CAClD,SACE,eACA,yBAKG;EACH,kCACE,eACA,uBACC,MAAM,YAAY,aAAa;GAC9B,eAAe,wBACb,MACA,YACA,SACA,QACF;EACF,CACF;CACF;AACF"}
|
|
@@ -12,5 +12,6 @@ export { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRes
|
|
|
12
12
|
export type { OpenAPIConfig } from "./swagger.js";
|
|
13
13
|
export { generateOpenAPISpec, setupSwaggerUI, } from "./swagger.js";
|
|
14
14
|
export { runWithTenantGate, setTenantEntryPointRunner, type TenantEntryPointRunner, type TenantGateOptions, } from "./tenant-gate.js";
|
|
15
|
+
export { fieldTypeToJsonSchema, type ToolFieldMeta, type ToolJsonSchema, } from "./tool-schema.js";
|
|
15
16
|
export { normalizeTypedHttpError, type TypedHttpFailure, } from "./typed-http-error.js";
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,sBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,wBAAwB,EACxB,gCAAgC,EAChC,8BAA8B,EAC9B,uBAAuB,EACvB,wBAAwB,EACxB,6BAA6B,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,eAAe,EACf,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,4BAA4B,EAC5B,qBAAqB,EACrB,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,4BAA4B,EAC5B,8BAA8B,EAC9B,qCAAqC,EACrC,KAAK,UAAU,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,oBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,mBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,UAAO,CAAC;AAEf,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,2BAA2B,EAC3B,YAAY,GACb,MAAM,UAAO,CAAC;AAIf,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,oBAAoB,EACpB,iBAAiB,EACjB,6BAA6B,EAC7B,gCAAgC,EAChC,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,sBAAmB,CAAC;AAC3B,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAQ,CAAC;AAEtE,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,GAChB,MAAM,WAAQ,CAAC;AAChB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,cAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,kBAAe,CAAC;AAEvB,OAAO,EACL,uBAAuB,EACvB,KAAK,gBAAgB,GACtB,MAAM,uBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,sBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,wBAAwB,EACxB,gCAAgC,EAChC,8BAA8B,EAC9B,uBAAuB,EACvB,wBAAwB,EACxB,6BAA6B,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,eAAe,EACf,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,4BAA4B,EAC5B,qBAAqB,EACrB,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,4BAA4B,EAC5B,8BAA8B,EAC9B,qCAAqC,EACrC,KAAK,UAAU,EACf,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,oBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,mBAAgB,CAAC;AACxB,YAAY,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,OAAO,EACP,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,UAAO,CAAC;AAEf,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,2BAA2B,EAC3B,YAAY,GACb,MAAM,UAAO,CAAC;AAIf,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,oBAAoB,EACpB,iBAAiB,EACjB,6BAA6B,EAC7B,gCAAgC,EAChC,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,sBAAmB,CAAC;AAC3B,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAQ,CAAC;AAEtE,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,GAChB,MAAM,WAAQ,CAAC;AAChB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,cAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,kBAAe,CAAC;AAIvB,OAAO,EACL,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,kBAAe,CAAC;AAEvB,OAAO,EACL,uBAAuB,EACvB,KAAK,gBAAgB,GACtB,MAAM,uBAAoB,CAAC"}
|
package/dist/generators/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import { PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEta
|
|
|
2
2
|
import { CRUD_OPERATIONS, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildCustomActionInputSchema, buildCustomActionInvocationArgs, classifyMethodWireability, classifyParameterWireability, coerceCustomActionArgument, createClassNamePredicate, createManifestClassNamePredicate, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, isCrudOperation, isCrudToolAction, normalizeCustomActionFailure, queryStringDecoderFor, readMethodDecoratorConfig, resolveApiMethodExposure, resolveCustomActionMetadata, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber } from "./custom-action.js";
|
|
3
3
|
import { DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, buildChangeEventStream, changeEventSubscribersAtCapacity, eventStreamCapacityExceededResponse, normalizeEventsMaxSubscribers, signalVisibleToTenant, tryReserveChangeEventSubscriberSlot } from "./events-route.js";
|
|
4
4
|
import { runWithTenantGate, setTenantEntryPointRunner } from "./tenant-gate.js";
|
|
5
|
+
import { fieldTypeToJsonSchema } from "./tool-schema.js";
|
|
5
6
|
import { MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, resolveMCPToolListCacheHint, sortMCPTools } from "./mcp.js";
|
|
6
7
|
import { PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, handlePlaybookPreflightRoute, isApiActionEnabledForObject, isRestActionRoutable, isRestRoutePublic, resolveRegisteredObjectName, restFieldReadPermissions, restMethodForApiAction } from "./preflight-route.js";
|
|
7
8
|
import { normalizeTypedHttpError } from "./typed-http-error.js";
|
|
8
9
|
import { APIGenerator, computeRuntimeWebManifestHash, createRestServer, startRestServer } from "./rest.js";
|
|
9
10
|
import { generateOpenAPISpec, setupSwaggerUI } from "./swagger.js";
|
|
10
|
-
export { APIGenerator, CRUD_OPERATIONS, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, classifyMethodWireability, classifyParameterWireability, coerceCustomActionArgument, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createClassNamePredicate, createManifestClassNamePredicate, createRestServer, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, eventStreamCapacityExceededResponse, generateOpenAPISpec, handlePlaybookPreflightRoute, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, isApiActionEnabledForObject, isCrudOperation, isCrudToolAction, isRestActionRoutable, isRestRoutePublic, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, normalizeTypedHttpError, queryStringDecoderFor, readMethodDecoratorConfig, resolveApiMethodExposure, resolveCustomActionMetadata, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata, resolveMCPToolListCacheHint, resolveReadCacheControl, resolveRegisteredObjectName, resolveTenantEtagDiscriminator, restFieldReadPermissions, restMethodForApiAction, runWithTenantGate, setTenantEntryPointRunner, setupSwaggerUI, signalVisibleToTenant, sortMCPTools, startRestServer, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
11
|
+
export { APIGenerator, CRUD_OPERATIONS, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, PRIVATE_READ_CACHE_CONTROL, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, canonicalReadRepresentation, changeEventSubscribersAtCapacity, classifyMethodWireability, classifyParameterWireability, coerceCustomActionArgument, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, createClassNamePredicate, createManifestClassNamePredicate, createRestServer, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, eventStreamCapacityExceededResponse, fieldTypeToJsonSchema, generateOpenAPISpec, handlePlaybookPreflightRoute, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, isApiActionEnabledForObject, isCrudOperation, isCrudToolAction, isRestActionRoutable, isRestRoutePublic, normalizeCustomActionFailure, normalizeEventsMaxSubscribers, normalizeTypedHttpError, queryStringDecoderFor, readMethodDecoratorConfig, resolveApiMethodExposure, resolveCustomActionMetadata, resolveDeclaredScopeMismatch, resolveEffectiveActionMetadata, resolveMCPToolListCacheHint, resolveReadCacheControl, resolveRegisteredObjectName, resolveTenantEtagDiscriminator, restFieldReadPermissions, restMethodForApiAction, runWithTenantGate, setTenantEntryPointRunner, setupSwaggerUI, signalVisibleToTenant, sortMCPTools, startRestServer, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber, tryReserveChangeEventSubscriberSlot, versionConditionalResponse, warnIfSharedCacheNeutralized };
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Error handling and logging
|
|
10
10
|
* - Graceful shutdown
|
|
11
11
|
*/
|
|
12
|
+
import type { ApiSerializerReference } from '../registry/types.js';
|
|
12
13
|
import type { CustomActionScope } from './custom-action.js';
|
|
13
14
|
import type { MCPConfig, MCPContext } from './mcp.js';
|
|
14
15
|
import type { McpToolTarget } from './mcp-tool-name.js';
|
|
@@ -48,6 +49,8 @@ export interface RuntimeOptions {
|
|
|
48
49
|
}>;
|
|
49
50
|
/** Explicit original execution targets for generated protocol aliases. */
|
|
50
51
|
toolTargets?: Record<string, McpToolTarget>;
|
|
52
|
+
/** Async serializers for standard get tools, keyed by emitted tool name. */
|
|
53
|
+
getSerializers?: Record<string, ApiSerializerReference>;
|
|
51
54
|
/** Cache hint emitted for deploy-static tools/list results. */
|
|
52
55
|
toolListCacheHint?: {
|
|
53
56
|
ttlMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASxD,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kCAAkC;IAClC,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvC,WAAW,CAAC,EAAE;YACZ,YAAY,EAAE,OAAO,CAAC;YACtB,eAAe,EAAE,OAAO,CAAC;YACzB,cAAc,EAAE,OAAO,CAAC;YACxB,aAAa,EAAE,OAAO,CAAC;SACxB,CAAC;QACF,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,KAAK,CAAC;YACZ,GAAG,EAAE,MAAM,CAAC;YACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;YACjB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;SAC1B,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC,CAAC;IACH,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;KAClC,CAAC;IACF,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN;QACE,KAAK,EAAE,iBAAiB,CAAC;QACzB,QAAQ,EAAE,OAAO,CAAC;QAClB,sEAAsE;QACtE,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,OAAO,CAAC;KACxB,CACF,CAAC;IACF,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAClB,MAAM,EACN;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,qDAAqD;QACrD,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF,CAAC;IAEF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEpD;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;CACjD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"mcp-runtime-template.d.ts","sourceRoot":"","sources":["../../src/generators/mcp-runtime-template.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ function capitalize(str) {
|
|
|
24
24
|
* @returns TypeScript code for server entry point
|
|
25
25
|
*/
|
|
26
26
|
function generateRuntimeBootstrap(options = {}) {
|
|
27
|
-
const { name = "smrt-mcp-server", version = "1.0.0", description = "Auto-generated MCP server from SMRT objects", debug = false, tools = [], toolTargets: explicitToolTargets, customActions = {}, taskActions = {}, tenantScopedObjects = [], stiTargets = {}, listOrderBy = {}, toolListCacheHint = {
|
|
27
|
+
const { name = "smrt-mcp-server", version = "1.0.0", description = "Auto-generated MCP server from SMRT objects", debug = false, tools = [], toolTargets: explicitToolTargets, getSerializers = {}, customActions = {}, taskActions = {}, tenantScopedObjects = [], stiTargets = {}, listOrderBy = {}, toolListCacheHint = {
|
|
28
28
|
ttlMs: 864e5,
|
|
29
29
|
cacheScope: "private"
|
|
30
30
|
} } = options;
|
|
@@ -85,7 +85,7 @@ ${indent} if (!item) {
|
|
|
85
85
|
${indent} throw new Error('Object not found');
|
|
86
86
|
${indent} }
|
|
87
87
|
|
|
88
|
-
${indent} return successResult(
|
|
88
|
+
${indent} return successResult(await serializeGetResult(${JSON.stringify(tool.name)}, item));
|
|
89
89
|
${indent}}`;
|
|
90
90
|
case "create": return `${indent}case '${tool.name}': {
|
|
91
91
|
${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget(${JSON.stringify(objectName)}, ${JSON.stringify(lookup)}, args, aiConfig);
|
|
@@ -248,6 +248,20 @@ const PUBLIC_JSON_OPTIONS = {
|
|
|
248
248
|
.map((permission) => permission.trim())
|
|
249
249
|
.filter(Boolean),
|
|
250
250
|
};
|
|
251
|
+
const GET_SERIALIZERS: Record<string, { importPath: string; exportName: string }> = ${JSON.stringify(getSerializers)};
|
|
252
|
+
|
|
253
|
+
async function serializeGetResult(toolName: string, item: any): Promise<any> {
|
|
254
|
+
const reference = GET_SERIALIZERS[toolName];
|
|
255
|
+
if (!reference) return item.toPublicJSON(PUBLIC_JSON_OPTIONS);
|
|
256
|
+
const serializerModule = await import(reference.importPath);
|
|
257
|
+
const serializer = serializerModule[reference.exportName];
|
|
258
|
+
if (typeof serializer !== 'function') {
|
|
259
|
+
throw new Error(
|
|
260
|
+
\`MCP get serializer '\${reference.exportName}' was not exported by '\${reference.importPath}'\`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return toPublicResult(await serializer(item, PUBLIC_JSON_OPTIONS));
|
|
264
|
+
}
|
|
251
265
|
|
|
252
266
|
/**
|
|
253
267
|
* Page-bound guard (#2367). MCP tool arguments are untyped JSON, so \`limit\`
|
|
@@ -357,6 +371,17 @@ function errorResult(structuredContent: any, text: string, _meta?: Record<string
|
|
|
357
371
|
};
|
|
358
372
|
}
|
|
359
373
|
|
|
374
|
+
function normalizePublicToolError(error: any): { code: string; message: string } | undefined {
|
|
375
|
+
if (!error || typeof error !== 'object') return undefined;
|
|
376
|
+
if (
|
|
377
|
+
typeof error.code !== 'string' ||
|
|
378
|
+
!/^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/.test(error.code) ||
|
|
379
|
+
typeof error.publicMessage !== 'string' ||
|
|
380
|
+
error.publicMessage.length === 0
|
|
381
|
+
) return undefined;
|
|
382
|
+
return { code: error.code, message: error.publicMessage };
|
|
383
|
+
}
|
|
384
|
+
|
|
360
385
|
${hasTaskActions ? `
|
|
361
386
|
// The bundled SDK validates tools/call responses against its older result
|
|
362
387
|
// codec, which does not yet know CreateTaskResult. Intercept the extension at
|
|
@@ -678,11 +703,12 @@ ${hasTenantScoped ? `
|
|
|
678
703
|
|
|
679
704
|
return result;
|
|
680
705
|
} catch (error) {
|
|
681
|
-
const
|
|
706
|
+
const publicError = normalizePublicToolError(error);
|
|
707
|
+
const errorMessage = publicError?.message ?? (error instanceof Error ? error.message : 'Unknown error');
|
|
682
708
|
console.error(\`[MCP] Tool execution failed: \${toolName}\`, error);
|
|
683
709
|
|
|
684
710
|
return errorResult(
|
|
685
|
-
{ error: { message: errorMessage } },
|
|
711
|
+
{ error: publicError ?? { message: errorMessage } },
|
|
686
712
|
\`Error executing tool \${toolName}: \${errorMessage}\`,
|
|
687
713
|
);
|
|
688
714
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-runtime-template.js","names":[],"sources":["../../src/generators/mcp-runtime-template.ts"],"sourcesContent":["/**\n * Runtime bootstrap template for generated MCP servers\n *\n * This template provides stdio transport integration for SMRT-generated MCP servers.\n * It handles:\n * - Server initialization with @modelcontextprotocol/server v2\n * - Tool registration from MCPGenerator\n * - Stdio transport connection\n * - Error handling and logging\n * - Graceful shutdown\n */\n\nimport {\n DEFAULT_LIST_LIMIT,\n DEFAULT_LIST_ORDER_BY,\n MAX_LIST_LIMIT,\n} from '../query-bounds.js';\nimport type { CustomActionScope } from './custom-action.js';\nimport type { MCPConfig, MCPContext } from './mcp.js';\nimport type { McpToolTarget } from './mcp-tool-name.js';\n\n/**\n * Helper function to capitalize first letter\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport interface RuntimeOptions {\n /** Server name (defaults to package name) */\n name?: string;\n\n /** Server version (defaults to package version) */\n version?: string;\n\n /** Server description */\n description?: string;\n\n /** MCP generator configuration */\n config?: MCPConfig;\n\n /** MCP context (database, AI client, etc.) */\n context?: MCPContext;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Static tool definitions (generated at build time) */\n tools?: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n annotations?: {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n };\n title?: string;\n icons?: Array<{\n src: string;\n mimeType?: string;\n sizes?: string[];\n theme?: 'light' | 'dark';\n }>;\n _meta?: Record<string, unknown>;\n }>;\n /** Explicit original execution targets for generated protocol aliases. */\n toolTargets?: Record<string, McpToolTarget>;\n /** Cache hint emitted for deploy-static tools/list results. */\n toolListCacheHint?: {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n };\n /** Internal invocation metadata; never exposed by the MCP tools/list result. */\n customActions?: Record<\n string,\n {\n scope: CustomActionScope;\n isStatic: boolean;\n /** Declared method name; tool IDs are lowercased protocol aliases. */\n methodName?: string;\n parameterNames?: string[];\n optionsParameter?: boolean;\n legacyOptions: boolean;\n }\n >;\n /** Task-enabled item custom actions. Never emitted in tools/list. */\n taskActions?: Record<\n string,\n {\n objectName: string;\n objectType: string;\n /** Registry identity of the task's class (#3490). */\n registryKey?: string;\n }\n >;\n\n /**\n * Lowercased simple names of objects that are `@TenantScoped` (#1554). When\n * non-empty, the generated server imports the tenancy fail-closed gate and\n * wraps tenant-scoped tool calls so a stdio invocation cannot read across all\n * tenants. The tenant is sourced from `SMRT_MCP_TENANT_ID` /\n * `SMRT_MCP_ALLOW_CROSS_TENANT` env vars (this server has no auth principal).\n */\n tenantScopedObjects?: string[];\n\n /**\n * Build-time approved STI discriminators, keyed by the lowercased MCP object\n * prefix. The generated runtime uses this instead of searching an initially\n * empty registry, then loads the approved qualified type through the public\n * collection API.\n */\n stiTargets?: Record<string, Record<string, string>>;\n\n /**\n * Default list ordering per lowercased MCP object prefix (#2367).\n *\n * Baked in at generation time because the tiebreak follows the model's\n * declared primary key, which the emitted server cannot resolve on its own.\n * Prefixes absent from this map fall back to {@link DEFAULT_LIST_ORDER_BY}.\n */\n listOrderBy?: Record<string, readonly string[]>;\n}\n\n/**\n * Generate runtime bootstrap code for MCP server\n *\n * @param options - Runtime configuration options\n * @returns TypeScript code for server entry point\n */\nexport function generateRuntimeBootstrap(options: RuntimeOptions = {}): string {\n const {\n name = 'smrt-mcp-server',\n version = '1.0.0',\n description = 'Auto-generated MCP server from SMRT objects',\n debug = false,\n tools = [],\n toolTargets: explicitToolTargets,\n customActions = {},\n taskActions = {},\n tenantScopedObjects = [],\n stiTargets = {},\n listOrderBy = {},\n toolListCacheHint = { ttlMs: 86_400_000, cacheScope: 'private' },\n } = options;\n\n // Direct template callers retain their legacy object_action inputs. The\n // owning generator always supplies explicit targets, including valid aliases.\n const toolTargets: Record<string, McpToolTarget> =\n explicitToolTargets ??\n Object.fromEntries(\n tools.map((tool) => {\n const separator = tool.name.indexOf('_');\n return [\n tool.name,\n {\n objectName: capitalize(tool.name.slice(0, separator)),\n action: tool.name.slice(separator + 1),\n },\n ];\n }),\n );\n for (const tool of tools) {\n if (!Object.hasOwn(toolTargets, tool.name)) {\n throw new Error(`Missing MCP tool target: ${tool.name}`);\n }\n }\n\n // Generate static tool array as TypeScript code\n const toolsCode = tools.length > 0 ? JSON.stringify(tools, null, 2) : '[]';\n const hasTaskActions = Object.keys(taskActions).length > 0;\n\n // Fail-closed tenant context (#1554): only wire the tenancy gate when at\n // least one exposed object is tenant-scoped, so apps without tenancy never\n // get a dangling import.\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n // Generate static switch cases using shared helper\n const generateSwitchCases = (indent: string) => {\n return tools\n .map((tool) => {\n const objectName = toolTargets[tool.name].objectName.toLowerCase();\n const action = toolTargets[tool.name].action;\n // Registry identity for every emitted lookup (#3490).\n const lookup =\n toolTargets[tool.name].registryKey ??\n toolTargets[tool.name].objectName;\n\n switch (action) {\n case 'list':\n return `${indent}case '${tool.name}': {\n${indent} const limit = resolveListBound(args.limit, 'limit', ${DEFAULT_LIST_LIMIT}, ${MAX_LIST_LIMIT});\n${indent} const offset = resolveListBound(args.offset, 'offset', 0);\n${indent} // #2367: the tool schema advertises \\`orderBy\\`, so honour it; and page\n${indent} // deterministically when it is omitted — LIMIT/OFFSET with no ORDER BY\n${indent} // lets successive pages repeat and skip rows on PostgreSQL.\n${indent} const orderBy = args.orderBy ?? ${JSON.stringify(\n listOrderBy[objectName] ?? DEFAULT_LIST_ORDER_BY,\n )};\n${indent} const where = args.where ?? {};\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const items = await collection.list({ where, limit, offset, orderBy });\n${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent} const structuredContent = {\n${indent} data: itemsPublic,\n${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },\n${indent} };\n${indent} return successResult(structuredContent, JSON.stringify(itemsPublic));\n${indent}}`;\n\n case 'get':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id && !args.slug) {\n${indent} throw new Error('Either id or slug is required');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const filter = args.id || args.slug;\n${indent} const item = await collection.get(filter);\n\n${indent} if (!item) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} return successResult(item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget(${JSON.stringify(objectName)}, ${JSON.stringify(lookup)}, args, aiConfig);\n\n${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));\n\n${indent} return successResult(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'update':\n return `${indent}case '${tool.name}': {\n${indent} const { id, ...updateData } = args;\n${indent} if (!id) {\n${indent} throw new Error('ID is required for update');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const existing = await collection.get(id);\n${indent} if (!existing) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await collection.withAuditMutation(existing, 'updated', async (bound) => {\n${indent} Object.assign(bound, applyWritablePolicy(${JSON.stringify(lookup)}, updateData));\n${indent} await bound.save();\n${indent} });\n\n${indent} return successResult(existing.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'delete':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id) {\n${indent} throw new Error('ID is required for delete');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const toDelete = await collection.get(args.id);\n${indent} if (!toDelete) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await collection.delete(args.id);\n\n${indent} return successResult({ success: true, message: 'Object deleted successfully' });\n${indent}}`;\n\n default:\n // Custom action. Its descriptor is deliberately kept separate\n // from TOOLS so MCP clients receive only protocol-defined fields.\n return `${indent}case '${tool.name}': {\n${indent} const actionMeta = CUSTOM_ACTIONS['${tool.name}'] || { scope: 'item', isStatic: false, legacyOptions: true };\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (actionMeta.scope === 'item' && !id) {\n${indent} throw new Error(${JSON.stringify(`ID is required for custom action ${action}`)});\n${indent} }\n${indent} if (actionMeta.scope === 'collection' && id) {\n${indent} throw new Error(${JSON.stringify(`Custom action ${action} is collection-scoped and does not accept an ID`)});\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = actionMeta.scope === 'item'\n${indent} ? await collection.get(id)\n${indent} : actionMeta.isStatic\n${indent} ? ObjectRegistry.getClass(${JSON.stringify(lookup)})?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(actionMeta.scope === 'item' ? 'Object not found' : 'Custom action target not found');\n${indent} }\n${indent} const actionMethod = target[actionMeta.methodName || ${JSON.stringify(action)}];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error(${JSON.stringify(`Method ${action} not found on custom action target`)});\n${indent} }\n\n${indent} const methodArgs = actionMeta.legacyOptions\n${indent} ? [Object.keys(options ?? {}).length > 0 ? options : directArgs]\n${indent} : actionMeta.optionsParameter\n${indent} ? [options]\n${indent} : (actionMeta.parameterNames || []).map((parameterName) => args[\n${indent} parameterName === 'id'\n${indent} ? 'actionId'\n${indent} : parameterName\n${indent} ]);\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return errorResult(\n${indent} { error: failure },\n${indent} JSON.stringify({ error: failure }),\n${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} );\n${indent} }\n\n${indent} const publicResult = toPublicResult(result);\n${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));\n${indent}}`;\n }\n })\n .join('\\n\\n');\n };\n\n const switchCases = generateSwitchCases(' ');\n\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @smrt/core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n *\n * SECURITY (#1540): tool responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This stdio server has NO\n * per-call authentication principal — its trust boundary is the host process /\n * MCP client that launches it. Run it only in a trusted context, or front it\n * with an authenticated gateway. Do not expose it directly to untrusted callers.\n */\n\nimport {\n type CallToolRequest,\n type ListToolsRequest,\n Server,\n ${hasTaskActions ? 'specTypeSchemas,' : ''}\n} from '@modelcontextprotocol/server';\nimport { ${hasTaskActions ? 'StdioServerTransport, ' : ''}serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\nimport { loadConfig } from '@happyvertical/smrt-config';\n${hasTaskActions ? \"import { McpTaskStore, TaskRunner } from '@happyvertical/smrt-jobs';\\n\" : ''}\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}\n// Server configuration\nconst SERVER_NAME = ${JSON.stringify(name)};\nconst SERVER_VERSION = ${JSON.stringify(version)};\nconst SERVER_DESCRIPTION = ${JSON.stringify(description)};\nconst DEBUG = ${debug};\n\n// Static tool definitions (generated at build time)\nconst TOOLS = ${toolsCode};\nconst TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};\nconst TOOL_TARGETS: Record<string, { objectName: string; action: string; registryKey?: string }> = ${JSON.stringify(toolTargets)};\nconst CUSTOM_ACTIONS = ${JSON.stringify(customActions)};\nconst TASK_ACTIONS = ${JSON.stringify(taskActions)};\nconst STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};\n${\n hasTenantScoped\n ? `\n// Fail-closed tenant context (#1554): tenant-scoped objects must run inside a\n// tenant. This stdio server has no auth principal, so the tenant is taken from\n// the environment; without it (and with tenancy enabled) tenant-scoped tools\n// throw rather than reading across all tenants.\nconst TENANT_SCOPED = new Set(${JSON.stringify(tenantScopedSet)});\nconst MCP_TENANT_ID = process.env.SMRT_MCP_TENANT_ID || undefined;\nconst MCP_ALLOW_CROSS_TENANT = process.env.SMRT_MCP_ALLOW_CROSS_TENANT === 'true';\n`\n : ''\n}\nconst PUBLIC_JSON_OPTIONS = {\n permissions: (process.env.SMRT_MCP_PERMISSIONS || '')\n .split(',')\n .map((permission) => permission.trim())\n .filter(Boolean),\n};\n\n/**\n * Page-bound guard (#2367). MCP tool arguments are untyped JSON, so \\`limit\\`\n * arrives as whatever the client sent: \\`\"abc\"\\` used to become \\`LIMIT NaN\\`\n * (a driver error, not a tool error), and nothing capped the value, so one\n * \\`limit: 100000000\\` call was a full table scan. Rejects anything that is not\n * a non-negative integer and clamps the rest to \\`max\\`.\n */\nfunction resolveListBound(raw: any, name: string, fallback: number, max?: number): number {\n if (raw === undefined || raw === null || raw === '') {\n return max === undefined ? fallback : Math.min(fallback, max);\n }\n const value = typeof raw === 'string' && /^\\\\d+$/.test(raw.trim()) ? Number(raw.trim()) : raw;\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {\n throw new Error(\"Invalid \" + name + \": expected a non-negative integer, got \" + JSON.stringify(raw));\n }\n return max === undefined ? value : Math.min(value, max);\n}\n\n/**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * \\`@field({ readonly: true })\\` fields from create/update bodies, intersecting\n * with the optional \\`@smrt({ api: { writable: [...] } })\\` allowlist.\n */\nfunction applyWritablePolicy(objectName: string, data: any): Record<string, any> {\n if (!data || typeof data !== 'object') return {};\n const serverManaged = new Set([\n 'id', 'tenantId', 'tenant_id',\n 'createdAt', 'created_at', 'updatedAt', 'updated_at',\n ]);\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api as any;\n if (apiConfig && typeof apiConfig === 'object' && Array.isArray(apiConfig.writable)) {\n writable = apiConfig.writable;\n }\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && ((def as any).readonly === true || (def as any)._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n}\n\n/** Resolve an advertised STI discriminator to its registered subtype collection. */\nasync function resolveCreateTarget(stiKey: string, baseObjectName: string, args: Record<string, any>, aiConfig: any) {\n let objectName = baseObjectName;\n const discriminator = args._meta_type;\n const targets = STI_TARGETS[stiKey];\n if (typeof discriminator === 'string' && targets) {\n const target = targets[discriminator];\n if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);\n objectName = target;\n }\n const collection = await ObjectRegistry.getCollection(objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n ai: aiConfig,\n });\n return { collection, objectName };\n}\n\n/**\n * Sensitive-field-safe serialization for custom-action results (#1540).\n * Recurses through arrays and plain objects so nested SmrtObjects are stripped\n * too; non-plain instances (Date, etc.) and primitives pass through. Cycle-safe.\n */\nfunction toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any {\n if (value === null || typeof value !== 'object') return value;\n if (typeof value.toPublicJSON === 'function') return value.toPublicJSON(PUBLIC_JSON_OPTIONS);\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry: any) => toPublicResult(entry, seen));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, any> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\nfunction successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {\n return {\n content: [{ type: 'text', text }],\n structuredContent,\n };\n}\n\nfunction errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {\n return {\n content: [{ type: 'text', text }],\n isError: true,\n structuredContent,\n ...(_meta ? { _meta } : {}),\n };\n}\n\n${\n hasTaskActions\n ? `\n// The bundled SDK validates tools/call responses against its older result\n// codec, which does not yet know CreateTaskResult. Intercept the extension at\n// the transport boundary, then pass every non-task message untouched to the\n// SDK server. This keeps ordinary MCP behaviour and version negotiation owned\n// by the SDK while making the extension available today.\nconst MCP_TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';\nlet taskRuntime: Promise<{ store: McpTaskStore; runner: TaskRunner }> | undefined;\n\nfunction clientSupportsTasks(message: any): boolean {\n return message?.params?._meta?.['io.modelcontextprotocol/clientCapabilities']\n ?.extensions?.[MCP_TASKS_EXTENSION] !== undefined;\n}\n\n/** Validate the 2026 request envelope before task dispatch mutates a job. */\nfunction taskEnvelopeError(message: any): { code: number; message: string; data?: any } | undefined {\n const meta = message?.params?._meta;\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {\n return {\n code: -32602,\n message: 'Request is missing the required _meta envelope for protocol revision 2026-07-28',\n };\n }\n const protocolVersion = meta['io.modelcontextprotocol/protocolVersion'];\n if (protocolVersion !== '2026-07-28') {\n return typeof protocolVersion === 'string'\n ? {\n code: -32022,\n message: 'Unsupported protocol version: ' + protocolVersion,\n data: { supported: ['2026-07-28'], requested: protocolVersion },\n }\n : {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/protocolVersion must be a string',\n };\n }\n if (!specTypeSchemas.ClientCapabilities.safeParse(\n meta['io.modelcontextprotocol/clientCapabilities'],\n ).success) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientCapabilities is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/clientInfo'] !== undefined &&\n !specTypeSchemas.Implementation.safeParse(\n meta['io.modelcontextprotocol/clientInfo'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientInfo is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/logLevel'] !== undefined &&\n !specTypeSchemas.LoggingLevel.safeParse(\n meta['io.modelcontextprotocol/logLevel'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/logLevel is invalid',\n };\n }\n if (\n meta.progressToken !== undefined &&\n !specTypeSchemas.ProgressToken.safeParse(meta.progressToken).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: progressToken is invalid',\n };\n }\n return undefined;\n}\n\nfunction jsonRpcError(id: any, code: number, message: string, data?: any) {\n return { jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } };\n}\n\nasync function getTaskRuntime() {\n if (!taskRuntime) {\n taskRuntime = (async () => {\n const firstAction = Object.values(TASK_ACTIONS)[0] as { objectName: string; registryKey?: string } | undefined;\n if (!firstAction) throw new Error('No task-enabled MCP action is configured');\n const collection = await ObjectRegistry.getCollection(firstAction.registryKey ?? firstAction.objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n });\n const db = collection.db;\n const store = await McpTaskStore.create(db, { ownerId: process.env.SMRT_MCP_TENANT_ID || null, tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'} });\n const runner = new TaskRunner({ queues: ['mcp-tasks'] });\n await runner.initialize(db);\n await runner.start();\n return { store, runner };\n })();\n }\n return taskRuntime;\n}\n\nfunction taskInvocationArgs(actionMeta: any, args: Record<string, any>): any[] {\n const { id: _id, options, ...directArgs } = args;\n if (actionMeta.legacyOptions) {\n return [Object.keys(options ?? {}).length > 0 ? options : directArgs];\n }\n if (actionMeta.optionsParameter) return [options];\n const parameterNames = actionMeta.parameterNames || [];\n const invocationParameterNames = parameterNames.at(-1) === 'context'\n ? parameterNames.slice(0, -1)\n : parameterNames;\n return invocationParameterNames.map((parameterName: string) =>\n args[parameterName === 'id' ? 'actionId' : parameterName],\n );\n}\n\nasync function handleTaskExtensionMessage(message: any): Promise<any | null> {\n if (!message || typeof message !== 'object' || message.id === undefined) return null;\n const method = message.method;\n const params = message.params ?? {};\n const action = method === 'tools/call' ? TASK_ACTIONS[params.name] : undefined;\n const isTaskMethod = action || ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(method);\n if (!isTaskMethod) return null;\n // A task-enabled tool retains its normal synchronous behaviour until its\n // client explicitly opts into Tasks. Do not impose the task envelope on\n // legacy calls that will be passed untouched to the SDK.\n if (!clientSupportsTasks(message) && method === 'tools/call') return null;\n const envelopeError = taskEnvelopeError(message);\n if (envelopeError) {\n return jsonRpcError(message.id, envelopeError.code, envelopeError.message, envelopeError.data);\n }\n if (!clientSupportsTasks(message)) {\n // A task-only method cannot fall back to a legacy response shape. A task\n // tool can still run normally when the client did not opt in, so let the\n // SDK handle that direct tools/call path.\n if (method === 'tools/call') return null;\n return jsonRpcError(message.id, -32021, 'Missing required client capability', {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n });\n }\n try {\n const { store } = await getTaskRuntime();\n if (action) {\n if (typeof params.arguments?.id !== 'string') {\n return jsonRpcError(message.id, -32602, 'Task-enabled custom actions require an id');\n }\n const actionMeta = CUSTOM_ACTIONS[params.name];\n const task = await store.createTask({\n objectType: action.objectType,\n objectId: params.arguments.id,\n method: actionMeta.methodName || TOOL_TARGETS[params.name]?.action,\n invocationArgs: taskInvocationArgs(actionMeta, params.arguments),\n tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'},\n });\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'task', content: [], structuredContent: {}, ...task } };\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcError(message.id, -32602, 'taskId is required');\n }\n if (method === 'tasks/get') {\n const task = await store.getTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete', ...task } };\n }\n if (method === 'tasks/update') {\n await store.updateTask(\n params.taskId,\n params.inputResponses && typeof params.inputResponses === 'object'\n ? params.inputResponses\n : {},\n );\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n }\n await store.cancelTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n } catch (error) {\n const messageText = error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcError(message.id, -32602, messageText);\n }\n}\n\nclass McpTaskExtensionTransport {\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: any) => void;\n\n // An explicit field, not a parameter property: a \\`.ts\\` target has to stay\n // erasable-syntax-only so Node's type stripping can run it (#2279).\n wire: StdioServerTransport;\n\n constructor(wire: StdioServerTransport) {\n this.wire = wire;\n }\n\n async start() {\n this.wire.onclose = () => this.onclose?.();\n this.wire.onerror = (error) => this.onerror?.(error);\n this.wire.onmessage = (message) => {\n void (async () => {\n const response = await handleTaskExtensionMessage(message);\n if (response) await this.wire.send(response);\n else this.onmessage?.(message);\n })().catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));\n };\n await this.wire.start();\n }\n\n close() { return this.wire.close(); }\n send(message: any) { return this.wire.send(message); }\n}\n`\n : ''\n}\n\n/**\n * Main server startup function\n */\nexport async function createServer(): Promise<Server> {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n }\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context (#1554): install the tenancy interceptor so\n // tenant-scoped tools are actually filtered, and so the entry-point gate\n // throws (rather than passing through) when no tenant is supplied. Without\n // this, a tenant set via SMRT_MCP_TENANT_ID would only set async context\n // with no interceptor to enforce it.\n enableTenancy();\n`\n : ''\n}\n // Register the application package manifest before resolving generated\n // object names. Generated servers are commonly run from the application\n // package itself, which is not a node_modules dependency of its process.\n const localManifestPaths = [\n resolve(process.cwd(), 'dist', 'manifest.json'),\n resolve(process.cwd(), '.smrt', 'manifest.json'),\n ].filter(existsSync);\n if (localManifestPaths.length > 0) {\n ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });\n }\n\n // A manifest supplies schemas and tool metadata, while an executable\n // custom action also needs the application's actual class constructor.\n // Consumer builds generate this registration module; load it after the\n // manifest so decorators enrich the already-known metadata.\n const localRegisterPath = process.env.SMRT_MCP_REGISTER_PATH\n || resolve(process.cwd(), '.smrt', 'register.js');\n if (existsSync(localRegisterPath)) {\n await import(pathToFileURL(localRegisterPath).href);\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await loadConfig();\n const aiConfig = appConfig?.ai || {};\n\n if (DEBUG) {\n console.error(\\`[MCP] Loaded \\${TOOLS.length} static tools\\`);\n console.error(\\`[MCP] Available tools:\\`, TOOLS.map(t => t.name).join(', '));\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n ${hasTaskActions ? \"extensions: { 'io.modelcontextprotocol/tasks': {} },\" : ''}\n },\n cacheHints: {\n 'tools/list': TOOL_LIST_CACHE_HINT,\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler('tools/list', async (_request: ListToolsRequest) => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: [...TOOLS].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler('tools/call', async (request: CallToolRequest) => {\n const { name: toolName, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${toolName}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n try {\n // Static switch statement for tool execution\n const runToolBody = async () => {\n switch (toolName) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${toolName}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const toolObject = TOOL_TARGETS[toolName]?.objectName;\n const result =\n toolObject && TENANT_SCOPED.has(toolObject.toLowerCase())\n ? await runTenantScopedEntryPoint(\n { tenantScoped: true, tenantId: MCP_TENANT_ID, allowCrossTenant: MCP_ALLOW_CROSS_TENANT, surface: 'MCP' },\n runToolBody,\n )\n : await runToolBody();`\n : `\n const result = await runToolBody();`\n}\n\n if (DEBUG) {\n console.error(\\`[MCP] Tool executed successfully: \\${toolName}\\`);\n }\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error(\\`[MCP] Tool execution failed: \\${toolName}\\`, error);\n\n return errorResult(\n { error: { message: errorMessage } },\n \\`Error executing tool \\${toolName}: \\${errorMessage}\\`,\n );\n }\n });\n\n return server;\n}\n\nasync function main() {\n try {\n // Initialize the registry before accepting an intercepted task call. The\n // SDK would normally invoke this factory for the first regular message,\n // but task calls can be the first message on a stdio connection.\n const server = await createServer();\n const transport = ${hasTaskActions ? 'new McpTaskExtensionTransport(new StdioServerTransport())' : 'undefined'};\n const handle = serveStdio(() => server, {\n ...(transport ? { transport } : {}),\n onerror: (error) => console.error('[MCP] Protocol error:', error),\n });\n const shutdown = async () => {\n if (DEBUG) console.error('[MCP] Shutting down gracefully');\n ${hasTaskActions ? 'if (taskRuntime) {\\n const { runner } = await taskRuntime;\\n await runner.stop();\\n }' : ''}\n await handle.close();\n process.exit(0);\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n } catch (error) {\n console.error('[MCP] Fatal error during server startup:', error);\n process.exit(1);\n }\n}\n\n// Start only when executed, so adapters and tests may import the factory.\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().catch((error) => {\n console.error('[MCP] Unhandled error:', error);\n process.exit(1);\n });\n}\n`;\n}\n\n/**\n * Generate package.json script for running MCP server\n *\n * @param serverPath - Path to generated server file (relative to package root)\n * @returns Script command for package.json\n */\nexport function generateMCPScript(\n serverPath: string = 'dist/mcp-server.js',\n): string {\n return `node ${serverPath}`;\n}\n\n/**\n * Generate Claude Desktop configuration example\n *\n * @param serverName - Name for the MCP server\n * @param serverPath - Absolute path to server file\n * @returns Configuration object for claude_desktop_config.json\n */\nexport function generateClaudeConfig(\n serverName: string,\n serverPath: string,\n): object {\n return {\n mcpServers: {\n [serverName]: {\n command: 'node',\n args: [serverPath],\n },\n },\n };\n}\n\n/**\n * Generate README documentation for MCP server setup\n *\n * @param serverName - Name of the MCP server\n * @param serverPath - Path to the server file\n * @returns Markdown documentation\n */\nexport function generateMCPDocumentation(\n serverName: string,\n serverPath: string,\n): string {\n return `# MCP Server Setup\n\nThis project includes an auto-generated MCP (Model Context Protocol) server that exposes SMRT objects as tools for AI integration.\n\n## Quick Start\n\n### 1. Build the MCP Server\n\n\\`\\`\\`bash\nnpm run build\n\\`\\`\\`\n\nThis generates the MCP server at: \\`${serverPath}\\`\n\n### 2. Configure Claude Desktop\n\nAdd the following to your Claude Desktop configuration file:\n\n**macOS**: \\`~/.config/Claude/claude_desktop_config.json\\`\n**Windows**: \\`%APPDATA%\\\\Claude\\\\claude_desktop_config.json\\`\n\n\\`\\`\\`json\n{\n \"mcpServers\": {\n \"${serverName}\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/${serverPath}\"]\n }\n }\n}\n\\`\\`\\`\n\nReplace \\`/absolute/path/to/\\` with the actual absolute path to your project directory.\n\n### 3. Restart Claude Desktop\n\nClose and reopen Claude Desktop to load the new MCP server.\n\n### 4. Test the Integration\n\nIn Claude Code, you can now use the auto-generated tools. For example:\n\n- \\`list_products\\` - List all products\n- \\`get_product\\` - Get a specific product by ID\n- \\`create_product\\` - Create a new product\n- And more...\n\n## Environment Variables\n\nThe MCP server supports optional environment variables:\n\n- \\`DATABASE_URL\\` - Database connection string\n\n**AI Provider Configuration (in priority order):**\n1. **\\`ai\\` block in \\`smrt.config.ts\\`** (declare once; keep the secret in the environment):\n - \\`ai: { provider: 'openai', model: 'gpt-4o', apiKeyEnv: 'OPENAI_API_KEY' }\\`\n\n2. **Generic environment configuration** (supports any provider):\n - \\`SMRT_AI_PROVIDER\\` - Provider name (e.g., 'openai', 'anthropic', 'claude-cli', 'gemini')\n - \\`SMRT_AI_API_KEY\\` - API key for the provider\n - \\`SMRT_AI_MODEL\\` - Model to use (optional)\n - \\`SMRT_AI_BASE_URL\\` - Endpoint override (optional)\n\n3. **Provider-specific key fallbacks** (used when the \\`ai\\` block selects the provider, and only for that provider):\n - \\`OPENAI_API_KEY\\`, \\`ANTHROPIC_API_KEY\\`, \\`GEMINI_API_KEY\\`\n - \\`CLAUDE_API_KEY\\` + \\`CLAUDE_MODEL\\` - Claude CLI provider (defaults to 'sonnet'); read by \\`@happyvertical/ai\\`\n\n**Examples:**\n\\`\\`\\`bash\n# Using generic configuration (recommended)\nexport SMRT_AI_PROVIDER=claude-cli\nexport SMRT_AI_MODEL=sonnet\n\n# Using provider-specific configuration\nexport CLAUDE_API_KEY=your-key\nexport CLAUDE_MODEL=sonnet\n\n# Using OpenAI\nexport OPENAI_API_KEY=your-openai-key\n\\`\\`\\`\n\n## Troubleshooting\n\n### Server Not Appearing in Claude\n\n1. Check that the path in \\`claude_desktop_config.json\\` is absolute\n2. Verify the server file exists at the specified path\n3. Check Claude Desktop logs for errors\n\n### Tools Not Working\n\n1. Ensure your database is accessible (if using one)\n2. Check that SMRT objects are properly decorated with \\`@smrt()\\`\n3. Look for errors in the MCP server output\n\n### Debug Mode\n\nTo enable debug logging, set the \\`DEBUG\\` constant to \\`true\\` in the generated server file.\n\n## Generated Tools\n\nThe following tools are automatically generated from your SMRT objects:\n\n- **CRUD Operations**: \\`list_\\`, \\`get_\\`, \\`create_\\`, \\`update_\\`, \\`delete_\\` for each object type\n- **Custom Actions**: Any custom methods included in the \\`@smrt()\\` decorator configuration\n\nSee the SMRT object definitions for the complete list of available tools and their parameters.\n`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-runtime-template.js","names":[],"sources":["../../src/generators/mcp-runtime-template.ts"],"sourcesContent":["/**\n * Runtime bootstrap template for generated MCP servers\n *\n * This template provides stdio transport integration for SMRT-generated MCP servers.\n * It handles:\n * - Server initialization with @modelcontextprotocol/server v2\n * - Tool registration from MCPGenerator\n * - Stdio transport connection\n * - Error handling and logging\n * - Graceful shutdown\n */\n\nimport {\n DEFAULT_LIST_LIMIT,\n DEFAULT_LIST_ORDER_BY,\n MAX_LIST_LIMIT,\n} from '../query-bounds.js';\nimport type { ApiSerializerReference } from '../registry/types.js';\nimport type { CustomActionScope } from './custom-action.js';\nimport type { MCPConfig, MCPContext } from './mcp.js';\nimport type { McpToolTarget } from './mcp-tool-name.js';\n\n/**\n * Helper function to capitalize first letter\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport interface RuntimeOptions {\n /** Server name (defaults to package name) */\n name?: string;\n\n /** Server version (defaults to package version) */\n version?: string;\n\n /** Server description */\n description?: string;\n\n /** MCP generator configuration */\n config?: MCPConfig;\n\n /** MCP context (database, AI client, etc.) */\n context?: MCPContext;\n\n /** Enable debug logging */\n debug?: boolean;\n\n /** Static tool definitions (generated at build time) */\n tools?: Array<{\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n annotations?: {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n };\n title?: string;\n icons?: Array<{\n src: string;\n mimeType?: string;\n sizes?: string[];\n theme?: 'light' | 'dark';\n }>;\n _meta?: Record<string, unknown>;\n }>;\n /** Explicit original execution targets for generated protocol aliases. */\n toolTargets?: Record<string, McpToolTarget>;\n /** Async serializers for standard get tools, keyed by emitted tool name. */\n getSerializers?: Record<string, ApiSerializerReference>;\n /** Cache hint emitted for deploy-static tools/list results. */\n toolListCacheHint?: {\n ttlMs: number;\n cacheScope: 'private' | 'public';\n };\n /** Internal invocation metadata; never exposed by the MCP tools/list result. */\n customActions?: Record<\n string,\n {\n scope: CustomActionScope;\n isStatic: boolean;\n /** Declared method name; tool IDs are lowercased protocol aliases. */\n methodName?: string;\n parameterNames?: string[];\n optionsParameter?: boolean;\n legacyOptions: boolean;\n }\n >;\n /** Task-enabled item custom actions. Never emitted in tools/list. */\n taskActions?: Record<\n string,\n {\n objectName: string;\n objectType: string;\n /** Registry identity of the task's class (#3490). */\n registryKey?: string;\n }\n >;\n\n /**\n * Lowercased simple names of objects that are `@TenantScoped` (#1554). When\n * non-empty, the generated server imports the tenancy fail-closed gate and\n * wraps tenant-scoped tool calls so a stdio invocation cannot read across all\n * tenants. The tenant is sourced from `SMRT_MCP_TENANT_ID` /\n * `SMRT_MCP_ALLOW_CROSS_TENANT` env vars (this server has no auth principal).\n */\n tenantScopedObjects?: string[];\n\n /**\n * Build-time approved STI discriminators, keyed by the lowercased MCP object\n * prefix. The generated runtime uses this instead of searching an initially\n * empty registry, then loads the approved qualified type through the public\n * collection API.\n */\n stiTargets?: Record<string, Record<string, string>>;\n\n /**\n * Default list ordering per lowercased MCP object prefix (#2367).\n *\n * Baked in at generation time because the tiebreak follows the model's\n * declared primary key, which the emitted server cannot resolve on its own.\n * Prefixes absent from this map fall back to {@link DEFAULT_LIST_ORDER_BY}.\n */\n listOrderBy?: Record<string, readonly string[]>;\n}\n\n/**\n * Generate runtime bootstrap code for MCP server\n *\n * @param options - Runtime configuration options\n * @returns TypeScript code for server entry point\n */\nexport function generateRuntimeBootstrap(options: RuntimeOptions = {}): string {\n const {\n name = 'smrt-mcp-server',\n version = '1.0.0',\n description = 'Auto-generated MCP server from SMRT objects',\n debug = false,\n tools = [],\n toolTargets: explicitToolTargets,\n getSerializers = {},\n customActions = {},\n taskActions = {},\n tenantScopedObjects = [],\n stiTargets = {},\n listOrderBy = {},\n toolListCacheHint = { ttlMs: 86_400_000, cacheScope: 'private' },\n } = options;\n\n // Direct template callers retain their legacy object_action inputs. The\n // owning generator always supplies explicit targets, including valid aliases.\n const toolTargets: Record<string, McpToolTarget> =\n explicitToolTargets ??\n Object.fromEntries(\n tools.map((tool) => {\n const separator = tool.name.indexOf('_');\n return [\n tool.name,\n {\n objectName: capitalize(tool.name.slice(0, separator)),\n action: tool.name.slice(separator + 1),\n },\n ];\n }),\n );\n for (const tool of tools) {\n if (!Object.hasOwn(toolTargets, tool.name)) {\n throw new Error(`Missing MCP tool target: ${tool.name}`);\n }\n }\n\n // Generate static tool array as TypeScript code\n const toolsCode = tools.length > 0 ? JSON.stringify(tools, null, 2) : '[]';\n const hasTaskActions = Object.keys(taskActions).length > 0;\n\n // Fail-closed tenant context (#1554): only wire the tenancy gate when at\n // least one exposed object is tenant-scoped, so apps without tenancy never\n // get a dangling import.\n const tenantScopedSet = Array.from(\n new Set(tenantScopedObjects.map((n) => n.toLowerCase())),\n );\n const hasTenantScoped = tenantScopedSet.length > 0;\n\n // Generate static switch cases using shared helper\n const generateSwitchCases = (indent: string) => {\n return tools\n .map((tool) => {\n const objectName = toolTargets[tool.name].objectName.toLowerCase();\n const action = toolTargets[tool.name].action;\n // Registry identity for every emitted lookup (#3490).\n const lookup =\n toolTargets[tool.name].registryKey ??\n toolTargets[tool.name].objectName;\n\n switch (action) {\n case 'list':\n return `${indent}case '${tool.name}': {\n${indent} const limit = resolveListBound(args.limit, 'limit', ${DEFAULT_LIST_LIMIT}, ${MAX_LIST_LIMIT});\n${indent} const offset = resolveListBound(args.offset, 'offset', 0);\n${indent} // #2367: the tool schema advertises \\`orderBy\\`, so honour it; and page\n${indent} // deterministically when it is omitted — LIMIT/OFFSET with no ORDER BY\n${indent} // lets successive pages repeat and skip rows on PostgreSQL.\n${indent} const orderBy = args.orderBy ?? ${JSON.stringify(\n listOrderBy[objectName] ?? DEFAULT_LIST_ORDER_BY,\n )};\n${indent} const where = args.where ?? {};\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const items = await collection.list({ where, limit, offset, orderBy });\n${indent} const itemsPublic = items.map((item) => item.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent} const structuredContent = {\n${indent} data: itemsPublic,\n${indent} meta: { total: await collection.count({ where }), limit, offset, count: items.length },\n${indent} };\n${indent} return successResult(structuredContent, JSON.stringify(itemsPublic));\n${indent}}`;\n\n case 'get':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id && !args.slug) {\n${indent} throw new Error('Either id or slug is required');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const filter = args.id || args.slug;\n${indent} const item = await collection.get(filter);\n\n${indent} if (!item) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} return successResult(await serializeGetResult(${JSON.stringify(tool.name)}, item));\n${indent}}`;\n\n case 'create':\n return `${indent}case '${tool.name}': {\n${indent} const { collection, objectName: targetObjectName } = await resolveCreateTarget(${JSON.stringify(objectName)}, ${JSON.stringify(lookup)}, args, aiConfig);\n\n${indent} const newItem = await collection.create(applyWritablePolicy(targetObjectName, args));\n\n${indent} return successResult(newItem.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'update':\n return `${indent}case '${tool.name}': {\n${indent} const { id, ...updateData } = args;\n${indent} if (!id) {\n${indent} throw new Error('ID is required for update');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const existing = await collection.get(id);\n${indent} if (!existing) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await collection.withAuditMutation(existing, 'updated', async (bound) => {\n${indent} Object.assign(bound, applyWritablePolicy(${JSON.stringify(lookup)}, updateData));\n${indent} await bound.save();\n${indent} });\n\n${indent} return successResult(existing.toPublicJSON(PUBLIC_JSON_OPTIONS));\n${indent}}`;\n\n case 'delete':\n return `${indent}case '${tool.name}': {\n${indent} if (!args.id) {\n${indent} throw new Error('ID is required for delete');\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const toDelete = await collection.get(args.id);\n${indent} if (!toDelete) {\n${indent} throw new Error('Object not found');\n${indent} }\n\n${indent} await collection.delete(args.id);\n\n${indent} return successResult({ success: true, message: 'Object deleted successfully' });\n${indent}}`;\n\n default:\n // Custom action. Its descriptor is deliberately kept separate\n // from TOOLS so MCP clients receive only protocol-defined fields.\n return `${indent}case '${tool.name}': {\n${indent} const actionMeta = CUSTOM_ACTIONS['${tool.name}'] || { scope: 'item', isStatic: false, legacyOptions: true };\n${indent} const { id, options, ...directArgs } = args;\n\n${indent} if (actionMeta.scope === 'item' && !id) {\n${indent} throw new Error(${JSON.stringify(`ID is required for custom action ${action}`)});\n${indent} }\n${indent} if (actionMeta.scope === 'collection' && id) {\n${indent} throw new Error(${JSON.stringify(`Custom action ${action} is collection-scoped and does not accept an ID`)});\n${indent} }\n\n${indent} const collection = await ObjectRegistry.getCollection(${JSON.stringify(lookup)}, {\n${indent} persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n${indent} ai: aiConfig\n${indent} });\n\n${indent} const target = actionMeta.scope === 'item'\n${indent} ? await collection.get(id)\n${indent} : actionMeta.isStatic\n${indent} ? ObjectRegistry.getClass(${JSON.stringify(lookup)})?.constructor\n${indent} : collection;\n${indent} if (!target) {\n${indent} throw new Error(actionMeta.scope === 'item' ? 'Object not found' : 'Custom action target not found');\n${indent} }\n${indent} const actionMethod = target[actionMeta.methodName || ${JSON.stringify(action)}];\n${indent} if (typeof actionMethod !== 'function') {\n${indent} throw new Error(${JSON.stringify(`Method ${action} not found on custom action target`)});\n${indent} }\n\n${indent} const methodArgs = actionMeta.legacyOptions\n${indent} ? [Object.keys(options ?? {}).length > 0 ? options : directArgs]\n${indent} : actionMeta.optionsParameter\n${indent} ? [options]\n${indent} : (actionMeta.parameterNames || []).map((parameterName) => args[\n${indent} parameterName === 'id'\n${indent} ? 'actionId'\n${indent} : parameterName\n${indent} ]);\n${indent} const result = await actionMethod.call(target, ...methodArgs);\n${indent} const failure = normalizeCustomActionFailure(result);\n${indent} if (failure) {\n${indent} return errorResult(\n${indent} { error: failure },\n${indent} JSON.stringify({ error: failure }),\n${indent} { [SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY]: failure },\n${indent} );\n${indent} }\n\n${indent} const publicResult = toPublicResult(result);\n${indent} return successResult({ data: publicResult }, JSON.stringify(publicResult));\n${indent}}`;\n }\n })\n .join('\\n\\n');\n };\n\n const switchCases = generateSwitchCases(' ');\n\n return `#!/usr/bin/env node\n/**\n * Auto-generated MCP Server\n * Generated by @smrt/core MCPGenerator\n *\n * This server exposes SMRT objects as MCP tools for AI integration.\n *\n * SECURITY (#1540): tool responses exclude @field({ sensitive }) fields and\n * create/update bodies are mass-assignment guarded. This stdio server has NO\n * per-call authentication principal — its trust boundary is the host process /\n * MCP client that launches it. Run it only in a trusted context, or front it\n * with an authenticated gateway. Do not expose it directly to untrusted callers.\n */\n\nimport {\n type CallToolRequest,\n type ListToolsRequest,\n Server,\n ${hasTaskActions ? 'specTypeSchemas,' : ''}\n} from '@modelcontextprotocol/server';\nimport { ${hasTaskActions ? 'StdioServerTransport, ' : ''}serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { normalizeCustomActionFailure, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY } from '@happyvertical/smrt-core';\nimport { loadConfig } from '@happyvertical/smrt-config';\n${hasTaskActions ? \"import { McpTaskStore, TaskRunner } from '@happyvertical/smrt-jobs';\\n\" : ''}\n${hasTenantScoped ? \"import { enableTenancy, runTenantScopedEntryPoint } from '@happyvertical/smrt-tenancy';\\n\" : ''}\n// Server configuration\nconst SERVER_NAME = ${JSON.stringify(name)};\nconst SERVER_VERSION = ${JSON.stringify(version)};\nconst SERVER_DESCRIPTION = ${JSON.stringify(description)};\nconst DEBUG = ${debug};\n\n// Static tool definitions (generated at build time)\nconst TOOLS = ${toolsCode};\nconst TOOL_LIST_CACHE_HINT = ${JSON.stringify(toolListCacheHint)};\nconst TOOL_TARGETS: Record<string, { objectName: string; action: string; registryKey?: string }> = ${JSON.stringify(toolTargets)};\nconst CUSTOM_ACTIONS = ${JSON.stringify(customActions)};\nconst TASK_ACTIONS = ${JSON.stringify(taskActions)};\nconst STI_TARGETS: Record<string, Record<string, string>> = ${JSON.stringify(stiTargets)};\n${\n hasTenantScoped\n ? `\n// Fail-closed tenant context (#1554): tenant-scoped objects must run inside a\n// tenant. This stdio server has no auth principal, so the tenant is taken from\n// the environment; without it (and with tenancy enabled) tenant-scoped tools\n// throw rather than reading across all tenants.\nconst TENANT_SCOPED = new Set(${JSON.stringify(tenantScopedSet)});\nconst MCP_TENANT_ID = process.env.SMRT_MCP_TENANT_ID || undefined;\nconst MCP_ALLOW_CROSS_TENANT = process.env.SMRT_MCP_ALLOW_CROSS_TENANT === 'true';\n`\n : ''\n}\nconst PUBLIC_JSON_OPTIONS = {\n permissions: (process.env.SMRT_MCP_PERMISSIONS || '')\n .split(',')\n .map((permission) => permission.trim())\n .filter(Boolean),\n};\nconst GET_SERIALIZERS: Record<string, { importPath: string; exportName: string }> = ${JSON.stringify(getSerializers)};\n\nasync function serializeGetResult(toolName: string, item: any): Promise<any> {\n const reference = GET_SERIALIZERS[toolName];\n if (!reference) return item.toPublicJSON(PUBLIC_JSON_OPTIONS);\n const serializerModule = await import(reference.importPath);\n const serializer = serializerModule[reference.exportName];\n if (typeof serializer !== 'function') {\n throw new Error(\n \\`MCP get serializer '\\${reference.exportName}' was not exported by '\\${reference.importPath}'\\`,\n );\n }\n return toPublicResult(await serializer(item, PUBLIC_JSON_OPTIONS));\n}\n\n/**\n * Page-bound guard (#2367). MCP tool arguments are untyped JSON, so \\`limit\\`\n * arrives as whatever the client sent: \\`\"abc\"\\` used to become \\`LIMIT NaN\\`\n * (a driver error, not a tool error), and nothing capped the value, so one\n * \\`limit: 100000000\\` call was a full table scan. Rejects anything that is not\n * a non-negative integer and clamps the rest to \\`max\\`.\n */\nfunction resolveListBound(raw: any, name: string, fallback: number, max?: number): number {\n if (raw === undefined || raw === null || raw === '') {\n return max === undefined ? fallback : Math.min(fallback, max);\n }\n const value = typeof raw === 'string' && /^\\\\d+$/.test(raw.trim()) ? Number(raw.trim()) : raw;\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {\n throw new Error(\"Invalid \" + name + \": expected a non-negative integer, got \" + JSON.stringify(raw));\n }\n return max === undefined ? value : Math.min(value, max);\n}\n\n/**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * \\`@field({ readonly: true })\\` fields from create/update bodies, intersecting\n * with the optional \\`@smrt({ api: { writable: [...] } })\\` allowlist.\n */\nfunction applyWritablePolicy(objectName: string, data: any): Record<string, any> {\n if (!data || typeof data !== 'object') return {};\n const serverManaged = new Set([\n 'id', 'tenantId', 'tenant_id',\n 'createdAt', 'created_at', 'updatedAt', 'updated_at',\n ]);\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api as any;\n if (apiConfig && typeof apiConfig === 'object' && Array.isArray(apiConfig.writable)) {\n writable = apiConfig.writable;\n }\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && ((def as any).readonly === true || (def as any)._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n}\n\n/** Resolve an advertised STI discriminator to its registered subtype collection. */\nasync function resolveCreateTarget(stiKey: string, baseObjectName: string, args: Record<string, any>, aiConfig: any) {\n let objectName = baseObjectName;\n const discriminator = args._meta_type;\n const targets = STI_TARGETS[stiKey];\n if (typeof discriminator === 'string' && targets) {\n const target = targets[discriminator];\n if (!target) throw new Error('Unknown STI discriminator: ' + discriminator);\n objectName = target;\n }\n const collection = await ObjectRegistry.getCollection(objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n ai: aiConfig,\n });\n return { collection, objectName };\n}\n\n/**\n * Sensitive-field-safe serialization for custom-action results (#1540).\n * Recurses through arrays and plain objects so nested SmrtObjects are stripped\n * too; non-plain instances (Date, etc.) and primitives pass through. Cycle-safe.\n */\nfunction toPublicResult(value: any, seen: WeakSet<object> = new WeakSet()): any {\n if (value === null || typeof value !== 'object') return value;\n if (typeof value.toPublicJSON === 'function') return value.toPublicJSON(PUBLIC_JSON_OPTIONS);\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry: any) => toPublicResult(entry, seen));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, any> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = toPublicResult(entry, seen);\n }\n return out;\n}\n\nfunction successResult(structuredContent: any, text = JSON.stringify(structuredContent)) {\n return {\n content: [{ type: 'text', text }],\n structuredContent,\n };\n}\n\nfunction errorResult(structuredContent: any, text: string, _meta?: Record<string, any>) {\n return {\n content: [{ type: 'text', text }],\n isError: true,\n structuredContent,\n ...(_meta ? { _meta } : {}),\n };\n}\n\nfunction normalizePublicToolError(error: any): { code: string; message: string } | undefined {\n if (!error || typeof error !== 'object') return undefined;\n if (\n typeof error.code !== 'string' ||\n !/^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/.test(error.code) ||\n typeof error.publicMessage !== 'string' ||\n error.publicMessage.length === 0\n ) return undefined;\n return { code: error.code, message: error.publicMessage };\n}\n\n${\n hasTaskActions\n ? `\n// The bundled SDK validates tools/call responses against its older result\n// codec, which does not yet know CreateTaskResult. Intercept the extension at\n// the transport boundary, then pass every non-task message untouched to the\n// SDK server. This keeps ordinary MCP behaviour and version negotiation owned\n// by the SDK while making the extension available today.\nconst MCP_TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';\nlet taskRuntime: Promise<{ store: McpTaskStore; runner: TaskRunner }> | undefined;\n\nfunction clientSupportsTasks(message: any): boolean {\n return message?.params?._meta?.['io.modelcontextprotocol/clientCapabilities']\n ?.extensions?.[MCP_TASKS_EXTENSION] !== undefined;\n}\n\n/** Validate the 2026 request envelope before task dispatch mutates a job. */\nfunction taskEnvelopeError(message: any): { code: number; message: string; data?: any } | undefined {\n const meta = message?.params?._meta;\n if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {\n return {\n code: -32602,\n message: 'Request is missing the required _meta envelope for protocol revision 2026-07-28',\n };\n }\n const protocolVersion = meta['io.modelcontextprotocol/protocolVersion'];\n if (protocolVersion !== '2026-07-28') {\n return typeof protocolVersion === 'string'\n ? {\n code: -32022,\n message: 'Unsupported protocol version: ' + protocolVersion,\n data: { supported: ['2026-07-28'], requested: protocolVersion },\n }\n : {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/protocolVersion must be a string',\n };\n }\n if (!specTypeSchemas.ClientCapabilities.safeParse(\n meta['io.modelcontextprotocol/clientCapabilities'],\n ).success) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientCapabilities is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/clientInfo'] !== undefined &&\n !specTypeSchemas.Implementation.safeParse(\n meta['io.modelcontextprotocol/clientInfo'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/clientInfo is invalid',\n };\n }\n if (\n meta['io.modelcontextprotocol/logLevel'] !== undefined &&\n !specTypeSchemas.LoggingLevel.safeParse(\n meta['io.modelcontextprotocol/logLevel'],\n ).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: io.modelcontextprotocol/logLevel is invalid',\n };\n }\n if (\n meta.progressToken !== undefined &&\n !specTypeSchemas.ProgressToken.safeParse(meta.progressToken).success\n ) {\n return {\n code: -32602,\n message: 'Invalid _meta envelope for protocol revision 2026-07-28: progressToken is invalid',\n };\n }\n return undefined;\n}\n\nfunction jsonRpcError(id: any, code: number, message: string, data?: any) {\n return { jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } };\n}\n\nasync function getTaskRuntime() {\n if (!taskRuntime) {\n taskRuntime = (async () => {\n const firstAction = Object.values(TASK_ACTIONS)[0] as { objectName: string; registryKey?: string } | undefined;\n if (!firstAction) throw new Error('No task-enabled MCP action is configured');\n const collection = await ObjectRegistry.getCollection(firstAction.registryKey ?? firstAction.objectName, {\n persistence: { type: process.env.DATABASE_TYPE || 'sqlite', url: process.env.DATABASE_URL || ':memory:' },\n });\n const db = collection.db;\n const store = await McpTaskStore.create(db, { ownerId: process.env.SMRT_MCP_TENANT_ID || null, tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'} });\n const runner = new TaskRunner({ queues: ['mcp-tasks'] });\n await runner.initialize(db);\n await runner.start();\n return { store, runner };\n })();\n }\n return taskRuntime;\n}\n\nfunction taskInvocationArgs(actionMeta: any, args: Record<string, any>): any[] {\n const { id: _id, options, ...directArgs } = args;\n if (actionMeta.legacyOptions) {\n return [Object.keys(options ?? {}).length > 0 ? options : directArgs];\n }\n if (actionMeta.optionsParameter) return [options];\n const parameterNames = actionMeta.parameterNames || [];\n const invocationParameterNames = parameterNames.at(-1) === 'context'\n ? parameterNames.slice(0, -1)\n : parameterNames;\n return invocationParameterNames.map((parameterName: string) =>\n args[parameterName === 'id' ? 'actionId' : parameterName],\n );\n}\n\nasync function handleTaskExtensionMessage(message: any): Promise<any | null> {\n if (!message || typeof message !== 'object' || message.id === undefined) return null;\n const method = message.method;\n const params = message.params ?? {};\n const action = method === 'tools/call' ? TASK_ACTIONS[params.name] : undefined;\n const isTaskMethod = action || ['tasks/get', 'tasks/update', 'tasks/cancel'].includes(method);\n if (!isTaskMethod) return null;\n // A task-enabled tool retains its normal synchronous behaviour until its\n // client explicitly opts into Tasks. Do not impose the task envelope on\n // legacy calls that will be passed untouched to the SDK.\n if (!clientSupportsTasks(message) && method === 'tools/call') return null;\n const envelopeError = taskEnvelopeError(message);\n if (envelopeError) {\n return jsonRpcError(message.id, envelopeError.code, envelopeError.message, envelopeError.data);\n }\n if (!clientSupportsTasks(message)) {\n // A task-only method cannot fall back to a legacy response shape. A task\n // tool can still run normally when the client did not opt in, so let the\n // SDK handle that direct tools/call path.\n if (method === 'tools/call') return null;\n return jsonRpcError(message.id, -32021, 'Missing required client capability', {\n requiredCapabilities: { extensions: { [MCP_TASKS_EXTENSION]: {} } },\n });\n }\n try {\n const { store } = await getTaskRuntime();\n if (action) {\n if (typeof params.arguments?.id !== 'string') {\n return jsonRpcError(message.id, -32602, 'Task-enabled custom actions require an id');\n }\n const actionMeta = CUSTOM_ACTIONS[params.name];\n const task = await store.createTask({\n objectType: action.objectType,\n objectId: params.arguments.id,\n method: actionMeta.methodName || TOOL_TARGETS[params.name]?.action,\n invocationArgs: taskInvocationArgs(actionMeta, params.arguments),\n tenantId: ${hasTenantScoped ? 'MCP_TENANT_ID ?? null' : 'null'},\n });\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'task', content: [], structuredContent: {}, ...task } };\n }\n if (typeof params.taskId !== 'string') {\n return jsonRpcError(message.id, -32602, 'taskId is required');\n }\n if (method === 'tasks/get') {\n const task = await store.getTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete', ...task } };\n }\n if (method === 'tasks/update') {\n await store.updateTask(\n params.taskId,\n params.inputResponses && typeof params.inputResponses === 'object'\n ? params.inputResponses\n : {},\n );\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n }\n await store.cancelTask(params.taskId);\n return { jsonrpc: '2.0', id: message.id, result: { resultType: 'complete' } };\n } catch (error) {\n const messageText = error instanceof Error ? error.message : 'Task operation failed';\n return jsonRpcError(message.id, -32602, messageText);\n }\n}\n\nclass McpTaskExtensionTransport {\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: any) => void;\n\n // An explicit field, not a parameter property: a \\`.ts\\` target has to stay\n // erasable-syntax-only so Node's type stripping can run it (#2279).\n wire: StdioServerTransport;\n\n constructor(wire: StdioServerTransport) {\n this.wire = wire;\n }\n\n async start() {\n this.wire.onclose = () => this.onclose?.();\n this.wire.onerror = (error) => this.onerror?.(error);\n this.wire.onmessage = (message) => {\n void (async () => {\n const response = await handleTaskExtensionMessage(message);\n if (response) await this.wire.send(response);\n else this.onmessage?.(message);\n })().catch((error) => this.onerror?.(error instanceof Error ? error : new Error(String(error))));\n };\n await this.wire.start();\n }\n\n close() { return this.wire.close(); }\n send(message: any) { return this.wire.send(message); }\n}\n`\n : ''\n}\n\n/**\n * Main server startup function\n */\nexport async function createServer(): Promise<Server> {\n if (DEBUG) {\n console.error(\\`[MCP] Starting server: \\${SERVER_NAME} v\\${SERVER_VERSION}\\`);\n }\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context (#1554): install the tenancy interceptor so\n // tenant-scoped tools are actually filtered, and so the entry-point gate\n // throws (rather than passing through) when no tenant is supplied. Without\n // this, a tenant set via SMRT_MCP_TENANT_ID would only set async context\n // with no interceptor to enforce it.\n enableTenancy();\n`\n : ''\n}\n // Register the application package manifest before resolving generated\n // object names. Generated servers are commonly run from the application\n // package itself, which is not a node_modules dependency of its process.\n const localManifestPaths = [\n resolve(process.cwd(), 'dist', 'manifest.json'),\n resolve(process.cwd(), '.smrt', 'manifest.json'),\n ].filter(existsSync);\n if (localManifestPaths.length > 0) {\n ObjectRegistry.loadAllManifests({ manifestPaths: localManifestPaths });\n }\n\n // A manifest supplies schemas and tool metadata, while an executable\n // custom action also needs the application's actual class constructor.\n // Consumer builds generate this registration module; load it after the\n // manifest so decorators enrich the already-known metadata.\n const localRegisterPath = process.env.SMRT_MCP_REGISTER_PATH\n || resolve(process.cwd(), '.smrt', 'register.js');\n if (existsSync(localRegisterPath)) {\n await import(pathToFileURL(localRegisterPath).href);\n }\n\n // Load configuration from environment and .smrt.config files\n const appConfig = await loadConfig();\n const aiConfig = appConfig?.ai || {};\n\n if (DEBUG) {\n console.error(\\`[MCP] Loaded \\${TOOLS.length} static tools\\`);\n console.error(\\`[MCP] Available tools:\\`, TOOLS.map(t => t.name).join(', '));\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n },\n {\n capabilities: {\n tools: {},\n ${hasTaskActions ? \"extensions: { 'io.modelcontextprotocol/tasks': {} },\" : ''}\n },\n cacheHints: {\n 'tools/list': TOOL_LIST_CACHE_HINT,\n },\n }\n );\n\n // Register ListTools handler\n server.setRequestHandler('tools/list', async (_request: ListToolsRequest) => {\n if (DEBUG) {\n console.error(\\`[MCP] ListTools request received\\`);\n }\n\n return {\n tools: [...TOOLS].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),\n };\n });\n\n // Register CallTool handler\n server.setRequestHandler('tools/call', async (request: CallToolRequest) => {\n const { name: toolName, arguments: args = {} } = request.params;\n\n if (DEBUG) {\n console.error(\\`[MCP] CallTool request: \\${toolName}\\`);\n console.error(\\`[MCP] Arguments:\\`, JSON.stringify(args, null, 2));\n }\n\n try {\n // Static switch statement for tool execution\n const runToolBody = async () => {\n switch (toolName) {\n${switchCases}\n\n default:\n throw new Error(\\`Unknown tool: \\${toolName}\\`);\n }\n };\n${\n hasTenantScoped\n ? `\n // Fail-closed tenant context for tenant-scoped tools (#1554).\n const toolObject = TOOL_TARGETS[toolName]?.objectName;\n const result =\n toolObject && TENANT_SCOPED.has(toolObject.toLowerCase())\n ? await runTenantScopedEntryPoint(\n { tenantScoped: true, tenantId: MCP_TENANT_ID, allowCrossTenant: MCP_ALLOW_CROSS_TENANT, surface: 'MCP' },\n runToolBody,\n )\n : await runToolBody();`\n : `\n const result = await runToolBody();`\n}\n\n if (DEBUG) {\n console.error(\\`[MCP] Tool executed successfully: \\${toolName}\\`);\n }\n\n return result;\n } catch (error) {\n const publicError = normalizePublicToolError(error);\n const errorMessage = publicError?.message ?? (error instanceof Error ? error.message : 'Unknown error');\n console.error(\\`[MCP] Tool execution failed: \\${toolName}\\`, error);\n\n return errorResult(\n { error: publicError ?? { message: errorMessage } },\n \\`Error executing tool \\${toolName}: \\${errorMessage}\\`,\n );\n }\n });\n\n return server;\n}\n\nasync function main() {\n try {\n // Initialize the registry before accepting an intercepted task call. The\n // SDK would normally invoke this factory for the first regular message,\n // but task calls can be the first message on a stdio connection.\n const server = await createServer();\n const transport = ${hasTaskActions ? 'new McpTaskExtensionTransport(new StdioServerTransport())' : 'undefined'};\n const handle = serveStdio(() => server, {\n ...(transport ? { transport } : {}),\n onerror: (error) => console.error('[MCP] Protocol error:', error),\n });\n const shutdown = async () => {\n if (DEBUG) console.error('[MCP] Shutting down gracefully');\n ${hasTaskActions ? 'if (taskRuntime) {\\n const { runner } = await taskRuntime;\\n await runner.stop();\\n }' : ''}\n await handle.close();\n process.exit(0);\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n } catch (error) {\n console.error('[MCP] Fatal error during server startup:', error);\n process.exit(1);\n }\n}\n\n// Start only when executed, so adapters and tests may import the factory.\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().catch((error) => {\n console.error('[MCP] Unhandled error:', error);\n process.exit(1);\n });\n}\n`;\n}\n\n/**\n * Generate package.json script for running MCP server\n *\n * @param serverPath - Path to generated server file (relative to package root)\n * @returns Script command for package.json\n */\nexport function generateMCPScript(\n serverPath: string = 'dist/mcp-server.js',\n): string {\n return `node ${serverPath}`;\n}\n\n/**\n * Generate Claude Desktop configuration example\n *\n * @param serverName - Name for the MCP server\n * @param serverPath - Absolute path to server file\n * @returns Configuration object for claude_desktop_config.json\n */\nexport function generateClaudeConfig(\n serverName: string,\n serverPath: string,\n): object {\n return {\n mcpServers: {\n [serverName]: {\n command: 'node',\n args: [serverPath],\n },\n },\n };\n}\n\n/**\n * Generate README documentation for MCP server setup\n *\n * @param serverName - Name of the MCP server\n * @param serverPath - Path to the server file\n * @returns Markdown documentation\n */\nexport function generateMCPDocumentation(\n serverName: string,\n serverPath: string,\n): string {\n return `# MCP Server Setup\n\nThis project includes an auto-generated MCP (Model Context Protocol) server that exposes SMRT objects as tools for AI integration.\n\n## Quick Start\n\n### 1. Build the MCP Server\n\n\\`\\`\\`bash\nnpm run build\n\\`\\`\\`\n\nThis generates the MCP server at: \\`${serverPath}\\`\n\n### 2. Configure Claude Desktop\n\nAdd the following to your Claude Desktop configuration file:\n\n**macOS**: \\`~/.config/Claude/claude_desktop_config.json\\`\n**Windows**: \\`%APPDATA%\\\\Claude\\\\claude_desktop_config.json\\`\n\n\\`\\`\\`json\n{\n \"mcpServers\": {\n \"${serverName}\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/${serverPath}\"]\n }\n }\n}\n\\`\\`\\`\n\nReplace \\`/absolute/path/to/\\` with the actual absolute path to your project directory.\n\n### 3. Restart Claude Desktop\n\nClose and reopen Claude Desktop to load the new MCP server.\n\n### 4. Test the Integration\n\nIn Claude Code, you can now use the auto-generated tools. For example:\n\n- \\`list_products\\` - List all products\n- \\`get_product\\` - Get a specific product by ID\n- \\`create_product\\` - Create a new product\n- And more...\n\n## Environment Variables\n\nThe MCP server supports optional environment variables:\n\n- \\`DATABASE_URL\\` - Database connection string\n\n**AI Provider Configuration (in priority order):**\n1. **\\`ai\\` block in \\`smrt.config.ts\\`** (declare once; keep the secret in the environment):\n - \\`ai: { provider: 'openai', model: 'gpt-4o', apiKeyEnv: 'OPENAI_API_KEY' }\\`\n\n2. **Generic environment configuration** (supports any provider):\n - \\`SMRT_AI_PROVIDER\\` - Provider name (e.g., 'openai', 'anthropic', 'claude-cli', 'gemini')\n - \\`SMRT_AI_API_KEY\\` - API key for the provider\n - \\`SMRT_AI_MODEL\\` - Model to use (optional)\n - \\`SMRT_AI_BASE_URL\\` - Endpoint override (optional)\n\n3. **Provider-specific key fallbacks** (used when the \\`ai\\` block selects the provider, and only for that provider):\n - \\`OPENAI_API_KEY\\`, \\`ANTHROPIC_API_KEY\\`, \\`GEMINI_API_KEY\\`\n - \\`CLAUDE_API_KEY\\` + \\`CLAUDE_MODEL\\` - Claude CLI provider (defaults to 'sonnet'); read by \\`@happyvertical/ai\\`\n\n**Examples:**\n\\`\\`\\`bash\n# Using generic configuration (recommended)\nexport SMRT_AI_PROVIDER=claude-cli\nexport SMRT_AI_MODEL=sonnet\n\n# Using provider-specific configuration\nexport CLAUDE_API_KEY=your-key\nexport CLAUDE_MODEL=sonnet\n\n# Using OpenAI\nexport OPENAI_API_KEY=your-openai-key\n\\`\\`\\`\n\n## Troubleshooting\n\n### Server Not Appearing in Claude\n\n1. Check that the path in \\`claude_desktop_config.json\\` is absolute\n2. Verify the server file exists at the specified path\n3. Check Claude Desktop logs for errors\n\n### Tools Not Working\n\n1. Ensure your database is accessible (if using one)\n2. Check that SMRT objects are properly decorated with \\`@smrt()\\`\n3. Look for errors in the MCP server output\n\n### Debug Mode\n\nTo enable debug logging, set the \\`DEBUG\\` constant to \\`true\\` in the generated server file.\n\n## Generated Tools\n\nThe following tools are automatically generated from your SMRT objects:\n\n- **CRUD Operations**: \\`list_\\`, \\`get_\\`, \\`create_\\`, \\`update_\\`, \\`delete_\\` for each object type\n- **Custom Actions**: Any custom methods included in the \\`@smrt()\\` decorator configuration\n\nSee the SMRT object definitions for the complete list of available tools and their parameters.\n`;\n}\n"],"mappings":"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"}
|