@happyvertical/smrt-core 0.41.0 → 0.42.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.md +1 -1
- package/agents/schema-paths.md +79 -8
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +2 -2
- package/dist/change-feed.js.map +1 -1
- package/dist/change-signals.d.ts.map +1 -1
- package/dist/change-signals.js +8 -1
- package/dist/change-signals.js.map +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +10 -9
- package/dist/class.js.map +1 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +2 -2
- package/dist/collection.js.map +1 -1
- package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
- package/dist/dispatch/collections/Dispatches.js +5 -5
- package/dist/dispatch/collections/Dispatches.js.map +1 -1
- package/dist/dispatch/models/Dispatch.d.ts +1 -1
- package/dist/dispatch/models/Dispatch.d.ts.map +1 -1
- package/dist/dispatch/models/Dispatch.js +3 -2
- package/dist/dispatch/models/Dispatch.js.map +1 -1
- package/dist/dispatch/models/DispatchSubscription.d.ts +1 -1
- package/dist/dispatch/models/DispatchSubscription.d.ts.map +1 -1
- package/dist/dispatch/models/DispatchSubscription.js +2 -1
- package/dist/dispatch/models/DispatchSubscription.js.map +1 -1
- package/dist/embeddings/storage.d.ts.map +1 -1
- package/dist/embeddings/storage.js +2 -1
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/learning/memory.d.ts.map +1 -1
- package/dist/learning/memory.js +5 -4
- package/dist/learning/memory.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.js +2 -2
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations/integer-width.d.ts +99 -0
- package/dist/migrations/integer-width.d.ts.map +1 -0
- package/dist/migrations/integer-width.js +237 -0
- package/dist/migrations/integer-width.js.map +1 -0
- package/dist/migrations/minor-units.d.ts +2 -2
- package/dist/migrations/minor-units.d.ts.map +1 -1
- package/dist/migrations/minor-units.js +16 -14
- package/dist/migrations/minor-units.js.map +1 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +28 -20
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations.js +2 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +1 -0
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +2 -0
- package/dist/schema/ddl/postgres-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +1 -33
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +1 -150
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index.d.ts +1 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -2
- package/dist/schema/live-parity.d.ts +3 -1
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +19 -0
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/schema/types.d.ts +17 -10
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema.js +1 -2
- package/dist/smrt-knowledge.json +7 -7
- package/dist/system/retention.d.ts.map +1 -1
- package/dist/system/retention.js +3 -3
- package/dist/system/retention.js.map +1 -1
- package/dist/system/schema.d.ts +2 -1
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +4 -2
- package/dist/system/schema.js.map +1 -1
- package/dist/utils/safe-integer.d.ts +19 -0
- package/dist/utils/safe-integer.d.ts.map +1 -0
- package/dist/utils/safe-integer.js +31 -0
- package/dist/utils/safe-integer.js.map +1 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -3
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +0 -53
- package/dist/vite-plugin/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/schema/override-system.d.ts +0 -43
- package/dist/schema/override-system.d.ts.map +0 -1
- package/dist/schema/override-system.js +0 -205
- package/dist/schema/override-system.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../src/schema/generator.ts"],"sourcesContent":["/**\n * Schema generator for SMRT objects\n * Converts AST field definitions to database schema definitions\n */\n\nimport { createHash } from 'node:crypto';\nimport type {\n FieldDefinition,\n FieldMeta,\n ManifestColumnDefinition,\n ManifestIndexDefinition,\n ManifestSchema,\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { classnameToTablename } from '../utils/naming.js';\nimport {\n type ConflictTableStrategy,\n conflictIndexName,\n resolveConflictColumns,\n resolveTenantColumn,\n servesSlugLookup,\n} from './conflict-target.js';\nimport { getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport {\n assertIdentifierFits,\n enforceIdentifierLimits,\n shortenIdentifier,\n} from './index-utils.js';\nimport {\n formatDefaultValue as formatDefaultValueShared,\n quoteIdentifier,\n quoteStringLiteral,\n} from './sql-identifiers.js';\nimport type {\n ColumnDefinition,\n DeclaredIndexDefinition,\n ForeignKeyDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n TriggerDefinition,\n} from './types.js';\n\n/**\n * Structural shape of a field as read from either the build-time manifest\n * (`FieldDefinition`) or the runtime ObjectRegistry (`getAllFields()` returns\n * `Map<string, any>` upstream). The schema generator consumes both through a\n * single set of property reads, so this interface captures every field key the\n * generator touches without coupling to either source's exact type.\n *\n * `_meta` carries the structured field-helper metadata (`FieldMeta`); a few\n * legacy fields (`indexed`, `idType`, `__tenancy`) may also appear at the top\n * level depending on the source, so they are declared here too.\n */\ninterface RegistryField {\n type?: FieldDefinition['type'];\n related?: string;\n required?: boolean;\n default?: unknown;\n description?: string;\n transient?: boolean;\n indexed?: boolean;\n idType?: 'uuid' | 'text';\n __tenancy?: FieldMeta['__tenancy'];\n _meta?: FieldMeta;\n}\n\ntype SchemaGeneratorConfig = {\n /**\n * The table's resolved conflict target. Registry callers pass\n * `ObjectRegistry.getConflictColumns()`; the manifest path passes the\n * object's `decoratorConfig` after `ManifestGenerator.normalizeConflictColumns()`\n * materialized the tenant-aware default (#2360). When absent, the generator\n * derives the default itself from `tenantScoped` and the strategy.\n */\n conflictColumns?: string[];\n /**\n * The schema owner's `@smrt({ tenantScoped })` config as the manifest\n * carries it — consulted only to derive the default conflict target when\n * `conflictColumns` is absent.\n */\n tenantScoped?: boolean | { field?: string };\n idType?: 'uuid' | 'text';\n /**\n * `@smrt({ indexes: [...] })` declarations (#2357).\n *\n * The manifest paths receive the whole `decoratorConfig`, so this arrives for\n * free there. The runtime paths rebuild a narrow config bag by hand\n * (`schema/utils.ts`, `testing/database.ts`) and must thread it explicitly —\n * omitting it is what made the option unreachable at runtime.\n */\n indexes?: DeclaredIndexDefinition[];\n registry?: {\n getConfig?(className: string): { idType?: 'uuid' | 'text' };\n getDescendants(baseClassName: string): string[];\n getAllFields(className: string): Promise<Map<string, RegistryField>>;\n getSTIBase?(className: string): string | null;\n };\n};\n\nexport class SchemaGenerator {\n /**\n * Generate schema definition from SMRT object definition\n */\n generateSchema(objectDef: SmartObjectDefinition): SchemaDefinition {\n const tableName = this.getTableName(objectDef);\n const columns = this.generateColumns(\n objectDef.fields,\n objectDef.decoratorConfig,\n );\n const indexes = this.generateIndexes(objectDef, columns);\n const triggers = this.generateTriggers(objectDef, tableName);\n const foreignKeys = this.extractForeignKeys(columns);\n const dependencies = this.extractDependencies(objectDef, foreignKeys);\n const version = this.generateVersion(objectDef);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes generateIndexes() appended, before the reference-column pass so\n // the composite suppresses a standalone tenant index.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n for (const trigger of triggers) {\n trigger.name = shortenIdentifier(trigger.name);\n }\n\n return {\n tableName,\n columns,\n indexes,\n triggers,\n foreignKeys,\n dependencies,\n version,\n packageName: this.extractPackageName(objectDef.filePath),\n baseClass: objectDef.extends,\n };\n }\n\n /**\n * Convert field type to SQL data type\n */\n private mapFieldTypeToSQL(fieldType: FieldDefinition['type']): SQLDataType {\n switch (fieldType) {\n case 'text':\n return 'TEXT';\n case 'integer':\n return 'INTEGER';\n case 'decimal':\n return 'REAL';\n case 'boolean':\n return 'BOOLEAN';\n case 'datetime':\n return 'TIMESTAMP';\n case 'json':\n return 'JSON';\n case 'foreignKey':\n return 'UUID'; // Foreign keys default to UUID, then same-package refs can be reconciled to target idType\n case 'crossPackageRef':\n return 'UUID'; // Cross-package refs are UUID ids with no DDL FK constraint\n default:\n return 'TEXT'; // Default fallback\n }\n }\n\n private getIdColumnType(config?: { idType?: 'uuid' | 'text' }): SQLDataType {\n return config?.idType === 'text' ? 'TEXT' : 'UUID';\n }\n\n private getRelationshipColumnType(field: RegistryField): SQLDataType {\n if (field?._meta?.sqlType) {\n return String(field._meta.sqlType).toUpperCase() as SQLDataType;\n }\n\n if (\n field?.type === 'crossPackageRef' &&\n (field._meta?.idType === 'text' || field.idType === 'text')\n ) {\n return 'TEXT';\n }\n\n return this.mapFieldTypeToSQL(field?.type || 'text');\n }\n\n private getReferenceKind(\n field: RegistryField,\n ): ColumnDefinition['referenceKind'] | undefined {\n if (\n field?.__tenancy?.isTenantIdField ||\n field?._meta?.__tenancy?.isTenantIdField\n ) {\n return 'tenantId';\n }\n\n if (field?.type === 'foreignKey') {\n return 'foreignKey';\n }\n\n if (field?.type === 'crossPackageRef') {\n return 'crossPackageRef';\n }\n\n return undefined;\n }\n\n private shouldEmitDefault(\n field: RegistryField,\n defaultValue: unknown,\n ): boolean {\n return !(\n this.getReferenceKind(field) === 'tenantId' &&\n this.getRelationshipColumnType(field) === 'UUID' &&\n defaultValue === ''\n );\n }\n\n private getRegistryTargetIdColumnType(\n relatedName: string | undefined,\n registry: SchemaGeneratorConfig['registry'] | undefined,\n ): SQLDataType | undefined {\n if (!relatedName || !registry?.getConfig) {\n return undefined;\n }\n\n const targetName = relatedName.split('.')[0];\n const targetNames = [targetName];\n const stiBase = registry.getSTIBase?.(targetName);\n if (stiBase && !targetNames.includes(stiBase)) {\n targetNames.push(stiBase);\n }\n\n for (const name of targetNames) {\n const idType = registry.getConfig(name)?.idType;\n if (idType === 'text') {\n return 'TEXT';\n }\n if (idType === 'uuid') {\n return 'UUID';\n }\n }\n\n return undefined;\n }\n\n private reconcileRegistryForeignKeyColumnTypes(\n columns: Record<string, ColumnDefinition>,\n fields: Map<string, RegistryField>,\n registry: SchemaGeneratorConfig['registry'] | undefined,\n ): void {\n if (!registry?.getConfig) {\n return;\n }\n\n for (const [fieldName, field] of fields.entries()) {\n if (field.type !== 'foreignKey' || field._meta?.sqlType) {\n continue;\n }\n\n const targetIdType = this.getRegistryTargetIdColumnType(\n field.related,\n registry,\n );\n if (!targetIdType) {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n if (columns[columnName]) {\n columns[columnName] = {\n ...columns[columnName],\n type: targetIdType,\n };\n }\n }\n }\n\n /**\n * Generate column definitions\n */\n private generateColumns(\n fields: Record<string, FieldDefinition>,\n config?: { idType?: 'uuid' | 'text' },\n ): Record<string, ColumnDefinition> {\n const columns: Record<string, ColumnDefinition> = {};\n\n // Always include base SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n description: 'Primary identifier',\n };\n\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n\n // Add fields from object definition\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n // Skip id fields as we handle them above\n if (\n fieldName === 'id' ||\n fieldName === 'created_at' ||\n fieldName === 'updated_at'\n ) {\n continue;\n }\n\n // Skip transient fields (non-persisted)\n // NOTE: This filtering logic must match SmrtObject.toJSON()\n // Changes to field filtering should be applied in BOTH places\n if (fieldDef.transient || fieldDef._meta?.transient) {\n continue;\n }\n\n // Skip relationship fields that don't create columns\n // oneToMany and manyToMany are relationship metadata, not actual database columns\n // foreignKey DOES create a column (it stores the foreign key ID)\n // NOTE: This must match the filtering in SmrtObject.toJSON()\n if (fieldDef.type === 'oneToMany' || fieldDef.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column (STI only)\n // In CTI mode, meta fields shouldn't be used, but skip them anyway for safety\n if (fieldDef.type === 'meta') {\n continue;\n }\n\n const column: ColumnDefinition = {\n type: this.getRelationshipColumnType(fieldDef),\n referenceKind: this.getReferenceKind(fieldDef),\n // If _meta.nullable is true, the field can be null regardless of required\n // This handles field helpers like text({ required: true, nullable: true })\n notNull: fieldDef._meta?.nullable ? false : fieldDef.required || false,\n unique: fieldDef._meta?.unique || false,\n description: fieldDef.description,\n };\n\n // Handle default values\n if (\n fieldDef.default !== undefined &&\n this.shouldEmitDefault(fieldDef, fieldDef.default)\n ) {\n column.defaultValue = fieldDef.default;\n }\n // Note: Removed automatic NOT NULL DEFAULT '' for TEXT columns\n // This was forcing all TEXT fields to be NOT NULL regardless of required option\n // If DuckDB ANY type inference is an issue, it should be handled at the adapter level\n // or with an explicit field option, not by silently changing schema semantics\n\n // Handle foreign keys\n if (fieldDef.type === 'foreignKey' && fieldDef.related) {\n const [table, columnName = 'id'] = fieldDef.related.split('.');\n column.foreignKey = {\n table,\n column: columnName,\n onDelete: 'CASCADE', // Default behavior\n onUpdate: 'CASCADE',\n };\n }\n\n // Handle unique constraints\n if (fieldName === 'slug' || fieldName === 'email') {\n column.unique = true;\n }\n\n columns[fieldName] = column;\n }\n\n return columns;\n }\n\n /**\n * Whether an existing index already leads with `leadingColumns`, in order.\n *\n * Only an UNQUALIFIED index (no partial `WHERE`, no JSON-path expression)\n * counts. A partial index such as `... WHERE _meta_type = 'Article'` cannot\n * serve a base-class polymorphic query, which carries no subtype predicate,\n * so it must not suppress the standalone index (#2359, review of #2384).\n *\n * A B-tree serves any prefix of its column list, so an index over\n * `(tenant_id, created_at, status)` covers both `(tenant_id)` equality\n * lookups and `(tenant_id, created_at)` ordering — hence the prefix test\n * rather than an exact match (#2363).\n */\n private hasUnqualifiedLeadingColumns(\n indexes: ReadonlyArray<{\n columns: string[];\n where?: string;\n jsonPath?: unknown;\n }>,\n leadingColumns: readonly string[],\n ): boolean {\n return indexes.some(\n (index) =>\n !index.where &&\n !index.jsonPath &&\n (index.columns?.length ?? 0) >= leadingColumns.length &&\n leadingColumns.every((column, i) => index.columns[i] === column),\n );\n }\n\n /**\n * Whether an existing index already serves equality lookups on `column`.\n *\n * Single-column shorthand for {@link hasUnqualifiedLeadingColumns}.\n */\n private hasUnqualifiedLeadingIndex(\n indexes: ReadonlyArray<{\n columns: string[];\n where?: string;\n jsonPath?: unknown;\n }>,\n column: string,\n ): boolean {\n return this.hasUnqualifiedLeadingColumns(indexes, [column]);\n }\n\n /**\n * The column list of the default list-ordering index for a table.\n *\n * Every generated list surface — REST, MCP, and the SvelteKit list route —\n * pages with `ORDER BY created_at DESC, <pk> ASC` (`DEFAULT_LIST_ORDER_BY`,\n * #2367). On a tenant-scoped table that page is always preceded by a\n * `tenant_id = ?` equality filter from the tenancy interceptor, so the\n * serving index leads with the tenant column and orders inside it.\n *\n * `created_at` is generator-owned on every path — a declared `createdAt`\n * field is rewritten to the same `TIMESTAMP NOT NULL DEFAULT\n * current_timestamp` column — so it is never a primary key and never\n * inline-UNIQUE. That is why only `indexes` needs checking for existing\n * coverage: no constraint-backed index can already order this column.\n *\n * Returns `null` when the table has no `created_at` column (no path emits\n * such a table today, but the helper stays total).\n */\n private getDefaultListOrderingColumns(\n columns: Record<string, { referenceKind?: string } | undefined>,\n ): string[] | null {\n if (!columns.created_at) return null;\n\n // The tenant column name is configurable (`@smrt({ tenantScoped: { field } })`),\n // so find it by reference kind rather than by the `tenant_id` spelling.\n const tenantColumn = Object.entries(columns).find(\n ([, columnDef]) => columnDef?.referenceKind === 'tenantId',\n )?.[0];\n\n return tenantColumn ? [tenantColumn, 'created_at'] : ['created_at'];\n }\n\n /**\n * Ensure the table can serve its own default list page from an index.\n *\n * Generated REST/MCP/SvelteKit list routes all page with\n * `ORDER BY created_at DESC, <pk> ASC LIMIT n` and no `created_at` index\n * existed on any schema path (the dead AST path indexed `updated_at`\n * instead), so every default list page was a sequential scan plus a top-N\n * sort of the whole table — 21 ms against 0.1 ms with the right composite on\n * the workload the assessment measured (#2363, finding A2).\n *\n * Emitted shape:\n *\n * - tenant-scoped table → `(<tenant column>, created_at)`. The tenancy\n * interceptor adds `tenant_id = ?` to every list, so the tenant column\n * leads and `created_at` orders within it.\n * - otherwise → `(created_at)`.\n *\n * No `DESC` declaration: PostgreSQL scans a B-tree backwards just as\n * cheaply, and `IndexDefinition` carries no per-column direction. The\n * trailing primary-key tiebreak is left out deliberately — its direction is\n * opposite to `created_at`'s, so no single-direction index can satisfy the\n * whole key anyway; the leading columns turn a full sort into an index scan\n * with an incremental sort over rows that share a timestamp.\n *\n * Call this AFTER {@link appendDeclaredIndexes} and BEFORE\n * {@link ensureReferenceColumnIndexes}: a declared composite that already\n * leads with the same columns suppresses this one, and this composite in\n * turn suppresses the standalone tenant index that would otherwise be a\n * redundant prefix of it.\n */\n private ensureDefaultListOrderingIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, { referenceKind?: string } | undefined>,\n tableName: string,\n ): void {\n const orderingColumns = this.getDefaultListOrderingColumns(columns);\n if (!orderingColumns) return;\n if (this.hasUnqualifiedLeadingColumns(indexes, orderingColumns)) return;\n\n const name = `${tableName}_${orderingColumns.join('_')}_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: orderingColumns });\n }\n\n /**\n * The `(meta_type, meta_id)` owner-lookup columns for a polymorphic\n * association table, or `null` when the table isn't one.\n *\n * `SmrtPolymorphicAssociation` contributes `metaType`/`metaId`/`role` (and\n * `sortOrder`) to any concrete subclass's manifest — the scanner's\n * `FRAMEWORK_ABSTRACT_BASE_NAMES` wiring merges the abstract base's fields\n * directly into the subclass, because the base carries no `@smrt()`\n * decorator of its own to declare an index on. Detected structurally, by\n * the same three-column test `cascade.ts`'s `isPolymorphicAssociationClass`\n * uses on registry field maps: requiring all three keeps an unrelated class\n * that merely happens to carry a `metaType` column from being treated as an\n * association table.\n */\n private getPolymorphicAssociationOwnerColumns(\n columns: Record<string, unknown>,\n ): string[] | null {\n if (\n !Object.hasOwn(columns, 'meta_type') ||\n !Object.hasOwn(columns, 'meta_id') ||\n !Object.hasOwn(columns, 'role')\n ) {\n return null;\n }\n return ['meta_type', 'meta_id'];\n }\n\n /**\n * Ensure a polymorphic association table indexes its owner lookup\n * `(meta_type, meta_id)` (#2364, epic #2382 finding A3).\n *\n * A concrete association's `conflictColumns` lead with its own FK (e.g.\n * `AssetAssociation`'s `asset_id, meta_type, meta_id, role`), so\n * `meta_type`/`meta_id` sit in the *middle* of that unique index — a\n * lookup like `AssetAssociationCollection.byLeft(metaType, metaId)`\n * (\"what points at this target\") filters columns 2-3 of a 4-column index\n * and cannot use it as a leading prefix. `metaId` is deliberately a bare\n * string, never a typed `@foreignKey`/`@crossPackageRef` (there is no\n * single target table), so {@link ensureReferenceColumnIndexes} never\n * covers it either.\n *\n * Call this alongside {@link ensureDefaultListOrderingIndex}, before\n * {@link ensureReferenceColumnIndexes}.\n *\n * Blast radius: this applies retroactively to every\n * `SmrtPolymorphicAssociation` subclass across every package on the next\n * `db:migrate`, not only the ones a given change touches — roll the wave\n * out with `smrt db:migrate --postgres-safe` (#2362), same as any other\n * bulk index addition.\n */\n private ensurePolymorphicAssociationIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, unknown>,\n tableName: string,\n ): void {\n const ownerColumns = this.getPolymorphicAssociationOwnerColumns(columns);\n if (!ownerColumns) return;\n if (this.hasUnqualifiedLeadingColumns(indexes, ownerColumns)) return;\n\n const name = `${tableName}_${ownerColumns.join('_')}_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: ownerColumns });\n }\n\n /**\n * Append `@smrt({ indexes: [...] })` declarations to a generated index set\n * (#2357).\n *\n * Shared by every schema path — build-time AST, runtime registry CTI/STI, and\n * manifest CTI/STI — so one declaration behaves identically however the schema\n * was derived. Call it *first*, ahead of\n * {@link ensureDefaultListOrderingIndex} (#2363) and\n * {@link ensureReferenceColumnIndexes}: a declared composite leading with a\n * reference column (e.g. `tenant_id`), or with the default ordering columns,\n * is exactly what those helpers' leads-with check is meant to defer to, and\n * appending afterwards would leave the table with a redundant standalone\n * index (#2384, #2359).\n *\n * Nothing is dropped quietly. A column that resolves to no column on the\n * table, a malformed entry, and a name collision with a different index are\n * all hard errors — a silently missing index is a production performance bug\n * that surfaces only under load.\n *\n * @param indexes - Generated index set, appended in place\n * @param declared - The object's `indexes` config, from any config source\n * @param columns - Emitted columns, keyed by column name\n * @param tableName - Table the indexes belong to (for error messages)\n */\n private appendDeclaredIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n }>,\n declared: unknown,\n columns: Record<string, unknown>,\n tableName: string,\n ): void {\n if (declared === undefined || declared === null) return;\n if (!Array.isArray(declared)) {\n throw new Error(\n `@smrt({ indexes }) on \"${tableName}\" must be an array of index declarations.`,\n );\n }\n\n for (const entry of declared) {\n const spec = this.validateDeclaredIndex(entry, tableName);\n\n const mappedColumns = spec.columns.map((column) =>\n this.resolveDeclaredIndexColumn(column, columns),\n );\n // `Object.hasOwn`, not `in`: the column map is a plain object, so `in`\n // also answers true for `toString`, `constructor` and every other\n // `Object.prototype` key — a declaration naming one would sail past this\n // check and emit an index on a column that does not exist.\n const unknownColumns = mappedColumns.filter(\n (column) => !Object.hasOwn(columns, column),\n );\n if (unknownColumns.length > 0) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" references unknown column(s): ` +\n `${unknownColumns.join(', ')}. Declared index columns must name a field or ` +\n `column on the object.`,\n );\n }\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n const index = {\n name: spec.name,\n columns: mappedColumns,\n ...(spec.unique === true ? { unique: true } : {}),\n ...(spec.where !== undefined ? { where: spec.where } : {}),\n };\n\n const existing = indexes.find((other) => other.name === index.name);\n if (existing) {\n // An identical re-declaration is harmless (the same object can be\n // generated through more than one path). A *different* index under a\n // name already taken is not: one of the two would vanish.\n if (this.sameIndexTarget(existing, index)) continue;\n throw new Error(\n `Declared index \"${index.name}\" on \"${tableName}\" collides with a different index ` +\n `of the same name. Rename the declared index.`,\n );\n }\n\n indexes.push(index);\n }\n }\n\n /** Narrow one raw `indexes[]` entry, rejecting anything unusable. */\n private validateDeclaredIndex(\n entry: unknown,\n tableName: string,\n ): DeclaredIndexDefinition {\n const spec = entry as Partial<DeclaredIndexDefinition> | null | undefined;\n const describe = () => {\n try {\n return JSON.stringify(entry) ?? String(entry);\n } catch {\n return String(entry);\n }\n };\n\n if (typeof spec !== 'object' || spec === null || Array.isArray(spec)) {\n throw new Error(\n `Declared index on \"${tableName}\" must be an object: got ${describe()}.`,\n );\n }\n if (typeof spec.name !== 'string' || spec.name.length === 0) {\n throw new Error(\n `Declared index on \"${tableName}\" needs a non-empty \"name\": got ${describe()}.`,\n );\n }\n // A declared name is the developer's, not the generator's: shortening it\n // would silently rename what they wrote, so an over-long one is refused\n // here — at manifest generation, which `smrt dev:knowledge-check` runs —\n // rather than truncated by PostgreSQL at deploy time (#2374).\n assertIdentifierFits(spec.name, 'Declared index', `table \"${tableName}\"`);\n if (\n !Array.isArray(spec.columns) ||\n spec.columns.length === 0 ||\n !spec.columns.every(\n (column) => typeof column === 'string' && column.length > 0,\n )\n ) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" needs a non-empty \"columns\" array of field or column names.`,\n );\n }\n if (spec.unique !== undefined && typeof spec.unique !== 'boolean') {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" has a non-boolean \"unique\": got ${describe()}.`,\n );\n }\n // An empty predicate would render `CREATE INDEX ... WHERE ;`, and treating\n // it as \"no predicate\" would silently widen a partial index to a full one.\n if (\n spec.where !== undefined &&\n (typeof spec.where !== 'string' || spec.where.trim().length === 0)\n ) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" has an empty or non-string \"where\" predicate.`,\n );\n }\n\n return spec as DeclaredIndexDefinition;\n }\n\n /**\n * Resolve a declared index column to an emitted column name.\n *\n * Declarations name SMRT fields, but only the runtime/manifest paths\n * snake_case them into columns — the build-time AST path keys columns by the\n * field name itself. Prefer an exact column hit, then the snake_case form, and\n * otherwise return the input unchanged so the caller reports it as unknown.\n */\n private resolveDeclaredIndexColumn(\n name: string,\n columns: Record<string, unknown>,\n ): string {\n // `Object.hasOwn` rather than `in` — see appendDeclaredIndexes: `in` would\n // resolve `toString` to itself and let the caller's unknown-column check\n // pass on a column that does not exist.\n if (Object.hasOwn(columns, name)) return name;\n const snakeCased = this.toSnakeCase(name);\n return Object.hasOwn(columns, snakeCased) ? snakeCased : name;\n }\n\n /** Whether two index definitions describe the same database object. */\n private sameIndexTarget(\n a: { columns: string[]; unique?: boolean; where?: string },\n b: { columns: string[]; unique?: boolean; where?: string },\n ): boolean {\n return (\n a.columns.length === b.columns.length &&\n a.columns.every((column, i) => column === b.columns[i]) &&\n (a.unique ?? false) === (b.unique ?? false) &&\n (a.where ?? null) === (b.where ?? null)\n );\n }\n\n /**\n * Ensure every reference column — `@foreignKey`, `@crossPackageRef`, and the\n * tenancy-injected `tenant_id` — has an index leading with it.\n *\n * These are the columns every relationship load, `include:` batch, reverse\n * ownership lookup and tenant-scoped read filters on. Before #2359 only the\n * registry (test) paths indexed foreign keys, the manifest (production)\n * paths indexed none of them, and `tenant_id` was indexed nowhere (#2356):\n * 196/231 `@foreignKey` and 91/92 `@crossPackageRef` columns shipped with no\n * serving index. Every schema path now calls this helper so the four paths\n * stay in step (see `schema-path-parity.test.ts`).\n *\n * \"Leading with it\" rather than \"always add\": a table that already has an\n * unqualified index starting on the column (commonly the `conflictColumns`\n * unique index, or an `indexed: true` opt-in) is already served, and a\n * standalone duplicate would only cost writes. A *partial* index does not\n * count — see {@link hasUnqualifiedLeadingIndex}.\n *\n * Reference columns are therefore always indexed; `indexed: true` on one of\n * them is redundant and harmless.\n *\n * Call this LAST in every path, after every other index (conflict, opt-in,\n * unique, the declared composites, the default list-ordering composite, and\n * any future addition) has been appended, so the leads-with suppression sees\n * the full set. In particular {@link ensureDefaultListOrderingIndex} runs\n * first on a tenant-scoped table: its `(tenant_id, created_at)` composite\n * serves the tenant equality filter too, so no standalone tenant index is\n * added (#2363).\n */\n private ensureReferenceColumnIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<\n string,\n | { referenceKind?: string; primaryKey?: boolean; unique?: boolean }\n | undefined\n >,\n tableName: string,\n ): void {\n for (const [columnName, columnDef] of Object.entries(columns)) {\n const kind = columnDef?.referenceKind;\n if (\n kind !== 'foreignKey' &&\n kind !== 'crossPackageRef' &&\n kind !== 'tenantId'\n ) {\n continue;\n }\n // The primary key, and a column-level UNIQUE (rendered inline on CTI\n // tables — PostgreSQL's implicit `_key` index), are already unique\n // indexes on every engine.\n if (columnDef?.primaryKey || columnDef?.unique) continue;\n if (this.hasUnqualifiedLeadingIndex(indexes, columnName)) continue;\n\n const name = `${tableName}_${columnName}_idx`;\n if (indexes.some((index) => index.name === name)) continue;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: [columnName] });\n }\n }\n\n /**\n * Whether `conflictColumns` names exactly the primary key column(s).\n *\n * `ON CONFLICT (id)` binds to the primary-key constraint on every engine, so\n * a separate unique index over the same column set is a second B-tree over\n * the same random UUIDs with nothing to add (#2359, finding A5).\n */\n private conflictColumnsArePrimaryKey(\n conflictColumns: string[],\n columns: Record<string, { primaryKey?: boolean } | undefined>,\n ): boolean {\n const primaryKeyColumns = Object.entries(columns)\n .filter(([, columnDef]) => columnDef?.primaryKey === true)\n .map(([name]) => name);\n return (\n primaryKeyColumns.length > 0 &&\n primaryKeyColumns.length === conflictColumns.length &&\n primaryKeyColumns.every((column) => conflictColumns.includes(column))\n );\n }\n\n /**\n * The table's tenant column as the generated columns describe it: the\n * column carrying `referenceKind: 'tenantId'` (set from the field's\n * `__tenancy.isTenantIdField` marker on every path).\n */\n private findTenantColumn(\n columns: Record<string, { referenceKind?: string } | undefined>,\n ): string | undefined {\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef?.referenceKind === 'tenantId') return columnName;\n }\n return undefined;\n }\n\n /**\n * Resolve the conflict target the unique conflict index must cover\n * (#2360). `config.conflictColumns` wins when the caller resolved it —\n * registry callers pass `ObjectRegistry.getConflictColumns()`, the manifest\n * pipeline passes the normalized `decoratorConfig` — otherwise the strategy\n * default, led by the tenant column when `config.tenantScoped` names a\n * tenant field that exists as a column. Both roads apply the same rule as\n * `ObjectRegistry.getConflictColumns()`, so schema and upsert agree by\n * construction (`schema-path-parity.test.ts`).\n */\n private resolveConflictTarget(\n strategy: ConflictTableStrategy,\n columns: Record<string, { referenceKind?: string } | undefined>,\n config: SchemaGeneratorConfig | undefined,\n ): { conflictColumns: string[]; tenantColumn: string | undefined } {\n const tenantColumn = this.findTenantColumn(columns);\n if (config?.conflictColumns && config.conflictColumns.length > 0) {\n return { conflictColumns: [...config.conflictColumns], tenantColumn };\n }\n const tenantScoped = config?.tenantScoped;\n const configuredTenantColumn = tenantScoped\n ? resolveTenantColumn(\n typeof tenantScoped === 'object'\n ? (tenantScoped.field ?? 'tenantId')\n : 'tenantId',\n (fieldName) => Boolean(columns[this.toSnakeCase(fieldName)]),\n (fieldName) => this.toSnakeCase(fieldName),\n )\n : undefined;\n return {\n conflictColumns: resolveConflictColumns({\n strategy,\n tenantColumn: configuredTenantColumn,\n }),\n tenantColumn,\n };\n }\n\n /**\n * Keep `(slug, context)` lookups served when `conflictColumns` are custom.\n *\n * The default conflict index is `(slug, context)`, and `loadFromSlug()`,\n * `getId()` and `getSavedId()` all filter on `slug`/`context` regardless of\n * the configured conflict key. A class that declares custom conflict\n * columns replaces that unique index and, before #2359, left those lookups\n * with no index at all — 120 tables (finding A7).\n *\n * Emitting a plain `(slug, context)` index is the safer of the two fixes:\n * routing the lookups through the conflict key would change which row a\n * slug resolves to on every such class, whereas an extra non-unique index\n * is purely additive. It is skipped when an unqualified index already\n * serves the lookup — one leading with `slug` (e.g. custom conflict columns\n * that still start with it), or the tenant-led default key\n * `(tenant_id, slug, context)` of a tenant-scoped table (#2360): every slug\n * lookup on such a table carries the tenant predicate (#2365) and is served\n * by that prefix, so a second index would only cost writes.\n */\n private ensureSlugLookupIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, { referenceKind?: string } | undefined>,\n tableName: string,\n ): void {\n if (!columns.slug || !columns.context) return;\n const tenantColumn = this.findTenantColumn(columns);\n if (\n indexes.some(\n (index) =>\n !index.where &&\n !index.jsonPath &&\n servesSlugLookup(index.columns ?? [], tenantColumn),\n )\n ) {\n return;\n }\n\n const name = `${tableName}_slug_context_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n indexes.push({ name, columns: ['slug', 'context'] });\n }\n\n /**\n * Emit the unique conflict index of an STI table from the resolved\n * conflict target (#2360): the STI default `(slug, context, _meta_type)`,\n * a custom `@smrt({ conflictColumns })` declared on the STI root, or the\n * default led by the tenant column when the root is tenant-scoped. Shared\n * by the registry and manifest STI paths so they cannot drift.\n *\n * No index is emitted when the target is the primary key itself\n * (`ON CONFLICT (id)` binds to the PK constraint), matching the CTI paths.\n */\n private emitStiConflictIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n description?: string;\n }>,\n columns: Record<\n string,\n { referenceKind?: string; primaryKey?: boolean } | undefined\n >,\n tableName: string,\n config: SchemaGeneratorConfig | undefined,\n ): void {\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'sti',\n columns,\n config,\n );\n if (this.conflictColumnsArePrimaryKey(conflictColumns, columns)) return;\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n });\n }\n\n /**\n * Emit unique indexes for `@field({ unique: true })` columns on an STI table.\n *\n * STI columns are the union of every class in the hierarchy, all nullable,\n * so a column-level `UNIQUE` (what CTI renders inline) is not enough on its\n * own: the migration differ cannot add a column constraint to an existing\n * table, but it can add an index. Two shapes (#2359, finding A4):\n *\n * - declared on the STI **base** (and therefore inherited by every class in\n * the table): one full unique index, table-wide like CTI;\n * - declared only on a **descendant**: one partial unique index per class\n * that carries the flag (the declaring class and, through inherited field\n * metadata, its own descendants), `WHERE _meta_type = '<qualified>'`, so\n * siblings that merely share the column name are not constrained.\n * Uniqueness is then enforced per concrete class, not across the subtree\n * — a documented limitation of the discriminator-partial shape. Engines\n * without partial indexes (DuckDB, JSON) skip this shape entirely rather\n * than widen it to a table-wide UNIQUE (see DuckDBStrategy.generateIndexes\n * and SchemaComparer.compareIndexes).\n *\n * @param declarers - column name → classes whose field metadata carries\n * `unique: true` for that column, in hierarchy order (base first).\n */\n private emitStiUniqueIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n }>,\n declarers: Map<string, Set<string>>,\n baseClassName: string,\n tableName: string,\n ): void {\n for (const [columnName, classNames] of declarers.entries()) {\n if (classNames.has(baseClassName)) {\n indexes.push({\n name: `${tableName}_${columnName}_unique_idx`,\n columns: [columnName],\n unique: true,\n });\n continue;\n }\n for (const className of classNames) {\n const simpleName = className.includes(':')\n ? className.slice(className.lastIndexOf(':') + 1)\n : className;\n let name = `${tableName}_${columnName}_${this.toSnakeCase(simpleName)}_unique_idx`;\n // Two classes of one hierarchy may share a simple name across\n // packages (`@a:Event`, `@b:Event`); the merged index list dedupes by\n // name, so disambiguate with a short digest of the qualified name.\n if (indexes.some((index) => index.name === name)) {\n const digest = createHash('sha256')\n .update(className)\n .digest('hex')\n .slice(0, 6);\n name = `${tableName}_${columnName}_${this.toSnakeCase(simpleName)}_${digest}_unique_idx`;\n }\n indexes.push({\n name,\n columns: [columnName],\n unique: true,\n where: `_meta_type = ${quoteStringLiteral(className)}`,\n });\n }\n }\n }\n\n /**\n * Generate index definitions\n */\n private generateIndexes(\n objectDef: SmartObjectDefinition,\n columns: Record<string, ColumnDefinition>,\n ): IndexDefinition[] {\n const indexes: IndexDefinition[] = [];\n const tableName = this.getTableName(objectDef);\n\n // Create indexes for foreign keys\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef.foreignKey) {\n indexes.push({\n name: `idx_${tableName}_${columnName}`,\n columns: [columnName],\n description: `Index for foreign key ${columnName}`,\n });\n }\n }\n\n // Create index for updated_at (common query pattern)\n indexes.push({\n name: `idx_${tableName}_updated_at`,\n columns: ['updated_at'],\n description: 'Index for timestamp queries',\n });\n\n // Create unique indexes\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef.unique && !columnDef.primaryKey) {\n indexes.push({\n name: `idx_${tableName}_${columnName}_unique`,\n columns: [columnName],\n unique: true,\n description: `Unique index for ${columnName}`,\n });\n }\n }\n\n // Declared indexes (#2357), before ensureReferenceColumnIndexes below.\n this.appendDeclaredIndexes(\n indexes,\n objectDef.decoratorConfig?.indexes,\n columns,\n tableName,\n );\n\n return indexes;\n }\n\n /**\n * Generate trigger definitions for automatic timestamp updates\n */\n private generateTriggers(\n _objectDef: SmartObjectDefinition,\n tableName: string,\n ): TriggerDefinition[] {\n return [\n {\n name: `trg_${tableName}_updated_at`,\n when: 'BEFORE',\n event: 'UPDATE',\n body: `UPDATE ${quoteIdentifier(tableName)} SET \"updated_at\" = current_timestamp WHERE \"id\" = NEW.\"id\";`,\n description: 'Automatically update updated_at timestamp',\n },\n ];\n }\n\n /**\n * Extract foreign key definitions\n */\n private extractForeignKeys(\n columns: Record<string, ColumnDefinition>,\n ): ForeignKeyDefinition[] {\n const foreignKeys: ForeignKeyDefinition[] = [];\n\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef.foreignKey) {\n foreignKeys.push({\n column: columnName,\n referencesTable: columnDef.foreignKey.table,\n referencesColumn: columnDef.foreignKey.column,\n onDelete: columnDef.foreignKey.onDelete,\n onUpdate: columnDef.foreignKey.onUpdate,\n });\n }\n }\n\n return foreignKeys;\n }\n\n /**\n * Extract schema dependencies from foreign keys and inheritance\n */\n private extractDependencies(\n objectDef: SmartObjectDefinition,\n foreignKeys: ForeignKeyDefinition[],\n ): string[] {\n const dependencies = new Set<string>();\n\n // Add dependencies from foreign keys\n for (const fk of foreignKeys) {\n dependencies.add(fk.referencesTable);\n }\n\n // Add dependencies from base class\n if (\n objectDef.extends &&\n objectDef.extends !== 'SmrtObject' &&\n objectDef.extends !== 'SmrtCollection'\n ) {\n dependencies.add(this.classNameToTableName(objectDef.extends));\n }\n\n return Array.from(dependencies);\n }\n\n /**\n * Generate version hash for schema\n */\n private generateVersion(objectDef: SmartObjectDefinition): string {\n const content = JSON.stringify({\n className: objectDef.className,\n fields: objectDef.fields,\n extends: objectDef.extends,\n });\n return createHash('sha256').update(content).digest('hex').substring(0, 8);\n }\n\n /**\n * Get table name from object definition\n */\n private getTableName(objectDef: SmartObjectDefinition): string {\n return this.classNameToTableName(objectDef.className);\n }\n\n /**\n * Convert class name to table name (camelCase to snake_case, pluralized)\n */\n private classNameToTableName(className: string): string {\n return classnameToTablename(className);\n }\n\n /**\n * Extract package name from file path\n */\n private extractPackageName(filePath: string): string {\n const match = filePath.match(/packages\\/([^/]+)/);\n return match ? match[1] : 'unknown';\n }\n\n /**\n * Generate schema definition from ObjectRegistry fields (runtime)\n *\n * This method builds a SchemaDefinition from ObjectRegistry cached fields,\n * enabling schema generation from decorated classes at runtime.\n *\n * @param className - Class name to look up in ObjectRegistry\n * @param tableName - Table name (from SMRT_TABLE_NAME or derived)\n * @param fields - Map of Field definitions from ObjectRegistry\n * @returns Schema definition object\n */\n generateSchemaFromRegistry(\n className: string,\n tableName: string,\n fields: Map<string, RegistryField>,\n config?: SchemaGeneratorConfig,\n ): SchemaDefinition {\n const columns: Record<string, ColumnDefinition> = {};\n\n // Check for custom primary key\n let hasCustomPK = false;\n for (const [_fieldName, field] of fields.entries()) {\n if (field._meta?.primaryKey) {\n hasCustomPK = true;\n break;\n }\n }\n\n // Add default SMRT fields if no custom primary key\n if (!hasCustomPK) {\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n description: 'Primary identifier',\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n description: 'URL-friendly identifier',\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n defaultValue: '',\n description: 'Context for slug scoping',\n };\n }\n\n // Track timestamp fields to avoid duplicates\n let hasCreatedAt = false;\n let hasUpdatedAt = false;\n // Track regular (column-backed) fields that opted into a plain column index\n // via `indexed: true`. FK, unique, and meta fields each go through their\n // own dedicated index emission paths and are excluded from this set.\n const indexedColumns = new Set<string>();\n\n // Add fields from ObjectRegistry\n for (const [fieldName, field] of fields.entries()) {\n // Skip transient fields (non-persisted)\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n const isDefaultSmrtColumn =\n fieldName === 'id' || fieldName === 'slug' || fieldName === 'context';\n\n // Default SMRT columns are added above when we use the standard primary\n // key path. When a class declares a custom primary key, only explicitly\n // declared id/slug/context fields should survive this loop.\n if (\n isDefaultSmrtColumn &&\n (!hasCustomPK || field._meta?.__smrtSystemField === true)\n ) {\n continue;\n }\n\n // Track timestamp fields\n if (fieldName === 'created_at' || fieldName === 'createdAt') {\n if (hasCreatedAt) continue;\n hasCreatedAt = true;\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n continue;\n }\n if (fieldName === 'updated_at' || fieldName === 'updatedAt') {\n if (hasUpdatedAt) continue;\n hasUpdatedAt = true;\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n continue;\n }\n\n // Skip relationship fields that don't create columns\n // oneToMany and manyToMany are relationship metadata, not actual database columns\n // foreignKey DOES create a column (it stores the foreign key ID)\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column (STI only)\n // In CTI mode, meta fields shouldn't be used, but skip them anyway for safety\n if (field.type === 'meta') {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // If _meta.nullable is true, the field can be null regardless of required\n notNull: field._meta?.nullable ? false : field._meta?.required || false,\n primaryKey: field._meta?.primaryKey || false,\n unique: field._meta?.unique || false,\n description: field._meta?.description,\n };\n\n // Get default value\n if (\n field._meta?.default !== undefined &&\n this.shouldEmitDefault(field, field._meta.default)\n ) {\n columnDef.defaultValue = field._meta.default;\n }\n // Note: Removed automatic NOT NULL DEFAULT '' for TEXT columns\n // This was forcing all TEXT fields to be NOT NULL regardless of required option\n // If DuckDB ANY type inference is an issue, it should be handled at the adapter level\n // or with an explicit field option, not by silently changing schema semantics\n\n // Handle foreign keys\n if (field.type === 'foreignKey') {\n // Type cast to access relationship-specific properties\n const relatedName = field.related; // Top-level property\n const onDeleteAction = field._meta?.onDelete;\n\n if (relatedName) {\n columnDef.foreignKey = {\n table: this.classNameToTableName(relatedName),\n column: 'id',\n onDelete: onDeleteAction || 'CASCADE',\n onUpdate: 'CASCADE',\n };\n }\n }\n\n // Track opt-in column indexes for regular (non-FK, non-unique) fields.\n // FK columns already get auto-indexed below; unique columns produce\n // their own unique index; meta fields go through the jsonPath path.\n const isIndexed = field._meta?.indexed === true || field.indexed === true;\n if (\n isIndexed &&\n !columnDef.foreignKey &&\n !columnDef.unique &&\n !columnDef.primaryKey\n ) {\n indexedColumns.add(this.toSnakeCase(fieldName));\n }\n\n columns[this.toSnakeCase(fieldName)] = columnDef;\n }\n\n // Ensure timestamp columns exist\n if (!hasCreatedAt) {\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n }\n if (!hasUpdatedAt) {\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n }\n\n this.reconcileRegistryForeignKeyColumnTypes(\n columns,\n fields,\n config?.registry,\n );\n\n // Generate indexes. No `<table>_id_idx`: the primary key is already a\n // unique index on every engine, and the second B-tree over the same\n // random UUIDs cost writes on 238/238 tables for nothing (#2359, A5).\n // The differ drops the legacy one from existing databases.\n const indexes: IndexDefinition[] = [];\n\n if (!hasCustomPK) {\n // Tenant-scoped tables key on `(tenant_id, slug, context)` under the\n // stable `<table>_slug_context_idx` name (#2360).\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'cti',\n columns,\n config,\n );\n if (!this.conflictColumnsArePrimaryKey(conflictColumns, columns)) {\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n description: `Unique conflict index for ${className}`,\n });\n }\n }\n\n // Custom conflict columns replace the (slug, context) index, but slug\n // loading still filters on it (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // Emit opt-in column indexes for regular fields tagged with `indexed: true`\n for (const colName of indexedColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Index for ${colName}`,\n });\n }\n\n // Indexes declared with `@smrt({ indexes: [...] })` (#2357). Appended\n // before the reference-column helper so a declared composite leading with\n // a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n return {\n tableName,\n columns,\n indexes,\n triggers: [],\n foreignKeys: this.extractForeignKeys(columns),\n dependencies: [],\n version: createHash('sha256')\n .update(JSON.stringify(columns))\n .digest('hex')\n .substring(0, 8),\n packageName: 'runtime',\n baseClass: 'SmrtObject',\n };\n }\n\n /**\n * Generate STI (Single Table Inheritance) schema from ObjectRegistry fields\n *\n * Creates a shared table for an inheritance hierarchy with:\n * - _meta_type: Discriminator column to identify class type\n * - _meta_data: JSON column for flexible field storage\n * - Union of all FK columns from descendants (all nullable)\n * - One plain index per reference column (FK / cross-package ref /\n * tenant_id). Plain rather than partial-by-class: base-class polymorphic\n * queries carry no `_meta_type` predicate, so a partial index could not\n * serve them, while a plain index serves both those and the child\n * collections' `_meta_type = X AND fk = ?` filters — one index per column\n * instead of one per (column, class) (#2359).\n * - Unique indexes for `@field({ unique: true })` columns — see\n * {@link emitStiUniqueIndexes}.\n *\n * @param baseClassName - Base class name for the STI hierarchy\n * @param tableName - Shared table name (from base class)\n * @param fields - Map of Field definitions from base class\n * @returns Schema definition object for STI table\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 * roomId = foreignKey(Room);\n * }\n *\n * // Generates single table 'events' with:\n * // - title (from Event)\n * // - room_id (from Meeting, nullable)\n * // - _meta_type TEXT NOT NULL (discriminator)\n * // - _meta_data JSON (flexible storage)\n * ```\n */\n async generateSTISchemaFromRegistry(\n baseClassName: string,\n tableName: string,\n _fields: Map<string, RegistryField>,\n config?: SchemaGeneratorConfig,\n ): Promise<SchemaDefinition> {\n const ObjectRegistry =\n config?.registry ?? (await import('../registry.js')).ObjectRegistry;\n const columns: Record<string, ColumnDefinition> = {};\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n description: 'Primary identifier',\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n description: 'URL-friendly identifier',\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n defaultValue: '',\n description: 'Context for slug scoping',\n };\n\n // Add STI discriminator column\n columns._meta_type = {\n type: 'TEXT',\n notNull: true,\n description: 'Class type discriminator for STI',\n };\n\n // Add meta column for flexible storage\n columns._meta_data = {\n type: 'JSON',\n notNull: false,\n description: 'Flexible JSON storage for meta() fields',\n };\n\n // Track timestamp fields to avoid duplicates\n let hasCreatedAt = false;\n let hasUpdatedAt = false;\n\n // Get all descendants to aggregate fields\n const descendants = ObjectRegistry.getDescendants(baseClassName);\n const allClassNames = [baseClassName, ...descendants];\n\n // Track meta fields opted into JSON-path indexing (deduped across STI subtypes)\n const indexedMetaFields = new Set<string>();\n // Track regular-field columns opted into plain column indexing\n const indexedStiColumns = new Set<string>();\n // Track `unique: true` columns and the classes declaring them (#2359, A4)\n const uniqueColumnDeclarers = new Map<string, Set<string>>();\n\n // Aggregate fields from base and all descendants\n for (const className of allClassNames) {\n const classFields: Map<string, RegistryField> =\n await ObjectRegistry.getAllFields(className);\n\n for (const [fieldName, field] of classFields.entries()) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Capture indexed meta fields before the skip-meta branch below\n if (\n field.type === 'meta' &&\n (field.indexed === true || field._meta?.indexed === true)\n ) {\n indexedMetaFields.add(fieldName);\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context'\n ) {\n continue;\n }\n\n // Track timestamp fields\n if (fieldName === 'created_at' || fieldName === 'createdAt') {\n if (hasCreatedAt) continue;\n hasCreatedAt = true;\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n continue;\n }\n if (fieldName === 'updated_at' || fieldName === 'updatedAt') {\n if (hasUpdatedAt) continue;\n hasUpdatedAt = true;\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n\n // `unique: true` is recorded per declaring class (before the\n // inherited-column skip below, since descendants re-list inherited\n // fields) so the index shape can depend on where it was declared.\n if (field._meta?.unique === true) {\n let declarers = uniqueColumnDeclarers.get(columnName);\n if (!declarers) {\n declarers = new Set();\n uniqueColumnDeclarers.set(columnName, declarers);\n }\n declarers.add(className);\n }\n\n // Check if column already exists (inherited from parent)\n if (columns[columnName]) {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // STI: All columns nullable (union of child fields)\n notNull: false,\n primaryKey: false,\n // Uniqueness is enforced through indexes (emitStiUniqueIndexes), not\n // an inline column constraint the differ could never add later.\n unique: false,\n description: field._meta?.description,\n };\n\n // Get default value (but not applied in STI - defaults handled by application)\n if (\n field._meta?.default !== undefined &&\n this.shouldEmitDefault(field, field._meta.default)\n ) {\n columnDef.defaultValue = field._meta.default;\n }\n\n // Handle foreign keys\n if (field.type === 'foreignKey') {\n const relatedName = field.related; // Top-level property\n const onDeleteAction = field._meta?.onDelete;\n\n if (relatedName) {\n columnDef.foreignKey = {\n table: this.classNameToTableName(relatedName),\n column: 'id',\n onDelete: onDeleteAction || 'CASCADE',\n onUpdate: 'CASCADE',\n };\n }\n }\n\n // Track opt-in column indexes for regular STI columns\n const isIndexed =\n field._meta?.indexed === true || field.indexed === true;\n if (isIndexed && !columnDef.foreignKey) {\n indexedStiColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n }\n\n // Ensure timestamp columns exist\n if (!hasCreatedAt) {\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n }\n if (!hasUpdatedAt) {\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n }\n\n for (const className of allClassNames) {\n const classFields = await ObjectRegistry.getAllFields(className);\n this.reconcileRegistryForeignKeyColumnTypes(\n columns,\n classFields,\n ObjectRegistry,\n );\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: IndexDefinition[] = [];\n\n // Unique conflict index — by default (slug, context, _meta_type): STI\n // subclasses share a table, so the discriminator participates in\n // identity and two subtypes can share the same (slug, context). The key\n // is derived from the resolved config rather than hard-coded (#2360,\n // A4): a custom `@smrt({ conflictColumns })` on the STI root replaces it\n // (previously ignored here while `getConflictColumns()` honoured it, so\n // every save failed on PostgreSQL), and a tenant-scoped root leads it\n // with the tenant column. PostgreSQL UPSERT requires the live schema to\n // carry a matching unique index; the migration differ repairs older\n // deployments in place by name (issue #1165, #2360).\n this.emitStiConflictIndex(indexes, columns, tableName, config);\n\n // Index on type column (for polymorphic queries)\n indexes.push({\n name: `${tableName}_meta_type_idx`,\n columns: ['_meta_type'],\n description: 'Index for type discriminator queries',\n });\n\n // Unique indexes for `@field({ unique: true })` columns (#2359, A4)\n this.emitStiUniqueIndexes(\n indexes,\n uniqueColumnDeclarers,\n baseClassName,\n tableName,\n );\n\n // Custom conflict columns replace the (slug, context, _meta_type) index,\n // but slug loading still filters on slug/context (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // JSON-path indexes for @meta({ indexed: true }) fields\n for (const fieldName of indexedMetaFields) {\n indexes.push({\n name: `${tableName}_meta_${this.toSnakeCase(fieldName)}_idx`,\n columns: [],\n jsonPath: { column: '_meta_data', path: fieldName },\n description: `JSON-path index for @meta({ indexed: true }) field ${fieldName}`,\n });\n }\n\n // Plain column indexes for regular STI columns opted in via `indexed: true`\n for (const colName of indexedStiColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Index for ${colName}`,\n });\n }\n\n // Indexes declared on the STI base with `@smrt({ indexes: [...] })`\n // (#2357). Descendants share one table, so the base owns its access paths\n // and a declaration there applies to every subtype's reads. Appended\n // before the reference-column helper so a declared composite leading with\n // a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n return {\n tableName,\n columns,\n indexes,\n triggers: [],\n foreignKeys: this.extractForeignKeys(columns),\n dependencies: [],\n version: createHash('sha256')\n .update(JSON.stringify({ columns, baseClassName, descendants }))\n .digest('hex')\n .substring(0, 8),\n packageName: 'runtime',\n baseClass: 'SmrtObject',\n };\n }\n\n /**\n * Generate STI schema from manifest data (build-time, no runtime registry)\n *\n * This method generates STI schema purely from manifest data, without depending\n * on ObjectRegistry. This enables pre-generating schemas at build time for\n * efficient external package consumption.\n *\n * @param baseClassName - Base class name for the STI hierarchy\n * @param tableName - Shared table name (from base class)\n * @param baseFields - Fields from the base class\n * @param manifest - Full manifest containing all objects\n * @returns ManifestSchema for storage in manifest.json\n */\n generateSTISchemaFromManifest(\n baseClassName: string,\n tableName: string,\n _baseFields: Record<string, FieldDefinition>,\n manifest: SmartObjectManifest,\n config?: SchemaGeneratorConfig,\n ): ManifestSchema {\n const columns: Record<string, ManifestColumnDefinition> = {};\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n default: '',\n };\n\n // Add STI discriminator column\n columns._meta_type = {\n type: 'TEXT',\n notNull: true,\n };\n\n // Add meta column for flexible storage\n columns._meta_data = {\n type: 'JSON',\n notNull: false,\n };\n\n // Timestamp fields\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n // Find all descendants in manifest. Manifest keys are qualified names\n // while callers may pass the base's simple name (an external STI base is\n // reported that way); resolve to the key so the base's own fields are\n // aggregated and the base-declared unique check below sees the same\n // spelling the descendant loop records.\n const baseKey = this.resolveManifestClassKey(baseClassName, manifest);\n const descendants = this.findDescendantsInManifest(baseKey, manifest);\n const allClassNames = [baseKey, ...descendants];\n\n // Track meta fields opted into JSON-path indexing (deduped across STI subtypes)\n const indexedMetaFields = new Set<string>();\n // Track regular columns opted into plain column indexing\n const indexedStiColumns = new Set<string>();\n // Track `unique: true` columns and the classes declaring them (#2359, A4)\n const uniqueColumnDeclarers = new Map<string, Set<string>>();\n\n // Aggregate fields from base and all descendants\n for (const className of allClassNames) {\n const objDef = manifest.objects[className];\n if (!objDef) continue;\n\n for (const [fieldName, field] of Object.entries(objDef.fields)) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Capture indexed meta fields before the skip-meta branch below.\n // `indexed` is normally carried in `_meta`, but tolerate a legacy\n // top-level flag too — RegistryField models both placements.\n const indexedField: RegistryField = field;\n if (\n field.type === 'meta' &&\n (indexedField.indexed === true || field._meta?.indexed === true)\n ) {\n indexedMetaFields.add(fieldName);\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context' ||\n fieldName === 'created_at' ||\n fieldName === 'createdAt' ||\n fieldName === 'updated_at' ||\n fieldName === 'updatedAt'\n ) {\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n\n // `unique: true` per declaring class — see generateSTISchemaFromRegistry.\n if (field._meta?.unique === true) {\n let declarers = uniqueColumnDeclarers.get(columnName);\n if (!declarers) {\n declarers = new Set();\n uniqueColumnDeclarers.set(columnName, declarers);\n }\n declarers.add(className);\n }\n\n // Check if column already exists (inherited from parent)\n if (columns[columnName]) {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ManifestColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // STI: All columns nullable (union of child fields)\n notNull: false,\n };\n\n // Get default value\n if (\n field.default !== undefined &&\n this.shouldEmitDefault(field, field.default)\n ) {\n columnDef.default = field.default;\n }\n\n // Track opt-in column indexes for regular STI columns\n const isIndexed =\n indexedField.indexed === true || field._meta?.indexed === true;\n if (isIndexed && field.type !== 'foreignKey') {\n indexedStiColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: ManifestIndexDefinition[] = [];\n\n // Unique conflict index (default slug, context, _meta_type; custom or\n // tenant-led per the root's config) — see generateSTISchemaFromRegistry.\n this.emitStiConflictIndex(indexes, columns, tableName, config);\n\n // Index on type column (for polymorphic queries)\n indexes.push({\n name: `${tableName}_meta_type_idx`,\n columns: ['_meta_type'],\n });\n\n // Unique indexes for `@field({ unique: true })` columns (#2359, A4)\n this.emitStiUniqueIndexes(\n indexes,\n uniqueColumnDeclarers,\n baseKey,\n tableName,\n );\n\n // Custom conflict columns replace the (slug, context, _meta_type) index,\n // but slug loading still filters on slug/context (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // JSON-path indexes for @meta({ indexed: true }) fields\n for (const fieldName of indexedMetaFields) {\n indexes.push({\n name: `${tableName}_meta_${this.toSnakeCase(fieldName)}_idx`,\n columns: [],\n jsonPath: { column: '_meta_data', path: fieldName },\n });\n }\n\n // Plain column indexes for regular STI columns opted in via `indexed: true`\n for (const colName of indexedStiColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n });\n }\n\n // Indexes declared on the STI base with `@smrt({ indexes: [...] })`\n // (#2357), before the reference-column helper so a declared composite\n // leading with a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359). Runs last so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates them; the cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n const schemaDefinition: SchemaDefinition = {\n tableName,\n columns: this.convertManifestColumnsToSchemaColumns(columns),\n indexes: indexes.map((idx) => ({\n name: idx.name,\n columns: idx.columns,\n unique: idx.unique,\n where: idx.where,\n jsonPath: idx.jsonPath,\n })),\n triggers: [],\n foreignKeys: [],\n dependencies: [],\n version: '',\n packageName: '',\n };\n\n const ddl = this.generateSQL(schemaDefinition);\n\n // Generate version hash\n const version = createHash('sha256')\n .update(JSON.stringify({ columns, baseClassName, descendants }))\n .digest('hex')\n .substring(0, 8);\n\n return {\n tableName,\n ddl,\n columns,\n indexes,\n version,\n };\n }\n\n /**\n * Generate CTI (Class Table Inheritance) schema from manifest data\n *\n * @param className - Class name\n * @param tableName - Table name\n * @param fields - Fields from the class\n * @returns ManifestSchema for storage in manifest.json\n */\n generateCTISchemaFromManifest(\n className: string,\n tableName: string,\n fields: Record<string, FieldDefinition>,\n config?: SchemaGeneratorConfig,\n ): ManifestSchema {\n const columns: Record<string, ManifestColumnDefinition> = {};\n\n // Track regular columns opted into a plain index via `@field({ indexed: true })`.\n // Mirrors the STI path (generateSTISchemaFromManifest) so CTI tables honor\n // the documented `FieldOptions.indexed` flag too.\n const indexedColumns = new Set<string>();\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n default: '',\n };\n\n // Timestamp fields\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n // Add class fields\n for (const [fieldName, field] of Object.entries(fields)) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context' ||\n fieldName === 'created_at' ||\n fieldName === 'createdAt' ||\n fieldName === 'updated_at' ||\n fieldName === 'updatedAt'\n ) {\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ManifestColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n notNull: field._meta?.nullable ? false : field.required || false,\n unique: field._meta?.unique || false,\n };\n\n if (\n field.default !== undefined &&\n this.shouldEmitDefault(field, field.default)\n ) {\n columnDef.default = field.default;\n }\n\n // Opt-in plain column index. FK columns and unique columns get their own\n // indexes, so skip those here.\n const indexedField: RegistryField = field;\n const isIndexed =\n indexedField.indexed === true || field._meta?.indexed === true;\n if (isIndexed && field.type !== 'foreignKey' && !columnDef.unique) {\n indexedColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: ManifestIndexDefinition[] = [];\n\n // Custom conflict columns if provided, otherwise slug+context — led by\n // the tenant column on a tenant-scoped table, under the same stable\n // index name (#2360).\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'cti',\n columns,\n config,\n );\n if (!this.conflictColumnsArePrimaryKey(conflictColumns, columns)) {\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n });\n }\n\n // Custom conflict columns replace the (slug, context) index, but slug\n // loading still filters on it (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // Plain column indexes for regular columns opted in via `indexed: true`.\n for (const colName of indexedColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n });\n }\n\n // Indexes declared with `@smrt({ indexes: [...] })` (#2357), before the\n // reference-column helper so a declared composite leading with a\n // reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359). Runs last so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates them; the cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n const schemaDefinition: SchemaDefinition = {\n tableName,\n columns: this.convertManifestColumnsToSchemaColumns(columns),\n indexes: indexes.map((idx) => ({\n name: idx.name,\n columns: idx.columns,\n unique: idx.unique,\n where: idx.where,\n jsonPath: idx.jsonPath,\n })),\n triggers: [],\n foreignKeys: [],\n dependencies: [],\n version: '',\n packageName: '',\n };\n\n const ddl = this.generateSQL(schemaDefinition);\n\n // Generate version hash\n const version = createHash('sha256')\n .update(JSON.stringify({ columns, className }))\n .digest('hex')\n .substring(0, 8);\n\n return {\n tableName,\n ddl,\n columns,\n indexes,\n version,\n };\n }\n\n /**\n * Resolve a class name (simple or qualified) to the key it is stored under\n * in `manifest.objects`. Falls back to the input when the manifest does not\n * carry the class (e.g. an external STI base absent from the aggregated\n * manifest), so callers can keep using it as an opaque label.\n */\n private resolveManifestClassKey(\n className: string,\n manifest: SmartObjectManifest,\n ): string {\n if (manifest.objects[className]) return className;\n const simpleName = className.includes(':')\n ? className.slice(className.lastIndexOf(':') + 1)\n : className;\n for (const [key, obj] of Object.entries(manifest.objects)) {\n if (obj.qualifiedName === className || obj.className === simpleName) {\n return key;\n }\n }\n return className;\n }\n\n /**\n * Find all descendants of a class in the manifest\n *\n * Note: Manifest keys are now qualified names (@pkg:ClassName) but `extends` field\n * stores simple PascalCase class names. We need to handle both qualified and simple\n * name lookups.\n *\n * Issue #713: Updated to handle qualified names in manifest keys\n */\n private findDescendantsInManifest(\n baseClassName: string,\n manifest: SmartObjectManifest,\n visited: Set<string> = new Set(),\n ): string[] {\n const descendants: string[] = [];\n\n // Prevent infinite recursion (e.g., class extends same-named class from another package)\n if (visited.has(baseClassName)) return descendants;\n visited.add(baseClassName);\n\n // Extract the simple class name from qualified name if needed\n // e.g., '@happyvertical/smrt-core:PolyEvent' -> 'PolyEvent'\n const simpleBaseClassName = baseClassName.includes(':')\n ? baseClassName.split(':').pop() || baseClassName\n : baseClassName;\n const baseClassLower = simpleBaseClassName.toLowerCase();\n\n for (const [name, obj] of Object.entries(manifest.objects)) {\n // Skip self-references (class extending same-named class from another package)\n if (\n obj.className.toLowerCase() === baseClassLower &&\n obj.extends?.toLowerCase() === baseClassLower\n ) {\n continue;\n }\n // obj.extends is a simple class name (e.g., 'PolyEvent')\n // Compare with the simple (non-qualified) version of the base class name\n if (obj.extends?.toLowerCase() === baseClassLower) {\n descendants.push(name);\n // Recursively find descendants of this class\n // Pass the qualified name (manifest key) for recursive lookup\n descendants.push(\n ...this.findDescendantsInManifest(name, manifest, visited),\n );\n }\n }\n\n return descendants;\n }\n\n /**\n * Convert ManifestColumnDefinition to ColumnDefinition\n */\n private convertManifestColumnsToSchemaColumns(\n manifestColumns: Record<string, ManifestColumnDefinition>,\n ): Record<string, ColumnDefinition> {\n const columns: Record<string, ColumnDefinition> = {};\n\n for (const [name, col] of Object.entries(manifestColumns)) {\n columns[name] = {\n type: col.type as SQLDataType,\n primaryKey: col.primaryKey,\n referenceKind: col.referenceKind,\n notNull: col.notNull,\n unique: col.unique,\n defaultValue: col.default,\n };\n }\n\n return columns;\n }\n\n /**\n * Convert camelCase to snake_case\n */\n private toSnakeCase(str: string): string {\n return str\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/^_/, '');\n }\n\n /**\n * Generate the CREATE TABLE statement for a schema definition.\n *\n * With an `engine` this delegates to that engine's DDL strategy. Without\n * one it renders the engine-neutral preview stored in `schema.ddl` and\n * `manifest.json`: abstract SQL types, no indexes, no triggers. That\n * preview is NOT an executable representation of the table (#2358) —\n * executable paths (`db:migrate`, `MigrationGenerator`, `SchemaAggregator`,\n * `createIsolatedTestDbFromManifest`) render `schema.columns` and\n * `schema.indexes` through `getDDLStrategy(engine)` instead.\n *\n * @param schema - Schema definition object\n * @param engine - Optional target engine; omit for the neutral preview\n * @returns SQL CREATE TABLE statement (no indexes)\n */\n generateSQL(schema: SchemaDefinition, engine?: DatabaseEngine): string {\n if (engine) {\n return getDDLStrategy(engine).generateCreateTable(schema);\n }\n\n const { tableName, columns } = schema;\n let sql = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\\n`;\n\n for (const [columnName, columnDef] of Object.entries(columns)) {\n const parts = [` ${quoteIdentifier(columnName)} ${columnDef.type}`];\n\n if (columnDef.primaryKey) {\n parts.push('PRIMARY KEY');\n }\n\n if (columnDef.notNull) {\n parts.push('NOT NULL');\n }\n\n if (columnDef.unique && !columnDef.primaryKey) {\n parts.push('UNIQUE');\n }\n\n if (columnDef.defaultValue !== undefined) {\n parts.push(\n `DEFAULT ${this.formatDefaultValue(\n columnDef.defaultValue,\n columnDef.type,\n )}`,\n );\n }\n\n sql += `${parts.join(' ')},\\n`;\n }\n\n return `${sql.slice(0, -2)}\\n);`;\n }\n\n /**\n * Format default value for SQL\n *\n * SQLite doesn't support CAST expressions in DEFAULT values (only literal values are allowed).\n * This method generates DEFAULT values that work with both SQLite and DuckDB.\n *\n * @param value - Default value\n * @param type - Column SQL type\n * @returns Formatted SQL default value expression\n */\n private formatDefaultValue(value: unknown, type: SQLDataType): string {\n // Delegate to the shared, injection-safe formatter so all DDL paths\n // converge on one set of rules (allowlisted keyword/function defaults\n // instead of \"contains `(`\", type-driven quoting, no string-\"null\" fold).\n // This path is engine-agnostic and never emits CAST expressions, so the\n // output stays valid for SQLite and DuckDB. Non-string TIMESTAMP defaults\n // fall back to lowercase `current_timestamp` to preserve prior output.\n return formatDefaultValueShared(value, type, {\n nonStringTimestampDefault: 'current_timestamp',\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../src/schema/generator.ts"],"sourcesContent":["/**\n * Schema generator for SMRT objects\n * Converts AST field definitions to database schema definitions\n */\n\nimport { createHash } from 'node:crypto';\nimport type {\n FieldDefinition,\n FieldMeta,\n ManifestColumnDefinition,\n ManifestIndexDefinition,\n ManifestSchema,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { classnameToTablename } from '../utils/naming.js';\nimport {\n type ConflictTableStrategy,\n conflictIndexName,\n resolveConflictColumns,\n resolveTenantColumn,\n servesSlugLookup,\n} from './conflict-target.js';\nimport { getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport {\n assertIdentifierFits,\n enforceIdentifierLimits,\n} from './index-utils.js';\nimport {\n formatDefaultValue as formatDefaultValueShared,\n quoteIdentifier,\n quoteStringLiteral,\n} from './sql-identifiers.js';\nimport type {\n ColumnDefinition,\n DeclaredIndexDefinition,\n ForeignKeyDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n} from './types.js';\n\n/**\n * Structural shape of a field as read from either the build-time manifest\n * (`FieldDefinition`) or the runtime ObjectRegistry (`getAllFields()` returns\n * `Map<string, any>` upstream). The schema generator consumes both through a\n * single set of property reads, so this interface captures every field key the\n * generator touches without coupling to either source's exact type.\n *\n * `_meta` carries the structured field-helper metadata (`FieldMeta`); a few\n * legacy fields (`indexed`, `idType`, `__tenancy`) may also appear at the top\n * level depending on the source, so they are declared here too.\n */\ninterface RegistryField {\n type?: FieldDefinition['type'];\n related?: string;\n required?: boolean;\n default?: unknown;\n description?: string;\n transient?: boolean;\n indexed?: boolean;\n idType?: 'uuid' | 'text';\n __tenancy?: FieldMeta['__tenancy'];\n _meta?: FieldMeta;\n}\n\ntype SchemaGeneratorConfig = {\n /**\n * The table's resolved conflict target. Registry callers pass\n * `ObjectRegistry.getConflictColumns()`; the manifest path passes the\n * object's `decoratorConfig` after `ManifestGenerator.normalizeConflictColumns()`\n * materialized the tenant-aware default (#2360). When absent, the generator\n * derives the default itself from `tenantScoped` and the strategy.\n */\n conflictColumns?: string[];\n /**\n * The schema owner's `@smrt({ tenantScoped })` config as the manifest\n * carries it — consulted only to derive the default conflict target when\n * `conflictColumns` is absent.\n */\n tenantScoped?: boolean | { field?: string };\n idType?: 'uuid' | 'text';\n /**\n * `@smrt({ indexes: [...] })` declarations (#2357).\n *\n * The manifest paths receive the whole `decoratorConfig`, so this arrives for\n * free there. The runtime paths rebuild a narrow config bag by hand\n * (`schema/utils.ts`, `testing/database.ts`) and must thread it explicitly —\n * omitting it is what made the option unreachable at runtime.\n */\n indexes?: DeclaredIndexDefinition[];\n registry?: {\n getConfig?(className: string): { idType?: 'uuid' | 'text' };\n getDescendants(baseClassName: string): string[];\n getAllFields(className: string): Promise<Map<string, RegistryField>>;\n getSTIBase?(className: string): string | null;\n };\n};\n\nexport class SchemaGenerator {\n /**\n * Convert field type to SQL data type\n */\n private mapFieldTypeToSQL(fieldType: FieldDefinition['type']): SQLDataType {\n switch (fieldType) {\n case 'text':\n return 'TEXT';\n case 'integer':\n return 'INTEGER';\n case 'decimal':\n return 'REAL';\n case 'boolean':\n return 'BOOLEAN';\n case 'datetime':\n return 'TIMESTAMP';\n case 'json':\n return 'JSON';\n case 'foreignKey':\n return 'UUID'; // Foreign keys default to UUID, then same-package refs can be reconciled to target idType\n case 'crossPackageRef':\n return 'UUID'; // Cross-package refs are UUID ids with no DDL FK constraint\n default:\n return 'TEXT'; // Default fallback\n }\n }\n\n private getIdColumnType(config?: { idType?: 'uuid' | 'text' }): SQLDataType {\n return config?.idType === 'text' ? 'TEXT' : 'UUID';\n }\n\n private getRelationshipColumnType(field: RegistryField): SQLDataType {\n if (field?._meta?.sqlType) {\n return String(field._meta.sqlType).toUpperCase() as SQLDataType;\n }\n\n if (\n field?.type === 'crossPackageRef' &&\n (field._meta?.idType === 'text' || field.idType === 'text')\n ) {\n return 'TEXT';\n }\n\n return this.mapFieldTypeToSQL(field?.type || 'text');\n }\n\n private getReferenceKind(\n field: RegistryField,\n ): ColumnDefinition['referenceKind'] | undefined {\n if (\n field?.__tenancy?.isTenantIdField ||\n field?._meta?.__tenancy?.isTenantIdField\n ) {\n return 'tenantId';\n }\n\n if (field?.type === 'foreignKey') {\n return 'foreignKey';\n }\n\n if (field?.type === 'crossPackageRef') {\n return 'crossPackageRef';\n }\n\n return undefined;\n }\n\n private shouldEmitDefault(\n field: RegistryField,\n defaultValue: unknown,\n ): boolean {\n return !(\n this.getReferenceKind(field) === 'tenantId' &&\n this.getRelationshipColumnType(field) === 'UUID' &&\n defaultValue === ''\n );\n }\n\n private getRegistryTargetIdColumnType(\n relatedName: string | undefined,\n registry: SchemaGeneratorConfig['registry'] | undefined,\n ): SQLDataType | undefined {\n if (!relatedName || !registry?.getConfig) {\n return undefined;\n }\n\n const targetName = relatedName.split('.')[0];\n const targetNames = [targetName];\n const stiBase = registry.getSTIBase?.(targetName);\n if (stiBase && !targetNames.includes(stiBase)) {\n targetNames.push(stiBase);\n }\n\n for (const name of targetNames) {\n const idType = registry.getConfig(name)?.idType;\n if (idType === 'text') {\n return 'TEXT';\n }\n if (idType === 'uuid') {\n return 'UUID';\n }\n }\n\n return undefined;\n }\n\n private reconcileRegistryForeignKeyColumnTypes(\n columns: Record<string, ColumnDefinition>,\n fields: Map<string, RegistryField>,\n registry: SchemaGeneratorConfig['registry'] | undefined,\n ): void {\n if (!registry?.getConfig) {\n return;\n }\n\n for (const [fieldName, field] of fields.entries()) {\n if (field.type !== 'foreignKey' || field._meta?.sqlType) {\n continue;\n }\n\n const targetIdType = this.getRegistryTargetIdColumnType(\n field.related,\n registry,\n );\n if (!targetIdType) {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n if (columns[columnName]) {\n columns[columnName] = {\n ...columns[columnName],\n type: targetIdType,\n };\n }\n }\n }\n\n /**\n * Whether an existing index already leads with `leadingColumns`, in order.\n *\n * Only an UNQUALIFIED index (no partial `WHERE`, no JSON-path expression)\n * counts. A partial index such as `... WHERE _meta_type = 'Article'` cannot\n * serve a base-class polymorphic query, which carries no subtype predicate,\n * so it must not suppress the standalone index (#2359, review of #2384).\n *\n * A B-tree serves any prefix of its column list, so an index over\n * `(tenant_id, created_at, status)` covers both `(tenant_id)` equality\n * lookups and `(tenant_id, created_at)` ordering — hence the prefix test\n * rather than an exact match (#2363).\n */\n private hasUnqualifiedLeadingColumns(\n indexes: ReadonlyArray<{\n columns: string[];\n where?: string;\n jsonPath?: unknown;\n }>,\n leadingColumns: readonly string[],\n ): boolean {\n return indexes.some(\n (index) =>\n !index.where &&\n !index.jsonPath &&\n (index.columns?.length ?? 0) >= leadingColumns.length &&\n leadingColumns.every((column, i) => index.columns[i] === column),\n );\n }\n\n /**\n * Whether an existing index already serves equality lookups on `column`.\n *\n * Single-column shorthand for {@link hasUnqualifiedLeadingColumns}.\n */\n private hasUnqualifiedLeadingIndex(\n indexes: ReadonlyArray<{\n columns: string[];\n where?: string;\n jsonPath?: unknown;\n }>,\n column: string,\n ): boolean {\n return this.hasUnqualifiedLeadingColumns(indexes, [column]);\n }\n\n /**\n * The column list of the default list-ordering index for a table.\n *\n * Every generated list surface — REST, MCP, and the SvelteKit list route —\n * pages with `ORDER BY created_at DESC, <pk> ASC` (`DEFAULT_LIST_ORDER_BY`,\n * #2367). On a tenant-scoped table that page is always preceded by a\n * `tenant_id = ?` equality filter from the tenancy interceptor, so the\n * serving index leads with the tenant column and orders inside it.\n *\n * `created_at` is generator-owned on every path — a declared `createdAt`\n * field is rewritten to the same `TIMESTAMP NOT NULL DEFAULT\n * current_timestamp` column — so it is never a primary key and never\n * inline-UNIQUE. That is why only `indexes` needs checking for existing\n * coverage: no constraint-backed index can already order this column.\n *\n * Returns `null` when the table has no `created_at` column (no path emits\n * such a table today, but the helper stays total).\n */\n private getDefaultListOrderingColumns(\n columns: Record<string, { referenceKind?: string } | undefined>,\n ): string[] | null {\n if (!columns.created_at) return null;\n\n // The tenant column name is configurable (`@smrt({ tenantScoped: { field } })`),\n // so find it by reference kind rather than by the `tenant_id` spelling.\n const tenantColumn = Object.entries(columns).find(\n ([, columnDef]) => columnDef?.referenceKind === 'tenantId',\n )?.[0];\n\n return tenantColumn ? [tenantColumn, 'created_at'] : ['created_at'];\n }\n\n /**\n * Ensure the table can serve its own default list page from an index.\n *\n * Generated REST/MCP/SvelteKit list routes all page with\n * `ORDER BY created_at DESC, <pk> ASC LIMIT n` and no `created_at` index\n * existed on any schema path (the dead AST path indexed `updated_at`\n * instead), so every default list page was a sequential scan plus a top-N\n * sort of the whole table — 21 ms against 0.1 ms with the right composite on\n * the workload the assessment measured (#2363, finding A2).\n *\n * Emitted shape:\n *\n * - tenant-scoped table → `(<tenant column>, created_at)`. The tenancy\n * interceptor adds `tenant_id = ?` to every list, so the tenant column\n * leads and `created_at` orders within it.\n * - otherwise → `(created_at)`.\n *\n * No `DESC` declaration: PostgreSQL scans a B-tree backwards just as\n * cheaply, and `IndexDefinition` carries no per-column direction. The\n * trailing primary-key tiebreak is left out deliberately — its direction is\n * opposite to `created_at`'s, so no single-direction index can satisfy the\n * whole key anyway; the leading columns turn a full sort into an index scan\n * with an incremental sort over rows that share a timestamp.\n *\n * Call this AFTER {@link appendDeclaredIndexes} and BEFORE\n * {@link ensureReferenceColumnIndexes}: a declared composite that already\n * leads with the same columns suppresses this one, and this composite in\n * turn suppresses the standalone tenant index that would otherwise be a\n * redundant prefix of it.\n */\n private ensureDefaultListOrderingIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, { referenceKind?: string } | undefined>,\n tableName: string,\n ): void {\n const orderingColumns = this.getDefaultListOrderingColumns(columns);\n if (!orderingColumns) return;\n if (this.hasUnqualifiedLeadingColumns(indexes, orderingColumns)) return;\n\n const name = `${tableName}_${orderingColumns.join('_')}_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: orderingColumns });\n }\n\n /**\n * The `(meta_type, meta_id)` owner-lookup columns for a polymorphic\n * association table, or `null` when the table isn't one.\n *\n * `SmrtPolymorphicAssociation` contributes `metaType`/`metaId`/`role` (and\n * `sortOrder`) to any concrete subclass's manifest — the scanner's\n * `FRAMEWORK_ABSTRACT_BASE_NAMES` wiring merges the abstract base's fields\n * directly into the subclass, because the base carries no `@smrt()`\n * decorator of its own to declare an index on. Detected structurally, by\n * the same three-column test `cascade.ts`'s `isPolymorphicAssociationClass`\n * uses on registry field maps: requiring all three keeps an unrelated class\n * that merely happens to carry a `metaType` column from being treated as an\n * association table.\n */\n private getPolymorphicAssociationOwnerColumns(\n columns: Record<string, unknown>,\n ): string[] | null {\n if (\n !Object.hasOwn(columns, 'meta_type') ||\n !Object.hasOwn(columns, 'meta_id') ||\n !Object.hasOwn(columns, 'role')\n ) {\n return null;\n }\n return ['meta_type', 'meta_id'];\n }\n\n /**\n * Ensure a polymorphic association table indexes its owner lookup\n * `(meta_type, meta_id)` (#2364, epic #2382 finding A3).\n *\n * A concrete association's `conflictColumns` lead with its own FK (e.g.\n * `AssetAssociation`'s `asset_id, meta_type, meta_id, role`), so\n * `meta_type`/`meta_id` sit in the *middle* of that unique index — a\n * lookup like `AssetAssociationCollection.byLeft(metaType, metaId)`\n * (\"what points at this target\") filters columns 2-3 of a 4-column index\n * and cannot use it as a leading prefix. `metaId` is deliberately a bare\n * string, never a typed `@foreignKey`/`@crossPackageRef` (there is no\n * single target table), so {@link ensureReferenceColumnIndexes} never\n * covers it either.\n *\n * Call this alongside {@link ensureDefaultListOrderingIndex}, before\n * {@link ensureReferenceColumnIndexes}.\n *\n * Blast radius: this applies retroactively to every\n * `SmrtPolymorphicAssociation` subclass across every package on the next\n * `db:migrate`, not only the ones a given change touches — roll the wave\n * out with `smrt db:migrate --postgres-safe` (#2362), same as any other\n * bulk index addition.\n */\n private ensurePolymorphicAssociationIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, unknown>,\n tableName: string,\n ): void {\n const ownerColumns = this.getPolymorphicAssociationOwnerColumns(columns);\n if (!ownerColumns) return;\n if (this.hasUnqualifiedLeadingColumns(indexes, ownerColumns)) return;\n\n const name = `${tableName}_${ownerColumns.join('_')}_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: ownerColumns });\n }\n\n /**\n * Append `@smrt({ indexes: [...] })` declarations to a generated index set\n * (#2357).\n *\n * Shared by every schema path — build-time AST, runtime registry CTI/STI, and\n * manifest CTI/STI — so one declaration behaves identically however the schema\n * was derived. Call it *first*, ahead of\n * {@link ensureDefaultListOrderingIndex} (#2363) and\n * {@link ensureReferenceColumnIndexes}: a declared composite leading with a\n * reference column (e.g. `tenant_id`), or with the default ordering columns,\n * is exactly what those helpers' leads-with check is meant to defer to, and\n * appending afterwards would leave the table with a redundant standalone\n * index (#2384, #2359).\n *\n * Nothing is dropped quietly. A column that resolves to no column on the\n * table, a malformed entry, and a name collision with a different index are\n * all hard errors — a silently missing index is a production performance bug\n * that surfaces only under load.\n *\n * @param indexes - Generated index set, appended in place\n * @param declared - The object's `indexes` config, from any config source\n * @param columns - Emitted columns, keyed by column name\n * @param tableName - Table the indexes belong to (for error messages)\n */\n private appendDeclaredIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n }>,\n declared: unknown,\n columns: Record<string, unknown>,\n tableName: string,\n ): void {\n if (declared === undefined || declared === null) return;\n if (!Array.isArray(declared)) {\n throw new Error(\n `@smrt({ indexes }) on \"${tableName}\" must be an array of index declarations.`,\n );\n }\n\n for (const entry of declared) {\n const spec = this.validateDeclaredIndex(entry, tableName);\n\n const mappedColumns = spec.columns.map((column) =>\n this.resolveDeclaredIndexColumn(column, columns),\n );\n // `Object.hasOwn`, not `in`: the column map is a plain object, so `in`\n // also answers true for `toString`, `constructor` and every other\n // `Object.prototype` key — a declaration naming one would sail past this\n // check and emit an index on a column that does not exist.\n const unknownColumns = mappedColumns.filter(\n (column) => !Object.hasOwn(columns, column),\n );\n if (unknownColumns.length > 0) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" references unknown column(s): ` +\n `${unknownColumns.join(', ')}. Declared index columns must name a field or ` +\n `column on the object.`,\n );\n }\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n const index = {\n name: spec.name,\n columns: mappedColumns,\n ...(spec.unique === true ? { unique: true } : {}),\n ...(spec.where !== undefined ? { where: spec.where } : {}),\n };\n\n const existing = indexes.find((other) => other.name === index.name);\n if (existing) {\n // An identical re-declaration is harmless (the same object can be\n // generated through more than one path). A *different* index under a\n // name already taken is not: one of the two would vanish.\n if (this.sameIndexTarget(existing, index)) continue;\n throw new Error(\n `Declared index \"${index.name}\" on \"${tableName}\" collides with a different index ` +\n `of the same name. Rename the declared index.`,\n );\n }\n\n indexes.push(index);\n }\n }\n\n /** Narrow one raw `indexes[]` entry, rejecting anything unusable. */\n private validateDeclaredIndex(\n entry: unknown,\n tableName: string,\n ): DeclaredIndexDefinition {\n const spec = entry as Partial<DeclaredIndexDefinition> | null | undefined;\n const describe = () => {\n try {\n return JSON.stringify(entry) ?? String(entry);\n } catch {\n return String(entry);\n }\n };\n\n if (typeof spec !== 'object' || spec === null || Array.isArray(spec)) {\n throw new Error(\n `Declared index on \"${tableName}\" must be an object: got ${describe()}.`,\n );\n }\n if (typeof spec.name !== 'string' || spec.name.length === 0) {\n throw new Error(\n `Declared index on \"${tableName}\" needs a non-empty \"name\": got ${describe()}.`,\n );\n }\n // A declared name is the developer's, not the generator's: shortening it\n // would silently rename what they wrote, so an over-long one is refused\n // here — at manifest generation, which `smrt dev:knowledge-check` runs —\n // rather than truncated by PostgreSQL at deploy time (#2374).\n assertIdentifierFits(spec.name, 'Declared index', `table \"${tableName}\"`);\n if (\n !Array.isArray(spec.columns) ||\n spec.columns.length === 0 ||\n !spec.columns.every(\n (column) => typeof column === 'string' && column.length > 0,\n )\n ) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" needs a non-empty \"columns\" array of field or column names.`,\n );\n }\n if (spec.unique !== undefined && typeof spec.unique !== 'boolean') {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" has a non-boolean \"unique\": got ${describe()}.`,\n );\n }\n // An empty predicate would render `CREATE INDEX ... WHERE ;`, and treating\n // it as \"no predicate\" would silently widen a partial index to a full one.\n if (\n spec.where !== undefined &&\n (typeof spec.where !== 'string' || spec.where.trim().length === 0)\n ) {\n throw new Error(\n `Declared index \"${spec.name}\" on \"${tableName}\" has an empty or non-string \"where\" predicate.`,\n );\n }\n\n return spec as DeclaredIndexDefinition;\n }\n\n /**\n * Resolve a declared index column to an emitted column name.\n *\n * Declarations name SMRT fields, but only the runtime/manifest paths\n * snake_case them into columns — the build-time AST path keys columns by the\n * field name itself. Prefer an exact column hit, then the snake_case form, and\n * otherwise return the input unchanged so the caller reports it as unknown.\n */\n private resolveDeclaredIndexColumn(\n name: string,\n columns: Record<string, unknown>,\n ): string {\n // `Object.hasOwn` rather than `in` — see appendDeclaredIndexes: `in` would\n // resolve `toString` to itself and let the caller's unknown-column check\n // pass on a column that does not exist.\n if (Object.hasOwn(columns, name)) return name;\n const snakeCased = this.toSnakeCase(name);\n return Object.hasOwn(columns, snakeCased) ? snakeCased : name;\n }\n\n /** Whether two index definitions describe the same database object. */\n private sameIndexTarget(\n a: { columns: string[]; unique?: boolean; where?: string },\n b: { columns: string[]; unique?: boolean; where?: string },\n ): boolean {\n return (\n a.columns.length === b.columns.length &&\n a.columns.every((column, i) => column === b.columns[i]) &&\n (a.unique ?? false) === (b.unique ?? false) &&\n (a.where ?? null) === (b.where ?? null)\n );\n }\n\n /**\n * Ensure every reference column — `@foreignKey`, `@crossPackageRef`, and the\n * tenancy-injected `tenant_id` — has an index leading with it.\n *\n * These are the columns every relationship load, `include:` batch, reverse\n * ownership lookup and tenant-scoped read filters on. Before #2359 only the\n * registry (test) paths indexed foreign keys, the manifest (production)\n * paths indexed none of them, and `tenant_id` was indexed nowhere (#2356):\n * 196/231 `@foreignKey` and 91/92 `@crossPackageRef` columns shipped with no\n * serving index. Every schema path now calls this helper so the four paths\n * stay in step (see `schema-path-parity.test.ts`).\n *\n * \"Leading with it\" rather than \"always add\": a table that already has an\n * unqualified index starting on the column (commonly the `conflictColumns`\n * unique index, or an `indexed: true` opt-in) is already served, and a\n * standalone duplicate would only cost writes. A *partial* index does not\n * count — see {@link hasUnqualifiedLeadingIndex}.\n *\n * Reference columns are therefore always indexed; `indexed: true` on one of\n * them is redundant and harmless.\n *\n * Call this LAST in every path, after every other index (conflict, opt-in,\n * unique, the declared composites, the default list-ordering composite, and\n * any future addition) has been appended, so the leads-with suppression sees\n * the full set. In particular {@link ensureDefaultListOrderingIndex} runs\n * first on a tenant-scoped table: its `(tenant_id, created_at)` composite\n * serves the tenant equality filter too, so no standalone tenant index is\n * added (#2363).\n */\n private ensureReferenceColumnIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<\n string,\n | { referenceKind?: string; primaryKey?: boolean; unique?: boolean }\n | undefined\n >,\n tableName: string,\n ): void {\n for (const [columnName, columnDef] of Object.entries(columns)) {\n const kind = columnDef?.referenceKind;\n if (\n kind !== 'foreignKey' &&\n kind !== 'crossPackageRef' &&\n kind !== 'tenantId'\n ) {\n continue;\n }\n // The primary key, and a column-level UNIQUE (rendered inline on CTI\n // tables — PostgreSQL's implicit `_key` index), are already unique\n // indexes on every engine.\n if (columnDef?.primaryKey || columnDef?.unique) continue;\n if (this.hasUnqualifiedLeadingIndex(indexes, columnName)) continue;\n\n const name = `${tableName}_${columnName}_idx`;\n if (indexes.some((index) => index.name === name)) continue;\n\n // No `description`: ManifestIndexDefinition has no such field, and this\n // helper feeds the manifest paths as well as the structured ones.\n indexes.push({ name, columns: [columnName] });\n }\n }\n\n /**\n * Whether `conflictColumns` names exactly the primary key column(s).\n *\n * `ON CONFLICT (id)` binds to the primary-key constraint on every engine, so\n * a separate unique index over the same column set is a second B-tree over\n * the same random UUIDs with nothing to add (#2359, finding A5).\n */\n private conflictColumnsArePrimaryKey(\n conflictColumns: string[],\n columns: Record<string, { primaryKey?: boolean } | undefined>,\n ): boolean {\n const primaryKeyColumns = Object.entries(columns)\n .filter(([, columnDef]) => columnDef?.primaryKey === true)\n .map(([name]) => name);\n return (\n primaryKeyColumns.length > 0 &&\n primaryKeyColumns.length === conflictColumns.length &&\n primaryKeyColumns.every((column) => conflictColumns.includes(column))\n );\n }\n\n /**\n * The table's tenant column as the generated columns describe it: the\n * column carrying `referenceKind: 'tenantId'` (set from the field's\n * `__tenancy.isTenantIdField` marker on every path).\n */\n private findTenantColumn(\n columns: Record<string, { referenceKind?: string } | undefined>,\n ): string | undefined {\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef?.referenceKind === 'tenantId') return columnName;\n }\n return undefined;\n }\n\n /**\n * Resolve the conflict target the unique conflict index must cover\n * (#2360). `config.conflictColumns` wins when the caller resolved it —\n * registry callers pass `ObjectRegistry.getConflictColumns()`, the manifest\n * pipeline passes the normalized `decoratorConfig` — otherwise the strategy\n * default, led by the tenant column when `config.tenantScoped` names a\n * tenant field that exists as a column. Both roads apply the same rule as\n * `ObjectRegistry.getConflictColumns()`, so schema and upsert agree by\n * construction (`schema-path-parity.test.ts`).\n */\n private resolveConflictTarget(\n strategy: ConflictTableStrategy,\n columns: Record<string, { referenceKind?: string } | undefined>,\n config: SchemaGeneratorConfig | undefined,\n ): { conflictColumns: string[]; tenantColumn: string | undefined } {\n const tenantColumn = this.findTenantColumn(columns);\n if (config?.conflictColumns && config.conflictColumns.length > 0) {\n return { conflictColumns: [...config.conflictColumns], tenantColumn };\n }\n const tenantScoped = config?.tenantScoped;\n const configuredTenantColumn = tenantScoped\n ? resolveTenantColumn(\n typeof tenantScoped === 'object'\n ? (tenantScoped.field ?? 'tenantId')\n : 'tenantId',\n (fieldName) => Boolean(columns[this.toSnakeCase(fieldName)]),\n (fieldName) => this.toSnakeCase(fieldName),\n )\n : undefined;\n return {\n conflictColumns: resolveConflictColumns({\n strategy,\n tenantColumn: configuredTenantColumn,\n }),\n tenantColumn,\n };\n }\n\n /**\n * Keep `(slug, context)` lookups served when `conflictColumns` are custom.\n *\n * The default conflict index is `(slug, context)`, and `loadFromSlug()`,\n * `getId()` and `getSavedId()` all filter on `slug`/`context` regardless of\n * the configured conflict key. A class that declares custom conflict\n * columns replaces that unique index and, before #2359, left those lookups\n * with no index at all — 120 tables (finding A7).\n *\n * Emitting a plain `(slug, context)` index is the safer of the two fixes:\n * routing the lookups through the conflict key would change which row a\n * slug resolves to on every such class, whereas an extra non-unique index\n * is purely additive. It is skipped when an unqualified index already\n * serves the lookup — one leading with `slug` (e.g. custom conflict columns\n * that still start with it), or the tenant-led default key\n * `(tenant_id, slug, context)` of a tenant-scoped table (#2360): every slug\n * lookup on such a table carries the tenant predicate (#2365) and is served\n * by that prefix, so a second index would only cost writes.\n */\n private ensureSlugLookupIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: unknown;\n }>,\n columns: Record<string, { referenceKind?: string } | undefined>,\n tableName: string,\n ): void {\n if (!columns.slug || !columns.context) return;\n const tenantColumn = this.findTenantColumn(columns);\n if (\n indexes.some(\n (index) =>\n !index.where &&\n !index.jsonPath &&\n servesSlugLookup(index.columns ?? [], tenantColumn),\n )\n ) {\n return;\n }\n\n const name = `${tableName}_slug_context_idx`;\n if (indexes.some((index) => index.name === name)) return;\n\n indexes.push({ name, columns: ['slug', 'context'] });\n }\n\n /**\n * Emit the unique conflict index of an STI table from the resolved\n * conflict target (#2360): the STI default `(slug, context, _meta_type)`,\n * a custom `@smrt({ conflictColumns })` declared on the STI root, or the\n * default led by the tenant column when the root is tenant-scoped. Shared\n * by the registry and manifest STI paths so they cannot drift.\n *\n * No index is emitted when the target is the primary key itself\n * (`ON CONFLICT (id)` binds to the PK constraint), matching the CTI paths.\n */\n private emitStiConflictIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n description?: string;\n }>,\n columns: Record<\n string,\n { referenceKind?: string; primaryKey?: boolean } | undefined\n >,\n tableName: string,\n config: SchemaGeneratorConfig | undefined,\n ): void {\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'sti',\n columns,\n config,\n );\n if (this.conflictColumnsArePrimaryKey(conflictColumns, columns)) return;\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n });\n }\n\n /**\n * Emit unique indexes for `@field({ unique: true })` columns on an STI table.\n *\n * STI columns are the union of every class in the hierarchy, all nullable,\n * so a column-level `UNIQUE` (what CTI renders inline) is not enough on its\n * own: the migration differ cannot add a column constraint to an existing\n * table, but it can add an index. Two shapes (#2359, finding A4):\n *\n * - declared on the STI **base** (and therefore inherited by every class in\n * the table): one full unique index, table-wide like CTI;\n * - declared only on a **descendant**: one partial unique index per class\n * that carries the flag (the declaring class and, through inherited field\n * metadata, its own descendants), `WHERE _meta_type = '<qualified>'`, so\n * siblings that merely share the column name are not constrained.\n * Uniqueness is then enforced per concrete class, not across the subtree\n * — a documented limitation of the discriminator-partial shape. Engines\n * without partial indexes (DuckDB, JSON) skip this shape entirely rather\n * than widen it to a table-wide UNIQUE (see DuckDBStrategy.generateIndexes\n * and SchemaComparer.compareIndexes).\n *\n * @param declarers - column name → classes whose field metadata carries\n * `unique: true` for that column, in hierarchy order (base first).\n */\n private emitStiUniqueIndexes(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n }>,\n declarers: Map<string, Set<string>>,\n baseClassName: string,\n tableName: string,\n ): void {\n for (const [columnName, classNames] of declarers.entries()) {\n if (classNames.has(baseClassName)) {\n indexes.push({\n name: `${tableName}_${columnName}_unique_idx`,\n columns: [columnName],\n unique: true,\n });\n continue;\n }\n for (const className of classNames) {\n const simpleName = className.includes(':')\n ? className.slice(className.lastIndexOf(':') + 1)\n : className;\n let name = `${tableName}_${columnName}_${this.toSnakeCase(simpleName)}_unique_idx`;\n // Two classes of one hierarchy may share a simple name across\n // packages (`@a:Event`, `@b:Event`); the merged index list dedupes by\n // name, so disambiguate with a short digest of the qualified name.\n if (indexes.some((index) => index.name === name)) {\n const digest = createHash('sha256')\n .update(className)\n .digest('hex')\n .slice(0, 6);\n name = `${tableName}_${columnName}_${this.toSnakeCase(simpleName)}_${digest}_unique_idx`;\n }\n indexes.push({\n name,\n columns: [columnName],\n unique: true,\n where: `_meta_type = ${quoteStringLiteral(className)}`,\n });\n }\n }\n }\n\n /**\n * Extract foreign key definitions\n */\n private extractForeignKeys(\n columns: Record<string, ColumnDefinition>,\n ): ForeignKeyDefinition[] {\n const foreignKeys: ForeignKeyDefinition[] = [];\n\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef.foreignKey) {\n foreignKeys.push({\n column: columnName,\n referencesTable: columnDef.foreignKey.table,\n referencesColumn: columnDef.foreignKey.column,\n onDelete: columnDef.foreignKey.onDelete,\n onUpdate: columnDef.foreignKey.onUpdate,\n });\n }\n }\n\n return foreignKeys;\n }\n\n /**\n * Convert class name to table name (camelCase to snake_case, pluralized)\n */\n private classNameToTableName(className: string): string {\n return classnameToTablename(className);\n }\n\n /**\n * Generate schema definition from ObjectRegistry fields (runtime)\n *\n * This method builds a SchemaDefinition from ObjectRegistry cached fields,\n * enabling schema generation from decorated classes at runtime.\n *\n * @param className - Class name to look up in ObjectRegistry\n * @param tableName - Table name (from SMRT_TABLE_NAME or derived)\n * @param fields - Map of Field definitions from ObjectRegistry\n * @returns Schema definition object\n */\n generateSchemaFromRegistry(\n className: string,\n tableName: string,\n fields: Map<string, RegistryField>,\n config?: SchemaGeneratorConfig,\n ): SchemaDefinition {\n const columns: Record<string, ColumnDefinition> = {};\n\n // Check for custom primary key\n let hasCustomPK = false;\n for (const [_fieldName, field] of fields.entries()) {\n if (field._meta?.primaryKey) {\n hasCustomPK = true;\n break;\n }\n }\n\n // Add default SMRT fields if no custom primary key\n if (!hasCustomPK) {\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n description: 'Primary identifier',\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n description: 'URL-friendly identifier',\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n defaultValue: '',\n description: 'Context for slug scoping',\n };\n }\n\n // Track timestamp fields to avoid duplicates\n let hasCreatedAt = false;\n let hasUpdatedAt = false;\n // Track regular (column-backed) fields that opted into a plain column index\n // via `indexed: true`. FK, unique, and meta fields each go through their\n // own dedicated index emission paths and are excluded from this set.\n const indexedColumns = new Set<string>();\n\n // Add fields from ObjectRegistry\n for (const [fieldName, field] of fields.entries()) {\n // Skip transient fields (non-persisted)\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n const isDefaultSmrtColumn =\n fieldName === 'id' || fieldName === 'slug' || fieldName === 'context';\n\n // Default SMRT columns are added above when we use the standard primary\n // key path. When a class declares a custom primary key, only explicitly\n // declared id/slug/context fields should survive this loop.\n if (\n isDefaultSmrtColumn &&\n (!hasCustomPK || field._meta?.__smrtSystemField === true)\n ) {\n continue;\n }\n\n // Track timestamp fields\n if (fieldName === 'created_at' || fieldName === 'createdAt') {\n if (hasCreatedAt) continue;\n hasCreatedAt = true;\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n continue;\n }\n if (fieldName === 'updated_at' || fieldName === 'updatedAt') {\n if (hasUpdatedAt) continue;\n hasUpdatedAt = true;\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n continue;\n }\n\n // Skip relationship fields that don't create columns\n // oneToMany and manyToMany are relationship metadata, not actual database columns\n // foreignKey DOES create a column (it stores the foreign key ID)\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column (STI only)\n // In CTI mode, meta fields shouldn't be used, but skip them anyway for safety\n if (field.type === 'meta') {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // If _meta.nullable is true, the field can be null regardless of required\n notNull: field._meta?.nullable ? false : field._meta?.required || false,\n primaryKey: field._meta?.primaryKey || false,\n unique: field._meta?.unique || false,\n description: field._meta?.description,\n };\n\n // Get default value\n if (\n field._meta?.default !== undefined &&\n this.shouldEmitDefault(field, field._meta.default)\n ) {\n columnDef.defaultValue = field._meta.default;\n }\n // Note: Removed automatic NOT NULL DEFAULT '' for TEXT columns\n // This was forcing all TEXT fields to be NOT NULL regardless of required option\n // If DuckDB ANY type inference is an issue, it should be handled at the adapter level\n // or with an explicit field option, not by silently changing schema semantics\n\n // Handle foreign keys\n if (field.type === 'foreignKey') {\n // Type cast to access relationship-specific properties\n const relatedName = field.related; // Top-level property\n const onDeleteAction = field._meta?.onDelete;\n\n if (relatedName) {\n columnDef.foreignKey = {\n table: this.classNameToTableName(relatedName),\n column: 'id',\n onDelete: onDeleteAction || 'CASCADE',\n onUpdate: 'CASCADE',\n };\n }\n }\n\n // Track opt-in column indexes for regular (non-FK, non-unique) fields.\n // FK columns already get auto-indexed below; unique columns produce\n // their own unique index; meta fields go through the jsonPath path.\n const isIndexed = field._meta?.indexed === true || field.indexed === true;\n if (\n isIndexed &&\n !columnDef.foreignKey &&\n !columnDef.unique &&\n !columnDef.primaryKey\n ) {\n indexedColumns.add(this.toSnakeCase(fieldName));\n }\n\n columns[this.toSnakeCase(fieldName)] = columnDef;\n }\n\n // Ensure timestamp columns exist\n if (!hasCreatedAt) {\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n }\n if (!hasUpdatedAt) {\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n }\n\n this.reconcileRegistryForeignKeyColumnTypes(\n columns,\n fields,\n config?.registry,\n );\n\n // Generate indexes. No `<table>_id_idx`: the primary key is already a\n // unique index on every engine, and the second B-tree over the same\n // random UUIDs cost writes on 238/238 tables for nothing (#2359, A5).\n // The differ drops the legacy one from existing databases.\n const indexes: IndexDefinition[] = [];\n\n if (!hasCustomPK) {\n // Tenant-scoped tables key on `(tenant_id, slug, context)` under the\n // stable `<table>_slug_context_idx` name (#2360).\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'cti',\n columns,\n config,\n );\n if (!this.conflictColumnsArePrimaryKey(conflictColumns, columns)) {\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n description: `Unique conflict index for ${className}`,\n });\n }\n }\n\n // Custom conflict columns replace the (slug, context) index, but slug\n // loading still filters on it (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // Emit opt-in column indexes for regular fields tagged with `indexed: true`\n for (const colName of indexedColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Index for ${colName}`,\n });\n }\n\n // Indexes declared with `@smrt({ indexes: [...] })` (#2357). Appended\n // before the reference-column helper so a declared composite leading with\n // a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n return {\n tableName,\n columns,\n indexes,\n triggers: [],\n foreignKeys: this.extractForeignKeys(columns),\n dependencies: [],\n version: createHash('sha256')\n .update(JSON.stringify(columns))\n .digest('hex')\n .substring(0, 8),\n packageName: 'runtime',\n baseClass: 'SmrtObject',\n };\n }\n\n /**\n * Generate STI (Single Table Inheritance) schema from ObjectRegistry fields\n *\n * Creates a shared table for an inheritance hierarchy with:\n * - _meta_type: Discriminator column to identify class type\n * - _meta_data: JSON column for flexible field storage\n * - Union of all FK columns from descendants (all nullable)\n * - One plain index per reference column (FK / cross-package ref /\n * tenant_id). Plain rather than partial-by-class: base-class polymorphic\n * queries carry no `_meta_type` predicate, so a partial index could not\n * serve them, while a plain index serves both those and the child\n * collections' `_meta_type = X AND fk = ?` filters — one index per column\n * instead of one per (column, class) (#2359).\n * - Unique indexes for `@field({ unique: true })` columns — see\n * {@link emitStiUniqueIndexes}.\n *\n * @param baseClassName - Base class name for the STI hierarchy\n * @param tableName - Shared table name (from base class)\n * @param fields - Map of Field definitions from base class\n * @returns Schema definition object for STI table\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 * roomId = foreignKey(Room);\n * }\n *\n * // Generates single table 'events' with:\n * // - title (from Event)\n * // - room_id (from Meeting, nullable)\n * // - _meta_type TEXT NOT NULL (discriminator)\n * // - _meta_data JSON (flexible storage)\n * ```\n */\n async generateSTISchemaFromRegistry(\n baseClassName: string,\n tableName: string,\n _fields: Map<string, RegistryField>,\n config?: SchemaGeneratorConfig,\n ): Promise<SchemaDefinition> {\n const ObjectRegistry =\n config?.registry ?? (await import('../registry.js')).ObjectRegistry;\n const columns: Record<string, ColumnDefinition> = {};\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n description: 'Primary identifier',\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n description: 'URL-friendly identifier',\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n defaultValue: '',\n description: 'Context for slug scoping',\n };\n\n // Add STI discriminator column\n columns._meta_type = {\n type: 'TEXT',\n notNull: true,\n description: 'Class type discriminator for STI',\n };\n\n // Add meta column for flexible storage\n columns._meta_data = {\n type: 'JSON',\n notNull: false,\n description: 'Flexible JSON storage for meta() fields',\n };\n\n // Track timestamp fields to avoid duplicates\n let hasCreatedAt = false;\n let hasUpdatedAt = false;\n\n // Get all descendants to aggregate fields\n const descendants = ObjectRegistry.getDescendants(baseClassName);\n const allClassNames = [baseClassName, ...descendants];\n\n // Track meta fields opted into JSON-path indexing (deduped across STI subtypes)\n const indexedMetaFields = new Set<string>();\n // Track regular-field columns opted into plain column indexing\n const indexedStiColumns = new Set<string>();\n // Track `unique: true` columns and the classes declaring them (#2359, A4)\n const uniqueColumnDeclarers = new Map<string, Set<string>>();\n\n // Aggregate fields from base and all descendants\n for (const className of allClassNames) {\n const classFields: Map<string, RegistryField> =\n await ObjectRegistry.getAllFields(className);\n\n for (const [fieldName, field] of classFields.entries()) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Capture indexed meta fields before the skip-meta branch below\n if (\n field.type === 'meta' &&\n (field.indexed === true || field._meta?.indexed === true)\n ) {\n indexedMetaFields.add(fieldName);\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context'\n ) {\n continue;\n }\n\n // Track timestamp fields\n if (fieldName === 'created_at' || fieldName === 'createdAt') {\n if (hasCreatedAt) continue;\n hasCreatedAt = true;\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n continue;\n }\n if (fieldName === 'updated_at' || fieldName === 'updatedAt') {\n if (hasUpdatedAt) continue;\n hasUpdatedAt = true;\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n\n // `unique: true` is recorded per declaring class (before the\n // inherited-column skip below, since descendants re-list inherited\n // fields) so the index shape can depend on where it was declared.\n if (field._meta?.unique === true) {\n let declarers = uniqueColumnDeclarers.get(columnName);\n if (!declarers) {\n declarers = new Set();\n uniqueColumnDeclarers.set(columnName, declarers);\n }\n declarers.add(className);\n }\n\n // Check if column already exists (inherited from parent)\n if (columns[columnName]) {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // STI: All columns nullable (union of child fields)\n notNull: false,\n primaryKey: false,\n // Uniqueness is enforced through indexes (emitStiUniqueIndexes), not\n // an inline column constraint the differ could never add later.\n unique: false,\n description: field._meta?.description,\n };\n\n // Get default value (but not applied in STI - defaults handled by application)\n if (\n field._meta?.default !== undefined &&\n this.shouldEmitDefault(field, field._meta.default)\n ) {\n columnDef.defaultValue = field._meta.default;\n }\n\n // Handle foreign keys\n if (field.type === 'foreignKey') {\n const relatedName = field.related; // Top-level property\n const onDeleteAction = field._meta?.onDelete;\n\n if (relatedName) {\n columnDef.foreignKey = {\n table: this.classNameToTableName(relatedName),\n column: 'id',\n onDelete: onDeleteAction || 'CASCADE',\n onUpdate: 'CASCADE',\n };\n }\n }\n\n // Track opt-in column indexes for regular STI columns\n const isIndexed =\n field._meta?.indexed === true || field.indexed === true;\n if (isIndexed && !columnDef.foreignKey) {\n indexedStiColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n }\n\n // Ensure timestamp columns exist\n if (!hasCreatedAt) {\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Creation timestamp',\n };\n }\n if (!hasUpdatedAt) {\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n defaultValue: 'current_timestamp',\n description: 'Last update timestamp',\n };\n }\n\n for (const className of allClassNames) {\n const classFields = await ObjectRegistry.getAllFields(className);\n this.reconcileRegistryForeignKeyColumnTypes(\n columns,\n classFields,\n ObjectRegistry,\n );\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: IndexDefinition[] = [];\n\n // Unique conflict index — by default (slug, context, _meta_type): STI\n // subclasses share a table, so the discriminator participates in\n // identity and two subtypes can share the same (slug, context). The key\n // is derived from the resolved config rather than hard-coded (#2360,\n // A4): a custom `@smrt({ conflictColumns })` on the STI root replaces it\n // (previously ignored here while `getConflictColumns()` honoured it, so\n // every save failed on PostgreSQL), and a tenant-scoped root leads it\n // with the tenant column. PostgreSQL UPSERT requires the live schema to\n // carry a matching unique index; the migration differ repairs older\n // deployments in place by name (issue #1165, #2360).\n this.emitStiConflictIndex(indexes, columns, tableName, config);\n\n // Index on type column (for polymorphic queries)\n indexes.push({\n name: `${tableName}_meta_type_idx`,\n columns: ['_meta_type'],\n description: 'Index for type discriminator queries',\n });\n\n // Unique indexes for `@field({ unique: true })` columns (#2359, A4)\n this.emitStiUniqueIndexes(\n indexes,\n uniqueColumnDeclarers,\n baseClassName,\n tableName,\n );\n\n // Custom conflict columns replace the (slug, context, _meta_type) index,\n // but slug loading still filters on slug/context (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // JSON-path indexes for @meta({ indexed: true }) fields\n for (const fieldName of indexedMetaFields) {\n indexes.push({\n name: `${tableName}_meta_${this.toSnakeCase(fieldName)}_idx`,\n columns: [],\n jsonPath: { column: '_meta_data', path: fieldName },\n description: `JSON-path index for @meta({ indexed: true }) field ${fieldName}`,\n });\n }\n\n // Plain column indexes for regular STI columns opted in via `indexed: true`\n for (const colName of indexedStiColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Index for ${colName}`,\n });\n }\n\n // Indexes declared on the STI base with `@smrt({ indexes: [...] })`\n // (#2357). Descendants share one table, so the base owns its access paths\n // and a declaration there applies to every subtype's reads. Appended\n // before the reference-column helper so a declared composite leading with\n // a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n return {\n tableName,\n columns,\n indexes,\n triggers: [],\n foreignKeys: this.extractForeignKeys(columns),\n dependencies: [],\n version: createHash('sha256')\n .update(JSON.stringify({ columns, baseClassName, descendants }))\n .digest('hex')\n .substring(0, 8),\n packageName: 'runtime',\n baseClass: 'SmrtObject',\n };\n }\n\n /**\n * Generate STI schema from manifest data (build-time, no runtime registry)\n *\n * This method generates STI schema purely from manifest data, without depending\n * on ObjectRegistry. This enables pre-generating schemas at build time for\n * efficient external package consumption.\n *\n * @param baseClassName - Base class name for the STI hierarchy\n * @param tableName - Shared table name (from base class)\n * @param baseFields - Fields from the base class\n * @param manifest - Full manifest containing all objects\n * @returns ManifestSchema for storage in manifest.json\n */\n generateSTISchemaFromManifest(\n baseClassName: string,\n tableName: string,\n _baseFields: Record<string, FieldDefinition>,\n manifest: SmartObjectManifest,\n config?: SchemaGeneratorConfig,\n ): ManifestSchema {\n const columns: Record<string, ManifestColumnDefinition> = {};\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n default: '',\n };\n\n // Add STI discriminator column\n columns._meta_type = {\n type: 'TEXT',\n notNull: true,\n };\n\n // Add meta column for flexible storage\n columns._meta_data = {\n type: 'JSON',\n notNull: false,\n };\n\n // Timestamp fields\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n // Find all descendants in manifest. Manifest keys are qualified names\n // while callers may pass the base's simple name (an external STI base is\n // reported that way); resolve to the key so the base's own fields are\n // aggregated and the base-declared unique check below sees the same\n // spelling the descendant loop records.\n const baseKey = this.resolveManifestClassKey(baseClassName, manifest);\n const descendants = this.findDescendantsInManifest(baseKey, manifest);\n const allClassNames = [baseKey, ...descendants];\n\n // Track meta fields opted into JSON-path indexing (deduped across STI subtypes)\n const indexedMetaFields = new Set<string>();\n // Track regular columns opted into plain column indexing\n const indexedStiColumns = new Set<string>();\n // Track `unique: true` columns and the classes declaring them (#2359, A4)\n const uniqueColumnDeclarers = new Map<string, Set<string>>();\n\n // Aggregate fields from base and all descendants\n for (const className of allClassNames) {\n const objDef = manifest.objects[className];\n if (!objDef) continue;\n\n for (const [fieldName, field] of Object.entries(objDef.fields)) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Capture indexed meta fields before the skip-meta branch below.\n // `indexed` is normally carried in `_meta`, but tolerate a legacy\n // top-level flag too — RegistryField models both placements.\n const indexedField: RegistryField = field;\n if (\n field.type === 'meta' &&\n (indexedField.indexed === true || field._meta?.indexed === true)\n ) {\n indexedMetaFields.add(fieldName);\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context' ||\n fieldName === 'created_at' ||\n fieldName === 'createdAt' ||\n fieldName === 'updated_at' ||\n fieldName === 'updatedAt'\n ) {\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields - they're stored in _meta_data JSONB column\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n\n // `unique: true` per declaring class — see generateSTISchemaFromRegistry.\n if (field._meta?.unique === true) {\n let declarers = uniqueColumnDeclarers.get(columnName);\n if (!declarers) {\n declarers = new Set();\n uniqueColumnDeclarers.set(columnName, declarers);\n }\n declarers.add(className);\n }\n\n // Check if column already exists (inherited from parent)\n if (columns[columnName]) {\n continue;\n }\n\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ManifestColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n // STI: All columns nullable (union of child fields)\n notNull: false,\n };\n\n // Get default value\n if (\n field.default !== undefined &&\n this.shouldEmitDefault(field, field.default)\n ) {\n columnDef.default = field.default;\n }\n\n // Track opt-in column indexes for regular STI columns\n const isIndexed =\n indexedField.indexed === true || field._meta?.indexed === true;\n if (isIndexed && field.type !== 'foreignKey') {\n indexedStiColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: ManifestIndexDefinition[] = [];\n\n // Unique conflict index (default slug, context, _meta_type; custom or\n // tenant-led per the root's config) — see generateSTISchemaFromRegistry.\n this.emitStiConflictIndex(indexes, columns, tableName, config);\n\n // Index on type column (for polymorphic queries)\n indexes.push({\n name: `${tableName}_meta_type_idx`,\n columns: ['_meta_type'],\n });\n\n // Unique indexes for `@field({ unique: true })` columns (#2359, A4)\n this.emitStiUniqueIndexes(\n indexes,\n uniqueColumnDeclarers,\n baseKey,\n tableName,\n );\n\n // Custom conflict columns replace the (slug, context, _meta_type) index,\n // but slug loading still filters on slug/context (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // JSON-path indexes for @meta({ indexed: true }) fields\n for (const fieldName of indexedMetaFields) {\n indexes.push({\n name: `${tableName}_meta_${this.toSnakeCase(fieldName)}_idx`,\n columns: [],\n jsonPath: { column: '_meta_data', path: fieldName },\n });\n }\n\n // Plain column indexes for regular STI columns opted in via `indexed: true`\n for (const colName of indexedStiColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n });\n }\n\n // Indexes declared on the STI base with `@smrt({ indexes: [...] })`\n // (#2357), before the reference-column helper so a declared composite\n // leading with a reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359). Runs last so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates them; the cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n const schemaDefinition: SchemaDefinition = {\n tableName,\n columns: this.convertManifestColumnsToSchemaColumns(columns),\n indexes: indexes.map((idx) => ({\n name: idx.name,\n columns: idx.columns,\n unique: idx.unique,\n where: idx.where,\n jsonPath: idx.jsonPath,\n })),\n triggers: [],\n foreignKeys: [],\n dependencies: [],\n version: '',\n packageName: '',\n };\n\n const ddl = this.generateSQL(schemaDefinition);\n\n // Generate version hash\n const version = createHash('sha256')\n .update(JSON.stringify({ columns, baseClassName, descendants }))\n .digest('hex')\n .substring(0, 8);\n\n return {\n tableName,\n ddl,\n columns,\n indexes,\n version,\n };\n }\n\n /**\n * Generate CTI (Class Table Inheritance) schema from manifest data\n *\n * @param className - Class name\n * @param tableName - Table name\n * @param fields - Fields from the class\n * @returns ManifestSchema for storage in manifest.json\n */\n generateCTISchemaFromManifest(\n className: string,\n tableName: string,\n fields: Record<string, FieldDefinition>,\n config?: SchemaGeneratorConfig,\n ): ManifestSchema {\n const columns: Record<string, ManifestColumnDefinition> = {};\n\n // Track regular columns opted into a plain index via `@field({ indexed: true })`.\n // Mirrors the STI path (generateSTISchemaFromManifest) so CTI tables honor\n // the documented `FieldOptions.indexed` flag too.\n const indexedColumns = new Set<string>();\n\n // Add default SMRT fields\n columns.id = {\n type: this.getIdColumnType(config),\n primaryKey: true,\n referenceKind: 'id',\n notNull: true,\n };\n\n columns.slug = {\n type: 'TEXT',\n notNull: true,\n };\n\n columns.context = {\n type: 'TEXT',\n notNull: true,\n default: '',\n };\n\n // Timestamp fields\n columns.created_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n columns.updated_at = {\n type: 'TIMESTAMP',\n notNull: true,\n default: 'current_timestamp',\n };\n\n // Add class fields\n for (const [fieldName, field] of Object.entries(fields)) {\n // Skip transient fields\n if (field.transient || field._meta?.transient) {\n continue;\n }\n\n // Skip default fields already added\n if (\n fieldName === 'id' ||\n fieldName === 'slug' ||\n fieldName === 'context' ||\n fieldName === 'created_at' ||\n fieldName === 'createdAt' ||\n fieldName === 'updated_at' ||\n fieldName === 'updatedAt'\n ) {\n continue;\n }\n\n // Skip relationship fields that don't create columns\n if (field.type === 'oneToMany' || field.type === 'manyToMany') {\n continue;\n }\n\n // Skip meta fields\n if (field.type === 'meta') {\n continue;\n }\n\n const columnName = this.toSnakeCase(fieldName);\n const sqlType = this.getRelationshipColumnType(field);\n\n const columnDef: ManifestColumnDefinition = {\n type: sqlType,\n referenceKind: this.getReferenceKind(field),\n notNull: field._meta?.nullable ? false : field.required || false,\n unique: field._meta?.unique || false,\n };\n\n if (\n field.default !== undefined &&\n this.shouldEmitDefault(field, field.default)\n ) {\n columnDef.default = field.default;\n }\n\n // Opt-in plain column index. FK columns and unique columns get their own\n // indexes, so skip those here.\n const indexedField: RegistryField = field;\n const isIndexed =\n indexedField.indexed === true || field._meta?.indexed === true;\n if (isIndexed && field.type !== 'foreignKey' && !columnDef.unique) {\n indexedColumns.add(columnName);\n }\n\n columns[columnName] = columnDef;\n }\n\n // Generate indexes. No `<table>_id_idx` — see generateSchemaFromRegistry.\n const indexes: ManifestIndexDefinition[] = [];\n\n // Custom conflict columns if provided, otherwise slug+context — led by\n // the tenant column on a tenant-scoped table, under the same stable\n // index name (#2360).\n const { conflictColumns, tenantColumn } = this.resolveConflictTarget(\n 'cti',\n columns,\n config,\n );\n if (!this.conflictColumnsArePrimaryKey(conflictColumns, columns)) {\n indexes.push({\n name: conflictIndexName(tableName, conflictColumns, tenantColumn),\n columns: conflictColumns,\n unique: true,\n });\n }\n\n // Custom conflict columns replace the (slug, context) index, but slug\n // loading still filters on it (#2359, A7).\n this.ensureSlugLookupIndex(indexes, columns, tableName);\n\n // Plain column indexes for regular columns opted in via `indexed: true`.\n for (const colName of indexedColumns) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n });\n }\n\n // Indexes declared with `@smrt({ indexes: [...] })` (#2357), before the\n // reference-column helper so a declared composite leading with a\n // reference column suppresses the standalone auto index.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // The default list page orders by created_at (#2363) — after the declared\n // indexes, before the reference-column pass.\n this.ensureDefaultListOrderingIndex(indexes, columns, tableName);\n\n // Polymorphic association owner lookup `(meta_type, meta_id)` (#2364,\n // epic #2382 finding A3) — alongside the default-ordering composite,\n // before the reference-column pass.\n this.ensurePolymorphicAssociationIndex(indexes, columns, tableName);\n\n // Reference columns (@foreignKey / @crossPackageRef / tenant_id) are\n // always indexed (#2356, #2359). Runs last so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates them; the cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureReferenceColumnIndexes(indexes, columns, tableName);\n\n // Last: nothing may lengthen a name after this (#2374).\n enforceIdentifierLimits(tableName, indexes);\n\n const schemaDefinition: SchemaDefinition = {\n tableName,\n columns: this.convertManifestColumnsToSchemaColumns(columns),\n indexes: indexes.map((idx) => ({\n name: idx.name,\n columns: idx.columns,\n unique: idx.unique,\n where: idx.where,\n jsonPath: idx.jsonPath,\n })),\n triggers: [],\n foreignKeys: [],\n dependencies: [],\n version: '',\n packageName: '',\n };\n\n const ddl = this.generateSQL(schemaDefinition);\n\n // Generate version hash\n const version = createHash('sha256')\n .update(JSON.stringify({ columns, className }))\n .digest('hex')\n .substring(0, 8);\n\n return {\n tableName,\n ddl,\n columns,\n indexes,\n version,\n };\n }\n\n /**\n * Resolve a class name (simple or qualified) to the key it is stored under\n * in `manifest.objects`. Falls back to the input when the manifest does not\n * carry the class (e.g. an external STI base absent from the aggregated\n * manifest), so callers can keep using it as an opaque label.\n */\n private resolveManifestClassKey(\n className: string,\n manifest: SmartObjectManifest,\n ): string {\n if (manifest.objects[className]) return className;\n const simpleName = className.includes(':')\n ? className.slice(className.lastIndexOf(':') + 1)\n : className;\n for (const [key, obj] of Object.entries(manifest.objects)) {\n if (obj.qualifiedName === className || obj.className === simpleName) {\n return key;\n }\n }\n return className;\n }\n\n /**\n * Find all descendants of a class in the manifest\n *\n * Note: Manifest keys are now qualified names (@pkg:ClassName) but `extends` field\n * stores simple PascalCase class names. We need to handle both qualified and simple\n * name lookups.\n *\n * Issue #713: Updated to handle qualified names in manifest keys\n */\n private findDescendantsInManifest(\n baseClassName: string,\n manifest: SmartObjectManifest,\n visited: Set<string> = new Set(),\n ): string[] {\n const descendants: string[] = [];\n\n // Prevent infinite recursion (e.g., class extends same-named class from another package)\n if (visited.has(baseClassName)) return descendants;\n visited.add(baseClassName);\n\n // Extract the simple class name from qualified name if needed\n // e.g., '@happyvertical/smrt-core:PolyEvent' -> 'PolyEvent'\n const simpleBaseClassName = baseClassName.includes(':')\n ? baseClassName.split(':').pop() || baseClassName\n : baseClassName;\n const baseClassLower = simpleBaseClassName.toLowerCase();\n\n for (const [name, obj] of Object.entries(manifest.objects)) {\n // Skip self-references (class extending same-named class from another package)\n if (\n obj.className.toLowerCase() === baseClassLower &&\n obj.extends?.toLowerCase() === baseClassLower\n ) {\n continue;\n }\n // obj.extends is a simple class name (e.g., 'PolyEvent')\n // Compare with the simple (non-qualified) version of the base class name\n if (obj.extends?.toLowerCase() === baseClassLower) {\n descendants.push(name);\n // Recursively find descendants of this class\n // Pass the qualified name (manifest key) for recursive lookup\n descendants.push(\n ...this.findDescendantsInManifest(name, manifest, visited),\n );\n }\n }\n\n return descendants;\n }\n\n /**\n * Convert ManifestColumnDefinition to ColumnDefinition\n */\n private convertManifestColumnsToSchemaColumns(\n manifestColumns: Record<string, ManifestColumnDefinition>,\n ): Record<string, ColumnDefinition> {\n const columns: Record<string, ColumnDefinition> = {};\n\n for (const [name, col] of Object.entries(manifestColumns)) {\n columns[name] = {\n type: col.type as SQLDataType,\n primaryKey: col.primaryKey,\n referenceKind: col.referenceKind,\n notNull: col.notNull,\n unique: col.unique,\n defaultValue: col.default,\n };\n }\n\n return columns;\n }\n\n /**\n * Convert camelCase to snake_case\n */\n private toSnakeCase(str: string): string {\n return str\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/^_/, '');\n }\n\n /**\n * Generate the CREATE TABLE statement for a schema definition.\n *\n * With an `engine` this delegates to that engine's DDL strategy. Without\n * one it renders the engine-neutral preview stored in `schema.ddl` and\n * `manifest.json`: abstract SQL types, no indexes, no triggers. That\n * preview is NOT an executable representation of the table (#2358) —\n * executable paths (`db:migrate`, `MigrationGenerator`, `SchemaAggregator`,\n * `createIsolatedTestDbFromManifest`) render `schema.columns` and\n * `schema.indexes` through `getDDLStrategy(engine)` instead.\n *\n * @param schema - Schema definition object\n * @param engine - Optional target engine; omit for the neutral preview\n * @returns SQL CREATE TABLE statement (no indexes)\n */\n generateSQL(schema: SchemaDefinition, engine?: DatabaseEngine): string {\n if (engine) {\n return getDDLStrategy(engine).generateCreateTable(schema);\n }\n\n const { tableName, columns } = schema;\n let sql = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\\n`;\n\n for (const [columnName, columnDef] of Object.entries(columns)) {\n const parts = [` ${quoteIdentifier(columnName)} ${columnDef.type}`];\n\n if (columnDef.primaryKey) {\n parts.push('PRIMARY KEY');\n }\n\n if (columnDef.notNull) {\n parts.push('NOT NULL');\n }\n\n if (columnDef.unique && !columnDef.primaryKey) {\n parts.push('UNIQUE');\n }\n\n if (columnDef.defaultValue !== undefined) {\n parts.push(\n `DEFAULT ${this.formatDefaultValue(\n columnDef.defaultValue,\n columnDef.type,\n )}`,\n );\n }\n\n sql += `${parts.join(' ')},\\n`;\n }\n\n return `${sql.slice(0, -2)}\\n);`;\n }\n\n /**\n * Format default value for SQL\n *\n * SQLite doesn't support CAST expressions in DEFAULT values (only literal values are allowed).\n * This method generates DEFAULT values that work with both SQLite and DuckDB.\n *\n * @param value - Default value\n * @param type - Column SQL type\n * @returns Formatted SQL default value expression\n */\n private formatDefaultValue(value: unknown, type: SQLDataType): string {\n // Delegate to the shared, injection-safe formatter so all DDL paths\n // converge on one set of rules (allowlisted keyword/function defaults\n // instead of \"contains `(`\", type-driven quoting, no string-\"null\" fold).\n // This path is engine-agnostic and never emits CAST expressions, so the\n // output stays valid for SQLite and DuckDB. Non-string TIMESTAMP defaults\n // fall back to lowercase `current_timestamp` to preserve prior output.\n return formatDefaultValueShared(value, type, {\n nonStringTimestampDefault: 'current_timestamp',\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -6,11 +6,10 @@ export * from './ddl/index.js';
|
|
|
6
6
|
export { SchemaGenerator } from './generator.js';
|
|
7
7
|
export { type ConflictTargetInput, checkLiveSchemaParity, type ExpectedTableOrigin, type LiveParityFinding, type LiveParityFindingKind, type LiveParitySeverity, LiveSchemaParityError, type LiveSchemaParityOptions, type LiveSchemaParityReport, normalizeSqlType, parseIndexDefColumns, } from './live-parity.js';
|
|
8
8
|
export { type CollectedManifestTable, collectManifestTables, type ManifestColumnLike, type ManifestIndexLike, type ManifestSchemaLike, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, renderCollectedManifestTable, } from './manifest-schema.js';
|
|
9
|
-
export { SchemaOverrideSystem } from './override-system.js';
|
|
10
9
|
export type { AggregatedTable, AggregateOptions, AggregationResult, } from './schema-aggregator.js';
|
|
11
10
|
export { SchemaAggregator } from './schema-aggregator.js';
|
|
12
11
|
export { createSchemaManager, SchemaManager } from './schema-manager.js';
|
|
13
12
|
export { getSystemTableShapes, parseTableShapes, SYSTEM_TABLE_NAMES, type SystemTableColumnShape, type SystemTableIndexShape, type SystemTableShape, } from './system-table-shapes.js';
|
|
14
|
-
export type { ColumnDefinition, ForeignKeyDefinition, IndexDefinition, SchemaDefinition, SchemaManifest, SchemaMigration,
|
|
13
|
+
export type { ColumnDefinition, ForeignKeyDefinition, IndexDefinition, SchemaDefinition, SchemaManifest, SchemaMigration, TriggerDefinition, } from './types.js';
|
|
15
14
|
export * from './types.js';
|
|
16
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EACL,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,KAAK,sBAAsB,EAC3B,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,4BAA4B,EAC5B,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EACL,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,KAAK,sBAAsB,EAC3B,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,4BAA4B,EAC5B,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,cAAc,YAAY,CAAC"}
|
package/dist/schema/index.js
CHANGED
|
@@ -10,6 +10,5 @@ import { SchemaManager, createSchemaManager } from "./schema-manager.js";
|
|
|
10
10
|
import { collectManifestTables, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, renderCollectedManifestTable } from "./manifest-schema.js";
|
|
11
11
|
import { LiveSchemaParityError, checkLiveSchemaParity, normalizeSqlType, parseIndexDefColumns } from "./live-parity.js";
|
|
12
12
|
import { SchemaCodeGenerator } from "./code-generator.js";
|
|
13
|
-
import { SchemaOverrideSystem } from "./override-system.js";
|
|
14
13
|
import { SchemaAggregator } from "./schema-aggregator.js";
|
|
15
|
-
export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, LiveSchemaParityError, PostgresStrategy, SQLiteStrategy, SYSTEM_TABLE_NAMES, SchemaAggregator, SchemaCodeGenerator, SchemaGenerator, SchemaManager,
|
|
14
|
+
export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, LiveSchemaParityError, PostgresStrategy, SQLiteStrategy, SYSTEM_TABLE_NAMES, SchemaAggregator, SchemaCodeGenerator, SchemaGenerator, SchemaManager, checkLiveSchemaParity, collectManifestTables, createSchemaManager, detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy, getSystemTableShapes, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, normalizeSqlType, parseIndexDefColumns, parseTableShapes, renderCollectedManifestTable };
|
|
@@ -4,7 +4,9 @@ import { SchemaDefinition } from './types.js';
|
|
|
4
4
|
/** How badly a finding breaks the deployment. */
|
|
5
5
|
export type LiveParitySeverity = 'error' | 'warning' | 'info';
|
|
6
6
|
/** The class of drift a finding describes. */
|
|
7
|
-
export type LiveParityFindingKind = 'missing_table' | 'extra_table' | 'missing_column' | 'extra_column' | 'column_type_drift' | 'column_nullability_drift' | 'missing_index' | 'extra_index' | 'unindexed_reference' | 'conflict_target_unindexed' | 'conflict_target_not_unique' | 'unique_constraint_missing' | 'invalid_index'
|
|
7
|
+
export type LiveParityFindingKind = 'missing_table' | 'extra_table' | 'missing_column' | 'extra_column' | 'column_type_drift' | 'column_nullability_drift' | 'missing_index' | 'extra_index' | 'unindexed_reference' | 'conflict_target_unindexed' | 'conflict_target_not_unique' | 'unique_constraint_missing' | 'invalid_index'
|
|
8
|
+
/** Pre-#2373 PostgreSQL/DuckDB int4 column needing opt-in widening. */
|
|
9
|
+
| 'legacy_integer_width';
|
|
8
10
|
/** One difference between the live database and the expected shape. */
|
|
9
11
|
export interface LiveParityFinding {
|
|
10
12
|
kind: LiveParityFindingKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-parity.d.ts","sourceRoot":"","sources":["../../src/schema/live-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"live-parity.d.ts","sourceRoot":"","sources":["../../src/schema/live-parity.ts"],"names":[],"mappings":"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"}
|