@happyvertical/smrt-core 0.43.0 → 0.43.2
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 +10 -1
- package/agents/schema-paths.md +8 -0
- package/dist/decorators/index.d.ts +18 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +7 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +30 -2
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +7 -0
- package/dist/migrations/generator.js.map +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +4 -2
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +13 -3
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +4 -2
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/ddl/base-strategy.js +2 -2
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +8 -2
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/foreign-key-ddl.d.ts +7 -0
- package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
- package/dist/schema/foreign-key-ddl.js +34 -2
- package/dist/schema/foreign-key-ddl.js.map +1 -1
- package/dist/schema/foreign-key-planner.d.ts.map +1 -1
- package/dist/schema/foreign-key-planner.js +5 -5
- package/dist/schema/foreign-key-planner.js.map +1 -1
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +12 -4
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/manifest-schema.d.ts.map +1 -1
- package/dist/schema/manifest-schema.js +2 -1
- package/dist/schema/manifest-schema.js.map +1 -1
- package/dist/schema/types.d.ts +6 -2
- 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 +4 -3
- package/dist/schema/utils.js.map +1 -1
- package/dist/smrt-knowledge.json +7 -7
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +2 -2
- package/dist/testing/database.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-schema.js","names":[],"sources":["../../src/schema/manifest-schema.ts"],"sourcesContent":["/**\n * Manifest schema → structured `SchemaDefinition` helpers (#2358).\n *\n * A manifest's `schema.ddl` string is an engine-neutral CREATE TABLE preview:\n * it carries no indexes and no per-engine type mapping, so nothing that needs\n * an executable schema may treat it as authoritative. Every consumer that\n * reads raw manifest JSON — `SchemaAggregator`, `createIsolatedTestDbFromManifest`\n * in `@happyvertical/smrt-vitest`, third-party tooling — should collect the\n * structured `columns` / `indexes` through this module and render them with\n * `getDDLStrategy(engine)`, exactly as `db:migrate` does.\n *\n * The cached `ddl` string is used only when a manifest object exposes no\n * structured columns at all (hand-authored or pre-structured manifests); that\n * legacy path keeps working but is not engine-complete.\n *\n * Keep this module light — type-only imports plus the DDL strategies and the\n * logger — so it can be re-exported from lightweight entry points without\n * pulling the registry/collection module graph in by itself.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { getDDLStrategy } from './ddl/index.js';\nimport {\n findCreateTableBodyRange,\n getSQLDefinitionComparisonKey,\n getSQLDefinitionIdentifier,\n isSQLTableConstraintDefinition,\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\nimport type { DatabaseEngine, EngineSpecificDDL } from './ddl/types.js';\nimport { quoteIdentifier } from './sql-identifiers.js';\nimport type {\n ColumnDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Column shape as it appears in raw manifest JSON. `ManifestColumnDefinition`\n * spells the default `default`; registry-side `ColumnDefinition` spells it\n * `defaultValue`. Hand-authored manifests use either.\n */\nexport interface ManifestColumnLike {\n type: string;\n primaryKey?: boolean;\n referenceKind?: ColumnDefinition['referenceKind'];\n notNull?: boolean;\n unique?: boolean;\n default?: unknown;\n defaultValue?: unknown;\n foreignKey?: ColumnDefinition['foreignKey'];\n check?: string;\n}\n\n/** Index shape as it appears in raw manifest JSON. */\nexport interface ManifestIndexLike {\n name: string;\n columns?: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: IndexDefinition['jsonPath'];\n}\n\n/** The subset of a manifest object's `schema` this module reads. */\nexport interface ManifestSchemaLike {\n tableName: string;\n ddl?: string;\n columns?: Record<string, ManifestColumnLike>;\n indexes?: ManifestIndexLike[];\n version?: string;\n}\n\n/**\n * Convert one raw manifest column map into structured `ColumnDefinition`s.\n * Picks known fields only, so stray manifest keys never leak into DDL.\n */\nexport function manifestColumnsToDefinitions(\n columns: Record<string, ManifestColumnLike> | undefined,\n): Record<string, ColumnDefinition> {\n const result: Record<string, ColumnDefinition> = {};\n for (const [name, column] of Object.entries(columns || {})) {\n if (!column || typeof column.type !== 'string') continue;\n const definition: ColumnDefinition = {\n type: column.type as SQLDataType,\n };\n if (column.primaryKey !== undefined)\n definition.primaryKey = column.primaryKey;\n if (column.referenceKind !== undefined)\n definition.referenceKind = column.referenceKind;\n if (column.notNull !== undefined) definition.notNull = column.notNull;\n if (column.unique !== undefined) definition.unique = column.unique;\n const defaultValue =\n column.defaultValue !== undefined ? column.defaultValue : column.default;\n if (defaultValue !== undefined) definition.defaultValue = defaultValue;\n if (column.foreignKey) definition.foreignKey = { ...column.foreignKey };\n if (column.check) definition.check = column.check;\n result[name] = definition;\n }\n return result;\n}\n\n/**\n * Convert raw manifest indexes into structured `IndexDefinition`s, keeping\n * `where` (partial) and `jsonPath` (expression) targets — the two fields the\n * retired string-based renderers dropped.\n */\nexport function manifestIndexesToDefinitions(\n indexes: ManifestIndexLike[] | undefined,\n): IndexDefinition[] {\n const result: IndexDefinition[] = [];\n for (const index of indexes || []) {\n if (!index?.name) continue;\n const definition: IndexDefinition = {\n name: index.name,\n columns: Array.isArray(index.columns) ? [...index.columns] : [],\n };\n if (index.unique !== undefined) definition.unique = index.unique;\n if (index.where) definition.where = index.where;\n if (index.jsonPath?.column && index.jsonPath.path) {\n definition.jsonPath = { ...index.jsonPath };\n }\n result.push(definition);\n }\n return result;\n}\n\n/**\n * Convert one manifest object's `schema` into a `SchemaDefinition`.\n *\n * The cached `ddl` string is carried along (non-authoritative) so callers can\n * fall back to it when the manifest exposes no structured columns.\n */\nexport function manifestSchemaToDefinition(\n schema: ManifestSchemaLike,\n): SchemaDefinition {\n const columns = manifestColumnsToDefinitions(schema.columns);\n const foreignKeys = Object.entries(columns).flatMap(([column, definition]) =>\n definition.foreignKey\n ? [\n {\n column,\n referencesTable: definition.foreignKey.table,\n referencesColumn: definition.foreignKey.column,\n onDelete: definition.foreignKey.onDelete,\n onUpdate: definition.foreignKey.onUpdate,\n },\n ]\n : [],\n );\n return {\n tableName: schema.tableName,\n ddl: schema.ddl,\n columns,\n indexes: manifestIndexesToDefinitions(schema.indexes),\n triggers: [],\n foreignKeys,\n dependencies: foreignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== schema.tableName),\n version: schema.version ?? '',\n };\n}\n\n/**\n * One physical table collected from one or more manifest objects (STI\n * subclasses share a table and each contributes columns and indexes).\n */\nexport interface CollectedManifestTable {\n tableName: string;\n /**\n * Structured union of every contributor: columns first-wins by name,\n * indexes deduplicated by name.\n */\n definition: SchemaDefinition;\n /**\n * `true` when every contributor exposed structured columns, i.e. the whole\n * table is rendered by a DDL strategy. `false` means at least one\n * contributor carried only a cached `ddl` string: the CREATE TABLE is then\n * the strategy render of the structured contributors (if any) merged with\n * the cached strings of the column-less ones. The decision is per table,\n * not per contributor.\n */\n structured: boolean;\n /**\n * The contributors' cached `ddl` strings merged column-wise (legacy path).\n * Empty when no contributor carried a `ddl`.\n */\n legacyDdl: string;\n /** `<package>:<ClassName>` labels of the contributors, in encounter order. */\n sources: string[];\n}\n\n/**\n * Deterministic STI merge of two structured definitions: existing columns\n * win, new columns are appended, indexes are deduplicated by name.\n */\nexport function mergeSchemaDefinitionInto(\n target: SchemaDefinition,\n incoming: SchemaDefinition,\n): void {\n for (const [name, column] of Object.entries(incoming.columns)) {\n if (!(name in target.columns)) {\n target.columns[name] = column;\n }\n }\n const indexNames = new Set(target.indexes.map((index) => index.name));\n for (const index of incoming.indexes) {\n if (!indexNames.has(index.name)) {\n indexNames.add(index.name);\n target.indexes.push(index);\n }\n }\n const foreignKeyKeys = new Set(\n target.foreignKeys.map(\n (foreignKey) =>\n `${foreignKey.column}\\0${foreignKey.referencesTable}\\0${foreignKey.referencesColumn}`,\n ),\n );\n for (const foreignKey of incoming.foreignKeys) {\n const key = `${foreignKey.column}\\0${foreignKey.referencesTable}\\0${foreignKey.referencesColumn}`;\n if (!foreignKeyKeys.has(key)) {\n foreignKeyKeys.add(key);\n target.foreignKeys.push(foreignKey);\n }\n }\n target.dependencies = Array.from(\n new Set([...target.dependencies, ...incoming.dependencies]),\n ).sort();\n}\n\n/**\n * Collect manifest schemas into physical tables.\n *\n * Accepts the `schema` entries of manifest objects (any package, any manifest)\n * in encounter order and groups them by `tableName`, merging STI contributors\n * structurally. Objects without a `schema`/`tableName` are skipped by the\n * caller; objects with neither columns nor `ddl` are ignored here.\n */\nexport function collectManifestTables(\n entries: Iterable<{ schema: ManifestSchemaLike; source: string }>,\n): Map<string, CollectedManifestTable> {\n const tables = new Map<string, CollectedManifestTable>();\n // Structured contributors whose cached ddl declares table constraints. Only\n // a fully structured table drops them (a mixed table re-merges the string),\n // so the warning is decided after collection.\n const constraintCarriers = new Map<\n string,\n Array<{ source: string; constraints: string[] }>\n >();\n\n for (const { schema, source } of entries) {\n if (!schema?.tableName) continue;\n const definition = manifestSchemaToDefinition(schema);\n const hasColumns = Object.keys(definition.columns).length > 0;\n const ddl = typeof schema.ddl === 'string' ? schema.ddl : '';\n if (!hasColumns && !ddl.trim()) continue;\n\n if (hasColumns && ddl.trim()) {\n const constraints = cachedDdlTableConstraints(ddl);\n if (constraints.length > 0) {\n const carriers = constraintCarriers.get(schema.tableName) ?? [];\n carriers.push({ source, constraints });\n constraintCarriers.set(schema.tableName, carriers);\n }\n }\n\n const existing = tables.get(schema.tableName);\n if (!existing) {\n tables.set(schema.tableName, {\n tableName: schema.tableName,\n definition,\n structured: hasColumns,\n legacyDdl: ddl,\n sources: [source],\n });\n continue;\n }\n\n mergeSchemaDefinitionInto(existing.definition, definition);\n existing.structured = existing.structured && hasColumns;\n existing.legacyDdl = existing.legacyDdl\n ? ddl\n ? mergeManifestDDL(existing.legacyDdl, ddl)\n : existing.legacyDdl\n : ddl;\n existing.sources.push(source);\n }\n\n for (const [tableName, carriers] of constraintCarriers) {\n if (!tables.get(tableName)?.structured) continue;\n for (const { source, constraints } of carriers) {\n warnAboutDroppedTableConstraints(tableName, source, constraints);\n }\n }\n\n return tables;\n}\n\n/**\n * Render a collected table for one engine.\n *\n * Structured tables go through the engine strategy for the CREATE TABLE\n * (per-engine types, inline UNIQUE where the engine needs it) and for every\n * index (partial `WHERE`, JSON-path expressions, engine-specific syntax).\n * Tables with a column-less contributor keep that contributor's cached `ddl`\n * string, made minimally safe with `materializeManifestDDLForEngine`, merged\n * on top of the strategy render of any structured contributors — so a\n * structured STI base never loses its engine typing to a hand-authored\n * subclass, and the subclass's extra columns and table constraints survive.\n * Indexes always render through the strategy — the string never carried them.\n */\nexport function renderCollectedManifestTable(\n table: CollectedManifestTable,\n engine: DatabaseEngine,\n): EngineSpecificDDL {\n const strategy = getDDLStrategy(engine);\n const hasColumns = Object.keys(table.definition.columns).length > 0;\n let createTable: string;\n if (table.structured) {\n createTable = strategy.generateCreateTable(table.definition);\n } else if (hasColumns) {\n createTable = mergeManifestDDL(\n strategy.generateCreateTable(table.definition),\n materializeManifestDDLForEngine(table.legacyDdl, engine),\n );\n } else {\n createTable = materializeManifestDDLForEngine(table.legacyDdl, engine);\n }\n\n // Engines that need UNIQUE inline for ON CONFLICT (DuckDB, JSON) get it\n // from `generateCreateTable` on the structured path and skip those indexes\n // in `generateIndexes`. A cached string never carried them, so a table with\n // a column-less contributor would lose its uniqueness entirely; append the\n // missing constraints as table elements (deduplicated against any the\n // string already declares).\n if (!table.structured && strategy.requiresInlineUnique()) {\n const uniqueElements = table.definition.indexes\n .filter(\n (index) =>\n index.unique &&\n !index.where &&\n !index.jsonPath &&\n index.columns.length > 0,\n )\n .map(\n (index) =>\n `UNIQUE(${index.columns.map((column) => quoteIdentifier(column)).join(', ')})`,\n );\n // Hand-authored strings may spell the same constraint without quotes\n // (`UNIQUE(tenant_id)`); compare quote- and whitespace-insensitively so\n // the synthesized element is not added beside it.\n const declared = new Set(\n cachedDdlTableConstraints(createTable).map(normalizeTableElement),\n );\n const missing = uniqueElements.filter(\n (element) => !declared.has(normalizeTableElement(element)),\n );\n if (missing.length > 0) {\n createTable = mergeManifestDDL(\n createTable,\n `CREATE TABLE ${quoteIdentifier(table.tableName)} (\\n ${missing.join(',\\n ')}\\n)`,\n );\n }\n }\n\n return {\n createTable,\n indexes: strategy.generateIndexes(table.definition),\n triggers: strategy.generateTriggers(table.definition),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Legacy cached-DDL string merge (column-less manifests only)\n// ---------------------------------------------------------------------------\n\nfunction parseDefinitionsFromDDL(ddl: string): {\n columns: Map<string, string>;\n tableElements: string[];\n} {\n const columns = new Map<string, string>();\n const tableElements: string[] = [];\n const bodyRange = findCreateTableBodyRange(ddl);\n if (!bodyRange) return { columns, tableElements };\n const [bodyStart, bodyEnd] = bodyRange;\n\n const segments = tokenizeSQLDDLBody(ddl.slice(bodyStart + 1, bodyEnd));\n\n for (const segment of segments) {\n const identifier = getSQLDefinitionIdentifier(segment);\n if (!identifier) continue;\n if (isSQLTableConstraintDefinition(segment)) {\n tableElements.push(segment);\n continue;\n }\n\n columns.set(identifier.name, segment);\n }\n\n return { columns, tableElements };\n}\n\n/**\n * Table-level constraints (`UNIQUE(...)`, `CHECK (...)`, `CONSTRAINT ...`,\n * ...) declared in a cached CREATE TABLE string. Structured `columns` cannot\n * represent them, so a structured render drops them — exactly as `db:migrate`\n * does. Exposed so callers can warn instead of losing them silently.\n *\n * @internal\n */\nexport function cachedDdlTableConstraints(ddl: string): string[] {\n return parseDefinitionsFromDDL(ddl).tableElements;\n}\n\n/** Quote/whitespace/case-insensitive key for a table constraint element. */\nfunction normalizeTableElement(element: string): string {\n return element.replace(/\"/g, '').replace(/\\s+/g, '').toLowerCase();\n}\n\nconst warnedDroppedConstraints = new Set<string>();\n\n/**\n * Warn once per process per table+source when a structured contributor's\n * cached `ddl` declares table constraints that the structured render cannot\n * carry. The structured schema is authoritative: a constraint that lives only\n * in the string never reaches `db:migrate` either, so rendering it here would\n * recreate the test/production drift #2358 removes.\n */\nfunction warnAboutDroppedTableConstraints(\n tableName: string,\n source: string,\n constraints: string[],\n): void {\n const key = `${tableName}\\0${source}`;\n if (warnedDroppedConstraints.has(key)) return;\n warnedDroppedConstraints.add(key);\n logger.warn(\n `[manifest-schema] ${source}: cached ddl for table \"${tableName}\" declares ` +\n `${constraints.length} table constraint(s) that structured columns cannot ` +\n `carry (${constraints.join('; ')}). They are not rendered — the ` +\n `structured schema is authoritative (#2358). Declare uniqueness through ` +\n `indexes/conflictColumns or a column-level constraint instead.`,\n );\n}\n\n/**\n * Merge two cached CREATE TABLE strings for the same table (STI subclasses):\n * the union of columns, existing definitions winning, new columns inserted\n * before any trailing table constraints, missing table constraints appended.\n *\n * Only the legacy column-less manifest path uses this; structured manifests\n * merge through `mergeSchemaDefinitionInto`.\n */\nfunction mergeManifestDDL(existingDDL: string, newDDL: string): string {\n const existingDefinitions = parseDefinitionsFromDDL(existingDDL);\n const newDefinitions = parseDefinitionsFromDDL(newDDL);\n\n const missingCols: string[] = [];\n for (const [name, def] of newDefinitions.columns) {\n if (!existingDefinitions.columns.has(name)) {\n missingCols.push(def);\n }\n }\n\n const existingTableElements = new Set(\n existingDefinitions.tableElements.map(getSQLDefinitionComparisonKey),\n );\n const missingTableElements = newDefinitions.tableElements.filter(\n (definition) =>\n !existingTableElements.has(getSQLDefinitionComparisonKey(definition)),\n );\n\n if (missingCols.length === 0 && missingTableElements.length === 0) {\n return existingDDL;\n }\n\n const bodyRange = findCreateTableBodyRange(existingDDL);\n if (!bodyRange) return existingDDL;\n const [bodyStart, bodyEnd] = bodyRange;\n const prefix = existingDDL.slice(0, bodyStart + 1);\n const body = existingDDL.slice(bodyStart + 1, bodyEnd);\n const suffix = existingDDL.slice(bodyEnd);\n const segments = tokenizeSQLDDLBody(body);\n const firstConstraintIndex = segments.findIndex((segment) =>\n isSQLTableConstraintDefinition(segment),\n );\n const insertionIndex =\n firstConstraintIndex === -1 ? segments.length : firstConstraintIndex;\n const mergedSegments = [\n ...segments.slice(0, insertionIndex),\n ...missingCols,\n ...segments.slice(insertionIndex),\n ...missingTableElements,\n ];\n\n return `${prefix}\\n${mergedSegments.map((segment) => ` ${segment}`).join(',\\n')}\\n${suffix}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manifest-schema.js","names":[],"sources":["../../src/schema/manifest-schema.ts"],"sourcesContent":["/**\n * Manifest schema → structured `SchemaDefinition` helpers (#2358).\n *\n * A manifest's `schema.ddl` string is an engine-neutral CREATE TABLE preview:\n * it carries no indexes and no per-engine type mapping, so nothing that needs\n * an executable schema may treat it as authoritative. Every consumer that\n * reads raw manifest JSON — `SchemaAggregator`, `createIsolatedTestDbFromManifest`\n * in `@happyvertical/smrt-vitest`, third-party tooling — should collect the\n * structured `columns` / `indexes` through this module and render them with\n * `getDDLStrategy(engine)`, exactly as `db:migrate` does.\n *\n * The cached `ddl` string is used only when a manifest object exposes no\n * structured columns at all (hand-authored or pre-structured manifests); that\n * legacy path keeps working but is not engine-complete.\n *\n * Keep this module light — type-only imports plus the DDL strategies and the\n * logger — so it can be re-exported from lightweight entry points without\n * pulling the registry/collection module graph in by itself.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { getDDLStrategy } from './ddl/index.js';\nimport {\n findCreateTableBodyRange,\n getSQLDefinitionComparisonKey,\n getSQLDefinitionIdentifier,\n isSQLTableConstraintDefinition,\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\nimport type { DatabaseEngine, EngineSpecificDDL } from './ddl/types.js';\nimport { quoteIdentifier } from './sql-identifiers.js';\nimport type {\n ColumnDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n} from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Column shape as it appears in raw manifest JSON. `ManifestColumnDefinition`\n * spells the default `default`; registry-side `ColumnDefinition` spells it\n * `defaultValue`. Hand-authored manifests use either.\n */\nexport interface ManifestColumnLike {\n type: string;\n primaryKey?: boolean;\n referenceKind?: ColumnDefinition['referenceKind'];\n notNull?: boolean;\n unique?: boolean;\n default?: unknown;\n defaultValue?: unknown;\n foreignKey?: ColumnDefinition['foreignKey'];\n check?: string;\n}\n\n/** Index shape as it appears in raw manifest JSON. */\nexport interface ManifestIndexLike {\n name: string;\n columns?: string[];\n unique?: boolean;\n where?: string;\n jsonPath?: IndexDefinition['jsonPath'];\n}\n\n/** The subset of a manifest object's `schema` this module reads. */\nexport interface ManifestSchemaLike {\n tableName: string;\n ddl?: string;\n columns?: Record<string, ManifestColumnLike>;\n indexes?: ManifestIndexLike[];\n version?: string;\n}\n\n/**\n * Convert one raw manifest column map into structured `ColumnDefinition`s.\n * Picks known fields only, so stray manifest keys never leak into DDL.\n */\nexport function manifestColumnsToDefinitions(\n columns: Record<string, ManifestColumnLike> | undefined,\n): Record<string, ColumnDefinition> {\n const result: Record<string, ColumnDefinition> = {};\n for (const [name, column] of Object.entries(columns || {})) {\n if (!column || typeof column.type !== 'string') continue;\n const definition: ColumnDefinition = {\n type: column.type as SQLDataType,\n };\n if (column.primaryKey !== undefined)\n definition.primaryKey = column.primaryKey;\n if (column.referenceKind !== undefined)\n definition.referenceKind = column.referenceKind;\n if (column.notNull !== undefined) definition.notNull = column.notNull;\n if (column.unique !== undefined) definition.unique = column.unique;\n const defaultValue =\n column.defaultValue !== undefined ? column.defaultValue : column.default;\n if (defaultValue !== undefined) definition.defaultValue = defaultValue;\n if (column.foreignKey) definition.foreignKey = { ...column.foreignKey };\n if (column.check) definition.check = column.check;\n result[name] = definition;\n }\n return result;\n}\n\n/**\n * Convert raw manifest indexes into structured `IndexDefinition`s, keeping\n * `where` (partial) and `jsonPath` (expression) targets — the two fields the\n * retired string-based renderers dropped.\n */\nexport function manifestIndexesToDefinitions(\n indexes: ManifestIndexLike[] | undefined,\n): IndexDefinition[] {\n const result: IndexDefinition[] = [];\n for (const index of indexes || []) {\n if (!index?.name) continue;\n const definition: IndexDefinition = {\n name: index.name,\n columns: Array.isArray(index.columns) ? [...index.columns] : [],\n };\n if (index.unique !== undefined) definition.unique = index.unique;\n if (index.where) definition.where = index.where;\n if (index.jsonPath?.column && index.jsonPath.path) {\n definition.jsonPath = { ...index.jsonPath };\n }\n result.push(definition);\n }\n return result;\n}\n\n/**\n * Convert one manifest object's `schema` into a `SchemaDefinition`.\n *\n * The cached `ddl` string is carried along (non-authoritative) so callers can\n * fall back to it when the manifest exposes no structured columns.\n */\nexport function manifestSchemaToDefinition(\n schema: ManifestSchemaLike,\n): SchemaDefinition {\n const columns = manifestColumnsToDefinitions(schema.columns);\n const foreignKeys = Object.entries(columns).flatMap(([column, definition]) =>\n definition.foreignKey\n ? [\n {\n column,\n referencesTable: definition.foreignKey.table,\n referencesColumn: definition.foreignKey.column,\n onDelete: definition.foreignKey.onDelete,\n onUpdate: definition.foreignKey.onUpdate,\n ...(definition.foreignKey.engines\n ? { engines: definition.foreignKey.engines }\n : {}),\n },\n ]\n : [],\n );\n return {\n tableName: schema.tableName,\n ddl: schema.ddl,\n columns,\n indexes: manifestIndexesToDefinitions(schema.indexes),\n triggers: [],\n foreignKeys,\n dependencies: foreignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== schema.tableName),\n version: schema.version ?? '',\n };\n}\n\n/**\n * One physical table collected from one or more manifest objects (STI\n * subclasses share a table and each contributes columns and indexes).\n */\nexport interface CollectedManifestTable {\n tableName: string;\n /**\n * Structured union of every contributor: columns first-wins by name,\n * indexes deduplicated by name.\n */\n definition: SchemaDefinition;\n /**\n * `true` when every contributor exposed structured columns, i.e. the whole\n * table is rendered by a DDL strategy. `false` means at least one\n * contributor carried only a cached `ddl` string: the CREATE TABLE is then\n * the strategy render of the structured contributors (if any) merged with\n * the cached strings of the column-less ones. The decision is per table,\n * not per contributor.\n */\n structured: boolean;\n /**\n * The contributors' cached `ddl` strings merged column-wise (legacy path).\n * Empty when no contributor carried a `ddl`.\n */\n legacyDdl: string;\n /** `<package>:<ClassName>` labels of the contributors, in encounter order. */\n sources: string[];\n}\n\n/**\n * Deterministic STI merge of two structured definitions: existing columns\n * win, new columns are appended, indexes are deduplicated by name.\n */\nexport function mergeSchemaDefinitionInto(\n target: SchemaDefinition,\n incoming: SchemaDefinition,\n): void {\n for (const [name, column] of Object.entries(incoming.columns)) {\n if (!(name in target.columns)) {\n target.columns[name] = column;\n }\n }\n const indexNames = new Set(target.indexes.map((index) => index.name));\n for (const index of incoming.indexes) {\n if (!indexNames.has(index.name)) {\n indexNames.add(index.name);\n target.indexes.push(index);\n }\n }\n const foreignKeyKeys = new Set(\n target.foreignKeys.map(\n (foreignKey) =>\n `${foreignKey.column}\\0${foreignKey.referencesTable}\\0${foreignKey.referencesColumn}`,\n ),\n );\n for (const foreignKey of incoming.foreignKeys) {\n const key = `${foreignKey.column}\\0${foreignKey.referencesTable}\\0${foreignKey.referencesColumn}`;\n if (!foreignKeyKeys.has(key)) {\n foreignKeyKeys.add(key);\n target.foreignKeys.push(foreignKey);\n }\n }\n target.dependencies = Array.from(\n new Set([...target.dependencies, ...incoming.dependencies]),\n ).sort();\n}\n\n/**\n * Collect manifest schemas into physical tables.\n *\n * Accepts the `schema` entries of manifest objects (any package, any manifest)\n * in encounter order and groups them by `tableName`, merging STI contributors\n * structurally. Objects without a `schema`/`tableName` are skipped by the\n * caller; objects with neither columns nor `ddl` are ignored here.\n */\nexport function collectManifestTables(\n entries: Iterable<{ schema: ManifestSchemaLike; source: string }>,\n): Map<string, CollectedManifestTable> {\n const tables = new Map<string, CollectedManifestTable>();\n // Structured contributors whose cached ddl declares table constraints. Only\n // a fully structured table drops them (a mixed table re-merges the string),\n // so the warning is decided after collection.\n const constraintCarriers = new Map<\n string,\n Array<{ source: string; constraints: string[] }>\n >();\n\n for (const { schema, source } of entries) {\n if (!schema?.tableName) continue;\n const definition = manifestSchemaToDefinition(schema);\n const hasColumns = Object.keys(definition.columns).length > 0;\n const ddl = typeof schema.ddl === 'string' ? schema.ddl : '';\n if (!hasColumns && !ddl.trim()) continue;\n\n if (hasColumns && ddl.trim()) {\n const constraints = cachedDdlTableConstraints(ddl);\n if (constraints.length > 0) {\n const carriers = constraintCarriers.get(schema.tableName) ?? [];\n carriers.push({ source, constraints });\n constraintCarriers.set(schema.tableName, carriers);\n }\n }\n\n const existing = tables.get(schema.tableName);\n if (!existing) {\n tables.set(schema.tableName, {\n tableName: schema.tableName,\n definition,\n structured: hasColumns,\n legacyDdl: ddl,\n sources: [source],\n });\n continue;\n }\n\n mergeSchemaDefinitionInto(existing.definition, definition);\n existing.structured = existing.structured && hasColumns;\n existing.legacyDdl = existing.legacyDdl\n ? ddl\n ? mergeManifestDDL(existing.legacyDdl, ddl)\n : existing.legacyDdl\n : ddl;\n existing.sources.push(source);\n }\n\n for (const [tableName, carriers] of constraintCarriers) {\n if (!tables.get(tableName)?.structured) continue;\n for (const { source, constraints } of carriers) {\n warnAboutDroppedTableConstraints(tableName, source, constraints);\n }\n }\n\n return tables;\n}\n\n/**\n * Render a collected table for one engine.\n *\n * Structured tables go through the engine strategy for the CREATE TABLE\n * (per-engine types, inline UNIQUE where the engine needs it) and for every\n * index (partial `WHERE`, JSON-path expressions, engine-specific syntax).\n * Tables with a column-less contributor keep that contributor's cached `ddl`\n * string, made minimally safe with `materializeManifestDDLForEngine`, merged\n * on top of the strategy render of any structured contributors — so a\n * structured STI base never loses its engine typing to a hand-authored\n * subclass, and the subclass's extra columns and table constraints survive.\n * Indexes always render through the strategy — the string never carried them.\n */\nexport function renderCollectedManifestTable(\n table: CollectedManifestTable,\n engine: DatabaseEngine,\n): EngineSpecificDDL {\n const strategy = getDDLStrategy(engine);\n const hasColumns = Object.keys(table.definition.columns).length > 0;\n let createTable: string;\n if (table.structured) {\n createTable = strategy.generateCreateTable(table.definition);\n } else if (hasColumns) {\n createTable = mergeManifestDDL(\n strategy.generateCreateTable(table.definition),\n materializeManifestDDLForEngine(table.legacyDdl, engine),\n );\n } else {\n createTable = materializeManifestDDLForEngine(table.legacyDdl, engine);\n }\n\n // Engines that need UNIQUE inline for ON CONFLICT (DuckDB, JSON) get it\n // from `generateCreateTable` on the structured path and skip those indexes\n // in `generateIndexes`. A cached string never carried them, so a table with\n // a column-less contributor would lose its uniqueness entirely; append the\n // missing constraints as table elements (deduplicated against any the\n // string already declares).\n if (!table.structured && strategy.requiresInlineUnique()) {\n const uniqueElements = table.definition.indexes\n .filter(\n (index) =>\n index.unique &&\n !index.where &&\n !index.jsonPath &&\n index.columns.length > 0,\n )\n .map(\n (index) =>\n `UNIQUE(${index.columns.map((column) => quoteIdentifier(column)).join(', ')})`,\n );\n // Hand-authored strings may spell the same constraint without quotes\n // (`UNIQUE(tenant_id)`); compare quote- and whitespace-insensitively so\n // the synthesized element is not added beside it.\n const declared = new Set(\n cachedDdlTableConstraints(createTable).map(normalizeTableElement),\n );\n const missing = uniqueElements.filter(\n (element) => !declared.has(normalizeTableElement(element)),\n );\n if (missing.length > 0) {\n createTable = mergeManifestDDL(\n createTable,\n `CREATE TABLE ${quoteIdentifier(table.tableName)} (\\n ${missing.join(',\\n ')}\\n)`,\n );\n }\n }\n\n return {\n createTable,\n indexes: strategy.generateIndexes(table.definition),\n triggers: strategy.generateTriggers(table.definition),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Legacy cached-DDL string merge (column-less manifests only)\n// ---------------------------------------------------------------------------\n\nfunction parseDefinitionsFromDDL(ddl: string): {\n columns: Map<string, string>;\n tableElements: string[];\n} {\n const columns = new Map<string, string>();\n const tableElements: string[] = [];\n const bodyRange = findCreateTableBodyRange(ddl);\n if (!bodyRange) return { columns, tableElements };\n const [bodyStart, bodyEnd] = bodyRange;\n\n const segments = tokenizeSQLDDLBody(ddl.slice(bodyStart + 1, bodyEnd));\n\n for (const segment of segments) {\n const identifier = getSQLDefinitionIdentifier(segment);\n if (!identifier) continue;\n if (isSQLTableConstraintDefinition(segment)) {\n tableElements.push(segment);\n continue;\n }\n\n columns.set(identifier.name, segment);\n }\n\n return { columns, tableElements };\n}\n\n/**\n * Table-level constraints (`UNIQUE(...)`, `CHECK (...)`, `CONSTRAINT ...`,\n * ...) declared in a cached CREATE TABLE string. Structured `columns` cannot\n * represent them, so a structured render drops them — exactly as `db:migrate`\n * does. Exposed so callers can warn instead of losing them silently.\n *\n * @internal\n */\nexport function cachedDdlTableConstraints(ddl: string): string[] {\n return parseDefinitionsFromDDL(ddl).tableElements;\n}\n\n/** Quote/whitespace/case-insensitive key for a table constraint element. */\nfunction normalizeTableElement(element: string): string {\n return element.replace(/\"/g, '').replace(/\\s+/g, '').toLowerCase();\n}\n\nconst warnedDroppedConstraints = new Set<string>();\n\n/**\n * Warn once per process per table+source when a structured contributor's\n * cached `ddl` declares table constraints that the structured render cannot\n * carry. The structured schema is authoritative: a constraint that lives only\n * in the string never reaches `db:migrate` either, so rendering it here would\n * recreate the test/production drift #2358 removes.\n */\nfunction warnAboutDroppedTableConstraints(\n tableName: string,\n source: string,\n constraints: string[],\n): void {\n const key = `${tableName}\\0${source}`;\n if (warnedDroppedConstraints.has(key)) return;\n warnedDroppedConstraints.add(key);\n logger.warn(\n `[manifest-schema] ${source}: cached ddl for table \"${tableName}\" declares ` +\n `${constraints.length} table constraint(s) that structured columns cannot ` +\n `carry (${constraints.join('; ')}). They are not rendered — the ` +\n `structured schema is authoritative (#2358). Declare uniqueness through ` +\n `indexes/conflictColumns or a column-level constraint instead.`,\n );\n}\n\n/**\n * Merge two cached CREATE TABLE strings for the same table (STI subclasses):\n * the union of columns, existing definitions winning, new columns inserted\n * before any trailing table constraints, missing table constraints appended.\n *\n * Only the legacy column-less manifest path uses this; structured manifests\n * merge through `mergeSchemaDefinitionInto`.\n */\nfunction mergeManifestDDL(existingDDL: string, newDDL: string): string {\n const existingDefinitions = parseDefinitionsFromDDL(existingDDL);\n const newDefinitions = parseDefinitionsFromDDL(newDDL);\n\n const missingCols: string[] = [];\n for (const [name, def] of newDefinitions.columns) {\n if (!existingDefinitions.columns.has(name)) {\n missingCols.push(def);\n }\n }\n\n const existingTableElements = new Set(\n existingDefinitions.tableElements.map(getSQLDefinitionComparisonKey),\n );\n const missingTableElements = newDefinitions.tableElements.filter(\n (definition) =>\n !existingTableElements.has(getSQLDefinitionComparisonKey(definition)),\n );\n\n if (missingCols.length === 0 && missingTableElements.length === 0) {\n return existingDDL;\n }\n\n const bodyRange = findCreateTableBodyRange(existingDDL);\n if (!bodyRange) return existingDDL;\n const [bodyStart, bodyEnd] = bodyRange;\n const prefix = existingDDL.slice(0, bodyStart + 1);\n const body = existingDDL.slice(bodyStart + 1, bodyEnd);\n const suffix = existingDDL.slice(bodyEnd);\n const segments = tokenizeSQLDDLBody(body);\n const firstConstraintIndex = segments.findIndex((segment) =>\n isSQLTableConstraintDefinition(segment),\n );\n const insertionIndex =\n firstConstraintIndex === -1 ? segments.length : firstConstraintIndex;\n const mergedSegments = [\n ...segments.slice(0, insertionIndex),\n ...missingCols,\n ...segments.slice(insertionIndex),\n ...missingTableElements,\n ];\n\n return `${prefix}\\n${mergedSegments.map((segment) => ` ${segment}`).join(',\\n')}\\n${suffix}`;\n}\n"],"mappings":"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"}
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ColumnDefinition {
|
|
|
20
20
|
column: string;
|
|
21
21
|
onDelete?: ForeignKeyAction;
|
|
22
22
|
onUpdate?: ForeignKeyAction;
|
|
23
|
+
/** Engines on which the physical constraint is emitted. */
|
|
24
|
+
engines?: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'>;
|
|
23
25
|
};
|
|
24
26
|
check?: string;
|
|
25
27
|
description?: string;
|
|
@@ -95,6 +97,8 @@ export interface ForeignKeyDefinition {
|
|
|
95
97
|
referencesColumn: string;
|
|
96
98
|
onDelete?: ForeignKeyAction;
|
|
97
99
|
onUpdate?: ForeignKeyAction;
|
|
100
|
+
/** Engines on which the physical constraint is emitted. */
|
|
101
|
+
engines?: Array<'postgres' | 'sqlite' | 'duckdb' | 'json'>;
|
|
98
102
|
}
|
|
99
103
|
export interface SchemaDefinition {
|
|
100
104
|
tableName: string;
|
|
@@ -285,7 +289,7 @@ export interface SchemaChangeAdvisory {
|
|
|
285
289
|
*/
|
|
286
290
|
export interface SchemaChange {
|
|
287
291
|
/** Type of change */
|
|
288
|
-
type: 'add_table' | 'drop_table' | 'add_column' | 'drop_column' | 'alter_column' | 'orphan_column' | 'add_index' | 'add_foreign_key' | 'drop_index' | 'orphan_index' | 'type_mismatch' | 'type_upgrade';
|
|
292
|
+
type: 'add_table' | 'drop_table' | 'add_column' | 'drop_column' | 'alter_column' | 'orphan_column' | 'add_index' | 'add_foreign_key' | 'drop_foreign_key' | 'drop_index' | 'orphan_index' | 'type_mismatch' | 'type_upgrade';
|
|
289
293
|
/** Affected table name */
|
|
290
294
|
table: string;
|
|
291
295
|
/** Column or index name (if applicable) */
|
|
@@ -294,7 +298,7 @@ export interface SchemaChange {
|
|
|
294
298
|
column?: ColumnDefinition;
|
|
295
299
|
/** Index definition (for add_index) */
|
|
296
300
|
index?: IndexDefinition;
|
|
297
|
-
/** Foreign-key definition (for add_foreign_key). */
|
|
301
|
+
/** Foreign-key definition (for add_foreign_key/drop_foreign_key). */
|
|
298
302
|
foreignKey?: ForeignKeyDefinition;
|
|
299
303
|
/**
|
|
300
304
|
* For type mismatches/upgrades: expected vs actual type. For
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,GACN,SAAS,GACT,MAAM,GACN,WAAW,GAKX,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,iBAAiB,GAAG,UAAU,CAAC;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,gBAAgB,CAAC;QAC5B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseInterface } from '@happyvertical/sql';
|
|
2
2
|
import { SmrtObject } from '../object.js';
|
|
3
3
|
import { FieldDefinition } from '../scanner/types.js';
|
|
4
|
-
import { DatabaseEngine } from './ddl/
|
|
4
|
+
import { DatabaseEngine } from './ddl/index.js';
|
|
5
5
|
export { materializeManifestDDLForEngine, tokenizeSQLDDLBody, } from './ddl/materialize-manifest.js';
|
|
6
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';
|
|
@@ -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,
|
|
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,EAAE,KAAK,cAAc,EAAgB,MAAM,gBAAgB,CAAC;AAInE,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,CA6Ff"}
|
package/dist/schema/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MAX_IDENTIFIER_BYTES, assertIdentifierFits, identifierByteLength, isJsonPathIndex, isStiSubtypeUniqueIndex, renderIndexTarget, shortenIdentifier } from "./index-utils.js";
|
|
2
|
-
import {
|
|
2
|
+
import { schemaDependenciesForEngine } from "./foreign-key-ddl.js";
|
|
3
|
+
import { detectEngine } from "./ddl/index.js";
|
|
3
4
|
import { tableNameFromClass } from "../utils.js";
|
|
4
5
|
import { ObjectRegistry } from "../registry.js";
|
|
5
6
|
import { SchemaManager } from "./schema-manager.js";
|
|
@@ -99,12 +100,12 @@ async function ensureSchema(db, className) {
|
|
|
99
100
|
if (!schemaDefinition?.tableName) throw new Error(`No schema definition found for class '${className}'. Run the manifest generation/build step before preparing schema.`);
|
|
100
101
|
const effectiveSchemaDefinition = ObjectRegistry.getAllSchemasAsDefinitions()[schemaDefinition.tableName] ?? schemaDefinition;
|
|
101
102
|
const allSchemas = ObjectRegistry.getAllSchemasAsDefinitions();
|
|
103
|
+
const engine = typeof db.exportTable === "function" ? "json" : detectEngine(db.url);
|
|
102
104
|
const required = /* @__PURE__ */ new Map();
|
|
103
105
|
const collect = (schema) => {
|
|
104
106
|
if (required.has(schema.tableName)) return;
|
|
105
107
|
required.set(schema.tableName, schema);
|
|
106
|
-
const
|
|
107
|
-
for (const dependency of dependencies) {
|
|
108
|
+
for (const dependency of schemaDependenciesForEngine(schema, engine)) {
|
|
108
109
|
const dependencySchema = allSchemas[dependency];
|
|
109
110
|
if (dependencySchema) collect(dependencySchema);
|
|
110
111
|
}
|
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 { schemaForeignKeys } from './foreign-key-ddl.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 // A single requested class can be part of a mutual FK cycle. Build the\n // complete registered dependency closure and let SchemaManager plan it as\n // one unit; creating the requested table alone would leave the first\n // PostgreSQL CREATE referencing a table that does not exist (#2413).\n const allSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n const required = new Map<string, typeof effectiveSchemaDefinition>();\n const collect = (schema: typeof effectiveSchemaDefinition): void => {\n if (required.has(schema.tableName)) return;\n required.set(schema.tableName, schema);\n const dependencies = new Set([\n ...schema.dependencies,\n ...schemaForeignKeys(schema).map(\n (foreignKey) => foreignKey.referencesTable,\n ),\n ]);\n for (const dependency of dependencies) {\n const dependencySchema = allSchemas[dependency];\n if (dependencySchema) collect(dependencySchema);\n }\n };\n collect(effectiveSchemaDefinition);\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTables([...required.values()]);\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, detectEngine } from './ddl/index.js';\nimport { schemaDependenciesForEngine } from './foreign-key-ddl.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 // A single requested class can be part of a mutual FK cycle. Build the\n // complete registered dependency closure and let SchemaManager plan it as\n // one unit; creating the requested table alone would leave the first\n // PostgreSQL CREATE referencing a table that does not exist (#2413).\n const allSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n const engine =\n typeof (db as { exportTable?: unknown }).exportTable === 'function'\n ? 'json'\n : detectEngine(db.url);\n const required = new Map<string, typeof effectiveSchemaDefinition>();\n const collect = (schema: typeof effectiveSchemaDefinition): void => {\n if (required.has(schema.tableName)) return;\n required.set(schema.tableName, schema);\n for (const dependency of schemaDependenciesForEngine(schema, engine)) {\n const dependencySchema = allSchemas[dependency];\n if (dependencySchema) collect(dependencySchema);\n }\n };\n collect(effectiveSchemaDefinition);\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTables([...required.values()]);\n}\n"],"mappings":"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"}
|