@prisma-next/sql-contract-psl 0.3.0-dev.54 → 0.3.0-dev.63
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/LICENSE +201 -0
- package/README.md +24 -0
- package/dist/index.d.mts +40 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/interpreter-IXr5c7s7.mjs +1376 -0
- package/dist/interpreter-IXr5c7s7.mjs.map +1 -0
- package/dist/provider.d.mts +1 -1
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +4 -3
- package/dist/provider.mjs.map +1 -1
- package/package.json +8 -8
- package/src/default-function-registry.ts +510 -0
- package/src/interpreter.ts +629 -67
- package/src/provider.ts +3 -1
- package/dist/interpreter-_6-Xk1_m.mjs +0 -661
- package/dist/interpreter-_6-Xk1_m.mjs.map +0 -1
package/src/provider.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import type { ContractConfig } from '@prisma-next/config/config-types';
|
|
2
3
|
import type { TargetPackRef } from '@prisma-next/contract/framework-components';
|
|
3
|
-
import type { ContractConfig } from '@prisma-next/core-control-plane/config-types';
|
|
4
4
|
import { parsePslDocument } from '@prisma-next/psl-parser';
|
|
5
5
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
6
6
|
import { notOk } from '@prisma-next/utils/result';
|
|
7
7
|
import { resolve } from 'pathe';
|
|
8
|
+
import { createBuiltinDefaultFunctionRegistry } from './default-function-registry';
|
|
8
9
|
import { interpretPslDocumentToSqlContractIR } from './interpreter';
|
|
9
10
|
|
|
10
11
|
export interface PrismaContractOptions {
|
|
@@ -56,6 +57,7 @@ export function prismaContract(
|
|
|
56
57
|
document,
|
|
57
58
|
...ifDefined('target', options?.target),
|
|
58
59
|
...ifDefined('composedExtensionPacks', options?.composedExtensionPacks),
|
|
60
|
+
defaultFunctionRegistry: createBuiltinDefaultFunctionRegistry(),
|
|
59
61
|
});
|
|
60
62
|
},
|
|
61
63
|
...ifDefined('output', options?.output),
|
|
@@ -1,661 +0,0 @@
|
|
|
1
|
-
import { defineContract } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
2
|
-
import { notOk, ok } from "@prisma-next/utils/result";
|
|
3
|
-
|
|
4
|
-
//#region src/interpreter.ts
|
|
5
|
-
const DEFAULT_POSTGRES_TARGET = {
|
|
6
|
-
kind: "target",
|
|
7
|
-
familyId: "sql",
|
|
8
|
-
targetId: "postgres",
|
|
9
|
-
id: "postgres",
|
|
10
|
-
version: "0.0.1",
|
|
11
|
-
capabilities: {}
|
|
12
|
-
};
|
|
13
|
-
const SCALAR_COLUMN_MAP = {
|
|
14
|
-
String: {
|
|
15
|
-
codecId: "pg/text@1",
|
|
16
|
-
nativeType: "text"
|
|
17
|
-
},
|
|
18
|
-
Boolean: {
|
|
19
|
-
codecId: "pg/bool@1",
|
|
20
|
-
nativeType: "bool"
|
|
21
|
-
},
|
|
22
|
-
Int: {
|
|
23
|
-
codecId: "pg/int4@1",
|
|
24
|
-
nativeType: "int4"
|
|
25
|
-
},
|
|
26
|
-
BigInt: {
|
|
27
|
-
codecId: "pg/int8@1",
|
|
28
|
-
nativeType: "int8"
|
|
29
|
-
},
|
|
30
|
-
Float: {
|
|
31
|
-
codecId: "pg/float8@1",
|
|
32
|
-
nativeType: "float8"
|
|
33
|
-
},
|
|
34
|
-
Decimal: {
|
|
35
|
-
codecId: "pg/numeric@1",
|
|
36
|
-
nativeType: "numeric"
|
|
37
|
-
},
|
|
38
|
-
DateTime: {
|
|
39
|
-
codecId: "pg/timestamptz@1",
|
|
40
|
-
nativeType: "timestamptz"
|
|
41
|
-
},
|
|
42
|
-
Json: {
|
|
43
|
-
codecId: "pg/jsonb@1",
|
|
44
|
-
nativeType: "jsonb"
|
|
45
|
-
},
|
|
46
|
-
Bytes: {
|
|
47
|
-
codecId: "pg/bytea@1",
|
|
48
|
-
nativeType: "bytea"
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
const REFERENTIAL_ACTION_MAP = {
|
|
52
|
-
NoAction: "noAction",
|
|
53
|
-
Restrict: "restrict",
|
|
54
|
-
Cascade: "cascade",
|
|
55
|
-
SetNull: "setNull",
|
|
56
|
-
SetDefault: "setDefault",
|
|
57
|
-
noAction: "noAction",
|
|
58
|
-
restrict: "restrict",
|
|
59
|
-
cascade: "cascade",
|
|
60
|
-
setNull: "setNull",
|
|
61
|
-
setDefault: "setDefault"
|
|
62
|
-
};
|
|
63
|
-
function lowerFirst(value) {
|
|
64
|
-
if (value.length === 0) return value;
|
|
65
|
-
return value[0]?.toLowerCase() + value.slice(1);
|
|
66
|
-
}
|
|
67
|
-
function getAttribute(attributes, name) {
|
|
68
|
-
return attributes.find((attribute) => attribute.name === name);
|
|
69
|
-
}
|
|
70
|
-
function getNamedArgument(attribute, name) {
|
|
71
|
-
const entry = attribute.args.find((arg) => arg.kind === "named" && arg.name === name);
|
|
72
|
-
if (!entry || entry.kind !== "named") return;
|
|
73
|
-
return entry.value;
|
|
74
|
-
}
|
|
75
|
-
function getPositionalArgument(attribute, index = 0) {
|
|
76
|
-
const entry = attribute.args.filter((arg) => arg.kind === "positional")[index];
|
|
77
|
-
if (!entry || entry.kind !== "positional") return;
|
|
78
|
-
return entry.value;
|
|
79
|
-
}
|
|
80
|
-
function unquoteStringLiteral(value) {
|
|
81
|
-
const trimmed = value.trim();
|
|
82
|
-
const match = trimmed.match(/^(['"])(.*)\1$/);
|
|
83
|
-
if (!match) return trimmed;
|
|
84
|
-
return match[2] ?? "";
|
|
85
|
-
}
|
|
86
|
-
function parseQuotedStringLiteral(value) {
|
|
87
|
-
const match = value.trim().match(/^(['"])(.*)\1$/);
|
|
88
|
-
if (!match) return;
|
|
89
|
-
return match[2] ?? "";
|
|
90
|
-
}
|
|
91
|
-
function parseFieldList(value) {
|
|
92
|
-
const trimmed = value.trim();
|
|
93
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return;
|
|
94
|
-
return trimmed.slice(1, -1).split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
95
|
-
}
|
|
96
|
-
function parseDefaultValueExpression(expression) {
|
|
97
|
-
const trimmed = expression.trim();
|
|
98
|
-
if (trimmed === "autoincrement()") return {
|
|
99
|
-
kind: "function",
|
|
100
|
-
expression: "autoincrement()"
|
|
101
|
-
};
|
|
102
|
-
if (trimmed === "now()") return {
|
|
103
|
-
kind: "function",
|
|
104
|
-
expression: "now()"
|
|
105
|
-
};
|
|
106
|
-
if (trimmed === "true" || trimmed === "false") return {
|
|
107
|
-
kind: "literal",
|
|
108
|
-
value: trimmed === "true"
|
|
109
|
-
};
|
|
110
|
-
const numericValue = Number(trimmed);
|
|
111
|
-
if (!Number.isNaN(numericValue) && trimmed.length > 0 && !/^(['"]).*\1$/.test(trimmed)) return {
|
|
112
|
-
kind: "literal",
|
|
113
|
-
value: numericValue
|
|
114
|
-
};
|
|
115
|
-
if (/^(['"]).*\1$/.test(trimmed)) return {
|
|
116
|
-
kind: "literal",
|
|
117
|
-
value: unquoteStringLiteral(trimmed)
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function parseMapName(input) {
|
|
121
|
-
if (!input.attribute) return input.defaultValue;
|
|
122
|
-
const value = getPositionalArgument(input.attribute);
|
|
123
|
-
if (!value) {
|
|
124
|
-
input.diagnostics.push({
|
|
125
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
126
|
-
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
127
|
-
sourceId: input.sourceId,
|
|
128
|
-
span: input.attribute.span
|
|
129
|
-
});
|
|
130
|
-
return input.defaultValue;
|
|
131
|
-
}
|
|
132
|
-
const parsed = parseQuotedStringLiteral(value);
|
|
133
|
-
if (parsed === void 0) {
|
|
134
|
-
input.diagnostics.push({
|
|
135
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
136
|
-
message: `${input.entityLabel} @map requires a positional quoted string literal argument`,
|
|
137
|
-
sourceId: input.sourceId,
|
|
138
|
-
span: input.attribute.span
|
|
139
|
-
});
|
|
140
|
-
return input.defaultValue;
|
|
141
|
-
}
|
|
142
|
-
return parsed;
|
|
143
|
-
}
|
|
144
|
-
function parsePgvectorLength(input) {
|
|
145
|
-
const namedLength = getNamedArgument(input.attribute, "length");
|
|
146
|
-
const namedDim = getNamedArgument(input.attribute, "dim");
|
|
147
|
-
const positional = getPositionalArgument(input.attribute);
|
|
148
|
-
const raw = namedLength ?? namedDim ?? positional;
|
|
149
|
-
if (!raw) {
|
|
150
|
-
input.diagnostics.push({
|
|
151
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
152
|
-
message: "@pgvector.column requires length/dim argument",
|
|
153
|
-
sourceId: input.sourceId,
|
|
154
|
-
span: input.attribute.span
|
|
155
|
-
});
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const parsed = Number(unquoteStringLiteral(raw));
|
|
159
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
160
|
-
input.diagnostics.push({
|
|
161
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
162
|
-
message: "@pgvector.column length/dim must be a positive integer",
|
|
163
|
-
sourceId: input.sourceId,
|
|
164
|
-
span: input.attribute.span
|
|
165
|
-
});
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
return parsed;
|
|
169
|
-
}
|
|
170
|
-
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors) {
|
|
171
|
-
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
172
|
-
if (namedTypeDescriptors.has(field.typeName)) return namedTypeDescriptors.get(field.typeName);
|
|
173
|
-
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
174
|
-
return SCALAR_COLUMN_MAP[field.typeName];
|
|
175
|
-
}
|
|
176
|
-
function collectResolvedFields(model, mapping, enumTypeDescriptors, namedTypeDescriptors, namedTypeBaseTypes, modelNames, composedExtensions, diagnostics, sourceId) {
|
|
177
|
-
const resolvedFields = [];
|
|
178
|
-
for (const field of model.fields) {
|
|
179
|
-
if (field.list) {
|
|
180
|
-
diagnostics.push({
|
|
181
|
-
code: "PSL_UNSUPPORTED_FIELD_LIST",
|
|
182
|
-
message: `Field "${model.name}.${field.name}" uses list types, which are not supported in SQL PSL provider v1`,
|
|
183
|
-
sourceId,
|
|
184
|
-
span: field.span
|
|
185
|
-
});
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
for (const attribute of field.attributes) {
|
|
189
|
-
if (attribute.name === "id" || attribute.name === "unique" || attribute.name === "default" || attribute.name === "relation" || attribute.name === "map" || attribute.name === "pgvector.column") continue;
|
|
190
|
-
if (attribute.name.startsWith("pgvector.") && !composedExtensions.has("pgvector")) {
|
|
191
|
-
diagnostics.push({
|
|
192
|
-
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
193
|
-
message: `Attribute "@${attribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
|
|
194
|
-
sourceId,
|
|
195
|
-
span: attribute.span
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
diagnostics.push({
|
|
200
|
-
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
201
|
-
message: `Field "${model.name}.${field.name}" uses unsupported attribute "@${attribute.name}"`,
|
|
202
|
-
sourceId,
|
|
203
|
-
span: attribute.span
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
|
|
207
|
-
let descriptor = resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors);
|
|
208
|
-
const pgvectorColumnAttribute = getAttribute(field.attributes, "pgvector.column");
|
|
209
|
-
if (pgvectorColumnAttribute) if (!composedExtensions.has("pgvector")) diagnostics.push({
|
|
210
|
-
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
211
|
-
message: "Attribute \"@pgvector.column\" uses unrecognized namespace \"pgvector\". Add extension pack \"pgvector\" to extensionPacks in prisma-next.config.ts.",
|
|
212
|
-
sourceId,
|
|
213
|
-
span: pgvectorColumnAttribute.span
|
|
214
|
-
});
|
|
215
|
-
else if (!(field.typeName === "Bytes" || namedTypeBaseTypes.get(field.typeRef ?? field.typeName) === "Bytes")) diagnostics.push({
|
|
216
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
217
|
-
message: `Field "${model.name}.${field.name}" uses @pgvector.column on unsupported base type "${field.typeName}"`,
|
|
218
|
-
sourceId,
|
|
219
|
-
span: pgvectorColumnAttribute.span
|
|
220
|
-
});
|
|
221
|
-
else {
|
|
222
|
-
const length = parsePgvectorLength({
|
|
223
|
-
attribute: pgvectorColumnAttribute,
|
|
224
|
-
diagnostics,
|
|
225
|
-
sourceId
|
|
226
|
-
});
|
|
227
|
-
if (length !== void 0) descriptor = {
|
|
228
|
-
codecId: "pg/vector@1",
|
|
229
|
-
nativeType: `vector(${length})`,
|
|
230
|
-
typeParams: { length }
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (!descriptor) {
|
|
234
|
-
diagnostics.push({
|
|
235
|
-
code: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
236
|
-
message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
|
|
237
|
-
sourceId,
|
|
238
|
-
span: field.span
|
|
239
|
-
});
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
243
|
-
const defaultValueRaw = defaultAttribute ? getPositionalArgument(defaultAttribute) : void 0;
|
|
244
|
-
const defaultValue = defaultValueRaw ? parseDefaultValueExpression(defaultValueRaw) : void 0;
|
|
245
|
-
if (defaultAttribute && defaultValueRaw && !defaultValue) diagnostics.push({
|
|
246
|
-
code: "PSL_INVALID_DEFAULT_VALUE",
|
|
247
|
-
message: `Unsupported default value "${defaultValueRaw}"`,
|
|
248
|
-
sourceId,
|
|
249
|
-
span: defaultAttribute.span
|
|
250
|
-
});
|
|
251
|
-
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
252
|
-
resolvedFields.push({
|
|
253
|
-
field,
|
|
254
|
-
columnName: mappedColumnName,
|
|
255
|
-
descriptor,
|
|
256
|
-
...defaultValue ? { defaultValue } : {},
|
|
257
|
-
isId: Boolean(getAttribute(field.attributes, "id")),
|
|
258
|
-
isUnique: Boolean(getAttribute(field.attributes, "unique"))
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
return resolvedFields;
|
|
262
|
-
}
|
|
263
|
-
function hasSameSpan(a, b) {
|
|
264
|
-
return a.start.offset === b.start.offset && a.end.offset === b.end.offset && a.start.line === b.start.line && a.end.line === b.end.line;
|
|
265
|
-
}
|
|
266
|
-
function mapParserDiagnostics(document) {
|
|
267
|
-
return document.diagnostics.map((diagnostic) => ({
|
|
268
|
-
code: diagnostic.code,
|
|
269
|
-
message: diagnostic.message,
|
|
270
|
-
sourceId: diagnostic.sourceId,
|
|
271
|
-
span: diagnostic.span
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
function normalizeReferentialAction(input) {
|
|
275
|
-
const normalized = REFERENTIAL_ACTION_MAP[input.actionToken];
|
|
276
|
-
if (normalized) return normalized;
|
|
277
|
-
input.diagnostics.push({
|
|
278
|
-
code: "PSL_UNSUPPORTED_REFERENTIAL_ACTION",
|
|
279
|
-
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported ${input.actionName} action "${input.actionToken}"`,
|
|
280
|
-
sourceId: input.sourceId,
|
|
281
|
-
span: input.span
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
function parseAttributeFieldList(input) {
|
|
285
|
-
const raw = getNamedArgument(input.attribute, "fields") ?? getPositionalArgument(input.attribute);
|
|
286
|
-
if (!raw) {
|
|
287
|
-
input.diagnostics.push({
|
|
288
|
-
code: input.code,
|
|
289
|
-
message: `${input.messagePrefix} requires fields list argument`,
|
|
290
|
-
sourceId: input.sourceId,
|
|
291
|
-
span: input.attribute.span
|
|
292
|
-
});
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const fields = parseFieldList(raw);
|
|
296
|
-
if (!fields || fields.length === 0) {
|
|
297
|
-
input.diagnostics.push({
|
|
298
|
-
code: input.code,
|
|
299
|
-
message: `${input.messagePrefix} requires bracketed field list argument`,
|
|
300
|
-
sourceId: input.sourceId,
|
|
301
|
-
span: input.attribute.span
|
|
302
|
-
});
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
return fields;
|
|
306
|
-
}
|
|
307
|
-
function mapFieldNamesToColumns(input) {
|
|
308
|
-
const columns = [];
|
|
309
|
-
for (const fieldName of input.fieldNames) {
|
|
310
|
-
const columnName = input.mapping.fieldColumns.get(fieldName);
|
|
311
|
-
if (!columnName) {
|
|
312
|
-
input.diagnostics.push({
|
|
313
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
314
|
-
message: `${input.contextLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
315
|
-
sourceId: input.sourceId,
|
|
316
|
-
span: input.span
|
|
317
|
-
});
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
columns.push(columnName);
|
|
321
|
-
}
|
|
322
|
-
return columns;
|
|
323
|
-
}
|
|
324
|
-
function buildModelMappings(models, diagnostics, sourceId) {
|
|
325
|
-
const result = /* @__PURE__ */ new Map();
|
|
326
|
-
for (const model of models) {
|
|
327
|
-
const tableName = parseMapName({
|
|
328
|
-
attribute: getAttribute(model.attributes, "map"),
|
|
329
|
-
defaultValue: lowerFirst(model.name),
|
|
330
|
-
sourceId,
|
|
331
|
-
diagnostics,
|
|
332
|
-
entityLabel: `Model "${model.name}"`,
|
|
333
|
-
span: model.span
|
|
334
|
-
});
|
|
335
|
-
const fieldColumns = /* @__PURE__ */ new Map();
|
|
336
|
-
for (const field of model.fields) {
|
|
337
|
-
const columnName = parseMapName({
|
|
338
|
-
attribute: getAttribute(field.attributes, "map"),
|
|
339
|
-
defaultValue: field.name,
|
|
340
|
-
sourceId,
|
|
341
|
-
diagnostics,
|
|
342
|
-
entityLabel: `Field "${model.name}.${field.name}"`,
|
|
343
|
-
span: field.span
|
|
344
|
-
});
|
|
345
|
-
fieldColumns.set(field.name, columnName);
|
|
346
|
-
}
|
|
347
|
-
result.set(model.name, {
|
|
348
|
-
model,
|
|
349
|
-
tableName,
|
|
350
|
-
fieldColumns
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return result;
|
|
354
|
-
}
|
|
355
|
-
function parseRelationAttribute(input) {
|
|
356
|
-
for (const arg of input.attribute.args) {
|
|
357
|
-
if (arg.kind === "positional") {
|
|
358
|
-
input.diagnostics.push({
|
|
359
|
-
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
360
|
-
message: `Relation field "${input.modelName}.${input.fieldName}" must use named arguments`,
|
|
361
|
-
sourceId: input.sourceId,
|
|
362
|
-
span: arg.span
|
|
363
|
-
});
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (arg.name !== "fields" && arg.name !== "references" && arg.name !== "onDelete" && arg.name !== "onUpdate") {
|
|
367
|
-
input.diagnostics.push({
|
|
368
|
-
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
369
|
-
message: `Relation field "${input.modelName}.${input.fieldName}" has unsupported argument "${arg.name}"`,
|
|
370
|
-
sourceId: input.sourceId,
|
|
371
|
-
span: arg.span
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
const fieldsRaw = getNamedArgument(input.attribute, "fields");
|
|
377
|
-
const referencesRaw = getNamedArgument(input.attribute, "references");
|
|
378
|
-
if (!fieldsRaw || !referencesRaw) {
|
|
379
|
-
input.diagnostics.push({
|
|
380
|
-
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
381
|
-
message: `Relation field "${input.modelName}.${input.fieldName}" requires fields and references arguments`,
|
|
382
|
-
sourceId: input.sourceId,
|
|
383
|
-
span: input.attribute.span
|
|
384
|
-
});
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
const fields = parseFieldList(fieldsRaw);
|
|
388
|
-
const references = parseFieldList(referencesRaw);
|
|
389
|
-
if (!fields || !references || fields.length === 0 || references.length === 0) {
|
|
390
|
-
input.diagnostics.push({
|
|
391
|
-
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
392
|
-
message: `Relation field "${input.modelName}.${input.fieldName}" requires bracketed fields and references lists`,
|
|
393
|
-
sourceId: input.sourceId,
|
|
394
|
-
span: input.attribute.span
|
|
395
|
-
});
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
return {
|
|
399
|
-
fields,
|
|
400
|
-
references,
|
|
401
|
-
...getNamedArgument(input.attribute, "onDelete") ? { onDelete: unquoteStringLiteral(getNamedArgument(input.attribute, "onDelete") ?? "") } : {},
|
|
402
|
-
...getNamedArgument(input.attribute, "onUpdate") ? { onUpdate: unquoteStringLiteral(getNamedArgument(input.attribute, "onUpdate") ?? "") } : {}
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
function interpretPslDocumentToSqlContractIR(input) {
|
|
406
|
-
const diagnostics = mapParserDiagnostics(input.document);
|
|
407
|
-
const modelNames = new Set(input.document.ast.models.map((model) => model.name));
|
|
408
|
-
const sourceId = input.document.ast.sourceId;
|
|
409
|
-
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
410
|
-
let builder = defineContract().target(input.target ?? DEFAULT_POSTGRES_TARGET);
|
|
411
|
-
const enumTypeDescriptors = /* @__PURE__ */ new Map();
|
|
412
|
-
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
413
|
-
const namedTypeBaseTypes = /* @__PURE__ */ new Map();
|
|
414
|
-
for (const enumDeclaration of input.document.ast.enums) {
|
|
415
|
-
const nativeType = enumDeclaration.name.toLowerCase();
|
|
416
|
-
const descriptor = {
|
|
417
|
-
codecId: "pg/enum@1",
|
|
418
|
-
nativeType,
|
|
419
|
-
typeRef: enumDeclaration.name
|
|
420
|
-
};
|
|
421
|
-
enumTypeDescriptors.set(enumDeclaration.name, descriptor);
|
|
422
|
-
builder = builder.storageType(enumDeclaration.name, {
|
|
423
|
-
codecId: "pg/enum@1",
|
|
424
|
-
nativeType,
|
|
425
|
-
typeParams: { values: enumDeclaration.values.map((value) => value.name) }
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
for (const declaration of input.document.ast.types?.declarations ?? []) {
|
|
429
|
-
const baseDescriptor = enumTypeDescriptors.get(declaration.baseType) ?? SCALAR_COLUMN_MAP[declaration.baseType];
|
|
430
|
-
if (!baseDescriptor) {
|
|
431
|
-
diagnostics.push({
|
|
432
|
-
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
433
|
-
message: `Named type "${declaration.name}" references unsupported base type "${declaration.baseType}"`,
|
|
434
|
-
sourceId,
|
|
435
|
-
span: declaration.span
|
|
436
|
-
});
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
namedTypeBaseTypes.set(declaration.name, declaration.baseType);
|
|
440
|
-
const pgvectorAttribute = getAttribute(declaration.attributes, "pgvector.column");
|
|
441
|
-
const unsupportedNamedTypeAttribute = declaration.attributes.find((attribute) => attribute.name !== "pgvector.column");
|
|
442
|
-
if (unsupportedNamedTypeAttribute) {
|
|
443
|
-
diagnostics.push({
|
|
444
|
-
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
445
|
-
message: `Named type "${declaration.name}" uses unsupported attribute "${unsupportedNamedTypeAttribute.name}"`,
|
|
446
|
-
sourceId,
|
|
447
|
-
span: unsupportedNamedTypeAttribute.span
|
|
448
|
-
});
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
if (pgvectorAttribute) {
|
|
452
|
-
if (!composedExtensions.has("pgvector")) {
|
|
453
|
-
diagnostics.push({
|
|
454
|
-
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
455
|
-
message: "Attribute \"@pgvector.column\" uses unrecognized namespace \"pgvector\". Add extension pack \"pgvector\" to extensionPacks in prisma-next.config.ts.",
|
|
456
|
-
sourceId,
|
|
457
|
-
span: pgvectorAttribute.span
|
|
458
|
-
});
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (declaration.baseType !== "Bytes") {
|
|
462
|
-
diagnostics.push({
|
|
463
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
464
|
-
message: `Named type "${declaration.name}" uses @pgvector.column on unsupported base type "${declaration.baseType}"`,
|
|
465
|
-
sourceId,
|
|
466
|
-
span: pgvectorAttribute.span
|
|
467
|
-
});
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
const length = parsePgvectorLength({
|
|
471
|
-
attribute: pgvectorAttribute,
|
|
472
|
-
diagnostics,
|
|
473
|
-
sourceId
|
|
474
|
-
});
|
|
475
|
-
if (length === void 0) continue;
|
|
476
|
-
namedTypeDescriptors.set(declaration.name, {
|
|
477
|
-
codecId: "pg/vector@1",
|
|
478
|
-
nativeType: `vector(${length})`,
|
|
479
|
-
typeRef: declaration.name
|
|
480
|
-
});
|
|
481
|
-
builder = builder.storageType(declaration.name, {
|
|
482
|
-
codecId: "pg/vector@1",
|
|
483
|
-
nativeType: `vector(${length})`,
|
|
484
|
-
typeParams: { length }
|
|
485
|
-
});
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
const descriptor = {
|
|
489
|
-
codecId: baseDescriptor.codecId,
|
|
490
|
-
nativeType: baseDescriptor.nativeType,
|
|
491
|
-
typeRef: declaration.name
|
|
492
|
-
};
|
|
493
|
-
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
494
|
-
builder = builder.storageType(declaration.name, {
|
|
495
|
-
codecId: baseDescriptor.codecId,
|
|
496
|
-
nativeType: baseDescriptor.nativeType,
|
|
497
|
-
typeParams: {}
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
const modelMappings = buildModelMappings(input.document.ast.models, diagnostics, sourceId);
|
|
501
|
-
for (const model of input.document.ast.models) {
|
|
502
|
-
const mapping = modelMappings.get(model.name);
|
|
503
|
-
if (!mapping) continue;
|
|
504
|
-
const tableName = mapping.tableName;
|
|
505
|
-
const resolvedFields = collectResolvedFields(model, mapping, enumTypeDescriptors, namedTypeDescriptors, namedTypeBaseTypes, modelNames, composedExtensions, diagnostics, sourceId);
|
|
506
|
-
const primaryKeyColumns = resolvedFields.filter((field) => field.isId).map((field) => field.columnName);
|
|
507
|
-
if (primaryKeyColumns.length === 0) diagnostics.push({
|
|
508
|
-
code: "PSL_MISSING_PRIMARY_KEY",
|
|
509
|
-
message: `Model "${model.name}" must declare at least one @id field for SQL provider`,
|
|
510
|
-
sourceId,
|
|
511
|
-
span: model.span
|
|
512
|
-
});
|
|
513
|
-
const relationAttributes = model.fields.map((field) => ({
|
|
514
|
-
field,
|
|
515
|
-
relation: getAttribute(field.attributes, "relation")
|
|
516
|
-
})).filter((entry) => Boolean(entry.relation));
|
|
517
|
-
builder = builder.table(tableName, (tableBuilder) => {
|
|
518
|
-
let table = tableBuilder;
|
|
519
|
-
for (const resolvedField of resolvedFields) {
|
|
520
|
-
const options = {
|
|
521
|
-
type: resolvedField.descriptor,
|
|
522
|
-
...resolvedField.field.optional ? { nullable: true } : {},
|
|
523
|
-
...resolvedField.defaultValue ? { default: resolvedField.defaultValue } : {}
|
|
524
|
-
};
|
|
525
|
-
table = table.column(resolvedField.columnName, options);
|
|
526
|
-
if (resolvedField.isUnique) table = table.unique([resolvedField.columnName]);
|
|
527
|
-
}
|
|
528
|
-
if (primaryKeyColumns.length > 0) table = table.primaryKey(primaryKeyColumns);
|
|
529
|
-
for (const modelAttribute of model.attributes) {
|
|
530
|
-
if (modelAttribute.name === "map") continue;
|
|
531
|
-
if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
|
|
532
|
-
const fieldNames = parseAttributeFieldList({
|
|
533
|
-
attribute: modelAttribute,
|
|
534
|
-
sourceId,
|
|
535
|
-
diagnostics,
|
|
536
|
-
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
537
|
-
messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
|
|
538
|
-
});
|
|
539
|
-
if (!fieldNames) continue;
|
|
540
|
-
const columnNames = mapFieldNamesToColumns({
|
|
541
|
-
modelName: model.name,
|
|
542
|
-
fieldNames,
|
|
543
|
-
mapping,
|
|
544
|
-
sourceId,
|
|
545
|
-
diagnostics,
|
|
546
|
-
span: modelAttribute.span,
|
|
547
|
-
contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
|
|
548
|
-
});
|
|
549
|
-
if (!columnNames) continue;
|
|
550
|
-
if (modelAttribute.name === "unique") table = table.unique(columnNames);
|
|
551
|
-
else table = table.index(columnNames);
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
if (modelAttribute.name.startsWith("pgvector.") && !composedExtensions.has("pgvector")) {
|
|
555
|
-
diagnostics.push({
|
|
556
|
-
code: "PSL_EXTENSION_NAMESPACE_NOT_COMPOSED",
|
|
557
|
-
message: `Attribute "@@${modelAttribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
|
|
558
|
-
sourceId,
|
|
559
|
-
span: modelAttribute.span
|
|
560
|
-
});
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
diagnostics.push({
|
|
564
|
-
code: "PSL_UNSUPPORTED_MODEL_ATTRIBUTE",
|
|
565
|
-
message: `Model "${model.name}" uses unsupported attribute "@@${modelAttribute.name}"`,
|
|
566
|
-
sourceId,
|
|
567
|
-
span: modelAttribute.span
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
for (const relationAttribute of relationAttributes) {
|
|
571
|
-
if (!modelNames.has(relationAttribute.field.typeName)) {
|
|
572
|
-
diagnostics.push({
|
|
573
|
-
code: "PSL_INVALID_RELATION_TARGET",
|
|
574
|
-
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
575
|
-
sourceId,
|
|
576
|
-
span: relationAttribute.field.span
|
|
577
|
-
});
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const parsedRelation = parseRelationAttribute({
|
|
581
|
-
attribute: relationAttribute.relation,
|
|
582
|
-
modelName: model.name,
|
|
583
|
-
fieldName: relationAttribute.field.name,
|
|
584
|
-
sourceId,
|
|
585
|
-
diagnostics
|
|
586
|
-
});
|
|
587
|
-
if (!parsedRelation) continue;
|
|
588
|
-
const targetMapping = modelMappings.get(relationAttribute.field.typeName);
|
|
589
|
-
if (!targetMapping) {
|
|
590
|
-
diagnostics.push({
|
|
591
|
-
code: "PSL_INVALID_RELATION_TARGET",
|
|
592
|
-
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${relationAttribute.field.typeName}"`,
|
|
593
|
-
sourceId,
|
|
594
|
-
span: relationAttribute.field.span
|
|
595
|
-
});
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
const localColumns = mapFieldNamesToColumns({
|
|
599
|
-
modelName: model.name,
|
|
600
|
-
fieldNames: parsedRelation.fields,
|
|
601
|
-
mapping,
|
|
602
|
-
sourceId,
|
|
603
|
-
diagnostics,
|
|
604
|
-
span: relationAttribute.relation.span,
|
|
605
|
-
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
606
|
-
});
|
|
607
|
-
if (!localColumns) continue;
|
|
608
|
-
const referencedColumns = mapFieldNamesToColumns({
|
|
609
|
-
modelName: targetMapping.model.name,
|
|
610
|
-
fieldNames: parsedRelation.references,
|
|
611
|
-
mapping: targetMapping,
|
|
612
|
-
sourceId,
|
|
613
|
-
diagnostics,
|
|
614
|
-
span: relationAttribute.relation.span,
|
|
615
|
-
contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
616
|
-
});
|
|
617
|
-
if (!referencedColumns) continue;
|
|
618
|
-
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction({
|
|
619
|
-
modelName: model.name,
|
|
620
|
-
fieldName: relationAttribute.field.name,
|
|
621
|
-
actionName: "onDelete",
|
|
622
|
-
actionToken: parsedRelation.onDelete,
|
|
623
|
-
sourceId,
|
|
624
|
-
span: relationAttribute.field.span,
|
|
625
|
-
diagnostics
|
|
626
|
-
}) : void 0;
|
|
627
|
-
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction({
|
|
628
|
-
modelName: model.name,
|
|
629
|
-
fieldName: relationAttribute.field.name,
|
|
630
|
-
actionName: "onUpdate",
|
|
631
|
-
actionToken: parsedRelation.onUpdate,
|
|
632
|
-
sourceId,
|
|
633
|
-
span: relationAttribute.field.span,
|
|
634
|
-
diagnostics
|
|
635
|
-
}) : void 0;
|
|
636
|
-
table = table.foreignKey(localColumns, {
|
|
637
|
-
table: targetMapping.tableName,
|
|
638
|
-
columns: referencedColumns
|
|
639
|
-
}, {
|
|
640
|
-
...onDelete ? { onDelete } : {},
|
|
641
|
-
...onUpdate ? { onUpdate } : {}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
return table;
|
|
645
|
-
});
|
|
646
|
-
builder = builder.model(model.name, tableName, (modelBuilder) => {
|
|
647
|
-
let next = modelBuilder;
|
|
648
|
-
for (const resolvedField of resolvedFields) next = next.field(resolvedField.field.name, resolvedField.columnName);
|
|
649
|
-
return next;
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
if (diagnostics.length > 0) return notOk({
|
|
653
|
-
summary: "PSL to SQL Contract IR normalization failed",
|
|
654
|
-
diagnostics: diagnostics.filter((diagnostic, index, allDiagnostics) => allDiagnostics.findIndex((candidate) => candidate.code === diagnostic.code && candidate.message === diagnostic.message && candidate.sourceId === diagnostic.sourceId && (candidate.span && diagnostic.span && hasSameSpan(candidate.span, diagnostic.span) || !candidate.span && !diagnostic.span)) === index)
|
|
655
|
-
});
|
|
656
|
-
return ok(builder.build());
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
//#endregion
|
|
660
|
-
export { interpretPslDocumentToSqlContractIR as t };
|
|
661
|
-
//# sourceMappingURL=interpreter-_6-Xk1_m.mjs.map
|