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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
  2. package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
  3. package/dist/contract-to-schema-ir-S-evq8E6.mjs +264 -0
  4. package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
  5. package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
  6. package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
  7. package/dist/control-adapter.d.mts +1 -1
  8. package/dist/control.d.mts +18 -3
  9. package/dist/control.d.mts.map +1 -1
  10. package/dist/control.mjs +81 -943
  11. package/dist/control.mjs.map +1 -1
  12. package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
  13. package/dist/diff.d.mts.map +1 -0
  14. package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
  15. package/dist/diff.mjs.map +1 -0
  16. package/dist/ir.d.mts +9 -6
  17. package/dist/ir.d.mts.map +1 -1
  18. package/dist/ir.mjs +1 -1
  19. package/dist/migration.d.mts +11 -2
  20. package/dist/migration.d.mts.map +1 -1
  21. package/dist/migration.mjs +7 -0
  22. package/dist/migration.mjs.map +1 -1
  23. package/dist/pack.mjs +1 -1
  24. package/dist/psl-infer.d.mts +80 -0
  25. package/dist/psl-infer.d.mts.map +1 -0
  26. package/dist/psl-infer.mjs +334 -0
  27. package/dist/psl-infer.mjs.map +1 -0
  28. package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
  29. package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
  30. package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
  31. package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
  32. package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
  33. package/dist/types-CkOIJXxU.d.mts.map +1 -0
  34. package/package.json +23 -23
  35. package/src/core/authoring-entity-types.ts +12 -37
  36. package/src/core/control-adapter.ts +10 -5
  37. package/src/core/control-instance.ts +174 -41
  38. package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
  39. package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
  40. package/src/core/ir/sql-contract-serializer-base.ts +23 -55
  41. package/src/core/ir/sql-contract-serializer.ts +5 -7
  42. package/src/core/migrations/contract-to-schema-ir.ts +49 -12
  43. package/src/core/migrations/field-event-planner.ts +2 -2
  44. package/src/core/migrations/schema-differ.ts +40 -0
  45. package/src/core/migrations/types.ts +43 -2
  46. package/src/core/psl-contract-infer/printer-config.ts +0 -6
  47. package/src/core/sql-migration.ts +12 -1
  48. package/src/exports/control.ts +1 -0
  49. package/src/exports/diff.ts +18 -0
  50. package/src/exports/psl-infer.ts +36 -0
  51. package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
  52. package/dist/schema-verify.d.mts.map +0 -1
  53. package/dist/schema-verify.mjs +0 -2
  54. package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
  55. package/dist/test-utils.d.mts +0 -2
  56. package/dist/test-utils.mjs +0 -2
  57. package/dist/types-kgstZ_Zd.d.mts.map +0 -1
  58. package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
  59. package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
  60. package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
  61. package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
  62. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
  63. package/src/exports/schema-verify.ts +0 -18
  64. package/src/exports/test-utils.ts +0 -9
  65. /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
  66. /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
package/dist/control.mjs CHANGED
@@ -1,17 +1,18 @@
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 { t as SqlContractSerializer } from "./sql-contract-serializer-CYtXg_zs.mjs";
4
3
  import { t as collectSupportedCodecTypeIds } from "./verify-C-G0obRm.mjs";
4
+ import { a as extractCodecControlHooks, i as resolveValueSetValues, n as contractToSchemaIR, r as detectDestructiveChanges, t as contractNamespaceToSchemaIR } from "./contract-to-schema-ir-S-evq8E6.mjs";
5
5
  import { n as temporalAuthoringPresets, r as timestampNowControlDescriptor } from "./timestamp-now-generator-CloimujU.mjs";
6
6
  import { sqlEmission } from "@prisma-next/sql-contract-emitter";
7
- import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions } from "@prisma-next/framework-components/control";
7
+ import { blindCast } from "@prisma-next/utils/casts";
8
+ import { effectiveControlPolicy } from "@prisma-next/contract/types";
9
+ import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions, dispositionForCategory } 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
15
  import { StorageTable } from "@prisma-next/sql-contract/types";
13
- import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma-next/framework-components/psl-ast";
14
- import { effectiveControlPolicy } from "@prisma-next/contract/types";
15
16
  import { notOk, ok } from "@prisma-next/utils/result";
16
17
  //#region src/core/operation-preview.ts
17
18
  function isDdlStatement(sqlStatement) {
@@ -46,921 +47,6 @@ function sqlOperationsToPreview(operations) {
46
47
  })) };
47
48
  }
48
49
  //#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
50
  //#region src/core/control-instance.ts
965
51
  function extractCodecTypeIdsFromContract(contract) {
966
52
  const typeIds = /* @__PURE__ */ new Set();
@@ -1123,8 +209,13 @@ function createSqlFamilyInstance(stack) {
1123
209
  let controlAdapter;
1124
210
  const getControlAdapter = () => controlAdapter ??= adapter.create(stack);
1125
211
  const targetSerializer = target.contractSerializer;
1126
- const deserializeWithTargetSerializer = (contractJson) => {
1127
- return (targetSerializer ?? new SqlContractSerializer()).deserializeContract(contractJson);
212
+ const targetInferPslContract = blindCast(target).inferPslContract;
213
+ const verifyDatabaseSchema = blindCast(target).verifyDatabaseSchema;
214
+ if (!verifyDatabaseSchema) throw new Error(`SQL target "${target.targetId}" is missing the required verifyDatabaseSchema descriptor operation`);
215
+ const deserializeWithTargetSerializer = (contractOrJson) => {
216
+ const serializer = targetSerializer ?? new SqlContractSerializer();
217
+ const json = targetSerializer !== void 0 && !isPlainRecord(contractOrJson) ? targetSerializer.serializeContract(blindCast(contractOrJson)) : contractOrJson;
218
+ return serializer.deserializeContract(json);
1128
219
  };
1129
220
  return {
1130
221
  familyId: "sql",
@@ -1227,16 +318,40 @@ function createSqlFamilyInstance(stack) {
1227
318
  },
1228
319
  verifySchema(options) {
1229
320
  const contract = deserializeWithTargetSerializer(options.contract);
1230
- const controlAdapter = getControlAdapter();
1231
- return verifySqlSchema({
321
+ const sqlResult = verifyDatabaseSchema({
1232
322
  contract,
1233
323
  schema: options.schema,
1234
324
  strict: options.strict,
1235
325
  typeMetadataRegistry,
1236
- frameworkComponents: options.frameworkComponents,
1237
- ...ifDefined("normalizeDefault", controlAdapter.normalizeDefault),
1238
- ...ifDefined("normalizeNativeType", controlAdapter.normalizeNativeType)
326
+ frameworkComponents: options.frameworkComponents
1239
327
  });
328
+ const schemaDiffIssues = filterSchemaDiffIssues(sqlResult.schema.schemaDiffIssues, contract.defaultControlPolicy);
329
+ const relationalFails = sqlResult.schema.counts.fail;
330
+ if (schemaDiffIssues.length === 0) {
331
+ if (schemaDiffIssues === sqlResult.schema.schemaDiffIssues) return sqlResult;
332
+ return {
333
+ ...sqlResult,
334
+ schema: {
335
+ ...sqlResult.schema,
336
+ schemaDiffIssues
337
+ }
338
+ };
339
+ }
340
+ const totalFails = relationalFails + schemaDiffIssues.length;
341
+ return {
342
+ ...sqlResult,
343
+ ok: false,
344
+ code: sqlResult.code ?? "PN-RUN-3010",
345
+ summary: `Database schema does not satisfy contract (${totalFails} failure${totalFails === 1 ? "" : "s"})`,
346
+ schema: {
347
+ ...sqlResult.schema,
348
+ schemaDiffIssues,
349
+ counts: {
350
+ ...sqlResult.schema.counts,
351
+ fail: totalFails
352
+ }
353
+ }
354
+ };
1240
355
  },
1241
356
  async sign(options) {
1242
357
  const { driver, contract: contractInput, contractPath, configPath } = options;
@@ -1326,7 +441,8 @@ function createSqlFamilyInstance(stack) {
1326
441
  return getControlAdapter().introspect(options.driver, options.contract);
1327
442
  },
1328
443
  inferPslContract(schemaIR) {
1329
- return sqlSchemaIrToPslAst(schemaIR);
444
+ if (!targetInferPslContract) throw new Error(`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`);
445
+ return targetInferPslContract(schemaIR);
1330
446
  },
1331
447
  lowerAst(ast, context) {
1332
448
  return getControlAdapter().lowerToExecuteRequest(ast, context);
@@ -1334,21 +450,25 @@ function createSqlFamilyInstance(stack) {
1334
450
  bootstrapControlTableQueries() {
1335
451
  return getControlAdapter().bootstrapControlTableQueries();
1336
452
  },
1337
- bootstrapSignMarkerQueries() {
1338
- return getControlAdapter().bootstrapSignMarkerQueries();
1339
- },
1340
453
  toOperationPreview(operations) {
1341
454
  return sqlOperationsToPreview(operations);
1342
455
  },
1343
456
  toSchemaView(schema) {
1344
- const tableNodes = Object.entries(schema.tables).map(([tableName, table]) => {
457
+ const root = blindCast(schema);
458
+ const namespaceEntries = root.namespaces !== void 0 ? Object.entries(root.namespaces).map(([namespaceKey, namespace]) => [namespaceKey, namespace.tables]) : [[void 0, root.tables ?? {}]];
459
+ const qualify = namespaceEntries.length > 1;
460
+ const tableNodes = namespaceEntries.flatMap(([namespaceKey, tables]) => Object.entries(tables).map(([tableName, table]) => [
461
+ qualify && namespaceKey !== void 0 ? `${namespaceKey}.${tableName}` : tableName,
462
+ tableName,
463
+ table
464
+ ])).map(([displayName, tableName, table]) => {
1345
465
  const children = [];
1346
466
  const columnNodes = [];
1347
467
  for (const [columnName, column] of Object.entries(table.columns)) {
1348
468
  const label = `${columnName}: ${column.nativeType} (${column.nullable ? "nullable" : "not nullable"})`;
1349
469
  columnNodes.push(new SchemaTreeNode({
1350
470
  kind: "field",
1351
- id: `column-${tableName}-${columnName}`,
471
+ id: `column-${displayName}-${columnName}`,
1352
472
  label,
1353
473
  meta: {
1354
474
  nativeType: column.nativeType,
@@ -1359,7 +479,7 @@ function createSqlFamilyInstance(stack) {
1359
479
  }
1360
480
  if (columnNodes.length > 0) children.push(new SchemaTreeNode({
1361
481
  kind: "collection",
1362
- id: `columns-${tableName}`,
482
+ id: `columns-${displayName}`,
1363
483
  label: "columns",
1364
484
  children: columnNodes
1365
485
  }));
@@ -1367,7 +487,7 @@ function createSqlFamilyInstance(stack) {
1367
487
  const pkColumns = table.primaryKey.columns.join(", ");
1368
488
  children.push(new SchemaTreeNode({
1369
489
  kind: "index",
1370
- id: `primary-key-${tableName}`,
490
+ id: `primary-key-${displayName}`,
1371
491
  label: `primary key: ${pkColumns}`,
1372
492
  meta: {
1373
493
  columns: table.primaryKey.columns,
@@ -1380,7 +500,7 @@ function createSqlFamilyInstance(stack) {
1380
500
  const label = `unique ${name}`;
1381
501
  children.push(new SchemaTreeNode({
1382
502
  kind: "index",
1383
- id: `unique-${tableName}-${name}`,
503
+ id: `unique-${displayName}-${name}`,
1384
504
  label,
1385
505
  meta: {
1386
506
  columns: unique.columns,
@@ -1393,7 +513,7 @@ function createSqlFamilyInstance(stack) {
1393
513
  const label = index.unique ? `unique index ${name}` : `index ${name}`;
1394
514
  children.push(new SchemaTreeNode({
1395
515
  kind: "index",
1396
- id: `index-${tableName}-${name}`,
516
+ id: `index-${displayName}-${name}`,
1397
517
  label,
1398
518
  meta: {
1399
519
  columns: index.columns,
@@ -1414,8 +534,8 @@ function createSqlFamilyInstance(stack) {
1414
534
  }));
1415
535
  return new SchemaTreeNode({
1416
536
  kind: "entity",
1417
- id: `table-${tableName}`,
1418
- label: `table ${tableName}`,
537
+ id: `table-${displayName}`,
538
+ label: `table ${displayName}`,
1419
539
  ...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
1420
540
  ...children.length > 0 ? { children } : {}
1421
541
  });
@@ -1429,6 +549,24 @@ function createSqlFamilyInstance(stack) {
1429
549
  }
1430
550
  };
1431
551
  }
552
+ /**
553
+ * Filters the structural schema-diff issues (from `diffDatabaseSchema`) through
554
+ * the contract's `defaultControlPolicy`. Issues whose outcome maps to a suppressed
555
+ * category under the effective policy are removed. This mirrors the control-policy
556
+ * filtering applied by `verifySqlSchema` for table/column-level findings.
557
+ *
558
+ * Outcome → category mapping:
559
+ * - `'extra'` → `'extraAuxiliary'` (an extra auxiliary entity, e.g. an RLS policy)
560
+ * - `'missing'` → `'declaredMissing'`
561
+ * - `'mismatch'` → `'declaredIncompatible'`
562
+ */
563
+ function filterSchemaDiffIssues(issues, defaultControlPolicy) {
564
+ if (issues.length === 0) return issues;
565
+ const policy = effectiveControlPolicy(void 0, defaultControlPolicy);
566
+ return issues.filter((issue) => {
567
+ return dispositionForCategory(policy, issue.outcome === "extra" ? "extraAuxiliary" : issue.outcome === "missing" ? "declaredMissing" : "declaredIncompatible") !== "suppress";
568
+ });
569
+ }
1432
570
  //#endregion
1433
571
  //#region src/core/control-descriptor.ts
1434
572
  var SqlFamilyDescriptor = class {
@@ -1635,8 +773,8 @@ function planFieldEventOperations(options) {
1635
773
  for (const tableName of tableNames) {
1636
774
  const priorTableRaw = priorTables?.[tableName];
1637
775
  const newTableRaw = newTables?.[tableName];
1638
- const priorTable = priorTableRaw instanceof StorageTable ? priorTableRaw : void 0;
1639
- const newTable = newTableRaw instanceof StorageTable ? newTableRaw : void 0;
776
+ const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
777
+ const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
1640
778
  const fieldNames = unionSorted(priorTable ? Object.keys(priorTable.columns) : [], newTable ? Object.keys(newTable.columns) : []);
1641
779
  for (const fieldName of fieldNames) {
1642
780
  const priorField = priorTable?.columns[fieldName];
@@ -1853,6 +991,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
1853
991
  //#region src/exports/control.ts
1854
992
  var control_default = new SqlFamilyDescriptor();
1855
993
  //#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 };
994
+ export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractNamespaceToSchemaIR, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
1857
995
 
1858
996
  //# sourceMappingURL=control.mjs.map