@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
package/dist/control.mjs CHANGED
@@ -1,17 +1,20 @@
1
- import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-CjFfO6LM.mjs";
2
- import { t as SqlContractSerializer } from "./sql-contract-serializer-BR2vC7Z-.mjs";
3
- import { a as contractToSchemaIR, c as extractCodecControlHooks, o as detectDestructiveChanges, s as resolveValueSetValues, t as verifySqlSchema } from "./verify-sql-schema-xT4udQLQ.mjs";
1
+ import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-CXd-8ydc.mjs";
2
+ import { n as classifyDiffSubjectGranularity, o as verifySqlSchemaByDiff, s as extractCodecControlHooks, t as classifyDiffEntityKind } from "./schema-verify-W3r631Jh.mjs";
3
+ import { t as SqlContractSerializer } from "./sql-contract-serializer-C75cfMSS.mjs";
4
4
  import { t as collectSupportedCodecTypeIds } from "./verify-C-G0obRm.mjs";
5
+ import { t as backingIndexColumnKeys } from "./foreign-key-index-backing-BP71iI-Q.mjs";
5
6
  import { n as temporalAuthoringPresets, r as timestampNowControlDescriptor } from "./timestamp-now-generator-CloimujU.mjs";
6
7
  import { sqlEmission } from "@prisma-next/sql-contract-emitter";
8
+ import { blindCast } from "@prisma-next/utils/casts";
7
9
  import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions } from "@prisma-next/framework-components/control";
10
+ import { isPlainRecord } from "@prisma-next/framework-components/ir";
8
11
  import { assertDescriptorSelfConsistency } from "@prisma-next/migration-tools/spaces";
9
12
  import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
10
13
  import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
11
14
  import { ifDefined } from "@prisma-next/utils/defined";
12
- import { StorageTable } from "@prisma-next/sql-contract/types";
13
- import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma-next/framework-components/psl-ast";
14
15
  import { effectiveControlPolicy } from "@prisma-next/contract/types";
16
+ import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
17
+ import { SqlSchemaIR, SqlTableIR } from "@prisma-next/sql-schema-ir/types";
15
18
  import { notOk, ok } from "@prisma-next/utils/result";
16
19
  //#region src/core/operation-preview.ts
17
20
  function isDdlStatement(sqlStatement) {
@@ -46,921 +49,6 @@ function sqlOperationsToPreview(operations) {
46
49
  })) };
47
50
  }
48
51
  //#endregion
49
- //#region src/core/psl-contract-infer/default-mapping.ts
50
- const DEFAULT_FUNCTION_ATTRIBUTES = {
51
- "autoincrement()": "@default(autoincrement())",
52
- "now()": "@default(now())"
53
- };
54
- function mapDefault(columnDefault, options) {
55
- switch (columnDefault.kind) {
56
- case "literal": return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };
57
- case "function": {
58
- const attribute = options?.functionAttributes?.[columnDefault.expression] ?? DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ?? options?.fallbackFunctionAttribute?.(columnDefault.expression);
59
- return attribute ? { attribute } : { comment: `// Raw default: ${columnDefault.expression.replace(/[\r\n]+/g, " ")}` };
60
- }
61
- }
62
- }
63
- function formatLiteralValue(value) {
64
- if (value === null) return "null";
65
- switch (typeof value) {
66
- case "boolean":
67
- case "number": return String(value);
68
- case "string": return quoteString(value);
69
- default: return quoteString(JSON.stringify(value));
70
- }
71
- }
72
- function quoteString(str) {
73
- return `"${escapeString(str)}"`;
74
- }
75
- function escapeString(str) {
76
- return JSON.stringify(str).slice(1, -1);
77
- }
78
- //#endregion
79
- //#region src/core/psl-contract-infer/name-transforms.ts
80
- const PSL_RESERVED_WORDS = new Set([
81
- "model",
82
- "enum",
83
- "types",
84
- "type",
85
- "generator",
86
- "datasource"
87
- ]);
88
- const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
89
- function hasSeparators(input) {
90
- return /[^A-Za-z0-9]/.test(input);
91
- }
92
- function extractIdentifierParts(input) {
93
- return input.match(IDENTIFIER_PART_PATTERN) ?? [];
94
- }
95
- function createSyntheticIdentifier(input) {
96
- let hash = 2166136261;
97
- for (const char of input) {
98
- hash ^= char.codePointAt(0) ?? 0;
99
- hash = Math.imul(hash, 16777619);
100
- }
101
- return `x${(hash >>> 0).toString(16)}`;
102
- }
103
- function sanitizeIdentifierCharacters(input) {
104
- const sanitized = input.replace(/[^\w]/g, "");
105
- return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);
106
- }
107
- function capitalize(word) {
108
- return word.charAt(0).toUpperCase() + word.slice(1);
109
- }
110
- function snakeToPascalCase(input) {
111
- const parts = extractIdentifierParts(input);
112
- if (parts.length === 0) return capitalize(sanitizeIdentifierCharacters(input));
113
- return parts.map(capitalize).join("");
114
- }
115
- function snakeToCamelCase(input) {
116
- const parts = extractIdentifierParts(input);
117
- if (parts.length === 0) return sanitizeIdentifierCharacters(input);
118
- const [firstPart = input, ...rest] = parts;
119
- return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join("");
120
- }
121
- function needsEscaping(name) {
122
- return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\d/.test(name);
123
- }
124
- function escapeName(name) {
125
- return `_${name}`;
126
- }
127
- function escapeIfNeeded(name) {
128
- return needsEscaping(name) ? escapeName(name) : name;
129
- }
130
- function toModelName(tableName) {
131
- let name;
132
- if (hasSeparators(tableName)) name = snakeToPascalCase(tableName);
133
- else name = tableName.charAt(0).toUpperCase() + tableName.slice(1);
134
- if (needsEscaping(name)) return {
135
- name: escapeName(name),
136
- map: tableName
137
- };
138
- if (name !== tableName) return {
139
- name,
140
- map: tableName
141
- };
142
- return { name };
143
- }
144
- function toFieldName(columnName) {
145
- let name;
146
- if (hasSeparators(columnName)) name = snakeToCamelCase(columnName);
147
- else name = columnName.charAt(0).toLowerCase() + columnName.slice(1);
148
- if (needsEscaping(name)) return {
149
- name: escapeName(name),
150
- map: columnName
151
- };
152
- if (name !== columnName) return {
153
- name,
154
- map: columnName
155
- };
156
- return { name };
157
- }
158
- function pluralize(word) {
159
- if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) return `${word}es`;
160
- if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
161
- return `${word}s`;
162
- }
163
- function deriveRelationFieldName(fkColumns, referencedTableName) {
164
- if (fkColumns.length === 1) {
165
- const [col = referencedTableName] = fkColumns;
166
- const stripped = col.replace(/_id$/i, "").replace(/Id$/, "");
167
- if (stripped.length > 0 && stripped !== col) return escapeIfNeeded(snakeToCamelCase(stripped));
168
- return escapeIfNeeded(snakeToCamelCase(referencedTableName));
169
- }
170
- return escapeIfNeeded(snakeToCamelCase(referencedTableName));
171
- }
172
- function deriveBackRelationFieldName(childModelName, isOneToOne) {
173
- const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);
174
- return isOneToOne ? base : pluralize(base);
175
- }
176
- function toNamedTypeName(columnName) {
177
- let name;
178
- if (hasSeparators(columnName)) name = snakeToPascalCase(columnName);
179
- else name = columnName.charAt(0).toUpperCase() + columnName.slice(1);
180
- return escapeIfNeeded(name);
181
- }
182
- //#endregion
183
- //#region src/core/psl-contract-infer/postgres-default-mapping.ts
184
- const POSTGRES_FUNCTION_ATTRIBUTES = { "gen_random_uuid()": "@default(dbgenerated(\"gen_random_uuid()\"))" };
185
- function formatDbGeneratedAttribute(expression) {
186
- return `@default(dbgenerated(${JSON.stringify(expression)}))`;
187
- }
188
- function createPostgresDefaultMapping() {
189
- return {
190
- functionAttributes: POSTGRES_FUNCTION_ATTRIBUTES,
191
- fallbackFunctionAttribute: formatDbGeneratedAttribute
192
- };
193
- }
194
- //#endregion
195
- //#region src/core/psl-contract-infer/postgres-type-map.ts
196
- const POSTGRES_TO_PSL = {
197
- text: "String",
198
- bool: "Boolean",
199
- boolean: "Boolean",
200
- int4: "Int",
201
- integer: "Int",
202
- int8: "BigInt",
203
- bigint: "BigInt",
204
- float8: "Float",
205
- "double precision": "Float",
206
- numeric: "Decimal",
207
- decimal: "Decimal",
208
- timestamptz: "DateTime",
209
- "timestamp with time zone": "DateTime",
210
- jsonb: "Json",
211
- bytea: "Bytes"
212
- };
213
- const PRESERVED_NATIVE_TYPES = {
214
- "character varying": {
215
- pslType: "String",
216
- attributeName: "db.VarChar"
217
- },
218
- character: {
219
- pslType: "String",
220
- attributeName: "db.Char"
221
- },
222
- char: {
223
- pslType: "String",
224
- attributeName: "db.Char"
225
- },
226
- varchar: {
227
- pslType: "String",
228
- attributeName: "db.VarChar"
229
- },
230
- uuid: {
231
- pslType: "String",
232
- attributeName: "db.Uuid"
233
- },
234
- int2: {
235
- pslType: "Int",
236
- attributeName: "db.SmallInt"
237
- },
238
- smallint: {
239
- pslType: "Int",
240
- attributeName: "db.SmallInt"
241
- },
242
- float4: {
243
- pslType: "Float",
244
- attributeName: "db.Real"
245
- },
246
- real: {
247
- pslType: "Float",
248
- attributeName: "db.Real"
249
- },
250
- timestamp: {
251
- pslType: "DateTime",
252
- attributeName: "db.Timestamp"
253
- },
254
- "timestamp without time zone": {
255
- pslType: "DateTime",
256
- attributeName: "db.Timestamp"
257
- },
258
- date: {
259
- pslType: "DateTime",
260
- attributeName: "db.Date"
261
- },
262
- time: {
263
- pslType: "DateTime",
264
- attributeName: "db.Time"
265
- },
266
- "time without time zone": {
267
- pslType: "DateTime",
268
- attributeName: "db.Time"
269
- },
270
- timetz: {
271
- pslType: "DateTime",
272
- attributeName: "db.Timetz"
273
- },
274
- "time with time zone": {
275
- pslType: "DateTime",
276
- attributeName: "db.Timetz"
277
- },
278
- json: {
279
- pslType: "Json",
280
- attributeName: "db.Json"
281
- }
282
- };
283
- const PARAMETERIZED_NATIVE_TYPES = {
284
- "character varying": {
285
- pslType: "String",
286
- attributeName: "db.VarChar"
287
- },
288
- character: {
289
- pslType: "String",
290
- attributeName: "db.Char"
291
- },
292
- char: {
293
- pslType: "String",
294
- attributeName: "db.Char"
295
- },
296
- varchar: {
297
- pslType: "String",
298
- attributeName: "db.VarChar"
299
- },
300
- numeric: {
301
- pslType: "Decimal",
302
- attributeName: "db.Numeric"
303
- },
304
- timestamp: {
305
- pslType: "DateTime",
306
- attributeName: "db.Timestamp"
307
- },
308
- timestamptz: {
309
- pslType: "DateTime",
310
- attributeName: "db.Timestamptz"
311
- },
312
- time: {
313
- pslType: "DateTime",
314
- attributeName: "db.Time"
315
- },
316
- timetz: {
317
- pslType: "DateTime",
318
- attributeName: "db.Timetz"
319
- }
320
- };
321
- const PARAMETERIZED_TYPE_PATTERN = /^(.+?)\((.+)\)$/;
322
- function getOwnMappingValue(map, key) {
323
- return Object.hasOwn(map, key) ? map[key] : void 0;
324
- }
325
- function getOwnRecordValue(map, key) {
326
- return Object.hasOwn(map, key) ? map[key] : void 0;
327
- }
328
- function createNativeTypeAttribute(name, args) {
329
- return args && args.length > 0 ? {
330
- name,
331
- args
332
- } : { name };
333
- }
334
- function splitTypeParameterList(params) {
335
- return params.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
336
- }
337
- function createPostgresTypeMap(enumTypeNames) {
338
- return { resolve(nativeType) {
339
- if (enumTypeNames?.has(nativeType)) return {
340
- pslType: nativeType,
341
- nativeType
342
- };
343
- const paramMatch = nativeType.match(PARAMETERIZED_TYPE_PATTERN);
344
- if (paramMatch) {
345
- const [, baseType = nativeType, params = ""] = paramMatch;
346
- const template = getOwnRecordValue(PARAMETERIZED_NATIVE_TYPES, baseType);
347
- if (template) return {
348
- pslType: template.pslType,
349
- nativeType,
350
- typeParams: {
351
- baseType,
352
- params
353
- },
354
- nativeTypeAttribute: createNativeTypeAttribute(template.attributeName, splitTypeParameterList(params))
355
- };
356
- }
357
- const preservedType = getOwnRecordValue(PRESERVED_NATIVE_TYPES, nativeType);
358
- if (preservedType) return {
359
- pslType: preservedType.pslType,
360
- nativeType,
361
- nativeTypeAttribute: createNativeTypeAttribute(preservedType.attributeName)
362
- };
363
- const pslType = getOwnMappingValue(POSTGRES_TO_PSL, nativeType);
364
- if (pslType) return {
365
- pslType,
366
- nativeType
367
- };
368
- return {
369
- unsupported: true,
370
- nativeType
371
- };
372
- } };
373
- }
374
- function extractEnumInfo(annotations) {
375
- const nativeEnumTypeNames = (annotations?.["pg"])?.["nativeEnumTypeNames"];
376
- const typeNames = /* @__PURE__ */ new Set();
377
- const definitions = /* @__PURE__ */ new Map();
378
- if (Array.isArray(nativeEnumTypeNames)) {
379
- for (const name of nativeEnumTypeNames) if (typeof name === "string") typeNames.add(name);
380
- }
381
- return {
382
- typeNames,
383
- definitions
384
- };
385
- }
386
- //#endregion
387
- //#region src/core/psl-contract-infer/raw-default-parser.ts
388
- const NEXTVAL_PATTERN = /^nextval\s*\(/i;
389
- const NOW_FUNCTION_PATTERN = /^(now\s*\(\s*\)|CURRENT_TIMESTAMP)$/i;
390
- const CLOCK_TIMESTAMP_PATTERN = /^clock_timestamp\s*\(\s*\)$/i;
391
- const TIMESTAMP_CAST_SUFFIX = /::timestamp(?:tz|\s+(?:with|without)\s+time\s+zone)?$/i;
392
- const TEXT_CAST_SUFFIX = /::text$/i;
393
- const NOW_LITERAL_PATTERN = /^'now'$/i;
394
- const UUID_PATTERN = /^gen_random_uuid\s*\(\s*\)$/i;
395
- const UUID_OSSP_PATTERN = /^uuid_generate_v4\s*\(\s*\)$/i;
396
- const NULL_PATTERN = /^NULL(?:::.+)?$/i;
397
- const TRUE_PATTERN = /^true$/i;
398
- const FALSE_PATTERN = /^false$/i;
399
- const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
400
- const JSON_CAST_SUFFIX = /::jsonb?$/i;
401
- const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
402
- function canonicalizeTimestampDefault(expr) {
403
- if (NOW_FUNCTION_PATTERN.test(expr)) return "now()";
404
- if (CLOCK_TIMESTAMP_PATTERN.test(expr)) return "clock_timestamp()";
405
- if (!TIMESTAMP_CAST_SUFFIX.test(expr)) return void 0;
406
- let inner = expr.replace(TIMESTAMP_CAST_SUFFIX, "").trim();
407
- if (inner.startsWith("(") && inner.endsWith(")")) inner = inner.slice(1, -1).trim();
408
- if (NOW_FUNCTION_PATTERN.test(inner)) return "now()";
409
- if (CLOCK_TIMESTAMP_PATTERN.test(inner)) return "clock_timestamp()";
410
- inner = inner.replace(TEXT_CAST_SUFFIX, "").trim();
411
- if (NOW_LITERAL_PATTERN.test(inner)) return "now()";
412
- }
413
- function parseRawDefault(rawDefault, nativeType) {
414
- const trimmed = rawDefault.trim();
415
- const normalizedType = nativeType?.toLowerCase();
416
- if (NEXTVAL_PATTERN.test(trimmed)) return {
417
- kind: "function",
418
- expression: "autoincrement()"
419
- };
420
- const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);
421
- if (canonicalTimestamp) return {
422
- kind: "function",
423
- expression: canonicalTimestamp
424
- };
425
- if (UUID_PATTERN.test(trimmed) || UUID_OSSP_PATTERN.test(trimmed)) return {
426
- kind: "function",
427
- expression: "gen_random_uuid()"
428
- };
429
- if (NULL_PATTERN.test(trimmed)) return {
430
- kind: "literal",
431
- value: null
432
- };
433
- if (TRUE_PATTERN.test(trimmed)) return {
434
- kind: "literal",
435
- value: true
436
- };
437
- if (FALSE_PATTERN.test(trimmed)) return {
438
- kind: "literal",
439
- value: false
440
- };
441
- if (NUMERIC_PATTERN.test(trimmed)) return {
442
- kind: "literal",
443
- value: Number(trimmed)
444
- };
445
- const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);
446
- if (stringMatch?.[1] !== void 0) {
447
- const unescaped = stringMatch[1].replace(/''/g, "'");
448
- if (normalizedType === "json" || normalizedType === "jsonb") {
449
- if (JSON_CAST_SUFFIX.test(trimmed)) return {
450
- kind: "function",
451
- expression: trimmed
452
- };
453
- try {
454
- return {
455
- kind: "literal",
456
- value: JSON.parse(unescaped)
457
- };
458
- } catch {}
459
- }
460
- return {
461
- kind: "literal",
462
- value: unescaped
463
- };
464
- }
465
- return {
466
- kind: "function",
467
- expression: trimmed
468
- };
469
- }
470
- //#endregion
471
- //#region src/core/psl-contract-infer/relation-inference.ts
472
- const DEFAULT_ON_DELETE = "noAction";
473
- const DEFAULT_ON_UPDATE = "noAction";
474
- const REFERENTIAL_ACTION_PSL = {
475
- noAction: "NoAction",
476
- restrict: "Restrict",
477
- cascade: "Cascade",
478
- setNull: "SetNull",
479
- setDefault: "SetDefault"
480
- };
481
- function inferRelations(tables, modelNameMap) {
482
- const relationsByTable = /* @__PURE__ */ new Map();
483
- const fkCountByPair = /* @__PURE__ */ new Map();
484
- for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
485
- const pairKey = `${table.name}→${fk.referencedTable}`;
486
- fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);
487
- }
488
- const usedFieldNames = /* @__PURE__ */ new Map();
489
- for (const table of Object.values(tables)) {
490
- const names = /* @__PURE__ */ new Set();
491
- for (const col of Object.values(table.columns)) names.add(col.name);
492
- usedFieldNames.set(table.name, names);
493
- }
494
- for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
495
- const childTableName = table.name;
496
- const parentTableName = fk.referencedTable;
497
- const childUsed = usedFieldNames.get(childTableName);
498
- const childModelName = modelNameMap.get(childTableName) ?? childTableName;
499
- const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;
500
- const pairKey = `${childTableName}→${parentTableName}`;
501
- const isSelfRelation = childTableName === parentTableName;
502
- const needsRelationName = fkCountByPair.get(pairKey) > 1 || isSelfRelation;
503
- const isOneToOne = detectOneToOne(fk, table);
504
- const childRelFieldName = resolveUniqueFieldName(deriveRelationFieldName(fk.columns, parentTableName), childUsed, parentModelName);
505
- const relationName = needsRelationName ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation) : void 0;
506
- addRelationField(relationsByTable, childTableName, buildChildRelationField(childRelFieldName, parentModelName, fk, fk.columns.some((columnName) => table.columns[columnName]?.nullable ?? false), relationName));
507
- childUsed.add(childRelFieldName);
508
- const parentUsed = usedFieldNames.get(parentTableName) ?? /* @__PURE__ */ new Set();
509
- usedFieldNames.set(parentTableName, parentUsed);
510
- const backRelFieldName = resolveUniqueFieldName(deriveBackRelationFieldName(childModelName, isOneToOne), parentUsed, childModelName);
511
- addRelationField(relationsByTable, parentTableName, {
512
- fieldName: backRelFieldName,
513
- typeName: childModelName,
514
- optional: isOneToOne,
515
- list: !isOneToOne,
516
- relationName
517
- });
518
- parentUsed.add(backRelFieldName);
519
- }
520
- return { relationsByTable };
521
- }
522
- function detectOneToOne(fk, table) {
523
- const fkCols = [...fk.columns].sort();
524
- if (table.primaryKey) {
525
- const pkCols = [...table.primaryKey.columns].sort();
526
- if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) return true;
527
- }
528
- for (const unique of table.uniques) {
529
- const uniqueCols = [...unique.columns].sort();
530
- if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) return true;
531
- }
532
- return false;
533
- }
534
- function deriveRelationName(fk, childRelationFieldName, parentModelName, isSelfRelation) {
535
- if (fk.name) return fk.name;
536
- if (isSelfRelation) return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;
537
- return fk.columns.join("_");
538
- }
539
- function buildChildRelationField(fieldName, parentModelName, fk, optional, relationName) {
540
- const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : void 0;
541
- const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : void 0;
542
- return {
543
- fieldName,
544
- typeName: parentModelName,
545
- referencedTableName: fk.referencedTable,
546
- optional,
547
- list: false,
548
- relationName,
549
- fkName: fk.name,
550
- fields: fk.columns,
551
- references: fk.referencedColumns,
552
- onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : void 0,
553
- onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : void 0
554
- };
555
- }
556
- function resolveUniqueFieldName(desired, usedNames, fallbackSuffix) {
557
- if (!usedNames.has(desired)) return desired;
558
- const withSuffix = `${desired}${fallbackSuffix}`;
559
- if (!usedNames.has(withSuffix)) return withSuffix;
560
- let counter = 2;
561
- while (usedNames.has(`${desired}${counter}`)) counter++;
562
- return `${desired}${counter}`;
563
- }
564
- function addRelationField(map, tableName, field) {
565
- const existing = map.get(tableName);
566
- if (existing) existing.push(field);
567
- else map.set(tableName, [field]);
568
- }
569
- //#endregion
570
- //#region src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
571
- const SYNTHETIC_SPAN = {
572
- start: {
573
- offset: 0,
574
- line: 1,
575
- column: 1
576
- },
577
- end: {
578
- offset: 0,
579
- line: 1,
580
- column: 1
581
- }
582
- };
583
- const PSL_SCALAR_TYPE_NAMES = new Set([
584
- "String",
585
- "Boolean",
586
- "Int",
587
- "BigInt",
588
- "Float",
589
- "Decimal",
590
- "DateTime",
591
- "Json",
592
- "Bytes"
593
- ]);
594
- /**
595
- * Converts a SQL schema IR into a PSL AST suitable for `printPsl`.
596
- *
597
- * This function owns all SQL-specific concerns: native type mapping (Postgres),
598
- * relation inference from foreign keys, enum extraction, and raw default parsing.
599
- * The output is a fully-formed `PslDocumentAst` with synthetic spans.
600
- */
601
- function sqlSchemaIrToPslAst(schemaIR) {
602
- const enumInfo = extractEnumInfo(schemaIR.annotations);
603
- if (enumInfo.typeNames.size > 0) {
604
- const names = [...enumInfo.typeNames].join(", ");
605
- throw new Error(`contract infer: the database contains native Postgres enum type(s): ${names}. Native Postgres enums (CREATE TYPE … AS ENUM) are not adoptable by contract infer. Drop the native type and replace each column with a text column carrying a CHECK constraint, then re-run contract infer. The domain enum (enum Name { @@type("pg/text@1") … }) authoring surface generates the required check automatically.`);
606
- }
607
- return buildPslDocumentAst(schemaIR, {
608
- typeMap: createPostgresTypeMap(/* @__PURE__ */ new Set()),
609
- defaultMapping: createPostgresDefaultMapping(),
610
- parseRawDefault
611
- });
612
- }
613
- function buildPslDocumentAst(schemaIR, options) {
614
- const { typeMap, defaultMapping, parseRawDefault: rawDefaultParser } = options;
615
- const modelNames = buildTopLevelNameMap(Object.keys(schemaIR.tables), toModelName, "model", "table");
616
- const modelNameMap = new Map([...modelNames].map(([tableName, result]) => [tableName, result.name]));
617
- const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames);
618
- const fieldNamesByTable = buildFieldNamesByTable(schemaIR.tables);
619
- const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);
620
- const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap, /* @__PURE__ */ new Map(), reservedNamedTypeNames);
621
- const models = [];
622
- for (const table of Object.values(schemaIR.tables)) models.push(buildModel(table, typeMap, /* @__PURE__ */ new Map(), fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationsByTable.get(table.name) ?? []));
623
- const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);
624
- const namedTypeEntries = [...namedTypes.entriesByKey.values()].sort((a, b) => a.name.localeCompare(b.name));
625
- const types = namedTypeEntries.length > 0 ? {
626
- kind: "types",
627
- declarations: namedTypeEntries.map(buildNamedTypeDeclaration),
628
- span: SYNTHETIC_SPAN
629
- } : void 0;
630
- return {
631
- kind: "document",
632
- sourceId: "<sql-schema-ir>",
633
- namespaces: [makePslNamespace({
634
- kind: "namespace",
635
- name: UNSPECIFIED_PSL_NAMESPACE_ID,
636
- entries: makePslNamespaceEntries(sortedModels, [], []),
637
- span: SYNTHETIC_SPAN
638
- })],
639
- ...types ? { types } : {},
640
- span: SYNTHETIC_SPAN
641
- };
642
- }
643
- function buildModel(table, typeMap, enumNameMap, fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationFields) {
644
- const { name: modelName, map: mapName } = toModelName(table.name);
645
- const fieldNameMap = fieldNamesByTable.get(table.name);
646
- const pkColumns = new Set(table.primaryKey?.columns ?? []);
647
- const isSinglePk = pkColumns.size === 1;
648
- const singlePkConstraintName = isSinglePk ? table.primaryKey?.name : void 0;
649
- const uniqueColumns = /* @__PURE__ */ new Map();
650
- for (const unique of table.uniques) if (unique.columns.length === 1) {
651
- const [columnName = ""] = unique.columns;
652
- const existingConstraintName = uniqueColumns.get(columnName);
653
- if (!uniqueColumns.has(columnName) || existingConstraintName === void 0 && unique.name) uniqueColumns.set(columnName, unique.name);
654
- }
655
- const fields = [];
656
- for (const column of Object.values(table.columns)) fields.push(buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns));
657
- const usedFieldNames = new Set(fields.map((field) => field.name));
658
- for (const rel of relationFields) fields.push(buildRelationField(rel, table.name, fieldNamesByTable, usedFieldNames));
659
- const modelAttributes = [];
660
- if (table.primaryKey && table.primaryKey.columns.length > 1) {
661
- const pkFieldNames = table.primaryKey.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
662
- modelAttributes.push(buildModelConstraintAttribute("id", pkFieldNames, table.primaryKey.name));
663
- }
664
- for (const unique of table.uniques) if (unique.columns.length > 1) {
665
- const uniqueFieldNames = unique.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
666
- modelAttributes.push(buildModelConstraintAttribute("unique", uniqueFieldNames, unique.name));
667
- }
668
- for (const index of table.indexes) if (!index.unique) {
669
- const indexFieldNames = index.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
670
- modelAttributes.push(buildModelConstraintAttribute("index", indexFieldNames, index.name));
671
- }
672
- if (mapName) modelAttributes.push(buildMapAttribute("model", mapName));
673
- const comment = table.primaryKey ? void 0 : "// WARNING: This table has no primary key in the database";
674
- return {
675
- kind: "model",
676
- name: modelName,
677
- fields,
678
- attributes: modelAttributes,
679
- span: SYNTHETIC_SPAN,
680
- ...comment !== void 0 ? { comment } : {}
681
- };
682
- }
683
- function buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns) {
684
- const resolvedField = fieldNameMap?.get(column.name);
685
- const fieldName = resolvedField?.fieldName ?? toFieldName(column.name).name;
686
- const fieldMap = resolvedField?.fieldMap;
687
- const resolution = typeMap.resolve(column.nativeType, table.annotations);
688
- if ("unsupported" in resolution) {
689
- const attrs = [];
690
- if (fieldMap !== void 0) attrs.push(buildMapAttribute("field", fieldMap));
691
- return {
692
- kind: "field",
693
- name: fieldName,
694
- typeName: `Unsupported("${escapePslString(resolution.nativeType)}")`,
695
- optional: column.nullable,
696
- list: false,
697
- attributes: attrs,
698
- span: SYNTHETIC_SPAN
699
- };
700
- }
701
- let typeName = resolution.pslType;
702
- const enumPslName = enumNameMap.get(column.nativeType);
703
- if (enumPslName) typeName = enumPslName;
704
- if (resolution.nativeTypeAttribute && !enumPslName) typeName = resolveNamedTypeName(namedTypes, resolution);
705
- const attributes = [];
706
- const isId = isSinglePk && pkColumns.has(column.name);
707
- if (isId) attributes.push(buildSimpleConstraintFieldAttribute("id", singlePkConstraintName));
708
- if (column.default !== void 0) {
709
- const parsed = parseColumnDefault(column.default, column.nativeType, rawDefaultParser);
710
- if (parsed) {
711
- const result = mapDefault(parsed, defaultMapping);
712
- if ("attribute" in result) attributes.push(parseDefaultAttributeString(result.attribute));
713
- }
714
- }
715
- if (uniqueColumns.has(column.name) && !isId) {
716
- const uniqueConstraintName = uniqueColumns.get(column.name);
717
- attributes.push(buildSimpleConstraintFieldAttribute("unique", uniqueConstraintName));
718
- }
719
- if (fieldMap !== void 0) attributes.push(buildMapAttribute("field", fieldMap));
720
- return {
721
- kind: "field",
722
- name: fieldName,
723
- typeName,
724
- optional: column.nullable,
725
- list: false,
726
- attributes,
727
- span: SYNTHETIC_SPAN
728
- };
729
- }
730
- function buildRelationField(rel, hostTableName, fieldNamesByTable, usedFieldNames) {
731
- const fieldName = createUniqueFieldName(rel.fieldName, usedFieldNames);
732
- usedFieldNames.add(fieldName);
733
- const args = [];
734
- if (rel.fields && rel.references) {
735
- if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
736
- args.push(namedArg("fields", `[${rel.fields.map((columnName) => resolveColumnFieldName(fieldNamesByTable, hostTableName, columnName)).join(", ")}]`));
737
- args.push(namedArg("references", `[${rel.references.map((columnName) => resolveColumnFieldName(fieldNamesByTable, rel.referencedTableName ?? "", columnName)).join(", ")}]`));
738
- if (rel.onDelete) args.push(namedArg("onDelete", rel.onDelete));
739
- if (rel.onUpdate) args.push(namedArg("onUpdate", rel.onUpdate));
740
- if (rel.fkName) args.push(namedArg("map", `"${escapePslString(rel.fkName)}"`));
741
- } else if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
742
- const attrs = args.length > 0 ? [buildAttribute("field", "relation", args)] : [];
743
- return {
744
- kind: "field",
745
- name: fieldName,
746
- typeName: rel.typeName,
747
- optional: rel.optional,
748
- list: rel.list,
749
- attributes: attrs,
750
- span: SYNTHETIC_SPAN
751
- };
752
- }
753
- function buildModelConstraintAttribute(name, fields, constraintName) {
754
- const args = [positionalArg(`[${fields.join(", ")}]`)];
755
- if (constraintName !== void 0) args.push(namedArg("map", `"${escapePslString(constraintName)}"`));
756
- return buildAttribute("model", name, args);
757
- }
758
- function buildSimpleConstraintFieldAttribute(name, constraintName) {
759
- if (constraintName === void 0) return buildAttribute("field", name, []);
760
- return buildAttribute("field", name, [namedArg("map", `"${escapePslString(constraintName)}"`)]);
761
- }
762
- function parseDefaultAttributeString(attributeText) {
763
- return buildAttribute("field", "default", [positionalArg(attributeText.replace(/^@default\(/, "").replace(/\)$/, ""))]);
764
- }
765
- function buildMapAttribute(target, mapName) {
766
- return buildAttribute(target, "map", [positionalArg(`"${escapePslString(mapName)}"`)]);
767
- }
768
- function buildAttribute(target, name, args) {
769
- return {
770
- kind: "attribute",
771
- target,
772
- name,
773
- args,
774
- span: SYNTHETIC_SPAN
775
- };
776
- }
777
- function positionalArg(value) {
778
- return {
779
- kind: "positional",
780
- value,
781
- span: SYNTHETIC_SPAN
782
- };
783
- }
784
- function namedArg(name, value) {
785
- return {
786
- kind: "named",
787
- name,
788
- value,
789
- span: SYNTHETIC_SPAN
790
- };
791
- }
792
- function buildNamedTypeDeclaration(entry) {
793
- const attribute = buildAttribute("namedType", entry.nativeTypeAttribute.name, (entry.nativeTypeAttribute.args ?? []).map(positionalArg));
794
- return {
795
- kind: "namedType",
796
- name: entry.name,
797
- baseType: entry.baseType,
798
- attributes: [attribute],
799
- span: SYNTHETIC_SPAN
800
- };
801
- }
802
- function escapePslString(value) {
803
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
804
- }
805
- /**
806
- * Resolves a `SqlColumnIR.default` value into a normalized {@link ColumnDefault}.
807
- *
808
- * `SqlSchemaIR` types the column default as `string` (a raw database default
809
- * expression). Some legacy fixtures and tests still pass already-normalized
810
- * `ColumnDefault` objects in the same slot, so we accept either shape
811
- * defensively at runtime.
812
- */
813
- function parseColumnDefault(value, nativeType, rawDefaultParser) {
814
- if (typeof value === "string") return rawDefaultParser ? rawDefaultParser(value, nativeType) : void 0;
815
- if (value !== null && typeof value === "object" && "kind" in value) return value;
816
- }
817
- function buildFieldNamesByTable(tables) {
818
- const fieldNamesByTable = /* @__PURE__ */ new Map();
819
- for (const table of Object.values(tables)) {
820
- const assignmentOrder = [...Object.values(table.columns).map((column, index) => {
821
- const { name, map } = toFieldName(column.name);
822
- return {
823
- columnName: column.name,
824
- desiredFieldName: name,
825
- fieldMap: map,
826
- index
827
- };
828
- })].sort((left, right) => {
829
- const mapComparison = Number(left.fieldMap !== void 0) - Number(right.fieldMap !== void 0);
830
- if (mapComparison !== 0) return mapComparison;
831
- return left.index - right.index;
832
- });
833
- const usedFieldNames = /* @__PURE__ */ new Set();
834
- const tableFieldNames = /* @__PURE__ */ new Map();
835
- for (const column of assignmentOrder) {
836
- const fieldName = createUniqueFieldName(column.desiredFieldName, usedFieldNames);
837
- usedFieldNames.add(fieldName);
838
- tableFieldNames.set(column.columnName, {
839
- fieldName,
840
- fieldMap: column.fieldMap
841
- });
842
- }
843
- fieldNamesByTable.set(table.name, tableFieldNames);
844
- }
845
- return fieldNamesByTable;
846
- }
847
- function resolveColumnFieldName(fieldNamesByTable, tableName, columnName) {
848
- return fieldNamesByTable.get(tableName)?.get(columnName)?.fieldName ?? toFieldName(columnName).name;
849
- }
850
- function createUniqueFieldName(desiredName, usedFieldNames) {
851
- if (!usedFieldNames.has(desiredName)) return desiredName;
852
- let counter = 2;
853
- while (usedFieldNames.has(`${desiredName}${counter}`)) counter++;
854
- return `${desiredName}${counter}`;
855
- }
856
- function buildTopLevelNameMap(sources, normalize, kind, sourceKind) {
857
- const results = /* @__PURE__ */ new Map();
858
- const normalizedToSources = /* @__PURE__ */ new Map();
859
- for (const source of sources) {
860
- const normalized = normalize(source);
861
- results.set(source, normalized);
862
- normalizedToSources.set(normalized.name, [...normalizedToSources.get(normalized.name) ?? [], source]);
863
- }
864
- const duplicates = [...normalizedToSources.entries()].filter(([, conflictingSources]) => conflictingSources.length > 1);
865
- if (duplicates.length > 0) {
866
- const details = duplicates.map(([normalizedName, conflictingSources]) => `- ${kind} "${normalizedName}" from ${sourceKind}s ${conflictingSources.map((source) => `"${source}"`).join(", ")}`);
867
- throw new Error(`PSL ${kind} name collisions detected:\n${details.join("\n")}`);
868
- }
869
- return results;
870
- }
871
- function createReservedNamedTypeNames(modelNames) {
872
- const reservedNames = new Set(PSL_SCALAR_TYPE_NAMES);
873
- for (const result of modelNames.values()) reservedNames.add(result.name);
874
- return reservedNames;
875
- }
876
- function seedNamedTypeRegistry(schemaIR, typeMap, enumNameMap, reservedNames) {
877
- const seeds = /* @__PURE__ */ new Map();
878
- for (const tableName of Object.keys(schemaIR.tables).sort()) {
879
- const table = schemaIR.tables[tableName];
880
- if (!table) continue;
881
- for (const columnName of Object.keys(table.columns).sort()) {
882
- const column = table.columns[columnName];
883
- if (!column) continue;
884
- const resolution = typeMap.resolve(column.nativeType, table.annotations);
885
- if ("unsupported" in resolution || enumNameMap.has(column.nativeType) || !resolution.nativeTypeAttribute) continue;
886
- const signatureKey = createNamedTypeSignatureKey(resolution);
887
- if (!seeds.has(signatureKey)) seeds.set(signatureKey, {
888
- baseType: resolution.pslType,
889
- desiredName: toNamedTypeName(column.name),
890
- nativeTypeAttribute: resolution.nativeTypeAttribute
891
- });
892
- }
893
- }
894
- const registry = {
895
- entriesByKey: /* @__PURE__ */ new Map(),
896
- usedNames: new Set(reservedNames)
897
- };
898
- const sortedSeeds = [...seeds.entries()].sort((left, right) => {
899
- const desiredNameComparison = left[1].desiredName.localeCompare(right[1].desiredName);
900
- if (desiredNameComparison !== 0) return desiredNameComparison;
901
- return left[0].localeCompare(right[0]);
902
- });
903
- for (const [signatureKey, seed] of sortedSeeds) {
904
- const name = createUniqueFieldName(seed.desiredName, registry.usedNames);
905
- registry.entriesByKey.set(signatureKey, {
906
- name,
907
- baseType: seed.baseType,
908
- nativeTypeAttribute: seed.nativeTypeAttribute
909
- });
910
- registry.usedNames.add(name);
911
- }
912
- return registry;
913
- }
914
- function resolveNamedTypeName(registry, resolution) {
915
- const key = createNamedTypeSignatureKey(resolution);
916
- const existing = registry.entriesByKey.get(key);
917
- if (existing) return existing.name;
918
- throw new Error(`Named type registry was not seeded for native type "${resolution.nativeType}"`);
919
- }
920
- function createNamedTypeSignatureKey(resolution) {
921
- return JSON.stringify({
922
- baseType: resolution.pslType,
923
- nativeTypeAttribute: resolution.nativeTypeAttribute ? {
924
- name: resolution.nativeTypeAttribute.name,
925
- args: resolution.nativeTypeAttribute.args ?? null
926
- } : null
927
- });
928
- }
929
- function topologicalSort(models, tables, modelNameMap) {
930
- const modelByName = /* @__PURE__ */ new Map();
931
- for (const model of models) modelByName.set(model.name, model);
932
- const deps = /* @__PURE__ */ new Map();
933
- const tableToModel = /* @__PURE__ */ new Map();
934
- for (const tableName of Object.keys(tables)) {
935
- const modelName = modelNameMap.get(tableName);
936
- tableToModel.set(tableName, modelName);
937
- deps.set(modelName, /* @__PURE__ */ new Set());
938
- }
939
- for (const [tableName, table] of Object.entries(tables)) {
940
- const modelName = tableToModel.get(tableName);
941
- for (const fk of table.foreignKeys) {
942
- const refModelName = tableToModel.get(fk.referencedTable);
943
- if (refModelName && refModelName !== modelName) deps.get(modelName).add(refModelName);
944
- }
945
- }
946
- const result = [];
947
- const visited = /* @__PURE__ */ new Set();
948
- const visiting = /* @__PURE__ */ new Set();
949
- const sortedNames = [...deps.keys()].sort();
950
- function visit(name) {
951
- if (visited.has(name)) return;
952
- if (visiting.has(name)) return;
953
- visiting.add(name);
954
- const sortedDeps = [...deps.get(name)].sort();
955
- for (const dep of sortedDeps) visit(dep);
956
- visiting.delete(name);
957
- visited.add(name);
958
- result.push(modelByName.get(name));
959
- }
960
- for (const name of sortedNames) visit(name);
961
- return result;
962
- }
963
- //#endregion
964
52
  //#region src/core/control-instance.ts
965
53
  function extractCodecTypeIdsFromContract(contract) {
966
54
  const typeIds = /* @__PURE__ */ new Set();
@@ -1050,7 +138,7 @@ function buildTransitiveDependsOnMap(extensions) {
1050
138
  }
1051
139
  return set;
1052
140
  };
1053
- for (const ext of extensions) resolve(ext.id, new Set([ext.id]));
141
+ for (const ext of extensions) resolve(ext.id, /* @__PURE__ */ new Set([ext.id]));
1054
142
  return result;
1055
143
  }
1056
144
  /**
@@ -1123,8 +211,18 @@ function createSqlFamilyInstance(stack) {
1123
211
  let controlAdapter;
1124
212
  const getControlAdapter = () => controlAdapter ??= adapter.create(stack);
1125
213
  const targetSerializer = target.contractSerializer;
1126
- const deserializeWithTargetSerializer = (contractJson) => {
1127
- return (targetSerializer ?? new SqlContractSerializer()).deserializeContract(contractJson);
214
+ const targetInferPslContract = blindCast(target).inferPslContract;
215
+ const diffSchema = blindCast(target).diffSchema;
216
+ const targetGranularityOf = blindCast(target).classifySubjectGranularity;
217
+ const targetEntityKindOf = blindCast(target).classifyEntityKind;
218
+ const describedContracts = extensions.flatMap((extension) => extension.contractSpace ? [{
219
+ spaceId: extension.id,
220
+ contract: extension.contractSpace.contractJson
221
+ }] : []);
222
+ const deserializeWithTargetSerializer = (contractOrJson) => {
223
+ const serializer = targetSerializer ?? new SqlContractSerializer();
224
+ const json = targetSerializer !== void 0 && !isPlainRecord(contractOrJson) ? targetSerializer.serializeContract(blindCast(contractOrJson)) : contractOrJson;
225
+ return serializer.deserializeContract(json);
1128
226
  };
1129
227
  return {
1130
228
  familyId: "sql",
@@ -1227,17 +325,41 @@ function createSqlFamilyInstance(stack) {
1227
325
  },
1228
326
  verifySchema(options) {
1229
327
  const contract = deserializeWithTargetSerializer(options.contract);
1230
- const controlAdapter = getControlAdapter();
1231
- return verifySqlSchema({
328
+ if (!diffSchema) throw new Error(`SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`);
329
+ if (!targetGranularityOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`);
330
+ return verifySqlSchemaByDiff({
1232
331
  contract,
1233
332
  schema: options.schema,
1234
333
  strict: options.strict,
1235
- typeMetadataRegistry,
1236
334
  frameworkComponents: options.frameworkComponents,
1237
- ...ifDefined("normalizeDefault", controlAdapter.normalizeDefault),
1238
- ...ifDefined("normalizeNativeType", controlAdapter.normalizeNativeType)
335
+ diffSchema,
336
+ granularityOf: targetGranularityOf
1239
337
  });
1240
338
  },
339
+ /**
340
+ * Classifies a diff issue's subject granularity on demand, by resolving
341
+ * its node's `nodeKind` through the target's classifier — the
342
+ * {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
343
+ * capability. Framework consumers spanning contract spaces (the
344
+ * migration aggregate's unclaimed-elements sweep) detect and call this
345
+ * instead of reaching into the concrete schema-IR node, which they
346
+ * cannot read; nothing is stamped on the issue or the node.
347
+ */
348
+ classifySubjectGranularity(issue) {
349
+ if (!targetGranularityOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`);
350
+ return classifyDiffSubjectGranularity(issue, targetGranularityOf);
351
+ },
352
+ /**
353
+ * Classifies a diff issue's subject storage `entityKind` on demand, by
354
+ * resolving its node's `nodeKind` through the target's classifier —
355
+ * the sibling of `classifySubjectGranularity` above, and part of the
356
+ * same {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
357
+ * capability.
358
+ */
359
+ classifyEntityKind(issue) {
360
+ if (!targetEntityKindOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`);
361
+ return classifyDiffEntityKind(issue, targetEntityKindOf);
362
+ },
1241
363
  async sign(options) {
1242
364
  const { driver, contract: contractInput, contractPath, configPath } = options;
1243
365
  const startTime = Date.now();
@@ -1326,7 +448,8 @@ function createSqlFamilyInstance(stack) {
1326
448
  return getControlAdapter().introspect(options.driver, options.contract);
1327
449
  },
1328
450
  inferPslContract(schemaIR) {
1329
- return sqlSchemaIrToPslAst(schemaIR);
451
+ if (!targetInferPslContract) throw new Error(`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`);
452
+ return targetInferPslContract(schemaIR, describedContracts);
1330
453
  },
1331
454
  lowerAst(ast, context) {
1332
455
  return getControlAdapter().lowerToExecuteRequest(ast, context);
@@ -1334,21 +457,25 @@ function createSqlFamilyInstance(stack) {
1334
457
  bootstrapControlTableQueries() {
1335
458
  return getControlAdapter().bootstrapControlTableQueries();
1336
459
  },
1337
- bootstrapSignMarkerQueries() {
1338
- return getControlAdapter().bootstrapSignMarkerQueries();
1339
- },
1340
460
  toOperationPreview(operations) {
1341
461
  return sqlOperationsToPreview(operations);
1342
462
  },
1343
463
  toSchemaView(schema) {
1344
- const tableNodes = Object.entries(schema.tables).map(([tableName, table]) => {
464
+ const root = blindCast(schema);
465
+ const namespaceEntries = root.namespaces !== void 0 ? Object.entries(root.namespaces).map(([namespaceKey, namespace]) => [namespaceKey, namespace.tables]) : [[void 0, root.tables ?? {}]];
466
+ const qualify = namespaceEntries.length > 1;
467
+ const tableNodes = namespaceEntries.flatMap(([namespaceKey, tables]) => Object.entries(tables).map(([tableName, table]) => [
468
+ qualify && namespaceKey !== void 0 ? `${namespaceKey}.${tableName}` : tableName,
469
+ tableName,
470
+ table
471
+ ])).map(([displayName, tableName, table]) => {
1345
472
  const children = [];
1346
473
  const columnNodes = [];
1347
474
  for (const [columnName, column] of Object.entries(table.columns)) {
1348
475
  const label = `${columnName}: ${column.nativeType} (${column.nullable ? "nullable" : "not nullable"})`;
1349
476
  columnNodes.push(new SchemaTreeNode({
1350
477
  kind: "field",
1351
- id: `column-${tableName}-${columnName}`,
478
+ id: `column-${displayName}-${columnName}`,
1352
479
  label,
1353
480
  meta: {
1354
481
  nativeType: column.nativeType,
@@ -1359,7 +486,7 @@ function createSqlFamilyInstance(stack) {
1359
486
  }
1360
487
  if (columnNodes.length > 0) children.push(new SchemaTreeNode({
1361
488
  kind: "collection",
1362
- id: `columns-${tableName}`,
489
+ id: `columns-${displayName}`,
1363
490
  label: "columns",
1364
491
  children: columnNodes
1365
492
  }));
@@ -1367,7 +494,7 @@ function createSqlFamilyInstance(stack) {
1367
494
  const pkColumns = table.primaryKey.columns.join(", ");
1368
495
  children.push(new SchemaTreeNode({
1369
496
  kind: "index",
1370
- id: `primary-key-${tableName}`,
497
+ id: `primary-key-${displayName}`,
1371
498
  label: `primary key: ${pkColumns}`,
1372
499
  meta: {
1373
500
  columns: table.primaryKey.columns,
@@ -1380,7 +507,7 @@ function createSqlFamilyInstance(stack) {
1380
507
  const label = `unique ${name}`;
1381
508
  children.push(new SchemaTreeNode({
1382
509
  kind: "index",
1383
- id: `unique-${tableName}-${name}`,
510
+ id: `unique-${displayName}-${name}`,
1384
511
  label,
1385
512
  meta: {
1386
513
  columns: unique.columns,
@@ -1393,7 +520,7 @@ function createSqlFamilyInstance(stack) {
1393
520
  const label = index.unique ? `unique index ${name}` : `index ${name}`;
1394
521
  children.push(new SchemaTreeNode({
1395
522
  kind: "index",
1396
- id: `index-${tableName}-${name}`,
523
+ id: `index-${displayName}-${name}`,
1397
524
  label,
1398
525
  meta: {
1399
526
  columns: index.columns,
@@ -1414,8 +541,8 @@ function createSqlFamilyInstance(stack) {
1414
541
  }));
1415
542
  return new SchemaTreeNode({
1416
543
  kind: "entity",
1417
- id: `table-${tableName}`,
1418
- label: `table ${tableName}`,
544
+ id: `table-${displayName}`,
545
+ label: `table ${displayName}`,
1419
546
  ...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
1420
547
  ...children.length > 0 ? { children } : {}
1421
548
  });
@@ -1448,6 +575,264 @@ var SqlFamilyDescriptor = class {
1448
575
  }
1449
576
  };
1450
577
  //#endregion
578
+ //#region src/core/migrations/contract-to-schema-ir.ts
579
+ function convertColumn(name, column, storageTypes, expandNativeType, renderDefault) {
580
+ const resolved = resolveColumnTypeMetadata(column, storageTypes);
581
+ const baseNativeType = expandNativeType ? expandNativeType({
582
+ nativeType: resolved.nativeType,
583
+ codecId: resolved.codecId,
584
+ ...ifDefined("typeParams", resolved.typeParams)
585
+ }) : resolved.nativeType;
586
+ const nativeType = baseNativeType;
587
+ const resolvedNativeType = column.many ? `${baseNativeType}[]` : baseNativeType;
588
+ return {
589
+ name,
590
+ nativeType,
591
+ nullable: column.nullable,
592
+ ...ifDefined("many", column.many),
593
+ ...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0),
594
+ resolvedNativeType,
595
+ ...ifDefined("resolvedDefault", column.default ?? void 0),
596
+ codecRef: buildColumnCodecRef(resolved, column.many),
597
+ codecBaseNativeType: resolved.nativeType,
598
+ ...column.typeRef !== void 0 ? { codecNamedType: true } : {}
599
+ };
600
+ }
601
+ /**
602
+ * Builds the column's `CodecRef` from its resolved (post-`typeRef`) codec
603
+ * identity — the same construction the query AST and the migration DDL
604
+ * renderer already use (TML-2456, TML-2918).
605
+ */
606
+ function buildColumnCodecRef(resolved, many) {
607
+ return {
608
+ codecId: resolved.codecId,
609
+ ...ifDefined("typeParams", resolved.typeParams !== void 0 ? blindCast(resolved.typeParams) : void 0),
610
+ ...ifDefined("many", many)
611
+ };
612
+ }
613
+ function resolveColumnTypeMetadata(column, storageTypes) {
614
+ if (!column.typeRef) return column;
615
+ const referenced = storageTypes[column.typeRef];
616
+ if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
617
+ if (isStorageTypeInstance(referenced)) return {
618
+ codecId: referenced.codecId,
619
+ nativeType: referenced.nativeType,
620
+ typeParams: referenced.typeParams
621
+ };
622
+ throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`);
623
+ }
624
+ /**
625
+ * Resolves a `ValueSetRef` to its permitted values from the contract storage.
626
+ *
627
+ * Throws when the referenced namespace or value-set is absent — this indicates
628
+ * the contract was built incorrectly (the check and the value-set must be
629
+ * co-emitted by the lowering step). Used by `convertCheck` (schema-IR
630
+ * projection), `verifyCheckConstraints` (verification), and
631
+ * `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
632
+ * the resolved values and the error behavior on a missing reference.
633
+ */
634
+ function allStrings(values) {
635
+ return values.every((value) => typeof value === "string");
636
+ }
637
+ function resolveValueSetValues(ref, storage, contextLabel) {
638
+ const ns = storage.namespaces[ref.namespaceId];
639
+ if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
640
+ const valueSet = ns.entries.valueSet?.[ref.entityName];
641
+ if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
642
+ const values = valueSet.values;
643
+ 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})`);
644
+ return values;
645
+ }
646
+ /**
647
+ * Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
648
+ * resolving the permitted values from the storage value-set it references.
649
+ *
650
+ * The `CheckConstraint.valueSet` ref points to
651
+ * `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
652
+ * values are lifted directly from `StorageValueSet.values` so verification
653
+ * compares value sets, not SQL predicate strings.
654
+ *
655
+ * Throws if the referenced namespace or value-set is absent — this
656
+ * indicates the contract was built incorrectly (the check and the
657
+ * value-set must be co-emitted by the lowering step).
658
+ */
659
+ function convertCheck(check, storage) {
660
+ const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
661
+ return {
662
+ name: check.name,
663
+ column: check.column,
664
+ permittedValues
665
+ };
666
+ }
667
+ function convertUnique(unique) {
668
+ return {
669
+ columns: unique.columns,
670
+ ...ifDefined("name", unique.name)
671
+ };
672
+ }
673
+ function convertIndex(index) {
674
+ return {
675
+ columns: index.columns,
676
+ unique: false,
677
+ ...ifDefined("name", index.name),
678
+ ...ifDefined("type", index.type),
679
+ ...ifDefined("options", index.options)
680
+ };
681
+ }
682
+ /**
683
+ * The FK's referenced-namespace identity comes from the target's namespace
684
+ * node, not the raw namespace-id string. An unbound target namespace stamps
685
+ * no `referencedSchema` at all — the FK node's id renders the absence as the
686
+ * empty segment, which is what flat (single-schema) introspection produces,
687
+ * so both diff sides' FK ids meet by construction. A bound namespace (or a
688
+ * cross-space target whose namespace lives in another contract's storage)
689
+ * stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
690
+ * real DDL schema downstream.
691
+ */
692
+ function convertForeignKey(fk, storage) {
693
+ const targetIsUnbound = storage.namespaces[fk.target.namespaceId]?.isUnbound === true;
694
+ return {
695
+ columns: fk.source.columns,
696
+ referencedTable: fk.target.tableName,
697
+ ...targetIsUnbound ? {} : { referencedSchema: fk.target.namespaceId },
698
+ referencedColumns: fk.target.columns,
699
+ ...ifDefined("name", fk.name),
700
+ ...ifDefined("onDelete", fk.onDelete),
701
+ ...ifDefined("onUpdate", fk.onUpdate)
702
+ };
703
+ }
704
+ function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
705
+ const columns = {};
706
+ for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
707
+ const satisfiedIndexColumns = new Set(backingIndexColumnKeys(table));
708
+ const fkBackingIndexes = [];
709
+ for (const fk of table.foreignKeys) {
710
+ if (fk.index === false) continue;
711
+ const key = fk.source.columns.join(",");
712
+ if (satisfiedIndexColumns.has(key)) continue;
713
+ fkBackingIndexes.push({
714
+ columns: fk.source.columns,
715
+ unique: false,
716
+ name: defaultIndexName(name, fk.source.columns)
717
+ });
718
+ satisfiedIndexColumns.add(key);
719
+ }
720
+ const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
721
+ return new SqlTableIR({
722
+ name,
723
+ columns,
724
+ ...ifDefined("primaryKey", table.primaryKey),
725
+ foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map((fk) => convertForeignKey(fk, storage)),
726
+ uniques: table.uniques.map(convertUnique),
727
+ indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
728
+ ...ifDefined("checks", checks)
729
+ });
730
+ }
731
+ /**
732
+ * Detects destructive changes between two contract storages.
733
+ *
734
+ * The additive-only planner silently ignores removals (tables, columns).
735
+ * This function detects those removals so callers can report them as conflicts
736
+ * rather than silently producing an empty plan.
737
+ *
738
+ * Returns an empty array if no destructive changes are found.
739
+ */
740
+ function detectDestructiveChanges(from, to) {
741
+ if (!from) return [];
742
+ const hasOwn = (value, key) => Object.hasOwn(value, key);
743
+ const conflicts = [];
744
+ const namespaceIds = [.../* @__PURE__ */ new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
745
+ for (const namespaceId of namespaceIds) {
746
+ const fromNs = from.namespaces[namespaceId];
747
+ const toNs = to.namespaces[namespaceId];
748
+ const fromTables = fromNs?.entries.table;
749
+ if (!fromTables) continue;
750
+ for (const tableName of Object.keys(fromTables)) {
751
+ const toTableRaw = toNs?.entries.table?.[tableName];
752
+ if (!StorageTable.is(toTableRaw)) {
753
+ conflicts.push({
754
+ kind: "tableRemoved",
755
+ summary: `Table "${tableName}" was removed`
756
+ });
757
+ continue;
758
+ }
759
+ const toTable = toTableRaw;
760
+ const fromTableRaw = fromTables[tableName];
761
+ if (!StorageTable.is(fromTableRaw)) continue;
762
+ const fromTable = fromTableRaw;
763
+ for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
764
+ kind: "columnRemoved",
765
+ summary: `Column "${tableName}"."${columnName}" was removed`
766
+ });
767
+ }
768
+ }
769
+ return conflicts;
770
+ }
771
+ /**
772
+ * Converts a `Contract` to `SqlSchemaIR`.
773
+ *
774
+ * Reads `contract.storage` for tables and `contract.storage.types` for type
775
+ * annotations. Storage-type annotations are written under
776
+ * `options.annotationNamespace`.
777
+ *
778
+ * Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
779
+ * structural information. When `expandNativeType` is provided, parameterized types
780
+ * are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
781
+ * resulting IR compares correctly against the "to" contract during planning.
782
+ *
783
+ * Returns an empty schema IR when `contract` is `null` (new project).
784
+ */
785
+ /**
786
+ * Converts the tables of a single namespace into a `SqlSchemaIR`, keyed by
787
+ * table name within that namespace. Unlike {@link contractToSchemaIR}, which
788
+ * flattens every namespace's tables into one bare-keyed record (and throws on a
789
+ * cross-namespace name collision), this scopes the table iteration to one
790
+ * namespace so the same table name can exist in two schemas.
791
+ *
792
+ * The full `storage` is still passed to `convertTable`, so value-set / enum /
793
+ * type resolution that legitimately spans namespaces is unaffected. Foreign
794
+ * keys are built purely from the FK descriptor (`fk.target`), so cross-namespace
795
+ * FKs survive per-namespace conversion. The `annotations` block (storage-type
796
+ * derived) is omitted here — the per-namespace tree consumer reads only the
797
+ * per-table fields.
798
+ */
799
+ function contractNamespaceToSchemaIR(storage, namespaceId, options) {
800
+ if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
801
+ const namespace = storage.namespaces[namespaceId];
802
+ if (!namespace) return new SqlSchemaIR({ tables: {} });
803
+ const storageTypes = { ...storage.types ?? {} };
804
+ const tables = {};
805
+ for (const [tableName, tableDefRaw] of Object.entries(namespace.entries.table ?? {})) {
806
+ StorageTable.assert(tableDefRaw, `namespaces.${namespaceId}.entries.table.${tableName}`);
807
+ tables[tableName] = convertTable(tableName, tableDefRaw, storageTypes, options.expandNativeType, options.renderDefault, storage);
808
+ }
809
+ return new SqlSchemaIR({ tables });
810
+ }
811
+ function contractToSchemaIR(contract, options) {
812
+ if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
813
+ if (!contract) return new SqlSchemaIR({ tables: {} });
814
+ const storage = contract.storage;
815
+ const storageTypes = { ...storage.types ?? {} };
816
+ const tables = {};
817
+ for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
818
+ StorageTable.assert(tableDefRaw, `namespaces.${ns.id}.entries.table.${tableName}`);
819
+ const tableDef = tableDefRaw;
820
+ if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
821
+ tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
822
+ }
823
+ return new SqlSchemaIR({
824
+ tables,
825
+ ...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumNamespaceSchema))
826
+ });
827
+ }
828
+ function deriveAnnotations(storage, annotationNamespace, _resolveEnumNamespaceSchema) {
829
+ const storageTypes = {};
830
+ for (const typeInstance of Object.values(storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
831
+ const envelope = { ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {} };
832
+ if (Object.keys(envelope).length === 0) return void 0;
833
+ return { [annotationNamespace]: envelope };
834
+ }
835
+ //#endregion
1451
836
  //#region src/core/migrations/control-policy.ts
1452
837
  /**
1453
838
  * The control policy that governs a single call. The `external` default is an
@@ -1478,43 +863,10 @@ function callAllowedUnderControlPolicy(policy, subject) {
1478
863
  case "observed": return false;
1479
864
  }
1480
865
  }
1481
- function defaultSubjectLabel(factoryName, subject) {
1482
- if (subject?.table) return `${factoryName}(${subject.table})`;
1483
- if (subject?.typeName) return `${factoryName}(${subject.typeName})`;
1484
- return factoryName;
1485
- }
1486
- function suppressionSummary(subjectLabel, subject, effectivePolicy) {
1487
- const namespace = subject?.namespaceId ?? "unknown";
1488
- const declared = subject?.explicitNodeControlPolicy;
1489
- if (effectivePolicy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but table declared 'managed'`;
1490
- return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${effectivePolicy}'${declared ? ` but table declared '${declared}'` : ""}`;
1491
- }
1492
- function buildSubjectSuppressionWarning(subject, effectivePolicy, factoryName, formatSubjectLabel) {
1493
- return {
1494
- kind: "controlPolicySuppressedCall",
1495
- summary: suppressionSummary(formatSubjectLabel(factoryName, subject), subject, effectivePolicy),
1496
- location: {
1497
- ...ifDefined("namespace", subject?.namespaceId),
1498
- ...ifDefined("table", subject?.table),
1499
- ...ifDefined("column", subject?.column),
1500
- ...ifDefined("type", subject?.typeName)
1501
- },
1502
- meta: {
1503
- controlPolicy: effectivePolicy,
1504
- factoryName,
1505
- ...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy)
1506
- }
1507
- };
1508
- }
1509
- function defaultModificationFactoryNameForSubject(subject) {
1510
- if (subject.table) return "alterTable";
1511
- if (subject.typeName) return "alterType";
1512
- return "alterSchema";
1513
- }
1514
866
  /**
1515
867
  * Partition the calls produced for a single set of subjects into those the
1516
868
  * effective control policy permits (`kept`) and a list of
1517
- * {@link SqlPlannerConflict} warnings describing the suppressed calls.
869
+ * {@link SuppressionRecord}s describing the suppressed calls.
1518
870
  *
1519
871
  * **Prefer {@link partitionIssuesByControlPolicy}** for the schema-issue
1520
872
  * pipeline: it filters subjects out of the planner's *input* so the planner
@@ -1526,21 +878,22 @@ function defaultModificationFactoryNameForSubject(subject) {
1526
878
  */
1527
879
  function partitionCallsByControlPolicy(options) {
1528
880
  const defaultControlPolicy = options.contract.defaultControlPolicy;
1529
- const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
1530
881
  const kept = [];
1531
- const warnings = [];
882
+ const suppressions = [];
1532
883
  for (const call of options.calls) {
1533
884
  const subject = options.resolveControlPolicySubject(call);
1534
885
  const policy = controlPolicyForCall(subject, defaultControlPolicy);
1535
886
  if (callAllowedUnderControlPolicy(policy, subject)) kept.push(call);
1536
- else {
1537
- const factoryName = options.resolveFactoryName(call);
1538
- warnings.push(buildSubjectSuppressionWarning(subject, policy, factoryName, formatSubjectLabel));
1539
- }
887
+ else suppressions.push({
888
+ subject,
889
+ policy,
890
+ factoryName: options.resolveFactoryName(call),
891
+ createsNewObject: subject?.createsNewObject ?? false
892
+ });
1540
893
  }
1541
894
  return Object.freeze({
1542
895
  kept: Object.freeze(kept),
1543
- warnings: Object.freeze(warnings)
896
+ suppressions: Object.freeze(suppressions)
1544
897
  });
1545
898
  }
1546
899
  /**
@@ -1557,23 +910,18 @@ function partitionCallsByControlPolicy(options) {
1557
910
  * cannot reason about the live shape of a subject the user marked as
1558
911
  * out-of-scope (`external`).
1559
912
  *
1560
- * `warnings` is one {@link SqlPlannerConflict} per suppressed subject (not per
1561
- * suppressed issue). `factoryName` is inferred from the subject's issue mix:
1562
- * if any of the subject's issues is whole-object creation, the warning takes
1563
- * the corresponding creation factoryName (e.g. `createTable`,
1564
- * `createEnumType`, `createSchema`); otherwise it falls back to
1565
- * `defaultModificationFactoryName(subject)` — a synthetic label that names
1566
- * the *kind* of mutation that would have run, since no concrete DDL call was
1567
- * generated.
913
+ * `suppressions` is one {@link SuppressionRecord} per suppressed subject (not
914
+ * per suppressed issue). Its `factoryName` is the creation factory name when any
915
+ * of the subject's issues is whole-object creation (e.g. `createTable`), else
916
+ * `undefined` — the family never invents a modification verb for an op that
917
+ * produced no call; the target renders the message.
1568
918
  *
1569
919
  * Unresolved-subject issues (`resolveControlPolicySubject` returns
1570
- * `undefined`) emit one warning each; they cannot be deduplicated because
1571
- * they carry no subject coordinate.
920
+ * `undefined`) emit one record each; they cannot be deduplicated because they
921
+ * carry no subject coordinate.
1572
922
  */
1573
923
  function partitionIssuesByControlPolicy(options) {
1574
924
  const defaultControlPolicy = options.contract.defaultControlPolicy;
1575
- const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
1576
- const inferModificationFactoryName = options.defaultModificationFactoryName ?? defaultModificationFactoryNameForSubject;
1577
925
  const plannable = [];
1578
926
  const suppressedSubjects = /* @__PURE__ */ new Map();
1579
927
  const unresolvedSuppressions = [];
@@ -1590,8 +938,12 @@ function partitionIssuesByControlPolicy(options) {
1590
938
  continue;
1591
939
  }
1592
940
  if (subject === void 0) {
1593
- const factoryName = creationFactoryName ?? "unknown";
1594
- unresolvedSuppressions.push(buildSubjectSuppressionWarning(void 0, policy, factoryName, formatSubjectLabel));
941
+ unresolvedSuppressions.push({
942
+ subject: void 0,
943
+ policy,
944
+ factoryName: creationFactoryName,
945
+ createsNewObject: false
946
+ });
1595
947
  continue;
1596
948
  }
1597
949
  const key = subjectKey(subject);
@@ -1604,18 +956,20 @@ function partitionIssuesByControlPolicy(options) {
1604
956
  ...ifDefined("creationFactoryName", creationFactoryName)
1605
957
  });
1606
958
  }
1607
- const warnings = [...unresolvedSuppressions];
1608
- for (const entry of suppressedSubjects.values()) {
1609
- const factoryName = entry.creationFactoryName ?? inferModificationFactoryName(entry.subject);
1610
- warnings.push(buildSubjectSuppressionWarning(entry.subject, entry.policy, factoryName, formatSubjectLabel));
1611
- }
959
+ const suppressions = [...unresolvedSuppressions];
960
+ for (const entry of suppressedSubjects.values()) suppressions.push({
961
+ subject: entry.subject,
962
+ policy: entry.policy,
963
+ factoryName: entry.creationFactoryName,
964
+ createsNewObject: entry.subject.createsNewObject
965
+ });
1612
966
  return Object.freeze({
1613
967
  plannable: Object.freeze(plannable),
1614
- warnings: Object.freeze(warnings)
968
+ suppressions: Object.freeze(suppressions)
1615
969
  });
1616
970
  }
1617
971
  function subjectKey(subject) {
1618
- return `${subject.namespaceId}\u0000${subject.table ?? ""}\u0000${subject.typeName ?? ""}`;
972
+ return `${subject.namespaceId}\u0000${subject.entityKind ?? ""}\u0000${subject.entityName ?? ""}`;
1619
973
  }
1620
974
  //#endregion
1621
975
  //#region src/core/migrations/field-event-planner.ts
@@ -1635,8 +989,8 @@ function planFieldEventOperations(options) {
1635
989
  for (const tableName of tableNames) {
1636
990
  const priorTableRaw = priorTables?.[tableName];
1637
991
  const newTableRaw = newTables?.[tableName];
1638
- const priorTable = priorTableRaw instanceof StorageTable ? priorTableRaw : void 0;
1639
- const newTable = newTableRaw instanceof StorageTable ? newTableRaw : void 0;
992
+ const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
993
+ const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
1640
994
  const fieldNames = unionSorted(priorTable ? Object.keys(priorTable.columns) : [], newTable ? Object.keys(newTable.columns) : []);
1641
995
  for (const fieldName of fieldNames) {
1642
996
  const priorField = priorTable?.columns[fieldName];
@@ -1742,6 +1096,25 @@ function unionSorted(a, b) {
1742
1096
  return [...set].sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
1743
1097
  }
1744
1098
  //#endregion
1099
+ //#region src/core/migrations/native-type-expander.ts
1100
+ /**
1101
+ * Builds the codec-hook-composed `expandNativeType` callback the contract→IR
1102
+ * derivation uses to expand parameterized native types (e.g. `character` +
1103
+ * `{ length: 36 }` → `character(36)`). Returns `undefined` when no framework
1104
+ * components are supplied, so callers can omit the option entirely.
1105
+ */
1106
+ function buildNativeTypeExpander(frameworkComponents) {
1107
+ if (!frameworkComponents) return;
1108
+ const codecHooks = extractCodecControlHooks(frameworkComponents);
1109
+ return (input) => {
1110
+ if (!input.typeParams) return input.nativeType;
1111
+ if (!input.codecId) return input.nativeType;
1112
+ const hooks = codecHooks.get(input.codecId);
1113
+ if (!hooks?.expandNativeType) return input.nativeType;
1114
+ return hooks.expandNativeType(input);
1115
+ };
1116
+ }
1117
+ //#endregion
1745
1118
  //#region src/core/migrations/plan-helpers.ts
1746
1119
  const readOnlyEmptyObject = Object.freeze({});
1747
1120
  function cloneRecord(value) {
@@ -1853,6 +1226,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
1853
1226
  //#region src/exports/control.ts
1854
1227
  var control_default = new SqlFamilyDescriptor();
1855
1228
  //#endregion
1856
- export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
1229
+ export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, buildNativeTypeExpander, contractNamespaceToSchemaIR, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
1857
1230
 
1858
1231
  //# sourceMappingURL=control.mjs.map