@happyvertical/smrt-core 0.40.69 → 0.40.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/query-bounds.md +45 -0
  4. package/agents/schema-paths.md +328 -0
  5. package/dist/browser.d.ts +1 -0
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +2 -1
  8. package/dist/class.d.ts.map +1 -1
  9. package/dist/class.js +28 -4
  10. package/dist/class.js.map +1 -1
  11. package/dist/collection.d.ts +101 -2
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +224 -43
  14. package/dist/collection.js.map +1 -1
  15. package/dist/database.d.ts +8 -0
  16. package/dist/database.d.ts.map +1 -1
  17. package/dist/database.js +16 -8
  18. package/dist/database.js.map +1 -1
  19. package/dist/db-errors.d.ts +105 -0
  20. package/dist/db-errors.d.ts.map +1 -0
  21. package/dist/db-errors.js +382 -0
  22. package/dist/db-errors.js.map +1 -0
  23. package/dist/decorators/index.d.ts +17 -1
  24. package/dist/decorators/index.d.ts.map +1 -1
  25. package/dist/decorators/index.js +65 -7
  26. package/dist/decorators/index.js.map +1 -1
  27. package/dist/dispatch/bus.d.ts.map +1 -1
  28. package/dist/dispatch/bus.js +4 -3
  29. package/dist/dispatch/bus.js.map +1 -1
  30. package/dist/errors.d.ts +30 -2
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +34 -2
  33. package/dist/errors.js.map +1 -1
  34. package/dist/generators/changes-route.d.ts.map +1 -1
  35. package/dist/generators/changes-route.js +6 -3
  36. package/dist/generators/changes-route.js.map +1 -1
  37. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  38. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  39. package/dist/generators/mcp-runtime-template.js +38 -4
  40. package/dist/generators/mcp-runtime-template.js.map +1 -1
  41. package/dist/generators/mcp.d.ts +16 -0
  42. package/dist/generators/mcp.d.ts.map +1 -1
  43. package/dist/generators/mcp.js +41 -3
  44. package/dist/generators/mcp.js.map +1 -1
  45. package/dist/generators/rest.d.ts +22 -0
  46. package/dist/generators/rest.d.ts.map +1 -1
  47. package/dist/generators/rest.js +34 -3
  48. package/dist/generators/rest.js.map +1 -1
  49. package/dist/index.d.ts +6 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +8 -3
  52. package/dist/manifest/static-manifest.js +1 -1
  53. package/dist/manifest/static-manifest.js.map +1 -1
  54. package/dist/manifest/store.js +1 -1
  55. package/dist/manifest.json +1 -1
  56. package/dist/migrations/differ.d.ts +202 -8
  57. package/dist/migrations/differ.d.ts.map +1 -1
  58. package/dist/migrations/differ.js +604 -46
  59. package/dist/migrations/differ.js.map +1 -1
  60. package/dist/migrations/generator.d.ts +31 -4
  61. package/dist/migrations/generator.d.ts.map +1 -1
  62. package/dist/migrations/generator.js +49 -5
  63. package/dist/migrations/generator.js.map +1 -1
  64. package/dist/migrations/index.d.ts +3 -2
  65. package/dist/migrations/index.d.ts.map +1 -1
  66. package/dist/migrations/index.js +5 -3
  67. package/dist/migrations/orchestrate.js +35 -6
  68. package/dist/migrations/orchestrate.js.map +1 -1
  69. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  70. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  71. package/dist/migrations/sqlite-rebuild.js +514 -0
  72. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  73. package/dist/migrations/tracker.d.ts +114 -1
  74. package/dist/migrations/tracker.d.ts.map +1 -1
  75. package/dist/migrations/tracker.js +331 -16
  76. package/dist/migrations/tracker.js.map +1 -1
  77. package/dist/migrations/types.d.ts +19 -4
  78. package/dist/migrations/types.d.ts.map +1 -1
  79. package/dist/migrations.js +5 -3
  80. package/dist/object.d.ts +45 -6
  81. package/dist/object.d.ts.map +1 -1
  82. package/dist/object.js +60 -13
  83. package/dist/object.js.map +1 -1
  84. package/dist/postgres-timeouts.d.ts +240 -0
  85. package/dist/postgres-timeouts.d.ts.map +1 -0
  86. package/dist/postgres-timeouts.js +204 -0
  87. package/dist/postgres-timeouts.js.map +1 -0
  88. package/dist/query-bounds.d.ts +97 -0
  89. package/dist/query-bounds.d.ts.map +1 -0
  90. package/dist/query-bounds.js +173 -0
  91. package/dist/query-bounds.js.map +1 -0
  92. package/dist/registry/types.d.ts +32 -1
  93. package/dist/registry/types.d.ts.map +1 -1
  94. package/dist/registry.js +1 -1
  95. package/dist/scanner/types.d.ts +5 -0
  96. package/dist/scanner/types.d.ts.map +1 -1
  97. package/dist/scanner/types.js.map +1 -1
  98. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  99. package/dist/schema/ddl/base-strategy.js +2 -2
  100. package/dist/schema/ddl/base-strategy.js.map +1 -1
  101. package/dist/schema/generator.d.ts +70 -5
  102. package/dist/schema/generator.d.ts.map +1 -1
  103. package/dist/schema/generator.js +124 -6
  104. package/dist/schema/generator.js.map +1 -1
  105. package/dist/schema/index.d.ts +3 -0
  106. package/dist/schema/index.d.ts.map +1 -1
  107. package/dist/schema/index.js +4 -1
  108. package/dist/schema/live-parity.d.ts +90 -0
  109. package/dist/schema/live-parity.d.ts.map +1 -0
  110. package/dist/schema/live-parity.js +600 -0
  111. package/dist/schema/live-parity.js.map +1 -0
  112. package/dist/schema/manifest-schema.d.ts +121 -0
  113. package/dist/schema/manifest-schema.d.ts.map +1 -0
  114. package/dist/schema/manifest-schema.js +267 -0
  115. package/dist/schema/manifest-schema.js.map +1 -0
  116. package/dist/schema/schema-aggregator.d.ts +24 -10
  117. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  118. package/dist/schema/schema-aggregator.js +35 -90
  119. package/dist/schema/schema-aggregator.js.map +1 -1
  120. package/dist/schema/system-table-shapes.d.ts +52 -0
  121. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  122. package/dist/schema/system-table-shapes.js +174 -0
  123. package/dist/schema/system-table-shapes.js.map +1 -0
  124. package/dist/schema/types.d.ts +103 -4
  125. package/dist/schema/types.d.ts.map +1 -1
  126. package/dist/schema/utils.d.ts +1 -0
  127. package/dist/schema/utils.d.ts.map +1 -1
  128. package/dist/schema/utils.js +3 -1
  129. package/dist/schema/utils.js.map +1 -1
  130. package/dist/schema.js +4 -1
  131. package/dist/smrt-knowledge.json +18 -6
  132. package/dist/sync/apply.d.ts.map +1 -1
  133. package/dist/sync/apply.js +9 -16
  134. package/dist/sync/apply.js.map +1 -1
  135. package/dist/testing/database.d.ts.map +1 -1
  136. package/dist/testing/database.js +1 -0
  137. package/dist/testing/database.js.map +1 -1
  138. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  139. package/dist/vite-plugin/sveltekit-generator.js +71 -5
  140. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  141. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  142. package/dist/vite-plugin/web-collections.js +6 -4
  143. package/dist/vite-plugin/web-collections.js.map +1 -1
  144. package/package.json +5 -5
@@ -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 { getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport {\n formatDefaultValue as formatDefaultValueShared,\n quoteIdentifier,\n quoteStringLiteral,\n} from './sql-identifiers.js';\nimport type {\n ColumnDefinition,\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 conflictColumns?: string[];\n idType?: 'uuid' | 'text';\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 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 * 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 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\n const indexes: IndexDefinition[] = [];\n\n if (!hasCustomPK) {\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n description: 'Primary key index',\n });\n\n const conflictColumns = config?.conflictColumns || ['slug', 'context'];\n const conflictIndexName =\n conflictColumns.length > 2\n ? `${tableName}_${conflictColumns.slice(0, 2).join('_')}_idx`\n : `${tableName}_${conflictColumns.join('_')}_idx`;\n\n indexes.push({\n name: conflictIndexName,\n columns: conflictColumns,\n unique: true,\n description: `Unique conflict index for ${className}`,\n });\n } else {\n // Find custom PK column and create index\n for (const [colName, colDef] of Object.entries(columns)) {\n if (colDef.primaryKey) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Primary key index`,\n });\n break;\n }\n }\n }\n\n // Create indexes for foreign keys\n for (const [colName, colDef] of Object.entries(columns)) {\n if (colDef.foreignKey) {\n indexes.push({\n name: `idx_${tableName}_${colName}`,\n columns: [colName],\n description: `Foreign key index for ${colName}`,\n });\n }\n }\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 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 * - Partial indexes for FK columns (filtered by _meta_type)\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 FK columns by class for partial indexes\n const fkColumnsByClass = new Map<string, Set<string>>();\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\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 fkColumnsByClass.set(className, new Set());\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 // 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 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 // Track FK column for this class (for partial indexes)\n fkColumnsByClass.get(className)?.add(columnName);\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\n const indexes: IndexDefinition[] = [];\n\n // Primary key index\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n description: 'Primary key index',\n });\n\n // Unique index for slug, context, and type (STI variation).\n // STI subclasses share a table, so the discriminator participates in\n // identity — two subtypes can share the same (slug, context). PostgreSQL\n // UPSERT requires the live schema to carry a matching unique index; the\n // migration differ repairs older deployments where this index was created\n // non-unique or never created at all (issue #1165).\n indexes.push({\n name: `${tableName}_slug_context_meta_type_idx`,\n columns: ['slug', 'context', '_meta_type'],\n unique: true,\n description: 'Unique index for slug, context, and type',\n });\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 // Partial indexes for FK columns (filtered by type)\n for (const [className, fkColumns] of fkColumnsByClass.entries()) {\n for (const fkColumn of fkColumns) {\n indexes.push({\n name: `idx_${tableName}_${fkColumn}_${className.toLowerCase()}`,\n columns: [fkColumn],\n where: `_meta_type = ${quoteStringLiteral(className)}`,\n description: `Partial index for ${fkColumn} in ${className} rows`,\n });\n }\n }\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 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\n const descendants = this.findDescendantsInManifest(baseClassName, manifest);\n const allClassNames = [baseClassName, ...descendants];\n\n // Track FK columns by class for partial indexes\n const fkColumnsByClass = new Map<string, Set<string>>();\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\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 fkColumnsByClass.set(className, new Set());\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 // 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 FK columns for this class (for partial indexes)\n if (field.type === 'foreignKey') {\n fkColumnsByClass.get(className)?.add(columnName);\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\n const indexes: ManifestIndexDefinition[] = [];\n\n // Primary key index\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n });\n\n // Unique index for slug, context, and type (STI variation) — see\n // generateSTISchemaFromRegistry for the rationale.\n indexes.push({\n name: `${tableName}_slug_context_meta_type_idx`,\n columns: ['slug', 'context', '_meta_type'],\n unique: true,\n });\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 // 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 // Generate DDL\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\n const indexes: ManifestIndexDefinition[] = [];\n\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n });\n\n // Use custom conflict columns if provided, otherwise default to slug+context\n const conflictColumns = config?.conflictColumns || ['slug', 'context'];\n const indexName =\n conflictColumns.length > 2\n ? `${tableName}_${conflictColumns.slice(0, 2).join('_')}_idx`\n : `${tableName}_${conflictColumns.join('_')}_idx`;\n\n indexes.push({\n name: indexName,\n columns: conflictColumns,\n unique: true,\n });\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 // Generate DDL\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 * 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 SQL CREATE TABLE statement from schema definition\n *\n * This is the single source of truth for SQL generation, consolidating\n * logic that was previously duplicated across multiple code paths.\n *\n * @param schema - Schema definition object\n * @returns SQL CREATE TABLE statement with indexes\n */\n generateSQL(schema: SchemaDefinition, engine?: DatabaseEngine): string {\n // NOTE: We no longer append indexes to DDL string here.\n // The SDK expects ddl to contain ONLY the CREATE TABLE statement.\n // Indexes are stored separately in schema.indexes as SQL strings\n // and the SDK handles them via syncSchema() or dedicated index creation.\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 SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { classnameToTablename } from '../utils/naming.js';\nimport { getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.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 conflictColumns?: 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 // Tenancy injects `tenant_id` but nothing indexes it (#2356).\n this.ensureTenantIdIndex(indexes, columns, tableName);\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 * 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 *before* `ensureTenantIdIndex`: a declared composite\n * leading with `tenant_id` is exactly what that helper's \"already leading\"\n * check is meant to defer to, and appending afterwards would leave the table\n * with a redundant standalone tenant index (#2384).\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 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 a tenancy-injected `tenant_id` column has an index leading with it.\n *\n * Tenancy injects the column but nothing indexed it: the generated set covers\n * foreign keys, unique columns, `updated_at` and the STI discriminator, and\n * `tenant_id` is in none of those. Every tenant-scoped read filters on it, so\n * without an index each one scans the whole multi-tenant table — measured in\n * one production database, 164 of 212 tenant-scoped tables had no such index\n * (#2356).\n *\n * \"Leading with it\" rather than \"always add\": a table that already has a\n * composite index starting on `tenant_id` (commonly from `conflictColumns`)\n * is already served, and a standalone duplicate would only cost writes.\n */\n private ensureTenantIdIndex(\n indexes: Array<{\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n }>,\n columns: Record<string, { referenceKind?: string } | undefined>,\n tableName: string,\n ): void {\n const tenantColumn = Object.entries(columns).find(\n ([, columnDef]) => columnDef?.referenceKind === 'tenantId',\n )?.[0];\n if (!tenantColumn) return;\n\n const alreadyLeading = indexes.some(\n (index) => index.columns?.[0] === tenantColumn,\n );\n if (alreadyLeading) 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({\n name: `${tableName}_${tenantColumn}_idx`,\n columns: [tenantColumn],\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 generateSchema()'s ensureTenantIdIndex.\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\n const indexes: IndexDefinition[] = [];\n\n if (!hasCustomPK) {\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n description: 'Primary key index',\n });\n\n const conflictColumns = config?.conflictColumns || ['slug', 'context'];\n const conflictIndexName =\n conflictColumns.length > 2\n ? `${tableName}_${conflictColumns.slice(0, 2).join('_')}_idx`\n : `${tableName}_${conflictColumns.join('_')}_idx`;\n\n indexes.push({\n name: conflictIndexName,\n columns: conflictColumns,\n unique: true,\n description: `Unique conflict index for ${className}`,\n });\n } else {\n // Find custom PK column and create index\n for (const [colName, colDef] of Object.entries(columns)) {\n if (colDef.primaryKey) {\n indexes.push({\n name: `${tableName}_${colName}_idx`,\n columns: [colName],\n description: `Primary key index`,\n });\n break;\n }\n }\n }\n\n // Create indexes for foreign keys\n for (const [colName, colDef] of Object.entries(columns)) {\n if (colDef.foreignKey) {\n indexes.push({\n name: `idx_${tableName}_${colName}`,\n columns: [colName],\n description: `Foreign key index for ${colName}`,\n });\n }\n }\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).\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // Tenancy injects `tenant_id` but nothing indexes it (#2356).\n this.ensureTenantIdIndex(indexes, columns, tableName);\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 * - Partial indexes for FK columns (filtered by _meta_type)\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 FK columns by class for partial indexes\n const fkColumnsByClass = new Map<string, Set<string>>();\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\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 fkColumnsByClass.set(className, new Set());\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 // 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 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 // Track FK column for this class (for partial indexes)\n fkColumnsByClass.get(className)?.add(columnName);\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\n const indexes: IndexDefinition[] = [];\n\n // Primary key index\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n description: 'Primary key index',\n });\n\n // Unique index for slug, context, and type (STI variation).\n // STI subclasses share a table, so the discriminator participates in\n // identity — two subtypes can share the same (slug, context). PostgreSQL\n // UPSERT requires the live schema to carry a matching unique index; the\n // migration differ repairs older deployments where this index was created\n // non-unique or never created at all (issue #1165).\n indexes.push({\n name: `${tableName}_slug_context_meta_type_idx`,\n columns: ['slug', 'context', '_meta_type'],\n unique: true,\n description: 'Unique index for slug, context, and type',\n });\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 // Partial indexes for FK columns (filtered by type)\n for (const [className, fkColumns] of fkColumnsByClass.entries()) {\n for (const fkColumn of fkColumns) {\n indexes.push({\n name: `idx_${tableName}_${fkColumn}_${className.toLowerCase()}`,\n columns: [fkColumn],\n where: `_meta_type = ${quoteStringLiteral(className)}`,\n description: `Partial index for ${fkColumn} in ${className} rows`,\n });\n }\n }\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.\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // Tenancy injects `tenant_id` but nothing indexes it (#2356).\n this.ensureTenantIdIndex(indexes, columns, tableName);\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\n const descendants = this.findDescendantsInManifest(baseClassName, manifest);\n const allClassNames = [baseClassName, ...descendants];\n\n // Track FK columns by class for partial indexes\n const fkColumnsByClass = new Map<string, Set<string>>();\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\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 fkColumnsByClass.set(className, new Set());\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 // 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 FK columns for this class (for partial indexes)\n if (field.type === 'foreignKey') {\n fkColumnsByClass.get(className)?.add(columnName);\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\n const indexes: ManifestIndexDefinition[] = [];\n\n // Primary key index\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n });\n\n // Unique index for slug, context, and type (STI variation) — see\n // generateSTISchemaFromRegistry for the rationale.\n indexes.push({\n name: `${tableName}_slug_context_meta_type_idx`,\n columns: ['slug', 'context', '_meta_type'],\n unique: true,\n });\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 // 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: [...] })` (#2357).\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // Tenancy injects `tenant_id` but nothing indexes it (#2356). Added\n // before the schema definition is finalized so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates it. The cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureTenantIdIndex(indexes, columns, tableName);\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\n const indexes: ManifestIndexDefinition[] = [];\n\n indexes.push({\n name: `${tableName}_id_idx`,\n columns: ['id'],\n });\n\n // Use custom conflict columns if provided, otherwise default to slug+context\n const conflictColumns = config?.conflictColumns || ['slug', 'context'];\n const indexName =\n conflictColumns.length > 2\n ? `${tableName}_${conflictColumns.slice(0, 2).join('_')}_idx`\n : `${tableName}_${conflictColumns.join('_')}_idx`;\n\n indexes.push({\n name: indexName,\n columns: conflictColumns,\n unique: true,\n });\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).\n this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);\n\n // Tenancy injects `tenant_id` but nothing indexes it (#2356). Added\n // before the schema definition is finalized so every consumer of the\n // structured `indexes` array (migrations, test databases, aggregation)\n // creates it. The cached `ddl` string below is CREATE TABLE only and is\n // not an executable representation of the table (#2358).\n this.ensureTenantIdIndex(indexes, columns, tableName);\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 * 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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"}
@@ -4,10 +4,13 @@
4
4
  export { SchemaCodeGenerator } from './code-generator.js';
5
5
  export * from './ddl/index.js';
6
6
  export { SchemaGenerator } from './generator.js';
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
+ export { type CollectedManifestTable, collectManifestTables, type ManifestColumnLike, type ManifestIndexLike, type ManifestSchemaLike, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, renderCollectedManifestTable, } from './manifest-schema.js';
7
9
  export { SchemaOverrideSystem } from './override-system.js';
8
10
  export type { AggregatedTable, AggregateOptions, AggregationResult, } from './schema-aggregator.js';
9
11
  export { SchemaAggregator } from './schema-aggregator.js';
10
12
  export { createSchemaManager, SchemaManager } from './schema-manager.js';
13
+ export { getSystemTableShapes, parseTableShapes, type SystemTableColumnShape, type SystemTableIndexShape, type SystemTableShape, } from './system-table-shapes.js';
11
14
  export type { ColumnDefinition, ForeignKeyDefinition, IndexDefinition, SchemaDefinition, SchemaManifest, SchemaMigration, SchemaOverride, TriggerDefinition, } from './types.js';
12
15
  export * from './types.js';
13
16
  //# 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;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,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;AAGzE,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,cAAc,YAAY,CAAC"}
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,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,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,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,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,cAAc,YAAY,CAAC"}
@@ -6,7 +6,10 @@ import { SQLiteStrategy } from "./ddl/sqlite-strategy.js";
6
6
  import { detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy } from "./ddl/index.js";
7
7
  import { SchemaGenerator } from "./generator.js";
8
8
  import { SchemaManager, createSchemaManager } from "./schema-manager.js";
9
+ import { collectManifestTables, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, renderCollectedManifestTable } from "./manifest-schema.js";
10
+ import { getSystemTableShapes, parseTableShapes } from "./system-table-shapes.js";
11
+ import { LiveSchemaParityError, checkLiveSchemaParity, normalizeSqlType, parseIndexDefColumns } from "./live-parity.js";
9
12
  import { SchemaCodeGenerator } from "./code-generator.js";
10
13
  import { SchemaOverrideSystem } from "./override-system.js";
11
14
  import { SchemaAggregator } from "./schema-aggregator.js";
12
- export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, PostgresStrategy, SQLiteStrategy, SchemaAggregator, SchemaCodeGenerator, SchemaGenerator, SchemaManager, SchemaOverrideSystem, createSchemaManager, detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy };
15
+ export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, LiveSchemaParityError, PostgresStrategy, SQLiteStrategy, SchemaAggregator, SchemaCodeGenerator, SchemaGenerator, SchemaManager, SchemaOverrideSystem, checkLiveSchemaParity, collectManifestTables, createSchemaManager, detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy, getSystemTableShapes, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, normalizeSqlType, parseIndexDefColumns, parseTableShapes, renderCollectedManifestTable };
@@ -0,0 +1,90 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ import { DatabaseEngine } from './ddl/types.js';
3
+ import { SchemaDefinition } from './types.js';
4
+ /** How badly a finding breaks the deployment. */
5
+ export type LiveParitySeverity = 'error' | 'warning' | 'info';
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';
8
+ /** One difference between the live database and the expected shape. */
9
+ export interface LiveParityFinding {
10
+ kind: LiveParityFindingKind;
11
+ severity: LiveParitySeverity;
12
+ /** Table the finding is about. */
13
+ table: string;
14
+ /** Column or index the finding is about, when it is narrower than a table. */
15
+ target?: string;
16
+ /** Whether the table is an application table or a `_smrt_*` system table. */
17
+ origin: ExpectedTableOrigin;
18
+ message: string;
19
+ recommendation: string;
20
+ details?: Record<string, unknown>;
21
+ }
22
+ /** Aggregate result of one parity run. */
23
+ export interface LiveSchemaParityReport {
24
+ engine: DatabaseEngine;
25
+ /** Expected tables that exist in the live database and were compared. */
26
+ tablesChecked: number;
27
+ /** Expected tables that are absent from the live database. */
28
+ tablesMissing: number;
29
+ /** Whether `_smrt_*` system tables participated. */
30
+ systemTablesIncluded: boolean;
31
+ /**
32
+ * `full` when index metadata (uniqueness, partial predicates, PostgreSQL
33
+ * validity) could be read for this engine; `unavailable` when it could not,
34
+ * in which case every index-class check is skipped rather than guessed.
35
+ */
36
+ indexIntrospection: 'full' | 'unavailable';
37
+ findings: LiveParityFinding[];
38
+ counts: Record<LiveParitySeverity, number>;
39
+ /** True when no `error`-severity finding was produced. */
40
+ ok: boolean;
41
+ }
42
+ /** A declared upsert conflict target for one table. */
43
+ export interface ConflictTargetInput {
44
+ columns: string[];
45
+ /** Where the target came from, e.g. a class name — used in messages. */
46
+ source?: string;
47
+ }
48
+ export interface LiveSchemaParityOptions {
49
+ db: DatabaseInterface;
50
+ /** Expected application table shapes, keyed by table name. */
51
+ schemas?: Record<string, SchemaDefinition>;
52
+ /** Declared upsert conflict targets, keyed by table name. */
53
+ conflictTargets?: Record<string, ConflictTargetInput[]>;
54
+ /** Include `_smrt_*` system tables (default true). */
55
+ includeSystemTables?: boolean;
56
+ /** Explicit engine hint for adapters whose URL is empty or ambiguous. */
57
+ engineHint?: string;
58
+ /** Report live tables no expected shape covers (default true). */
59
+ reportExtraTables?: boolean;
60
+ }
61
+ /** Whether an expected table is an application table or a system table. */
62
+ export type ExpectedTableOrigin = 'application' | 'system';
63
+ /**
64
+ * Thrown when the live database cannot be introspected. Callers fail closed:
65
+ * an unreadable database is never reported as "in sync".
66
+ */
67
+ export declare class LiveSchemaParityError extends Error {
68
+ constructor(message: string, options?: {
69
+ cause?: unknown;
70
+ });
71
+ }
72
+ /**
73
+ * Compare a live database against the expected shape and return every
74
+ * difference found.
75
+ *
76
+ * @throws {LiveSchemaParityError} when the database cannot be reached or the
77
+ * adapter cannot describe a table. Connection problems must fail closed.
78
+ */
79
+ export declare function checkLiveSchemaParity(options: LiveSchemaParityOptions): Promise<LiveSchemaParityReport>;
80
+ /** Fold engine-specific type spellings into comparable buckets. */
81
+ export declare function normalizeSqlType(type: string): string;
82
+ /**
83
+ * Extract the indexed column list from a `CREATE INDEX` definition.
84
+ *
85
+ * Expression components (`lower(name)`, `(meta ->> 'x')`) are kept verbatim so
86
+ * they never collide with a plain column of the same name; ordering is
87
+ * preserved because leading-column coverage is what the policy checks.
88
+ */
89
+ export declare function parseIndexDefColumns(indexDef: string): string[];
90
+ //# sourceMappingURL=live-parity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-parity.d.ts","sourceRoot":"","sources":["../../src/schema/live-parity.ts"],"names":[],"mappings":"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"}