@prisma-next/family-sql 0.5.0-dev.4 → 0.5.0-dev.41

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 (42) hide show
  1. package/README.md +2 -2
  2. package/dist/control-adapter.d.mts +12 -1
  3. package/dist/control-adapter.d.mts.map +1 -1
  4. package/dist/control.d.mts +3 -2
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +1025 -11
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/migration.d.mts +14 -2
  9. package/dist/migration.d.mts.map +1 -1
  10. package/dist/migration.mjs +16 -1
  11. package/dist/migration.mjs.map +1 -1
  12. package/dist/schema-verify.d.mts +2 -2
  13. package/dist/{types-C6K4mxDM.d.mts → types-gLyIyd2X.d.mts} +36 -11
  14. package/dist/types-gLyIyd2X.d.mts.map +1 -0
  15. package/dist/verify-BdES8wgQ.mjs +82 -0
  16. package/dist/verify-BdES8wgQ.mjs.map +1 -0
  17. package/dist/verify-sql-schema-Ovz7RXR5.mjs.map +1 -1
  18. package/dist/{verify-sql-schema-BBhkqEDo.d.mts → verify-sql-schema-_EoNcGIq.d.mts} +2 -2
  19. package/dist/{verify-sql-schema-BBhkqEDo.d.mts.map → verify-sql-schema-_EoNcGIq.d.mts.map} +1 -1
  20. package/dist/verify.d.mts +16 -20
  21. package/dist/verify.d.mts.map +1 -1
  22. package/dist/verify.mjs +2 -2
  23. package/package.json +19 -19
  24. package/src/core/control-adapter.ts +12 -0
  25. package/src/core/control-instance.ts +43 -15
  26. package/src/core/migrations/plan-helpers.ts +1 -0
  27. package/src/core/migrations/types.ts +29 -6
  28. package/src/core/operation-preview.ts +62 -0
  29. package/src/core/psl-contract-infer/default-mapping.ts +56 -0
  30. package/src/core/psl-contract-infer/name-transforms.ts +178 -0
  31. package/src/core/psl-contract-infer/postgres-default-mapping.ts +16 -0
  32. package/src/core/psl-contract-infer/postgres-type-map.ts +165 -0
  33. package/src/core/psl-contract-infer/printer-config.ts +55 -0
  34. package/src/core/psl-contract-infer/raw-default-parser.ts +91 -0
  35. package/src/core/psl-contract-infer/relation-inference.ts +196 -0
  36. package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +832 -0
  37. package/src/core/sql-migration.ts +16 -1
  38. package/src/core/verify.ts +46 -108
  39. package/src/exports/verify.ts +1 -1
  40. package/dist/types-C6K4mxDM.d.mts.map +0 -1
  41. package/dist/verify-4GshvY4p.mjs +0 -122
  42. package/dist/verify-4GshvY4p.mjs.map +0 -1
package/dist/control.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as sqlFamilyAuthoringFieldPresets, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-BAR65pSK.mjs";
2
2
  import { c as extractCodecControlHooks, o as collectInitDependencies, s as isDatabaseDependencyProvider, t as verifySqlSchema } from "./verify-sql-schema-Ovz7RXR5.mjs";
3
- import { r as readMarker, t as collectSupportedCodecTypeIds } from "./verify-4GshvY4p.mjs";
3
+ import { t as collectSupportedCodecTypeIds } from "./verify-BdES8wgQ.mjs";
4
4
  import { sqlEmission } from "@prisma-next/sql-contract-emitter";
5
5
  import { emptyCodecLookup } from "@prisma-next/framework-components/codec";
6
6
  import { SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions } from "@prisma-next/framework-components/control";
@@ -10,6 +10,1012 @@ import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
10
10
  import { ifDefined } from "@prisma-next/utils/defined";
11
11
  import { notOk, ok } from "@prisma-next/utils/result";
12
12
 
13
+ //#region src/core/operation-preview.ts
14
+ function isDdlStatement(sqlStatement) {
15
+ const trimmed = sqlStatement.trim().toLowerCase();
16
+ return trimmed.startsWith("create ") || trimmed.startsWith("alter ") || trimmed.startsWith("drop ");
17
+ }
18
+ function hasExecuteSteps(operation) {
19
+ const candidate = operation;
20
+ if (!("execute" in candidate) || !Array.isArray(candidate["execute"])) return false;
21
+ return candidate["execute"].every((step) => typeof step === "object" && step !== null && "sql" in step);
22
+ }
23
+ /**
24
+ * Extracts a best-effort SQL DDL preview for CLI plan output.
25
+ * Presentation-only: never used to decide migration correctness.
26
+ */
27
+ function extractSqlDdl(operations) {
28
+ const statements = [];
29
+ for (const operation of operations) {
30
+ if (!hasExecuteSteps(operation)) continue;
31
+ for (const step of operation.execute) if (typeof step.sql === "string" && isDdlStatement(step.sql)) statements.push(step.sql.trim());
32
+ }
33
+ return statements;
34
+ }
35
+ /**
36
+ * Wraps `extractSqlDdl` into the family-agnostic `OperationPreview` shape.
37
+ * Each statement carries `language: 'sql'`.
38
+ */
39
+ function sqlOperationsToPreview(operations) {
40
+ return { statements: extractSqlDdl(operations).map((text) => ({
41
+ text,
42
+ language: "sql"
43
+ })) };
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/core/psl-contract-infer/default-mapping.ts
48
+ const DEFAULT_FUNCTION_ATTRIBUTES = {
49
+ "autoincrement()": "@default(autoincrement())",
50
+ "now()": "@default(now())"
51
+ };
52
+ function mapDefault(columnDefault, options) {
53
+ switch (columnDefault.kind) {
54
+ case "literal": return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };
55
+ case "function": {
56
+ const attribute = options?.functionAttributes?.[columnDefault.expression] ?? DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ?? options?.fallbackFunctionAttribute?.(columnDefault.expression);
57
+ return attribute ? { attribute } : { comment: `// Raw default: ${columnDefault.expression.replace(/[\r\n]+/g, " ")}` };
58
+ }
59
+ }
60
+ }
61
+ function formatLiteralValue(value) {
62
+ if (value === null) return "null";
63
+ switch (typeof value) {
64
+ case "boolean":
65
+ case "number": return String(value);
66
+ case "string": return quoteString(value);
67
+ default: return quoteString(JSON.stringify(value));
68
+ }
69
+ }
70
+ function quoteString(str) {
71
+ return `"${escapeString(str)}"`;
72
+ }
73
+ function escapeString(str) {
74
+ return JSON.stringify(str).slice(1, -1);
75
+ }
76
+
77
+ //#endregion
78
+ //#region src/core/psl-contract-infer/name-transforms.ts
79
+ const PSL_RESERVED_WORDS = new Set([
80
+ "model",
81
+ "enum",
82
+ "types",
83
+ "type",
84
+ "generator",
85
+ "datasource"
86
+ ]);
87
+ const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
88
+ function hasSeparators(input) {
89
+ return /[^A-Za-z0-9]/.test(input);
90
+ }
91
+ function extractIdentifierParts(input) {
92
+ return input.match(IDENTIFIER_PART_PATTERN) ?? [];
93
+ }
94
+ function createSyntheticIdentifier(input) {
95
+ let hash = 2166136261;
96
+ for (const char of input) {
97
+ hash ^= char.codePointAt(0) ?? 0;
98
+ hash = Math.imul(hash, 16777619);
99
+ }
100
+ return `x${(hash >>> 0).toString(16)}`;
101
+ }
102
+ function sanitizeIdentifierCharacters(input) {
103
+ const sanitized = input.replace(/[^\w]/g, "");
104
+ return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);
105
+ }
106
+ function capitalize(word) {
107
+ return word.charAt(0).toUpperCase() + word.slice(1);
108
+ }
109
+ function snakeToPascalCase(input) {
110
+ const parts = extractIdentifierParts(input);
111
+ if (parts.length === 0) return capitalize(sanitizeIdentifierCharacters(input));
112
+ return parts.map(capitalize).join("");
113
+ }
114
+ function snakeToCamelCase(input) {
115
+ const parts = extractIdentifierParts(input);
116
+ if (parts.length === 0) return sanitizeIdentifierCharacters(input);
117
+ const [firstPart = input, ...rest] = parts;
118
+ return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join("");
119
+ }
120
+ function needsEscaping(name) {
121
+ return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\d/.test(name);
122
+ }
123
+ function escapeName(name) {
124
+ return `_${name}`;
125
+ }
126
+ function escapeIfNeeded(name) {
127
+ return needsEscaping(name) ? escapeName(name) : name;
128
+ }
129
+ function toModelName(tableName) {
130
+ let name;
131
+ if (hasSeparators(tableName)) name = snakeToPascalCase(tableName);
132
+ else name = tableName.charAt(0).toUpperCase() + tableName.slice(1);
133
+ if (needsEscaping(name)) return {
134
+ name: escapeName(name),
135
+ map: tableName
136
+ };
137
+ if (name !== tableName) return {
138
+ name,
139
+ map: tableName
140
+ };
141
+ return { name };
142
+ }
143
+ function toFieldName(columnName) {
144
+ let name;
145
+ if (hasSeparators(columnName)) name = snakeToCamelCase(columnName);
146
+ else name = columnName.charAt(0).toLowerCase() + columnName.slice(1);
147
+ if (needsEscaping(name)) return {
148
+ name: escapeName(name),
149
+ map: columnName
150
+ };
151
+ if (name !== columnName) return {
152
+ name,
153
+ map: columnName
154
+ };
155
+ return { name };
156
+ }
157
+ function toEnumName(pgTypeName) {
158
+ let name;
159
+ if (hasSeparators(pgTypeName)) name = snakeToPascalCase(pgTypeName);
160
+ else name = pgTypeName.charAt(0).toUpperCase() + pgTypeName.slice(1);
161
+ if (needsEscaping(name)) return {
162
+ name: escapeName(name),
163
+ map: pgTypeName
164
+ };
165
+ if (name !== pgTypeName) return {
166
+ name,
167
+ map: pgTypeName
168
+ };
169
+ return { name };
170
+ }
171
+ function pluralize(word) {
172
+ if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) return `${word}es`;
173
+ if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
174
+ return `${word}s`;
175
+ }
176
+ function deriveRelationFieldName(fkColumns, referencedTableName) {
177
+ if (fkColumns.length === 1) {
178
+ const [col = referencedTableName] = fkColumns;
179
+ const stripped = col.replace(/_id$/i, "").replace(/Id$/, "");
180
+ if (stripped.length > 0 && stripped !== col) return escapeIfNeeded(snakeToCamelCase(stripped));
181
+ return escapeIfNeeded(snakeToCamelCase(referencedTableName));
182
+ }
183
+ return escapeIfNeeded(snakeToCamelCase(referencedTableName));
184
+ }
185
+ function deriveBackRelationFieldName(childModelName, isOneToOne) {
186
+ const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);
187
+ return isOneToOne ? base : pluralize(base);
188
+ }
189
+ function toNamedTypeName(columnName) {
190
+ let name;
191
+ if (hasSeparators(columnName)) name = snakeToPascalCase(columnName);
192
+ else name = columnName.charAt(0).toUpperCase() + columnName.slice(1);
193
+ return escapeIfNeeded(name);
194
+ }
195
+
196
+ //#endregion
197
+ //#region src/core/psl-contract-infer/postgres-default-mapping.ts
198
+ const POSTGRES_FUNCTION_ATTRIBUTES = { "gen_random_uuid()": "@default(dbgenerated(\"gen_random_uuid()\"))" };
199
+ function formatDbGeneratedAttribute(expression) {
200
+ return `@default(dbgenerated(${JSON.stringify(expression)}))`;
201
+ }
202
+ function createPostgresDefaultMapping() {
203
+ return {
204
+ functionAttributes: POSTGRES_FUNCTION_ATTRIBUTES,
205
+ fallbackFunctionAttribute: formatDbGeneratedAttribute
206
+ };
207
+ }
208
+
209
+ //#endregion
210
+ //#region src/core/psl-contract-infer/postgres-type-map.ts
211
+ const POSTGRES_TO_PSL = {
212
+ text: "String",
213
+ bool: "Boolean",
214
+ boolean: "Boolean",
215
+ int4: "Int",
216
+ integer: "Int",
217
+ int8: "BigInt",
218
+ bigint: "BigInt",
219
+ float8: "Float",
220
+ "double precision": "Float",
221
+ numeric: "Decimal",
222
+ decimal: "Decimal",
223
+ timestamptz: "DateTime",
224
+ "timestamp with time zone": "DateTime",
225
+ jsonb: "Json",
226
+ bytea: "Bytes"
227
+ };
228
+ const PRESERVED_NATIVE_TYPES = {
229
+ "character varying": {
230
+ pslType: "String",
231
+ attributeName: "db.VarChar"
232
+ },
233
+ character: {
234
+ pslType: "String",
235
+ attributeName: "db.Char"
236
+ },
237
+ char: {
238
+ pslType: "String",
239
+ attributeName: "db.Char"
240
+ },
241
+ varchar: {
242
+ pslType: "String",
243
+ attributeName: "db.VarChar"
244
+ },
245
+ uuid: {
246
+ pslType: "String",
247
+ attributeName: "db.Uuid"
248
+ },
249
+ int2: {
250
+ pslType: "Int",
251
+ attributeName: "db.SmallInt"
252
+ },
253
+ smallint: {
254
+ pslType: "Int",
255
+ attributeName: "db.SmallInt"
256
+ },
257
+ float4: {
258
+ pslType: "Float",
259
+ attributeName: "db.Real"
260
+ },
261
+ real: {
262
+ pslType: "Float",
263
+ attributeName: "db.Real"
264
+ },
265
+ timestamp: {
266
+ pslType: "DateTime",
267
+ attributeName: "db.Timestamp"
268
+ },
269
+ "timestamp without time zone": {
270
+ pslType: "DateTime",
271
+ attributeName: "db.Timestamp"
272
+ },
273
+ date: {
274
+ pslType: "DateTime",
275
+ attributeName: "db.Date"
276
+ },
277
+ time: {
278
+ pslType: "DateTime",
279
+ attributeName: "db.Time"
280
+ },
281
+ "time without time zone": {
282
+ pslType: "DateTime",
283
+ attributeName: "db.Time"
284
+ },
285
+ timetz: {
286
+ pslType: "DateTime",
287
+ attributeName: "db.Timetz"
288
+ },
289
+ "time with time zone": {
290
+ pslType: "DateTime",
291
+ attributeName: "db.Timetz"
292
+ },
293
+ json: {
294
+ pslType: "Json",
295
+ attributeName: "db.Json"
296
+ }
297
+ };
298
+ const PARAMETERIZED_NATIVE_TYPES = {
299
+ "character varying": {
300
+ pslType: "String",
301
+ attributeName: "db.VarChar"
302
+ },
303
+ character: {
304
+ pslType: "String",
305
+ attributeName: "db.Char"
306
+ },
307
+ char: {
308
+ pslType: "String",
309
+ attributeName: "db.Char"
310
+ },
311
+ varchar: {
312
+ pslType: "String",
313
+ attributeName: "db.VarChar"
314
+ },
315
+ numeric: {
316
+ pslType: "Decimal",
317
+ attributeName: "db.Numeric"
318
+ },
319
+ timestamp: {
320
+ pslType: "DateTime",
321
+ attributeName: "db.Timestamp"
322
+ },
323
+ timestamptz: {
324
+ pslType: "DateTime",
325
+ attributeName: "db.Timestamptz"
326
+ },
327
+ time: {
328
+ pslType: "DateTime",
329
+ attributeName: "db.Time"
330
+ },
331
+ timetz: {
332
+ pslType: "DateTime",
333
+ attributeName: "db.Timetz"
334
+ }
335
+ };
336
+ const PARAMETERIZED_TYPE_PATTERN = /^(.+?)\((.+)\)$/;
337
+ const ENUM_CODEC_ID = "pg/enum@1";
338
+ function getOwnMappingValue(map, key) {
339
+ return Object.hasOwn(map, key) ? map[key] : void 0;
340
+ }
341
+ function getOwnRecordValue(map, key) {
342
+ return Object.hasOwn(map, key) ? map[key] : void 0;
343
+ }
344
+ function createNativeTypeAttribute(name, args) {
345
+ return args && args.length > 0 ? {
346
+ name,
347
+ args
348
+ } : { name };
349
+ }
350
+ function splitTypeParameterList(params) {
351
+ return params.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
352
+ }
353
+ function createPostgresTypeMap(enumTypeNames) {
354
+ return { resolve(nativeType) {
355
+ if (enumTypeNames?.has(nativeType)) return {
356
+ pslType: nativeType,
357
+ nativeType
358
+ };
359
+ const paramMatch = nativeType.match(PARAMETERIZED_TYPE_PATTERN);
360
+ if (paramMatch) {
361
+ const [, baseType = nativeType, params = ""] = paramMatch;
362
+ const template = getOwnRecordValue(PARAMETERIZED_NATIVE_TYPES, baseType);
363
+ if (template) return {
364
+ pslType: template.pslType,
365
+ nativeType,
366
+ typeParams: {
367
+ baseType,
368
+ params
369
+ },
370
+ nativeTypeAttribute: createNativeTypeAttribute(template.attributeName, splitTypeParameterList(params))
371
+ };
372
+ }
373
+ const preservedType = getOwnRecordValue(PRESERVED_NATIVE_TYPES, nativeType);
374
+ if (preservedType) return {
375
+ pslType: preservedType.pslType,
376
+ nativeType,
377
+ nativeTypeAttribute: createNativeTypeAttribute(preservedType.attributeName)
378
+ };
379
+ const pslType = getOwnMappingValue(POSTGRES_TO_PSL, nativeType);
380
+ if (pslType) return {
381
+ pslType,
382
+ nativeType
383
+ };
384
+ return {
385
+ unsupported: true,
386
+ nativeType
387
+ };
388
+ } };
389
+ }
390
+ function extractEnumInfo(annotations) {
391
+ const storageTypes = (annotations?.["pg"])?.["storageTypes"];
392
+ const typeNames = /* @__PURE__ */ new Set();
393
+ const definitions = /* @__PURE__ */ new Map();
394
+ if (storageTypes) {
395
+ for (const [key, typeInstance] of Object.entries(storageTypes)) if (typeInstance.codecId === ENUM_CODEC_ID) {
396
+ typeNames.add(key);
397
+ const values = typeInstance.typeParams?.["values"];
398
+ if (Array.isArray(values)) definitions.set(key, values);
399
+ }
400
+ }
401
+ return {
402
+ typeNames,
403
+ definitions
404
+ };
405
+ }
406
+
407
+ //#endregion
408
+ //#region src/core/psl-contract-infer/raw-default-parser.ts
409
+ const NEXTVAL_PATTERN = /^nextval\s*\(/i;
410
+ const NOW_FUNCTION_PATTERN = /^(now\s*\(\s*\)|CURRENT_TIMESTAMP)$/i;
411
+ const CLOCK_TIMESTAMP_PATTERN = /^clock_timestamp\s*\(\s*\)$/i;
412
+ const TIMESTAMP_CAST_SUFFIX = /::timestamp(?:tz|\s+(?:with|without)\s+time\s+zone)?$/i;
413
+ const TEXT_CAST_SUFFIX = /::text$/i;
414
+ const NOW_LITERAL_PATTERN = /^'now'$/i;
415
+ const UUID_PATTERN = /^gen_random_uuid\s*\(\s*\)$/i;
416
+ const UUID_OSSP_PATTERN = /^uuid_generate_v4\s*\(\s*\)$/i;
417
+ const NULL_PATTERN = /^NULL(?:::.+)?$/i;
418
+ const TRUE_PATTERN = /^true$/i;
419
+ const FALSE_PATTERN = /^false$/i;
420
+ const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
421
+ const JSON_CAST_SUFFIX = /::jsonb?$/i;
422
+ const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
423
+ function canonicalizeTimestampDefault(expr) {
424
+ if (NOW_FUNCTION_PATTERN.test(expr)) return "now()";
425
+ if (CLOCK_TIMESTAMP_PATTERN.test(expr)) return "clock_timestamp()";
426
+ if (!TIMESTAMP_CAST_SUFFIX.test(expr)) return void 0;
427
+ let inner = expr.replace(TIMESTAMP_CAST_SUFFIX, "").trim();
428
+ if (inner.startsWith("(") && inner.endsWith(")")) inner = inner.slice(1, -1).trim();
429
+ if (NOW_FUNCTION_PATTERN.test(inner)) return "now()";
430
+ if (CLOCK_TIMESTAMP_PATTERN.test(inner)) return "clock_timestamp()";
431
+ inner = inner.replace(TEXT_CAST_SUFFIX, "").trim();
432
+ if (NOW_LITERAL_PATTERN.test(inner)) return "now()";
433
+ }
434
+ function parseRawDefault(rawDefault, nativeType) {
435
+ const trimmed = rawDefault.trim();
436
+ const normalizedType = nativeType?.toLowerCase();
437
+ if (NEXTVAL_PATTERN.test(trimmed)) return {
438
+ kind: "function",
439
+ expression: "autoincrement()"
440
+ };
441
+ const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);
442
+ if (canonicalTimestamp) return {
443
+ kind: "function",
444
+ expression: canonicalTimestamp
445
+ };
446
+ if (UUID_PATTERN.test(trimmed) || UUID_OSSP_PATTERN.test(trimmed)) return {
447
+ kind: "function",
448
+ expression: "gen_random_uuid()"
449
+ };
450
+ if (NULL_PATTERN.test(trimmed)) return {
451
+ kind: "literal",
452
+ value: null
453
+ };
454
+ if (TRUE_PATTERN.test(trimmed)) return {
455
+ kind: "literal",
456
+ value: true
457
+ };
458
+ if (FALSE_PATTERN.test(trimmed)) return {
459
+ kind: "literal",
460
+ value: false
461
+ };
462
+ if (NUMERIC_PATTERN.test(trimmed)) return {
463
+ kind: "literal",
464
+ value: Number(trimmed)
465
+ };
466
+ const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);
467
+ if (stringMatch?.[1] !== void 0) {
468
+ const unescaped = stringMatch[1].replace(/''/g, "'");
469
+ if (normalizedType === "json" || normalizedType === "jsonb") {
470
+ if (JSON_CAST_SUFFIX.test(trimmed)) return {
471
+ kind: "function",
472
+ expression: trimmed
473
+ };
474
+ try {
475
+ return {
476
+ kind: "literal",
477
+ value: JSON.parse(unescaped)
478
+ };
479
+ } catch {}
480
+ }
481
+ return {
482
+ kind: "literal",
483
+ value: unescaped
484
+ };
485
+ }
486
+ return {
487
+ kind: "function",
488
+ expression: trimmed
489
+ };
490
+ }
491
+
492
+ //#endregion
493
+ //#region src/core/psl-contract-infer/relation-inference.ts
494
+ const DEFAULT_ON_DELETE = "noAction";
495
+ const DEFAULT_ON_UPDATE = "noAction";
496
+ const REFERENTIAL_ACTION_PSL = {
497
+ noAction: "NoAction",
498
+ restrict: "Restrict",
499
+ cascade: "Cascade",
500
+ setNull: "SetNull",
501
+ setDefault: "SetDefault"
502
+ };
503
+ function inferRelations(tables, modelNameMap) {
504
+ const relationsByTable = /* @__PURE__ */ new Map();
505
+ const fkCountByPair = /* @__PURE__ */ new Map();
506
+ for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
507
+ const pairKey = `${table.name}→${fk.referencedTable}`;
508
+ fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);
509
+ }
510
+ const usedFieldNames = /* @__PURE__ */ new Map();
511
+ for (const table of Object.values(tables)) {
512
+ const names = /* @__PURE__ */ new Set();
513
+ for (const col of Object.values(table.columns)) names.add(col.name);
514
+ usedFieldNames.set(table.name, names);
515
+ }
516
+ for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
517
+ const childTableName = table.name;
518
+ const parentTableName = fk.referencedTable;
519
+ const childUsed = usedFieldNames.get(childTableName);
520
+ const childModelName = modelNameMap.get(childTableName) ?? childTableName;
521
+ const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;
522
+ const pairKey = `${childTableName}→${parentTableName}`;
523
+ const isSelfRelation = childTableName === parentTableName;
524
+ const needsRelationName = fkCountByPair.get(pairKey) > 1 || isSelfRelation;
525
+ const isOneToOne = detectOneToOne(fk, table);
526
+ const childRelFieldName = resolveUniqueFieldName(deriveRelationFieldName(fk.columns, parentTableName), childUsed, parentModelName);
527
+ const relationName = needsRelationName ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation) : void 0;
528
+ addRelationField(relationsByTable, childTableName, buildChildRelationField(childRelFieldName, parentModelName, fk, fk.columns.some((columnName) => table.columns[columnName]?.nullable ?? false), relationName));
529
+ childUsed.add(childRelFieldName);
530
+ const parentUsed = usedFieldNames.get(parentTableName) ?? /* @__PURE__ */ new Set();
531
+ usedFieldNames.set(parentTableName, parentUsed);
532
+ const backRelFieldName = resolveUniqueFieldName(deriveBackRelationFieldName(childModelName, isOneToOne), parentUsed, childModelName);
533
+ addRelationField(relationsByTable, parentTableName, {
534
+ fieldName: backRelFieldName,
535
+ typeName: childModelName,
536
+ optional: isOneToOne,
537
+ list: !isOneToOne,
538
+ relationName
539
+ });
540
+ parentUsed.add(backRelFieldName);
541
+ }
542
+ return { relationsByTable };
543
+ }
544
+ function detectOneToOne(fk, table) {
545
+ const fkCols = [...fk.columns].sort();
546
+ if (table.primaryKey) {
547
+ const pkCols = [...table.primaryKey.columns].sort();
548
+ if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) return true;
549
+ }
550
+ for (const unique of table.uniques) {
551
+ const uniqueCols = [...unique.columns].sort();
552
+ if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) return true;
553
+ }
554
+ return false;
555
+ }
556
+ function deriveRelationName(fk, childRelationFieldName, parentModelName, isSelfRelation) {
557
+ if (fk.name) return fk.name;
558
+ if (isSelfRelation) return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;
559
+ return fk.columns.join("_");
560
+ }
561
+ function buildChildRelationField(fieldName, parentModelName, fk, optional, relationName) {
562
+ const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : void 0;
563
+ const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : void 0;
564
+ return {
565
+ fieldName,
566
+ typeName: parentModelName,
567
+ referencedTableName: fk.referencedTable,
568
+ optional,
569
+ list: false,
570
+ relationName,
571
+ fkName: fk.name,
572
+ fields: fk.columns,
573
+ references: fk.referencedColumns,
574
+ onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : void 0,
575
+ onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : void 0
576
+ };
577
+ }
578
+ function resolveUniqueFieldName(desired, usedNames, fallbackSuffix) {
579
+ if (!usedNames.has(desired)) return desired;
580
+ const withSuffix = `${desired}${fallbackSuffix}`;
581
+ if (!usedNames.has(withSuffix)) return withSuffix;
582
+ let counter = 2;
583
+ while (usedNames.has(`${desired}${counter}`)) counter++;
584
+ return `${desired}${counter}`;
585
+ }
586
+ function addRelationField(map, tableName, field) {
587
+ const existing = map.get(tableName);
588
+ if (existing) existing.push(field);
589
+ else map.set(tableName, [field]);
590
+ }
591
+
592
+ //#endregion
593
+ //#region src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
594
+ const SYNTHETIC_SPAN = {
595
+ start: {
596
+ offset: 0,
597
+ line: 1,
598
+ column: 1
599
+ },
600
+ end: {
601
+ offset: 0,
602
+ line: 1,
603
+ column: 1
604
+ }
605
+ };
606
+ const PSL_SCALAR_TYPE_NAMES = new Set([
607
+ "String",
608
+ "Boolean",
609
+ "Int",
610
+ "BigInt",
611
+ "Float",
612
+ "Decimal",
613
+ "DateTime",
614
+ "Json",
615
+ "Bytes"
616
+ ]);
617
+ /**
618
+ * Converts a SQL schema IR into a PSL AST suitable for `printPsl`.
619
+ *
620
+ * This function owns all SQL-specific concerns: native type mapping (Postgres),
621
+ * relation inference from foreign keys, enum extraction, and raw default parsing.
622
+ * The output is a fully-formed `PslDocumentAst` with synthetic spans.
623
+ */
624
+ function sqlSchemaIrToPslAst(schemaIR) {
625
+ const enumInfo = extractEnumInfo(schemaIR.annotations);
626
+ return buildPslDocumentAst(schemaIR, {
627
+ typeMap: createPostgresTypeMap(enumInfo.typeNames),
628
+ defaultMapping: createPostgresDefaultMapping(),
629
+ enumInfo,
630
+ parseRawDefault
631
+ });
632
+ }
633
+ function buildPslDocumentAst(schemaIR, options) {
634
+ const { typeMap, defaultMapping, enumInfo, parseRawDefault: rawDefaultParser } = options;
635
+ const { typeNames: enumTypeNames, definitions: enumDefinitions } = enumInfo ?? {
636
+ typeNames: /* @__PURE__ */ new Set(),
637
+ definitions: /* @__PURE__ */ new Map()
638
+ };
639
+ const modelNames = buildTopLevelNameMap(Object.keys(schemaIR.tables), toModelName, "model", "table");
640
+ const enumNames = buildTopLevelNameMap(enumTypeNames, toEnumName, "enum", "enum type");
641
+ assertNoCrossKindNameCollisions(modelNames, enumNames);
642
+ const modelNameMap = new Map([...modelNames].map(([tableName, result]) => [tableName, result.name]));
643
+ const enumNameMap = new Map([...enumNames].map(([pgTypeName, result]) => [pgTypeName, result.name]));
644
+ const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames, enumNames);
645
+ const fieldNamesByTable = buildFieldNamesByTable(schemaIR.tables);
646
+ const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);
647
+ const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap, enumNameMap, reservedNamedTypeNames);
648
+ const models = [];
649
+ for (const table of Object.values(schemaIR.tables)) models.push(buildModel(table, typeMap, enumNameMap, fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationsByTable.get(table.name) ?? []));
650
+ const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);
651
+ const enums = [];
652
+ for (const [pgTypeName, values] of enumDefinitions) {
653
+ const enumName = enumNames.get(pgTypeName);
654
+ enums.push(buildEnum(enumName, values));
655
+ }
656
+ enums.sort((a, b) => a.name.localeCompare(b.name));
657
+ const namedTypeEntries = [...namedTypes.entriesByKey.values()].sort((a, b) => a.name.localeCompare(b.name));
658
+ const types = namedTypeEntries.length > 0 ? {
659
+ kind: "types",
660
+ declarations: namedTypeEntries.map(buildNamedTypeDeclaration),
661
+ span: SYNTHETIC_SPAN
662
+ } : void 0;
663
+ return {
664
+ kind: "document",
665
+ sourceId: "<sql-schema-ir>",
666
+ models: sortedModels,
667
+ enums,
668
+ compositeTypes: [],
669
+ ...types ? { types } : {},
670
+ span: SYNTHETIC_SPAN
671
+ };
672
+ }
673
+ function buildModel(table, typeMap, enumNameMap, fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationFields) {
674
+ const { name: modelName, map: mapName } = toModelName(table.name);
675
+ const fieldNameMap = fieldNamesByTable.get(table.name);
676
+ const pkColumns = new Set(table.primaryKey?.columns ?? []);
677
+ const isSinglePk = pkColumns.size === 1;
678
+ const singlePkConstraintName = isSinglePk ? table.primaryKey?.name : void 0;
679
+ const uniqueColumns = /* @__PURE__ */ new Map();
680
+ for (const unique of table.uniques) if (unique.columns.length === 1) {
681
+ const [columnName = ""] = unique.columns;
682
+ const existingConstraintName = uniqueColumns.get(columnName);
683
+ if (!uniqueColumns.has(columnName) || existingConstraintName === void 0 && unique.name) uniqueColumns.set(columnName, unique.name);
684
+ }
685
+ const fields = [];
686
+ for (const column of Object.values(table.columns)) fields.push(buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns));
687
+ const usedFieldNames = new Set(fields.map((field) => field.name));
688
+ for (const rel of relationFields) fields.push(buildRelationField(rel, table.name, fieldNamesByTable, usedFieldNames));
689
+ const modelAttributes = [];
690
+ if (table.primaryKey && table.primaryKey.columns.length > 1) {
691
+ const pkFieldNames = table.primaryKey.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
692
+ modelAttributes.push(buildModelConstraintAttribute("id", pkFieldNames, table.primaryKey.name));
693
+ }
694
+ for (const unique of table.uniques) if (unique.columns.length > 1) {
695
+ const uniqueFieldNames = unique.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
696
+ modelAttributes.push(buildModelConstraintAttribute("unique", uniqueFieldNames, unique.name));
697
+ }
698
+ for (const index of table.indexes) if (!index.unique) {
699
+ const indexFieldNames = index.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
700
+ modelAttributes.push(buildModelConstraintAttribute("index", indexFieldNames, index.name));
701
+ }
702
+ if (mapName) modelAttributes.push(buildMapAttribute("model", mapName));
703
+ const comment = table.primaryKey ? void 0 : "// WARNING: This table has no primary key in the database";
704
+ return {
705
+ kind: "model",
706
+ name: modelName,
707
+ fields,
708
+ attributes: modelAttributes,
709
+ span: SYNTHETIC_SPAN,
710
+ ...comment !== void 0 ? { comment } : {}
711
+ };
712
+ }
713
+ function buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns) {
714
+ const resolvedField = fieldNameMap?.get(column.name);
715
+ const fieldName = resolvedField?.fieldName ?? toFieldName(column.name).name;
716
+ const fieldMap = resolvedField?.fieldMap;
717
+ const resolution = typeMap.resolve(column.nativeType, table.annotations);
718
+ if ("unsupported" in resolution) {
719
+ const attrs = [];
720
+ if (fieldMap !== void 0) attrs.push(buildMapAttribute("field", fieldMap));
721
+ return {
722
+ kind: "field",
723
+ name: fieldName,
724
+ typeName: `Unsupported("${escapePslString(resolution.nativeType)}")`,
725
+ optional: column.nullable,
726
+ list: false,
727
+ attributes: attrs,
728
+ span: SYNTHETIC_SPAN
729
+ };
730
+ }
731
+ let typeName = resolution.pslType;
732
+ const enumPslName = enumNameMap.get(column.nativeType);
733
+ if (enumPslName) typeName = enumPslName;
734
+ if (resolution.nativeTypeAttribute && !enumPslName) typeName = resolveNamedTypeName(namedTypes, resolution);
735
+ const attributes = [];
736
+ const isId = isSinglePk && pkColumns.has(column.name);
737
+ if (isId) attributes.push(buildSimpleConstraintFieldAttribute("id", singlePkConstraintName));
738
+ if (column.default !== void 0) {
739
+ const parsed = parseColumnDefault(column.default, column.nativeType, rawDefaultParser);
740
+ if (parsed) {
741
+ const result = mapDefault(parsed, defaultMapping);
742
+ if ("attribute" in result) attributes.push(parseDefaultAttributeString(result.attribute));
743
+ }
744
+ }
745
+ if (uniqueColumns.has(column.name) && !isId) {
746
+ const uniqueConstraintName = uniqueColumns.get(column.name);
747
+ attributes.push(buildSimpleConstraintFieldAttribute("unique", uniqueConstraintName));
748
+ }
749
+ if (fieldMap !== void 0) attributes.push(buildMapAttribute("field", fieldMap));
750
+ return {
751
+ kind: "field",
752
+ name: fieldName,
753
+ typeName,
754
+ optional: column.nullable,
755
+ list: false,
756
+ attributes,
757
+ span: SYNTHETIC_SPAN
758
+ };
759
+ }
760
+ function buildRelationField(rel, hostTableName, fieldNamesByTable, usedFieldNames) {
761
+ const fieldName = createUniqueFieldName(rel.fieldName, usedFieldNames);
762
+ usedFieldNames.add(fieldName);
763
+ const args = [];
764
+ if (rel.fields && rel.references) {
765
+ if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
766
+ args.push(namedArg("fields", `[${rel.fields.map((columnName) => resolveColumnFieldName(fieldNamesByTable, hostTableName, columnName)).join(", ")}]`));
767
+ args.push(namedArg("references", `[${rel.references.map((columnName) => resolveColumnFieldName(fieldNamesByTable, rel.referencedTableName ?? "", columnName)).join(", ")}]`));
768
+ if (rel.onDelete) args.push(namedArg("onDelete", rel.onDelete));
769
+ if (rel.onUpdate) args.push(namedArg("onUpdate", rel.onUpdate));
770
+ if (rel.fkName) args.push(namedArg("map", `"${escapePslString(rel.fkName)}"`));
771
+ } else if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
772
+ const attrs = args.length > 0 ? [buildAttribute("field", "relation", args)] : [];
773
+ return {
774
+ kind: "field",
775
+ name: fieldName,
776
+ typeName: rel.typeName,
777
+ optional: rel.optional,
778
+ list: rel.list,
779
+ attributes: attrs,
780
+ span: SYNTHETIC_SPAN
781
+ };
782
+ }
783
+ function buildModelConstraintAttribute(name, fields, constraintName) {
784
+ const args = [positionalArg(`[${fields.join(", ")}]`)];
785
+ if (constraintName !== void 0) args.push(namedArg("map", `"${escapePslString(constraintName)}"`));
786
+ return buildAttribute("model", name, args);
787
+ }
788
+ function buildSimpleConstraintFieldAttribute(name, constraintName) {
789
+ if (constraintName === void 0) return buildAttribute("field", name, []);
790
+ return buildAttribute("field", name, [namedArg("map", `"${escapePslString(constraintName)}"`)]);
791
+ }
792
+ function parseDefaultAttributeString(attributeText) {
793
+ return buildAttribute("field", "default", [positionalArg(attributeText.replace(/^@default\(/, "").replace(/\)$/, ""))]);
794
+ }
795
+ function buildMapAttribute(target, mapName) {
796
+ return buildAttribute(target, "map", [positionalArg(`"${escapePslString(mapName)}"`)]);
797
+ }
798
+ function buildAttribute(target, name, args) {
799
+ return {
800
+ kind: "attribute",
801
+ target,
802
+ name,
803
+ args,
804
+ span: SYNTHETIC_SPAN
805
+ };
806
+ }
807
+ function positionalArg(value) {
808
+ return {
809
+ kind: "positional",
810
+ value,
811
+ span: SYNTHETIC_SPAN
812
+ };
813
+ }
814
+ function namedArg(name, value) {
815
+ return {
816
+ kind: "named",
817
+ name,
818
+ value,
819
+ span: SYNTHETIC_SPAN
820
+ };
821
+ }
822
+ function buildEnum(name, values) {
823
+ const attrs = [];
824
+ if (name.map) attrs.push(buildMapAttribute("enum", name.map));
825
+ return {
826
+ kind: "enum",
827
+ name: name.name,
828
+ values: values.map((value) => ({
829
+ kind: "enumValue",
830
+ name: value,
831
+ span: SYNTHETIC_SPAN
832
+ })),
833
+ attributes: attrs,
834
+ span: SYNTHETIC_SPAN
835
+ };
836
+ }
837
+ function buildNamedTypeDeclaration(entry) {
838
+ const attribute = buildAttribute("namedType", entry.nativeTypeAttribute.name, (entry.nativeTypeAttribute.args ?? []).map(positionalArg));
839
+ return {
840
+ kind: "namedType",
841
+ name: entry.name,
842
+ baseType: entry.baseType,
843
+ attributes: [attribute],
844
+ span: SYNTHETIC_SPAN
845
+ };
846
+ }
847
+ function escapePslString(value) {
848
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
849
+ }
850
+ /**
851
+ * Resolves a `SqlColumnIR.default` value into a normalized {@link ColumnDefault}.
852
+ *
853
+ * `SqlSchemaIR` types the column default as `string` (a raw database default
854
+ * expression). Some legacy fixtures and tests still pass already-normalized
855
+ * `ColumnDefault` objects in the same slot, so we accept either shape
856
+ * defensively at runtime.
857
+ */
858
+ function parseColumnDefault(value, nativeType, rawDefaultParser) {
859
+ if (typeof value === "string") return rawDefaultParser ? rawDefaultParser(value, nativeType) : void 0;
860
+ if (value !== null && typeof value === "object" && "kind" in value) return value;
861
+ }
862
+ function buildFieldNamesByTable(tables) {
863
+ const fieldNamesByTable = /* @__PURE__ */ new Map();
864
+ for (const table of Object.values(tables)) {
865
+ const assignmentOrder = [...Object.values(table.columns).map((column, index) => {
866
+ const { name, map } = toFieldName(column.name);
867
+ return {
868
+ columnName: column.name,
869
+ desiredFieldName: name,
870
+ fieldMap: map,
871
+ index
872
+ };
873
+ })].sort((left, right) => {
874
+ const mapComparison = Number(left.fieldMap !== void 0) - Number(right.fieldMap !== void 0);
875
+ if (mapComparison !== 0) return mapComparison;
876
+ return left.index - right.index;
877
+ });
878
+ const usedFieldNames = /* @__PURE__ */ new Set();
879
+ const tableFieldNames = /* @__PURE__ */ new Map();
880
+ for (const column of assignmentOrder) {
881
+ const fieldName = createUniqueFieldName(column.desiredFieldName, usedFieldNames);
882
+ usedFieldNames.add(fieldName);
883
+ tableFieldNames.set(column.columnName, {
884
+ fieldName,
885
+ fieldMap: column.fieldMap
886
+ });
887
+ }
888
+ fieldNamesByTable.set(table.name, tableFieldNames);
889
+ }
890
+ return fieldNamesByTable;
891
+ }
892
+ function resolveColumnFieldName(fieldNamesByTable, tableName, columnName) {
893
+ return fieldNamesByTable.get(tableName)?.get(columnName)?.fieldName ?? toFieldName(columnName).name;
894
+ }
895
+ function createUniqueFieldName(desiredName, usedFieldNames) {
896
+ if (!usedFieldNames.has(desiredName)) return desiredName;
897
+ let counter = 2;
898
+ while (usedFieldNames.has(`${desiredName}${counter}`)) counter++;
899
+ return `${desiredName}${counter}`;
900
+ }
901
+ function buildTopLevelNameMap(sources, normalize, kind, sourceKind) {
902
+ const results = /* @__PURE__ */ new Map();
903
+ const normalizedToSources = /* @__PURE__ */ new Map();
904
+ for (const source of sources) {
905
+ const normalized = normalize(source);
906
+ results.set(source, normalized);
907
+ normalizedToSources.set(normalized.name, [...normalizedToSources.get(normalized.name) ?? [], source]);
908
+ }
909
+ const duplicates = [...normalizedToSources.entries()].filter(([, conflictingSources]) => conflictingSources.length > 1);
910
+ if (duplicates.length > 0) {
911
+ const details = duplicates.map(([normalizedName, conflictingSources]) => `- ${kind} "${normalizedName}" from ${sourceKind}s ${conflictingSources.map((source) => `"${source}"`).join(", ")}`);
912
+ throw new Error(`PSL ${kind} name collisions detected:\n${details.join("\n")}`);
913
+ }
914
+ return results;
915
+ }
916
+ function assertNoCrossKindNameCollisions(modelNames, enumNames) {
917
+ const enumSourceByName = new Map([...enumNames].map(([source, result]) => [result.name, source]));
918
+ const collisions = [...modelNames.entries()].map(([tableName, result]) => {
919
+ const enumSource = enumSourceByName.get(result.name);
920
+ return enumSource ? `- identifier "${result.name}" from table "${tableName}" collides with enum type "${enumSource}"` : void 0;
921
+ }).filter((detail) => detail !== void 0);
922
+ if (collisions.length > 0) throw new Error(`PSL top-level name collisions detected:\n${collisions.join("\n")}`);
923
+ }
924
+ function createReservedNamedTypeNames(modelNames, enumNames) {
925
+ const reservedNames = new Set(PSL_SCALAR_TYPE_NAMES);
926
+ for (const result of modelNames.values()) reservedNames.add(result.name);
927
+ for (const result of enumNames.values()) reservedNames.add(result.name);
928
+ return reservedNames;
929
+ }
930
+ function seedNamedTypeRegistry(schemaIR, typeMap, enumNameMap, reservedNames) {
931
+ const seeds = /* @__PURE__ */ new Map();
932
+ for (const tableName of Object.keys(schemaIR.tables).sort()) {
933
+ const table = schemaIR.tables[tableName];
934
+ if (!table) continue;
935
+ for (const columnName of Object.keys(table.columns).sort()) {
936
+ const column = table.columns[columnName];
937
+ if (!column) continue;
938
+ const resolution = typeMap.resolve(column.nativeType, table.annotations);
939
+ if ("unsupported" in resolution || enumNameMap.has(column.nativeType) || !resolution.nativeTypeAttribute) continue;
940
+ const signatureKey = createNamedTypeSignatureKey(resolution);
941
+ if (!seeds.has(signatureKey)) seeds.set(signatureKey, {
942
+ baseType: resolution.pslType,
943
+ desiredName: toNamedTypeName(column.name),
944
+ nativeTypeAttribute: resolution.nativeTypeAttribute
945
+ });
946
+ }
947
+ }
948
+ const registry = {
949
+ entriesByKey: /* @__PURE__ */ new Map(),
950
+ usedNames: new Set(reservedNames)
951
+ };
952
+ const sortedSeeds = [...seeds.entries()].sort((left, right) => {
953
+ const desiredNameComparison = left[1].desiredName.localeCompare(right[1].desiredName);
954
+ if (desiredNameComparison !== 0) return desiredNameComparison;
955
+ return left[0].localeCompare(right[0]);
956
+ });
957
+ for (const [signatureKey, seed] of sortedSeeds) {
958
+ const name = createUniqueFieldName(seed.desiredName, registry.usedNames);
959
+ registry.entriesByKey.set(signatureKey, {
960
+ name,
961
+ baseType: seed.baseType,
962
+ nativeTypeAttribute: seed.nativeTypeAttribute
963
+ });
964
+ registry.usedNames.add(name);
965
+ }
966
+ return registry;
967
+ }
968
+ function resolveNamedTypeName(registry, resolution) {
969
+ const key = createNamedTypeSignatureKey(resolution);
970
+ const existing = registry.entriesByKey.get(key);
971
+ if (existing) return existing.name;
972
+ throw new Error(`Named type registry was not seeded for native type "${resolution.nativeType}"`);
973
+ }
974
+ function createNamedTypeSignatureKey(resolution) {
975
+ return JSON.stringify({
976
+ baseType: resolution.pslType,
977
+ nativeTypeAttribute: resolution.nativeTypeAttribute ? {
978
+ name: resolution.nativeTypeAttribute.name,
979
+ args: resolution.nativeTypeAttribute.args ?? null
980
+ } : null
981
+ });
982
+ }
983
+ function topologicalSort(models, tables, modelNameMap) {
984
+ const modelByName = /* @__PURE__ */ new Map();
985
+ for (const model of models) modelByName.set(model.name, model);
986
+ const deps = /* @__PURE__ */ new Map();
987
+ const tableToModel = /* @__PURE__ */ new Map();
988
+ for (const tableName of Object.keys(tables)) {
989
+ const modelName = modelNameMap.get(tableName);
990
+ tableToModel.set(tableName, modelName);
991
+ deps.set(modelName, /* @__PURE__ */ new Set());
992
+ }
993
+ for (const [tableName, table] of Object.entries(tables)) {
994
+ const modelName = tableToModel.get(tableName);
995
+ for (const fk of table.foreignKeys) {
996
+ const refModelName = tableToModel.get(fk.referencedTable);
997
+ if (refModelName && refModelName !== modelName) deps.get(modelName).add(refModelName);
998
+ }
999
+ }
1000
+ const result = [];
1001
+ const visited = /* @__PURE__ */ new Set();
1002
+ const visiting = /* @__PURE__ */ new Set();
1003
+ const sortedNames = [...deps.keys()].sort();
1004
+ function visit(name) {
1005
+ if (visited.has(name)) return;
1006
+ if (visiting.has(name)) return;
1007
+ visiting.add(name);
1008
+ const sortedDeps = [...deps.get(name)].sort();
1009
+ for (const dep of sortedDeps) visit(dep);
1010
+ visiting.delete(name);
1011
+ visited.add(name);
1012
+ result.push(modelByName.get(name));
1013
+ }
1014
+ for (const name of sortedNames) visit(name);
1015
+ return result;
1016
+ }
1017
+
1018
+ //#endregion
13
1019
  //#region src/core/control-instance.ts
14
1020
  function extractCodecTypeIdsFromContract(contract) {
15
1021
  const typeIds = /* @__PURE__ */ new Set();
@@ -49,7 +1055,7 @@ function createVerifyResult(options) {
49
1055
  return result;
50
1056
  }
51
1057
  function isSqlControlAdapter(value) {
52
- return typeof value === "object" && value !== null && "introspect" in value && typeof value.introspect === "function";
1058
+ return typeof value === "object" && value !== null && "introspect" in value && typeof value.introspect === "function" && "readMarker" in value && typeof value.readMarker === "function";
53
1059
  }
54
1060
  function buildSqlTypeMetadataRegistry(options) {
55
1061
  const { target, adapter, extensionPacks: extensions } = options;
@@ -83,6 +1089,11 @@ function createSqlFamilyInstance(stack) {
83
1089
  adapter,
84
1090
  extensionPacks: extensions
85
1091
  });
1092
+ const getControlAdapter = () => {
1093
+ const controlAdapter = adapter.create(stack);
1094
+ if (!isSqlControlAdapter(controlAdapter)) throw new Error("Adapter does not implement SqlControlAdapter (missing introspect or readMarker)");
1095
+ return controlAdapter;
1096
+ };
86
1097
  return {
87
1098
  familyId: "sql",
88
1099
  codecTypeImports,
@@ -99,7 +1110,7 @@ function createSqlFamilyInstance(stack) {
99
1110
  const contractStorageHash = contract.storage.storageHash;
100
1111
  const contractProfileHash = contract.profileHash;
101
1112
  const contractTarget = contract.target;
102
- const marker = await readMarker(driver);
1113
+ const marker = await getControlAdapter().readMarker(driver);
103
1114
  let missingCodecs;
104
1115
  let codecCoverageSkipped = false;
105
1116
  const supportedTypeIds = collectSupportedCodecTypeIds([
@@ -186,8 +1197,7 @@ function createSqlFamilyInstance(stack) {
186
1197
  async schemaVerify(options) {
187
1198
  const { driver, contract: contractInput, strict, context, frameworkComponents } = options;
188
1199
  const contract = validateContract(contractInput, emptyCodecLookup);
189
- const controlAdapter = adapter.create(stack);
190
- if (!isSqlControlAdapter(controlAdapter)) throw new Error("Adapter does not implement SqlControlAdapter.introspect()");
1200
+ const controlAdapter = getControlAdapter();
191
1201
  return verifySqlSchema({
192
1202
  contract,
193
1203
  schema: await controlAdapter.introspect(driver, contractInput),
@@ -208,7 +1218,7 @@ function createSqlFamilyInstance(stack) {
208
1218
  const contractTarget = contract.target;
209
1219
  await driver.query(ensureSchemaStatement.sql, ensureSchemaStatement.params);
210
1220
  await driver.query(ensureTableStatement.sql, ensureTableStatement.params);
211
- const existingMarker = await readMarker(driver);
1221
+ const existingMarker = await getControlAdapter().readMarker(driver);
212
1222
  let markerCreated = false;
213
1223
  let markerUpdated = false;
214
1224
  let previousHashes;
@@ -268,13 +1278,16 @@ function createSqlFamilyInstance(stack) {
268
1278
  };
269
1279
  },
270
1280
  async readMarker(options) {
271
- return readMarker(options.driver);
1281
+ return getControlAdapter().readMarker(options.driver);
272
1282
  },
273
1283
  async introspect(options) {
274
- const { driver, contract } = options;
275
- const controlAdapter = adapter.create(stack);
276
- if (!isSqlControlAdapter(controlAdapter)) throw new Error("Adapter does not implement SqlControlAdapter.introspect()");
277
- return controlAdapter.introspect(driver, contract);
1284
+ return getControlAdapter().introspect(options.driver, options.contract);
1285
+ },
1286
+ inferPslContract(schemaIR) {
1287
+ return sqlSchemaIrToPslAst(schemaIR);
1288
+ },
1289
+ toOperationPreview(operations) {
1290
+ return sqlOperationsToPreview(operations);
278
1291
  },
279
1292
  toSchemaView(schema) {
280
1293
  const tableNodes = Object.entries(schema.tables).map(([tableName, table]) => {
@@ -597,6 +1610,7 @@ function createMigrationPlan(options) {
597
1610
  ...options.origin !== void 0 ? { origin: options.origin ? Object.freeze({ ...options.origin }) : null } : {},
598
1611
  destination: Object.freeze({ ...options.destination }),
599
1612
  operations: freezeOperations(options.operations),
1613
+ providedInvariants: Object.freeze([...options.providedInvariants]),
600
1614
  ...options.meta ? { meta: cloneRecord(options.meta) } : {}
601
1615
  });
602
1616
  }