@happyvertical/smrt-core 0.40.70 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -3
- package/agents/change-feed.md +1 -1
- package/agents/schema-paths.md +561 -32
- package/dist/browser.js +5 -4
- package/dist/cascade.d.ts +120 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +430 -0
- package/dist/cascade.js.map +1 -0
- package/dist/change-feed.d.ts +34 -2
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +54 -13
- package/dist/change-feed.js.map +1 -1
- package/dist/change-signals.d.ts.map +1 -1
- package/dist/change-signals.js +8 -1
- package/dist/change-signals.js.map +1 -1
- package/dist/class.d.ts +36 -3
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +69 -14
- package/dist/class.js.map +1 -1
- package/dist/collection-cache.js +0 -0
- package/dist/collection-cache.js.map +1 -1
- package/dist/collection.d.ts +29 -0
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +68 -16
- package/dist/collection.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/decorators/index.d.ts +63 -5
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +37 -5
- package/dist/decorators/index.js.map +1 -1
- package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
- package/dist/dispatch/collections/Dispatches.js +21 -6
- package/dist/dispatch/collections/Dispatches.js.map +1 -1
- package/dist/dispatch/models/Dispatch.d.ts +1 -1
- package/dist/dispatch/models/Dispatch.d.ts.map +1 -1
- package/dist/dispatch/models/Dispatch.js +3 -2
- package/dist/dispatch/models/Dispatch.js.map +1 -1
- package/dist/dispatch/models/DispatchSubscription.d.ts +1 -1
- package/dist/dispatch/models/DispatchSubscription.d.ts.map +1 -1
- package/dist/dispatch/models/DispatchSubscription.js +2 -1
- package/dist/dispatch/models/DispatchSubscription.js.map +1 -1
- package/dist/dispatch/types.d.ts +5 -0
- package/dist/dispatch/types.d.ts.map +1 -1
- package/dist/embedded-write-queue.d.ts +46 -0
- package/dist/embedded-write-queue.d.ts.map +1 -0
- package/dist/embedded-write-queue.js +66 -0
- package/dist/embedded-write-queue.js.map +1 -0
- package/dist/embeddings/storage.d.ts +7 -0
- package/dist/embeddings/storage.d.ts.map +1 -1
- package/dist/embeddings/storage.js +31 -13
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/hierarchical.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/interceptors.d.ts +21 -0
- package/dist/interceptors.d.ts.map +1 -1
- package/dist/interceptors.js +27 -1
- package/dist/interceptors.js.map +1 -1
- package/dist/learning/memory.d.ts.map +1 -1
- package/dist/learning/memory.js +5 -4
- package/dist/learning/memory.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +4 -7
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/static-manifest.js +10 -10
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +19 -19
- package/dist/migrations/differ.d.ts +9 -1
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +12 -7
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -1
- package/dist/migrations/integer-width.d.ts +99 -0
- package/dist/migrations/integer-width.d.ts.map +1 -0
- package/dist/migrations/integer-width.js +237 -0
- package/dist/migrations/integer-width.js.map +1 -0
- package/dist/migrations/minor-units.d.ts +162 -0
- package/dist/migrations/minor-units.d.ts.map +1 -0
- package/dist/migrations/minor-units.js +383 -0
- package/dist/migrations/minor-units.js.map +1 -0
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +28 -20
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations.js +3 -1
- package/dist/object.d.ts +98 -5
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +138 -30
- package/dist/object.js.map +1 -1
- package/dist/query-bounds.d.ts +5 -1
- package/dist/query-bounds.d.ts.map +1 -1
- package/dist/query-bounds.js +5 -1
- package/dist/query-bounds.js.map +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +3 -1
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/manifest-field-merge.d.ts +12 -0
- package/dist/registry/manifest-field-merge.d.ts.map +1 -1
- package/dist/registry/manifest-field-merge.js +14 -2
- package/dist/registry/manifest-field-merge.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +22 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +205 -165
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +3 -2
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +41 -46
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +61 -83
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +45 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +92 -28
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/schema/conflict-target.d.ts +104 -0
- package/dist/schema/conflict-target.d.ts.map +1 -0
- package/dist/schema/conflict-target.js +129 -0
- package/dist/schema/conflict-target.js.map +1 -0
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +3 -1
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +14 -1
- package/dist/schema/ddl/postgres-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +245 -41
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +397 -241
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index-utils.d.ts +120 -0
- package/dist/schema/index-utils.d.ts.map +1 -1
- package/dist/schema/index-utils.js +242 -1
- package/dist/schema/index-utils.js.map +1 -1
- package/dist/schema/index.d.ts +2 -3
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -3
- package/dist/schema/live-parity.d.ts +3 -1
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +23 -2
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/schema/system-table-shapes.d.ts +13 -0
- package/dist/schema/system-table-shapes.d.ts.map +1 -1
- package/dist/schema/system-table-shapes.js +14 -1
- package/dist/schema/system-table-shapes.js.map +1 -1
- package/dist/schema/types.d.ts +17 -10
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +1 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +3 -3
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +2 -3
- package/dist/smrt-knowledge.json +9 -9
- package/dist/system/compatibility.d.ts +42 -0
- package/dist/system/compatibility.d.ts.map +1 -1
- package/dist/system/compatibility.js +182 -9
- package/dist/system/compatibility.js.map +1 -1
- package/dist/system/index.d.ts +1 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +3 -2
- package/dist/system/retention.d.ts +237 -0
- package/dist/system/retention.d.ts.map +1 -0
- package/dist/system/retention.js +497 -0
- package/dist/system/retention.js.map +1 -0
- package/dist/system/schema.d.ts +102 -16
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +85 -47
- package/dist/system/schema.js.map +1 -1
- package/dist/system/types.d.ts +0 -2
- package/dist/system/types.d.ts.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/utils/safe-integer.d.ts +19 -0
- package/dist/utils/safe-integer.d.ts.map +1 -0
- package/dist/utils/safe-integer.js +31 -0
- package/dist/utils/safe-integer.js.map +1 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -3
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +4 -62
- package/dist/vite-plugin/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/schema/override-system.d.ts +0 -43
- package/dist/schema/override-system.d.ts.map +0 -1
- package/dist/schema/override-system.js +0 -205
- package/dist/schema/override-system.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-parity.js","names":[],"sources":["../../src/schema/live-parity.ts"],"sourcesContent":["/**\n * Live-schema parity check (#2368)\n *\n * Compares a **live** database against the shape the model layer assumes.\n * Three inputs define \"expected\":\n *\n * 1. **Declared table shapes** — the manifest schemas the caller passes in.\n * These give tables, columns, types, nullability, and declared indexes.\n * 2. **Hand-DDL system tables** — `_smrt_*` tables parsed out of\n * `src/system/schema.ts` (see `system-table-shapes.ts`). They never enter\n * the manifest, so nothing else has ever diffed them.\n * 3. **An expected-shape index policy that does not consult the manifest** —\n * every foreign-key / cross-package-ref / tenant column carries a leading\n * index, every declared upsert conflict target is backed by a matching\n * UNIQUE index, and every `unique: true` column is actually unique in the\n * live database.\n *\n * Point 3 is the reason this module exists rather than reusing the migration\n * differ: the differ compares the live database to the manifest, i.e. to the\n * artifact that dropped the index in the first place, which is why a database\n * missing 164 tenant-column indexes reported \"in sync\" (#2356).\n *\n * This module is read-only. It emits findings; it never repairs.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { detectEngine, getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { getSystemTableShapes } from './system-table-shapes.js';\nimport type { ColumnDefinition, SchemaDefinition } from './types.js';\n\n/** How badly a finding breaks the deployment. */\nexport type LiveParitySeverity = 'error' | 'warning' | 'info';\n\n/** The class of drift a finding describes. */\nexport type LiveParityFindingKind =\n | 'missing_table'\n | 'extra_table'\n | 'missing_column'\n | 'extra_column'\n | 'column_type_drift'\n | 'column_nullability_drift'\n | 'missing_index'\n | 'extra_index'\n | 'unindexed_reference'\n | 'conflict_target_unindexed'\n | 'conflict_target_not_unique'\n | 'unique_constraint_missing'\n | 'invalid_index';\n\n/** One difference between the live database and the expected shape. */\nexport interface LiveParityFinding {\n kind: LiveParityFindingKind;\n severity: LiveParitySeverity;\n /** Table the finding is about. */\n table: string;\n /** Column or index the finding is about, when it is narrower than a table. */\n target?: string;\n /** Whether the table is an application table or a `_smrt_*` system table. */\n origin: ExpectedTableOrigin;\n message: string;\n recommendation: string;\n details?: Record<string, unknown>;\n}\n\n/** Aggregate result of one parity run. */\nexport interface LiveSchemaParityReport {\n engine: DatabaseEngine;\n /** Expected tables that exist in the live database and were compared. */\n tablesChecked: number;\n /** Expected tables that are absent from the live database. */\n tablesMissing: number;\n /** Whether `_smrt_*` system tables participated. */\n systemTablesIncluded: boolean;\n /**\n * `full` when index metadata (uniqueness, partial predicates, PostgreSQL\n * validity) could be read for this engine; `unavailable` when it could not,\n * in which case every index-class check is skipped rather than guessed.\n */\n indexIntrospection: 'full' | 'unavailable';\n findings: LiveParityFinding[];\n counts: Record<LiveParitySeverity, number>;\n /** True when no `error`-severity finding was produced. */\n ok: boolean;\n}\n\n/** A declared upsert conflict target for one table. */\nexport interface ConflictTargetInput {\n columns: string[];\n /** Where the target came from, e.g. a class name — used in messages. */\n source?: string;\n}\n\nexport interface LiveSchemaParityOptions {\n db: DatabaseInterface;\n /** Expected application table shapes, keyed by table name. */\n schemas?: Record<string, SchemaDefinition>;\n /** Declared upsert conflict targets, keyed by table name. */\n conflictTargets?: Record<string, ConflictTargetInput[]>;\n /** Include `_smrt_*` system tables (default true). */\n includeSystemTables?: boolean;\n /** Explicit engine hint for adapters whose URL is empty or ambiguous. */\n engineHint?: string;\n /** Report live tables no expected shape covers (default true). */\n reportExtraTables?: boolean;\n}\n\n/** Whether an expected table is an application table or a system table. */\nexport type ExpectedTableOrigin = 'application' | 'system';\n\ninterface ExpectedColumn {\n name: string;\n /** Engine-materialized type text. */\n type: string;\n primaryKey: boolean;\n notNull: boolean;\n unique: boolean;\n hasDefault: boolean;\n reference?: ColumnDefinition['referenceKind'];\n}\n\ninterface ExpectedIndex {\n name: string;\n columns: string[];\n unique: boolean;\n}\n\ninterface ExpectedTable {\n name: string;\n origin: ExpectedTableOrigin;\n columns: ExpectedColumn[];\n indexes: ExpectedIndex[];\n conflictTargets: ConflictTargetInput[];\n}\n\ninterface LiveColumn {\n name: string;\n type: string;\n notNull: boolean;\n primaryKey: boolean;\n hasDefault: boolean;\n}\n\ninterface LiveIndex {\n name: string;\n columns: string[];\n unique: boolean;\n /** PostgreSQL `indisvalid`; always true on engines without the concept. */\n valid: boolean;\n /** True for the index backing a primary key. */\n primary: boolean;\n /** True when the index is partial (has a WHERE predicate). */\n partial: boolean;\n}\n\n/**\n * Thrown when the live database cannot be introspected. Callers fail closed:\n * an unreadable database is never reported as \"in sync\".\n */\nexport class LiveSchemaParityError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'LiveSchemaParityError';\n }\n}\n\n/**\n * Compare a live database against the expected shape and return every\n * difference found.\n *\n * @throws {LiveSchemaParityError} when the database cannot be reached or the\n * adapter cannot describe a table. Connection problems must fail closed.\n */\nexport async function checkLiveSchemaParity(\n options: LiveSchemaParityOptions,\n): Promise<LiveSchemaParityReport> {\n const {\n db,\n schemas = {},\n conflictTargets = {},\n includeSystemTables = true,\n engineHint,\n reportExtraTables = true,\n } = options;\n\n const engine = resolveEngine(db, engineHint);\n const expected = buildExpectedTables({\n schemas,\n conflictTargets,\n includeSystemTables,\n engine,\n });\n\n const liveTableNames = await listLiveTables(db, engine);\n const indexCatalog = await readIndexCatalog(db, engine);\n const findings: LiveParityFinding[] = [];\n\n let tablesChecked = 0;\n let tablesMissing = 0;\n\n for (const table of expected) {\n if (!liveTableNames.has(table.name)) {\n tablesMissing++;\n findings.push({\n kind: 'missing_table',\n severity: 'error',\n table: table.name,\n origin: table.origin,\n message: `Table \\`${table.name}\\` is declared but does not exist in the live database.`,\n recommendation:\n table.origin === 'system'\n ? 'Initialize SMRT system tables against this database (they are created on framework bootstrap).'\n : 'Run `smrt db:migrate` to create the missing table.',\n });\n continue;\n }\n\n tablesChecked++;\n const liveColumns = await readLiveColumns(db, table.name);\n findings.push(...compareColumns(table, liveColumns, engine));\n\n if (indexCatalog) {\n const liveIndexes = await indexCatalog.forTable(table.name);\n findings.push(...compareIndexes(table, liveColumns, liveIndexes));\n }\n }\n\n if (reportExtraTables) {\n const expectedNames = new Set(expected.map((table) => table.name));\n for (const liveName of liveTableNames) {\n if (expectedNames.has(liveName)) continue;\n if (isInternalTableName(liveName)) continue;\n // Without system tables in scope, every `_smrt_*` table would be\n // reported as unexplained; that is a scoping artifact, not drift.\n if (!includeSystemTables && liveName.startsWith('_smrt_')) continue;\n findings.push({\n kind: 'extra_table',\n severity: 'info',\n table: liveName,\n origin: liveName.startsWith('_smrt_') ? 'system' : 'application',\n message: `Live table \\`${liveName}\\` is not covered by any declared schema.`,\n recommendation:\n 'Confirm the table belongs to another application sharing this database, or remove it once its owning model is gone.',\n });\n }\n }\n\n const counts: Record<LiveParitySeverity, number> = {\n error: 0,\n warning: 0,\n info: 0,\n };\n for (const finding of findings) {\n counts[finding.severity]++;\n }\n\n return {\n engine,\n tablesChecked,\n tablesMissing,\n systemTablesIncluded: includeSystemTables,\n indexIntrospection: indexCatalog ? 'full' : 'unavailable',\n findings,\n counts,\n ok: counts.error === 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Expected shape\n// ---------------------------------------------------------------------------\n\nfunction buildExpectedTables(input: {\n schemas: Record<string, SchemaDefinition>;\n conflictTargets: Record<string, ConflictTargetInput[]>;\n includeSystemTables: boolean;\n engine: DatabaseEngine;\n}): ExpectedTable[] {\n const { schemas, conflictTargets, includeSystemTables, engine } = input;\n const strategy = getDDLStrategy(engine);\n const tables: ExpectedTable[] = [];\n\n for (const [key, schema] of Object.entries(schemas)) {\n const tableName = schema?.tableName || key;\n if (!tableName) continue;\n\n const columns: ExpectedColumn[] = Object.entries(schema.columns ?? {}).map(\n ([columnName, column]) => ({\n name: columnName,\n type: strategy.mapType(column.type),\n primaryKey: column.primaryKey === true,\n notNull: column.notNull === true || column.primaryKey === true,\n unique: column.unique === true,\n hasDefault: column.defaultValue !== undefined,\n reference: column.foreignKey ? 'foreignKey' : column.referenceKind,\n }),\n );\n\n tables.push({\n name: tableName,\n origin: 'application',\n columns,\n indexes: (schema.indexes ?? [])\n // Expression indexes (`@meta({ indexed: true })`) surface with empty\n // or null column lists in introspection, so a column-based comparison\n // can only produce false drift for them.\n .filter((index) => !index.jsonPath && (index.columns?.length ?? 0) > 0)\n .map((index) => ({\n name: index.name,\n columns: index.columns,\n unique: index.unique === true,\n })),\n conflictTargets: dedupeConflictTargets(conflictTargets[tableName] ?? []),\n });\n }\n\n if (includeSystemTables) {\n for (const shape of getSystemTableShapes(engine).values()) {\n tables.push({\n name: shape.tableName,\n origin: 'system',\n columns: shape.columns.map((column) => ({\n name: column.name,\n type: column.type,\n primaryKey: column.primaryKey,\n notNull: column.notNull || column.primaryKey,\n unique: column.unique,\n hasDefault: column.hasDefault,\n })),\n indexes: shape.indexes.map((index) => ({\n name: index.name,\n columns: index.columns,\n unique: index.unique,\n })),\n // Inline `UNIQUE (...)` constraints are what the framework upserts\n // against on system tables, so they are the conflict targets here.\n conflictTargets: dedupeConflictTargets(\n shape.uniqueConstraints.map((columns) => ({\n columns,\n source: `${shape.tableName} DDL`,\n })),\n ),\n });\n }\n }\n\n return tables;\n}\n\nfunction dedupeConflictTargets(\n targets: ConflictTargetInput[],\n): ConflictTargetInput[] {\n const seen = new Map<string, ConflictTargetInput>();\n for (const target of targets) {\n const columns = (target.columns ?? []).filter(Boolean);\n if (columns.length === 0) continue;\n const key = [...columns].sort().join(',');\n if (!seen.has(key)) {\n seen.set(key, { columns, source: target.source });\n }\n }\n return [...seen.values()];\n}\n\n// ---------------------------------------------------------------------------\n// Column comparison\n// ---------------------------------------------------------------------------\n\nfunction compareColumns(\n table: ExpectedTable,\n liveColumns: Map<string, LiveColumn>,\n engine: DatabaseEngine,\n): LiveParityFinding[] {\n const findings: LiveParityFinding[] = [];\n const expectedNames = new Set(table.columns.map((column) => column.name));\n\n for (const column of table.columns) {\n const live = liveColumns.get(column.name);\n if (!live) {\n findings.push({\n kind: 'missing_column',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared but missing from the live database.`,\n recommendation:\n 'Run `smrt db:migrate` to add the column before application writes reach this table.',\n details: { expectedType: column.type },\n });\n continue;\n }\n\n if (!typesAreEquivalent(column, live, engine)) {\n findings.push({\n kind: 'column_type_drift',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is \\`${live.type}\\` in the live database but declared \\`${column.type}\\`.`,\n recommendation:\n 'Reconcile the column type with `smrt db:migrate`, or repair the declaration if the live type is correct.',\n details: { expected: column.type, actual: live.type },\n });\n }\n\n // A declared-NOT NULL column that is nullable live silently accepts rows\n // the model layer believes cannot exist; the reverse breaks every insert\n // that omits the column, which is the worse failure.\n if (!column.primaryKey && column.notNull && !live.notNull) {\n findings.push({\n kind: 'column_nullability_drift',\n severity: 'warning',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared NOT NULL but is nullable in the live database.`,\n recommendation:\n 'Backfill any NULL values and add the NOT NULL constraint, or relax the declaration.',\n });\n } else if (!column.notNull && live.notNull && !live.hasDefault) {\n findings.push({\n kind: 'column_nullability_drift',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is NOT NULL in the live database with no default, but is not declared NOT NULL. Inserts that omit it will fail.`,\n recommendation:\n 'Drop the orphan NOT NULL constraint, give the column a default, or declare the column required.',\n });\n }\n }\n\n for (const live of liveColumns.values()) {\n if (expectedNames.has(live.name)) continue;\n\n const breaksInserts = live.notNull && !live.hasDefault;\n findings.push({\n kind: 'extra_column',\n severity: breaksInserts ? 'error' : 'info',\n table: table.name,\n target: live.name,\n origin: table.origin,\n message: breaksInserts\n ? `Live column \\`${table.name}.${live.name}\\` is undeclared, NOT NULL, and has no default — every framework insert into this table will fail.`\n : `Live column \\`${table.name}.${live.name}\\` is not declared by the current schema.`,\n recommendation: breaksInserts\n ? 'Drop the orphan column or give it a default; this is the residue of a renamed or removed required field.'\n : 'Confirm the column is intentional legacy data, or drop it once nothing reads it.',\n details: { type: live.type },\n });\n }\n\n return findings;\n}\n\n/**\n * Whether a live column type satisfies its declaration.\n *\n * Two tolerances match the model layer's actual conventions, and mirror the\n * migration differ so the two tools never disagree:\n *\n * - `TEXT` ↔ `UUID` for structural identifier/reference columns, because\n * SQLite has no uuid type and older PostgreSQL deployments legitimately\n * still carry text ids (R11).\n * - `TEXT` ↔ `JSON`/`JSONB` in both directions, because SMRT serializes JSON\n * values into text columns and a native json column already holds exactly\n * that (#1335).\n */\nfunction typesAreEquivalent(\n expected: ExpectedColumn,\n live: LiveColumn,\n engine: DatabaseEngine,\n): boolean {\n const expectedType = normalizeSqlType(expected.type);\n const actualType = normalizeSqlType(live.type);\n if (expectedType === actualType) return true;\n\n const uuidTextPair =\n (expectedType === 'UUID' && actualType === 'TEXT') ||\n (expectedType === 'TEXT' && actualType === 'UUID');\n if (uuidTextPair && isStructuralReference(expected)) {\n return true;\n }\n\n const jsonTextPair =\n (expectedType === 'JSON' && actualType === 'TEXT') ||\n (expectedType === 'TEXT' && actualType === 'JSON');\n if (jsonTextPair) return true;\n\n // SQLite has dynamic typing: BOOLEAN/TIMESTAMP columns are declared with\n // affinities that pragma reports verbatim, and INTEGER/NUMERIC storage is\n // interchangeable for the values SMRT writes.\n if (engine === 'sqlite') {\n const numericPair =\n (expectedType === 'BOOLEAN' && actualType === 'INTEGER') ||\n (expectedType === 'INTEGER' && actualType === 'BOOLEAN');\n if (numericPair) return true;\n }\n\n return false;\n}\n\nfunction isStructuralReference(column: ExpectedColumn): boolean {\n return (\n column.primaryKey ||\n column.reference === 'id' ||\n column.reference === 'foreignKey' ||\n column.reference === 'crossPackageRef' ||\n column.reference === 'tenantId'\n );\n}\n\n/** Fold engine-specific type spellings into comparable buckets. */\nexport function normalizeSqlType(type: string): string {\n const upper = String(type ?? '')\n .toUpperCase()\n .trim()\n .replace(/\\(\\s*[^)]*\\s*\\)/g, '')\n .replace(/\\[\\]$/, '')\n .trim();\n\n if (\n /^(INTEGER|INT|INT2|INT4|INT8|BIGINT|SMALLINT|TINYINT|SERIAL|BIGSERIAL)$/.test(\n upper,\n )\n )\n return 'INTEGER';\n if (\n /^(TEXT|CLOB|STRING|VARCHAR|CHAR|CHARACTER|CHARACTER VARYING|NAME)$/.test(\n upper,\n )\n )\n return 'TEXT';\n if (upper === 'UUID') return 'UUID';\n if (\n /^(REAL|FLOAT|FLOAT4|FLOAT8|DOUBLE|DOUBLE PRECISION|DECIMAL|NUMERIC|NUMBER)$/.test(\n upper,\n )\n )\n return 'REAL';\n if (/^(BOOLEAN|BOOL)$/.test(upper)) return 'BOOLEAN';\n if (/^(TIMESTAMPTZ|TIMESTAMP WITH TIME ZONE)$/.test(upper))\n return 'TIMESTAMPTZ';\n if (\n /^(DATETIME|TIMESTAMP|TIMESTAMP WITHOUT TIME ZONE|DATE|TIME)$/.test(upper)\n )\n return 'TIMESTAMP';\n if (/^(BLOB|BINARY|BYTEA)$/.test(upper)) return 'BLOB';\n if (/^(JSON|JSONB)$/.test(upper)) return 'JSON';\n\n return upper;\n}\n\n// ---------------------------------------------------------------------------\n// Index comparison — the manifest-independent policy lives here\n// ---------------------------------------------------------------------------\n\nfunction compareIndexes(\n table: ExpectedTable,\n liveColumns: Map<string, LiveColumn>,\n liveIndexes: LiveIndex[],\n): LiveParityFinding[] {\n const findings: LiveParityFinding[] = [];\n const byName = new Map(liveIndexes.map((index) => [index.name, index]));\n const signatures = new Set(\n liveIndexes.map((index) => indexSignature(index.columns, index.unique)),\n );\n\n for (const index of liveIndexes) {\n if (index.valid) continue;\n findings.push({\n kind: 'invalid_index',\n severity: 'error',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: `Index \\`${index.name}\\` on \\`${table.name}\\` is INVALID — PostgreSQL will not use it and a unique index in this state enforces nothing.`,\n recommendation:\n 'Drop and rebuild it (`REINDEX INDEX CONCURRENTLY`), then re-run the parity check.',\n });\n }\n\n // 1. Declared indexes the live database does not carry in that shape.\n // Matching is by column/uniqueness signature rather than by name so an\n // equivalent index under a different name is not reported as missing\n // (#741), and so a same-name index whose uniqueness or columns drifted\n // still is.\n for (const index of table.indexes) {\n if (signatures.has(indexSignature(index.columns, index.unique))) continue;\n // Conflict-target coverage is reported below with a message that explains\n // the actual consequence; do not report the same index twice.\n if (\n table.conflictTargets.some((target) =>\n sameColumnSet(index.columns, target.columns),\n )\n ) {\n continue;\n }\n\n const sameName = byName.get(index.name);\n findings.push({\n kind: 'missing_index',\n severity: 'warning',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: sameName\n ? `Index \\`${index.name}\\` on \\`${table.name}\\` exists but its shape drifted: declared (${index.columns.join(', ')})${index.unique ? ' UNIQUE' : ''}, live (${sameName.columns.join(', ')})${sameName.unique ? ' UNIQUE' : ''}.`\n : `Declared index \\`${index.name}\\` on \\`${table.name}\\` (${index.columns.join(', ')}) is missing from the live database.`,\n recommendation:\n table.origin === 'system'\n ? 'Re-run SMRT system-table initialization to create the missing system index.'\n : 'Run `smrt db:migrate` to create the missing index.',\n });\n }\n\n // 2. Conflict targets. An upsert whose conflict target has no matching\n // UNIQUE index either errors outright (PostgreSQL) or silently inserts\n // duplicates, so this is the one index class that is an error.\n for (const target of table.conflictTargets) {\n if (target.columns.some((column) => !liveColumns.has(column))) {\n // A missing column is already reported; do not double-report it here.\n continue;\n }\n\n const candidates = liveIndexes.filter((index) =>\n sameColumnSet(index.columns, target.columns),\n );\n // A *partial* unique index cannot arbitrate a plain `ON CONFLICT (cols)`:\n // PostgreSQL only infers an index whose predicate the statement repeats,\n // and on other engines it simply does not constrain the excluded rows.\n if (candidates.some((index) => index.unique && !index.partial)) {\n continue;\n }\n\n const partialUnique = candidates.find(\n (index) => index.unique && index.partial,\n );\n const nonUnique = candidates.find((index) => !index.unique);\n const columnList = target.columns.join(', ');\n const suffix = target.source ? ` (declared by ${target.source})` : '';\n\n if (nonUnique) {\n findings.push({\n kind: 'conflict_target_not_unique',\n severity: 'error',\n table: table.name,\n target: nonUnique.name,\n origin: table.origin,\n message: `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} is backed by \\`${nonUnique.name}\\`, which is not UNIQUE.`,\n recommendation:\n 'Recreate the index as UNIQUE (`smrt db:migrate`); until then upserts insert duplicates instead of updating.',\n });\n continue;\n }\n\n findings.push({\n kind: 'conflict_target_unindexed',\n severity: 'error',\n table: table.name,\n target: partialUnique ? partialUnique.name : columnList,\n origin: table.origin,\n message: partialUnique\n ? `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} is backed only by the partial index \\`${partialUnique.name}\\`, which cannot arbitrate the upsert.`\n : `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} has no matching UNIQUE index in the live database.`,\n recommendation:\n 'Create a full UNIQUE index over exactly these columns (`smrt db:migrate`); PostgreSQL rejects the upsert outright and other engines duplicate rows.',\n });\n }\n\n // 3. Columns declared unique that are not actually unique live.\n for (const column of table.columns) {\n if (!column.unique) continue;\n if (!liveColumns.has(column.name)) continue;\n // A partial unique index constrains only the rows its predicate selects,\n // so it does not make the column unique as declared.\n const unique = liveIndexes.some(\n (index) =>\n index.unique &&\n !index.partial &&\n sameColumnSet(index.columns, [column.name]),\n );\n if (unique) continue;\n\n findings.push({\n kind: 'unique_constraint_missing',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared unique but no UNIQUE index enforces it in the live database.`,\n recommendation:\n 'De-duplicate existing values and create the UNIQUE index; the declaration is currently decorative.',\n });\n }\n\n // 4. Manifest-independent coverage policy: every foreign-key, cross-package\n // reference and tenant column needs an index it *leads*, or every lookup\n // and every tenant-scoped list is a full scan (#2356).\n for (const column of table.columns) {\n if (!column.reference) continue;\n if (column.reference === 'id') continue;\n if (!liveColumns.has(column.name)) continue;\n\n const covered = liveIndexes.some(\n (index) => index.columns[0] === column.name,\n );\n if (covered) continue;\n\n findings.push({\n kind: 'unindexed_reference',\n severity: 'warning',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Reference column \\`${table.name}.${column.name}\\` (${column.reference}) leads no index in the live database.`,\n recommendation:\n 'Add an index led by this column; relationship loads, joins and tenant-scoped reads currently scan the whole table.',\n details: { referenceKind: column.reference },\n });\n }\n\n // 5. Live indexes nothing declares. Informational: an operator may have\n // hand-added them, and nothing will recreate them after a rebuild.\n // Indexes that exist because rule 4 demands them are explained, even\n // though the manifest does not declare them.\n const policyLeadColumns = new Set(\n table.columns\n .filter((column) => column.reference && column.reference !== 'id')\n .map((column) => column.name),\n );\n const declaredNames = new Set(table.indexes.map((index) => index.name));\n const declaredSignatures = new Set(\n table.indexes.map((index) => indexSignature(index.columns, index.unique)),\n );\n for (const index of liveIndexes) {\n if (index.primary) continue;\n // Constraint-owned indexes (`sqlite_autoindex_*`, PostgreSQL `*_key` /\n // `*_pkey`) exist because of a table constraint, not because anyone\n // declared an index; reporting them as undeclared is pure noise.\n if (isConstraintOwnedIndexName(index.name)) continue;\n if (policyLeadColumns.has(index.columns[0])) continue;\n if (declaredNames.has(index.name)) continue;\n if (declaredSignatures.has(indexSignature(index.columns, index.unique)))\n continue;\n if (\n table.conflictTargets.some((target) =>\n sameColumnSet(index.columns, target.columns),\n )\n ) {\n continue;\n }\n\n findings.push({\n kind: 'extra_index',\n severity: 'info',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: `Live index \\`${index.name}\\` on \\`${table.name}\\` (${index.columns.join(', ') || 'expression'}) is not declared by the current schema.`,\n recommendation:\n 'Declare it so a rebuilt database keeps it, or drop it if it is obsolete.',\n details: { unique: index.unique, partial: index.partial },\n });\n }\n\n return findings;\n}\n\nfunction isConstraintOwnedIndexName(name: string): boolean {\n return (\n name.startsWith('sqlite_autoindex_') ||\n name.endsWith('_pkey') ||\n name.endsWith('_key')\n );\n}\n\nfunction indexSignature(columns: string[], unique: boolean): string {\n return `${columns.join(',')}:${unique}`;\n}\n\n/**\n * Conflict targets and unique constraints are order-insensitive: PostgreSQL\n * matches `ON CONFLICT (a, b)` against a unique index on `(b, a)`.\n */\nfunction sameColumnSet(left: string[], right: string[]): boolean {\n if (left.length !== right.length) return false;\n const sortedLeft = [...left].sort();\n const sortedRight = [...right].sort();\n return sortedLeft.every((column, index) => column === sortedRight[index]);\n}\n\n// ---------------------------------------------------------------------------\n// Live introspection\n// ---------------------------------------------------------------------------\n\nfunction resolveEngine(\n db: DatabaseInterface,\n engineHint?: string,\n): DatabaseEngine {\n if (typeof (db as { exportTable?: unknown }).exportTable === 'function') {\n return 'json';\n }\n const dbWithConfig = db as DatabaseInterface & { config?: { url?: string } };\n return detectEngine(db.url || dbWithConfig.config?.url || '', engineHint);\n}\n\nfunction isInternalTableName(name: string): boolean {\n return (\n name.startsWith('sqlite_') ||\n name.startsWith('pg_') ||\n name.startsWith('duckdb_') ||\n name === 'information_schema'\n );\n}\n\nasync function listLiveTables(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<Set<string>> {\n const sql =\n engine === 'postgres'\n ? `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`\n : `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;\n\n try {\n const result = await db.query(sql);\n const rows = (result?.rows ?? []) as {\n name?: string;\n table_name?: string;\n }[];\n return new Set(\n rows.map((row) => row.name || row.table_name || '').filter(Boolean),\n );\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not list tables in the live database: ${describeError(error)}`,\n { cause: error },\n );\n }\n}\n\nasync function readLiveColumns(\n db: DatabaseInterface,\n tableName: string,\n): Promise<Map<string, LiveColumn>> {\n if (typeof db.getTableSchema !== 'function') {\n throw new LiveSchemaParityError(\n 'The configured database adapter cannot describe tables (`getTableSchema` is unavailable), so live-schema parity cannot be verified.',\n );\n }\n\n let schema: Awaited<\n ReturnType<NonNullable<DatabaseInterface['getTableSchema']>>\n >;\n try {\n schema = await db.getTableSchema(tableName);\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read the live schema of \\`${tableName}\\`: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const columns = new Map<string, LiveColumn>();\n for (const [name, column] of Object.entries(schema?.columns ?? {})) {\n columns.set(name, {\n name,\n type: String(column?.type ?? ''),\n notNull: column?.notNull === true,\n primaryKey: column?.primaryKey === true,\n hasDefault:\n column?.defaultValue !== undefined && column?.defaultValue !== null,\n });\n }\n return columns;\n}\n\ninterface IndexCatalog {\n forTable(tableName: string): Promise<LiveIndex[]>;\n}\n\n/**\n * Build an index reader for the engine, or `null` when index metadata cannot\n * be read. A null catalog disables every index-class check rather than\n * reporting indexes as missing on an engine we cannot introspect.\n */\nasync function readIndexCatalog(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<IndexCatalog | null> {\n if (engine === 'postgres') {\n return readPostgresIndexCatalog(db);\n }\n if (engine === 'sqlite') {\n return { forTable: (tableName) => readSqliteIndexes(db, tableName) };\n }\n return readDuckDbIndexCatalog(db);\n}\n\nasync function readPostgresIndexCatalog(\n db: DatabaseInterface,\n): Promise<IndexCatalog> {\n // One catalog pass for the whole schema: `pg_index` carries uniqueness,\n // primary-key ownership and — unlike `pg_indexes` — `indisvalid`, which is\n // the only way to see an index left INVALID by a failed CONCURRENTLY build.\n const sql = `\n SELECT t.relname AS table_name,\n c.relname AS index_name,\n i.indisunique AS is_unique,\n i.indisprimary AS is_primary,\n i.indisvalid AS is_valid,\n (i.indpred IS NOT NULL) AS is_partial,\n pg_get_indexdef(i.indexrelid) AS index_def\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indexrelid\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE n.nspname = 'public'`;\n\n let rows: Record<string, unknown>[];\n try {\n const result = await db.query(sql);\n rows = (result?.rows ?? []) as Record<string, unknown>[];\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read the PostgreSQL index catalog: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const byTable = new Map<string, LiveIndex[]>();\n for (const row of rows) {\n const tableName = String(row.table_name ?? '');\n if (!tableName) continue;\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: String(row.index_name ?? ''),\n columns: parseIndexDefColumns(String(row.index_def ?? '')),\n unique: toBoolean(row.is_unique),\n primary: toBoolean(row.is_primary),\n valid: row.is_valid === undefined ? true : toBoolean(row.is_valid),\n partial: toBoolean(row.is_partial),\n });\n byTable.set(tableName, bucket);\n }\n\n return { forTable: async (tableName) => byTable.get(tableName) ?? [] };\n}\n\n/**\n * Extract the indexed column list from a `CREATE INDEX` definition.\n *\n * Expression components (`lower(name)`, `(meta ->> 'x')`) are kept verbatim so\n * they never collide with a plain column of the same name; ordering is\n * preserved because leading-column coverage is what the policy checks.\n */\nexport function parseIndexDefColumns(indexDef: string): string[] {\n const open = indexDef.indexOf('(');\n if (open === -1) return [];\n\n let depth = 0;\n let end = -1;\n for (let i = open; i < indexDef.length; i++) {\n if (indexDef[i] === '(') depth++;\n else if (indexDef[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end === -1) return [];\n\n const body = indexDef.slice(open + 1, end);\n const parts: string[] = [];\n let current = '';\n depth = 0;\n for (const char of body) {\n if (char === '(') depth++;\n if (char === ')') depth--;\n if (char === ',' && depth === 0) {\n parts.push(current);\n current = '';\n continue;\n }\n current += char;\n }\n parts.push(current);\n\n return parts\n .map((part) =>\n part\n .trim()\n // Drop operator classes and per-column modifiers PostgreSQL renders.\n .replace(/\\s+(ASC|DESC|NULLS\\s+(FIRST|LAST)|[a-z_]+_ops)\\b/gi, '')\n .trim()\n .replace(/^\"(.*)\"$/, '$1'),\n )\n .filter((part) => part.length > 0);\n}\n\nasync function readSqliteIndexes(\n db: DatabaseInterface,\n tableName: string,\n): Promise<LiveIndex[]> {\n // `pragma_index_list` — unlike `getTableSchema()` — also reports the\n // implicit indexes SQLite creates for inline UNIQUE/PRIMARY KEY table\n // constraints (`sqlite_autoindex_*`), which is exactly where system-table\n // uniqueness lives.\n let listRows: Record<string, unknown>[];\n try {\n const result = await db.query(\n `SELECT * FROM pragma_index_list(${quoteLiteral(tableName)})`,\n );\n listRows = (result?.rows ?? []) as Record<string, unknown>[];\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read SQLite indexes for \\`${tableName}\\`: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const indexes: LiveIndex[] = [];\n for (const row of listRows) {\n const name = String(row.name ?? '');\n if (!name) continue;\n\n const infoResult = await db.query(\n `SELECT * FROM pragma_index_info(${quoteLiteral(name)})`,\n );\n const infoRows = (infoResult?.rows ?? []) as Record<string, unknown>[];\n const columns = infoRows\n .slice()\n .sort((left, right) => Number(left.seqno ?? 0) - Number(right.seqno ?? 0))\n .map((infoRow) => (infoRow.name == null ? '' : String(infoRow.name)))\n .filter((column) => column.length > 0);\n\n indexes.push({\n name,\n columns,\n unique: toBoolean(row.unique),\n primary: String(row.origin ?? '') === 'pk',\n valid: true,\n partial: toBoolean(row.partial),\n });\n }\n\n return indexes;\n}\n\nasync function readDuckDbIndexCatalog(\n db: DatabaseInterface,\n): Promise<IndexCatalog | null> {\n // DuckDB (and the DuckDB-backed JSON adapter) expose declared indexes\n // through `duckdb_indexes()`; uniqueness constraints live in\n // `duckdb_constraints()`. Both are best-effort: if either catalog is\n // unavailable the caller skips index checks entirely.\n const byTable = new Map<string, LiveIndex[]>();\n try {\n const result = await db.query(\n `SELECT table_name, index_name, is_unique, sql FROM duckdb_indexes()`,\n );\n for (const row of (result?.rows ?? []) as Record<string, unknown>[]) {\n const tableName = String(row.table_name ?? '');\n if (!tableName) continue;\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: String(row.index_name ?? ''),\n columns: parseIndexDefColumns(String(row.sql ?? '')),\n unique: toBoolean(row.is_unique),\n primary: false,\n valid: true,\n partial: false,\n });\n byTable.set(tableName, bucket);\n }\n } catch {\n return null;\n }\n\n try {\n const result = await db.query(\n `SELECT table_name, constraint_type, constraint_column_names FROM duckdb_constraints()`,\n );\n for (const row of (result?.rows ?? []) as Record<string, unknown>[]) {\n const constraintType = String(row.constraint_type ?? '').toUpperCase();\n if (constraintType !== 'UNIQUE' && constraintType !== 'PRIMARY KEY') {\n continue;\n }\n const tableName = String(row.table_name ?? '');\n const columns = normalizeColumnNameList(row.constraint_column_names);\n if (!tableName || columns.length === 0) continue;\n\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: `${tableName}_${columns.join('_')}_${constraintType === 'UNIQUE' ? 'key' : 'pkey'}`,\n columns,\n unique: true,\n primary: constraintType === 'PRIMARY KEY',\n valid: true,\n partial: false,\n });\n byTable.set(tableName, bucket);\n }\n } catch {\n // Constraints are not optional metadata on this engine: DuckDB requires an\n // inline UNIQUE constraint for upsert, so uniqueness lives almost entirely\n // in `duckdb_constraints()` rather than in `duckdb_indexes()`. Without it,\n // `conflict_target_unindexed` and `unique_constraint_missing` would fire as\n // *errors* against a correct database. Degrade the whole catalog instead —\n // the caller then skips every index check and the report says\n // `indexIntrospection: 'unavailable'`.\n return null;\n }\n\n return { forTable: async (tableName) => byTable.get(tableName) ?? [] };\n}\n\nfunction normalizeColumnNameList(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry)).filter(Boolean);\n }\n if (typeof value === 'string') {\n return value\n .replace(/^[[{]|[\\]}]$/g, '')\n .split(',')\n .map((entry) => entry.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter(Boolean);\n }\n return [];\n}\n\nfunction toBoolean(value: unknown): boolean {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'number') return value !== 0;\n if (typeof value === 'string') {\n return value === '1' || value.toLowerCase() === 'true' || value === 't';\n }\n return false;\n}\n\nfunction quoteLiteral(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-parity.js","names":[],"sources":["../../src/schema/live-parity.ts"],"sourcesContent":["/**\n * Live-schema parity check (#2368)\n *\n * Compares a **live** database against the shape the model layer assumes.\n * Three inputs define \"expected\":\n *\n * 1. **Declared table shapes** — the manifest schemas the caller passes in.\n * These give tables, columns, types, nullability, and declared indexes.\n * 2. **Hand-DDL system tables** — `_smrt_*` tables parsed out of\n * `src/system/schema.ts` (see `system-table-shapes.ts`). They never enter\n * the manifest, so nothing else has ever diffed them.\n * 3. **An expected-shape index policy that does not consult the manifest** —\n * every foreign-key / cross-package-ref / tenant column carries a leading\n * index, every declared upsert conflict target is backed by a matching\n * UNIQUE index, and every `unique: true` column is actually unique in the\n * live database.\n *\n * Point 3 is the reason this module exists rather than reusing the migration\n * differ: the differ compares the live database to the manifest, i.e. to the\n * artifact that dropped the index in the first place, which is why a database\n * missing 164 tenant-column indexes reported \"in sync\" (#2356).\n *\n * This module is read-only. It emits findings; it never repairs.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n collectIntegerWidthTargets,\n preflightIntegerWidthWidening,\n} from '../migrations/integer-width.js';\nimport { RETIRED_SYSTEM_TABLES } from '../system/schema.js';\nimport { detectEngine, getDDLStrategy } from './ddl/index.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { getSystemTableShapes } from './system-table-shapes.js';\nimport type { ColumnDefinition, SchemaDefinition } from './types.js';\n\n/** How badly a finding breaks the deployment. */\nexport type LiveParitySeverity = 'error' | 'warning' | 'info';\n\n/** The class of drift a finding describes. */\nexport type LiveParityFindingKind =\n | 'missing_table'\n | 'extra_table'\n | 'missing_column'\n | 'extra_column'\n | 'column_type_drift'\n | 'column_nullability_drift'\n | 'missing_index'\n | 'extra_index'\n | 'unindexed_reference'\n | 'conflict_target_unindexed'\n | 'conflict_target_not_unique'\n | 'unique_constraint_missing'\n | 'invalid_index'\n /** Pre-#2373 PostgreSQL/DuckDB int4 column needing opt-in widening. */\n | 'legacy_integer_width';\n\n/** One difference between the live database and the expected shape. */\nexport interface LiveParityFinding {\n kind: LiveParityFindingKind;\n severity: LiveParitySeverity;\n /** Table the finding is about. */\n table: string;\n /** Column or index the finding is about, when it is narrower than a table. */\n target?: string;\n /** Whether the table is an application table or a `_smrt_*` system table. */\n origin: ExpectedTableOrigin;\n message: string;\n recommendation: string;\n details?: Record<string, unknown>;\n}\n\n/** Aggregate result of one parity run. */\nexport interface LiveSchemaParityReport {\n engine: DatabaseEngine;\n /** Expected tables that exist in the live database and were compared. */\n tablesChecked: number;\n /** Expected tables that are absent from the live database. */\n tablesMissing: number;\n /** Whether `_smrt_*` system tables participated. */\n systemTablesIncluded: boolean;\n /**\n * `full` when index metadata (uniqueness, partial predicates, PostgreSQL\n * validity) could be read for this engine; `unavailable` when it could not,\n * in which case every index-class check is skipped rather than guessed.\n */\n indexIntrospection: 'full' | 'unavailable';\n findings: LiveParityFinding[];\n counts: Record<LiveParitySeverity, number>;\n /** True when no `error`-severity finding was produced. */\n ok: boolean;\n}\n\n/** A declared upsert conflict target for one table. */\nexport interface ConflictTargetInput {\n columns: string[];\n /** Where the target came from, e.g. a class name — used in messages. */\n source?: string;\n}\n\nexport interface LiveSchemaParityOptions {\n db: DatabaseInterface;\n /** Expected application table shapes, keyed by table name. */\n schemas?: Record<string, SchemaDefinition>;\n /** Declared upsert conflict targets, keyed by table name. */\n conflictTargets?: Record<string, ConflictTargetInput[]>;\n /** Include `_smrt_*` system tables (default true). */\n includeSystemTables?: boolean;\n /** Explicit engine hint for adapters whose URL is empty or ambiguous. */\n engineHint?: string;\n /** Report live tables no expected shape covers (default true). */\n reportExtraTables?: boolean;\n}\n\n/** Whether an expected table is an application table or a system table. */\nexport type ExpectedTableOrigin = 'application' | 'system';\n\ninterface ExpectedColumn {\n name: string;\n /** Engine-materialized type text. */\n type: string;\n primaryKey: boolean;\n notNull: boolean;\n unique: boolean;\n hasDefault: boolean;\n reference?: ColumnDefinition['referenceKind'];\n}\n\ninterface ExpectedIndex {\n name: string;\n columns: string[];\n unique: boolean;\n}\n\ninterface ExpectedTable {\n name: string;\n origin: ExpectedTableOrigin;\n columns: ExpectedColumn[];\n indexes: ExpectedIndex[];\n conflictTargets: ConflictTargetInput[];\n}\n\ninterface LiveColumn {\n name: string;\n type: string;\n notNull: boolean;\n primaryKey: boolean;\n hasDefault: boolean;\n}\n\ninterface LiveIndex {\n name: string;\n columns: string[];\n unique: boolean;\n /** PostgreSQL `indisvalid`; always true on engines without the concept. */\n valid: boolean;\n /** True for the index backing a primary key. */\n primary: boolean;\n /** True when the index is partial (has a WHERE predicate). */\n partial: boolean;\n}\n\n/**\n * Thrown when the live database cannot be introspected. Callers fail closed:\n * an unreadable database is never reported as \"in sync\".\n */\nexport class LiveSchemaParityError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'LiveSchemaParityError';\n }\n}\n\n/**\n * Compare a live database against the expected shape and return every\n * difference found.\n *\n * @throws {LiveSchemaParityError} when the database cannot be reached or the\n * adapter cannot describe a table. Connection problems must fail closed.\n */\nexport async function checkLiveSchemaParity(\n options: LiveSchemaParityOptions,\n): Promise<LiveSchemaParityReport> {\n const {\n db,\n schemas = {},\n conflictTargets = {},\n includeSystemTables = true,\n engineHint,\n reportExtraTables = true,\n } = options;\n\n const engine = resolveEngine(db, engineHint);\n const expected = buildExpectedTables({\n schemas,\n conflictTargets,\n includeSystemTables,\n engine,\n });\n\n const liveTableNames = await listLiveTables(db, engine);\n const indexCatalog = await readIndexCatalog(db, engine);\n const findings: LiveParityFinding[] = [];\n\n let tablesChecked = 0;\n let tablesMissing = 0;\n\n for (const table of expected) {\n if (!liveTableNames.has(table.name)) {\n tablesMissing++;\n findings.push({\n kind: 'missing_table',\n severity: 'error',\n table: table.name,\n origin: table.origin,\n message: `Table \\`${table.name}\\` is declared but does not exist in the live database.`,\n recommendation:\n table.origin === 'system'\n ? 'Initialize SMRT system tables against this database (they are created on framework bootstrap).'\n : 'Run `smrt db:migrate` to create the missing table.',\n });\n continue;\n }\n\n tablesChecked++;\n const liveColumns = await readLiveColumns(db, table.name);\n findings.push(...compareColumns(table, liveColumns, engine));\n\n if (indexCatalog) {\n const liveIndexes = await indexCatalog.forTable(table.name);\n findings.push(...compareIndexes(table, liveColumns, liveIndexes));\n }\n }\n\n // #2373 intentionally leaves int4/int8 equivalent to the ordinary differ\n // (and to structural parity) so fresh BIGINT DDL does not auto-rewrite a\n // production table. Surface the remaining 32-bit columns as advisory-only\n // findings here, with row counts for a maintenance-window estimate.\n const widthPreflight = await preflightIntegerWidthWidening(\n db,\n collectIntegerWidthTargets(schemas, { includeSystemTables }),\n { engineHint },\n );\n if (widthPreflight.supported) {\n for (const table of widthPreflight.tables) {\n for (const column of table.columns) {\n if (column.state !== 'pending') continue;\n findings.push({\n kind: 'legacy_integer_width',\n severity: 'warning',\n table: table.table,\n target: column.column,\n origin: table.table.startsWith('_smrt_') ? 'system' : 'application',\n message:\n `Column \\`${table.table}.${column.column}\\` is legacy \\`${column.declaredType}\\` ` +\n `instead of BIGINT (${table.rowCount ?? 0} row(s) in the table).`,\n recommendation:\n 'Schedule a maintenance window, run `smrt db:migrate-int8 --dry-run`, then run `smrt db:migrate-int8` after reviewing the table-rewrite plan.',\n details: {\n actual: column.declaredType,\n expected: 'BIGINT',\n rowCount: table.rowCount,\n },\n });\n }\n }\n }\n\n if (reportExtraTables) {\n const expectedNames = new Set(expected.map((table) => table.name));\n for (const liveName of liveTableNames) {\n if (expectedNames.has(liveName)) continue;\n if (isInternalTableName(liveName)) continue;\n // Without system tables in scope, every `_smrt_*` table would be\n // reported as unexplained; that is a scoping artifact, not drift.\n if (!includeSystemTables && liveName.startsWith('_smrt_')) continue;\n // A retired system table is not unexplained drift — the framework\n // deliberately stopped creating it and deliberately does not drop it\n // (issue #2376). Name the exact remedy instead of the generic advice.\n const retired = RETIRED_SYSTEM_TABLES.includes(liveName);\n findings.push({\n kind: 'extra_table',\n severity: 'info',\n table: liveName,\n origin: liveName.startsWith('_smrt_') ? 'system' : 'application',\n message: retired\n ? `Live table \\`${liveName}\\` is a retired SMRT system table; nothing reads or writes it.`\n : `Live table \\`${liveName}\\` is not covered by any declared schema.`,\n recommendation: retired\n ? `Drop it once you have confirmed it is unused: DROP TABLE IF EXISTS ${liveName};`\n : 'Confirm the table belongs to another application sharing this database, or remove it once its owning model is gone.',\n });\n }\n }\n\n const counts: Record<LiveParitySeverity, number> = {\n error: 0,\n warning: 0,\n info: 0,\n };\n for (const finding of findings) {\n counts[finding.severity]++;\n }\n\n return {\n engine,\n tablesChecked,\n tablesMissing,\n systemTablesIncluded: includeSystemTables,\n indexIntrospection: indexCatalog ? 'full' : 'unavailable',\n findings,\n counts,\n ok: counts.error === 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Expected shape\n// ---------------------------------------------------------------------------\n\nfunction buildExpectedTables(input: {\n schemas: Record<string, SchemaDefinition>;\n conflictTargets: Record<string, ConflictTargetInput[]>;\n includeSystemTables: boolean;\n engine: DatabaseEngine;\n}): ExpectedTable[] {\n const { schemas, conflictTargets, includeSystemTables, engine } = input;\n const strategy = getDDLStrategy(engine);\n const tables: ExpectedTable[] = [];\n\n for (const [key, schema] of Object.entries(schemas)) {\n const tableName = schema?.tableName || key;\n if (!tableName) continue;\n\n const columns: ExpectedColumn[] = Object.entries(schema.columns ?? {}).map(\n ([columnName, column]) => ({\n name: columnName,\n type: strategy.mapType(column.type),\n primaryKey: column.primaryKey === true,\n notNull: column.notNull === true || column.primaryKey === true,\n unique: column.unique === true,\n hasDefault: column.defaultValue !== undefined,\n reference: column.foreignKey ? 'foreignKey' : column.referenceKind,\n }),\n );\n\n tables.push({\n name: tableName,\n origin: 'application',\n columns,\n indexes: (schema.indexes ?? [])\n // Expression indexes (`@meta({ indexed: true })`) surface with empty\n // or null column lists in introspection, so a column-based comparison\n // can only produce false drift for them.\n .filter((index) => !index.jsonPath && (index.columns?.length ?? 0) > 0)\n .map((index) => ({\n name: index.name,\n columns: index.columns,\n unique: index.unique === true,\n })),\n conflictTargets: dedupeConflictTargets(conflictTargets[tableName] ?? []),\n });\n }\n\n if (includeSystemTables) {\n for (const shape of getSystemTableShapes(engine).values()) {\n tables.push({\n name: shape.tableName,\n origin: 'system',\n columns: shape.columns.map((column) => ({\n name: column.name,\n type: column.type,\n primaryKey: column.primaryKey,\n notNull: column.notNull || column.primaryKey,\n unique: column.unique,\n hasDefault: column.hasDefault,\n })),\n indexes: shape.indexes.map((index) => ({\n name: index.name,\n columns: index.columns,\n unique: index.unique,\n })),\n // Inline `UNIQUE (...)` constraints are what the framework upserts\n // against on system tables, so they are the conflict targets here.\n conflictTargets: dedupeConflictTargets(\n shape.uniqueConstraints.map((columns) => ({\n columns,\n source: `${shape.tableName} DDL`,\n })),\n ),\n });\n }\n }\n\n return tables;\n}\n\nfunction dedupeConflictTargets(\n targets: ConflictTargetInput[],\n): ConflictTargetInput[] {\n const seen = new Map<string, ConflictTargetInput>();\n for (const target of targets) {\n const columns = (target.columns ?? []).filter(Boolean);\n if (columns.length === 0) continue;\n const key = [...columns].sort().join(',');\n if (!seen.has(key)) {\n seen.set(key, { columns, source: target.source });\n }\n }\n return [...seen.values()];\n}\n\n// ---------------------------------------------------------------------------\n// Column comparison\n// ---------------------------------------------------------------------------\n\nfunction compareColumns(\n table: ExpectedTable,\n liveColumns: Map<string, LiveColumn>,\n engine: DatabaseEngine,\n): LiveParityFinding[] {\n const findings: LiveParityFinding[] = [];\n const expectedNames = new Set(table.columns.map((column) => column.name));\n\n for (const column of table.columns) {\n const live = liveColumns.get(column.name);\n if (!live) {\n findings.push({\n kind: 'missing_column',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared but missing from the live database.`,\n recommendation:\n 'Run `smrt db:migrate` to add the column before application writes reach this table.',\n details: { expectedType: column.type },\n });\n continue;\n }\n\n if (!typesAreEquivalent(column, live, engine)) {\n findings.push({\n kind: 'column_type_drift',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is \\`${live.type}\\` in the live database but declared \\`${column.type}\\`.`,\n recommendation:\n 'Reconcile the column type with `smrt db:migrate`, or repair the declaration if the live type is correct.',\n details: { expected: column.type, actual: live.type },\n });\n }\n\n // A declared-NOT NULL column that is nullable live silently accepts rows\n // the model layer believes cannot exist; the reverse breaks every insert\n // that omits the column, which is the worse failure.\n if (!column.primaryKey && column.notNull && !live.notNull) {\n findings.push({\n kind: 'column_nullability_drift',\n severity: 'warning',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared NOT NULL but is nullable in the live database.`,\n recommendation:\n 'Backfill any NULL values and add the NOT NULL constraint, or relax the declaration.',\n });\n } else if (!column.notNull && live.notNull && !live.hasDefault) {\n findings.push({\n kind: 'column_nullability_drift',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is NOT NULL in the live database with no default, but is not declared NOT NULL. Inserts that omit it will fail.`,\n recommendation:\n 'Drop the orphan NOT NULL constraint, give the column a default, or declare the column required.',\n });\n }\n }\n\n for (const live of liveColumns.values()) {\n if (expectedNames.has(live.name)) continue;\n\n const breaksInserts = live.notNull && !live.hasDefault;\n findings.push({\n kind: 'extra_column',\n severity: breaksInserts ? 'error' : 'info',\n table: table.name,\n target: live.name,\n origin: table.origin,\n message: breaksInserts\n ? `Live column \\`${table.name}.${live.name}\\` is undeclared, NOT NULL, and has no default — every framework insert into this table will fail.`\n : `Live column \\`${table.name}.${live.name}\\` is not declared by the current schema.`,\n recommendation: breaksInserts\n ? 'Drop the orphan column or give it a default; this is the residue of a renamed or removed required field.'\n : 'Confirm the column is intentional legacy data, or drop it once nothing reads it.',\n details: { type: live.type },\n });\n }\n\n return findings;\n}\n\n/**\n * Whether a live column type satisfies its declaration.\n *\n * Two tolerances match the model layer's actual conventions, and mirror the\n * migration differ so the two tools never disagree:\n *\n * - `TEXT` ↔ `UUID` for structural identifier/reference columns, because\n * SQLite has no uuid type and older PostgreSQL deployments legitimately\n * still carry text ids (R11).\n * - `TEXT` ↔ `JSON`/`JSONB` in both directions, because SMRT serializes JSON\n * values into text columns and a native json column already holds exactly\n * that (#1335).\n */\nfunction typesAreEquivalent(\n expected: ExpectedColumn,\n live: LiveColumn,\n engine: DatabaseEngine,\n): boolean {\n const expectedType = normalizeSqlType(expected.type);\n const actualType = normalizeSqlType(live.type);\n if (expectedType === actualType) return true;\n\n const uuidTextPair =\n (expectedType === 'UUID' && actualType === 'TEXT') ||\n (expectedType === 'TEXT' && actualType === 'UUID');\n if (uuidTextPair && isStructuralReference(expected)) {\n return true;\n }\n\n const jsonTextPair =\n (expectedType === 'JSON' && actualType === 'TEXT') ||\n (expectedType === 'TEXT' && actualType === 'JSON');\n if (jsonTextPair) return true;\n\n // SQLite has dynamic typing: BOOLEAN/TIMESTAMP columns are declared with\n // affinities that pragma reports verbatim, and INTEGER/NUMERIC storage is\n // interchangeable for the values SMRT writes.\n if (engine === 'sqlite') {\n const numericPair =\n (expectedType === 'BOOLEAN' && actualType === 'INTEGER') ||\n (expectedType === 'INTEGER' && actualType === 'BOOLEAN');\n if (numericPair) return true;\n }\n\n return false;\n}\n\nfunction isStructuralReference(column: ExpectedColumn): boolean {\n return (\n column.primaryKey ||\n column.reference === 'id' ||\n column.reference === 'foreignKey' ||\n column.reference === 'crossPackageRef' ||\n column.reference === 'tenantId'\n );\n}\n\n/** Fold engine-specific type spellings into comparable buckets. */\nexport function normalizeSqlType(type: string): string {\n const upper = String(type ?? '')\n .toUpperCase()\n .trim()\n .replace(/\\(\\s*[^)]*\\s*\\)/g, '')\n .replace(/\\[\\]$/, '')\n .trim();\n\n if (\n /^(INTEGER|INT|INT2|INT4|INT8|BIGINT|SMALLINT|TINYINT|SERIAL|BIGSERIAL)$/.test(\n upper,\n )\n )\n return 'INTEGER';\n if (\n /^(TEXT|CLOB|STRING|VARCHAR|CHAR|CHARACTER|CHARACTER VARYING|NAME)$/.test(\n upper,\n )\n )\n return 'TEXT';\n if (upper === 'UUID') return 'UUID';\n if (\n /^(REAL|FLOAT|FLOAT4|FLOAT8|DOUBLE|DOUBLE PRECISION|DECIMAL|NUMERIC|NUMBER)$/.test(\n upper,\n )\n )\n return 'REAL';\n if (/^(BOOLEAN|BOOL)$/.test(upper)) return 'BOOLEAN';\n if (/^(TIMESTAMPTZ|TIMESTAMP WITH TIME ZONE)$/.test(upper))\n return 'TIMESTAMPTZ';\n if (\n /^(DATETIME|TIMESTAMP|TIMESTAMP WITHOUT TIME ZONE|DATE|TIME)$/.test(upper)\n )\n return 'TIMESTAMP';\n if (/^(BLOB|BINARY|BYTEA)$/.test(upper)) return 'BLOB';\n if (/^(JSON|JSONB)$/.test(upper)) return 'JSON';\n\n return upper;\n}\n\n// ---------------------------------------------------------------------------\n// Index comparison — the manifest-independent policy lives here\n// ---------------------------------------------------------------------------\n\nfunction compareIndexes(\n table: ExpectedTable,\n liveColumns: Map<string, LiveColumn>,\n liveIndexes: LiveIndex[],\n): LiveParityFinding[] {\n const findings: LiveParityFinding[] = [];\n const byName = new Map(liveIndexes.map((index) => [index.name, index]));\n const signatures = new Set(\n liveIndexes.map((index) => indexSignature(index.columns, index.unique)),\n );\n\n for (const index of liveIndexes) {\n if (index.valid) continue;\n findings.push({\n kind: 'invalid_index',\n severity: 'error',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: `Index \\`${index.name}\\` on \\`${table.name}\\` is INVALID — PostgreSQL will not use it and a unique index in this state enforces nothing.`,\n recommendation:\n 'Drop and rebuild it (`REINDEX INDEX CONCURRENTLY`), then re-run the parity check.',\n });\n }\n\n // 1. Declared indexes the live database does not carry in that shape.\n // Matching is by column/uniqueness signature rather than by name so an\n // equivalent index under a different name is not reported as missing\n // (#741), and so a same-name index whose uniqueness or columns drifted\n // still is.\n for (const index of table.indexes) {\n if (signatures.has(indexSignature(index.columns, index.unique))) continue;\n // Conflict-target coverage is reported below with a message that explains\n // the actual consequence; do not report the same index twice.\n if (\n table.conflictTargets.some((target) =>\n sameColumnSet(index.columns, target.columns),\n )\n ) {\n continue;\n }\n\n const sameName = byName.get(index.name);\n findings.push({\n kind: 'missing_index',\n severity: 'warning',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: sameName\n ? `Index \\`${index.name}\\` on \\`${table.name}\\` exists but its shape drifted: declared (${index.columns.join(', ')})${index.unique ? ' UNIQUE' : ''}, live (${sameName.columns.join(', ')})${sameName.unique ? ' UNIQUE' : ''}.`\n : `Declared index \\`${index.name}\\` on \\`${table.name}\\` (${index.columns.join(', ')}) is missing from the live database.`,\n recommendation:\n table.origin === 'system'\n ? 'Re-run SMRT system-table initialization to create the missing system index.'\n : 'Run `smrt db:migrate` to create the missing index.',\n });\n }\n\n // 2. Conflict targets. An upsert whose conflict target has no matching\n // UNIQUE index either errors outright (PostgreSQL) or silently inserts\n // duplicates, so this is the one index class that is an error.\n for (const target of table.conflictTargets) {\n if (target.columns.some((column) => !liveColumns.has(column))) {\n // A missing column is already reported; do not double-report it here.\n continue;\n }\n\n const candidates = liveIndexes.filter((index) =>\n sameColumnSet(index.columns, target.columns),\n );\n // A *partial* unique index cannot arbitrate a plain `ON CONFLICT (cols)`:\n // PostgreSQL only infers an index whose predicate the statement repeats,\n // and on other engines it simply does not constrain the excluded rows.\n if (candidates.some((index) => index.unique && !index.partial)) {\n continue;\n }\n\n const partialUnique = candidates.find(\n (index) => index.unique && index.partial,\n );\n const nonUnique = candidates.find((index) => !index.unique);\n const columnList = target.columns.join(', ');\n const suffix = target.source ? ` (declared by ${target.source})` : '';\n\n if (nonUnique) {\n findings.push({\n kind: 'conflict_target_not_unique',\n severity: 'error',\n table: table.name,\n target: nonUnique.name,\n origin: table.origin,\n message: `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} is backed by \\`${nonUnique.name}\\`, which is not UNIQUE.`,\n recommendation:\n 'Recreate the index as UNIQUE (`smrt db:migrate`); until then upserts insert duplicates instead of updating.',\n });\n continue;\n }\n\n findings.push({\n kind: 'conflict_target_unindexed',\n severity: 'error',\n table: table.name,\n target: partialUnique ? partialUnique.name : columnList,\n origin: table.origin,\n message: partialUnique\n ? `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} is backed only by the partial index \\`${partialUnique.name}\\`, which cannot arbitrate the upsert.`\n : `Upsert conflict target \\`${table.name}\\` (${columnList})${suffix} has no matching UNIQUE index in the live database.`,\n recommendation:\n 'Create a full UNIQUE index over exactly these columns (`smrt db:migrate`); PostgreSQL rejects the upsert outright and other engines duplicate rows.',\n });\n }\n\n // 3. Columns declared unique that are not actually unique live.\n for (const column of table.columns) {\n if (!column.unique) continue;\n if (!liveColumns.has(column.name)) continue;\n // A partial unique index constrains only the rows its predicate selects,\n // so it does not make the column unique as declared.\n const unique = liveIndexes.some(\n (index) =>\n index.unique &&\n !index.partial &&\n sameColumnSet(index.columns, [column.name]),\n );\n if (unique) continue;\n\n findings.push({\n kind: 'unique_constraint_missing',\n severity: 'error',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Column \\`${table.name}.${column.name}\\` is declared unique but no UNIQUE index enforces it in the live database.`,\n recommendation:\n 'De-duplicate existing values and create the UNIQUE index; the declaration is currently decorative.',\n });\n }\n\n // 4. Manifest-independent coverage policy: every foreign-key, cross-package\n // reference and tenant column needs an index it *leads*, or every lookup\n // and every tenant-scoped list is a full scan (#2356).\n for (const column of table.columns) {\n if (!column.reference) continue;\n if (column.reference === 'id') continue;\n if (!liveColumns.has(column.name)) continue;\n\n const covered = liveIndexes.some(\n (index) => index.columns[0] === column.name,\n );\n if (covered) continue;\n\n findings.push({\n kind: 'unindexed_reference',\n severity: 'warning',\n table: table.name,\n target: column.name,\n origin: table.origin,\n message: `Reference column \\`${table.name}.${column.name}\\` (${column.reference}) leads no index in the live database.`,\n recommendation:\n 'Add an index led by this column; relationship loads, joins and tenant-scoped reads currently scan the whole table.',\n details: { referenceKind: column.reference },\n });\n }\n\n // 5. Live indexes nothing declares. Informational: an operator may have\n // hand-added them, and nothing will recreate them after a rebuild.\n // Indexes that exist because rule 4 demands them are explained, even\n // though the manifest does not declare them.\n const policyLeadColumns = new Set(\n table.columns\n .filter((column) => column.reference && column.reference !== 'id')\n .map((column) => column.name),\n );\n const declaredNames = new Set(table.indexes.map((index) => index.name));\n const declaredSignatures = new Set(\n table.indexes.map((index) => indexSignature(index.columns, index.unique)),\n );\n for (const index of liveIndexes) {\n if (index.primary) continue;\n // Constraint-owned indexes (`sqlite_autoindex_*`, PostgreSQL `*_key` /\n // `*_pkey`) exist because of a table constraint, not because anyone\n // declared an index; reporting them as undeclared is pure noise.\n if (isConstraintOwnedIndexName(index.name)) continue;\n if (policyLeadColumns.has(index.columns[0])) continue;\n if (declaredNames.has(index.name)) continue;\n if (declaredSignatures.has(indexSignature(index.columns, index.unique)))\n continue;\n if (\n table.conflictTargets.some((target) =>\n sameColumnSet(index.columns, target.columns),\n )\n ) {\n continue;\n }\n\n findings.push({\n kind: 'extra_index',\n severity: 'info',\n table: table.name,\n target: index.name,\n origin: table.origin,\n message: `Live index \\`${index.name}\\` on \\`${table.name}\\` (${index.columns.join(', ') || 'expression'}) is not declared by the current schema.`,\n recommendation:\n 'Declare it so a rebuilt database keeps it, or drop it if it is obsolete.',\n details: { unique: index.unique, partial: index.partial },\n });\n }\n\n return findings;\n}\n\nfunction isConstraintOwnedIndexName(name: string): boolean {\n return (\n name.startsWith('sqlite_autoindex_') ||\n name.endsWith('_pkey') ||\n name.endsWith('_key')\n );\n}\n\nfunction indexSignature(columns: string[], unique: boolean): string {\n return `${columns.join(',')}:${unique}`;\n}\n\n/**\n * Conflict targets and unique constraints are order-insensitive: PostgreSQL\n * matches `ON CONFLICT (a, b)` against a unique index on `(b, a)`.\n */\nfunction sameColumnSet(left: string[], right: string[]): boolean {\n if (left.length !== right.length) return false;\n const sortedLeft = [...left].sort();\n const sortedRight = [...right].sort();\n return sortedLeft.every((column, index) => column === sortedRight[index]);\n}\n\n// ---------------------------------------------------------------------------\n// Live introspection\n// ---------------------------------------------------------------------------\n\nfunction resolveEngine(\n db: DatabaseInterface,\n engineHint?: string,\n): DatabaseEngine {\n if (typeof (db as { exportTable?: unknown }).exportTable === 'function') {\n return 'json';\n }\n const dbWithConfig = db as DatabaseInterface & { config?: { url?: string } };\n return detectEngine(db.url || dbWithConfig.config?.url || '', engineHint);\n}\n\nfunction isInternalTableName(name: string): boolean {\n return (\n name.startsWith('sqlite_') ||\n name.startsWith('pg_') ||\n name.startsWith('duckdb_') ||\n name === 'information_schema'\n );\n}\n\nasync function listLiveTables(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<Set<string>> {\n const sql =\n engine === 'postgres'\n ? `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`\n : `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;\n\n try {\n const result = await db.query(sql);\n const rows = (result?.rows ?? []) as {\n name?: string;\n table_name?: string;\n }[];\n return new Set(\n rows.map((row) => row.name || row.table_name || '').filter(Boolean),\n );\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not list tables in the live database: ${describeError(error)}`,\n { cause: error },\n );\n }\n}\n\nasync function readLiveColumns(\n db: DatabaseInterface,\n tableName: string,\n): Promise<Map<string, LiveColumn>> {\n if (typeof db.getTableSchema !== 'function') {\n throw new LiveSchemaParityError(\n 'The configured database adapter cannot describe tables (`getTableSchema` is unavailable), so live-schema parity cannot be verified.',\n );\n }\n\n let schema: Awaited<\n ReturnType<NonNullable<DatabaseInterface['getTableSchema']>>\n >;\n try {\n schema = await db.getTableSchema(tableName);\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read the live schema of \\`${tableName}\\`: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const columns = new Map<string, LiveColumn>();\n for (const [name, column] of Object.entries(schema?.columns ?? {})) {\n columns.set(name, {\n name,\n type: String(column?.type ?? ''),\n notNull: column?.notNull === true,\n primaryKey: column?.primaryKey === true,\n hasDefault:\n column?.defaultValue !== undefined && column?.defaultValue !== null,\n });\n }\n return columns;\n}\n\ninterface IndexCatalog {\n forTable(tableName: string): Promise<LiveIndex[]>;\n}\n\n/**\n * Build an index reader for the engine, or `null` when index metadata cannot\n * be read. A null catalog disables every index-class check rather than\n * reporting indexes as missing on an engine we cannot introspect.\n */\nasync function readIndexCatalog(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<IndexCatalog | null> {\n if (engine === 'postgres') {\n return readPostgresIndexCatalog(db);\n }\n if (engine === 'sqlite') {\n return { forTable: (tableName) => readSqliteIndexes(db, tableName) };\n }\n return readDuckDbIndexCatalog(db);\n}\n\nasync function readPostgresIndexCatalog(\n db: DatabaseInterface,\n): Promise<IndexCatalog> {\n // One catalog pass for the whole schema: `pg_index` carries uniqueness,\n // primary-key ownership and — unlike `pg_indexes` — `indisvalid`, which is\n // the only way to see an index left INVALID by a failed CONCURRENTLY build.\n const sql = `\n SELECT t.relname AS table_name,\n c.relname AS index_name,\n i.indisunique AS is_unique,\n i.indisprimary AS is_primary,\n i.indisvalid AS is_valid,\n (i.indpred IS NOT NULL) AS is_partial,\n pg_get_indexdef(i.indexrelid) AS index_def\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indexrelid\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE n.nspname = 'public'`;\n\n let rows: Record<string, unknown>[];\n try {\n const result = await db.query(sql);\n rows = (result?.rows ?? []) as Record<string, unknown>[];\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read the PostgreSQL index catalog: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const byTable = new Map<string, LiveIndex[]>();\n for (const row of rows) {\n const tableName = String(row.table_name ?? '');\n if (!tableName) continue;\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: String(row.index_name ?? ''),\n columns: parseIndexDefColumns(String(row.index_def ?? '')),\n unique: toBoolean(row.is_unique),\n primary: toBoolean(row.is_primary),\n valid: row.is_valid === undefined ? true : toBoolean(row.is_valid),\n partial: toBoolean(row.is_partial),\n });\n byTable.set(tableName, bucket);\n }\n\n return { forTable: async (tableName) => byTable.get(tableName) ?? [] };\n}\n\n/**\n * Extract the indexed column list from a `CREATE INDEX` definition.\n *\n * Expression components (`lower(name)`, `(meta ->> 'x')`) are kept verbatim so\n * they never collide with a plain column of the same name; ordering is\n * preserved because leading-column coverage is what the policy checks.\n */\nexport function parseIndexDefColumns(indexDef: string): string[] {\n const open = indexDef.indexOf('(');\n if (open === -1) return [];\n\n let depth = 0;\n let end = -1;\n for (let i = open; i < indexDef.length; i++) {\n if (indexDef[i] === '(') depth++;\n else if (indexDef[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end === -1) return [];\n\n const body = indexDef.slice(open + 1, end);\n const parts: string[] = [];\n let current = '';\n depth = 0;\n for (const char of body) {\n if (char === '(') depth++;\n if (char === ')') depth--;\n if (char === ',' && depth === 0) {\n parts.push(current);\n current = '';\n continue;\n }\n current += char;\n }\n parts.push(current);\n\n return parts\n .map((part) =>\n part\n .trim()\n // Drop operator classes and per-column modifiers PostgreSQL renders.\n .replace(/\\s+(ASC|DESC|NULLS\\s+(FIRST|LAST)|[a-z_]+_ops)\\b/gi, '')\n .trim()\n .replace(/^\"(.*)\"$/, '$1'),\n )\n .filter((part) => part.length > 0);\n}\n\nasync function readSqliteIndexes(\n db: DatabaseInterface,\n tableName: string,\n): Promise<LiveIndex[]> {\n // `pragma_index_list` — unlike `getTableSchema()` — also reports the\n // implicit indexes SQLite creates for inline UNIQUE/PRIMARY KEY table\n // constraints (`sqlite_autoindex_*`), which is exactly where system-table\n // uniqueness lives.\n let listRows: Record<string, unknown>[];\n try {\n const result = await db.query(\n `SELECT * FROM pragma_index_list(${quoteLiteral(tableName)})`,\n );\n listRows = (result?.rows ?? []) as Record<string, unknown>[];\n } catch (error) {\n throw new LiveSchemaParityError(\n `Could not read SQLite indexes for \\`${tableName}\\`: ${describeError(error)}`,\n { cause: error },\n );\n }\n\n const indexes: LiveIndex[] = [];\n for (const row of listRows) {\n const name = String(row.name ?? '');\n if (!name) continue;\n\n const infoResult = await db.query(\n `SELECT * FROM pragma_index_info(${quoteLiteral(name)})`,\n );\n const infoRows = (infoResult?.rows ?? []) as Record<string, unknown>[];\n const columns = infoRows\n .slice()\n .sort((left, right) => Number(left.seqno ?? 0) - Number(right.seqno ?? 0))\n .map((infoRow) => (infoRow.name == null ? '' : String(infoRow.name)))\n .filter((column) => column.length > 0);\n\n indexes.push({\n name,\n columns,\n unique: toBoolean(row.unique),\n primary: String(row.origin ?? '') === 'pk',\n valid: true,\n partial: toBoolean(row.partial),\n });\n }\n\n return indexes;\n}\n\nasync function readDuckDbIndexCatalog(\n db: DatabaseInterface,\n): Promise<IndexCatalog | null> {\n // DuckDB (and the DuckDB-backed JSON adapter) expose declared indexes\n // through `duckdb_indexes()`; uniqueness constraints live in\n // `duckdb_constraints()`. Both are best-effort: if either catalog is\n // unavailable the caller skips index checks entirely.\n const byTable = new Map<string, LiveIndex[]>();\n try {\n const result = await db.query(\n `SELECT table_name, index_name, is_unique, sql FROM duckdb_indexes()`,\n );\n for (const row of (result?.rows ?? []) as Record<string, unknown>[]) {\n const tableName = String(row.table_name ?? '');\n if (!tableName) continue;\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: String(row.index_name ?? ''),\n columns: parseIndexDefColumns(String(row.sql ?? '')),\n unique: toBoolean(row.is_unique),\n primary: false,\n valid: true,\n partial: false,\n });\n byTable.set(tableName, bucket);\n }\n } catch {\n return null;\n }\n\n try {\n const result = await db.query(\n `SELECT table_name, constraint_type, constraint_column_names FROM duckdb_constraints()`,\n );\n for (const row of (result?.rows ?? []) as Record<string, unknown>[]) {\n const constraintType = String(row.constraint_type ?? '').toUpperCase();\n if (constraintType !== 'UNIQUE' && constraintType !== 'PRIMARY KEY') {\n continue;\n }\n const tableName = String(row.table_name ?? '');\n const columns = normalizeColumnNameList(row.constraint_column_names);\n if (!tableName || columns.length === 0) continue;\n\n const bucket = byTable.get(tableName) ?? [];\n bucket.push({\n name: `${tableName}_${columns.join('_')}_${constraintType === 'UNIQUE' ? 'key' : 'pkey'}`,\n columns,\n unique: true,\n primary: constraintType === 'PRIMARY KEY',\n valid: true,\n partial: false,\n });\n byTable.set(tableName, bucket);\n }\n } catch {\n // Constraints are not optional metadata on this engine: DuckDB requires an\n // inline UNIQUE constraint for upsert, so uniqueness lives almost entirely\n // in `duckdb_constraints()` rather than in `duckdb_indexes()`. Without it,\n // `conflict_target_unindexed` and `unique_constraint_missing` would fire as\n // *errors* against a correct database. Degrade the whole catalog instead —\n // the caller then skips every index check and the report says\n // `indexIntrospection: 'unavailable'`.\n return null;\n }\n\n return { forTable: async (tableName) => byTable.get(tableName) ?? [] };\n}\n\nfunction normalizeColumnNameList(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.map((entry) => String(entry)).filter(Boolean);\n }\n if (typeof value === 'string') {\n return value\n .replace(/^[[{]|[\\]}]$/g, '')\n .split(',')\n .map((entry) => entry.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter(Boolean);\n }\n return [];\n}\n\nfunction toBoolean(value: unknown): boolean {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'number') return value !== 0;\n if (typeof value === 'string') {\n return value === '1' || value.toLowerCase() === 'true' || value === 't';\n }\n return false;\n}\n\nfunction quoteLiteral(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;;;;;AAsKA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,YAAY,SAAiB,SAA+B;EAC1D,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,eAAsB,sBACpB,SACiC;CACjC,MAAM,EACJ,IACA,UAAU,CAAC,GACX,kBAAkB,CAAC,GACnB,sBAAsB,MACtB,YACA,oBAAoB,SAClB;CAEJ,MAAM,SAAS,cAAc,IAAI,UAAU;CAC3C,MAAM,WAAW,oBAAoB;EACnC;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,iBAAiB,MAAM,eAAe,IAAI,MAAM;CACtD,MAAM,eAAe,MAAM,iBAAiB,IAAI,MAAM;CACtD,MAAM,WAAgC,CAAC;CAEvC,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CAEpB,KAAK,MAAM,SAAS,UAAU;EAC5B,IAAI,CAAC,eAAe,IAAI,MAAM,IAAI,GAAG;GACnC;GACA,SAAS,KAAK;IACZ,MAAM;IACN,UAAU;IACV,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,SAAS,WAAW,MAAM,KAAK;IAC/B,gBACE,MAAM,WAAW,WACb,mGACA;GACR,CAAC;GACD;EACF;EAEA;EACA,MAAM,cAAc,MAAM,gBAAgB,IAAI,MAAM,IAAI;EACxD,SAAS,KAAK,GAAG,eAAe,OAAO,aAAa,MAAM,CAAC;EAE3D,IAAI,cAAc;GAChB,MAAM,cAAc,MAAM,aAAa,SAAS,MAAM,IAAI;GAC1D,SAAS,KAAK,GAAG,eAAe,OAAO,aAAa,WAAW,CAAC;EAClE;CACF;CAMA,MAAM,iBAAiB,MAAM,8BAC3B,IACA,2BAA2B,SAAS,EAAE,oBAAoB,CAAC,GAC3D,EAAE,WAAW,CACf;CACA,IAAI,eAAe,WACjB,KAAK,MAAM,SAAS,eAAe,QACjC,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,IAAI,OAAO,UAAU,WAAW;EAChC,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM,MAAM,WAAW,QAAQ,IAAI,WAAW;GACtD,SACE,YAAY,MAAM,MAAM,GAAG,OAAO,OAAO,iBAAiB,OAAO,aAAa,wBACxD,MAAM,YAAY,EAAE;GAC5C,gBACE;GACF,SAAS;IACP,QAAQ,OAAO;IACf,UAAU;IACV,UAAU,MAAM;GAClB;EACF,CAAC;CACH;CAIJ,IAAI,mBAAmB;EACrB,MAAM,gBAAgB,IAAI,IAAI,SAAS,KAAK,UAAU,MAAM,IAAI,CAAC;EACjE,KAAK,MAAM,YAAY,gBAAgB;GACrC,IAAI,cAAc,IAAI,QAAQ,GAAG;GACjC,IAAI,oBAAoB,QAAQ,GAAG;GAGnC,IAAI,CAAC,uBAAuB,SAAS,WAAW,QAAQ,GAAG;GAI3D,MAAM,UAAU,sBAAsB,SAAS,QAAQ;GACvD,SAAS,KAAK;IACZ,MAAM;IACN,UAAU;IACV,OAAO;IACP,QAAQ,SAAS,WAAW,QAAQ,IAAI,WAAW;IACnD,SAAS,UACL,gBAAgB,SAAS,kEACzB,gBAAgB,SAAS;IAC7B,gBAAgB,UACZ,sEAAsE,SAAS,KAC/E;GACN,CAAC;EACH;CACF;CAEA,MAAM,SAA6C;EACjD,OAAO;EACP,SAAS;EACT,MAAM;CACR;CACA,KAAK,MAAM,WAAW,UACpB,OAAO,QAAQ,SAAS;CAG1B,OAAO;EACL;EACA;EACA;EACA,sBAAsB;EACtB,oBAAoB,eAAe,SAAS;EAC5C;EACA;EACA,IAAI,OAAO,UAAU;CACvB;AACF;AAMA,SAAS,oBAAoB,OAKT;CAClB,MAAM,EAAE,SAAS,iBAAiB,qBAAqB,WAAW;CAClE,MAAM,WAAW,eAAe,MAAM;CACtC,MAAM,SAA0B,CAAC;CAEjC,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,OAAO,GAAG;EACnD,MAAM,YAAY,QAAQ,aAAa;EACvC,IAAI,CAAC,WAAW;EAEhB,MAAM,UAA4B,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,KACpE,CAAC,YAAY,aAAa;GACzB,MAAM;GACN,MAAM,SAAS,QAAQ,OAAO,IAAI;GAClC,YAAY,OAAO,eAAe;GAClC,SAAS,OAAO,YAAY,QAAQ,OAAO,eAAe;GAC1D,QAAQ,OAAO,WAAW;GAC1B,YAAY,OAAO,iBAAiB,KAAA;GACpC,WAAW,OAAO,aAAa,eAAe,OAAO;EACvD,EACF;EAEA,OAAO,KAAK;GACV,MAAM;GACN,QAAQ;GACR;GACA,UAAU,OAAO,WAAW,CAAC,EAAA,CAI1B,QAAQ,UAAU,CAAC,MAAM,aAAa,MAAM,SAAS,UAAU,KAAK,CAAC,CAAC,CACtE,KAAK,WAAW;IACf,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,QAAQ,MAAM,WAAW;GAC3B,EAAE;GACJ,iBAAiB,sBAAsB,gBAAgB,cAAc,CAAC,CAAC;EACzE,CAAC;CACH;CAEA,IAAI,qBACF,KAAK,MAAM,SAAS,qBAAqB,MAAM,CAAC,CAAC,OAAO,GACtD,OAAO,KAAK;EACV,MAAM,MAAM;EACZ,QAAQ;EACR,SAAS,MAAM,QAAQ,KAAK,YAAY;GACtC,MAAM,OAAO;GACb,MAAM,OAAO;GACb,YAAY,OAAO;GACnB,SAAS,OAAO,WAAW,OAAO;GAClC,QAAQ,OAAO;GACf,YAAY,OAAO;EACrB,EAAE;EACF,SAAS,MAAM,QAAQ,KAAK,WAAW;GACrC,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,QAAQ,MAAM;EAChB,EAAE;EAGF,iBAAiB,sBACf,MAAM,kBAAkB,KAAK,aAAa;GACxC;GACA,QAAQ,GAAG,MAAM,UAAU;EAC7B,EAAE,CACJ;CACF,CAAC;CAIL,OAAO;AACT;AAEA,SAAS,sBACP,SACuB;CACvB,MAAM,uBAAO,IAAI,IAAiC;CAClD,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,OAAO,WAAW,CAAC,EAAA,CAAG,OAAO,OAAO;EACrD,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;EACxC,IAAI,CAAC,KAAK,IAAI,GAAG,GACf,KAAK,IAAI,KAAK;GAAE;GAAS,QAAQ,OAAO;EAAO,CAAC;CAEpD;CACA,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B;AAMA,SAAS,eACP,OACA,aACA,QACqB;CACrB,MAAM,WAAgC,CAAC;CACvC,MAAM,gBAAgB,IAAI,IAAI,MAAM,QAAQ,KAAK,WAAW,OAAO,IAAI,CAAC;CAExE,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,MAAM,OAAO,YAAY,IAAI,OAAO,IAAI;EACxC,IAAI,CAAC,MAAM;GACT,SAAS,KAAK;IACZ,MAAM;IACN,UAAU;IACV,OAAO,MAAM;IACb,QAAQ,OAAO;IACf,QAAQ,MAAM;IACd,SAAS,YAAY,MAAM,KAAK,GAAG,OAAO,KAAK;IAC/C,gBACE;IACF,SAAS,EAAE,cAAc,OAAO,KAAK;GACvC,CAAC;GACD;EACF;EAEA,IAAI,CAAC,mBAAmB,QAAQ,MAAM,MAAM,GAC1C,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM;GACd,SAAS,YAAY,MAAM,KAAK,GAAG,OAAO,KAAK,UAAU,KAAK,KAAK,yCAAyC,OAAO,KAAK;GACxH,gBACE;GACF,SAAS;IAAE,UAAU,OAAO;IAAM,QAAQ,KAAK;GAAK;EACtD,CAAC;EAMH,IAAI,CAAC,OAAO,cAAc,OAAO,WAAW,CAAC,KAAK,SAChD,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM;GACd,SAAS,YAAY,MAAM,KAAK,GAAG,OAAO,KAAK;GAC/C,gBACE;EACJ,CAAC;OACI,IAAI,CAAC,OAAO,WAAW,KAAK,WAAW,CAAC,KAAK,YAClD,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM;GACd,SAAS,YAAY,MAAM,KAAK,GAAG,OAAO,KAAK;GAC/C,gBACE;EACJ,CAAC;CAEL;CAEA,KAAK,MAAM,QAAQ,YAAY,OAAO,GAAG;EACvC,IAAI,cAAc,IAAI,KAAK,IAAI,GAAG;EAElC,MAAM,gBAAgB,KAAK,WAAW,CAAC,KAAK;EAC5C,SAAS,KAAK;GACZ,MAAM;GACN,UAAU,gBAAgB,UAAU;GACpC,OAAO,MAAM;GACb,QAAQ,KAAK;GACb,QAAQ,MAAM;GACd,SAAS,gBACL,iBAAiB,MAAM,KAAK,GAAG,KAAK,KAAK,sGACzC,iBAAiB,MAAM,KAAK,GAAG,KAAK,KAAK;GAC7C,gBAAgB,gBACZ,6GACA;GACJ,SAAS,EAAE,MAAM,KAAK,KAAK;EAC7B,CAAC;CACH;CAEA,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAS,mBACP,UACA,MACA,QACS;CACT,MAAM,eAAe,iBAAiB,SAAS,IAAI;CACnD,MAAM,aAAa,iBAAiB,KAAK,IAAI;CAC7C,IAAI,iBAAiB,YAAY,OAAO;CAKxC,KAFG,iBAAiB,UAAU,eAAe,UAC1C,iBAAiB,UAAU,eAAe,WACzB,sBAAsB,QAAQ,GAChD,OAAO;CAMT,IAFG,iBAAiB,UAAU,eAAe,UAC1C,iBAAiB,UAAU,eAAe,QAC3B,OAAO;CAKzB,IAAI,WAAW;MAEV,iBAAiB,aAAa,eAAe,aAC7C,iBAAiB,aAAa,eAAe,WAC/B,OAAO;CAAA;CAG1B,OAAO;AACT;AAEA,SAAS,sBAAsB,QAAiC;CAC9D,OACE,OAAO,cACP,OAAO,cAAc,QACrB,OAAO,cAAc,gBACrB,OAAO,cAAc,qBACrB,OAAO,cAAc;AAEzB;;AAGA,SAAgB,iBAAiB,MAAsB;CACrD,MAAM,QAAQ,OAAO,QAAQ,EAAE,CAAC,CAC7B,YAAY,CAAC,CACb,KAAK,CAAC,CACN,QAAQ,oBAAoB,EAAE,CAAC,CAC/B,QAAQ,SAAS,EAAE,CAAC,CACpB,KAAK;CAER,IACE,0EAA0E,KACxE,KACF,GAEA,OAAO;CACT,IACE,qEAAqE,KACnE,KACF,GAEA,OAAO;CACT,IAAI,UAAU,QAAQ,OAAO;CAC7B,IACE,8EAA8E,KAC5E,KACF,GAEA,OAAO;CACT,IAAI,mBAAmB,KAAK,KAAK,GAAG,OAAO;CAC3C,IAAI,2CAA2C,KAAK,KAAK,GACvD,OAAO;CACT,IACE,+DAA+D,KAAK,KAAK,GAEzE,OAAO;CACT,IAAI,wBAAwB,KAAK,KAAK,GAAG,OAAO;CAChD,IAAI,iBAAiB,KAAK,KAAK,GAAG,OAAO;CAEzC,OAAO;AACT;AAMA,SAAS,eACP,OACA,aACA,aACqB;CACrB,MAAM,WAAgC,CAAC;CACvC,MAAM,SAAS,IAAI,IAAI,YAAY,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CACtE,MAAM,aAAa,IAAI,IACrB,YAAY,KAAK,UAAU,eAAe,MAAM,SAAS,MAAM,MAAM,CAAC,CACxE;CAEA,KAAK,MAAM,SAAS,aAAa;EAC/B,IAAI,MAAM,OAAO;EACjB,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,SAAS,WAAW,MAAM,KAAK,UAAU,MAAM,KAAK;GACpD,gBACE;EACJ,CAAC;CACH;CAOA,KAAK,MAAM,SAAS,MAAM,SAAS;EACjC,IAAI,WAAW,IAAI,eAAe,MAAM,SAAS,MAAM,MAAM,CAAC,GAAG;EAGjE,IACE,MAAM,gBAAgB,MAAM,WAC1B,cAAc,MAAM,SAAS,OAAO,OAAO,CAC7C,GAEA;EAGF,MAAM,WAAW,OAAO,IAAI,MAAM,IAAI;EACtC,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,SAAS,WACL,WAAW,MAAM,KAAK,UAAU,MAAM,KAAK,6CAA6C,MAAM,QAAQ,KAAK,IAAI,EAAE,GAAG,MAAM,SAAS,YAAY,GAAG,UAAU,SAAS,QAAQ,KAAK,IAAI,EAAE,GAAG,SAAS,SAAS,YAAY,GAAG,KAC5N,oBAAoB,MAAM,KAAK,UAAU,MAAM,KAAK,MAAM,MAAM,QAAQ,KAAK,IAAI,EAAE;GACvF,gBACE,MAAM,WAAW,WACb,gFACA;EACR,CAAC;CACH;CAKA,KAAK,MAAM,UAAU,MAAM,iBAAiB;EAC1C,IAAI,OAAO,QAAQ,MAAM,WAAW,CAAC,YAAY,IAAI,MAAM,CAAC,GAE1D;EAGF,MAAM,aAAa,YAAY,QAAQ,UACrC,cAAc,MAAM,SAAS,OAAO,OAAO,CAC7C;EAIA,IAAI,WAAW,MAAM,UAAU,MAAM,UAAU,CAAC,MAAM,OAAO,GAC3D;EAGF,MAAM,gBAAgB,WAAW,MAC9B,UAAU,MAAM,UAAU,MAAM,OACnC;EACA,MAAM,YAAY,WAAW,MAAM,UAAU,CAAC,MAAM,MAAM;EAC1D,MAAM,aAAa,OAAO,QAAQ,KAAK,IAAI;EAC3C,MAAM,SAAS,OAAO,SAAS,iBAAiB,OAAO,OAAO,KAAK;EAEnE,IAAI,WAAW;GACb,SAAS,KAAK;IACZ,MAAM;IACN,UAAU;IACV,OAAO,MAAM;IACb,QAAQ,UAAU;IAClB,QAAQ,MAAM;IACd,SAAS,4BAA4B,MAAM,KAAK,MAAM,WAAW,GAAG,OAAO,kBAAkB,UAAU,KAAK;IAC5G,gBACE;GACJ,CAAC;GACD;EACF;EAEA,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,gBAAgB,cAAc,OAAO;GAC7C,QAAQ,MAAM;GACd,SAAS,gBACL,4BAA4B,MAAM,KAAK,MAAM,WAAW,GAAG,OAAO,yCAAyC,cAAc,KAAK,0CAC9H,4BAA4B,MAAM,KAAK,MAAM,WAAW,GAAG,OAAO;GACtE,gBACE;EACJ,CAAC;CACH;CAGA,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO,QAAQ;EACpB,IAAI,CAAC,YAAY,IAAI,OAAO,IAAI,GAAG;EASnC,IANe,YAAY,MACxB,UACC,MAAM,UACN,CAAC,MAAM,WACP,cAAc,MAAM,SAAS,CAAC,OAAO,IAAI,CAAC,CAE1C,GAAQ;EAEZ,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM;GACd,SAAS,YAAY,MAAM,KAAK,GAAG,OAAO,KAAK;GAC/C,gBACE;EACJ,CAAC;CACH;CAKA,KAAK,MAAM,UAAU,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO,WAAW;EACvB,IAAI,OAAO,cAAc,MAAM;EAC/B,IAAI,CAAC,YAAY,IAAI,OAAO,IAAI,GAAG;EAKnC,IAHgB,YAAY,MACzB,UAAU,MAAM,QAAQ,OAAO,OAAO,IAErC,GAAS;EAEb,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,OAAO;GACf,QAAQ,MAAM;GACd,SAAS,sBAAsB,MAAM,KAAK,GAAG,OAAO,KAAK,MAAM,OAAO,UAAU;GAChF,gBACE;GACF,SAAS,EAAE,eAAe,OAAO,UAAU;EAC7C,CAAC;CACH;CAMA,MAAM,oBAAoB,IAAI,IAC5B,MAAM,QACH,QAAQ,WAAW,OAAO,aAAa,OAAO,cAAc,IAAI,CAAC,CACjE,KAAK,WAAW,OAAO,IAAI,CAChC;CACA,MAAM,gBAAgB,IAAI,IAAI,MAAM,QAAQ,KAAK,UAAU,MAAM,IAAI,CAAC;CACtE,MAAM,qBAAqB,IAAI,IAC7B,MAAM,QAAQ,KAAK,UAAU,eAAe,MAAM,SAAS,MAAM,MAAM,CAAC,CAC1E;CACA,KAAK,MAAM,SAAS,aAAa;EAC/B,IAAI,MAAM,SAAS;EAInB,IAAI,2BAA2B,MAAM,IAAI,GAAG;EAC5C,IAAI,kBAAkB,IAAI,MAAM,QAAQ,EAAE,GAAG;EAC7C,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG;EACnC,IAAI,mBAAmB,IAAI,eAAe,MAAM,SAAS,MAAM,MAAM,CAAC,GACpE;EACF,IACE,MAAM,gBAAgB,MAAM,WAC1B,cAAc,MAAM,SAAS,OAAO,OAAO,CAC7C,GAEA;EAGF,SAAS,KAAK;GACZ,MAAM;GACN,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,SAAS,gBAAgB,MAAM,KAAK,UAAU,MAAM,KAAK,MAAM,MAAM,QAAQ,KAAK,IAAI,KAAK,aAAa;GACxG,gBACE;GACF,SAAS;IAAE,QAAQ,MAAM;IAAQ,SAAS,MAAM;GAAQ;EAC1D,CAAC;CACH;CAEA,OAAO;AACT;AAEA,SAAS,2BAA2B,MAAuB;CACzD,OACE,KAAK,WAAW,mBAAmB,KACnC,KAAK,SAAS,OAAO,KACrB,KAAK,SAAS,MAAM;AAExB;AAEA,SAAS,eAAe,SAAmB,QAAyB;CAClE,OAAO,GAAG,QAAQ,KAAK,GAAG,EAAE,GAAG;AACjC;;;;;AAMA,SAAS,cAAc,MAAgB,OAA0B;CAC/D,IAAI,KAAK,WAAW,MAAM,QAAQ,OAAO;CACzC,MAAM,aAAa,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK;CAClC,MAAM,cAAc,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK;CACpC,OAAO,WAAW,OAAO,QAAQ,UAAU,WAAW,YAAY,MAAM;AAC1E;AAMA,SAAS,cACP,IACA,YACgB;CAChB,IAAI,OAAQ,GAAiC,gBAAgB,YAC3D,OAAO;CAET,MAAM,eAAe;CACrB,OAAO,aAAa,GAAG,OAAO,aAAa,QAAQ,OAAO,IAAI,UAAU;AAC1E;AAEA,SAAS,oBAAoB,MAAuB;CAClD,OACE,KAAK,WAAW,SAAS,KACzB,KAAK,WAAW,KAAK,KACrB,KAAK,WAAW,SAAS,KACzB,SAAS;AAEb;AAEA,eAAe,eACb,IACA,QACsB;CACtB,MAAM,MACJ,WAAW,aACP,mFACA;CAEN,IAAI;EAEF,MAAM,QAAQ,MADO,GAAG,MAAM,GAAG,EAAA,EACX,QAAQ,CAAC;EAI/B,OAAO,IAAI,IACT,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAI,cAAc,EAAE,CAAC,CAAC,OAAO,OAAO,CACpE;CACF,SAAS,OAAO;EACd,MAAM,IAAI,sBACR,+CAA+C,cAAc,KAAK,KAClE,EAAE,OAAO,MAAM,CACjB;CACF;AACF;AAEA,eAAe,gBACb,IACA,WACkC;CAClC,IAAI,OAAO,GAAG,mBAAmB,YAC/B,MAAM,IAAI,sBACR,qIACF;CAGF,IAAI;CAGJ,IAAI;EACF,SAAS,MAAM,GAAG,eAAe,SAAS;CAC5C,SAAS,OAAO;EACd,MAAM,IAAI,sBACR,uCAAuC,UAAU,MAAM,cAAc,KAAK,KAC1E,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,MAAM,0BAAU,IAAI,IAAwB;CAC5C,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,GAC/D,QAAQ,IAAI,MAAM;EAChB;EACA,MAAM,OAAO,QAAQ,QAAQ,EAAE;EAC/B,SAAS,QAAQ,YAAY;EAC7B,YAAY,QAAQ,eAAe;EACnC,YACE,QAAQ,iBAAiB,KAAA,KAAa,QAAQ,iBAAiB;CACnE,CAAC;CAEH,OAAO;AACT;;;;;;AAWA,eAAe,iBACb,IACA,QAC8B;CAC9B,IAAI,WAAW,YACb,OAAO,yBAAyB,EAAE;CAEpC,IAAI,WAAW,UACb,OAAO,EAAE,WAAW,cAAc,kBAAkB,IAAI,SAAS,EAAE;CAErE,OAAO,uBAAuB,EAAE;AAClC;AAEA,eAAe,yBACb,IACuB;CAIvB,MAAM,MAAM;;;;;;;;;;;;;CAcZ,IAAI;CACJ,IAAI;EAEF,QAAQ,MADa,GAAG,MAAM,GAAG,EAAA,EACjB,QAAQ,CAAC;CAC3B,SAAS,OAAO;EACd,MAAM,IAAI,sBACR,gDAAgD,cAAc,KAAK,KACnE,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,MAAM,0BAAU,IAAI,IAAyB;CAC7C,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,YAAY,OAAO,IAAI,cAAc,EAAE;EAC7C,IAAI,CAAC,WAAW;EAChB,MAAM,SAAS,QAAQ,IAAI,SAAS,KAAK,CAAC;EAC1C,OAAO,KAAK;GACV,MAAM,OAAO,IAAI,cAAc,EAAE;GACjC,SAAS,qBAAqB,OAAO,IAAI,aAAa,EAAE,CAAC;GACzD,QAAQ,UAAU,IAAI,SAAS;GAC/B,SAAS,UAAU,IAAI,UAAU;GACjC,OAAO,IAAI,aAAa,KAAA,IAAY,OAAO,UAAU,IAAI,QAAQ;GACjE,SAAS,UAAU,IAAI,UAAU;EACnC,CAAC;EACD,QAAQ,IAAI,WAAW,MAAM;CAC/B;CAEA,OAAO,EAAE,UAAU,OAAO,cAAc,QAAQ,IAAI,SAAS,KAAK,CAAC,EAAE;AACvE;;;;;;;;AASA,SAAgB,qBAAqB,UAA4B;CAC/D,MAAM,OAAO,SAAS,QAAQ,GAAG;CACjC,IAAI,SAAS,IAAI,OAAO,CAAC;CAEzB,IAAI,QAAQ;CACZ,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,MAAM,IAAI,SAAS,QAAQ,KACtC,IAAI,SAAS,OAAO,KAAK;MACpB,IAAI,SAAS,OAAO,KAAK;EAC5B;EACA,IAAI,UAAU,GAAG;GACf,MAAM;GACN;EACF;CACF;CAEF,IAAI,QAAQ,IAAI,OAAO,CAAC;CAExB,MAAM,OAAO,SAAS,MAAM,OAAO,GAAG,GAAG;CACzC,MAAM,QAAkB,CAAC;CACzB,IAAI,UAAU;CACd,QAAQ;CACR,KAAK,MAAM,QAAQ,MAAM;EACvB,IAAI,SAAS,KAAK;EAClB,IAAI,SAAS,KAAK;EAClB,IAAI,SAAS,OAAO,UAAU,GAAG;GAC/B,MAAM,KAAK,OAAO;GAClB,UAAU;GACV;EACF;EACA,WAAW;CACb;CACA,MAAM,KAAK,OAAO;CAElB,OAAO,MACJ,KAAK,SACJ,KACG,KAAK,CAAC,CAEN,QAAQ,sDAAsD,EAAE,CAAC,CACjE,KAAK,CAAC,CACN,QAAQ,YAAY,IAAI,CAC7B,CAAC,CACA,QAAQ,SAAS,KAAK,SAAS,CAAC;AACrC;AAEA,eAAe,kBACb,IACA,WACsB;CAKtB,IAAI;CACJ,IAAI;EAIF,YAAY,MAHS,GAAG,MACtB,mCAAmC,aAAa,SAAS,EAAE,EAC7D,EAAA,EACoB,QAAQ,CAAC;CAC/B,SAAS,OAAO;EACd,MAAM,IAAI,sBACR,uCAAuC,UAAU,MAAM,cAAc,KAAK,KAC1E,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,MAAM,UAAuB,CAAC;CAC9B,KAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,OAAO,OAAO,IAAI,QAAQ,EAAE;EAClC,IAAI,CAAC,MAAM;EAMX,MAAM,YADY,MAHO,GAAG,MAC1B,mCAAmC,aAAa,IAAI,EAAE,EACxD,EAAA,EAC8B,QAAQ,CAAC,EAAA,CAEpC,MAAM,CAAC,CACP,MAAM,MAAM,UAAU,OAAO,KAAK,SAAS,CAAC,IAAI,OAAO,MAAM,SAAS,CAAC,CAAC,CAAC,CACzE,KAAK,YAAa,QAAQ,QAAQ,OAAO,KAAK,OAAO,QAAQ,IAAI,CAAE,CAAC,CACpE,QAAQ,WAAW,OAAO,SAAS,CAAC;EAEvC,QAAQ,KAAK;GACX;GACA;GACA,QAAQ,UAAU,IAAI,MAAM;GAC5B,SAAS,OAAO,IAAI,UAAU,EAAE,MAAM;GACtC,OAAO;GACP,SAAS,UAAU,IAAI,OAAO;EAChC,CAAC;CACH;CAEA,OAAO;AACT;AAEA,eAAe,uBACb,IAC8B;CAK9B,MAAM,0BAAU,IAAI,IAAyB;CAC7C,IAAI;EACF,MAAM,SAAS,MAAM,GAAG,MACtB,qEACF;EACA,KAAK,MAAM,OAAQ,QAAQ,QAAQ,CAAC,GAAiC;GACnE,MAAM,YAAY,OAAO,IAAI,cAAc,EAAE;GAC7C,IAAI,CAAC,WAAW;GAChB,MAAM,SAAS,QAAQ,IAAI,SAAS,KAAK,CAAC;GAC1C,OAAO,KAAK;IACV,MAAM,OAAO,IAAI,cAAc,EAAE;IACjC,SAAS,qBAAqB,OAAO,IAAI,OAAO,EAAE,CAAC;IACnD,QAAQ,UAAU,IAAI,SAAS;IAC/B,SAAS;IACT,OAAO;IACP,SAAS;GACX,CAAC;GACD,QAAQ,IAAI,WAAW,MAAM;EAC/B;CACF,QAAQ;EACN,OAAO;CACT;CAEA,IAAI;EACF,MAAM,SAAS,MAAM,GAAG,MACtB,uFACF;EACA,KAAK,MAAM,OAAQ,QAAQ,QAAQ,CAAC,GAAiC;GACnE,MAAM,iBAAiB,OAAO,IAAI,mBAAmB,EAAE,CAAC,CAAC,YAAY;GACrE,IAAI,mBAAmB,YAAY,mBAAmB,eACpD;GAEF,MAAM,YAAY,OAAO,IAAI,cAAc,EAAE;GAC7C,MAAM,UAAU,wBAAwB,IAAI,uBAAuB;GACnE,IAAI,CAAC,aAAa,QAAQ,WAAW,GAAG;GAExC,MAAM,SAAS,QAAQ,IAAI,SAAS,KAAK,CAAC;GAC1C,OAAO,KAAK;IACV,MAAM,GAAG,UAAU,GAAG,QAAQ,KAAK,GAAG,EAAE,GAAG,mBAAmB,WAAW,QAAQ;IACjF;IACA,QAAQ;IACR,SAAS,mBAAmB;IAC5B,OAAO;IACP,SAAS;GACX,CAAC;GACD,QAAQ,IAAI,WAAW,MAAM;EAC/B;CACF,QAAQ;EAQN,OAAO;CACT;CAEA,OAAO,EAAE,UAAU,OAAO,cAAc,QAAQ,IAAI,SAAS,KAAK,CAAC,EAAE;AACvE;AAEA,SAAS,wBAAwB,OAA0B;CACzD,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO;CAE3D,IAAI,OAAO,UAAU,UACnB,OAAO,MACJ,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,QAAQ,gBAAgB,EAAE,CAAC,CAAC,CACxD,OAAO,OAAO;CAEnB,OAAO,CAAC;AACV;AAEA,SAAS,UAAU,OAAyB;CAC1C,IAAI,OAAO,UAAU,WAAW,OAAO;CACvC,IAAI,OAAO,UAAU,UAAU,OAAO,UAAU;CAChD,IAAI,OAAO,UAAU,UACnB,OAAO,UAAU,OAAO,MAAM,YAAY,MAAM,UAAU,UAAU;CAEtE,OAAO;AACT;AAEA,SAAS,aAAa,OAAuB;CAC3C,OAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,EAAE;AACvC;AAEA,SAAS,cAAc,OAAwB;CAC7C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D"}
|
|
@@ -49,4 +49,17 @@ export declare function getSystemTableShapes(engine: DatabaseEngine): Map<string
|
|
|
49
49
|
* diagnostic input, never an execution path.
|
|
50
50
|
*/
|
|
51
51
|
export declare function parseTableShapes(ddl: string): Map<string, SystemTableShape>;
|
|
52
|
+
/**
|
|
53
|
+
* Names of every table the hand-written system DDL creates.
|
|
54
|
+
*
|
|
55
|
+
* This is the framework's definition of "a system table" — bookkeeping DDL
|
|
56
|
+
* replayed by `bootstrapSystemTables()` — as opposed to the ~25
|
|
57
|
+
* `_smrt_`-prefixed *domain* tables that belong to `@smrt()` models and are
|
|
58
|
+
* created by `db:migrate` from the manifest. Derived from the same parse the
|
|
59
|
+
* parity check uses so the two can never disagree (issue #2376).
|
|
60
|
+
*
|
|
61
|
+
* Declared last in the module because it evaluates the parser at load time,
|
|
62
|
+
* which needs every helper above to be initialized.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SYSTEM_TABLE_NAMES: readonly string[];
|
|
52
65
|
//# sourceMappingURL=system-table-shapes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-table-shapes.d.ts","sourceRoot":"","sources":["../../src/schema/system-table-shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,+DAA+D;AAC/D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,EAAE,EAAE,CAAC;CAC/B;AAqBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,GACrB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CA8B3E"}
|
|
1
|
+
{"version":3,"file":"system-table-shapes.d.ts","sourceRoot":"","sources":["../../src/schema/system-table-shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,+DAA+D;AAC/D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,EAAE,EAAE,CAAC;CAC/B;AAqBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,GACrB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CA8B3E;AA8ID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAE9C,CAAC"}
|
|
@@ -168,7 +168,20 @@ function splitTopLevel(body) {
|
|
|
168
168
|
function unquoteIdentifier(identifier) {
|
|
169
169
|
return identifier.replace(/^["`[]|["`\]]$/g, "");
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Names of every table the hand-written system DDL creates.
|
|
173
|
+
*
|
|
174
|
+
* This is the framework's definition of "a system table" — bookkeeping DDL
|
|
175
|
+
* replayed by `bootstrapSystemTables()` — as opposed to the ~25
|
|
176
|
+
* `_smrt_`-prefixed *domain* tables that belong to `@smrt()` models and are
|
|
177
|
+
* created by `db:migrate` from the manifest. Derived from the same parse the
|
|
178
|
+
* parity check uses so the two can never disagree (issue #2376).
|
|
179
|
+
*
|
|
180
|
+
* Declared last in the module because it evaluates the parser at load time,
|
|
181
|
+
* which needs every helper above to be initialized.
|
|
182
|
+
*/
|
|
183
|
+
var SYSTEM_TABLE_NAMES = Object.freeze([...getSystemTableShapes("sqlite").keys()]);
|
|
171
184
|
//#endregion
|
|
172
|
-
export { getSystemTableShapes, parseTableShapes };
|
|
185
|
+
export { SYSTEM_TABLE_NAMES, getSystemTableShapes, parseTableShapes };
|
|
173
186
|
|
|
174
187
|
//# sourceMappingURL=system-table-shapes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-table-shapes.js","names":[],"sources":["../../src/schema/system-table-shapes.ts"],"sourcesContent":["/**\n * System-table shape parsing (#2368)\n *\n * `_smrt_*` tables are created from hand-written DDL in `src/system/schema.ts`\n * rather than from decorated classes, so they never enter the manifest and the\n * manifest-driven differ has never compared them against a live database.\n *\n * This module parses that DDL back into a structural description — columns,\n * declared indexes, and inline `UNIQUE (...)` constraints — so the live-schema\n * parity check can hold system tables to the same standard as application\n * tables. The DDL is the source of truth for these tables; parsing it (rather\n * than duplicating a hand-maintained expectation table) keeps the two from\n * drifting apart.\n *\n * The parser is deliberately narrow: it understands the portable subset SMRT\n * actually writes (`CREATE TABLE IF NOT EXISTS`, `CREATE [UNIQUE] INDEX IF NOT\n * EXISTS`, simple column definitions, inline `UNIQUE`/`PRIMARY KEY` table\n * constraints) and ignores anything else rather than guessing.\n */\n\nimport { getSystemTableDDL } from '../system/schema.js';\nimport type { DatabaseEngine } from './ddl/types.js';\n\n/** One column as declared by hand-written system-table DDL. */\nexport interface SystemTableColumnShape {\n name: string;\n /**\n * The engine-materialized type text exactly as declared (`TEXT`, `BIGINT`,\n * `TIMESTAMPTZ`, …). System DDL is materialized per engine by\n * `getSystemTableDDL()`, so no abstract-type mapping is involved.\n */\n type: string;\n primaryKey: boolean;\n notNull: boolean;\n unique: boolean;\n hasDefault: boolean;\n}\n\n/** One index as declared by hand-written system-table DDL. */\nexport interface SystemTableIndexShape {\n name: string;\n columns: string[];\n unique: boolean;\n where?: string;\n}\n\n/** The complete declared shape of one `_smrt_*` table. */\nexport interface SystemTableShape {\n tableName: string;\n columns: SystemTableColumnShape[];\n indexes: SystemTableIndexShape[];\n /**\n * Column sets declared by inline `UNIQUE (...)` table constraints. These\n * back framework upserts but materialize as implicit indexes\n * (`sqlite_autoindex_*` / `<table>_key`) that name-based introspection\n * misses, so they are tracked separately from {@link indexes}.\n */\n uniqueConstraints: string[][];\n}\n\nconst CREATE_TABLE_RE =\n /^CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\1\\s*\\(/i;\n\nconst CREATE_INDEX_RE =\n /^CREATE\\s+(UNIQUE\\s+)?INDEX\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\2\\s+ON\\s+(\"?)([A-Za-z_][\\w$]*)\\4\\s*\\(([^)]*)\\)\\s*(?:WHERE\\s+([\\s\\S]+))?$/i;\n\n/**\n * Table-level constraint clauses that carry no column-shape information.\n *\n * Anchored on a following delimiter rather than a bare prefix so a column\n * legitimately named `checksum` is not mistaken for a `CHECK` constraint.\n */\nconst IGNORED_CONSTRAINT_PATTERNS = [\n /^CHECK\\s*\\(/i,\n /^FOREIGN\\s+KEY\\b/i,\n /^CONSTRAINT\\s+/i,\n /^EXCLUDE\\b/i,\n];\n\n/**\n * Parse the complete system-table DDL for an engine into table shapes.\n *\n * @param engine - Target engine; selects the materialized DDL variant\n * (PostgreSQL uses TIMESTAMPTZ where the portable DDL says TIMESTAMP).\n * @returns One shape per declared `_smrt_*` table, keyed by table name.\n */\nexport function getSystemTableShapes(\n engine: DatabaseEngine,\n): Map<string, SystemTableShape> {\n return parseTableShapes(getSystemTableDDL(engine).join('\\n'));\n}\n\n/**\n * Parse an arbitrary DDL script into table shapes.\n *\n * Exported for tests and for callers that materialize system DDL themselves.\n * Statements the parser does not understand are skipped silently — this is a\n * diagnostic input, never an execution path.\n */\nexport function parseTableShapes(ddl: string): Map<string, SystemTableShape> {\n const shapes = new Map<string, SystemTableShape>();\n\n for (const statement of splitStatements(stripComments(ddl))) {\n const tableMatch = statement.match(CREATE_TABLE_RE);\n if (tableMatch) {\n const shape = parseCreateTable(statement, tableMatch[2]);\n if (shape) {\n shapes.set(shape.tableName, shape);\n }\n continue;\n }\n\n const indexMatch = statement.match(CREATE_INDEX_RE);\n if (!indexMatch) continue;\n\n const [, uniqueKeyword, , indexName, , tableName, columnList, where] =\n indexMatch;\n const shape = shapes.get(tableName);\n if (!shape) continue;\n\n shape.indexes.push({\n name: indexName,\n columns: splitColumnList(columnList),\n unique: Boolean(uniqueKeyword),\n ...(where ? { where: where.trim() } : {}),\n });\n }\n\n return shapes;\n}\n\n/** Remove `--` line comments so they cannot be mistaken for statements. */\nfunction stripComments(ddl: string): string {\n return ddl.replace(/--[^\\n]*/g, '');\n}\n\n/** Split a DDL script on statement terminators, dropping empty fragments. */\nfunction splitStatements(ddl: string): string[] {\n return ddl\n .split(';')\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0);\n}\n\nfunction parseCreateTable(\n statement: string,\n tableName: string,\n): SystemTableShape | null {\n const open = statement.indexOf('(');\n const close = statement.lastIndexOf(')');\n if (open === -1 || close === -1 || close <= open) {\n return null;\n }\n\n const shape: SystemTableShape = {\n tableName,\n columns: [],\n indexes: [],\n uniqueConstraints: [],\n };\n\n for (const item of splitTopLevel(statement.slice(open + 1, close))) {\n const uniqueConstraint = item.match(/^UNIQUE\\s*\\(([^)]*)\\)$/i);\n if (uniqueConstraint) {\n shape.uniqueConstraints.push(splitColumnList(uniqueConstraint[1]));\n continue;\n }\n\n if (/^PRIMARY\\s+KEY\\s*\\(/i.test(item)) {\n const primaryKeyColumns = item.match(/^PRIMARY\\s+KEY\\s*\\(([^)]*)\\)$/i);\n if (primaryKeyColumns) {\n const columnNames = new Set(splitColumnList(primaryKeyColumns[1]));\n for (const column of shape.columns) {\n if (columnNames.has(column.name)) {\n column.primaryKey = true;\n }\n }\n }\n continue;\n }\n\n if (IGNORED_CONSTRAINT_PATTERNS.some((pattern) => pattern.test(item))) {\n continue;\n }\n\n const column = parseColumnDefinition(item);\n if (column) {\n shape.columns.push(column);\n }\n }\n\n return shape;\n}\n\n/**\n * Multi-word type tails SMRT's portable DDL can produce. Kept explicit so a\n * trailing constraint keyword is never absorbed into the type text.\n */\nconst MULTI_WORD_TYPE_TAILS = [\n 'WITH TIME ZONE',\n 'WITHOUT TIME ZONE',\n 'PRECISION',\n 'VARYING',\n];\n\nfunction parseColumnDefinition(item: string): SystemTableColumnShape | null {\n const tokens = item.split(/\\s+/);\n if (tokens.length < 2) return null;\n\n const name = unquoteIdentifier(tokens[0]);\n if (!/^[A-Za-z_][\\w$]*$/.test(name)) return null;\n\n let type = tokens[1];\n let rest = tokens.slice(2).join(' ');\n for (const tail of MULTI_WORD_TYPE_TAILS) {\n if (rest.toUpperCase().startsWith(tail)) {\n type = `${type} ${rest.slice(0, tail.length)}`;\n rest = rest.slice(tail.length).trim();\n break;\n }\n }\n\n const restUpper = rest.toUpperCase();\n return {\n name,\n type,\n primaryKey: /\\bPRIMARY\\s+KEY\\b/.test(restUpper),\n notNull: /\\bNOT\\s+NULL\\b/.test(restUpper),\n unique: /\\bUNIQUE\\b/.test(restUpper),\n hasDefault: /\\bDEFAULT\\b/.test(restUpper),\n };\n}\n\n/** Split a parenthesized column list into bare column names. */\nfunction splitColumnList(columnList: string): string[] {\n return columnList\n .split(',')\n .map((column) => unquoteIdentifier(column.trim()))\n .filter((column) => column.length > 0);\n}\n\n/** Split on commas that sit outside any parenthesis nesting. */\nfunction splitTopLevel(body: string): string[] {\n const items: string[] = [];\n let depth = 0;\n let current = '';\n\n for (const char of body) {\n if (char === '(') depth++;\n if (char === ')') depth--;\n if (char === ',' && depth === 0) {\n items.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n\n if (current.trim().length > 0) {\n items.push(current.trim());\n }\n\n return items\n .map((item) => item.replace(/\\s+/g, ' ').trim())\n .filter((item) => item.length > 0);\n}\n\nfunction unquoteIdentifier(identifier: string): string {\n return identifier.replace(/^[\"`[]|[\"`\\]]$/g, '');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"system-table-shapes.js","names":[],"sources":["../../src/schema/system-table-shapes.ts"],"sourcesContent":["/**\n * System-table shape parsing (#2368)\n *\n * `_smrt_*` tables are created from hand-written DDL in `src/system/schema.ts`\n * rather than from decorated classes, so they never enter the manifest and the\n * manifest-driven differ has never compared them against a live database.\n *\n * This module parses that DDL back into a structural description — columns,\n * declared indexes, and inline `UNIQUE (...)` constraints — so the live-schema\n * parity check can hold system tables to the same standard as application\n * tables. The DDL is the source of truth for these tables; parsing it (rather\n * than duplicating a hand-maintained expectation table) keeps the two from\n * drifting apart.\n *\n * The parser is deliberately narrow: it understands the portable subset SMRT\n * actually writes (`CREATE TABLE IF NOT EXISTS`, `CREATE [UNIQUE] INDEX IF NOT\n * EXISTS`, simple column definitions, inline `UNIQUE`/`PRIMARY KEY` table\n * constraints) and ignores anything else rather than guessing.\n */\n\nimport { getSystemTableDDL } from '../system/schema.js';\nimport type { DatabaseEngine } from './ddl/types.js';\n\n/** One column as declared by hand-written system-table DDL. */\nexport interface SystemTableColumnShape {\n name: string;\n /**\n * The engine-materialized type text exactly as declared (`TEXT`, `BIGINT`,\n * `TIMESTAMPTZ`, …). System DDL is materialized per engine by\n * `getSystemTableDDL()`, so no abstract-type mapping is involved.\n */\n type: string;\n primaryKey: boolean;\n notNull: boolean;\n unique: boolean;\n hasDefault: boolean;\n}\n\n/** One index as declared by hand-written system-table DDL. */\nexport interface SystemTableIndexShape {\n name: string;\n columns: string[];\n unique: boolean;\n where?: string;\n}\n\n/** The complete declared shape of one `_smrt_*` table. */\nexport interface SystemTableShape {\n tableName: string;\n columns: SystemTableColumnShape[];\n indexes: SystemTableIndexShape[];\n /**\n * Column sets declared by inline `UNIQUE (...)` table constraints. These\n * back framework upserts but materialize as implicit indexes\n * (`sqlite_autoindex_*` / `<table>_key`) that name-based introspection\n * misses, so they are tracked separately from {@link indexes}.\n */\n uniqueConstraints: string[][];\n}\n\nconst CREATE_TABLE_RE =\n /^CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\1\\s*\\(/i;\n\nconst CREATE_INDEX_RE =\n /^CREATE\\s+(UNIQUE\\s+)?INDEX\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\2\\s+ON\\s+(\"?)([A-Za-z_][\\w$]*)\\4\\s*\\(([^)]*)\\)\\s*(?:WHERE\\s+([\\s\\S]+))?$/i;\n\n/**\n * Table-level constraint clauses that carry no column-shape information.\n *\n * Anchored on a following delimiter rather than a bare prefix so a column\n * legitimately named `checksum` is not mistaken for a `CHECK` constraint.\n */\nconst IGNORED_CONSTRAINT_PATTERNS = [\n /^CHECK\\s*\\(/i,\n /^FOREIGN\\s+KEY\\b/i,\n /^CONSTRAINT\\s+/i,\n /^EXCLUDE\\b/i,\n];\n\n/**\n * Parse the complete system-table DDL for an engine into table shapes.\n *\n * @param engine - Target engine; selects the materialized DDL variant\n * (PostgreSQL uses TIMESTAMPTZ where the portable DDL says TIMESTAMP).\n * @returns One shape per declared `_smrt_*` table, keyed by table name.\n */\nexport function getSystemTableShapes(\n engine: DatabaseEngine,\n): Map<string, SystemTableShape> {\n return parseTableShapes(getSystemTableDDL(engine).join('\\n'));\n}\n\n/**\n * Parse an arbitrary DDL script into table shapes.\n *\n * Exported for tests and for callers that materialize system DDL themselves.\n * Statements the parser does not understand are skipped silently — this is a\n * diagnostic input, never an execution path.\n */\nexport function parseTableShapes(ddl: string): Map<string, SystemTableShape> {\n const shapes = new Map<string, SystemTableShape>();\n\n for (const statement of splitStatements(stripComments(ddl))) {\n const tableMatch = statement.match(CREATE_TABLE_RE);\n if (tableMatch) {\n const shape = parseCreateTable(statement, tableMatch[2]);\n if (shape) {\n shapes.set(shape.tableName, shape);\n }\n continue;\n }\n\n const indexMatch = statement.match(CREATE_INDEX_RE);\n if (!indexMatch) continue;\n\n const [, uniqueKeyword, , indexName, , tableName, columnList, where] =\n indexMatch;\n const shape = shapes.get(tableName);\n if (!shape) continue;\n\n shape.indexes.push({\n name: indexName,\n columns: splitColumnList(columnList),\n unique: Boolean(uniqueKeyword),\n ...(where ? { where: where.trim() } : {}),\n });\n }\n\n return shapes;\n}\n\n/** Remove `--` line comments so they cannot be mistaken for statements. */\nfunction stripComments(ddl: string): string {\n return ddl.replace(/--[^\\n]*/g, '');\n}\n\n/** Split a DDL script on statement terminators, dropping empty fragments. */\nfunction splitStatements(ddl: string): string[] {\n return ddl\n .split(';')\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0);\n}\n\nfunction parseCreateTable(\n statement: string,\n tableName: string,\n): SystemTableShape | null {\n const open = statement.indexOf('(');\n const close = statement.lastIndexOf(')');\n if (open === -1 || close === -1 || close <= open) {\n return null;\n }\n\n const shape: SystemTableShape = {\n tableName,\n columns: [],\n indexes: [],\n uniqueConstraints: [],\n };\n\n for (const item of splitTopLevel(statement.slice(open + 1, close))) {\n const uniqueConstraint = item.match(/^UNIQUE\\s*\\(([^)]*)\\)$/i);\n if (uniqueConstraint) {\n shape.uniqueConstraints.push(splitColumnList(uniqueConstraint[1]));\n continue;\n }\n\n if (/^PRIMARY\\s+KEY\\s*\\(/i.test(item)) {\n const primaryKeyColumns = item.match(/^PRIMARY\\s+KEY\\s*\\(([^)]*)\\)$/i);\n if (primaryKeyColumns) {\n const columnNames = new Set(splitColumnList(primaryKeyColumns[1]));\n for (const column of shape.columns) {\n if (columnNames.has(column.name)) {\n column.primaryKey = true;\n }\n }\n }\n continue;\n }\n\n if (IGNORED_CONSTRAINT_PATTERNS.some((pattern) => pattern.test(item))) {\n continue;\n }\n\n const column = parseColumnDefinition(item);\n if (column) {\n shape.columns.push(column);\n }\n }\n\n return shape;\n}\n\n/**\n * Multi-word type tails SMRT's portable DDL can produce. Kept explicit so a\n * trailing constraint keyword is never absorbed into the type text.\n */\nconst MULTI_WORD_TYPE_TAILS = [\n 'WITH TIME ZONE',\n 'WITHOUT TIME ZONE',\n 'PRECISION',\n 'VARYING',\n];\n\nfunction parseColumnDefinition(item: string): SystemTableColumnShape | null {\n const tokens = item.split(/\\s+/);\n if (tokens.length < 2) return null;\n\n const name = unquoteIdentifier(tokens[0]);\n if (!/^[A-Za-z_][\\w$]*$/.test(name)) return null;\n\n let type = tokens[1];\n let rest = tokens.slice(2).join(' ');\n for (const tail of MULTI_WORD_TYPE_TAILS) {\n if (rest.toUpperCase().startsWith(tail)) {\n type = `${type} ${rest.slice(0, tail.length)}`;\n rest = rest.slice(tail.length).trim();\n break;\n }\n }\n\n const restUpper = rest.toUpperCase();\n return {\n name,\n type,\n primaryKey: /\\bPRIMARY\\s+KEY\\b/.test(restUpper),\n notNull: /\\bNOT\\s+NULL\\b/.test(restUpper),\n unique: /\\bUNIQUE\\b/.test(restUpper),\n hasDefault: /\\bDEFAULT\\b/.test(restUpper),\n };\n}\n\n/** Split a parenthesized column list into bare column names. */\nfunction splitColumnList(columnList: string): string[] {\n return columnList\n .split(',')\n .map((column) => unquoteIdentifier(column.trim()))\n .filter((column) => column.length > 0);\n}\n\n/** Split on commas that sit outside any parenthesis nesting. */\nfunction splitTopLevel(body: string): string[] {\n const items: string[] = [];\n let depth = 0;\n let current = '';\n\n for (const char of body) {\n if (char === '(') depth++;\n if (char === ')') depth--;\n if (char === ',' && depth === 0) {\n items.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n\n if (current.trim().length > 0) {\n items.push(current.trim());\n }\n\n return items\n .map((item) => item.replace(/\\s+/g, ' ').trim())\n .filter((item) => item.length > 0);\n}\n\nfunction unquoteIdentifier(identifier: string): string {\n return identifier.replace(/^[\"`[]|[\"`\\]]$/g, '');\n}\n\n/**\n * Names of every table the hand-written system DDL creates.\n *\n * This is the framework's definition of \"a system table\" — bookkeeping DDL\n * replayed by `bootstrapSystemTables()` — as opposed to the ~25\n * `_smrt_`-prefixed *domain* tables that belong to `@smrt()` models and are\n * created by `db:migrate` from the manifest. Derived from the same parse the\n * parity check uses so the two can never disagree (issue #2376).\n *\n * Declared last in the module because it evaluates the parser at load time,\n * which needs every helper above to be initialized.\n */\nexport const SYSTEM_TABLE_NAMES: readonly string[] = Object.freeze([\n ...getSystemTableShapes('sqlite').keys(),\n]);\n"],"mappings":"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"}
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -110,6 +110,23 @@ export interface SchemaDefinition {
|
|
|
110
110
|
ddl?: string;
|
|
111
111
|
columns: Record<string, ColumnDefinition>;
|
|
112
112
|
indexes: IndexDefinition[];
|
|
113
|
+
/**
|
|
114
|
+
* Always `[]` on every schema a `@smrt()` class can currently produce (#2380).
|
|
115
|
+
*
|
|
116
|
+
* `DDLStrategy.generateTriggers()` (`schema/ddl/*.ts`) renders this array into
|
|
117
|
+
* real `CREATE TRIGGER` DDL and runs on every table creation, but there is no
|
|
118
|
+
* `@smrt()`/`@field()` option that populates it — unlike `indexes`, which
|
|
119
|
+
* `@smrt({ indexes: [...] })` (#2357) authors directly. The one producer that
|
|
120
|
+
* ever set it (the AST `generateSchema()` entry point, which emitted a single
|
|
121
|
+
* `updated_at`-bump trigger) fed only the `smrt:schema` virtual module, which
|
|
122
|
+
* had no consumer, and was deleted rather than wired up: `updated_at` is
|
|
123
|
+
* maintained at the application layer (`SmrtObject.save()`), the migration
|
|
124
|
+
* differ (`migrations/differ.ts`) never diffs triggers, so a hand-populated
|
|
125
|
+
* one would only ever apply to newly `CREATE TABLE`d tables and never
|
|
126
|
+
* retrofit existing ones. Populate this only via a caller who constructs a
|
|
127
|
+
* `SchemaDefinition` directly (tests, or third-party tooling) — never assume
|
|
128
|
+
* a generated schema carries one.
|
|
129
|
+
*/
|
|
113
130
|
triggers: TriggerDefinition[];
|
|
114
131
|
foreignKeys: ForeignKeyDefinition[];
|
|
115
132
|
version: string;
|
|
@@ -124,16 +141,6 @@ export interface SchemaManifest {
|
|
|
124
141
|
schemas: Record<string, SchemaDefinition>;
|
|
125
142
|
dependencies: string[];
|
|
126
143
|
}
|
|
127
|
-
export interface SchemaOverride {
|
|
128
|
-
tableName: string;
|
|
129
|
-
addColumns?: Record<string, ColumnDefinition>;
|
|
130
|
-
removeColumns?: string[];
|
|
131
|
-
addIndexes?: IndexDefinition[];
|
|
132
|
-
removeIndexes?: string[];
|
|
133
|
-
addTriggers?: TriggerDefinition[];
|
|
134
|
-
removeTriggers?: string[];
|
|
135
|
-
packageName: string;
|
|
136
|
-
}
|
|
137
144
|
export interface SchemaMigration {
|
|
138
145
|
id: string;
|
|
139
146
|
version: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"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"}
|
package/dist/schema/utils.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { SmrtObject } from '../object.js';
|
|
|
3
3
|
import { FieldDefinition } from '../scanner/types.js';
|
|
4
4
|
import { DatabaseEngine } from './ddl/types.js';
|
|
5
5
|
export { materializeManifestDDLForEngine, tokenizeSQLDDLBody, } from './ddl/materialize-manifest.js';
|
|
6
|
-
export { isJsonPathIndex, renderIndexTarget } from './index-utils.js';
|
|
6
|
+
export { assertIdentifierFits, identifierByteLength, isJsonPathIndex, isStiSubtypeUniqueIndex, MAX_IDENTIFIER_BYTES, renderIndexTarget, shortenIdentifier, } from './index-utils.js';
|
|
7
7
|
export { type CollectedManifestTable, collectManifestTables, type ManifestColumnLike, type ManifestIndexLike, type ManifestSchemaLike, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, mergeSchemaDefinitionInto, renderCollectedManifestTable, } from './manifest-schema.js';
|
|
8
8
|
/**
|
|
9
9
|
* Generates a complete database schema SQL statement for a class
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/schema/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/schema/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,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;AAE9B;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAMlC,SAAS,EAAE;IAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAC7C,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,cAAc,CAAA;CAAO,mBAkJ1C;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyEf"}
|
package/dist/schema/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isJsonPathIndex, renderIndexTarget } from "./index-utils.js";
|
|
1
|
+
import { MAX_IDENTIFIER_BYTES, assertIdentifierFits, identifierByteLength, isJsonPathIndex, isStiSubtypeUniqueIndex, renderIndexTarget, shortenIdentifier } from "./index-utils.js";
|
|
2
2
|
import { tableNameFromClass } from "../utils.js";
|
|
3
3
|
import { ObjectRegistry } from "../registry.js";
|
|
4
4
|
import { SchemaManager } from "./schema-manager.js";
|
|
@@ -35,7 +35,7 @@ async function generateSchema(ClassType, providedFields, options = {}) {
|
|
|
35
35
|
const generator = new SchemaGenerator();
|
|
36
36
|
const registeredClass = ObjectRegistry.getClass(className);
|
|
37
37
|
const runtimeSchemaConfig = registeredClass?.config ? {
|
|
38
|
-
conflictColumns:
|
|
38
|
+
conflictColumns: ObjectRegistry.getConflictColumns(className),
|
|
39
39
|
idType: registeredClass.config.idType,
|
|
40
40
|
indexes: registeredClass.config.indexes,
|
|
41
41
|
registry: ObjectRegistry
|
|
@@ -100,6 +100,6 @@ async function ensureSchema(db, className) {
|
|
|
100
100
|
await new SchemaManager(db, { skipTriggers: typeof db.exportTable === "function" }).ensureTable(effectiveSchemaDefinition);
|
|
101
101
|
}
|
|
102
102
|
//#endregion
|
|
103
|
-
export { collectManifestTables, ensureSchema, generateSchema, isJsonPathIndex, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, materializeManifestDDLForEngine, mergeSchemaDefinitionInto, renderCollectedManifestTable, renderIndexTarget, tokenizeSQLDDLBody };
|
|
103
|
+
export { MAX_IDENTIFIER_BYTES, assertIdentifierFits, collectManifestTables, ensureSchema, generateSchema, identifierByteLength, isJsonPathIndex, isStiSubtypeUniqueIndex, manifestColumnsToDefinitions, manifestIndexesToDefinitions, manifestSchemaToDefinition, materializeManifestDDLForEngine, mergeSchemaDefinitionInto, renderCollectedManifestTable, renderIndexTarget, shortenIdentifier, tokenizeSQLDDLBody };
|
|
104
104
|
|
|
105
105
|
//# sourceMappingURL=utils.js.map
|
package/dist/schema/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../src/schema/utils.ts"],"sourcesContent":["/**\n * Schema generation utilities - Node.js only\n *\n * These functions use SchemaGenerator which depends on node:crypto.\n * Separated from main utils.ts to prevent bundling in browser builds.\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtObject } from '../object.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldDefinition } from '../scanner/types.js';\nimport { tableNameFromClass } from '../utils.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { SchemaManager } from './schema-manager.js';\n\nexport {\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\n// Index-rendering helpers live in a separate file to avoid pulling the\n// heavyweight registry/collection module graph into the DDL strategies.\nexport { isJsonPathIndex, renderIndexTarget } from './index-utils.js';\n// Structured manifest → executable DDL helpers (#2358). Re-exported here for\n// `@happyvertical/smrt-vitest`, which already imports this subpath.\nexport {\n type CollectedManifestTable,\n collectManifestTables,\n type ManifestColumnLike,\n type ManifestIndexLike,\n type ManifestSchemaLike,\n manifestColumnsToDefinitions,\n manifestIndexesToDefinitions,\n manifestSchemaToDefinition,\n mergeSchemaDefinitionInto,\n renderCollectedManifestTable,\n} from './manifest-schema.js';\n\n/**\n * Generates a complete database schema SQL statement for a class\n *\n * This is a thin wrapper around SchemaGenerator that provides the\n * single source of truth for schema generation. Uses ObjectRegistry\n * cached fields from AST manifest for consistent schema generation.\n *\n * **Note**: Uses dynamic import for SchemaGenerator to avoid bundling\n * Node.js-only code (node:crypto) in browser builds.\n *\n * @param ClassType - Class constructor to generate schema for\n * @param providedFields - Optional fields map (used during registration)\n * @returns SQL schema creation statement with CREATE TABLE and CREATE INDEX statements\n */\nexport async function generateSchema(\n // Accepts any SmrtObject constructor: the registry-stored `typeof SmrtObject`\n // as well as collection item classes whose construct signature is narrower\n // than the full static surface. Only `.name` and the construct identity are\n // used here. `never[]` keeps the construct signature contravariantly\n // compatible with constructors that declare their own argument shapes.\n ClassType: { new (...args: never[]): SmrtObject; readonly name: string },\n providedFields?: Map<string, FieldDefinition>,\n options: { engine?: DatabaseEngine } = {},\n) {\n const className = ClassType.name;\n const tableName = tableNameFromClass(ClassType);\n\n // For external packages, ensure manifest is loaded before proceeding\n if (!providedFields || providedFields.size === 0) {\n await ObjectRegistry.ensureManifestLoaded(className);\n }\n\n // Use provided fields if available AND non-empty (during registration), otherwise get from registry\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields =\n providedFields && providedFields.size > 0\n ? providedFields\n : await ObjectRegistry.getAllFields(className);\n\n // Throw error if no fields found\n if (cachedFields.size === 0) {\n // Detect if running in test environment\n const testGlobals = globalThis as {\n describe?: unknown;\n it?: unknown;\n };\n const isTestEnv =\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n typeof testGlobals.describe !== 'undefined' ||\n typeof testGlobals.it !== 'undefined';\n\n const testHint = isTestEnv\n ? `\\n\\n⚠️ Are you using 'smrt test'? ` +\n `Tests require manifest generation.\\n` +\n ` ✅ Use: smrt test\\n` +\n ` ❌ NOT: npx vitest\\n`\n : '';\n\n // Check if class is actually registered (decorator ran but no fields loaded)\n const isRegistered = ObjectRegistry.hasClass(className);\n\n if (isRegistered) {\n // Class registered but no fields - manifest problem\n throw new Error(\n `No field metadata found for class '${className}'. ` +\n `The class is registered (decorator ran) but has no field definitions. ` +\n `This usually means the manifest file is missing or stale.` +\n testHint,\n );\n } else {\n // Class not registered - decorator never ran\n throw new Error(\n `Cannot generate schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() for schema generation to work. ` +\n `Runtime introspection has been removed per issue #131.` +\n testHint,\n );\n }\n }\n\n // Check if class uses STI strategy\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n\n // Dynamic import SchemaGenerator (Node.js-only, uses node:crypto)\n // This prevents bundling it into browser builds\n const { SchemaGenerator } = await import('./generator.js');\n const generator = new SchemaGenerator();\n const registeredClass = ObjectRegistry.getClass(className);\n // Every key the generator reads from `@smrt()` config must be listed here:\n // this bag is rebuilt by hand rather than passed through, so an unlisted\n // option is silently unreachable at runtime while still appearing in the\n // manifest. `indexes` (#2357) was exactly that.\n const runtimeSchemaConfig = registeredClass?.config\n ? {\n conflictColumns: registeredClass.config.conflictColumns,\n idType: registeredClass.config.idType,\n indexes: registeredClass.config.indexes,\n registry: ObjectRegistry,\n }\n : undefined;\n\n let schemaDefinition: Awaited<\n ReturnType<\n InstanceType<typeof SchemaGenerator>['generateSchemaFromRegistry']\n >\n >;\n\n if (tableStrategy === 'sti') {\n // STI: Generate shared table for base class\n const stiBase = ObjectRegistry.getSTIBase(className);\n\n if (!stiBase) {\n throw new Error(\n `STI strategy detected for '${className}' but no STI base class found. ` +\n `This should not happen - please report this bug.`,\n );\n }\n\n // Only generate schema for the base class (not for children).\n // R5-canon: `getSTIBase` returns the qualified name; compare against\n // the qualified form of this class so an STI base isn't\n // mis-classified as a subclass and skipped.\n const qualifiedClassName =\n registeredClass?.qualifiedName ?? registeredClass?.name ?? className;\n if (qualifiedClassName === stiBase || className === stiBase) {\n // This is the base class - generate STI schema\n schemaDefinition = await generator.generateSTISchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n } else {\n // This is a child class - return null or empty schema\n // The base class schema already includes all fields\n // Child classes don't need their own tables\n return ''; // Empty SQL - table already created by base class\n }\n } else {\n // CTI: Generate separate table for each class\n schemaDefinition = generator.generateSchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n }\n\n // Store the full schema definition in the registry\n // This is critical for:\n // - STI tables where descendants have additional columns (issue #427)\n // - Per-engine DDL generation via SchemaManager\n if (registeredClass) {\n // Store the full SchemaDefinition for SchemaManager to use\n registeredClass.schema = schemaDefinition;\n // Also store generated DDL for backward compatibility\n registeredClass.schema.ddl = generator.generateSQL(\n schemaDefinition,\n 'sqlite',\n );\n }\n\n return generator.generateSQL(schemaDefinition, options.engine);\n}\n\n/**\n * Ensure schema exists for a registered class.\n *\n * This compatibility helper is kept for tooling flows such as CLI commands that\n * explicitly prepare schema ahead of runtime. Core runtime no longer calls this\n * automatically.\n *\n * @deprecated Prefer explicit migration/bootstrap tooling. Runtime verifies\n * schema and fails fast when tables are missing.\n */\nexport async function ensureSchema(\n db: DatabaseInterface,\n className: string,\n): Promise<void> {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n throw new Error(\n `Cannot ensure schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() and registered in the ObjectRegistry.`,\n );\n }\n\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(className);\n // R5-canon: `getSTIBase` returns the qualified name. Compare\n // against the qualified form of `className` so an STI base isn't\n // mis-classified as a child and recursed on. Falls back to a\n // simple-name compare for classes without a package context.\n const qualifiedClassName =\n registered.qualifiedName ?? registered.name ?? className;\n if (stiBase && stiBase !== qualifiedClassName && stiBase !== className) {\n await ensureSchema(db, stiBase);\n return;\n }\n }\n\n let schemaDefinition = ObjectRegistry.getSchema(className);\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n const providedFields =\n registered.fields.size > 0 ? registered.fields : undefined;\n await generateSchema(registered.constructor, providedFields);\n schemaDefinition = ObjectRegistry.getSchema(className);\n\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n throw new Error(\n `No schema definition found for class '${className}'. ` +\n `Run the manifest generation/build step before preparing schema.`,\n );\n }\n\n // Use the merged table definition for shared tables (especially STI).\n // Per-class schema metadata can reflect only the current class, while\n // the database table must include columns from all classes sharing it.\n const mergedSchemaDefinition =\n ObjectRegistry.getAllSchemasAsDefinitions()[schemaDefinition.tableName];\n const effectiveSchemaDefinition = mergedSchemaDefinition ?? schemaDefinition;\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTable(effectiveSchemaDefinition);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../src/schema/utils.ts"],"sourcesContent":["/**\n * Schema generation utilities - Node.js only\n *\n * These functions use SchemaGenerator which depends on node:crypto.\n * Separated from main utils.ts to prevent bundling in browser builds.\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtObject } from '../object.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldDefinition } from '../scanner/types.js';\nimport { tableNameFromClass } from '../utils.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { SchemaManager } from './schema-manager.js';\n\nexport {\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\n// Index-rendering helpers live in a separate file to avoid pulling the\n// heavyweight registry/collection module graph into the DDL strategies.\nexport {\n assertIdentifierFits,\n identifierByteLength,\n isJsonPathIndex,\n isStiSubtypeUniqueIndex,\n MAX_IDENTIFIER_BYTES,\n renderIndexTarget,\n shortenIdentifier,\n} from './index-utils.js';\n// Structured manifest → executable DDL helpers (#2358). Re-exported here for\n// `@happyvertical/smrt-vitest`, which already imports this subpath.\nexport {\n type CollectedManifestTable,\n collectManifestTables,\n type ManifestColumnLike,\n type ManifestIndexLike,\n type ManifestSchemaLike,\n manifestColumnsToDefinitions,\n manifestIndexesToDefinitions,\n manifestSchemaToDefinition,\n mergeSchemaDefinitionInto,\n renderCollectedManifestTable,\n} from './manifest-schema.js';\n\n/**\n * Generates a complete database schema SQL statement for a class\n *\n * This is a thin wrapper around SchemaGenerator that provides the\n * single source of truth for schema generation. Uses ObjectRegistry\n * cached fields from AST manifest for consistent schema generation.\n *\n * **Note**: Uses dynamic import for SchemaGenerator to avoid bundling\n * Node.js-only code (node:crypto) in browser builds.\n *\n * @param ClassType - Class constructor to generate schema for\n * @param providedFields - Optional fields map (used during registration)\n * @returns SQL schema creation statement with CREATE TABLE and CREATE INDEX statements\n */\nexport async function generateSchema(\n // Accepts any SmrtObject constructor: the registry-stored `typeof SmrtObject`\n // as well as collection item classes whose construct signature is narrower\n // than the full static surface. Only `.name` and the construct identity are\n // used here. `never[]` keeps the construct signature contravariantly\n // compatible with constructors that declare their own argument shapes.\n ClassType: { new (...args: never[]): SmrtObject; readonly name: string },\n providedFields?: Map<string, FieldDefinition>,\n options: { engine?: DatabaseEngine } = {},\n) {\n const className = ClassType.name;\n const tableName = tableNameFromClass(ClassType);\n\n // For external packages, ensure manifest is loaded before proceeding\n if (!providedFields || providedFields.size === 0) {\n await ObjectRegistry.ensureManifestLoaded(className);\n }\n\n // Use provided fields if available AND non-empty (during registration), otherwise get from registry\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields =\n providedFields && providedFields.size > 0\n ? providedFields\n : await ObjectRegistry.getAllFields(className);\n\n // Throw error if no fields found\n if (cachedFields.size === 0) {\n // Detect if running in test environment\n const testGlobals = globalThis as {\n describe?: unknown;\n it?: unknown;\n };\n const isTestEnv =\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n typeof testGlobals.describe !== 'undefined' ||\n typeof testGlobals.it !== 'undefined';\n\n const testHint = isTestEnv\n ? `\\n\\n⚠️ Are you using 'smrt test'? ` +\n `Tests require manifest generation.\\n` +\n ` ✅ Use: smrt test\\n` +\n ` ❌ NOT: npx vitest\\n`\n : '';\n\n // Check if class is actually registered (decorator ran but no fields loaded)\n const isRegistered = ObjectRegistry.hasClass(className);\n\n if (isRegistered) {\n // Class registered but no fields - manifest problem\n throw new Error(\n `No field metadata found for class '${className}'. ` +\n `The class is registered (decorator ran) but has no field definitions. ` +\n `This usually means the manifest file is missing or stale.` +\n testHint,\n );\n } else {\n // Class not registered - decorator never ran\n throw new Error(\n `Cannot generate schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() for schema generation to work. ` +\n `Runtime introspection has been removed per issue #131.` +\n testHint,\n );\n }\n }\n\n // Check if class uses STI strategy\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n\n // Dynamic import SchemaGenerator (Node.js-only, uses node:crypto)\n // This prevents bundling it into browser builds\n const { SchemaGenerator } = await import('./generator.js');\n const generator = new SchemaGenerator();\n const registeredClass = ObjectRegistry.getClass(className);\n // Every key the generator reads from `@smrt()` config must be listed here:\n // this bag is rebuilt by hand rather than passed through, so an unlisted\n // option is silently unreachable at runtime while still appearing in the\n // manifest. `indexes` (#2357) was exactly that. `conflictColumns` is the\n // RESOLVED conflict target, not the raw decorator option: for a\n // tenant-scoped class with no explicit `conflictColumns` the registry\n // derives `[tenant_id, ...natural key]` (#2360), and `save()` upserts on\n // exactly that, so the unique index generated here must match it.\n const runtimeSchemaConfig = registeredClass?.config\n ? {\n conflictColumns: ObjectRegistry.getConflictColumns(className),\n idType: registeredClass.config.idType,\n indexes: registeredClass.config.indexes,\n registry: ObjectRegistry,\n }\n : undefined;\n\n let schemaDefinition: Awaited<\n ReturnType<\n InstanceType<typeof SchemaGenerator>['generateSchemaFromRegistry']\n >\n >;\n\n if (tableStrategy === 'sti') {\n // STI: Generate shared table for base class\n const stiBase = ObjectRegistry.getSTIBase(className);\n\n if (!stiBase) {\n throw new Error(\n `STI strategy detected for '${className}' but no STI base class found. ` +\n `This should not happen - please report this bug.`,\n );\n }\n\n // Only generate schema for the base class (not for children).\n // R5-canon: `getSTIBase` returns the qualified name; compare against\n // the qualified form of this class so an STI base isn't\n // mis-classified as a subclass and skipped.\n const qualifiedClassName =\n registeredClass?.qualifiedName ?? registeredClass?.name ?? className;\n if (qualifiedClassName === stiBase || className === stiBase) {\n // This is the base class - generate STI schema\n schemaDefinition = await generator.generateSTISchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n } else {\n // This is a child class - return null or empty schema\n // The base class schema already includes all fields\n // Child classes don't need their own tables\n return ''; // Empty SQL - table already created by base class\n }\n } else {\n // CTI: Generate separate table for each class\n schemaDefinition = generator.generateSchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n }\n\n // Store the full schema definition in the registry\n // This is critical for:\n // - STI tables where descendants have additional columns (issue #427)\n // - Per-engine DDL generation via SchemaManager\n if (registeredClass) {\n // Store the full SchemaDefinition for SchemaManager to use\n registeredClass.schema = schemaDefinition;\n // Also store generated DDL for backward compatibility\n registeredClass.schema.ddl = generator.generateSQL(\n schemaDefinition,\n 'sqlite',\n );\n }\n\n return generator.generateSQL(schemaDefinition, options.engine);\n}\n\n/**\n * Ensure schema exists for a registered class.\n *\n * This compatibility helper is kept for tooling flows such as CLI commands that\n * explicitly prepare schema ahead of runtime. Core runtime no longer calls this\n * automatically.\n *\n * @deprecated Prefer explicit migration/bootstrap tooling. Runtime verifies\n * schema and fails fast when tables are missing.\n */\nexport async function ensureSchema(\n db: DatabaseInterface,\n className: string,\n): Promise<void> {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n throw new Error(\n `Cannot ensure schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() and registered in the ObjectRegistry.`,\n );\n }\n\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(className);\n // R5-canon: `getSTIBase` returns the qualified name. Compare\n // against the qualified form of `className` so an STI base isn't\n // mis-classified as a child and recursed on. Falls back to a\n // simple-name compare for classes without a package context.\n const qualifiedClassName =\n registered.qualifiedName ?? registered.name ?? className;\n if (stiBase && stiBase !== qualifiedClassName && stiBase !== className) {\n await ensureSchema(db, stiBase);\n return;\n }\n }\n\n let schemaDefinition = ObjectRegistry.getSchema(className);\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n const providedFields =\n registered.fields.size > 0 ? registered.fields : undefined;\n await generateSchema(registered.constructor, providedFields);\n schemaDefinition = ObjectRegistry.getSchema(className);\n\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n throw new Error(\n `No schema definition found for class '${className}'. ` +\n `Run the manifest generation/build step before preparing schema.`,\n );\n }\n\n // Use the merged table definition for shared tables (especially STI).\n // Per-class schema metadata can reflect only the current class, while\n // the database table must include columns from all classes sharing it.\n const mergedSchemaDefinition =\n ObjectRegistry.getAllSchemasAsDefinitions()[schemaDefinition.tableName];\n const effectiveSchemaDefinition = mergedSchemaDefinition ?? schemaDefinition;\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTable(effectiveSchemaDefinition);\n}\n"],"mappings":"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"}
|