@prisma-next/family-sql 0.14.0-dev.39 → 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 (46) hide show
  1. package/dist/contract-to-schema-ir-S-evq8E6.mjs +264 -0
  2. package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
  3. package/dist/{control-adapter-B2q9WN1V.d.mts → control-adapter-DHYFuOBy.d.mts} +14 -17
  4. package/dist/{control-adapter-B2q9WN1V.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
  5. package/dist/control-adapter.d.mts +1 -1
  6. package/dist/control.d.mts +17 -2
  7. package/dist/control.d.mts.map +1 -1
  8. package/dist/control.mjs +42 -943
  9. package/dist/control.mjs.map +1 -1
  10. package/dist/{schema-verify.d.mts → diff.d.mts} +4 -4
  11. package/dist/diff.d.mts.map +1 -0
  12. package/dist/{verify-sql-schema-CEY7b78t.mjs → diff.mjs} +154 -244
  13. package/dist/diff.mjs.map +1 -0
  14. package/dist/migration.d.mts +1 -1
  15. package/dist/psl-infer.d.mts +80 -0
  16. package/dist/psl-infer.d.mts.map +1 -0
  17. package/dist/psl-infer.mjs +334 -0
  18. package/dist/psl-infer.mjs.map +1 -0
  19. package/dist/{verify-sql-schema-B8uEjw2s.d.mts → sql-schema-diff-6z36dZt6.d.mts} +43 -4
  20. package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
  21. package/dist/{types-BxsFkHYv.d.mts → types-CkOIJXxU.d.mts} +83 -16
  22. package/dist/types-CkOIJXxU.d.mts.map +1 -0
  23. package/package.json +23 -22
  24. package/src/core/control-adapter.ts +12 -24
  25. package/src/core/control-instance.ts +113 -45
  26. package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +196 -8
  27. package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
  28. package/src/core/migrations/contract-to-schema-ir.ts +48 -13
  29. package/src/core/migrations/field-event-planner.ts +3 -8
  30. package/src/core/migrations/schema-differ.ts +40 -0
  31. package/src/core/migrations/types.ts +43 -2
  32. package/src/core/psl-contract-infer/printer-config.ts +0 -6
  33. package/src/exports/control.ts +1 -0
  34. package/src/exports/diff.ts +18 -0
  35. package/src/exports/psl-infer.ts +36 -0
  36. package/dist/schema-verify.d.mts.map +0 -1
  37. package/dist/schema-verify.mjs +0 -2
  38. package/dist/types-BxsFkHYv.d.mts.map +0 -1
  39. package/dist/verify-sql-schema-B8uEjw2s.d.mts.map +0 -1
  40. package/dist/verify-sql-schema-CEY7b78t.mjs.map +0 -1
  41. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  42. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  43. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  44. package/src/exports/schema-verify.ts +0 -18
  45. /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
  46. /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-B8uEjw2s.mjs";
1
+ import { a as verifySqlSchemaTree, i as verifySqlSchema, n as NativeTypeNormalizer, r as VerifySqlSchemaOptions } from "./sql-schema-diff-6z36dZt6.mjs";
2
2
  import { ControlPolicy } from "@prisma-next/contract/types";
3
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,242 +1,12 @@
1
+ import { a as extractCodecControlHooks, i as resolveValueSetValues } from "./contract-to-schema-ir-S-evq8E6.mjs";
2
+ import { blindCast } from "@prisma-next/utils/casts";
1
3
  import { effectiveControlPolicy } from "@prisma-next/contract/types";
2
- import { assertUniqueCodecOwner, dispositionForCategory } from "@prisma-next/framework-components/control";
4
+ import { dispositionForCategory } from "@prisma-next/framework-components/control";
3
5
  import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
4
- import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
5
6
  import { ifDefined } from "@prisma-next/utils/defined";
6
- import { isStorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
7
+ import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-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
- const baseNativeType = expandNativeType ? expandNativeType({
40
- nativeType: resolved.nativeType,
41
- codecId: resolved.codecId,
42
- ...ifDefined("typeParams", resolved.typeParams)
43
- }) : resolved.nativeType;
44
- return {
45
- name,
46
- nativeType: column.many ? `${baseNativeType}[]` : baseNativeType,
47
- nullable: column.nullable,
48
- ...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0)
49
- };
50
- }
51
- function resolveColumnTypeMetadata(column, storageTypes) {
52
- if (!column.typeRef) return column;
53
- const referenced = storageTypes[column.typeRef];
54
- if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
55
- if (isStorageTypeInstance(referenced)) return {
56
- codecId: referenced.codecId,
57
- nativeType: referenced.nativeType,
58
- typeParams: referenced.typeParams
59
- };
60
- throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`);
61
- }
62
- /**
63
- * Resolves a `ValueSetRef` to its permitted values from the contract storage.
64
- *
65
- * Throws when the referenced namespace or value-set is absent — this indicates
66
- * the contract was built incorrectly (the check and the value-set must be
67
- * co-emitted by the lowering step). Used by `convertCheck` (schema-IR
68
- * projection), `verifyCheckConstraints` (verification), and
69
- * `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
70
- * the resolved values and the error behavior on a missing reference.
71
- */
72
- function allStrings(values) {
73
- return values.every((value) => typeof value === "string");
74
- }
75
- function resolveValueSetValues(ref, storage, contextLabel) {
76
- const ns = storage.namespaces[ref.namespaceId];
77
- if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
78
- const valueSet = ns.entries.valueSet?.[ref.entityName];
79
- if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
80
- const values = valueSet.values;
81
- 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})`);
82
- return values;
83
- }
84
- /**
85
- * Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
86
- * resolving the permitted values from the storage value-set it references.
87
- *
88
- * The `CheckConstraint.valueSet` ref points to
89
- * `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
90
- * values are lifted directly from `StorageValueSet.values` so verification
91
- * compares value sets, not SQL predicate strings.
92
- *
93
- * Throws if the referenced namespace or value-set is absent — this
94
- * indicates the contract was built incorrectly (the check and the
95
- * value-set must be co-emitted by the lowering step).
96
- */
97
- function convertCheck(check, storage) {
98
- const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
99
- return {
100
- name: check.name,
101
- column: check.column,
102
- permittedValues
103
- };
104
- }
105
- function convertUnique(unique) {
106
- return {
107
- columns: unique.columns,
108
- ...ifDefined("name", unique.name)
109
- };
110
- }
111
- function convertIndex(index) {
112
- return {
113
- columns: index.columns,
114
- unique: false,
115
- ...ifDefined("name", index.name)
116
- };
117
- }
118
- function convertForeignKey(fk) {
119
- return {
120
- columns: fk.source.columns,
121
- referencedTable: fk.target.tableName,
122
- referencedSchema: fk.target.namespaceId,
123
- referencedColumns: fk.target.columns,
124
- ...ifDefined("name", fk.name),
125
- ...ifDefined("onDelete", fk.onDelete),
126
- ...ifDefined("onUpdate", fk.onUpdate)
127
- };
128
- }
129
- function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
130
- const columns = {};
131
- for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
132
- const satisfiedIndexColumns = new Set([
133
- ...table.indexes.map((idx) => idx.columns.join(",")),
134
- ...table.uniques.map((unique) => unique.columns.join(",")),
135
- ...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
136
- ]);
137
- const fkBackingIndexes = [];
138
- for (const fk of table.foreignKeys) {
139
- if (fk.index === false) continue;
140
- const key = fk.source.columns.join(",");
141
- if (satisfiedIndexColumns.has(key)) continue;
142
- fkBackingIndexes.push({
143
- columns: fk.source.columns,
144
- unique: false,
145
- name: defaultIndexName(name, fk.source.columns)
146
- });
147
- satisfiedIndexColumns.add(key);
148
- }
149
- const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
150
- return {
151
- name,
152
- columns,
153
- ...ifDefined("primaryKey", table.primaryKey),
154
- foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
155
- uniques: table.uniques.map(convertUnique),
156
- indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
157
- ...ifDefined("checks", checks)
158
- };
159
- }
160
- /**
161
- * Detects destructive changes between two contract storages.
162
- *
163
- * The additive-only planner silently ignores removals (tables, columns).
164
- * This function detects those removals so callers can report them as conflicts
165
- * rather than silently producing an empty plan.
166
- *
167
- * Returns an empty array if no destructive changes are found.
168
- */
169
- function detectDestructiveChanges(from, to) {
170
- if (!from) return [];
171
- const hasOwn = (value, key) => Object.hasOwn(value, key);
172
- const conflicts = [];
173
- const namespaceIds = [...new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
174
- for (const namespaceId of namespaceIds) {
175
- const fromNs = from.namespaces[namespaceId];
176
- const toNs = to.namespaces[namespaceId];
177
- const fromTables = fromNs?.entries.table;
178
- if (!fromTables) continue;
179
- for (const tableName of Object.keys(fromTables)) {
180
- const toTableRaw = toNs?.entries.table?.[tableName];
181
- if (!isStorageTable(toTableRaw)) {
182
- conflicts.push({
183
- kind: "tableRemoved",
184
- summary: `Table "${tableName}" was removed`
185
- });
186
- continue;
187
- }
188
- const toTable = toTableRaw;
189
- const fromTableRaw = fromTables[tableName];
190
- if (!isStorageTable(fromTableRaw)) continue;
191
- const fromTable = fromTableRaw;
192
- for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
193
- kind: "columnRemoved",
194
- summary: `Column "${tableName}"."${columnName}" was removed`
195
- });
196
- }
197
- }
198
- return conflicts;
199
- }
200
- /**
201
- * Converts a `Contract` to `SqlSchemaIR`.
202
- *
203
- * Reads `contract.storage` for tables and `contract.storage.types` for type
204
- * annotations. Storage-type annotations are written under
205
- * `options.annotationNamespace`.
206
- *
207
- * Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
208
- * structural information. When `expandNativeType` is provided, parameterized types
209
- * are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
210
- * resulting IR compares correctly against the "to" contract during planning.
211
- *
212
- * Returns an empty schema IR when `contract` is `null` (new project).
213
- */
214
- function contractToSchemaIR(contract, options) {
215
- if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
216
- if (!contract) return { tables: {} };
217
- const storage = contract.storage;
218
- const storageTypes = { ...storage.types ?? {} };
219
- const tables = {};
220
- for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
221
- if (!isStorageTable(tableDefRaw)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`);
222
- const tableDef = tableDefRaw;
223
- if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
224
- tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
225
- }
226
- return {
227
- tables,
228
- ...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumNamespaceSchema))
229
- };
230
- }
231
- function deriveAnnotations(storage, annotationNamespace, _resolveEnumNamespaceSchema) {
232
- const storageTypes = {};
233
- for (const typeInstance of Object.values(storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
234
- const envelope = { ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {} };
235
- if (Object.keys(envelope).length === 0) return void 0;
236
- return { [annotationNamespace]: envelope };
237
- }
238
- //#endregion
239
- //#region src/core/schema-verify/verifier-disposition.ts
9
+ //#region src/core/diff/verifier-disposition.ts
240
10
  /**
241
11
  * Classifies the relational verifier issue kinds the SQL family emits (tables,
242
12
  * columns, constraints, indexes, defaults, enum types) into the target-neutral
@@ -276,7 +46,7 @@ function verifierDisposition(controlPolicy, issueKind) {
276
46
  return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
277
47
  }
278
48
  //#endregion
279
- //#region src/core/schema-verify/control-verify-emit.ts
49
+ //#region src/core/diff/control-verify-emit.ts
280
50
  /**
281
51
  * Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
282
52
  * issue and a status-stamped verification node. Returns the resolved outcome so
@@ -304,7 +74,7 @@ function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
304
74
  return disposition;
305
75
  }
306
76
  //#endregion
307
- //#region src/core/schema-verify/verify-helpers.ts
77
+ //#region src/core/diff/verify-helpers.ts
308
78
  function indexOptionsLooselyEqual(a, b) {
309
79
  const aKeys = a ? Object.keys(a).sort() : [];
310
80
  const bKeys = b ? Object.keys(b).sort() : [];
@@ -369,6 +139,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
369
139
  if (!schemaPK) {
370
140
  const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
371
141
  kind: "primary_key_mismatch",
142
+ reason: "not-equal",
372
143
  table: tableName,
373
144
  namespaceId,
374
145
  expected: contractPK.columns.join(", "),
@@ -379,6 +150,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
379
150
  if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
380
151
  const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
381
152
  kind: "primary_key_mismatch",
153
+ reason: "not-equal",
382
154
  table: tableName,
383
155
  namespaceId,
384
156
  expected: contractPK.columns.join(", "),
@@ -406,6 +178,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
406
178
  });
407
179
  if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
408
180
  kind: "foreign_key_mismatch",
181
+ reason: "not-equal",
409
182
  table: tableName,
410
183
  namespaceId,
411
184
  expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
@@ -429,6 +202,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
429
202
  const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
430
203
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
431
204
  kind: "foreign_key_mismatch",
205
+ reason: "not-equal",
432
206
  table: tableName,
433
207
  namespaceId,
434
208
  indexOrConstraint: matchingFK.name ?? `fk(${contractFK.source.columns.join(",")})`,
@@ -465,6 +239,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
465
239
  return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
466
240
  })) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
467
241
  kind: "extra_foreign_key",
242
+ reason: "not-expected",
468
243
  table: tableName,
469
244
  namespaceId,
470
245
  indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
@@ -502,6 +277,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
502
277
  const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
503
278
  if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
504
279
  kind: "unique_constraint_mismatch",
280
+ reason: "not-equal",
505
281
  table: tableName,
506
282
  namespaceId,
507
283
  expected: contractUnique.columns.join(", "),
@@ -532,6 +308,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
532
308
  if (strict) {
533
309
  for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
534
310
  kind: "extra_unique_constraint",
311
+ reason: "not-expected",
535
312
  table: tableName,
536
313
  namespaceId,
537
314
  indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
@@ -569,6 +346,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
569
346
  const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
570
347
  if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
571
348
  kind: "index_mismatch",
349
+ reason: "not-equal",
572
350
  table: tableName,
573
351
  namespaceId,
574
352
  expected: contractIndex.columns.join(", "),
@@ -600,6 +378,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
600
378
  if (schemaIndex.unique) continue;
601
379
  if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
602
380
  kind: "extra_index",
381
+ reason: "not-expected",
603
382
  table: tableName,
604
383
  namespaceId,
605
384
  indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
@@ -708,6 +487,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
708
487
  const liveCheck = schemaChecks.find((c) => c.name === contractCheck.name);
709
488
  if (!liveCheck) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
710
489
  kind: "check_missing",
490
+ reason: "not-found",
711
491
  table: tableName,
712
492
  namespaceId,
713
493
  indexOrConstraint: contractCheck.name,
@@ -726,6 +506,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
726
506
  }, issues, nodes);
727
507
  else if (!valueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
728
508
  kind: "check_mismatch",
509
+ reason: "not-equal",
729
510
  table: tableName,
730
511
  namespaceId,
731
512
  indexOrConstraint: contractCheck.name,
@@ -758,6 +539,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
758
539
  if (strict) {
759
540
  for (const liveCheck of schemaChecks) if (!contractChecks.find((c) => c.name === liveCheck.name)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
760
541
  kind: "check_removed",
542
+ reason: "not-equal",
761
543
  table: tableName,
762
544
  namespaceId,
763
545
  indexOrConstraint: liveCheck.name,
@@ -778,7 +560,20 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
778
560
  return nodes;
779
561
  }
780
562
  //#endregion
781
- //#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
+ }
782
577
  /**
783
578
  * Verifies that a SqlSchemaIR matches a Contract.
784
579
  *
@@ -802,7 +597,8 @@ function verifySqlSchema(options) {
802
597
  codecHooks,
803
598
  storageTypes: contract.storage.types ?? {},
804
599
  ...ifDefined("normalizeDefault", normalizeDefault),
805
- ...ifDefined("normalizeNativeType", normalizeNativeType)
600
+ ...ifDefined("normalizeNativeType", normalizeNativeType),
601
+ ...ifDefined("restrictToNamespaceIds", options.restrictToNamespaceIds)
806
602
  });
807
603
  validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
808
604
  const typeNodes = [];
@@ -893,17 +689,18 @@ function extractContractMetadata(contract) {
893
689
  };
894
690
  }
895
691
  function verifySchemaTables(options) {
896
- const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
692
+ const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, restrictToNamespaceIds } = options;
897
693
  const contractDefaultControl = contract.defaultControlPolicy;
898
694
  const issues = [];
899
695
  const rootChildren = [];
900
696
  const schemaTables = schema.tables;
901
697
  const namespaceIds = Object.keys(contract.storage.namespaces).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
902
698
  for (const namespaceId of namespaceIds) {
699
+ if (restrictToNamespaceIds !== void 0 && !restrictToNamespaceIds.has(namespaceId)) continue;
903
700
  const ns = contract.storage.namespaces[namespaceId];
904
701
  if (!ns) continue;
905
702
  for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table ?? {})) {
906
- if (!isStorageTable(contractTableRaw)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`);
703
+ StorageTable.assert(contractTableRaw, `storage.namespaces.${namespaceId}.entries.table.${tableName}`);
907
704
  const contractTable = contractTableRaw;
908
705
  const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
909
706
  const schemaTable = schemaTables[tableName];
@@ -911,6 +708,7 @@ function verifySchemaTables(options) {
911
708
  if (!schemaTable) {
912
709
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
913
710
  kind: "missing_table",
711
+ reason: "not-found",
914
712
  table: tableName,
915
713
  namespaceId,
916
714
  message: `Table "${tableName}" is missing from database`
@@ -949,6 +747,7 @@ function verifySchemaTables(options) {
949
747
  if (strict) {
950
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), {
951
749
  kind: "extra_table",
750
+ reason: "not-expected",
952
751
  table: tableName,
953
752
  message: `Extra table "${tableName}" found in database (not in contract)`
954
753
  }, {
@@ -960,7 +759,8 @@ function verifySchemaTables(options) {
960
759
  message: `Extra table "${tableName}" found`,
961
760
  expected: void 0,
962
761
  actual: void 0,
963
- children: []
762
+ children: [],
763
+ reason: "not-expected"
964
764
  }, issues, rootChildren);
965
765
  }
966
766
  return {
@@ -1034,6 +834,7 @@ function verifyTableChildren(options) {
1034
834
  });
1035
835
  } else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1036
836
  kind: "extra_primary_key",
837
+ reason: "not-expected",
1037
838
  table: tableName,
1038
839
  namespaceId,
1039
840
  message: "Extra primary key found in database (not in contract)"
@@ -1078,6 +879,7 @@ function collectContractColumnNodes(options) {
1078
879
  if (!schemaColumn) {
1079
880
  emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1080
881
  kind: "missing_column",
882
+ reason: "not-found",
1081
883
  table: tableName,
1082
884
  namespaceId,
1083
885
  column: columnName,
@@ -1118,6 +920,7 @@ function appendExtraColumnNodes(options) {
1118
920
  const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
1119
921
  for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
1120
922
  kind: "extra_column",
923
+ reason: "not-expected",
1121
924
  table: tableName,
1122
925
  namespaceId,
1123
926
  column: columnName,
@@ -1151,6 +954,7 @@ function verifyColumn(options) {
1151
954
  if (!(contractNativeType === schemaNativeType)) {
1152
955
  const issue = {
1153
956
  kind: "type_mismatch",
957
+ reason: "not-equal",
1154
958
  table: tableName,
1155
959
  namespaceId,
1156
960
  column: columnName,
@@ -1203,6 +1007,7 @@ function verifyColumn(options) {
1203
1007
  if (contractColumn.nullable !== schemaColumn.nullable) {
1204
1008
  const issue = {
1205
1009
  kind: "nullability_mismatch",
1010
+ reason: "not-equal",
1206
1011
  table: tableName,
1207
1012
  namespaceId,
1208
1013
  column: columnName,
@@ -1232,6 +1037,7 @@ function verifyColumn(options) {
1232
1037
  const defaultDescription = describeColumnDefault(contractColumn.default);
1233
1038
  const issue = {
1234
1039
  kind: "default_missing",
1040
+ reason: "not-found",
1235
1041
  table: tableName,
1236
1042
  namespaceId,
1237
1043
  column: columnName,
@@ -1259,6 +1065,7 @@ function verifyColumn(options) {
1259
1065
  const actualDescription = schemaColumn.default;
1260
1066
  const issue = {
1261
1067
  kind: "default_mismatch",
1068
+ reason: "not-equal",
1262
1069
  table: tableName,
1263
1070
  namespaceId,
1264
1071
  column: columnName,
@@ -1286,6 +1093,7 @@ function verifyColumn(options) {
1286
1093
  } else if (strict && schemaColumn.default) {
1287
1094
  const issue = {
1288
1095
  kind: "extra_default",
1096
+ reason: "not-expected",
1289
1097
  table: tableName,
1290
1098
  namespaceId,
1291
1099
  column: columnName,
@@ -1501,7 +1309,109 @@ function formatLiteralValue(value) {
1501
1309
  if (typeof value === "string") return value;
1502
1310
  return JSON.stringify(value);
1503
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
+ }
1504
1414
  //#endregion
1505
- 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 };
1506
1416
 
1507
- //# sourceMappingURL=verify-sql-schema-CEY7b78t.mjs.map
1417
+ //# sourceMappingURL=diff.mjs.map