@prisma-next/family-sql 0.14.0-dev.9 → 0.14.0-dev.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
  2. package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
  3. package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-BQgad8Zc.d.mts} +30 -8
  4. package/dist/control-adapter-BQgad8Zc.d.mts.map +1 -0
  5. package/dist/control-adapter.d.mts +1 -1
  6. package/dist/control.d.mts +279 -34
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +378 -1005
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/diff.d.mts +102 -0
  11. package/dist/diff.d.mts.map +1 -0
  12. package/dist/diff.mjs +14 -0
  13. package/dist/diff.mjs.map +1 -0
  14. package/dist/foreign-key-index-backing-BP71iI-Q.mjs +32 -0
  15. package/dist/foreign-key-index-backing-BP71iI-Q.mjs.map +1 -0
  16. package/dist/ir.d.mts +26 -9
  17. package/dist/ir.d.mts.map +1 -1
  18. package/dist/ir.mjs +2 -2
  19. package/dist/ir.mjs.map +1 -1
  20. package/dist/migration.d.mts +11 -2
  21. package/dist/migration.d.mts.map +1 -1
  22. package/dist/migration.mjs +7 -0
  23. package/dist/migration.mjs.map +1 -1
  24. package/dist/pack.mjs +1 -1
  25. package/dist/psl-infer.d.mts +119 -0
  26. package/dist/psl-infer.d.mts.map +1 -0
  27. package/dist/psl-infer.mjs +366 -0
  28. package/dist/psl-infer.mjs.map +1 -0
  29. package/dist/schema-differ-DnoopSXm.d.mts +45 -0
  30. package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
  31. package/dist/schema-verify-W3r631Jh.mjs +226 -0
  32. package/dist/schema-verify-W3r631Jh.mjs.map +1 -0
  33. package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-C75cfMSS.mjs} +46 -20
  34. package/dist/sql-contract-serializer-C75cfMSS.mjs.map +1 -0
  35. package/dist/{types-kgstZ_Zd.d.mts → types-BPv_y7iS.d.mts} +36 -142
  36. package/dist/types-BPv_y7iS.d.mts.map +1 -0
  37. package/package.json +26 -26
  38. package/src/core/authoring-entity-types.ts +12 -37
  39. package/src/core/control-adapter.ts +11 -5
  40. package/src/core/control-instance.ts +212 -42
  41. package/src/core/control-target-descriptor.ts +98 -0
  42. package/src/core/diff/schema-verify.ts +324 -0
  43. package/src/core/diff/sql-schema-diff.ts +41 -0
  44. package/src/core/diff/verifier-disposition.ts +30 -0
  45. package/src/core/foreign-key-index-backing.ts +37 -0
  46. package/src/core/ir/sql-contract-serializer-base.ts +70 -56
  47. package/src/core/ir/sql-contract-serializer.ts +5 -7
  48. package/src/core/ir/sql-schema-verifier-base.ts +5 -5
  49. package/src/core/migrations/contract-to-schema-ir.ts +141 -37
  50. package/src/core/migrations/control-policy.ts +66 -115
  51. package/src/core/migrations/field-event-planner.ts +2 -2
  52. package/src/core/migrations/native-type-expander.ts +28 -0
  53. package/src/core/migrations/schema-differ.ts +41 -0
  54. package/src/core/migrations/types.ts +33 -37
  55. package/src/core/psl-contract-infer/name-transforms.ts +15 -0
  56. package/src/core/psl-contract-infer/printer-config.ts +14 -6
  57. package/src/core/psl-contract-infer/relation-inference.ts +27 -1
  58. package/src/core/sql-migration.ts +12 -1
  59. package/src/exports/control.ts +12 -2
  60. package/src/exports/diff.ts +25 -0
  61. package/src/exports/psl-infer.ts +40 -0
  62. package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
  63. package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
  64. package/dist/schema-verify.d.mts +0 -39
  65. package/dist/schema-verify.d.mts.map +0 -1
  66. package/dist/schema-verify.mjs +0 -2
  67. package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
  68. package/dist/test-utils.d.mts +0 -2
  69. package/dist/test-utils.mjs +0 -2
  70. package/dist/types-kgstZ_Zd.d.mts.map +0 -1
  71. package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
  72. package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
  73. package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
  74. package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
  75. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  76. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  77. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  78. package/src/core/schema-verify/control-verify-emit.ts +0 -46
  79. package/src/core/schema-verify/verifier-disposition.ts +0 -58
  80. package/src/core/schema-verify/verify-helpers.ts +0 -820
  81. package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
  82. package/src/exports/schema-verify.ts +0 -18
  83. package/src/exports/test-utils.ts +0 -9
@@ -1,1501 +0,0 @@
1
- import { assertUniqueCodecOwner, dispositionForCategory } from "@prisma-next/framework-components/control";
2
- import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
3
- import { ifDefined } from "@prisma-next/utils/defined";
4
- import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
5
- import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
6
- import { effectiveControlPolicy } from "@prisma-next/contract/types";
7
- import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
8
- //#region src/core/assembly.ts
9
- function hasCodecControlHooks(descriptor) {
10
- if (typeof descriptor !== "object" || descriptor === null) return false;
11
- const hooks = descriptor.types?.codecTypes?.controlPlaneHooks;
12
- return hooks !== null && hooks !== void 0 && typeof hooks === "object";
13
- }
14
- function extractCodecControlHooks(descriptors) {
15
- const hooks = /* @__PURE__ */ new Map();
16
- const owners = /* @__PURE__ */ new Map();
17
- for (const descriptor of descriptors) {
18
- if (typeof descriptor !== "object" || descriptor === null) continue;
19
- if (!hasCodecControlHooks(descriptor)) continue;
20
- const controlPlaneHooks = descriptor.types.codecTypes.controlPlaneHooks;
21
- for (const [codecId, hook] of Object.entries(controlPlaneHooks)) {
22
- assertUniqueCodecOwner({
23
- codecId,
24
- owners,
25
- descriptorId: descriptor.id,
26
- entityLabel: "control hooks",
27
- entityOwnershipLabel: "owner"
28
- });
29
- hooks.set(codecId, hook);
30
- owners.set(codecId, descriptor.id);
31
- }
32
- }
33
- return hooks;
34
- }
35
- //#endregion
36
- //#region src/core/migrations/contract-to-schema-ir.ts
37
- function convertColumn(name, column, storageTypes, expandNativeType, renderDefault) {
38
- const resolved = resolveColumnTypeMetadata(column, storageTypes);
39
- return {
40
- name,
41
- nativeType: expandNativeType ? expandNativeType({
42
- nativeType: resolved.nativeType,
43
- codecId: resolved.codecId,
44
- ...ifDefined("typeParams", resolved.typeParams)
45
- }) : resolved.nativeType,
46
- nullable: column.nullable,
47
- ...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0)
48
- };
49
- }
50
- function resolveColumnTypeMetadata(column, storageTypes) {
51
- if (!column.typeRef) return column;
52
- const referenced = storageTypes[column.typeRef];
53
- if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
54
- if (isStorageTypeInstance(referenced)) return {
55
- codecId: referenced.codecId,
56
- nativeType: referenced.nativeType,
57
- typeParams: referenced.typeParams
58
- };
59
- throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`);
60
- }
61
- /**
62
- * Resolves a `ValueSetRef` to its permitted values from the contract storage.
63
- *
64
- * Throws when the referenced namespace or value-set is absent — this indicates
65
- * the contract was built incorrectly (the check and the value-set must be
66
- * co-emitted by the lowering step). Used by `convertCheck` (schema-IR
67
- * projection), `verifyCheckConstraints` (verification), and
68
- * `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
69
- * the resolved values and the error behavior on a missing reference.
70
- */
71
- function allStrings(values) {
72
- return values.every((value) => typeof value === "string");
73
- }
74
- function resolveValueSetValues(ref, storage, contextLabel) {
75
- const ns = storage.namespaces[ref.namespaceId];
76
- if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
77
- const valueSet = ns.entries.valueSet?.[ref.entityName];
78
- if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
79
- const values = valueSet.values;
80
- if (!allStrings(values)) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" in namespace "${ref.namespaceId}" has a non-string value; numeric-enum CHECK constraints are not yet supported (${contextLabel})`);
81
- return values;
82
- }
83
- /**
84
- * Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
85
- * resolving the permitted values from the storage value-set it references.
86
- *
87
- * The `CheckConstraint.valueSet` ref points to
88
- * `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
89
- * values are lifted directly from `StorageValueSet.values` so verification
90
- * compares value sets, not SQL predicate strings.
91
- *
92
- * Throws if the referenced namespace or value-set is absent — this
93
- * indicates the contract was built incorrectly (the check and the
94
- * value-set must be co-emitted by the lowering step).
95
- */
96
- function convertCheck(check, storage) {
97
- const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
98
- return {
99
- name: check.name,
100
- column: check.column,
101
- permittedValues
102
- };
103
- }
104
- function convertUnique(unique) {
105
- return {
106
- columns: unique.columns,
107
- ...ifDefined("name", unique.name)
108
- };
109
- }
110
- function convertIndex(index) {
111
- return {
112
- columns: index.columns,
113
- unique: false,
114
- ...ifDefined("name", index.name)
115
- };
116
- }
117
- function convertForeignKey(fk) {
118
- return {
119
- columns: fk.source.columns,
120
- referencedTable: fk.target.tableName,
121
- referencedSchema: fk.target.namespaceId,
122
- referencedColumns: fk.target.columns,
123
- ...ifDefined("name", fk.name),
124
- ...ifDefined("onDelete", fk.onDelete),
125
- ...ifDefined("onUpdate", fk.onUpdate)
126
- };
127
- }
128
- function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
129
- const columns = {};
130
- for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
131
- const satisfiedIndexColumns = new Set([
132
- ...table.indexes.map((idx) => idx.columns.join(",")),
133
- ...table.uniques.map((unique) => unique.columns.join(",")),
134
- ...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
135
- ]);
136
- const fkBackingIndexes = [];
137
- for (const fk of table.foreignKeys) {
138
- if (fk.index === false) continue;
139
- const key = fk.source.columns.join(",");
140
- if (satisfiedIndexColumns.has(key)) continue;
141
- fkBackingIndexes.push({
142
- columns: fk.source.columns,
143
- unique: false,
144
- name: defaultIndexName(name, fk.source.columns)
145
- });
146
- satisfiedIndexColumns.add(key);
147
- }
148
- const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
149
- return {
150
- name,
151
- columns,
152
- ...ifDefined("primaryKey", table.primaryKey),
153
- foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
154
- uniques: table.uniques.map(convertUnique),
155
- indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
156
- ...ifDefined("checks", checks)
157
- };
158
- }
159
- /**
160
- * Detects destructive changes between two contract storages.
161
- *
162
- * The additive-only planner silently ignores removals (tables, columns).
163
- * This function detects those removals so callers can report them as conflicts
164
- * rather than silently producing an empty plan.
165
- *
166
- * Returns an empty array if no destructive changes are found.
167
- */
168
- function detectDestructiveChanges(from, to) {
169
- if (!from) return [];
170
- const hasOwn = (value, key) => Object.hasOwn(value, key);
171
- const conflicts = [];
172
- const namespaceIds = [...new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
173
- for (const namespaceId of namespaceIds) {
174
- const fromNs = from.namespaces[namespaceId];
175
- const toNs = to.namespaces[namespaceId];
176
- const fromTables = fromNs?.entries.table;
177
- if (!fromTables) continue;
178
- for (const tableName of Object.keys(fromTables)) {
179
- const toTableRaw = toNs?.entries.table?.[tableName];
180
- if (!(toTableRaw instanceof StorageTable)) {
181
- conflicts.push({
182
- kind: "tableRemoved",
183
- summary: `Table "${tableName}" was removed`
184
- });
185
- continue;
186
- }
187
- const toTable = toTableRaw;
188
- const fromTableRaw = fromTables[tableName];
189
- if (!(fromTableRaw instanceof StorageTable)) continue;
190
- const fromTable = fromTableRaw;
191
- for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
192
- kind: "columnRemoved",
193
- summary: `Column "${tableName}"."${columnName}" was removed`
194
- });
195
- }
196
- }
197
- return conflicts;
198
- }
199
- /**
200
- * Converts a `Contract` to `SqlSchemaIR`.
201
- *
202
- * Reads `contract.storage` for tables and `contract.storage.types` for type
203
- * annotations. Storage-type annotations are written under
204
- * `options.annotationNamespace`.
205
- *
206
- * Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
207
- * structural information. When `expandNativeType` is provided, parameterized types
208
- * are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
209
- * resulting IR compares correctly against the "to" contract during planning.
210
- *
211
- * Returns an empty schema IR when `contract` is `null` (new project).
212
- */
213
- function contractToSchemaIR(contract, options) {
214
- if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
215
- if (!contract) return { tables: {} };
216
- const storage = contract.storage;
217
- const storageTypes = { ...storage.types ?? {} };
218
- const tables = {};
219
- for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
220
- if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`);
221
- const tableDef = tableDefRaw;
222
- if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
223
- tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
224
- }
225
- return {
226
- tables,
227
- ...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumNamespaceSchema))
228
- };
229
- }
230
- function deriveAnnotations(storage, annotationNamespace, _resolveEnumNamespaceSchema) {
231
- const storageTypes = {};
232
- for (const typeInstance of Object.values(storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
233
- const envelope = { ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {} };
234
- if (Object.keys(envelope).length === 0) return void 0;
235
- return { [annotationNamespace]: envelope };
236
- }
237
- //#endregion
238
- //#region src/core/schema-verify/verifier-disposition.ts
239
- /**
240
- * Classifies the relational verifier issue kinds the SQL family emits (tables,
241
- * columns, constraints, indexes, defaults, enum types) into the target-neutral
242
- * categories the framework grades. The relational vocabulary lives here, in the
243
- * SQL domain — the framework never switches over `extra_foreign_key` and friends.
244
- */
245
- function classifySqlVerifierIssueKind(kind) {
246
- switch (kind) {
247
- case "extra_column": return "extraNestedElement";
248
- case "extra_primary_key":
249
- case "extra_foreign_key":
250
- case "extra_unique_constraint":
251
- case "extra_index":
252
- case "extra_validator":
253
- case "extra_default": return "extraAuxiliary";
254
- case "extra_table": return "extraTopLevelObject";
255
- case "missing_schema":
256
- case "missing_table":
257
- case "missing_column":
258
- case "type_missing":
259
- case "default_missing": return "declaredMissing";
260
- case "type_values_mismatch":
261
- case "enum_values_changed":
262
- case "check_mismatch": return "valueDrift";
263
- case "type_mismatch":
264
- case "nullability_mismatch":
265
- case "primary_key_mismatch":
266
- case "foreign_key_mismatch":
267
- case "unique_constraint_mismatch":
268
- case "index_mismatch":
269
- case "default_mismatch": return "declaredIncompatible";
270
- case "check_missing": return "declaredMissing";
271
- case "check_removed": return "extraAuxiliary";
272
- }
273
- }
274
- function verifierDisposition(controlPolicy, issueKind) {
275
- return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
276
- }
277
- //#endregion
278
- //#region src/core/schema-verify/control-verify-emit.ts
279
- /**
280
- * Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
281
- * issue and a status-stamped verification node. Returns the resolved outcome so
282
- * the caller never re-grades the same issue.
283
- */
284
- function emitIssueAndNodeUnderControlPolicy(controlPolicy, issue, node, issues, nodes) {
285
- const disposition = verifierDisposition(controlPolicy, issue.kind);
286
- if (disposition === "suppress") return disposition;
287
- issues.push(issue);
288
- nodes.push({
289
- ...node,
290
- status: disposition
291
- });
292
- return disposition;
293
- }
294
- /**
295
- * Grades `issue` under `controlPolicy` and, unless suppressed, pushes the issue
296
- * (no verification node). Returns the resolved outcome so the caller maps it to
297
- * a node status itself without re-grading.
298
- */
299
- function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
300
- const disposition = verifierDisposition(controlPolicy, issue.kind);
301
- if (disposition === "suppress") return disposition;
302
- issues.push(issue);
303
- return disposition;
304
- }
305
- //#endregion
306
- //#region src/core/schema-verify/verify-helpers.ts
307
- function indexOptionsLooselyEqual(a, b) {
308
- const aKeys = a ? Object.keys(a).sort() : [];
309
- const bKeys = b ? Object.keys(b).sort() : [];
310
- if (aKeys.length !== bKeys.length) return false;
311
- for (let i = 0; i < aKeys.length; i += 1) if (aKeys[i] !== bKeys[i]) return false;
312
- if (aKeys.length === 0) return true;
313
- for (const key of aKeys) if (String(a[key]) !== String(b[key])) return false;
314
- return true;
315
- }
316
- function indexExtrasMatch(contractIndex, schemaIndex) {
317
- if ((contractIndex.type ?? null) !== (schemaIndex.type ?? null)) return false;
318
- return indexOptionsLooselyEqual(contractIndex.options, schemaIndex.options);
319
- }
320
- /**
321
- * Compares two arrays of strings for equality (order-sensitive).
322
- */
323
- function arraysEqual(a, b) {
324
- if (a.length !== b.length) return false;
325
- for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
326
- return true;
327
- }
328
- /**
329
- * Checks if a unique constraint requirement is satisfied by the given columns.
330
- *
331
- * Semantic satisfaction: a unique constraint requirement can be satisfied by:
332
- * - A unique constraint with the same columns, OR
333
- * - A unique index with the same columns
334
- *
335
- * @param uniques - The unique constraints in the schema table
336
- * @param indexes - The indexes in the schema table
337
- * @param columns - The columns required by the unique constraint
338
- * @returns true if the requirement is satisfied
339
- */
340
- function isUniqueConstraintSatisfied(uniques, indexes, columns) {
341
- if (uniques.some((unique) => arraysEqual(unique.columns, columns))) return true;
342
- return indexes.some((index) => index.unique && arraysEqual(index.columns, columns));
343
- }
344
- /**
345
- * Checks if an index requirement is satisfied by the given columns.
346
- *
347
- * Semantic satisfaction: a non-unique index requirement can be satisfied by:
348
- * - Any index (unique or non-unique) with the same columns, OR
349
- * - A unique constraint with the same columns (stronger satisfies weaker)
350
- *
351
- * @param indexes - The indexes in the schema table
352
- * @param uniques - The unique constraints in the schema table
353
- * @param columns - The columns required by the index
354
- * @returns true if the requirement is satisfied
355
- */
356
- function isIndexSatisfied(indexes, uniques, columns) {
357
- if (indexes.some((index) => arraysEqual(index.columns, columns))) return true;
358
- return uniques.some((unique) => arraysEqual(unique.columns, columns));
359
- }
360
- /**
361
- * Verifies primary key matches between contract and schema.
362
- * Returns 'pass' or 'fail'.
363
- *
364
- * Uses semantic satisfaction: identity is based on (table + kind + columns).
365
- * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
366
- */
367
- function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableControlPolicy, issues) {
368
- if (!schemaPK) {
369
- const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
370
- kind: "primary_key_mismatch",
371
- table: tableName,
372
- namespaceId,
373
- expected: contractPK.columns.join(", "),
374
- message: `Table "${tableName}" is missing primary key`
375
- }, issues);
376
- return outcome === "suppress" ? "pass" : outcome;
377
- }
378
- if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
379
- const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
380
- kind: "primary_key_mismatch",
381
- table: tableName,
382
- namespaceId,
383
- expected: contractPK.columns.join(", "),
384
- actual: schemaPK.columns.join(", "),
385
- message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
386
- }, issues);
387
- return outcome === "suppress" ? "pass" : outcome;
388
- }
389
- return "pass";
390
- }
391
- /**
392
- * Verifies foreign keys match between contract and schema.
393
- * Returns verification nodes for the tree.
394
- *
395
- * Uses semantic satisfaction: identity is based on (table + columns + referenced table + referenced columns).
396
- * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
397
- */
398
- function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
399
- const nodes = [];
400
- for (const contractFK of contractFKs) {
401
- const fkPath = `${tablePath}.foreignKeys[${contractFK.source.columns.join(",")}]`;
402
- const matchingFK = schemaFKs.find((fk) => {
403
- const tablesMatch = fk.referencedSchema !== void 0 && contractFK.target.namespaceId !== UNBOUND_NAMESPACE_ID ? fk.referencedSchema === contractFK.target.namespaceId && fk.referencedTable === contractFK.target.tableName : fk.referencedTable === contractFK.target.tableName;
404
- return arraysEqual(fk.columns, contractFK.source.columns) && tablesMatch && arraysEqual(fk.referencedColumns, contractFK.target.columns);
405
- });
406
- if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
407
- kind: "foreign_key_mismatch",
408
- table: tableName,
409
- namespaceId,
410
- expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
411
- message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`
412
- }, {
413
- status: "fail",
414
- kind: "foreignKey",
415
- name: `foreignKey(${contractFK.source.columns.join(", ")})`,
416
- contractPath: fkPath,
417
- code: "foreign_key_mismatch",
418
- message: "Foreign key missing",
419
- expected: contractFK,
420
- actual: void 0,
421
- children: []
422
- }, issues, nodes);
423
- else {
424
- const actionMismatches = getReferentialActionMismatches(contractFK, matchingFK);
425
- if (actionMismatches.length > 0) {
426
- const combinedMessage = actionMismatches.map((m) => m.message).join("; ");
427
- const combinedExpected = actionMismatches.map((m) => m.expected).join(", ");
428
- const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
429
- emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
430
- kind: "foreign_key_mismatch",
431
- table: tableName,
432
- namespaceId,
433
- indexOrConstraint: matchingFK.name ?? `fk(${contractFK.source.columns.join(",")})`,
434
- expected: combinedExpected,
435
- actual: combinedActual,
436
- message: `Table "${tableName}" foreign key ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}: ${combinedMessage}`
437
- }, {
438
- status: "fail",
439
- kind: "foreignKey",
440
- name: `foreignKey(${contractFK.source.columns.join(", ")})`,
441
- contractPath: fkPath,
442
- code: "foreign_key_mismatch",
443
- message: combinedMessage,
444
- expected: contractFK,
445
- actual: matchingFK,
446
- children: []
447
- }, issues, nodes);
448
- } else nodes.push({
449
- status: "pass",
450
- kind: "foreignKey",
451
- name: `foreignKey(${contractFK.source.columns.join(", ")})`,
452
- contractPath: fkPath,
453
- code: "",
454
- message: "",
455
- expected: void 0,
456
- actual: void 0,
457
- children: []
458
- });
459
- }
460
- }
461
- if (strict) {
462
- for (const schemaFK of schemaFKs) if (!contractFKs.find((fk) => {
463
- const tablesMatch = schemaFK.referencedSchema !== void 0 && fk.target.namespaceId !== UNBOUND_NAMESPACE_ID ? schemaFK.referencedSchema === fk.target.namespaceId && schemaFK.referencedTable === fk.target.tableName : schemaFK.referencedTable === fk.target.tableName;
464
- return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
465
- })) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
466
- kind: "extra_foreign_key",
467
- table: tableName,
468
- namespaceId,
469
- indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
470
- message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
471
- }, {
472
- status: "fail",
473
- kind: "foreignKey",
474
- name: `foreignKey(${schemaFK.columns.join(", ")})`,
475
- contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
476
- code: "extra_foreign_key",
477
- message: "Extra foreign key found",
478
- expected: void 0,
479
- actual: schemaFK,
480
- children: []
481
- }, issues, nodes);
482
- }
483
- return nodes;
484
- }
485
- /**
486
- * Verifies unique constraints match between contract and schema.
487
- * Returns verification nodes for the tree.
488
- *
489
- * Uses semantic satisfaction: identity is based on (table + kind + columns).
490
- * A unique constraint requirement can be satisfied by either:
491
- * - A unique constraint with the same columns, or
492
- * - A unique index with the same columns
493
- *
494
- * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
495
- */
496
- function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
497
- const nodes = [];
498
- for (const contractUnique of contractUniques) {
499
- const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
500
- const matchingUnique = schemaUniques.find((u) => arraysEqual(u.columns, contractUnique.columns));
501
- const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
502
- if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
503
- kind: "unique_constraint_mismatch",
504
- table: tableName,
505
- namespaceId,
506
- expected: contractUnique.columns.join(", "),
507
- message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
508
- }, {
509
- status: "fail",
510
- kind: "unique",
511
- name: `unique(${contractUnique.columns.join(", ")})`,
512
- contractPath: uniquePath,
513
- code: "unique_constraint_mismatch",
514
- message: "Unique constraint missing",
515
- expected: contractUnique,
516
- actual: void 0,
517
- children: []
518
- }, issues, nodes);
519
- else nodes.push({
520
- status: "pass",
521
- kind: "unique",
522
- name: `unique(${contractUnique.columns.join(", ")})`,
523
- contractPath: uniquePath,
524
- code: "",
525
- message: "",
526
- expected: void 0,
527
- actual: void 0,
528
- children: []
529
- });
530
- }
531
- if (strict) {
532
- for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
533
- kind: "extra_unique_constraint",
534
- table: tableName,
535
- namespaceId,
536
- indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
537
- message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
538
- }, {
539
- status: "fail",
540
- kind: "unique",
541
- name: `unique(${schemaUnique.columns.join(", ")})`,
542
- contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
543
- code: "extra_unique_constraint",
544
- message: "Extra unique constraint found",
545
- expected: void 0,
546
- actual: schemaUnique,
547
- children: []
548
- }, issues, nodes);
549
- }
550
- return nodes;
551
- }
552
- /**
553
- * Verifies indexes match between contract and schema.
554
- * Returns verification nodes for the tree.
555
- *
556
- * Uses semantic satisfaction: identity is based on (table + kind + columns).
557
- * A non-unique index requirement can be satisfied by either:
558
- * - A non-unique index with the same columns, or
559
- * - A unique index with the same columns (stronger satisfies weaker)
560
- *
561
- * Name differences are ignored by default (names are for DDL/diagnostics, not identity).
562
- */
563
- function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
564
- const nodes = [];
565
- for (const contractIndex of contractIndexes) {
566
- const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
567
- const matchingIndex = schemaIndexes.find((idx) => arraysEqual(idx.columns, contractIndex.columns) && indexExtrasMatch(contractIndex, idx));
568
- const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
569
- if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
570
- kind: "index_mismatch",
571
- table: tableName,
572
- namespaceId,
573
- expected: contractIndex.columns.join(", "),
574
- message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
575
- }, {
576
- status: "fail",
577
- kind: "index",
578
- name: `index(${contractIndex.columns.join(", ")})`,
579
- contractPath: indexPath,
580
- code: "index_mismatch",
581
- message: "Index missing",
582
- expected: contractIndex,
583
- actual: void 0,
584
- children: []
585
- }, issues, nodes);
586
- else nodes.push({
587
- status: "pass",
588
- kind: "index",
589
- name: `index(${contractIndex.columns.join(", ")})`,
590
- contractPath: indexPath,
591
- code: "",
592
- message: "",
593
- expected: void 0,
594
- actual: void 0,
595
- children: []
596
- });
597
- }
598
- if (strict) for (const schemaIndex of schemaIndexes) {
599
- if (schemaIndex.unique) continue;
600
- if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
601
- kind: "extra_index",
602
- table: tableName,
603
- namespaceId,
604
- indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
605
- message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
606
- }, {
607
- status: "fail",
608
- kind: "index",
609
- name: `index(${schemaIndex.columns.join(", ")})`,
610
- contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
611
- code: "extra_index",
612
- message: "Extra index found",
613
- expected: void 0,
614
- actual: schemaIndex,
615
- children: []
616
- }, issues, nodes);
617
- }
618
- return nodes;
619
- }
620
- /**
621
- * Computes counts of pass/warn/fail nodes by traversing the tree.
622
- */
623
- function computeCounts(node) {
624
- let pass = 0;
625
- let warn = 0;
626
- let fail = 0;
627
- function traverse(n) {
628
- if (n.status === "pass") pass++;
629
- else if (n.status === "warn") warn++;
630
- else if (n.status === "fail") fail++;
631
- if (n.children) for (const child of n.children) traverse(child);
632
- }
633
- traverse(node);
634
- return {
635
- pass,
636
- warn,
637
- fail,
638
- totalNodes: pass + warn + fail
639
- };
640
- }
641
- /**
642
- * Compares referential actions between a contract FK and a schema FK.
643
- * Only compares when the contract FK explicitly specifies onDelete or onUpdate.
644
- * Returns all mismatches (both onDelete and onUpdate) so both are reported at once.
645
- *
646
- * Note: 'noAction' in the contract is semantically equivalent to undefined in the
647
- * schema IR, because the introspection adapter omits 'NO ACTION' (the database default)
648
- * to keep the IR sparse. We normalize both sides before comparing.
649
- */
650
- function getReferentialActionMismatches(contractFK, schemaFK) {
651
- const mismatches = [];
652
- const contractOnDelete = normalizeReferentialAction(contractFK.onDelete);
653
- const schemaOnDelete = normalizeReferentialAction(schemaFK.onDelete);
654
- if (contractOnDelete !== void 0 && contractOnDelete !== schemaOnDelete) mismatches.push({
655
- expected: `onDelete: ${contractFK.onDelete}`,
656
- actual: `onDelete: ${schemaFK.onDelete ?? "noAction (default)"}`,
657
- message: `onDelete mismatch: expected ${contractFK.onDelete}, got ${schemaFK.onDelete ?? "noAction (default)"}`
658
- });
659
- const contractOnUpdate = normalizeReferentialAction(contractFK.onUpdate);
660
- const schemaOnUpdate = normalizeReferentialAction(schemaFK.onUpdate);
661
- if (contractOnUpdate !== void 0 && contractOnUpdate !== schemaOnUpdate) mismatches.push({
662
- expected: `onUpdate: ${contractFK.onUpdate}`,
663
- actual: `onUpdate: ${schemaFK.onUpdate ?? "noAction (default)"}`,
664
- message: `onUpdate mismatch: expected ${contractFK.onUpdate}, got ${schemaFK.onUpdate ?? "noAction (default)"}`
665
- });
666
- return mismatches;
667
- }
668
- /**
669
- * Normalizes a referential action value for comparison.
670
- * 'noAction' is the database default and equivalent to undefined (omitted) in the sparse IR.
671
- */
672
- function normalizeReferentialAction(action) {
673
- return action === "noAction" ? void 0 : action;
674
- }
675
- /**
676
- * Compares two value arrays as unordered sets.
677
- * Returns true when both sides contain exactly the same values.
678
- */
679
- function valueSetsEqual(a, b) {
680
- const aSet = new Set(a);
681
- const bSet = new Set(b);
682
- if (aSet.size !== bSet.size) return false;
683
- return [...aSet].every((v) => bSet.has(v));
684
- }
685
- /**
686
- * Verifies check constraints match between contract-projected checks and
687
- * introspected live checks.
688
- *
689
- * Comparison is value-set-based, not SQL-string-based. Postgres rewrites
690
- * `col IN ('a','b')` as `col = ANY (ARRAY['a','b'])` in
691
- * `pg_get_constraintdef`, so comparing the extracted value sets (after
692
- * the introspection adapter parses the predicate) avoids false mismatches
693
- * from the `IN`-vs-`= ANY (ARRAY…)` rendering difference.
694
- *
695
- * Issues emitted:
696
- * - `check_missing` — check expected by contract but absent from live DB
697
- * - `check_removed` — check present in live DB but not in contract
698
- * - `check_mismatch` — check present on both sides but permitted values differ
699
- *
700
- * `check_removed` is emitted only when `strict` is true so non-strict
701
- * verification (the normal path) does not complain about extra constraints.
702
- */
703
- function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
704
- const nodes = [];
705
- for (const contractCheck of contractChecks) {
706
- const checkPath = `${tablePath}.checks[${contractCheck.name}]`;
707
- const liveCheck = schemaChecks.find((c) => c.name === contractCheck.name);
708
- if (!liveCheck) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
709
- kind: "check_missing",
710
- table: tableName,
711
- namespaceId,
712
- indexOrConstraint: contractCheck.name,
713
- expected: contractCheck.permittedValues.join(", "),
714
- message: `Table "${tableName}" is missing check constraint "${contractCheck.name}" (column "${contractCheck.column}" IN (${contractCheck.permittedValues.join(", ")}))`
715
- }, {
716
- status: "fail",
717
- kind: "checkConstraint",
718
- name: `check(${contractCheck.name})`,
719
- contractPath: checkPath,
720
- code: "check_missing",
721
- message: `Check constraint "${contractCheck.name}" missing`,
722
- expected: contractCheck,
723
- actual: void 0,
724
- children: []
725
- }, issues, nodes);
726
- else if (!valueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
727
- kind: "check_mismatch",
728
- table: tableName,
729
- namespaceId,
730
- indexOrConstraint: contractCheck.name,
731
- expected: contractCheck.permittedValues.join(", "),
732
- actual: liveCheck.permittedValues.join(", "),
733
- message: `Table "${tableName}" check constraint "${contractCheck.name}" has different permitted values: expected [${contractCheck.permittedValues.join(", ")}], got [${liveCheck.permittedValues.join(", ")}]`
734
- }, {
735
- status: "fail",
736
- kind: "checkConstraint",
737
- name: `check(${contractCheck.name})`,
738
- contractPath: checkPath,
739
- code: "check_mismatch",
740
- message: `Check constraint "${contractCheck.name}" values mismatch`,
741
- expected: contractCheck,
742
- actual: liveCheck,
743
- children: []
744
- }, issues, nodes);
745
- else nodes.push({
746
- status: "pass",
747
- kind: "checkConstraint",
748
- name: `check(${contractCheck.name})`,
749
- contractPath: checkPath,
750
- code: "",
751
- message: "",
752
- expected: void 0,
753
- actual: void 0,
754
- children: []
755
- });
756
- }
757
- if (strict) {
758
- for (const liveCheck of schemaChecks) if (!contractChecks.find((c) => c.name === liveCheck.name)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
759
- kind: "check_removed",
760
- table: tableName,
761
- namespaceId,
762
- indexOrConstraint: liveCheck.name,
763
- actual: liveCheck.permittedValues.join(", "),
764
- message: `Table "${tableName}" has extra check constraint "${liveCheck.name}" in database (not in contract)`
765
- }, {
766
- status: "fail",
767
- kind: "checkConstraint",
768
- name: `check(${liveCheck.name})`,
769
- contractPath: `${tablePath}.checks[${liveCheck.name}]`,
770
- code: "check_removed",
771
- message: `Extra check constraint "${liveCheck.name}" found`,
772
- expected: void 0,
773
- actual: liveCheck,
774
- children: []
775
- }, issues, nodes);
776
- }
777
- return nodes;
778
- }
779
- //#endregion
780
- //#region src/core/schema-verify/verify-sql-schema.ts
781
- /**
782
- * Verifies that a SqlSchemaIR matches a Contract.
783
- *
784
- * This is a pure function that does NOT perform any database I/O.
785
- * It takes an already-introspected schema IR and compares it against
786
- * the contract requirements.
787
- *
788
- * @param options - Verification options
789
- * @returns VerifyDatabaseSchemaResult with verification tree and issues
790
- */
791
- function verifySqlSchema(options) {
792
- const { contract, schema, strict, context, typeMetadataRegistry, normalizeDefault, normalizeNativeType } = options;
793
- const startTime = Date.now();
794
- const codecHooks = extractCodecControlHooks(options.frameworkComponents);
795
- const { contractStorageHash, contractProfileHash, contractTarget } = extractContractMetadata(contract);
796
- const { issues, rootChildren } = verifySchemaTables({
797
- contract,
798
- schema,
799
- strict,
800
- typeMetadataRegistry,
801
- codecHooks,
802
- storageTypes: contract.storage.types ?? {},
803
- ...ifDefined("normalizeDefault", normalizeDefault),
804
- ...ifDefined("normalizeNativeType", normalizeNativeType)
805
- });
806
- validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
807
- const typeNodes = [];
808
- const pushTypeNode = (typeName, contractPath, typeIssues, controlPolicy) => {
809
- let status = "pass";
810
- let code = "";
811
- let emitted = 0;
812
- for (const issue of typeIssues) {
813
- const disposition = verifierDisposition(controlPolicy, issue.kind);
814
- if (disposition === "suppress") continue;
815
- issues.push(issue);
816
- emitted += 1;
817
- if (code === "") code = issue.kind;
818
- if (disposition === "fail") status = "fail";
819
- else if (disposition === "warn" && status !== "fail") status = "warn";
820
- }
821
- typeNodes.push({
822
- status,
823
- kind: "storageType",
824
- name: `type ${typeName}`,
825
- contractPath,
826
- code: status === "pass" ? "" : code,
827
- message: emitted > 0 ? `${emitted} issue${emitted === 1 ? "" : "s"}` : "",
828
- expected: void 0,
829
- actual: void 0,
830
- children: []
831
- });
832
- };
833
- for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) {
834
- const hook = codecHooks.get(typeInstance.codecId);
835
- pushTypeNode(typeName, `storage.types.${typeName}`, hook?.verifyType ? hook.verifyType({
836
- typeName,
837
- typeInstance,
838
- schema
839
- }) : [], effectiveControlPolicy(void 0, contract.defaultControlPolicy));
840
- }
841
- if (typeNodes.length > 0) {
842
- const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : typeNodes.some((n) => n.status === "warn") ? "warn" : "pass";
843
- rootChildren.push({
844
- status: typesStatus,
845
- kind: "storageTypes",
846
- name: "types",
847
- contractPath: "storage.types",
848
- code: typesStatus === "fail" ? "type_mismatch" : "",
849
- message: "",
850
- expected: void 0,
851
- actual: void 0,
852
- children: typeNodes
853
- });
854
- }
855
- const root = buildRootNode(rootChildren);
856
- const counts = computeCounts(root);
857
- const ok = counts.fail === 0;
858
- const code = ok ? void 0 : "PN-SCHEMA-0001";
859
- const summary = ok ? "Database schema satisfies contract" : `Database schema does not satisfy contract (${counts.fail} failure${counts.fail === 1 ? "" : "s"})`;
860
- const totalTime = Date.now() - startTime;
861
- return {
862
- ok,
863
- ...ifDefined("code", code),
864
- summary,
865
- contract: {
866
- storageHash: contractStorageHash,
867
- ...ifDefined("profileHash", contractProfileHash)
868
- },
869
- target: {
870
- expected: contractTarget,
871
- actual: contractTarget
872
- },
873
- schema: {
874
- issues,
875
- root,
876
- counts
877
- },
878
- meta: {
879
- strict,
880
- ...ifDefined("contractPath", context?.contractPath),
881
- ...ifDefined("configPath", context?.configPath)
882
- },
883
- timings: { total: totalTime }
884
- };
885
- }
886
- function extractContractMetadata(contract) {
887
- return {
888
- contractStorageHash: contract.storage.storageHash,
889
- contractProfileHash: "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : void 0,
890
- contractTarget: contract.target
891
- };
892
- }
893
- function verifySchemaTables(options) {
894
- const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
895
- const contractDefaultControl = contract.defaultControlPolicy;
896
- const issues = [];
897
- const rootChildren = [];
898
- const schemaTables = schema.tables;
899
- const namespaceIds = Object.keys(contract.storage.namespaces).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
900
- for (const namespaceId of namespaceIds) {
901
- const ns = contract.storage.namespaces[namespaceId];
902
- if (!ns) continue;
903
- for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table ?? {})) {
904
- if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`);
905
- const contractTable = contractTableRaw;
906
- const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
907
- const schemaTable = schemaTables[tableName];
908
- const tablePath = `storage.namespaces.${namespaceId}.entries.table.${tableName}`;
909
- if (!schemaTable) {
910
- emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
911
- kind: "missing_table",
912
- table: tableName,
913
- namespaceId,
914
- message: `Table "${tableName}" is missing from database`
915
- }, {
916
- status: "fail",
917
- kind: "table",
918
- name: `table ${tableName}`,
919
- contractPath: tablePath,
920
- code: "missing_table",
921
- message: `Table "${tableName}" is missing`,
922
- expected: void 0,
923
- actual: void 0,
924
- children: []
925
- }, issues, rootChildren);
926
- continue;
927
- }
928
- const tableChildren = verifyTableChildren({
929
- contractTable,
930
- schemaTable,
931
- tableName,
932
- namespaceId,
933
- tablePath,
934
- tableControlPolicy,
935
- issues,
936
- strict,
937
- typeMetadataRegistry,
938
- codecHooks,
939
- storageTypes,
940
- contractStorage: contract.storage,
941
- ...ifDefined("normalizeDefault", normalizeDefault),
942
- ...ifDefined("normalizeNativeType", normalizeNativeType)
943
- });
944
- rootChildren.push(buildTableNode(tableName, tablePath, tableChildren));
945
- }
946
- }
947
- if (strict) {
948
- for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.entries.table?.[tableName] !== void 0)) emitIssueAndNodeUnderControlPolicy(effectiveControlPolicy(void 0, contractDefaultControl), {
949
- kind: "extra_table",
950
- table: tableName,
951
- message: `Extra table "${tableName}" found in database (not in contract)`
952
- }, {
953
- status: "fail",
954
- kind: "table",
955
- name: `table ${tableName}`,
956
- contractPath: `storage.namespaces.*.entries.table.${tableName}`,
957
- code: "extra_table",
958
- message: `Extra table "${tableName}" found`,
959
- expected: void 0,
960
- actual: void 0,
961
- children: []
962
- }, issues, rootChildren);
963
- }
964
- return {
965
- issues,
966
- rootChildren
967
- };
968
- }
969
- function verifyTableChildren(options) {
970
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, contractStorage } = options;
971
- const tableChildren = [];
972
- const columnNodes = collectContractColumnNodes({
973
- contractTable,
974
- schemaTable,
975
- tableName,
976
- namespaceId,
977
- tablePath,
978
- tableControlPolicy,
979
- issues,
980
- strict,
981
- typeMetadataRegistry,
982
- codecHooks,
983
- storageTypes,
984
- ...ifDefined("normalizeDefault", normalizeDefault),
985
- ...ifDefined("normalizeNativeType", normalizeNativeType)
986
- });
987
- if (columnNodes.length > 0) tableChildren.push(buildColumnsNode(tablePath, columnNodes));
988
- if (strict) appendExtraColumnNodes({
989
- contractTable,
990
- schemaTable,
991
- tableName,
992
- namespaceId,
993
- tablePath,
994
- tableControlPolicy,
995
- issues,
996
- columnNodes
997
- });
998
- if (contractTable.primaryKey) {
999
- const pkStatus = verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, tableControlPolicy, issues);
1000
- if (pkStatus === "fail") tableChildren.push({
1001
- status: "fail",
1002
- kind: "primaryKey",
1003
- name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
1004
- contractPath: `${tablePath}.primaryKey`,
1005
- code: "primary_key_mismatch",
1006
- message: "Primary key mismatch",
1007
- expected: contractTable.primaryKey,
1008
- actual: schemaTable.primaryKey,
1009
- children: []
1010
- });
1011
- else if (pkStatus === "warn") tableChildren.push({
1012
- status: "warn",
1013
- kind: "primaryKey",
1014
- name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
1015
- contractPath: `${tablePath}.primaryKey`,
1016
- code: "primary_key_mismatch",
1017
- message: "Primary key mismatch",
1018
- expected: contractTable.primaryKey,
1019
- actual: schemaTable.primaryKey,
1020
- children: []
1021
- });
1022
- else tableChildren.push({
1023
- status: "pass",
1024
- kind: "primaryKey",
1025
- name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
1026
- contractPath: `${tablePath}.primaryKey`,
1027
- code: "",
1028
- message: "",
1029
- expected: void 0,
1030
- actual: void 0,
1031
- children: []
1032
- });
1033
- } else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1034
- kind: "extra_primary_key",
1035
- table: tableName,
1036
- namespaceId,
1037
- message: "Extra primary key found in database (not in contract)"
1038
- }, {
1039
- status: "fail",
1040
- kind: "primaryKey",
1041
- name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
1042
- contractPath: `${tablePath}.primaryKey`,
1043
- code: "extra_primary_key",
1044
- message: "Extra primary key found",
1045
- expected: void 0,
1046
- actual: schemaTable.primaryKey,
1047
- children: []
1048
- }, issues, tableChildren);
1049
- const constraintFks = contractTable.foreignKeys.filter((fk) => fk.constraint === true);
1050
- if (constraintFks.length > 0 || strict) {
1051
- const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
1052
- tableChildren.push(...fkStatuses);
1053
- }
1054
- const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
1055
- tableChildren.push(...uniqueStatuses);
1056
- const fkBackingIndexes = contractTable.foreignKeys.filter((fk) => fk.index === true && !contractTable.indexes.some((idx) => arraysEqual(idx.columns, fk.source.columns))).map((fk) => ({ columns: fk.source.columns }));
1057
- const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
1058
- tableChildren.push(...indexStatuses);
1059
- const contractCheckIRs = (contractTable.checks ?? []).map((c) => ({
1060
- name: c.name,
1061
- column: c.column,
1062
- permittedValues: resolveValueSetValues(c.valueSet, contractStorage, `check "${c.name}"`)
1063
- }));
1064
- if (strict || contractCheckIRs.length > 0) {
1065
- const checkStatuses = verifyCheckConstraints(contractCheckIRs, schemaTable.checks ?? [], tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
1066
- tableChildren.push(...checkStatuses);
1067
- }
1068
- return tableChildren;
1069
- }
1070
- function collectContractColumnNodes(options) {
1071
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
1072
- const columnNodes = [];
1073
- for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
1074
- const schemaColumn = schemaTable.columns[columnName];
1075
- const columnPath = `${tablePath}.columns.${columnName}`;
1076
- if (!schemaColumn) {
1077
- emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1078
- kind: "missing_column",
1079
- table: tableName,
1080
- namespaceId,
1081
- column: columnName,
1082
- message: `Column "${tableName}"."${columnName}" is missing from database`
1083
- }, {
1084
- status: "fail",
1085
- kind: "column",
1086
- name: `${columnName}: missing`,
1087
- contractPath: columnPath,
1088
- code: "missing_column",
1089
- message: `Column "${columnName}" is missing`,
1090
- expected: void 0,
1091
- actual: void 0,
1092
- children: []
1093
- }, issues, columnNodes);
1094
- continue;
1095
- }
1096
- columnNodes.push(verifyColumn({
1097
- tableName,
1098
- namespaceId,
1099
- columnName,
1100
- contractColumn,
1101
- schemaColumn,
1102
- columnPath,
1103
- tableControlPolicy,
1104
- issues,
1105
- strict,
1106
- typeMetadataRegistry,
1107
- codecHooks,
1108
- storageTypes,
1109
- ...ifDefined("normalizeDefault", normalizeDefault),
1110
- ...ifDefined("normalizeNativeType", normalizeNativeType)
1111
- }));
1112
- }
1113
- return columnNodes;
1114
- }
1115
- function appendExtraColumnNodes(options) {
1116
- const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
1117
- for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1118
- kind: "extra_column",
1119
- table: tableName,
1120
- namespaceId,
1121
- column: columnName,
1122
- message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
1123
- }, {
1124
- status: "fail",
1125
- kind: "column",
1126
- name: `${columnName}: extra`,
1127
- contractPath: `${tablePath}.columns.${columnName}`,
1128
- code: "extra_column",
1129
- message: `Extra column "${columnName}" found`,
1130
- expected: void 0,
1131
- actual: nativeType,
1132
- children: []
1133
- }, issues, columnNodes);
1134
- }
1135
- function verifyColumn(options) {
1136
- const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, tableControlPolicy, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
1137
- const columnChildren = [];
1138
- let columnStatus = "pass";
1139
- const resolvedContractColumn = resolveContractColumnTypeMetadata(contractColumn, storageTypes, {
1140
- tableName,
1141
- columnName
1142
- });
1143
- const contractNativeType = renderExpectedNativeType(contractColumn, storageTypes, codecHooks, {
1144
- tableName,
1145
- columnName
1146
- });
1147
- const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
1148
- if (!(contractNativeType === schemaNativeType)) {
1149
- const issue = {
1150
- kind: "type_mismatch",
1151
- table: tableName,
1152
- namespaceId,
1153
- column: columnName,
1154
- expected: contractNativeType,
1155
- actual: schemaNativeType,
1156
- message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
1157
- };
1158
- const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1159
- if (disposition !== "suppress") {
1160
- issues.push(issue);
1161
- columnChildren.push({
1162
- status: disposition,
1163
- kind: "type",
1164
- name: "type",
1165
- contractPath: `${columnPath}.nativeType`,
1166
- code: "type_mismatch",
1167
- message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
1168
- expected: contractNativeType,
1169
- actual: schemaNativeType,
1170
- children: []
1171
- });
1172
- columnStatus = disposition;
1173
- }
1174
- }
1175
- if (resolvedContractColumn.codecId) {
1176
- const typeMetadata = options.typeMetadataRegistry.get(resolvedContractColumn.codecId);
1177
- if (!typeMetadata) columnChildren.push({
1178
- status: "warn",
1179
- kind: "type",
1180
- name: "type_metadata_missing",
1181
- contractPath: `${columnPath}.codecId`,
1182
- code: "type_metadata_missing",
1183
- message: `codecId "${resolvedContractColumn.codecId}" not found in type metadata registry`,
1184
- expected: resolvedContractColumn.codecId,
1185
- actual: void 0,
1186
- children: []
1187
- });
1188
- else if (typeMetadata.nativeType && typeMetadata.nativeType !== resolvedContractColumn.nativeType) columnChildren.push({
1189
- status: "warn",
1190
- kind: "type",
1191
- name: "type_consistency",
1192
- contractPath: `${columnPath}.codecId`,
1193
- code: "type_consistency_warning",
1194
- message: `codecId "${resolvedContractColumn.codecId}" maps to nativeType "${typeMetadata.nativeType}" in registry, but contract has "${resolvedContractColumn.nativeType}"`,
1195
- expected: typeMetadata.nativeType,
1196
- actual: resolvedContractColumn.nativeType,
1197
- children: []
1198
- });
1199
- }
1200
- if (contractColumn.nullable !== schemaColumn.nullable) {
1201
- const issue = {
1202
- kind: "nullability_mismatch",
1203
- table: tableName,
1204
- namespaceId,
1205
- column: columnName,
1206
- expected: String(contractColumn.nullable),
1207
- actual: String(schemaColumn.nullable),
1208
- message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
1209
- };
1210
- const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1211
- if (disposition !== "suppress") {
1212
- issues.push(issue);
1213
- columnChildren.push({
1214
- status: disposition,
1215
- kind: "nullability",
1216
- name: "nullability",
1217
- contractPath: `${columnPath}.nullable`,
1218
- code: "nullability_mismatch",
1219
- message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
1220
- expected: contractColumn.nullable,
1221
- actual: schemaColumn.nullable,
1222
- children: []
1223
- });
1224
- columnStatus = disposition;
1225
- }
1226
- }
1227
- if (contractColumn.default) {
1228
- if (!schemaColumn.default) {
1229
- const defaultDescription = describeColumnDefault(contractColumn.default);
1230
- const issue = {
1231
- kind: "default_missing",
1232
- table: tableName,
1233
- namespaceId,
1234
- column: columnName,
1235
- expected: defaultDescription,
1236
- message: `Column "${tableName}"."${columnName}" should have default ${defaultDescription} but database has no default`
1237
- };
1238
- const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1239
- if (disposition !== "suppress") {
1240
- issues.push(issue);
1241
- columnChildren.push({
1242
- status: disposition,
1243
- kind: "default",
1244
- name: "default",
1245
- contractPath: `${columnPath}.default`,
1246
- code: "default_missing",
1247
- message: `Default missing: expected ${defaultDescription}`,
1248
- expected: defaultDescription,
1249
- actual: void 0,
1250
- children: []
1251
- });
1252
- columnStatus = disposition;
1253
- }
1254
- } else if (!columnDefaultsEqual(contractColumn.default, schemaColumn.default, normalizeDefault, schemaNativeType)) {
1255
- const expectedDescription = describeColumnDefault(contractColumn.default);
1256
- const actualDescription = schemaColumn.default;
1257
- const issue = {
1258
- kind: "default_mismatch",
1259
- table: tableName,
1260
- namespaceId,
1261
- column: columnName,
1262
- expected: expectedDescription,
1263
- actual: actualDescription,
1264
- message: `Column "${tableName}"."${columnName}" has default mismatch: expected ${expectedDescription}, got ${actualDescription}`
1265
- };
1266
- const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1267
- if (disposition !== "suppress") {
1268
- issues.push(issue);
1269
- columnChildren.push({
1270
- status: disposition,
1271
- kind: "default",
1272
- name: "default",
1273
- contractPath: `${columnPath}.default`,
1274
- code: "default_mismatch",
1275
- message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
1276
- expected: expectedDescription,
1277
- actual: actualDescription,
1278
- children: []
1279
- });
1280
- columnStatus = disposition;
1281
- }
1282
- }
1283
- } else if (strict && schemaColumn.default) {
1284
- const issue = {
1285
- kind: "extra_default",
1286
- table: tableName,
1287
- namespaceId,
1288
- column: columnName,
1289
- actual: schemaColumn.default,
1290
- message: `Column "${tableName}"."${columnName}" has default ${schemaColumn.default} in database but contract specifies no default`
1291
- };
1292
- const disposition = verifierDisposition(tableControlPolicy, issue.kind);
1293
- if (disposition !== "suppress") {
1294
- issues.push(issue);
1295
- columnChildren.push({
1296
- status: disposition,
1297
- kind: "default",
1298
- name: "default",
1299
- contractPath: `${columnPath}.default`,
1300
- code: "extra_default",
1301
- message: `Extra default: ${schemaColumn.default}`,
1302
- expected: void 0,
1303
- actual: schemaColumn.default,
1304
- children: []
1305
- });
1306
- columnStatus = disposition;
1307
- }
1308
- }
1309
- const aggregated = aggregateChildState(columnChildren, columnStatus);
1310
- const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
1311
- const columnTypeDisplay = resolvedContractColumn.codecId ? `${contractNativeType} (${resolvedContractColumn.codecId})` : contractNativeType;
1312
- const columnMessage = aggregated.failureMessages.join("; ");
1313
- return {
1314
- status: aggregated.status,
1315
- kind: "column",
1316
- name: `${columnName}: ${columnTypeDisplay} (${nullableText})`,
1317
- contractPath: columnPath,
1318
- code: aggregated.firstCode,
1319
- message: columnMessage,
1320
- expected: void 0,
1321
- actual: void 0,
1322
- children: columnChildren
1323
- };
1324
- }
1325
- function buildColumnsNode(tablePath, columnNodes) {
1326
- return {
1327
- status: aggregateChildState(columnNodes, "pass").status,
1328
- kind: "columns",
1329
- name: "columns",
1330
- contractPath: `${tablePath}.columns`,
1331
- code: "",
1332
- message: "",
1333
- expected: void 0,
1334
- actual: void 0,
1335
- children: columnNodes
1336
- };
1337
- }
1338
- function buildTableNode(tableName, tablePath, tableChildren) {
1339
- const tableStatus = aggregateChildState(tableChildren, "pass").status;
1340
- const tableFailureMessages = tableChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
1341
- const tableMessage = tableStatus === "fail" && tableFailureMessages.length > 0 ? `${tableFailureMessages.length} issue${tableFailureMessages.length === 1 ? "" : "s"}` : "";
1342
- const tableCode = tableStatus === "fail" && tableChildren.length > 0 && tableChildren[0] ? tableChildren[0].code : "";
1343
- return {
1344
- status: tableStatus,
1345
- kind: "table",
1346
- name: `table ${tableName}`,
1347
- contractPath: tablePath,
1348
- code: tableCode,
1349
- message: tableMessage,
1350
- expected: void 0,
1351
- actual: void 0,
1352
- children: tableChildren
1353
- };
1354
- }
1355
- function buildRootNode(rootChildren) {
1356
- return {
1357
- status: aggregateChildState(rootChildren, "pass").status,
1358
- kind: "contract",
1359
- name: "contract",
1360
- contractPath: "",
1361
- code: "",
1362
- message: "",
1363
- expected: void 0,
1364
- actual: void 0,
1365
- children: rootChildren
1366
- };
1367
- }
1368
- /**
1369
- * Aggregates status, failure messages, and code from children in a single pass.
1370
- * This is more efficient than calling separate functions that each iterate the array.
1371
- */
1372
- function aggregateChildState(children, fallback) {
1373
- let status = fallback;
1374
- const failureMessages = [];
1375
- let firstCode = "";
1376
- for (const child of children) if (child.status === "fail") {
1377
- status = "fail";
1378
- if (!firstCode) firstCode = child.code;
1379
- if (child.message && typeof child.message === "string" && child.message.length > 0) failureMessages.push(child.message);
1380
- } else if (child.status === "warn" && status !== "fail") {
1381
- status = "warn";
1382
- if (!firstCode) firstCode = child.code;
1383
- }
1384
- return {
1385
- status,
1386
- failureMessages,
1387
- firstCode
1388
- };
1389
- }
1390
- function validateFrameworkComponentsForExtensions(contract, frameworkComponents) {
1391
- const contractExtensionPacks = contract.extensionPacks ?? {};
1392
- for (const extensionNamespace of Object.keys(contractExtensionPacks)) if (!frameworkComponents.some((component) => component.id === extensionNamespace && (component.kind === "extension" || component.kind === "adapter" || component.kind === "target"))) throw new Error(`Extension pack '${extensionNamespace}' is declared in the contract but not found in framework components. This indicates a configuration mismatch - the contract was emitted with this extension pack, but it is not provided in the current configuration.`);
1393
- }
1394
- /**
1395
- * Renders the expected native type for a contract column, expanding parameterized types
1396
- * using codec control hooks when available.
1397
- *
1398
- * This function delegates to the `expandNativeType` hook if the codec provides one,
1399
- * ensuring that the SQL family layer remains dialect-agnostic while allowing
1400
- * target-specific adapters (like Postgres) to provide their own expansion logic.
1401
- */
1402
- function renderExpectedNativeType(contractColumn, storageTypes, codecHooks, context) {
1403
- const { codecId, nativeType, typeParams } = resolveContractColumnTypeMetadata(contractColumn, storageTypes, context);
1404
- if (!typeParams || !codecId) return nativeType;
1405
- const hooks = codecHooks.get(codecId);
1406
- if (hooks?.expandNativeType) return hooks.expandNativeType({
1407
- nativeType,
1408
- codecId,
1409
- typeParams
1410
- });
1411
- return nativeType;
1412
- }
1413
- function resolveContractColumnTypeMetadata(contractColumn, storageTypes, context) {
1414
- if (!contractColumn.typeRef) return contractColumn;
1415
- const referencedType = storageTypes[contractColumn.typeRef];
1416
- if (!referencedType) {
1417
- const columnLabel = context ? `Column "${context.tableName}"."${context.columnName}"` : "Column";
1418
- throw new Error(`${columnLabel} references storage type "${contractColumn.typeRef}" but it is not defined in storage.types.`);
1419
- }
1420
- if (isStorageTypeInstance(referencedType)) return {
1421
- codecId: referencedType.codecId,
1422
- nativeType: referencedType.nativeType,
1423
- typeParams: referencedType.typeParams
1424
- };
1425
- throw new Error(`Storage type "${contractColumn.typeRef}" has an unknown kind; expected a codec-typed StorageTypeInstance.`);
1426
- }
1427
- /**
1428
- * Describes a column default for display purposes.
1429
- */
1430
- function describeColumnDefault(columnDefault) {
1431
- switch (columnDefault.kind) {
1432
- case "literal": return `literal(${formatLiteralValue(columnDefault.value)})`;
1433
- case "function": return columnDefault.expression;
1434
- }
1435
- }
1436
- /**
1437
- * Compares a contract ColumnDefault against a schema raw default string for semantic equality.
1438
- *
1439
- * When a normalizer is provided, the raw schema default is first normalized to a ColumnDefault
1440
- * before comparison. Without a normalizer, falls back to direct string comparison against
1441
- * the contract expression.
1442
- *
1443
- * @param contractDefault - The expected default from the contract (normalized ColumnDefault)
1444
- * @param schemaDefault - The raw default expression from the database (string)
1445
- * @param normalizer - Optional target-specific normalizer to convert raw defaults
1446
- * @param nativeType - The column's native type, passed to normalizer for context
1447
- */
1448
- function columnDefaultsEqual(contractDefault, schemaDefault, normalizer, nativeType) {
1449
- if (!normalizer) {
1450
- if (contractDefault.kind === "function") return contractDefault.expression === schemaDefault;
1451
- const normalizedValue = normalizeLiteralValue(contractDefault.value, nativeType);
1452
- if (typeof normalizedValue === "string") return normalizedValue === schemaDefault || `'${normalizedValue}'` === schemaDefault;
1453
- return String(normalizedValue) === schemaDefault;
1454
- }
1455
- const normalizedSchema = normalizer(schemaDefault, nativeType ?? "");
1456
- if (!normalizedSchema) return false;
1457
- if (contractDefault.kind !== normalizedSchema.kind) return false;
1458
- if (contractDefault.kind === "literal" && normalizedSchema.kind === "literal") return literalValuesEqual(normalizeLiteralValue(contractDefault.value, nativeType), normalizeLiteralValue(normalizedSchema.value, nativeType));
1459
- if (contractDefault.kind === "function" && normalizedSchema.kind === "function") {
1460
- const normalizeExpr = (expr) => expr.toLowerCase().replace(/\s+/g, "");
1461
- return normalizeExpr(contractDefault.expression) === normalizeExpr(normalizedSchema.expression);
1462
- }
1463
- return false;
1464
- }
1465
- function isTemporalNativeType(nativeType) {
1466
- if (!nativeType) return false;
1467
- const normalized = nativeType.toLowerCase();
1468
- return normalized.includes("timestamp") || normalized === "date";
1469
- }
1470
- function normalizeLiteralValue(value, nativeType) {
1471
- if (value instanceof Date) return value.toISOString();
1472
- if (typeof value === "string" && isTemporalNativeType(nativeType)) {
1473
- const parsed = new Date(value);
1474
- if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
1475
- }
1476
- return value;
1477
- }
1478
- function literalValuesEqual(a, b) {
1479
- if (a === b) return true;
1480
- if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) return canonicalStringify(a) === canonicalStringify(b);
1481
- if (typeof a === "object" && a !== null && typeof b === "string") try {
1482
- return canonicalStringify(a) === canonicalStringify(JSON.parse(b));
1483
- } catch {
1484
- return false;
1485
- }
1486
- if (typeof a === "string" && typeof b === "object" && b !== null) try {
1487
- return canonicalStringify(JSON.parse(a)) === canonicalStringify(b);
1488
- } catch {
1489
- return false;
1490
- }
1491
- return false;
1492
- }
1493
- function formatLiteralValue(value) {
1494
- if (value instanceof Date) return value.toISOString();
1495
- if (typeof value === "string") return value;
1496
- return JSON.stringify(value);
1497
- }
1498
- //#endregion
1499
- export { contractToSchemaIR as a, extractCodecControlHooks as c, isUniqueConstraintSatisfied as i, arraysEqual as n, detectDestructiveChanges as o, isIndexSatisfied as r, resolveValueSetValues as s, verifySqlSchema as t };
1500
-
1501
- //# sourceMappingURL=verify-sql-schema-xT4udQLQ.mjs.map