@prisma-next/family-sql 0.14.0-dev.4 → 0.14.0-dev.40

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 (66) 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/contract-to-schema-ir-S-evq8E6.mjs +264 -0
  4. package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
  5. package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
  6. package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
  7. package/dist/control-adapter.d.mts +1 -1
  8. package/dist/control.d.mts +18 -3
  9. package/dist/control.d.mts.map +1 -1
  10. package/dist/control.mjs +81 -943
  11. package/dist/control.mjs.map +1 -1
  12. package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
  13. package/dist/diff.d.mts.map +1 -0
  14. package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
  15. package/dist/diff.mjs.map +1 -0
  16. package/dist/ir.d.mts +9 -6
  17. package/dist/ir.d.mts.map +1 -1
  18. package/dist/ir.mjs +1 -1
  19. package/dist/migration.d.mts +11 -2
  20. package/dist/migration.d.mts.map +1 -1
  21. package/dist/migration.mjs +7 -0
  22. package/dist/migration.mjs.map +1 -1
  23. package/dist/pack.mjs +1 -1
  24. package/dist/psl-infer.d.mts +80 -0
  25. package/dist/psl-infer.d.mts.map +1 -0
  26. package/dist/psl-infer.mjs +334 -0
  27. package/dist/psl-infer.mjs.map +1 -0
  28. package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
  29. package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
  30. package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
  31. package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
  32. package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
  33. package/dist/types-CkOIJXxU.d.mts.map +1 -0
  34. package/package.json +23 -23
  35. package/src/core/authoring-entity-types.ts +12 -37
  36. package/src/core/control-adapter.ts +10 -5
  37. package/src/core/control-instance.ts +174 -41
  38. package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
  39. package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
  40. package/src/core/ir/sql-contract-serializer-base.ts +23 -55
  41. package/src/core/ir/sql-contract-serializer.ts +5 -7
  42. package/src/core/migrations/contract-to-schema-ir.ts +49 -12
  43. package/src/core/migrations/field-event-planner.ts +2 -2
  44. package/src/core/migrations/schema-differ.ts +40 -0
  45. package/src/core/migrations/types.ts +43 -2
  46. package/src/core/psl-contract-infer/printer-config.ts +0 -6
  47. package/src/core/sql-migration.ts +12 -1
  48. package/src/exports/control.ts +1 -0
  49. package/src/exports/diff.ts +18 -0
  50. package/src/exports/psl-infer.ts +36 -0
  51. package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
  52. package/dist/schema-verify.d.mts.map +0 -1
  53. package/dist/schema-verify.mjs +0 -2
  54. package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
  55. package/dist/test-utils.d.mts +0 -2
  56. package/dist/test-utils.mjs +0 -2
  57. package/dist/types-kgstZ_Zd.d.mts.map +0 -1
  58. package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
  59. package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
  60. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  61. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  62. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  63. package/src/exports/schema-verify.ts +0 -18
  64. package/src/exports/test-utils.ts +0 -9
  65. /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
  66. /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
@@ -1,9 +1,9 @@
1
- import { i as verifySqlSchema, n as NativeTypeNormalizer, r as VerifySqlSchemaOptions } from "./verify-sql-schema-thU-jKpf.mjs";
2
- import { SchemaIssue } from "@prisma-next/framework-components/control";
1
+ import { a as verifySqlSchemaTree, i as verifySqlSchema, n as NativeTypeNormalizer, r as VerifySqlSchemaOptions } from "./sql-schema-diff-6z36dZt6.mjs";
3
2
  import { ControlPolicy } from "@prisma-next/contract/types";
3
+ import { SchemaIssue } from "@prisma-next/framework-components/control";
4
4
  import { SqlIndexIR, SqlUniqueIR } from "@prisma-next/sql-schema-ir/types";
5
5
 
6
- //#region src/core/schema-verify/verify-helpers.d.ts
6
+ //#region src/core/diff/verify-helpers.d.ts
7
7
  /**
8
8
  * Compares two arrays of strings for equality (order-sensitive).
9
9
  */
@@ -35,5 +35,5 @@ declare function isUniqueConstraintSatisfied(uniques: readonly SqlUniqueIR[], in
35
35
  */
36
36
  declare function isIndexSatisfied(indexes: readonly SqlIndexIR[], uniques: readonly SqlUniqueIR[], columns: readonly string[]): boolean;
37
37
  //#endregion
38
- export { type NativeTypeNormalizer, type VerifySqlSchemaOptions, arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema };
39
- //# sourceMappingURL=schema-verify.d.mts.map
38
+ export { type NativeTypeNormalizer, type VerifySqlSchemaOptions, arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema, verifySqlSchemaTree };
39
+ //# sourceMappingURL=diff.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.mts","names":[],"sources":["../src/core/diff/verify-helpers.ts"],"mappings":";;;;;;;;;iBAgEgB,WAAA,CAAY,CAAA,qBAAsB,CAAoB;AA+BtE;;;;;;;;;;AAG4B;AAuB5B;AA1BA,iBAAgB,2BAAA,CACd,OAAA,WAAkB,WAAA,IAClB,OAAA,WAAkB,UAAU,IAC5B,OAAA;;;;;;;;;AA0B0B;;;;iBAHZ,gBAAA,CACd,OAAA,WAAkB,UAAA,IAClB,OAAA,WAAkB,WAAW,IAC7B,OAAA"}
@@ -1,241 +1,12 @@
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";
1
+ import { a as extractCodecControlHooks, i as resolveValueSetValues } from "./contract-to-schema-ir-S-evq8E6.mjs";
2
+ import { blindCast } from "@prisma-next/utils/casts";
3
+ import { effectiveControlPolicy } from "@prisma-next/contract/types";
4
+ import { dispositionForCategory } from "@prisma-next/framework-components/control";
4
5
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
6
+ import { ifDefined } from "@prisma-next/utils/defined";
5
7
  import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
6
- import { effectiveControlPolicy } from "@prisma-next/contract/types";
7
8
  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
9
+ //#region src/core/diff/verifier-disposition.ts
239
10
  /**
240
11
  * Classifies the relational verifier issue kinds the SQL family emits (tables,
241
12
  * columns, constraints, indexes, defaults, enum types) into the target-neutral
@@ -275,7 +46,7 @@ function verifierDisposition(controlPolicy, issueKind) {
275
46
  return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
276
47
  }
277
48
  //#endregion
278
- //#region src/core/schema-verify/control-verify-emit.ts
49
+ //#region src/core/diff/control-verify-emit.ts
279
50
  /**
280
51
  * Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
281
52
  * issue and a status-stamped verification node. Returns the resolved outcome so
@@ -303,7 +74,7 @@ function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
303
74
  return disposition;
304
75
  }
305
76
  //#endregion
306
- //#region src/core/schema-verify/verify-helpers.ts
77
+ //#region src/core/diff/verify-helpers.ts
307
78
  function indexOptionsLooselyEqual(a, b) {
308
79
  const aKeys = a ? Object.keys(a).sort() : [];
309
80
  const bKeys = b ? Object.keys(b).sort() : [];
@@ -368,6 +139,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
368
139
  if (!schemaPK) {
369
140
  const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
370
141
  kind: "primary_key_mismatch",
142
+ reason: "not-equal",
371
143
  table: tableName,
372
144
  namespaceId,
373
145
  expected: contractPK.columns.join(", "),
@@ -378,6 +150,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
378
150
  if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
379
151
  const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
380
152
  kind: "primary_key_mismatch",
153
+ reason: "not-equal",
381
154
  table: tableName,
382
155
  namespaceId,
383
156
  expected: contractPK.columns.join(", "),
@@ -405,6 +178,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
405
178
  });
406
179
  if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
407
180
  kind: "foreign_key_mismatch",
181
+ reason: "not-equal",
408
182
  table: tableName,
409
183
  namespaceId,
410
184
  expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
@@ -428,6 +202,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
428
202
  const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
429
203
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
430
204
  kind: "foreign_key_mismatch",
205
+ reason: "not-equal",
431
206
  table: tableName,
432
207
  namespaceId,
433
208
  indexOrConstraint: matchingFK.name ?? `fk(${contractFK.source.columns.join(",")})`,
@@ -464,6 +239,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
464
239
  return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
465
240
  })) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
466
241
  kind: "extra_foreign_key",
242
+ reason: "not-expected",
467
243
  table: tableName,
468
244
  namespaceId,
469
245
  indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
@@ -501,6 +277,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
501
277
  const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
502
278
  if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
503
279
  kind: "unique_constraint_mismatch",
280
+ reason: "not-equal",
504
281
  table: tableName,
505
282
  namespaceId,
506
283
  expected: contractUnique.columns.join(", "),
@@ -531,6 +308,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
531
308
  if (strict) {
532
309
  for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
533
310
  kind: "extra_unique_constraint",
311
+ reason: "not-expected",
534
312
  table: tableName,
535
313
  namespaceId,
536
314
  indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
@@ -568,6 +346,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
568
346
  const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
569
347
  if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
570
348
  kind: "index_mismatch",
349
+ reason: "not-equal",
571
350
  table: tableName,
572
351
  namespaceId,
573
352
  expected: contractIndex.columns.join(", "),
@@ -599,6 +378,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
599
378
  if (schemaIndex.unique) continue;
600
379
  if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
601
380
  kind: "extra_index",
381
+ reason: "not-expected",
602
382
  table: tableName,
603
383
  namespaceId,
604
384
  indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
@@ -707,6 +487,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
707
487
  const liveCheck = schemaChecks.find((c) => c.name === contractCheck.name);
708
488
  if (!liveCheck) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
709
489
  kind: "check_missing",
490
+ reason: "not-found",
710
491
  table: tableName,
711
492
  namespaceId,
712
493
  indexOrConstraint: contractCheck.name,
@@ -725,6 +506,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
725
506
  }, issues, nodes);
726
507
  else if (!valueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
727
508
  kind: "check_mismatch",
509
+ reason: "not-equal",
728
510
  table: tableName,
729
511
  namespaceId,
730
512
  indexOrConstraint: contractCheck.name,
@@ -757,6 +539,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
757
539
  if (strict) {
758
540
  for (const liveCheck of schemaChecks) if (!contractChecks.find((c) => c.name === liveCheck.name)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
759
541
  kind: "check_removed",
542
+ reason: "not-equal",
760
543
  table: tableName,
761
544
  namespaceId,
762
545
  indexOrConstraint: liveCheck.name,
@@ -777,7 +560,20 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
777
560
  return nodes;
778
561
  }
779
562
  //#endregion
780
- //#region src/core/schema-verify/verify-sql-schema.ts
563
+ //#region src/core/diff/sql-schema-diff.ts
564
+ /**
565
+ * Returns the per-schema namespace nodes of an introspected schema node, for
566
+ * the relational verify to consume one at a time. Structure-agnostic — imports
567
+ * no target node class. A root exposing a `namespaces` record (Postgres) yields
568
+ * its namespace nodes (never merged, so same-named tables in different schemas
569
+ * cannot collide); a flat schema (SQLite) is its own single namespace and
570
+ * yields itself. Handles spread-flattened input (own-enumerable fields survive).
571
+ */
572
+ function namespaceSchemaNodes(schema) {
573
+ const obj = blindCast(schema);
574
+ if (obj.namespaces !== void 0) return Object.values(obj.namespaces);
575
+ return [blindCast(schema)];
576
+ }
781
577
  /**
782
578
  * Verifies that a SqlSchemaIR matches a Contract.
783
579
  *
@@ -801,7 +597,8 @@ function verifySqlSchema(options) {
801
597
  codecHooks,
802
598
  storageTypes: contract.storage.types ?? {},
803
599
  ...ifDefined("normalizeDefault", normalizeDefault),
804
- ...ifDefined("normalizeNativeType", normalizeNativeType)
600
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
601
+ ...ifDefined("restrictToNamespaceIds", options.restrictToNamespaceIds)
805
602
  });
806
603
  validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
807
604
  const typeNodes = [];
@@ -872,6 +669,7 @@ function verifySqlSchema(options) {
872
669
  },
873
670
  schema: {
874
671
  issues,
672
+ schemaDiffIssues: [],
875
673
  root,
876
674
  counts
877
675
  },
@@ -891,17 +689,18 @@ function extractContractMetadata(contract) {
891
689
  };
892
690
  }
893
691
  function verifySchemaTables(options) {
894
- const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
692
+ const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, restrictToNamespaceIds } = options;
895
693
  const contractDefaultControl = contract.defaultControlPolicy;
896
694
  const issues = [];
897
695
  const rootChildren = [];
898
696
  const schemaTables = schema.tables;
899
697
  const namespaceIds = Object.keys(contract.storage.namespaces).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
900
698
  for (const namespaceId of namespaceIds) {
699
+ if (restrictToNamespaceIds !== void 0 && !restrictToNamespaceIds.has(namespaceId)) continue;
901
700
  const ns = contract.storage.namespaces[namespaceId];
902
701
  if (!ns) continue;
903
702
  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}`);
703
+ StorageTable.assert(contractTableRaw, `storage.namespaces.${namespaceId}.entries.table.${tableName}`);
905
704
  const contractTable = contractTableRaw;
906
705
  const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
907
706
  const schemaTable = schemaTables[tableName];
@@ -909,6 +708,7 @@ function verifySchemaTables(options) {
909
708
  if (!schemaTable) {
910
709
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
911
710
  kind: "missing_table",
711
+ reason: "not-found",
912
712
  table: tableName,
913
713
  namespaceId,
914
714
  message: `Table "${tableName}" is missing from database`
@@ -947,6 +747,7 @@ function verifySchemaTables(options) {
947
747
  if (strict) {
948
748
  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
749
  kind: "extra_table",
750
+ reason: "not-expected",
950
751
  table: tableName,
951
752
  message: `Extra table "${tableName}" found in database (not in contract)`
952
753
  }, {
@@ -958,7 +759,8 @@ function verifySchemaTables(options) {
958
759
  message: `Extra table "${tableName}" found`,
959
760
  expected: void 0,
960
761
  actual: void 0,
961
- children: []
762
+ children: [],
763
+ reason: "not-expected"
962
764
  }, issues, rootChildren);
963
765
  }
964
766
  return {
@@ -1032,6 +834,7 @@ function verifyTableChildren(options) {
1032
834
  });
1033
835
  } else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1034
836
  kind: "extra_primary_key",
837
+ reason: "not-expected",
1035
838
  table: tableName,
1036
839
  namespaceId,
1037
840
  message: "Extra primary key found in database (not in contract)"
@@ -1076,6 +879,7 @@ function collectContractColumnNodes(options) {
1076
879
  if (!schemaColumn) {
1077
880
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1078
881
  kind: "missing_column",
882
+ reason: "not-found",
1079
883
  table: tableName,
1080
884
  namespaceId,
1081
885
  column: columnName,
@@ -1116,6 +920,7 @@ function appendExtraColumnNodes(options) {
1116
920
  const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
1117
921
  for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1118
922
  kind: "extra_column",
923
+ reason: "not-expected",
1119
924
  table: tableName,
1120
925
  namespaceId,
1121
926
  column: columnName,
@@ -1144,10 +949,12 @@ function verifyColumn(options) {
1144
949
  tableName,
1145
950
  columnName
1146
951
  });
1147
- const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
952
+ const schemaBaseNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
953
+ const schemaNativeType = schemaColumn.many ? `${schemaBaseNativeType}[]` : schemaBaseNativeType;
1148
954
  if (!(contractNativeType === schemaNativeType)) {
1149
955
  const issue = {
1150
956
  kind: "type_mismatch",
957
+ reason: "not-equal",
1151
958
  table: tableName,
1152
959
  namespaceId,
1153
960
  column: columnName,
@@ -1200,6 +1007,7 @@ function verifyColumn(options) {
1200
1007
  if (contractColumn.nullable !== schemaColumn.nullable) {
1201
1008
  const issue = {
1202
1009
  kind: "nullability_mismatch",
1010
+ reason: "not-equal",
1203
1011
  table: tableName,
1204
1012
  namespaceId,
1205
1013
  column: columnName,
@@ -1229,6 +1037,7 @@ function verifyColumn(options) {
1229
1037
  const defaultDescription = describeColumnDefault(contractColumn.default);
1230
1038
  const issue = {
1231
1039
  kind: "default_missing",
1040
+ reason: "not-found",
1232
1041
  table: tableName,
1233
1042
  namespaceId,
1234
1043
  column: columnName,
@@ -1256,6 +1065,7 @@ function verifyColumn(options) {
1256
1065
  const actualDescription = schemaColumn.default;
1257
1066
  const issue = {
1258
1067
  kind: "default_mismatch",
1068
+ reason: "not-equal",
1259
1069
  table: tableName,
1260
1070
  namespaceId,
1261
1071
  column: columnName,
@@ -1283,6 +1093,7 @@ function verifyColumn(options) {
1283
1093
  } else if (strict && schemaColumn.default) {
1284
1094
  const issue = {
1285
1095
  kind: "extra_default",
1096
+ reason: "not-expected",
1286
1097
  table: tableName,
1287
1098
  namespaceId,
1288
1099
  column: columnName,
@@ -1401,14 +1212,17 @@ function validateFrameworkComponentsForExtensions(contract, frameworkComponents)
1401
1212
  */
1402
1213
  function renderExpectedNativeType(contractColumn, storageTypes, codecHooks, context) {
1403
1214
  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;
1215
+ let baseType;
1216
+ if (!typeParams || !codecId) baseType = nativeType;
1217
+ else {
1218
+ const hooks = codecHooks.get(codecId);
1219
+ baseType = hooks?.expandNativeType ? hooks.expandNativeType({
1220
+ nativeType,
1221
+ codecId,
1222
+ typeParams
1223
+ }) : nativeType;
1224
+ }
1225
+ return contractColumn.many ? `${baseType}[]` : baseType;
1412
1226
  }
1413
1227
  function resolveContractColumnTypeMetadata(contractColumn, storageTypes, context) {
1414
1228
  if (!contractColumn.typeRef) return contractColumn;
@@ -1495,7 +1309,109 @@ function formatLiteralValue(value) {
1495
1309
  if (typeof value === "string") return value;
1496
1310
  return JSON.stringify(value);
1497
1311
  }
1312
+ /**
1313
+ * Reads a namespace node's DDL schema name. Namespaced target nodes (Postgres
1314
+ * `PostgresNamespaceSchemaNode`) carry `schemaName`; a flat schema (SQLite) has
1315
+ * none and pairs by position as the sole namespace.
1316
+ */
1317
+ function namespaceSchemaName(node) {
1318
+ return blindCast(node).schemaName;
1319
+ }
1320
+ /**
1321
+ * Returns a shallow copy of `contract` exposing only the one named namespace —
1322
+ * used solely to resolve that namespace's live DDL schema via the target's
1323
+ * expected-tree projection (never for verification, so global value-sets it may
1324
+ * reference are not consulted).
1325
+ */
1326
+ function scopeContractToNamespace(contract, namespaceId) {
1327
+ const namespace = contract.storage.namespaces[namespaceId];
1328
+ const scopedNamespaces = namespace === void 0 ? {} : { [namespaceId]: namespace };
1329
+ return blindCast({
1330
+ ...contract,
1331
+ storage: blindCast({
1332
+ ...contract.storage,
1333
+ namespaces: scopedNamespaces
1334
+ })
1335
+ });
1336
+ }
1337
+ /**
1338
+ * Combines two `VerifyDatabaseSchemaResult`s by concatenating issues and summing
1339
+ * counts — used to fold the per-namespace pairings of a multi-schema database
1340
+ * into one result. The verification-tree `root` of the first pairing is
1341
+ * retained (multi-schema verify-tree shaping is future work).
1342
+ */
1343
+ function mergeVerifyResults(a, b) {
1344
+ return {
1345
+ ...a,
1346
+ ok: a.ok && b.ok,
1347
+ ...ifDefined("code", a.code ?? b.code),
1348
+ schema: {
1349
+ ...a.schema,
1350
+ issues: [...a.schema.issues, ...b.schema.issues],
1351
+ schemaDiffIssues: [...a.schema.schemaDiffIssues, ...b.schema.schemaDiffIssues],
1352
+ counts: {
1353
+ pass: a.schema.counts.pass + b.schema.counts.pass,
1354
+ warn: a.schema.counts.warn + b.schema.counts.warn,
1355
+ fail: a.schema.counts.fail + b.schema.counts.fail,
1356
+ totalNodes: a.schema.counts.totalNodes + b.schema.counts.totalNodes
1357
+ }
1358
+ }
1359
+ };
1360
+ }
1361
+ /**
1362
+ * The single per-namespace-paired relational verify shared by the migration
1363
+ * planner and the family schema verify — there is exactly one such operation.
1364
+ *
1365
+ * Each contract namespace is paired to the introspected namespace node holding
1366
+ * the same DDL schema, then `verifySqlSchema` checks that namespace's tables
1367
+ * against the matching actual node (a contract table under `auth` is only ever
1368
+ * looked up in the `auth` actual node, so a multi-schema database no longer
1369
+ * reports tables in other schemas as missing). The full contract is passed every
1370
+ * time — `restrictToNamespaceIds` scopes only which tables are checked, so
1371
+ * cross-namespace value-set / control-policy resolution is unaffected.
1372
+ *
1373
+ * The DDL schema of each contract namespace is read from a single-namespace
1374
+ * expected projection (`buildExpectedSchema`), which both callers already build
1375
+ * the same way. Empty contract namespaces verify nothing and are skipped.
1376
+ * Single-schema (one namespace) and SQLite's flat schema are one pairing —
1377
+ * byte-identical to the prior per-node verify.
1378
+ */
1379
+ function verifySqlSchemaTree(options) {
1380
+ const baseOptions = {
1381
+ contract: options.contract,
1382
+ strict: options.strict,
1383
+ typeMetadataRegistry: options.typeMetadataRegistry,
1384
+ frameworkComponents: options.frameworkComponents,
1385
+ ...ifDefined("normalizeDefault", options.normalizeDefault),
1386
+ ...ifDefined("normalizeNativeType", options.normalizeNativeType)
1387
+ };
1388
+ const actualNodes = namespaceSchemaNodes(options.actualSchema);
1389
+ const actualByName = /* @__PURE__ */ new Map();
1390
+ for (const node of actualNodes) {
1391
+ const name = namespaceSchemaName(node);
1392
+ if (name !== void 0) actualByName.set(name, node);
1393
+ }
1394
+ const soleFlatActual = actualByName.size === 0 ? actualNodes[0] : void 0;
1395
+ const emptyNamespace = { tables: {} };
1396
+ let combined;
1397
+ for (const namespaceId of Object.keys(options.contract.storage.namespaces)) {
1398
+ const namespace = options.contract.storage.namespaces[namespaceId];
1399
+ if (!namespace || Object.keys(namespace.entries.table ?? {}).length === 0) continue;
1400
+ const ddlSchema = namespaceSchemaNodes(options.buildExpectedSchema(scopeContractToNamespace(options.contract, namespaceId))).map(namespaceSchemaName).find((name) => name !== void 0);
1401
+ const actualNode = (ddlSchema !== void 0 ? actualByName.get(ddlSchema) : soleFlatActual) ?? emptyNamespace;
1402
+ const result = verifySqlSchema({
1403
+ ...baseOptions,
1404
+ schema: actualNode,
1405
+ restrictToNamespaceIds: new Set([namespaceId])
1406
+ });
1407
+ combined = combined === void 0 ? result : mergeVerifyResults(combined, result);
1408
+ }
1409
+ return combined ?? verifySqlSchema({
1410
+ ...baseOptions,
1411
+ schema: emptyNamespace
1412
+ });
1413
+ }
1498
1414
  //#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 };
1415
+ export { arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema, verifySqlSchemaTree };
1500
1416
 
1501
- //# sourceMappingURL=verify-sql-schema-xT4udQLQ.mjs.map
1417
+ //# sourceMappingURL=diff.mjs.map