@prisma-next/family-sql 0.14.0 → 0.15.0-dev.10
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.
- package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-BQgad8Zc.d.mts} +30 -8
- package/dist/control-adapter-BQgad8Zc.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +279 -34
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +414 -1005
- package/dist/control.mjs.map +1 -1
- package/dist/diff.d.mts +102 -0
- package/dist/diff.d.mts.map +1 -0
- package/dist/diff.mjs +14 -0
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +26 -9
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +2 -2
- package/dist/ir.mjs.map +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +119 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +366 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/schema-differ-DnoopSXm.d.mts +45 -0
- package/dist/schema-differ-DnoopSXm.d.mts.map +1 -0
- package/dist/schema-verify-3sOPsdn_.mjs +226 -0
- package/dist/schema-verify-3sOPsdn_.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-C75cfMSS.mjs} +46 -20
- package/dist/sql-contract-serializer-C75cfMSS.mjs.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-BPv_y7iS.d.mts} +36 -142
- package/dist/types-BPv_y7iS.d.mts.map +1 -0
- package/package.json +26 -26
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +11 -5
- package/src/core/control-instance.ts +212 -42
- package/src/core/control-target-descriptor.ts +98 -0
- package/src/core/diff/schema-verify.ts +324 -0
- package/src/core/diff/sql-schema-diff.ts +41 -0
- package/src/core/diff/verifier-disposition.ts +30 -0
- package/src/core/ir/sql-contract-serializer-base.ts +70 -56
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/ir/sql-schema-verifier-base.ts +5 -5
- package/src/core/migrations/contract-to-schema-ir.ts +200 -54
- package/src/core/migrations/control-policy.ts +66 -115
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/native-type-expander.ts +28 -0
- package/src/core/migrations/schema-differ.ts +41 -0
- package/src/core/migrations/types.ts +33 -37
- package/src/core/psl-contract-infer/name-transforms.ts +15 -0
- package/src/core/psl-contract-infer/printer-config.ts +14 -6
- package/src/core/psl-contract-infer/relation-inference.ts +30 -1
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +12 -2
- package/src/exports/diff.ts +25 -0
- package/src/exports/psl-infer.ts +40 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/control-adapter-Cmw9LvEP.d.mts.map +0 -1
- package/dist/schema-verify.d.mts +0 -39
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts +0 -66
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs +0 -1501
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/core/schema-verify/control-verify-emit.ts +0 -46
- package/src/core/schema-verify/verifier-disposition.ts +0 -58
- package/src/core/schema-verify/verify-helpers.ts +0 -820
- package/src/core/schema-verify/verify-sql-schema.ts +0 -1311
- package/src/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
package/dist/control.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
1
|
+
import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-CXd-8ydc.mjs";
|
|
2
|
+
import { n as classifyDiffSubjectGranularity, o as verifySqlSchemaByDiff, s as extractCodecControlHooks, t as classifyDiffEntityKind } from "./schema-verify-3sOPsdn_.mjs";
|
|
3
|
+
import { t as SqlContractSerializer } from "./sql-contract-serializer-C75cfMSS.mjs";
|
|
4
4
|
import { t as collectSupportedCodecTypeIds } from "./verify-C-G0obRm.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 { blindCast } from "@prisma-next/utils/casts";
|
|
7
8
|
import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions } from "@prisma-next/framework-components/control";
|
|
9
|
+
import { isPlainRecord } from "@prisma-next/framework-components/ir";
|
|
8
10
|
import { assertDescriptorSelfConsistency } from "@prisma-next/migration-tools/spaces";
|
|
9
11
|
import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
|
|
10
12
|
import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
|
|
11
13
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
12
|
-
import { StorageTable } from "@prisma-next/sql-contract/types";
|
|
13
|
-
import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma-next/framework-components/psl-ast";
|
|
14
14
|
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
15
|
+
import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
|
|
16
|
+
import { RelationalSchemaNodeKind, SqlSchemaIR, SqlTableIR } from "@prisma-next/sql-schema-ir/types";
|
|
15
17
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
16
18
|
//#region src/core/operation-preview.ts
|
|
17
19
|
function isDdlStatement(sqlStatement) {
|
|
@@ -46,921 +48,6 @@ function sqlOperationsToPreview(operations) {
|
|
|
46
48
|
})) };
|
|
47
49
|
}
|
|
48
50
|
//#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
51
|
//#region src/core/control-instance.ts
|
|
965
52
|
function extractCodecTypeIdsFromContract(contract) {
|
|
966
53
|
const typeIds = /* @__PURE__ */ new Set();
|
|
@@ -1050,7 +137,7 @@ function buildTransitiveDependsOnMap(extensions) {
|
|
|
1050
137
|
}
|
|
1051
138
|
return set;
|
|
1052
139
|
};
|
|
1053
|
-
for (const ext of extensions) resolve(ext.id, new Set([ext.id]));
|
|
140
|
+
for (const ext of extensions) resolve(ext.id, /* @__PURE__ */ new Set([ext.id]));
|
|
1054
141
|
return result;
|
|
1055
142
|
}
|
|
1056
143
|
/**
|
|
@@ -1123,8 +210,18 @@ function createSqlFamilyInstance(stack) {
|
|
|
1123
210
|
let controlAdapter;
|
|
1124
211
|
const getControlAdapter = () => controlAdapter ??= adapter.create(stack);
|
|
1125
212
|
const targetSerializer = target.contractSerializer;
|
|
1126
|
-
const
|
|
1127
|
-
|
|
213
|
+
const targetInferPslContract = blindCast(target).inferPslContract;
|
|
214
|
+
const diffSchema = blindCast(target).diffSchema;
|
|
215
|
+
const targetGranularityOf = blindCast(target).classifySubjectGranularity;
|
|
216
|
+
const targetEntityKindOf = blindCast(target).classifyEntityKind;
|
|
217
|
+
const describedContracts = extensions.flatMap((extension) => extension.contractSpace ? [{
|
|
218
|
+
spaceId: extension.id,
|
|
219
|
+
contract: extension.contractSpace.contractJson
|
|
220
|
+
}] : []);
|
|
221
|
+
const deserializeWithTargetSerializer = (contractOrJson) => {
|
|
222
|
+
const serializer = targetSerializer ?? new SqlContractSerializer();
|
|
223
|
+
const json = targetSerializer !== void 0 && !isPlainRecord(contractOrJson) ? targetSerializer.serializeContract(blindCast(contractOrJson)) : contractOrJson;
|
|
224
|
+
return serializer.deserializeContract(json);
|
|
1128
225
|
};
|
|
1129
226
|
return {
|
|
1130
227
|
familyId: "sql",
|
|
@@ -1227,17 +324,41 @@ function createSqlFamilyInstance(stack) {
|
|
|
1227
324
|
},
|
|
1228
325
|
verifySchema(options) {
|
|
1229
326
|
const contract = deserializeWithTargetSerializer(options.contract);
|
|
1230
|
-
|
|
1231
|
-
|
|
327
|
+
if (!diffSchema) throw new Error(`SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`);
|
|
328
|
+
if (!targetGranularityOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`);
|
|
329
|
+
return verifySqlSchemaByDiff({
|
|
1232
330
|
contract,
|
|
1233
331
|
schema: options.schema,
|
|
1234
332
|
strict: options.strict,
|
|
1235
|
-
typeMetadataRegistry,
|
|
1236
333
|
frameworkComponents: options.frameworkComponents,
|
|
1237
|
-
|
|
1238
|
-
|
|
334
|
+
diffSchema,
|
|
335
|
+
granularityOf: targetGranularityOf
|
|
1239
336
|
});
|
|
1240
337
|
},
|
|
338
|
+
/**
|
|
339
|
+
* Classifies a diff issue's subject granularity on demand, by resolving
|
|
340
|
+
* its node's `nodeKind` through the target's classifier — the
|
|
341
|
+
* {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
342
|
+
* capability. Framework consumers spanning contract spaces (the
|
|
343
|
+
* migration aggregate's unclaimed-elements sweep) detect and call this
|
|
344
|
+
* instead of reaching into the concrete schema-IR node, which they
|
|
345
|
+
* cannot read; nothing is stamped on the issue or the node.
|
|
346
|
+
*/
|
|
347
|
+
classifySubjectGranularity(issue) {
|
|
348
|
+
if (!targetGranularityOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`);
|
|
349
|
+
return classifyDiffSubjectGranularity(issue, targetGranularityOf);
|
|
350
|
+
},
|
|
351
|
+
/**
|
|
352
|
+
* Classifies a diff issue's subject storage `entityKind` on demand, by
|
|
353
|
+
* resolving its node's `nodeKind` through the target's classifier —
|
|
354
|
+
* the sibling of `classifySubjectGranularity` above, and part of the
|
|
355
|
+
* same {@link import('@prisma-next/framework-components/control').SchemaSubjectClassifierCapable}
|
|
356
|
+
* capability.
|
|
357
|
+
*/
|
|
358
|
+
classifyEntityKind(issue) {
|
|
359
|
+
if (!targetEntityKindOf) throw new Error(`SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`);
|
|
360
|
+
return classifyDiffEntityKind(issue, targetEntityKindOf);
|
|
361
|
+
},
|
|
1241
362
|
async sign(options) {
|
|
1242
363
|
const { driver, contract: contractInput, contractPath, configPath } = options;
|
|
1243
364
|
const startTime = Date.now();
|
|
@@ -1326,7 +447,8 @@ function createSqlFamilyInstance(stack) {
|
|
|
1326
447
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
1327
448
|
},
|
|
1328
449
|
inferPslContract(schemaIR) {
|
|
1329
|
-
|
|
450
|
+
if (!targetInferPslContract) throw new Error(`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`);
|
|
451
|
+
return targetInferPslContract(schemaIR, describedContracts);
|
|
1330
452
|
},
|
|
1331
453
|
lowerAst(ast, context) {
|
|
1332
454
|
return getControlAdapter().lowerToExecuteRequest(ast, context);
|
|
@@ -1334,21 +456,25 @@ function createSqlFamilyInstance(stack) {
|
|
|
1334
456
|
bootstrapControlTableQueries() {
|
|
1335
457
|
return getControlAdapter().bootstrapControlTableQueries();
|
|
1336
458
|
},
|
|
1337
|
-
bootstrapSignMarkerQueries() {
|
|
1338
|
-
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
1339
|
-
},
|
|
1340
459
|
toOperationPreview(operations) {
|
|
1341
460
|
return sqlOperationsToPreview(operations);
|
|
1342
461
|
},
|
|
1343
462
|
toSchemaView(schema) {
|
|
1344
|
-
const
|
|
463
|
+
const root = blindCast(schema);
|
|
464
|
+
const namespaceEntries = root.namespaces !== void 0 ? Object.entries(root.namespaces).map(([namespaceKey, namespace]) => [namespaceKey, namespace.tables]) : [[void 0, root.tables ?? {}]];
|
|
465
|
+
const qualify = namespaceEntries.length > 1;
|
|
466
|
+
const tableNodes = namespaceEntries.flatMap(([namespaceKey, tables]) => Object.entries(tables).map(([tableName, table]) => [
|
|
467
|
+
qualify && namespaceKey !== void 0 ? `${namespaceKey}.${tableName}` : tableName,
|
|
468
|
+
tableName,
|
|
469
|
+
table
|
|
470
|
+
])).map(([displayName, tableName, table]) => {
|
|
1345
471
|
const children = [];
|
|
1346
472
|
const columnNodes = [];
|
|
1347
473
|
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
1348
474
|
const label = `${columnName}: ${column.nativeType} (${column.nullable ? "nullable" : "not nullable"})`;
|
|
1349
475
|
columnNodes.push(new SchemaTreeNode({
|
|
1350
476
|
kind: "field",
|
|
1351
|
-
id: `column-${
|
|
477
|
+
id: `column-${displayName}-${columnName}`,
|
|
1352
478
|
label,
|
|
1353
479
|
meta: {
|
|
1354
480
|
nativeType: column.nativeType,
|
|
@@ -1359,7 +485,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1359
485
|
}
|
|
1360
486
|
if (columnNodes.length > 0) children.push(new SchemaTreeNode({
|
|
1361
487
|
kind: "collection",
|
|
1362
|
-
id: `columns-${
|
|
488
|
+
id: `columns-${displayName}`,
|
|
1363
489
|
label: "columns",
|
|
1364
490
|
children: columnNodes
|
|
1365
491
|
}));
|
|
@@ -1367,7 +493,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1367
493
|
const pkColumns = table.primaryKey.columns.join(", ");
|
|
1368
494
|
children.push(new SchemaTreeNode({
|
|
1369
495
|
kind: "index",
|
|
1370
|
-
id: `primary-key-${
|
|
496
|
+
id: `primary-key-${displayName}`,
|
|
1371
497
|
label: `primary key: ${pkColumns}`,
|
|
1372
498
|
meta: {
|
|
1373
499
|
columns: table.primaryKey.columns,
|
|
@@ -1380,7 +506,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1380
506
|
const label = `unique ${name}`;
|
|
1381
507
|
children.push(new SchemaTreeNode({
|
|
1382
508
|
kind: "index",
|
|
1383
|
-
id: `unique-${
|
|
509
|
+
id: `unique-${displayName}-${name}`,
|
|
1384
510
|
label,
|
|
1385
511
|
meta: {
|
|
1386
512
|
columns: unique.columns,
|
|
@@ -1393,7 +519,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1393
519
|
const label = index.unique ? `unique index ${name}` : `index ${name}`;
|
|
1394
520
|
children.push(new SchemaTreeNode({
|
|
1395
521
|
kind: "index",
|
|
1396
|
-
id: `index-${
|
|
522
|
+
id: `index-${displayName}-${name}`,
|
|
1397
523
|
label,
|
|
1398
524
|
meta: {
|
|
1399
525
|
columns: index.columns,
|
|
@@ -1414,8 +540,8 @@ function createSqlFamilyInstance(stack) {
|
|
|
1414
540
|
}));
|
|
1415
541
|
return new SchemaTreeNode({
|
|
1416
542
|
kind: "entity",
|
|
1417
|
-
id: `table-${
|
|
1418
|
-
label: `table ${
|
|
543
|
+
id: `table-${displayName}`,
|
|
544
|
+
label: `table ${displayName}`,
|
|
1419
545
|
...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
|
|
1420
546
|
...children.length > 0 ? { children } : {}
|
|
1421
547
|
});
|
|
@@ -1448,6 +574,301 @@ var SqlFamilyDescriptor = class {
|
|
|
1448
574
|
}
|
|
1449
575
|
};
|
|
1450
576
|
//#endregion
|
|
577
|
+
//#region src/core/migrations/contract-to-schema-ir.ts
|
|
578
|
+
function convertColumn(name, column, storageTypes, expandNativeType, renderDefault) {
|
|
579
|
+
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
580
|
+
const baseNativeType = expandNativeType ? expandNativeType({
|
|
581
|
+
nativeType: resolved.nativeType,
|
|
582
|
+
codecId: resolved.codecId,
|
|
583
|
+
...ifDefined("typeParams", resolved.typeParams)
|
|
584
|
+
}) : resolved.nativeType;
|
|
585
|
+
const nativeType = baseNativeType;
|
|
586
|
+
const resolvedNativeType = column.many ? `${baseNativeType}[]` : baseNativeType;
|
|
587
|
+
return {
|
|
588
|
+
name,
|
|
589
|
+
nativeType,
|
|
590
|
+
nullable: column.nullable,
|
|
591
|
+
...ifDefined("many", column.many),
|
|
592
|
+
...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0),
|
|
593
|
+
resolvedNativeType,
|
|
594
|
+
...ifDefined("resolvedDefault", column.default ?? void 0),
|
|
595
|
+
codecRef: buildColumnCodecRef(resolved, column.many),
|
|
596
|
+
codecBaseNativeType: resolved.nativeType,
|
|
597
|
+
...column.typeRef !== void 0 ? { codecNamedType: true } : {}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Builds the column's `CodecRef` from its resolved (post-`typeRef`) codec
|
|
602
|
+
* identity — the same construction the query AST and the migration DDL
|
|
603
|
+
* renderer already use (TML-2456, TML-2918).
|
|
604
|
+
*/
|
|
605
|
+
function buildColumnCodecRef(resolved, many) {
|
|
606
|
+
return {
|
|
607
|
+
codecId: resolved.codecId,
|
|
608
|
+
...ifDefined("typeParams", resolved.typeParams !== void 0 ? blindCast(resolved.typeParams) : void 0),
|
|
609
|
+
...ifDefined("many", many)
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function resolveColumnTypeMetadata(column, storageTypes) {
|
|
613
|
+
if (!column.typeRef) return column;
|
|
614
|
+
const referenced = storageTypes[column.typeRef];
|
|
615
|
+
if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
|
|
616
|
+
if (isStorageTypeInstance(referenced)) return {
|
|
617
|
+
codecId: referenced.codecId,
|
|
618
|
+
nativeType: referenced.nativeType,
|
|
619
|
+
typeParams: referenced.typeParams
|
|
620
|
+
};
|
|
621
|
+
throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Resolves a `ValueSetRef` to its permitted values from the contract storage.
|
|
625
|
+
*
|
|
626
|
+
* Throws when the referenced namespace or value-set is absent — this indicates
|
|
627
|
+
* the contract was built incorrectly (the check and the value-set must be
|
|
628
|
+
* co-emitted by the lowering step). Used by `convertCheck` (schema-IR
|
|
629
|
+
* projection), `verifyCheckConstraints` (verification), and
|
|
630
|
+
* `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
|
|
631
|
+
* the resolved values and the error behavior on a missing reference.
|
|
632
|
+
*/
|
|
633
|
+
function allStrings(values) {
|
|
634
|
+
return values.every((value) => typeof value === "string");
|
|
635
|
+
}
|
|
636
|
+
function resolveValueSetValues(ref, storage, contextLabel) {
|
|
637
|
+
const ns = storage.namespaces[ref.namespaceId];
|
|
638
|
+
if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
|
|
639
|
+
const valueSet = ns.entries.valueSet?.[ref.entityName];
|
|
640
|
+
if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
|
|
641
|
+
const values = valueSet.values;
|
|
642
|
+
if (!allStrings(values)) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" in namespace "${ref.namespaceId}" has a non-string value; numeric-enum CHECK constraints are not yet supported (${contextLabel})`);
|
|
643
|
+
return values;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
|
|
647
|
+
* resolving the permitted values from the storage value-set it references.
|
|
648
|
+
*
|
|
649
|
+
* The `CheckConstraint.valueSet` ref points to
|
|
650
|
+
* `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
|
|
651
|
+
* values are lifted directly from `StorageValueSet.values` so verification
|
|
652
|
+
* compares value sets, not SQL predicate strings.
|
|
653
|
+
*
|
|
654
|
+
* Throws if the referenced namespace or value-set is absent — this
|
|
655
|
+
* indicates the contract was built incorrectly (the check and the
|
|
656
|
+
* value-set must be co-emitted by the lowering step).
|
|
657
|
+
*/
|
|
658
|
+
function convertCheck(check, storage) {
|
|
659
|
+
const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
|
|
660
|
+
return {
|
|
661
|
+
name: check.name,
|
|
662
|
+
column: check.column,
|
|
663
|
+
permittedValues
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function convertUnique(unique, tableName) {
|
|
667
|
+
return {
|
|
668
|
+
columns: unique.columns,
|
|
669
|
+
...ifDefined("name", unique.name),
|
|
670
|
+
dependsOn: flatColumnDependsOn(tableName, unique.columns)
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function convertIndex(index, tableName) {
|
|
674
|
+
return {
|
|
675
|
+
columns: index.columns,
|
|
676
|
+
unique: false,
|
|
677
|
+
...ifDefined("name", index.name),
|
|
678
|
+
...ifDefined("type", index.type),
|
|
679
|
+
...ifDefined("options", index.options),
|
|
680
|
+
dependsOn: flatColumnDependsOn(tableName, index.columns)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* The referenced table's chain in the flat (single-schema) tree
|
|
685
|
+
* `contractToSchemaIR`/`contractNamespaceToSchemaIR` build: the root
|
|
686
|
+
* (`SqlSchemaIR`, fixed `'database'` id) followed by the table's own id.
|
|
687
|
+
* Postgres discards this when it re-derives the FK against its own
|
|
688
|
+
* multi-schema tree shape (`contractToPostgresDatabaseSchemaNode`); SQLite's
|
|
689
|
+
* flat tree uses it as-is.
|
|
690
|
+
*/
|
|
691
|
+
function flatSchemaDependsOn(tableName) {
|
|
692
|
+
return [{
|
|
693
|
+
nodeKind: RelationalSchemaNodeKind.schema,
|
|
694
|
+
id: "database"
|
|
695
|
+
}, {
|
|
696
|
+
nodeKind: RelationalSchemaNodeKind.table,
|
|
697
|
+
id: tableName
|
|
698
|
+
}];
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The chains from a table-child object (foreign key, index, unique, primary
|
|
702
|
+
* key) to each of the own columns it is built on, in the flat tree. Dropping
|
|
703
|
+
* a covered column auto-drops the object, so the object's drop must precede
|
|
704
|
+
* the column's; the graph derives that direction from these edges.
|
|
705
|
+
*/
|
|
706
|
+
function flatColumnDependsOn(tableName, columns) {
|
|
707
|
+
return columns.map((column) => [
|
|
708
|
+
{
|
|
709
|
+
nodeKind: RelationalSchemaNodeKind.schema,
|
|
710
|
+
id: "database"
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
nodeKind: RelationalSchemaNodeKind.table,
|
|
714
|
+
id: tableName
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
nodeKind: RelationalSchemaNodeKind.column,
|
|
718
|
+
id: `column:${column}`
|
|
719
|
+
}
|
|
720
|
+
]);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* The FK's referenced-namespace identity comes from the target's namespace
|
|
724
|
+
* node, not the raw namespace-id string. An unbound target namespace stamps
|
|
725
|
+
* no `referencedSchema` at all — the FK node's id renders the absence as the
|
|
726
|
+
* empty segment, which is what flat (single-schema) introspection produces,
|
|
727
|
+
* so both diff sides' FK ids meet by construction. A bound namespace (or a
|
|
728
|
+
* cross-space target whose namespace lives in another contract's storage)
|
|
729
|
+
* stamps its coordinate verbatim; namespaced targets (Postgres) resolve the
|
|
730
|
+
* real DDL schema downstream.
|
|
731
|
+
*
|
|
732
|
+
* `dependsOn` carries the referenced table (created before the FK, dropped
|
|
733
|
+
* after it) plus the FK's own columns (dropped after the FK, since dropping a
|
|
734
|
+
* column auto-drops the FK built on it).
|
|
735
|
+
*/
|
|
736
|
+
function convertForeignKey(fk, storage) {
|
|
737
|
+
const targetIsUnbound = storage.namespaces[fk.target.namespaceId]?.isUnbound === true;
|
|
738
|
+
return {
|
|
739
|
+
columns: fk.source.columns,
|
|
740
|
+
referencedTable: fk.target.tableName,
|
|
741
|
+
...targetIsUnbound ? {} : { referencedSchema: fk.target.namespaceId },
|
|
742
|
+
referencedColumns: fk.target.columns,
|
|
743
|
+
...ifDefined("name", fk.name),
|
|
744
|
+
...ifDefined("onDelete", fk.onDelete),
|
|
745
|
+
...ifDefined("onUpdate", fk.onUpdate),
|
|
746
|
+
dependsOn: [flatSchemaDependsOn(fk.target.tableName), ...flatColumnDependsOn(fk.source.tableName, fk.source.columns)]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
|
|
750
|
+
const columns = {};
|
|
751
|
+
for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
|
|
752
|
+
const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
|
|
753
|
+
return new SqlTableIR({
|
|
754
|
+
name,
|
|
755
|
+
columns,
|
|
756
|
+
...ifDefined("primaryKey", table.primaryKey !== void 0 ? {
|
|
757
|
+
columns: table.primaryKey.columns,
|
|
758
|
+
...ifDefined("name", table.primaryKey.name),
|
|
759
|
+
dependsOn: flatColumnDependsOn(name, table.primaryKey.columns)
|
|
760
|
+
} : void 0),
|
|
761
|
+
foreignKeys: table.foreignKeys.map((fk) => convertForeignKey(fk, storage)),
|
|
762
|
+
uniques: table.uniques.map((u) => convertUnique(u, name)),
|
|
763
|
+
indexes: table.indexes.map((i) => convertIndex(i, name)),
|
|
764
|
+
...ifDefined("checks", checks)
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Detects destructive changes between two contract storages.
|
|
769
|
+
*
|
|
770
|
+
* The additive-only planner silently ignores removals (tables, columns).
|
|
771
|
+
* This function detects those removals so callers can report them as conflicts
|
|
772
|
+
* rather than silently producing an empty plan.
|
|
773
|
+
*
|
|
774
|
+
* Returns an empty array if no destructive changes are found.
|
|
775
|
+
*/
|
|
776
|
+
function detectDestructiveChanges(from, to) {
|
|
777
|
+
if (!from) return [];
|
|
778
|
+
const hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
779
|
+
const conflicts = [];
|
|
780
|
+
const namespaceIds = [.../* @__PURE__ */ new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
781
|
+
for (const namespaceId of namespaceIds) {
|
|
782
|
+
const fromNs = from.namespaces[namespaceId];
|
|
783
|
+
const toNs = to.namespaces[namespaceId];
|
|
784
|
+
const fromTables = fromNs?.entries.table;
|
|
785
|
+
if (!fromTables) continue;
|
|
786
|
+
for (const tableName of Object.keys(fromTables)) {
|
|
787
|
+
const toTableRaw = toNs?.entries.table?.[tableName];
|
|
788
|
+
if (!StorageTable.is(toTableRaw)) {
|
|
789
|
+
conflicts.push({
|
|
790
|
+
kind: "tableRemoved",
|
|
791
|
+
summary: `Table "${tableName}" was removed`
|
|
792
|
+
});
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const toTable = toTableRaw;
|
|
796
|
+
const fromTableRaw = fromTables[tableName];
|
|
797
|
+
if (!StorageTable.is(fromTableRaw)) continue;
|
|
798
|
+
const fromTable = fromTableRaw;
|
|
799
|
+
for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
|
|
800
|
+
kind: "columnRemoved",
|
|
801
|
+
summary: `Column "${tableName}"."${columnName}" was removed`
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return conflicts;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Converts a `Contract` to `SqlSchemaIR`.
|
|
809
|
+
*
|
|
810
|
+
* Reads `contract.storage` for tables and `contract.storage.types` for type
|
|
811
|
+
* annotations. Storage-type annotations are written under
|
|
812
|
+
* `options.annotationNamespace`.
|
|
813
|
+
*
|
|
814
|
+
* Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
|
|
815
|
+
* structural information. When `expandNativeType` is provided, parameterized types
|
|
816
|
+
* are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
|
|
817
|
+
* resulting IR compares correctly against the "to" contract during planning.
|
|
818
|
+
*
|
|
819
|
+
* Returns an empty schema IR when `contract` is `null` (new project).
|
|
820
|
+
*/
|
|
821
|
+
/**
|
|
822
|
+
* Converts the tables of a single namespace into a `SqlSchemaIR`, keyed by
|
|
823
|
+
* table name within that namespace. Unlike {@link contractToSchemaIR}, which
|
|
824
|
+
* flattens every namespace's tables into one bare-keyed record (and throws on a
|
|
825
|
+
* cross-namespace name collision), this scopes the table iteration to one
|
|
826
|
+
* namespace so the same table name can exist in two schemas.
|
|
827
|
+
*
|
|
828
|
+
* The full `storage` is still passed to `convertTable`, so value-set / enum /
|
|
829
|
+
* type resolution that legitimately spans namespaces is unaffected. Foreign
|
|
830
|
+
* keys are built purely from the FK descriptor (`fk.target`), so cross-namespace
|
|
831
|
+
* FKs survive per-namespace conversion. The `annotations` block (storage-type
|
|
832
|
+
* derived) is omitted here — the per-namespace tree consumer reads only the
|
|
833
|
+
* per-table fields.
|
|
834
|
+
*/
|
|
835
|
+
function contractNamespaceToSchemaIR(storage, namespaceId, options) {
|
|
836
|
+
if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
|
|
837
|
+
const namespace = storage.namespaces[namespaceId];
|
|
838
|
+
if (!namespace) return new SqlSchemaIR({ tables: {} });
|
|
839
|
+
const storageTypes = { ...storage.types ?? {} };
|
|
840
|
+
const tables = {};
|
|
841
|
+
for (const [tableName, tableDefRaw] of Object.entries(namespace.entries.table ?? {})) {
|
|
842
|
+
StorageTable.assert(tableDefRaw, `namespaces.${namespaceId}.entries.table.${tableName}`);
|
|
843
|
+
tables[tableName] = convertTable(tableName, tableDefRaw, storageTypes, options.expandNativeType, options.renderDefault, storage);
|
|
844
|
+
}
|
|
845
|
+
return new SqlSchemaIR({ tables });
|
|
846
|
+
}
|
|
847
|
+
function contractToSchemaIR(contract, options) {
|
|
848
|
+
if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
|
|
849
|
+
if (!contract) return new SqlSchemaIR({ tables: {} });
|
|
850
|
+
const storage = contract.storage;
|
|
851
|
+
const storageTypes = { ...storage.types ?? {} };
|
|
852
|
+
const tables = {};
|
|
853
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
854
|
+
StorageTable.assert(tableDefRaw, `namespaces.${ns.id}.entries.table.${tableName}`);
|
|
855
|
+
const tableDef = tableDefRaw;
|
|
856
|
+
if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
|
|
857
|
+
tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
|
|
858
|
+
}
|
|
859
|
+
return new SqlSchemaIR({
|
|
860
|
+
tables,
|
|
861
|
+
...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumNamespaceSchema))
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
function deriveAnnotations(storage, annotationNamespace, _resolveEnumNamespaceSchema) {
|
|
865
|
+
const storageTypes = {};
|
|
866
|
+
for (const typeInstance of Object.values(storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
|
|
867
|
+
const envelope = { ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {} };
|
|
868
|
+
if (Object.keys(envelope).length === 0) return void 0;
|
|
869
|
+
return { [annotationNamespace]: envelope };
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
1451
872
|
//#region src/core/migrations/control-policy.ts
|
|
1452
873
|
/**
|
|
1453
874
|
* The control policy that governs a single call. The `external` default is an
|
|
@@ -1478,43 +899,10 @@ function callAllowedUnderControlPolicy(policy, subject) {
|
|
|
1478
899
|
case "observed": return false;
|
|
1479
900
|
}
|
|
1480
901
|
}
|
|
1481
|
-
function defaultSubjectLabel(factoryName, subject) {
|
|
1482
|
-
if (subject?.table) return `${factoryName}(${subject.table})`;
|
|
1483
|
-
if (subject?.typeName) return `${factoryName}(${subject.typeName})`;
|
|
1484
|
-
return factoryName;
|
|
1485
|
-
}
|
|
1486
|
-
function suppressionSummary(subjectLabel, subject, effectivePolicy) {
|
|
1487
|
-
const namespace = subject?.namespaceId ?? "unknown";
|
|
1488
|
-
const declared = subject?.explicitNodeControlPolicy;
|
|
1489
|
-
if (effectivePolicy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but table declared 'managed'`;
|
|
1490
|
-
return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${effectivePolicy}'${declared ? ` but table declared '${declared}'` : ""}`;
|
|
1491
|
-
}
|
|
1492
|
-
function buildSubjectSuppressionWarning(subject, effectivePolicy, factoryName, formatSubjectLabel) {
|
|
1493
|
-
return {
|
|
1494
|
-
kind: "controlPolicySuppressedCall",
|
|
1495
|
-
summary: suppressionSummary(formatSubjectLabel(factoryName, subject), subject, effectivePolicy),
|
|
1496
|
-
location: {
|
|
1497
|
-
...ifDefined("namespace", subject?.namespaceId),
|
|
1498
|
-
...ifDefined("table", subject?.table),
|
|
1499
|
-
...ifDefined("column", subject?.column),
|
|
1500
|
-
...ifDefined("type", subject?.typeName)
|
|
1501
|
-
},
|
|
1502
|
-
meta: {
|
|
1503
|
-
controlPolicy: effectivePolicy,
|
|
1504
|
-
factoryName,
|
|
1505
|
-
...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy)
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
function defaultModificationFactoryNameForSubject(subject) {
|
|
1510
|
-
if (subject.table) return "alterTable";
|
|
1511
|
-
if (subject.typeName) return "alterType";
|
|
1512
|
-
return "alterSchema";
|
|
1513
|
-
}
|
|
1514
902
|
/**
|
|
1515
903
|
* Partition the calls produced for a single set of subjects into those the
|
|
1516
904
|
* effective control policy permits (`kept`) and a list of
|
|
1517
|
-
* {@link
|
|
905
|
+
* {@link SuppressionRecord}s describing the suppressed calls.
|
|
1518
906
|
*
|
|
1519
907
|
* **Prefer {@link partitionIssuesByControlPolicy}** for the schema-issue
|
|
1520
908
|
* pipeline: it filters subjects out of the planner's *input* so the planner
|
|
@@ -1526,21 +914,22 @@ function defaultModificationFactoryNameForSubject(subject) {
|
|
|
1526
914
|
*/
|
|
1527
915
|
function partitionCallsByControlPolicy(options) {
|
|
1528
916
|
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1529
|
-
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1530
917
|
const kept = [];
|
|
1531
|
-
const
|
|
918
|
+
const suppressions = [];
|
|
1532
919
|
for (const call of options.calls) {
|
|
1533
920
|
const subject = options.resolveControlPolicySubject(call);
|
|
1534
921
|
const policy = controlPolicyForCall(subject, defaultControlPolicy);
|
|
1535
922
|
if (callAllowedUnderControlPolicy(policy, subject)) kept.push(call);
|
|
1536
|
-
else {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
923
|
+
else suppressions.push({
|
|
924
|
+
subject,
|
|
925
|
+
policy,
|
|
926
|
+
factoryName: options.resolveFactoryName(call),
|
|
927
|
+
createsNewObject: subject?.createsNewObject ?? false
|
|
928
|
+
});
|
|
1540
929
|
}
|
|
1541
930
|
return Object.freeze({
|
|
1542
931
|
kept: Object.freeze(kept),
|
|
1543
|
-
|
|
932
|
+
suppressions: Object.freeze(suppressions)
|
|
1544
933
|
});
|
|
1545
934
|
}
|
|
1546
935
|
/**
|
|
@@ -1557,23 +946,18 @@ function partitionCallsByControlPolicy(options) {
|
|
|
1557
946
|
* cannot reason about the live shape of a subject the user marked as
|
|
1558
947
|
* out-of-scope (`external`).
|
|
1559
948
|
*
|
|
1560
|
-
* `
|
|
1561
|
-
* suppressed issue). `factoryName` is
|
|
1562
|
-
*
|
|
1563
|
-
* the
|
|
1564
|
-
*
|
|
1565
|
-
* `defaultModificationFactoryName(subject)` — a synthetic label that names
|
|
1566
|
-
* the *kind* of mutation that would have run, since no concrete DDL call was
|
|
1567
|
-
* generated.
|
|
949
|
+
* `suppressions` is one {@link SuppressionRecord} per suppressed subject (not
|
|
950
|
+
* per suppressed issue). Its `factoryName` is the creation factory name when any
|
|
951
|
+
* of the subject's issues is whole-object creation (e.g. `createTable`), else
|
|
952
|
+
* `undefined` — the family never invents a modification verb for an op that
|
|
953
|
+
* produced no call; the target renders the message.
|
|
1568
954
|
*
|
|
1569
955
|
* Unresolved-subject issues (`resolveControlPolicySubject` returns
|
|
1570
|
-
* `undefined`) emit one
|
|
1571
|
-
*
|
|
956
|
+
* `undefined`) emit one record each; they cannot be deduplicated because they
|
|
957
|
+
* carry no subject coordinate.
|
|
1572
958
|
*/
|
|
1573
959
|
function partitionIssuesByControlPolicy(options) {
|
|
1574
960
|
const defaultControlPolicy = options.contract.defaultControlPolicy;
|
|
1575
|
-
const formatSubjectLabel = options.formatSubjectLabel ?? defaultSubjectLabel;
|
|
1576
|
-
const inferModificationFactoryName = options.defaultModificationFactoryName ?? defaultModificationFactoryNameForSubject;
|
|
1577
961
|
const plannable = [];
|
|
1578
962
|
const suppressedSubjects = /* @__PURE__ */ new Map();
|
|
1579
963
|
const unresolvedSuppressions = [];
|
|
@@ -1590,8 +974,12 @@ function partitionIssuesByControlPolicy(options) {
|
|
|
1590
974
|
continue;
|
|
1591
975
|
}
|
|
1592
976
|
if (subject === void 0) {
|
|
1593
|
-
|
|
1594
|
-
|
|
977
|
+
unresolvedSuppressions.push({
|
|
978
|
+
subject: void 0,
|
|
979
|
+
policy,
|
|
980
|
+
factoryName: creationFactoryName,
|
|
981
|
+
createsNewObject: false
|
|
982
|
+
});
|
|
1595
983
|
continue;
|
|
1596
984
|
}
|
|
1597
985
|
const key = subjectKey(subject);
|
|
@@ -1604,18 +992,20 @@ function partitionIssuesByControlPolicy(options) {
|
|
|
1604
992
|
...ifDefined("creationFactoryName", creationFactoryName)
|
|
1605
993
|
});
|
|
1606
994
|
}
|
|
1607
|
-
const
|
|
1608
|
-
for (const entry of suppressedSubjects.values()) {
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
995
|
+
const suppressions = [...unresolvedSuppressions];
|
|
996
|
+
for (const entry of suppressedSubjects.values()) suppressions.push({
|
|
997
|
+
subject: entry.subject,
|
|
998
|
+
policy: entry.policy,
|
|
999
|
+
factoryName: entry.creationFactoryName,
|
|
1000
|
+
createsNewObject: entry.subject.createsNewObject
|
|
1001
|
+
});
|
|
1612
1002
|
return Object.freeze({
|
|
1613
1003
|
plannable: Object.freeze(plannable),
|
|
1614
|
-
|
|
1004
|
+
suppressions: Object.freeze(suppressions)
|
|
1615
1005
|
});
|
|
1616
1006
|
}
|
|
1617
1007
|
function subjectKey(subject) {
|
|
1618
|
-
return `${subject.namespaceId}\u0000${subject.
|
|
1008
|
+
return `${subject.namespaceId}\u0000${subject.entityKind ?? ""}\u0000${subject.entityName ?? ""}`;
|
|
1619
1009
|
}
|
|
1620
1010
|
//#endregion
|
|
1621
1011
|
//#region src/core/migrations/field-event-planner.ts
|
|
@@ -1635,8 +1025,8 @@ function planFieldEventOperations(options) {
|
|
|
1635
1025
|
for (const tableName of tableNames) {
|
|
1636
1026
|
const priorTableRaw = priorTables?.[tableName];
|
|
1637
1027
|
const newTableRaw = newTables?.[tableName];
|
|
1638
|
-
const priorTable = priorTableRaw
|
|
1639
|
-
const newTable = newTableRaw
|
|
1028
|
+
const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
|
|
1029
|
+
const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
|
|
1640
1030
|
const fieldNames = unionSorted(priorTable ? Object.keys(priorTable.columns) : [], newTable ? Object.keys(newTable.columns) : []);
|
|
1641
1031
|
for (const fieldName of fieldNames) {
|
|
1642
1032
|
const priorField = priorTable?.columns[fieldName];
|
|
@@ -1742,6 +1132,25 @@ function unionSorted(a, b) {
|
|
|
1742
1132
|
return [...set].sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
|
|
1743
1133
|
}
|
|
1744
1134
|
//#endregion
|
|
1135
|
+
//#region src/core/migrations/native-type-expander.ts
|
|
1136
|
+
/**
|
|
1137
|
+
* Builds the codec-hook-composed `expandNativeType` callback the contract→IR
|
|
1138
|
+
* derivation uses to expand parameterized native types (e.g. `character` +
|
|
1139
|
+
* `{ length: 36 }` → `character(36)`). Returns `undefined` when no framework
|
|
1140
|
+
* components are supplied, so callers can omit the option entirely.
|
|
1141
|
+
*/
|
|
1142
|
+
function buildNativeTypeExpander(frameworkComponents) {
|
|
1143
|
+
if (!frameworkComponents) return;
|
|
1144
|
+
const codecHooks = extractCodecControlHooks(frameworkComponents);
|
|
1145
|
+
return (input) => {
|
|
1146
|
+
if (!input.typeParams) return input.nativeType;
|
|
1147
|
+
if (!input.codecId) return input.nativeType;
|
|
1148
|
+
const hooks = codecHooks.get(input.codecId);
|
|
1149
|
+
if (!hooks?.expandNativeType) return input.nativeType;
|
|
1150
|
+
return hooks.expandNativeType(input);
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
//#endregion
|
|
1745
1154
|
//#region src/core/migrations/plan-helpers.ts
|
|
1746
1155
|
const readOnlyEmptyObject = Object.freeze({});
|
|
1747
1156
|
function cloneRecord(value) {
|
|
@@ -1853,6 +1262,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
|
|
|
1853
1262
|
//#region src/exports/control.ts
|
|
1854
1263
|
var control_default = new SqlFamilyDescriptor();
|
|
1855
1264
|
//#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 };
|
|
1265
|
+
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, buildNativeTypeExpander, contractNamespaceToSchemaIR, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
1857
1266
|
|
|
1858
1267
|
//# sourceMappingURL=control.mjs.map
|