@prisma-next/family-sql 0.14.0-dev.4 → 0.14.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/contract-to-schema-ir-S-evq8E6.mjs +264 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
- package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +18 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +81 -943
- package/dist/control.mjs.map +1 -1
- package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
- package/dist/diff.d.mts.map +1 -0
- package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +9 -6
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +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 +80 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +334 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
- package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
- package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
- package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
- package/dist/types-CkOIJXxU.d.mts.map +1 -0
- package/package.json +23 -23
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +10 -5
- package/src/core/control-instance.ts +174 -41
- package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
- package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
- package/src/core/ir/sql-contract-serializer-base.ts +23 -55
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/migrations/contract-to-schema-ir.ts +49 -12
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/schema-differ.ts +40 -0
- package/src/core/migrations/types.ts +43 -2
- package/src/core/psl-contract-infer/printer-config.ts +0 -6
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +1 -0
- package/src/exports/diff.ts +18 -0
- package/src/exports/psl-infer.ts +36 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- 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.map +0 -1
- 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/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
- /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
- /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//#region src/core/psl-contract-infer/default-mapping.ts
|
|
2
|
+
const DEFAULT_FUNCTION_ATTRIBUTES = {
|
|
3
|
+
"autoincrement()": "@default(autoincrement())",
|
|
4
|
+
"now()": "@default(now())"
|
|
5
|
+
};
|
|
6
|
+
function mapDefault(columnDefault, options) {
|
|
7
|
+
switch (columnDefault.kind) {
|
|
8
|
+
case "literal": return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };
|
|
9
|
+
case "function": {
|
|
10
|
+
const attribute = options?.functionAttributes?.[columnDefault.expression] ?? DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ?? options?.fallbackFunctionAttribute?.(columnDefault.expression);
|
|
11
|
+
return attribute ? { attribute } : { comment: `// Raw default: ${columnDefault.expression.replace(/[\r\n]+/g, " ")}` };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function formatLiteralValue(value) {
|
|
16
|
+
if (value === null) return "null";
|
|
17
|
+
switch (typeof value) {
|
|
18
|
+
case "boolean":
|
|
19
|
+
case "number": return String(value);
|
|
20
|
+
case "string": return quoteString(value);
|
|
21
|
+
default: return quoteString(JSON.stringify(value));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function quoteString(str) {
|
|
25
|
+
return `"${escapeString(str)}"`;
|
|
26
|
+
}
|
|
27
|
+
function escapeString(str) {
|
|
28
|
+
return JSON.stringify(str).slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/psl-contract-infer/name-transforms.ts
|
|
32
|
+
const PSL_RESERVED_WORDS = new Set([
|
|
33
|
+
"model",
|
|
34
|
+
"enum",
|
|
35
|
+
"types",
|
|
36
|
+
"type",
|
|
37
|
+
"generator",
|
|
38
|
+
"datasource"
|
|
39
|
+
]);
|
|
40
|
+
const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
|
|
41
|
+
function hasSeparators(input) {
|
|
42
|
+
return /[^A-Za-z0-9]/.test(input);
|
|
43
|
+
}
|
|
44
|
+
function extractIdentifierParts(input) {
|
|
45
|
+
return input.match(IDENTIFIER_PART_PATTERN) ?? [];
|
|
46
|
+
}
|
|
47
|
+
function createSyntheticIdentifier(input) {
|
|
48
|
+
let hash = 2166136261;
|
|
49
|
+
for (const char of input) {
|
|
50
|
+
hash ^= char.codePointAt(0) ?? 0;
|
|
51
|
+
hash = Math.imul(hash, 16777619);
|
|
52
|
+
}
|
|
53
|
+
return `x${(hash >>> 0).toString(16)}`;
|
|
54
|
+
}
|
|
55
|
+
function sanitizeIdentifierCharacters(input) {
|
|
56
|
+
const sanitized = input.replace(/[^\w]/g, "");
|
|
57
|
+
return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);
|
|
58
|
+
}
|
|
59
|
+
function capitalize(word) {
|
|
60
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
61
|
+
}
|
|
62
|
+
function snakeToPascalCase(input) {
|
|
63
|
+
const parts = extractIdentifierParts(input);
|
|
64
|
+
if (parts.length === 0) return capitalize(sanitizeIdentifierCharacters(input));
|
|
65
|
+
return parts.map(capitalize).join("");
|
|
66
|
+
}
|
|
67
|
+
function snakeToCamelCase(input) {
|
|
68
|
+
const parts = extractIdentifierParts(input);
|
|
69
|
+
if (parts.length === 0) return sanitizeIdentifierCharacters(input);
|
|
70
|
+
const [firstPart = input, ...rest] = parts;
|
|
71
|
+
return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join("");
|
|
72
|
+
}
|
|
73
|
+
function needsEscaping(name) {
|
|
74
|
+
return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\d/.test(name);
|
|
75
|
+
}
|
|
76
|
+
function escapeName(name) {
|
|
77
|
+
return `_${name}`;
|
|
78
|
+
}
|
|
79
|
+
function escapeIfNeeded(name) {
|
|
80
|
+
return needsEscaping(name) ? escapeName(name) : name;
|
|
81
|
+
}
|
|
82
|
+
function toModelName(tableName) {
|
|
83
|
+
let name;
|
|
84
|
+
if (hasSeparators(tableName)) name = snakeToPascalCase(tableName);
|
|
85
|
+
else name = tableName.charAt(0).toUpperCase() + tableName.slice(1);
|
|
86
|
+
if (needsEscaping(name)) return {
|
|
87
|
+
name: escapeName(name),
|
|
88
|
+
map: tableName
|
|
89
|
+
};
|
|
90
|
+
if (name !== tableName) return {
|
|
91
|
+
name,
|
|
92
|
+
map: tableName
|
|
93
|
+
};
|
|
94
|
+
return { name };
|
|
95
|
+
}
|
|
96
|
+
function toFieldName(columnName) {
|
|
97
|
+
let name;
|
|
98
|
+
if (hasSeparators(columnName)) name = snakeToCamelCase(columnName);
|
|
99
|
+
else name = columnName.charAt(0).toLowerCase() + columnName.slice(1);
|
|
100
|
+
if (needsEscaping(name)) return {
|
|
101
|
+
name: escapeName(name),
|
|
102
|
+
map: columnName
|
|
103
|
+
};
|
|
104
|
+
if (name !== columnName) return {
|
|
105
|
+
name,
|
|
106
|
+
map: columnName
|
|
107
|
+
};
|
|
108
|
+
return { name };
|
|
109
|
+
}
|
|
110
|
+
function toEnumName(pgTypeName) {
|
|
111
|
+
let name;
|
|
112
|
+
if (hasSeparators(pgTypeName)) name = snakeToPascalCase(pgTypeName);
|
|
113
|
+
else name = pgTypeName.charAt(0).toUpperCase() + pgTypeName.slice(1);
|
|
114
|
+
if (needsEscaping(name)) return {
|
|
115
|
+
name: escapeName(name),
|
|
116
|
+
map: pgTypeName
|
|
117
|
+
};
|
|
118
|
+
if (name !== pgTypeName) return {
|
|
119
|
+
name,
|
|
120
|
+
map: pgTypeName
|
|
121
|
+
};
|
|
122
|
+
return { name };
|
|
123
|
+
}
|
|
124
|
+
function pluralize(word) {
|
|
125
|
+
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) return `${word}es`;
|
|
126
|
+
if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
|
|
127
|
+
return `${word}s`;
|
|
128
|
+
}
|
|
129
|
+
function deriveRelationFieldName(fkColumns, referencedTableName) {
|
|
130
|
+
if (fkColumns.length === 1) {
|
|
131
|
+
const [col = referencedTableName] = fkColumns;
|
|
132
|
+
const stripped = col.replace(/_id$/i, "").replace(/Id$/, "");
|
|
133
|
+
if (stripped.length > 0 && stripped !== col) return escapeIfNeeded(snakeToCamelCase(stripped));
|
|
134
|
+
return escapeIfNeeded(snakeToCamelCase(referencedTableName));
|
|
135
|
+
}
|
|
136
|
+
return escapeIfNeeded(snakeToCamelCase(referencedTableName));
|
|
137
|
+
}
|
|
138
|
+
function deriveBackRelationFieldName(childModelName, isOneToOne) {
|
|
139
|
+
const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);
|
|
140
|
+
return isOneToOne ? base : pluralize(base);
|
|
141
|
+
}
|
|
142
|
+
function toNamedTypeName(columnName) {
|
|
143
|
+
let name;
|
|
144
|
+
if (hasSeparators(columnName)) name = snakeToPascalCase(columnName);
|
|
145
|
+
else name = columnName.charAt(0).toUpperCase() + columnName.slice(1);
|
|
146
|
+
return escapeIfNeeded(name);
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/core/psl-contract-infer/raw-default-parser.ts
|
|
150
|
+
const NEXTVAL_PATTERN = /^nextval\s*\(/i;
|
|
151
|
+
const NOW_FUNCTION_PATTERN = /^(now\s*\(\s*\)|CURRENT_TIMESTAMP)$/i;
|
|
152
|
+
const CLOCK_TIMESTAMP_PATTERN = /^clock_timestamp\s*\(\s*\)$/i;
|
|
153
|
+
const TIMESTAMP_CAST_SUFFIX = /::timestamp(?:tz|\s+(?:with|without)\s+time\s+zone)?$/i;
|
|
154
|
+
const TEXT_CAST_SUFFIX = /::text$/i;
|
|
155
|
+
const NOW_LITERAL_PATTERN = /^'now'$/i;
|
|
156
|
+
const UUID_PATTERN = /^gen_random_uuid\s*\(\s*\)$/i;
|
|
157
|
+
const UUID_OSSP_PATTERN = /^uuid_generate_v4\s*\(\s*\)$/i;
|
|
158
|
+
const NULL_PATTERN = /^NULL(?:::.+)?$/i;
|
|
159
|
+
const TRUE_PATTERN = /^true$/i;
|
|
160
|
+
const FALSE_PATTERN = /^false$/i;
|
|
161
|
+
const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
|
|
162
|
+
const JSON_CAST_SUFFIX = /::jsonb?$/i;
|
|
163
|
+
const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
|
|
164
|
+
function canonicalizeTimestampDefault(expr) {
|
|
165
|
+
if (NOW_FUNCTION_PATTERN.test(expr)) return "now()";
|
|
166
|
+
if (CLOCK_TIMESTAMP_PATTERN.test(expr)) return "clock_timestamp()";
|
|
167
|
+
if (!TIMESTAMP_CAST_SUFFIX.test(expr)) return void 0;
|
|
168
|
+
let inner = expr.replace(TIMESTAMP_CAST_SUFFIX, "").trim();
|
|
169
|
+
if (inner.startsWith("(") && inner.endsWith(")")) inner = inner.slice(1, -1).trim();
|
|
170
|
+
if (NOW_FUNCTION_PATTERN.test(inner)) return "now()";
|
|
171
|
+
if (CLOCK_TIMESTAMP_PATTERN.test(inner)) return "clock_timestamp()";
|
|
172
|
+
inner = inner.replace(TEXT_CAST_SUFFIX, "").trim();
|
|
173
|
+
if (NOW_LITERAL_PATTERN.test(inner)) return "now()";
|
|
174
|
+
}
|
|
175
|
+
function parseRawDefault(rawDefault, nativeType) {
|
|
176
|
+
const trimmed = rawDefault.trim();
|
|
177
|
+
const normalizedType = nativeType?.toLowerCase();
|
|
178
|
+
if (NEXTVAL_PATTERN.test(trimmed)) return {
|
|
179
|
+
kind: "function",
|
|
180
|
+
expression: "autoincrement()"
|
|
181
|
+
};
|
|
182
|
+
const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);
|
|
183
|
+
if (canonicalTimestamp) return {
|
|
184
|
+
kind: "function",
|
|
185
|
+
expression: canonicalTimestamp
|
|
186
|
+
};
|
|
187
|
+
if (UUID_PATTERN.test(trimmed) || UUID_OSSP_PATTERN.test(trimmed)) return {
|
|
188
|
+
kind: "function",
|
|
189
|
+
expression: "gen_random_uuid()"
|
|
190
|
+
};
|
|
191
|
+
if (NULL_PATTERN.test(trimmed)) return {
|
|
192
|
+
kind: "literal",
|
|
193
|
+
value: null
|
|
194
|
+
};
|
|
195
|
+
if (TRUE_PATTERN.test(trimmed)) return {
|
|
196
|
+
kind: "literal",
|
|
197
|
+
value: true
|
|
198
|
+
};
|
|
199
|
+
if (FALSE_PATTERN.test(trimmed)) return {
|
|
200
|
+
kind: "literal",
|
|
201
|
+
value: false
|
|
202
|
+
};
|
|
203
|
+
if (NUMERIC_PATTERN.test(trimmed)) return {
|
|
204
|
+
kind: "literal",
|
|
205
|
+
value: Number(trimmed)
|
|
206
|
+
};
|
|
207
|
+
const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);
|
|
208
|
+
if (stringMatch?.[1] !== void 0) {
|
|
209
|
+
const unescaped = stringMatch[1].replace(/''/g, "'");
|
|
210
|
+
if (normalizedType === "json" || normalizedType === "jsonb") {
|
|
211
|
+
if (JSON_CAST_SUFFIX.test(trimmed)) return {
|
|
212
|
+
kind: "function",
|
|
213
|
+
expression: trimmed
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
return {
|
|
217
|
+
kind: "literal",
|
|
218
|
+
value: JSON.parse(unescaped)
|
|
219
|
+
};
|
|
220
|
+
} catch {}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
kind: "literal",
|
|
224
|
+
value: unescaped
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
kind: "function",
|
|
229
|
+
expression: trimmed
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/core/psl-contract-infer/relation-inference.ts
|
|
234
|
+
const DEFAULT_ON_DELETE = "noAction";
|
|
235
|
+
const DEFAULT_ON_UPDATE = "noAction";
|
|
236
|
+
const REFERENTIAL_ACTION_PSL = {
|
|
237
|
+
noAction: "NoAction",
|
|
238
|
+
restrict: "Restrict",
|
|
239
|
+
cascade: "Cascade",
|
|
240
|
+
setNull: "SetNull",
|
|
241
|
+
setDefault: "SetDefault"
|
|
242
|
+
};
|
|
243
|
+
function inferRelations(tables, modelNameMap) {
|
|
244
|
+
const relationsByTable = /* @__PURE__ */ new Map();
|
|
245
|
+
const fkCountByPair = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
|
|
247
|
+
const pairKey = `${table.name}→${fk.referencedTable}`;
|
|
248
|
+
fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);
|
|
249
|
+
}
|
|
250
|
+
const usedFieldNames = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const table of Object.values(tables)) {
|
|
252
|
+
const names = /* @__PURE__ */ new Set();
|
|
253
|
+
for (const col of Object.values(table.columns)) names.add(col.name);
|
|
254
|
+
usedFieldNames.set(table.name, names);
|
|
255
|
+
}
|
|
256
|
+
for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
|
|
257
|
+
const childTableName = table.name;
|
|
258
|
+
const parentTableName = fk.referencedTable;
|
|
259
|
+
const childUsed = usedFieldNames.get(childTableName);
|
|
260
|
+
const childModelName = modelNameMap.get(childTableName) ?? childTableName;
|
|
261
|
+
const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;
|
|
262
|
+
const pairKey = `${childTableName}→${parentTableName}`;
|
|
263
|
+
const isSelfRelation = childTableName === parentTableName;
|
|
264
|
+
const needsRelationName = fkCountByPair.get(pairKey) > 1 || isSelfRelation;
|
|
265
|
+
const isOneToOne = detectOneToOne(fk, table);
|
|
266
|
+
const childRelFieldName = resolveUniqueFieldName(deriveRelationFieldName(fk.columns, parentTableName), childUsed, parentModelName);
|
|
267
|
+
const relationName = needsRelationName ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation) : void 0;
|
|
268
|
+
addRelationField(relationsByTable, childTableName, buildChildRelationField(childRelFieldName, parentModelName, fk, fk.columns.some((columnName) => table.columns[columnName]?.nullable ?? false), relationName));
|
|
269
|
+
childUsed.add(childRelFieldName);
|
|
270
|
+
const parentUsed = usedFieldNames.get(parentTableName) ?? /* @__PURE__ */ new Set();
|
|
271
|
+
usedFieldNames.set(parentTableName, parentUsed);
|
|
272
|
+
const backRelFieldName = resolveUniqueFieldName(deriveBackRelationFieldName(childModelName, isOneToOne), parentUsed, childModelName);
|
|
273
|
+
addRelationField(relationsByTable, parentTableName, {
|
|
274
|
+
fieldName: backRelFieldName,
|
|
275
|
+
typeName: childModelName,
|
|
276
|
+
optional: isOneToOne,
|
|
277
|
+
list: !isOneToOne,
|
|
278
|
+
relationName
|
|
279
|
+
});
|
|
280
|
+
parentUsed.add(backRelFieldName);
|
|
281
|
+
}
|
|
282
|
+
return { relationsByTable };
|
|
283
|
+
}
|
|
284
|
+
function detectOneToOne(fk, table) {
|
|
285
|
+
const fkCols = [...fk.columns].sort();
|
|
286
|
+
if (table.primaryKey) {
|
|
287
|
+
const pkCols = [...table.primaryKey.columns].sort();
|
|
288
|
+
if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) return true;
|
|
289
|
+
}
|
|
290
|
+
for (const unique of table.uniques) {
|
|
291
|
+
const uniqueCols = [...unique.columns].sort();
|
|
292
|
+
if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) return true;
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
function deriveRelationName(fk, childRelationFieldName, parentModelName, isSelfRelation) {
|
|
297
|
+
if (fk.name) return fk.name;
|
|
298
|
+
if (isSelfRelation) return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;
|
|
299
|
+
return fk.columns.join("_");
|
|
300
|
+
}
|
|
301
|
+
function buildChildRelationField(fieldName, parentModelName, fk, optional, relationName) {
|
|
302
|
+
const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : void 0;
|
|
303
|
+
const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : void 0;
|
|
304
|
+
return {
|
|
305
|
+
fieldName,
|
|
306
|
+
typeName: parentModelName,
|
|
307
|
+
referencedTableName: fk.referencedTable,
|
|
308
|
+
optional,
|
|
309
|
+
list: false,
|
|
310
|
+
relationName,
|
|
311
|
+
fkName: fk.name,
|
|
312
|
+
fields: fk.columns,
|
|
313
|
+
references: fk.referencedColumns,
|
|
314
|
+
onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : void 0,
|
|
315
|
+
onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : void 0
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function resolveUniqueFieldName(desired, usedNames, fallbackSuffix) {
|
|
319
|
+
if (!usedNames.has(desired)) return desired;
|
|
320
|
+
const withSuffix = `${desired}${fallbackSuffix}`;
|
|
321
|
+
if (!usedNames.has(withSuffix)) return withSuffix;
|
|
322
|
+
let counter = 2;
|
|
323
|
+
while (usedNames.has(`${desired}${counter}`)) counter++;
|
|
324
|
+
return `${desired}${counter}`;
|
|
325
|
+
}
|
|
326
|
+
function addRelationField(map, tableName, field) {
|
|
327
|
+
const existing = map.get(tableName);
|
|
328
|
+
if (existing) existing.push(field);
|
|
329
|
+
else map.set(tableName, [field]);
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
export { deriveBackRelationFieldName, deriveRelationFieldName, inferRelations, mapDefault, parseRawDefault, pluralize, toEnumName, toFieldName, toModelName, toNamedTypeName };
|
|
333
|
+
|
|
334
|
+
//# sourceMappingURL=psl-infer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"psl-infer.mjs","names":[],"sources":["../src/core/psl-contract-infer/default-mapping.ts","../src/core/psl-contract-infer/name-transforms.ts","../src/core/psl-contract-infer/raw-default-parser.ts","../src/core/psl-contract-infer/relation-inference.ts"],"sourcesContent":["import type { ColumnDefault } from '@prisma-next/contract/types';\n\nconst DEFAULT_FUNCTION_ATTRIBUTES: Readonly<Record<string, string>> = {\n 'autoincrement()': '@default(autoincrement())',\n 'now()': '@default(now())',\n};\n\nexport interface DefaultMappingOptions {\n readonly functionAttributes?: Readonly<Record<string, string>>;\n readonly fallbackFunctionAttribute?: ((expression: string) => string | undefined) | undefined;\n}\n\nexport type DefaultMappingResult = { readonly attribute: string } | { readonly comment: string };\n\nexport function mapDefault(\n columnDefault: ColumnDefault,\n options?: DefaultMappingOptions,\n): DefaultMappingResult {\n switch (columnDefault.kind) {\n case 'literal':\n return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };\n case 'function': {\n const attribute =\n options?.functionAttributes?.[columnDefault.expression] ??\n DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ??\n options?.fallbackFunctionAttribute?.(columnDefault.expression);\n return attribute\n ? { attribute }\n : { comment: `// Raw default: ${columnDefault.expression.replace(/[\\r\\n]+/g, ' ')}` };\n }\n }\n}\n\nfunction formatLiteralValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n\n switch (typeof value) {\n case 'boolean':\n case 'number':\n return String(value);\n case 'string':\n return quoteString(value);\n default:\n return quoteString(JSON.stringify(value));\n }\n}\n\nfunction quoteString(str: string): string {\n return `\"${escapeString(str)}\"`;\n}\n\nfunction escapeString(str: string): string {\n return JSON.stringify(str).slice(1, -1);\n}\n","const PSL_RESERVED_WORDS = new Set(['model', 'enum', 'types', 'type', 'generator', 'datasource']);\n\nconst IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;\n\ntype NameResult = {\n readonly name: string;\n readonly map?: string;\n};\n\nfunction hasSeparators(input: string): boolean {\n return /[^A-Za-z0-9]/.test(input);\n}\n\nfunction extractIdentifierParts(input: string): string[] {\n return input.match(IDENTIFIER_PART_PATTERN) ?? [];\n}\n\nfunction createSyntheticIdentifier(input: string): string {\n let hash = 2166136261;\n\n for (const char of input) {\n hash ^= char.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n\n return `x${(hash >>> 0).toString(16)}`;\n}\n\nfunction sanitizeIdentifierCharacters(input: string): string {\n const sanitized = input.replace(/[^\\w]/g, '');\n return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);\n}\n\nfunction capitalize(word: string): string {\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\nfunction snakeToPascalCase(input: string): string {\n const parts = extractIdentifierParts(input);\n if (parts.length === 0) {\n return capitalize(sanitizeIdentifierCharacters(input));\n }\n return parts.map(capitalize).join('');\n}\n\nfunction snakeToCamelCase(input: string): string {\n const parts = extractIdentifierParts(input);\n if (parts.length === 0) {\n return sanitizeIdentifierCharacters(input);\n }\n const [firstPart = input, ...rest] = parts;\n return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join('');\n}\n\nfunction needsEscaping(name: string): boolean {\n return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\\d/.test(name);\n}\n\nfunction escapeName(name: string): string {\n return `_${name}`;\n}\n\nfunction escapeIfNeeded(name: string): string {\n return needsEscaping(name) ? escapeName(name) : name;\n}\n\nexport function toModelName(tableName: string): NameResult {\n let name: string;\n\n if (hasSeparators(tableName)) {\n name = snakeToPascalCase(tableName);\n } else {\n name = tableName.charAt(0).toUpperCase() + tableName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: tableName };\n }\n\n if (name !== tableName) {\n return { name, map: tableName };\n }\n\n return { name };\n}\n\nexport function toFieldName(columnName: string): NameResult {\n let name: string;\n\n if (hasSeparators(columnName)) {\n name = snakeToCamelCase(columnName);\n } else {\n name = columnName.charAt(0).toLowerCase() + columnName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: columnName };\n }\n\n if (name !== columnName) {\n return { name, map: columnName };\n }\n\n return { name };\n}\n\nexport function toEnumName(pgTypeName: string): NameResult {\n let name: string;\n\n if (hasSeparators(pgTypeName)) {\n name = snakeToPascalCase(pgTypeName);\n } else {\n name = pgTypeName.charAt(0).toUpperCase() + pgTypeName.slice(1);\n }\n\n if (needsEscaping(name)) {\n const escaped = escapeName(name);\n return { name: escaped, map: pgTypeName };\n }\n\n if (name !== pgTypeName) {\n return { name, map: pgTypeName };\n }\n\n return { name };\n}\n\nexport function pluralize(word: string): string {\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n ) {\n return `${word}es`;\n }\n if (word.endsWith('y') && !/[aeiou]y$/i.test(word)) {\n return `${word.slice(0, -1)}ies`;\n }\n return `${word}s`;\n}\n\nexport function deriveRelationFieldName(\n fkColumns: readonly string[],\n referencedTableName: string,\n): string {\n if (fkColumns.length === 1) {\n const [col = referencedTableName] = fkColumns;\n const stripped = col.replace(/_id$/i, '').replace(/Id$/, '');\n\n if (stripped.length > 0 && stripped !== col) {\n return escapeIfNeeded(snakeToCamelCase(stripped));\n }\n return escapeIfNeeded(snakeToCamelCase(referencedTableName));\n }\n\n return escapeIfNeeded(snakeToCamelCase(referencedTableName));\n}\n\nexport function deriveBackRelationFieldName(childModelName: string, isOneToOne: boolean): string {\n const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);\n return isOneToOne ? base : pluralize(base);\n}\n\nexport function toNamedTypeName(columnName: string): string {\n let name: string;\n\n if (hasSeparators(columnName)) {\n name = snakeToPascalCase(columnName);\n } else {\n name = columnName.charAt(0).toUpperCase() + columnName.slice(1);\n }\n\n return escapeIfNeeded(name);\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\n\nconst NEXTVAL_PATTERN = /^nextval\\s*\\(/i;\nconst NOW_FUNCTION_PATTERN = /^(now\\s*\\(\\s*\\)|CURRENT_TIMESTAMP)$/i;\nconst CLOCK_TIMESTAMP_PATTERN = /^clock_timestamp\\s*\\(\\s*\\)$/i;\nconst TIMESTAMP_CAST_SUFFIX = /::timestamp(?:tz|\\s+(?:with|without)\\s+time\\s+zone)?$/i;\nconst TEXT_CAST_SUFFIX = /::text$/i;\nconst NOW_LITERAL_PATTERN = /^'now'$/i;\nconst UUID_PATTERN = /^gen_random_uuid\\s*\\(\\s*\\)$/i;\nconst UUID_OSSP_PATTERN = /^uuid_generate_v4\\s*\\(\\s*\\)$/i;\nconst NULL_PATTERN = /^NULL(?:::.+)?$/i;\nconst TRUE_PATTERN = /^true$/i;\nconst FALSE_PATTERN = /^false$/i;\nconst NUMERIC_PATTERN = /^-?\\d+(\\.\\d+)?$/;\nconst JSON_CAST_SUFFIX = /::jsonb?$/i;\nconst STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:\"[^\"]+\"|[\\w\\s]+)(?:\\(\\d+\\))?)?$/;\n\nfunction canonicalizeTimestampDefault(expr: string): string | undefined {\n if (NOW_FUNCTION_PATTERN.test(expr)) return 'now()';\n if (CLOCK_TIMESTAMP_PATTERN.test(expr)) return 'clock_timestamp()';\n\n if (!TIMESTAMP_CAST_SUFFIX.test(expr)) return undefined;\n\n let inner = expr.replace(TIMESTAMP_CAST_SUFFIX, '').trim();\n if (inner.startsWith('(') && inner.endsWith(')')) {\n inner = inner.slice(1, -1).trim();\n }\n\n if (NOW_FUNCTION_PATTERN.test(inner)) return 'now()';\n if (CLOCK_TIMESTAMP_PATTERN.test(inner)) return 'clock_timestamp()';\n\n inner = inner.replace(TEXT_CAST_SUFFIX, '').trim();\n if (NOW_LITERAL_PATTERN.test(inner)) return 'now()';\n\n return undefined;\n}\n\nexport function parseRawDefault(\n rawDefault: string,\n nativeType?: string,\n): ColumnDefault | undefined {\n const trimmed = rawDefault.trim();\n const normalizedType = nativeType?.toLowerCase();\n\n if (NEXTVAL_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'autoincrement()' };\n }\n\n const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);\n if (canonicalTimestamp) {\n return { kind: 'function', expression: canonicalTimestamp };\n }\n\n if (UUID_PATTERN.test(trimmed) || UUID_OSSP_PATTERN.test(trimmed)) {\n return { kind: 'function', expression: 'gen_random_uuid()' };\n }\n\n if (NULL_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: null };\n }\n\n if (TRUE_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: true };\n }\n\n if (FALSE_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: false };\n }\n\n if (NUMERIC_PATTERN.test(trimmed)) {\n return { kind: 'literal', value: Number(trimmed) };\n }\n\n const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);\n if (stringMatch?.[1] !== undefined) {\n const unescaped = stringMatch[1].replace(/''/g, \"'\");\n if (normalizedType === 'json' || normalizedType === 'jsonb') {\n if (JSON_CAST_SUFFIX.test(trimmed)) {\n return { kind: 'function', expression: trimmed };\n }\n try {\n return { kind: 'literal', value: JSON.parse(unescaped) };\n } catch {\n // Fall through to the string form for malformed/non-JSON values.\n }\n }\n return { kind: 'literal', value: unescaped };\n }\n\n return { kind: 'function', expression: trimmed };\n}\n","import type { SqlForeignKeyIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';\nimport { deriveBackRelationFieldName, deriveRelationFieldName, pluralize } from './name-transforms';\nimport type { RelationField } from './printer-config';\n\nconst DEFAULT_ON_DELETE = 'noAction';\nconst DEFAULT_ON_UPDATE = 'noAction';\n\nconst REFERENTIAL_ACTION_PSL: Record<string, string> = {\n noAction: 'NoAction',\n restrict: 'Restrict',\n cascade: 'Cascade',\n setNull: 'SetNull',\n setDefault: 'SetDefault',\n};\n\nexport type InferredRelations = {\n readonly relationsByTable: ReadonlyMap<string, readonly RelationField[]>;\n};\n\nexport function inferRelations(\n tables: Record<string, SqlTableIR>,\n modelNameMap: ReadonlyMap<string, string>,\n): InferredRelations {\n const relationsByTable = new Map<string, RelationField[]>();\n\n const fkCountByPair = new Map<string, number>();\n for (const table of Object.values(tables)) {\n for (const fk of table.foreignKeys) {\n const pairKey = `${table.name}→${fk.referencedTable}`;\n fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);\n }\n }\n\n const usedFieldNames = new Map<string, Set<string>>();\n for (const table of Object.values(tables)) {\n const names = new Set<string>();\n for (const col of Object.values(table.columns)) {\n names.add(col.name);\n }\n usedFieldNames.set(table.name, names);\n }\n\n for (const table of Object.values(tables)) {\n for (const fk of table.foreignKeys) {\n const childTableName = table.name;\n const parentTableName = fk.referencedTable;\n const childUsed = usedFieldNames.get(childTableName) as Set<string>;\n const childModelName = modelNameMap.get(childTableName) ?? childTableName;\n const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;\n const pairKey = `${childTableName}→${parentTableName}`;\n const isSelfRelation = childTableName === parentTableName;\n const needsRelationName = (fkCountByPair.get(pairKey) as number) > 1 || isSelfRelation;\n\n const isOneToOne = detectOneToOne(fk, table);\n\n const childRelFieldName = resolveUniqueFieldName(\n deriveRelationFieldName(fk.columns, parentTableName),\n childUsed,\n parentModelName,\n );\n const relationName = needsRelationName\n ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation)\n : undefined;\n const childOptional = fk.columns.some(\n (columnName) => table.columns[columnName]?.nullable ?? false,\n );\n\n const childRelField = buildChildRelationField(\n childRelFieldName,\n parentModelName,\n fk,\n childOptional,\n relationName,\n );\n\n addRelationField(relationsByTable, childTableName, childRelField);\n childUsed.add(childRelFieldName);\n\n const parentUsed = usedFieldNames.get(parentTableName) ?? new Set();\n usedFieldNames.set(parentTableName, parentUsed);\n\n const backRelFieldName = resolveUniqueFieldName(\n deriveBackRelationFieldName(childModelName, isOneToOne),\n parentUsed,\n childModelName,\n );\n\n const backRelField: RelationField = {\n fieldName: backRelFieldName,\n typeName: childModelName,\n optional: isOneToOne,\n list: !isOneToOne,\n relationName,\n };\n\n addRelationField(relationsByTable, parentTableName, backRelField);\n parentUsed.add(backRelFieldName);\n }\n }\n\n return { relationsByTable };\n}\n\nfunction detectOneToOne(fk: SqlForeignKeyIR, table: SqlTableIR): boolean {\n const fkCols = [...fk.columns].sort();\n\n if (table.primaryKey) {\n const pkCols = [...table.primaryKey.columns].sort();\n if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) {\n return true;\n }\n }\n\n for (const unique of table.uniques) {\n const uniqueCols = [...unique.columns].sort();\n if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction deriveRelationName(\n fk: SqlForeignKeyIR,\n childRelationFieldName: string,\n parentModelName: string,\n isSelfRelation: boolean,\n): string {\n if (fk.name) {\n return fk.name;\n }\n if (isSelfRelation) {\n return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;\n }\n return fk.columns.join('_');\n}\n\nfunction buildChildRelationField(\n fieldName: string,\n parentModelName: string,\n fk: SqlForeignKeyIR,\n optional: boolean,\n relationName?: string,\n): RelationField {\n const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : undefined;\n const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : undefined;\n\n return {\n fieldName,\n typeName: parentModelName,\n referencedTableName: fk.referencedTable,\n optional,\n list: false,\n relationName,\n fkName: fk.name,\n fields: fk.columns,\n references: fk.referencedColumns,\n onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : undefined,\n onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : undefined,\n };\n}\n\nfunction resolveUniqueFieldName(\n desired: string,\n usedNames: ReadonlySet<string>,\n fallbackSuffix: string,\n): string {\n if (!usedNames.has(desired)) {\n return desired;\n }\n\n const withSuffix = `${desired}${fallbackSuffix}`;\n if (!usedNames.has(withSuffix)) {\n return withSuffix;\n }\n\n let counter = 2;\n while (usedNames.has(`${desired}${counter}`)) {\n counter++;\n }\n return `${desired}${counter}`;\n}\n\nfunction addRelationField(\n map: Map<string, RelationField[]>,\n tableName: string,\n field: RelationField,\n): void {\n const existing = map.get(tableName);\n if (existing) {\n existing.push(field);\n } else {\n map.set(tableName, [field]);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
2
|
+
import { hydrateNamespaceEntities } from "@prisma-next/framework-components/ir";
|
|
2
3
|
import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
|
|
3
4
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
|
-
import { NamespaceBase, UNBOUND_NAMESPACE_ID, hydrateNamespaceEntities } from "@prisma-next/framework-components/ir";
|
|
5
5
|
import { ContractValidationError } from "@prisma-next/contract/contract-validation-error";
|
|
6
|
-
import { isPlainRecord } from "@prisma-next/contract/is-plain-record";
|
|
6
|
+
import { isPlainRecord as isPlainRecord$1 } from "@prisma-next/contract/is-plain-record";
|
|
7
7
|
import { composeSqlEntityKinds } from "@prisma-next/sql-contract/entity-kinds";
|
|
8
|
-
import { SqlStorage,
|
|
8
|
+
import { SqlStorage, isMaterializedSqlNamespace } from "@prisma-next/sql-contract/types";
|
|
9
9
|
import { createSqlContractSchema, validateSqlContractFully } from "@prisma-next/sql-contract/validators";
|
|
10
10
|
import { type } from "arktype";
|
|
11
11
|
//#region src/core/ir/sql-contract-serializer-base.ts
|
|
@@ -65,39 +65,34 @@ var SqlContractSerializerBase = class {
|
|
|
65
65
|
const hydratedTypes = types !== void 0 ? Object.fromEntries(Object.entries(types).map(([name, entry]) => [name, this.hydrateStorageTypeEntry(entry)])) : void 0;
|
|
66
66
|
const rawNamespaces = validated.storage.namespaces;
|
|
67
67
|
if (rawNamespaces === void 0) throw new ContractValidationError("Contract storage.namespaces is required after structural validation", "structural");
|
|
68
|
-
const hydratedNamespaces = this.hydrateSqlNamespaceMap(rawNamespaces);
|
|
69
|
-
const withInjectedUnbound = this.defaultNamespaceId === UNBOUND_NAMESPACE_ID ? {
|
|
70
|
-
...hydratedNamespaces,
|
|
71
|
-
[UNBOUND_NAMESPACE_ID]: hydratedNamespaces[UNBOUND_NAMESPACE_ID] ?? SqlUnboundNamespace.instance
|
|
72
|
-
} : hydratedNamespaces;
|
|
68
|
+
const hydratedNamespaces = this.hydrateSqlNamespaceMap(blindCast(rawNamespaces));
|
|
73
69
|
return {
|
|
74
70
|
...validated,
|
|
75
71
|
storage: new SqlStorage({
|
|
76
72
|
storageHash: validated.storage.storageHash,
|
|
77
73
|
...ifDefined("types", hydratedTypes),
|
|
78
|
-
namespaces: blindCast(
|
|
74
|
+
namespaces: blindCast(hydratedNamespaces)
|
|
79
75
|
})
|
|
80
76
|
};
|
|
81
77
|
}
|
|
82
78
|
hydrateSqlNamespaceMap(namespaces) {
|
|
83
79
|
return Object.fromEntries(Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {
|
|
84
80
|
const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);
|
|
85
|
-
|
|
81
|
+
if (!isMaterializedSqlNamespace(namespaceHydrated)) throw new Error(`Target serializer bug: hydrateSqlNamespaceEntry for namespace "${nsId}" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`);
|
|
82
|
+
return [nsId, namespaceHydrated];
|
|
86
83
|
}));
|
|
87
84
|
}
|
|
88
85
|
hydrateSqlNamespaceEntry(nsId, raw) {
|
|
89
|
-
|
|
90
|
-
const rawRecord = isPlainRecord(raw) ? raw : {};
|
|
91
|
-
const id = typeof rawRecord["id"] === "string" ? rawRecord["id"] : nsId;
|
|
86
|
+
const id = typeof raw["id"] === "string" ? raw["id"] : nsId;
|
|
92
87
|
const parsed = NamespaceRawSchema({
|
|
93
|
-
...
|
|
88
|
+
...raw,
|
|
94
89
|
id
|
|
95
90
|
});
|
|
96
91
|
if (parsed instanceof type.errors) throw new ContractValidationError(`Namespace hydration failed: ${parsed.map((p) => p.message).join("; ")}`, "structural");
|
|
97
92
|
const entriesRaw = parsed.entries;
|
|
98
|
-
const rawEntriesMap = isPlainRecord(entriesRaw) ? entriesRaw : {};
|
|
93
|
+
const rawEntriesMap = isPlainRecord$1(entriesRaw) ? entriesRaw : {};
|
|
99
94
|
const entriesInput = {};
|
|
100
|
-
for (const [key, innerMap] of Object.entries(rawEntriesMap)) entriesInput[key] = isPlainRecord(innerMap) ? innerMap : Object.freeze({});
|
|
95
|
+
for (const [key, innerMap] of Object.entries(rawEntriesMap)) entriesInput[key] = isPlainRecord$1(innerMap) ? innerMap : Object.freeze({});
|
|
101
96
|
const entriesOutput = hydrateNamespaceEntities(entriesInput, this.entryKinds, "fail", id);
|
|
102
97
|
if (!Object.hasOwn(entriesOutput, "table")) entriesOutput["table"] = {};
|
|
103
98
|
return blindCast({
|
|
@@ -127,16 +122,18 @@ var SqlContractSerializerBase = class {
|
|
|
127
122
|
* codec-typed envelope wire a target-specific subclass with a populated
|
|
128
123
|
* registry (see Postgres). Family-level call sites instantiate this
|
|
129
124
|
* default directly when no target serializer is supplied.
|
|
125
|
+
*
|
|
126
|
+
* Because this serializer has no target concretion, deserialization of
|
|
127
|
+
* contracts that include namespace entries from JSON will throw unless
|
|
128
|
+
* the caller provides pre-hydrated `NamespaceBase` instances. Production
|
|
129
|
+
* paths always supply a target-specific serializer.
|
|
130
130
|
*/
|
|
131
131
|
var SqlContractSerializer = class extends SqlContractSerializerBase {
|
|
132
132
|
constructor() {
|
|
133
133
|
super(/* @__PURE__ */ new Map());
|
|
134
134
|
}
|
|
135
|
-
get defaultNamespaceId() {
|
|
136
|
-
return UNBOUND_NAMESPACE_ID;
|
|
137
|
-
}
|
|
138
135
|
};
|
|
139
136
|
//#endregion
|
|
140
137
|
export { SqlContractSerializerBase as n, SqlContractSerializer as t };
|
|
141
138
|
|
|
142
|
-
//# sourceMappingURL=sql-contract-serializer-
|
|
139
|
+
//# sourceMappingURL=sql-contract-serializer-CYtXg_zs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-contract-serializer-CYtXg_zs.mjs","names":["isPlainRecord"],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts"],"sourcesContent":["import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';\nimport { isPlainRecord } from '@prisma-next/contract/is-plain-record';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ContractSerializer } from '@prisma-next/framework-components/control';\nimport {\n type AnyEntityKindDescriptor,\n hydrateNamespaceEntities,\n type Namespace,\n} from '@prisma-next/framework-components/ir';\nimport { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';\nimport { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';\nimport {\n isMaterializedSqlNamespace,\n type SqlNamespaceInput,\n SqlStorage,\n type SqlStorageInput,\n type SqlStorageTypeEntry,\n} from '@prisma-next/sql-contract/types';\nimport {\n createSqlContractSchema,\n validateSqlContractFully,\n} from '@prisma-next/sql-contract/validators';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject } from '@prisma-next/utils/json';\nimport { type Type, type } from 'arktype';\n\nconst NamespaceRawSchema = type({\n id: 'string',\n 'kind?': 'string',\n entries: type({\n '+': 'ignore',\n }),\n});\n\nexport type SqlEntityHydrationFactory = (entry: unknown) => unknown;\n\n/**\n * SQL family `ContractSerializer` abstract base. Carries the SQL-shared\n * deserialization pipeline:\n *\n * 1. `parseSqlContractStructure` validates the on-disk JSON envelope\n * against the SQL contract arktype schema (`validateSqlContractFully`)\n * and returns the validated flat-data shape.\n * 2. `hydrateSqlStorage` walks the validated `storage` subtree and\n * constructs the family-shared SQL Contract IR class hierarchy\n * (`SqlStorage` -> `StorageTable` -> `StorageColumn` / `PrimaryKey`\n * / …). The rest of the contract envelope is JSON-clean primitive\n * data and passes through unchanged.\n * 3. `constructTargetContract` is the target-specific extension hook;\n * defaults to identity. Targets that need to attach target-only\n * fields (e.g. target-specific derived storage fields) override it.\n *\n * Default `serializeContract` is identity over the contract — concrete\n * SQL targets ship JSON-clean class instances, so the contract value\n * can be stringified directly. The non-enumerable family-level `kind`\n * discriminator on `SqlNode` instances stays out of the persisted\n * envelope automatically. Targets that need to canonicalize on the way\n * out (key ordering, dropping computed-only fields) override\n * `serializeContract` directly.\n */\nexport abstract class SqlContractSerializerBase<TContract extends Contract<SqlStorage>>\n implements ContractSerializer<TContract>\n{\n private readonly contractSchema: Type<unknown> | undefined;\n private readonly entryKinds: ReadonlyMap<string, AnyEntityKindDescriptor>;\n\n constructor(\n protected readonly entityHydrationRegistry: ReadonlyMap<\n string,\n SqlEntityHydrationFactory\n > = new Map(),\n packEntityKinds: readonly AnyEntityKindDescriptor[] = [],\n ) {\n this.entryKinds = composeSqlEntityKinds(packEntityKinds);\n this.contractSchema =\n packEntityKinds.length > 0 ? createSqlContractSchema(this.entryKinds) : undefined;\n }\n\n deserializeContract<T extends TContract = TContract>(json: unknown): T {\n const validated = this.parseSqlContractStructure(json);\n const hydrated = this.hydrateSqlStorage(validated);\n return this.constructTargetContract(hydrated) as T;\n }\n\n serializeContract(contract: TContract): JsonObject {\n return contract as unknown as JsonObject;\n }\n\n shouldPreserveEmpty = sqlContractCanonicalizationHooks.shouldPreserveEmpty;\n\n sortStorage = sqlContractCanonicalizationHooks.sortStorage;\n\n protected parseSqlContractStructure(json: unknown): Contract<SqlStorage> {\n return validateSqlContractFully<Contract<SqlStorage>>(\n json,\n this.contractSchema !== undefined ? { contractSchema: this.contractSchema } : undefined,\n );\n }\n\n protected hydrateSqlStorage(validated: Contract<SqlStorage>): Contract<SqlStorage> {\n const types = validated.storage.types;\n const hydratedTypes =\n types !== undefined\n ? Object.fromEntries(\n Object.entries(types).map(([name, entry]) => [\n name,\n this.hydrateStorageTypeEntry(entry),\n ]),\n )\n : undefined;\n\n const rawNamespaces = validated.storage.namespaces;\n if (rawNamespaces === undefined) {\n throw new ContractValidationError(\n 'Contract storage.namespaces is required after structural validation',\n 'structural',\n );\n }\n const hydratedNamespaces = this.hydrateSqlNamespaceMap(\n blindCast<\n Readonly<Record<string, Record<string, unknown>>>,\n 'parseSqlContractStructure validated raw JSON; namespace entries are plain objects, not SqlNamespace instances.'\n >(rawNamespaces),\n );\n\n return {\n ...validated,\n storage: new SqlStorage({\n storageHash: validated.storage.storageHash,\n ...ifDefined('types', hydratedTypes),\n namespaces: blindCast<\n SqlStorageInput['namespaces'],\n 'hydrateSqlNamespaceMap builds each namespace through the target serializer override, so every value is a SqlNamespace; the framework return type only promises the base Namespace.'\n >(hydratedNamespaces),\n }),\n };\n }\n\n protected hydrateSqlNamespaceMap(\n namespaces: Readonly<Record<string, Record<string, unknown>>>,\n ): Readonly<Record<string, Namespace>> {\n return Object.fromEntries(\n Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {\n const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);\n if (!isMaterializedSqlNamespace(namespaceHydrated)) {\n throw new Error(\n `Target serializer bug: hydrateSqlNamespaceEntry for namespace \"${nsId}\" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`,\n );\n }\n return [nsId, namespaceHydrated];\n }),\n );\n }\n\n protected hydrateSqlNamespaceEntry(\n nsId: string,\n raw: Record<string, unknown>,\n ): Namespace | SqlNamespaceInput {\n const id = typeof raw['id'] === 'string' ? raw['id'] : nsId;\n const parsed = NamespaceRawSchema({ ...raw, id });\n if (parsed instanceof type.errors) {\n const messages = parsed.map((p: { message: string }) => p.message).join('; ');\n throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');\n }\n const entriesRaw = parsed.entries;\n const rawEntriesMap = isPlainRecord(entriesRaw) ? entriesRaw : {};\n\n const entriesInput: Record<string, Readonly<Record<string, unknown>>> = {};\n for (const [key, innerMap] of Object.entries(rawEntriesMap)) {\n entriesInput[key] = isPlainRecord(innerMap) ? innerMap : Object.freeze({});\n }\n\n const entriesOutput = hydrateNamespaceEntities(entriesInput, this.entryKinds, 'fail', id);\n\n // Always ensure a 'table' key is present (may be empty).\n if (!Object.hasOwn(entriesOutput, 'table')) {\n entriesOutput['table'] = {};\n }\n\n return blindCast<\n SqlNamespaceInput,\n 'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the SqlNamespaceInput the target createNamespace consumes.'\n >({\n id,\n entries: entriesOutput,\n });\n }\n\n protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {\n if (typeof entry !== 'object' || entry === null) {\n return entry;\n }\n const kind = (entry as { kind?: unknown }).kind;\n if (typeof kind !== 'string') {\n return entry;\n }\n const factory = this.entityHydrationRegistry.get(kind);\n if (factory === undefined) {\n return entry;\n }\n return blindCast<\n SqlStorageTypeEntry,\n 'entity registry factory returns SqlStorageTypeEntry for storage.types entries'\n >(factory(entry));\n }\n\n protected constructTargetContract(hydrated: Contract<SqlStorage>): TContract {\n return hydrated as TContract;\n }\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport { SqlContractSerializerBase } from './sql-contract-serializer-base';\n\n/**\n * Default SQL family `ContractSerializer` concretion. Inherits the\n * full SQL-shared deserialization pipeline (structural validation +\n * IR-class hydration) without pack-registered `storage.types`\n * hydration factories — targets that emit polymorphic JSON outside the\n * codec-typed envelope wire a target-specific subclass with a populated\n * registry (see Postgres). Family-level call sites instantiate this\n * default directly when no target serializer is supplied.\n *\n * Because this serializer has no target concretion, deserialization of\n * contracts that include namespace entries from JSON will throw unless\n * the caller provides pre-hydrated `NamespaceBase` instances. Production\n * paths always supply a target-specific serializer.\n */\nexport class SqlContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {\n constructor() {\n super(new Map());\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { ColumnDefault, Contract } from "@prisma-next/contract/types";
|
|
1
2
|
import { OperationContext, VerifyDatabaseSchemaResult } from "@prisma-next/framework-components/control";
|
|
2
3
|
import { SqlStorage } from "@prisma-next/sql-contract/types";
|
|
3
|
-
import {
|
|
4
|
-
import { SqlSchemaIR } from "@prisma-next/sql-schema-ir/types";
|
|
4
|
+
import { SqlSchemaIR, SqlSchemaIRNode } from "@prisma-next/sql-schema-ir/types";
|
|
5
5
|
import { TargetBoundComponentDescriptor } from "@prisma-next/framework-components/components";
|
|
6
6
|
|
|
7
|
-
//#region src/core/
|
|
7
|
+
//#region src/core/diff/sql-schema-diff.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* Function type for normalizing raw database default expressions into ColumnDefault.
|
|
10
10
|
* Target-specific implementations handle database dialect differences.
|
|
@@ -49,6 +49,15 @@ interface VerifySqlSchemaOptions {
|
|
|
49
49
|
* with contract native types (e.g., Postgres 'varchar' → 'character varying').
|
|
50
50
|
*/
|
|
51
51
|
readonly normalizeNativeType?: NativeTypeNormalizer;
|
|
52
|
+
/**
|
|
53
|
+
* When set, only the contract tables in these namespace ids are checked
|
|
54
|
+
* against `schema` (the matching actual namespace node). The full contract is
|
|
55
|
+
* still consulted for cross-namespace value-set / control-policy resolution.
|
|
56
|
+
* Used by the multi-schema verify, which pairs each contract namespace to its
|
|
57
|
+
* own actual node. Absent ⇒ all contract namespaces are checked against the
|
|
58
|
+
* single (flat) `schema` — the single-schema / SQLite path.
|
|
59
|
+
*/
|
|
60
|
+
readonly restrictToNamespaceIds?: ReadonlySet<string>;
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
63
|
* Verifies that a SqlSchemaIR matches a Contract.
|
|
@@ -61,6 +70,36 @@ interface VerifySqlSchemaOptions {
|
|
|
61
70
|
* @returns VerifyDatabaseSchemaResult with verification tree and issues
|
|
62
71
|
*/
|
|
63
72
|
declare function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabaseSchemaResult;
|
|
73
|
+
/**
|
|
74
|
+
* The single per-namespace-paired relational verify shared by the migration
|
|
75
|
+
* planner and the family schema verify — there is exactly one such operation.
|
|
76
|
+
*
|
|
77
|
+
* Each contract namespace is paired to the introspected namespace node holding
|
|
78
|
+
* the same DDL schema, then `verifySqlSchema` checks that namespace's tables
|
|
79
|
+
* against the matching actual node (a contract table under `auth` is only ever
|
|
80
|
+
* looked up in the `auth` actual node, so a multi-schema database no longer
|
|
81
|
+
* reports tables in other schemas as missing). The full contract is passed every
|
|
82
|
+
* time — `restrictToNamespaceIds` scopes only which tables are checked, so
|
|
83
|
+
* cross-namespace value-set / control-policy resolution is unaffected.
|
|
84
|
+
*
|
|
85
|
+
* The DDL schema of each contract namespace is read from a single-namespace
|
|
86
|
+
* expected projection (`buildExpectedSchema`), which both callers already build
|
|
87
|
+
* the same way. Empty contract namespaces verify nothing and are skipped.
|
|
88
|
+
* Single-schema (one namespace) and SQLite's flat schema are one pairing —
|
|
89
|
+
* byte-identical to the prior per-node verify.
|
|
90
|
+
*/
|
|
91
|
+
declare function verifySqlSchemaTree(options: {
|
|
92
|
+
readonly contract: Contract<SqlStorage>;
|
|
93
|
+
readonly actualSchema: SqlSchemaIRNode;
|
|
94
|
+
readonly buildExpectedSchema: (contract: Contract<SqlStorage>) => SqlSchemaIRNode;
|
|
95
|
+
readonly strict: boolean;
|
|
96
|
+
readonly typeMetadataRegistry: ReadonlyMap<string, {
|
|
97
|
+
nativeType?: string;
|
|
98
|
+
}>;
|
|
99
|
+
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
100
|
+
readonly normalizeDefault?: DefaultNormalizer;
|
|
101
|
+
readonly normalizeNativeType?: NativeTypeNormalizer;
|
|
102
|
+
}): VerifyDatabaseSchemaResult;
|
|
64
103
|
//#endregion
|
|
65
|
-
export { verifySqlSchema as i, NativeTypeNormalizer as n, VerifySqlSchemaOptions as r, DefaultNormalizer as t };
|
|
66
|
-
//# sourceMappingURL=
|
|
104
|
+
export { verifySqlSchemaTree as a, verifySqlSchema as i, NativeTypeNormalizer as n, VerifySqlSchemaOptions as r, DefaultNormalizer as t };
|
|
105
|
+
//# sourceMappingURL=sql-schema-diff-6z36dZt6.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-schema-diff-6z36dZt6.d.mts","names":[],"sources":["../src/core/diff/sql-schema-diff.ts"],"mappings":";;;;;;;;;;AA4EkB;KAHN,iBAAA,IACV,UAAA,UACA,UAAA,aACG,aAAa;;;;AAOoC;AAKtD;KALY,oBAAA,IAAwB,UAAkB;;;;UAKrC,sBAAA;EAQI;EAAA,SANV,QAAA,EAAU,QAAA,CAAS,UAAA;EAagB;EAAA,SAXnC,MAAA,EAAQ,WAAA;EAiBW;EAAA,SAfnB,MAAA;EA8ByB;EAAA,SA5BzB,OAAA,GAAU,gBAAA;EA4B0B;EAAA,SA1BpC,oBAAA,EAAsB,WAAA;IAAsB,UAAA;EAAA;EAN5C;;;;EAAA,SAWA,mBAAA,EAAqB,aAAA,CAAc,8BAAA;EALnC;;;;;EAAA,SAWA,gBAAA,GAAmB,iBAAA;EAAnB;;;;;EAAA,SAMA,mBAAA,GAAsB,oBAAA;EASc;AAAA;AAa/C;;;;;;EAb+C,SAApC,sBAAA,GAAyB,WAAA;AAAA;AAawD;AAkyC5F;;;;;;;;;AAlyC4F,iBAA5E,eAAA,CAAgB,OAAA,EAAS,sBAAA,GAAyB,0BAA0B;;;;;;;;;;;;;;;;;;;iBAkyC5E,mBAAA,CAAoB,OAAA;EAAA,SACzB,QAAA,EAAU,QAAA,CAAS,UAAA;EAAA,SACnB,YAAA,EAAc,eAAA;EAAA,SACd,mBAAA,GAAsB,QAAA,EAAU,QAAA,CAAS,UAAA,MAAgB,eAAA;EAAA,SACzD,MAAA;EAAA,SACA,oBAAA,EAAsB,WAAA;IAAsB,UAAA;EAAA;EAAA,SAC5C,mBAAA,EAAqB,aAAA,CAAc,8BAAA;EAAA,SACnC,gBAAA,GAAmB,iBAAA;EAAA,SACnB,mBAAA,GAAsB,oBAAA;AAAA,IAC7B,0BAAA"}
|