@prisma-next/sql-contract-psl 0.3.0-dev.146 → 0.3.0-dev.162

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.
@@ -4,8 +4,17 @@ import type {
4
4
  AuthoringContributions,
5
5
  AuthoringTypeConstructorDescriptor,
6
6
  } from '@prisma-next/framework-components/authoring';
7
- import { isAuthoringTypeConstructorDescriptor } from '@prisma-next/framework-components/authoring';
8
- import type { PslAttribute, PslField } from '@prisma-next/psl-parser';
7
+ import {
8
+ instantiateAuthoringTypeConstructor,
9
+ isAuthoringTypeConstructorDescriptor,
10
+ validateAuthoringHelperArguments,
11
+ } from '@prisma-next/framework-components/authoring';
12
+ import type {
13
+ PslAttribute,
14
+ PslField,
15
+ PslSpan,
16
+ PslTypeConstructorCall,
17
+ } from '@prisma-next/psl-parser';
9
18
  import type {
10
19
  ControlMutationDefaultRegistry,
11
20
  MutationDefaultGeneratorDescriptor,
@@ -15,8 +24,6 @@ import {
15
24
  parseDefaultFunctionCall,
16
25
  } from './default-function-registry';
17
26
  import {
18
- getNamedArgument,
19
- getPositionalArgument,
20
27
  getPositionalArgumentEntry,
21
28
  getPositionalArguments,
22
29
  parseOptionalNumericArguments,
@@ -24,6 +31,7 @@ import {
24
31
  pushInvalidAttributeArgument,
25
32
  unquoteStringLiteral,
26
33
  } from './psl-attribute-parsing';
34
+ import { mapPslHelperArgs } from './psl-authoring-arguments';
27
35
 
28
36
  export type ColumnDescriptor = {
29
37
  readonly codecId: string;
@@ -59,6 +67,225 @@ export function getAuthoringTypeConstructor(
59
67
  return isAuthoringTypeConstructorDescriptor(current) ? current : undefined;
60
68
  }
61
69
 
70
+ /**
71
+ * Returns the namespace prefix of `attributeName` if it references an
72
+ * unrecognized extension namespace, otherwise `undefined`. A namespace is
73
+ * considered recognized when it is:
74
+ *
75
+ * - `db` (native-type spec, always allowed),
76
+ * - the active family id (e.g. `sql`),
77
+ * - the active target id (e.g. `postgres`),
78
+ * - present in `composedExtensions`.
79
+ *
80
+ * Family/target namespaces are exempted so that e.g. `@sql.foo` surfaces as
81
+ * PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
82
+ * PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
83
+ */
84
+ export function checkUncomposedNamespace(
85
+ attributeName: string,
86
+ composedExtensions: ReadonlySet<string>,
87
+ context?: { readonly familyId?: string; readonly targetId?: string },
88
+ ): string | undefined {
89
+ const dotIndex = attributeName.indexOf('.');
90
+ if (dotIndex <= 0 || dotIndex === attributeName.length - 1) {
91
+ return undefined;
92
+ }
93
+ const namespace = attributeName.slice(0, dotIndex);
94
+ if (
95
+ namespace === 'db' ||
96
+ namespace === context?.familyId ||
97
+ namespace === context?.targetId ||
98
+ composedExtensions.has(namespace)
99
+ ) {
100
+ return undefined;
101
+ }
102
+ return namespace;
103
+ }
104
+
105
+ /**
106
+ * Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a
107
+ * subject (attribute, model attribute, or type constructor) that references an
108
+ * extension namespace which is not composed in the current contract.
109
+ *
110
+ * The `data` payload carries the missing namespace so machine consumers
111
+ * (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
112
+ */
113
+ export function reportUncomposedNamespace(input: {
114
+ readonly subjectLabel: string;
115
+ readonly namespace: string;
116
+ readonly sourceId: string;
117
+ readonly span: PslSpan;
118
+ readonly diagnostics: ContractSourceDiagnostic[];
119
+ }): void {
120
+ input.diagnostics.push({
121
+ code: 'PSL_EXTENSION_NAMESPACE_NOT_COMPOSED',
122
+ message: `${input.subjectLabel} uses unrecognized namespace "${input.namespace}". Add extension pack "${input.namespace}" to extensionPacks in prisma-next.config.ts.`,
123
+ sourceId: input.sourceId,
124
+ span: input.span,
125
+ data: { namespace: input.namespace, suggestedPack: input.namespace },
126
+ });
127
+ }
128
+
129
+ export function instantiatePslTypeConstructor(input: {
130
+ readonly call: PslTypeConstructorCall;
131
+ readonly descriptor: AuthoringTypeConstructorDescriptor;
132
+ readonly diagnostics: ContractSourceDiagnostic[];
133
+ readonly sourceId: string;
134
+ readonly entityLabel: string;
135
+ }):
136
+ | {
137
+ readonly codecId: string;
138
+ readonly nativeType: string;
139
+ readonly typeParams?: Record<string, unknown>;
140
+ }
141
+ | undefined {
142
+ const helperPath = input.call.path.join('.');
143
+ const args = mapPslHelperArgs({
144
+ args: input.call.args,
145
+ descriptors: input.descriptor.args ?? [],
146
+ helperLabel: `constructor "${helperPath}"`,
147
+ span: input.call.span,
148
+ diagnostics: input.diagnostics,
149
+ sourceId: input.sourceId,
150
+ entityLabel: input.entityLabel,
151
+ });
152
+ if (!args) {
153
+ return undefined;
154
+ }
155
+
156
+ try {
157
+ validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
158
+ return instantiateAuthoringTypeConstructor(input.descriptor, args);
159
+ } catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ input.diagnostics.push({
162
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
163
+ message: `${input.entityLabel} constructor "${helperPath}" ${message}`,
164
+ sourceId: input.sourceId,
165
+ span: input.call.span,
166
+ });
167
+ return undefined;
168
+ }
169
+ }
170
+
171
+ function pushUnsupportedTypeConstructorDiagnostic(input: {
172
+ readonly diagnostics: ContractSourceDiagnostic[];
173
+ readonly sourceId: string;
174
+ readonly span: PslSpan;
175
+ readonly code: 'PSL_UNSUPPORTED_FIELD_TYPE' | 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR';
176
+ readonly message: string;
177
+ }): undefined {
178
+ input.diagnostics.push({
179
+ code: input.code,
180
+ message: input.message,
181
+ sourceId: input.sourceId,
182
+ span: input.span,
183
+ });
184
+ return undefined;
185
+ }
186
+
187
+ export function resolvePslTypeConstructorDescriptor(input: {
188
+ readonly call: PslTypeConstructorCall;
189
+ readonly authoringContributions: AuthoringContributions | undefined;
190
+ readonly composedExtensions: ReadonlySet<string>;
191
+ readonly familyId: string;
192
+ readonly targetId: string;
193
+ readonly diagnostics: ContractSourceDiagnostic[];
194
+ readonly sourceId: string;
195
+ readonly unsupportedCode: 'PSL_UNSUPPORTED_FIELD_TYPE' | 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR';
196
+ readonly unsupportedMessage: string;
197
+ }): AuthoringTypeConstructorDescriptor | undefined {
198
+ const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
199
+ if (descriptor) {
200
+ return descriptor;
201
+ }
202
+
203
+ const namespace = input.call.path.length > 1 ? input.call.path[0] : undefined;
204
+ if (
205
+ namespace &&
206
+ namespace !== 'db' &&
207
+ namespace !== input.familyId &&
208
+ namespace !== input.targetId &&
209
+ !input.composedExtensions.has(namespace)
210
+ ) {
211
+ reportUncomposedNamespace({
212
+ subjectLabel: `Type constructor "${input.call.path.join('.')}"`,
213
+ namespace,
214
+ sourceId: input.sourceId,
215
+ span: input.call.span,
216
+ diagnostics: input.diagnostics,
217
+ });
218
+ return undefined;
219
+ }
220
+
221
+ return pushUnsupportedTypeConstructorDiagnostic({
222
+ diagnostics: input.diagnostics,
223
+ sourceId: input.sourceId,
224
+ span: input.call.span,
225
+ code: input.unsupportedCode,
226
+ message: input.unsupportedMessage,
227
+ });
228
+ }
229
+
230
+ export type ResolveFieldTypeResult =
231
+ | { readonly ok: true; readonly descriptor: ColumnDescriptor }
232
+ | { readonly ok: false; readonly alreadyReported: boolean };
233
+
234
+ export function resolveFieldTypeDescriptor(input: {
235
+ readonly field: PslField;
236
+ readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
237
+ readonly namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
238
+ readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
239
+ readonly authoringContributions: AuthoringContributions | undefined;
240
+ readonly composedExtensions: ReadonlySet<string>;
241
+ readonly familyId: string;
242
+ readonly targetId: string;
243
+ readonly diagnostics: ContractSourceDiagnostic[];
244
+ readonly sourceId: string;
245
+ readonly entityLabel: string;
246
+ }): ResolveFieldTypeResult {
247
+ if (input.field.typeConstructor) {
248
+ const helperPath = input.field.typeConstructor.path.join('.');
249
+ const descriptor = resolvePslTypeConstructorDescriptor({
250
+ call: input.field.typeConstructor,
251
+ authoringContributions: input.authoringContributions,
252
+ composedExtensions: input.composedExtensions,
253
+ familyId: input.familyId,
254
+ targetId: input.targetId,
255
+ diagnostics: input.diagnostics,
256
+ sourceId: input.sourceId,
257
+ unsupportedCode: 'PSL_UNSUPPORTED_FIELD_TYPE',
258
+ unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`,
259
+ });
260
+ if (!descriptor) {
261
+ return { ok: false, alreadyReported: true };
262
+ }
263
+
264
+ const instantiated = instantiatePslTypeConstructor({
265
+ call: input.field.typeConstructor,
266
+ descriptor,
267
+ diagnostics: input.diagnostics,
268
+ sourceId: input.sourceId,
269
+ entityLabel: input.entityLabel,
270
+ });
271
+ if (!instantiated) {
272
+ return { ok: false, alreadyReported: true };
273
+ }
274
+ return { ok: true, descriptor: instantiated };
275
+ }
276
+
277
+ const descriptor = resolveColumnDescriptor(
278
+ input.field,
279
+ input.enumTypeDescriptors,
280
+ input.namedTypeDescriptors,
281
+ input.scalarTypeDescriptors,
282
+ );
283
+ if (!descriptor) {
284
+ return { ok: false, alreadyReported: false };
285
+ }
286
+ return { ok: true, descriptor };
287
+ }
288
+
62
289
  /**
63
290
  * Declarative specification for @db.* native type attributes.
64
291
  *
@@ -242,37 +469,6 @@ export function resolveDbNativeTypeAttribute(input: {
242
469
  }
243
470
  }
244
471
 
245
- export function parsePgvectorLength(input: {
246
- readonly attribute: PslAttribute;
247
- readonly diagnostics: ContractSourceDiagnostic[];
248
- readonly sourceId: string;
249
- }): number | undefined {
250
- const namedLength = getNamedArgument(input.attribute, 'length');
251
- const namedDim = getNamedArgument(input.attribute, 'dim');
252
- const positional = getPositionalArgument(input.attribute);
253
- const raw = namedLength ?? namedDim ?? positional;
254
- if (!raw) {
255
- input.diagnostics.push({
256
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
257
- message: '@pgvector.column requires length/dim argument',
258
- sourceId: input.sourceId,
259
- span: input.attribute.span,
260
- });
261
- return undefined;
262
- }
263
- const parsed = Number(unquoteStringLiteral(raw));
264
- if (!Number.isInteger(parsed) || parsed < 1) {
265
- input.diagnostics.push({
266
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
267
- message: '@pgvector.column length/dim must be a positive integer',
268
- sourceId: input.sourceId,
269
- span: input.attribute.span,
270
- });
271
- return undefined;
272
- }
273
- return parsed;
274
- }
275
-
276
472
  export function parseDefaultLiteralValue(expression: string): ColumnDefault | undefined {
277
473
  const trimmed = expression.trim();
278
474
  if (trimmed === 'true' || trimmed === 'false') {
@@ -387,8 +583,8 @@ export function lowerDefaultForField(input: {
387
583
 
388
584
  export function resolveColumnDescriptor(
389
585
  field: PslField,
390
- enumTypeDescriptors: Map<string, ColumnDescriptor>,
391
- namedTypeDescriptors: Map<string, ColumnDescriptor>,
586
+ enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
587
+ namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
392
588
  scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
393
589
  ): ColumnDescriptor | undefined {
394
590
  if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) {
@@ -1,8 +1,7 @@
1
1
  import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
2
2
  import type { ColumnDefault, ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
3
3
  import type { AuthoringContributions } from '@prisma-next/framework-components/authoring';
4
- import { instantiateAuthoringTypeConstructor } from '@prisma-next/framework-components/authoring';
5
- import type { PslField, PslModel } from '@prisma-next/psl-parser';
4
+ import type { PslAttribute, PslField, PslModel } from '@prisma-next/psl-parser';
6
5
  import { ifDefined } from '@prisma-next/utils/defined';
7
6
  import type {
8
7
  ControlMutationDefaultRegistry,
@@ -16,10 +15,10 @@ import {
16
15
  } from './psl-attribute-parsing';
17
16
  import type { ColumnDescriptor } from './psl-column-resolution';
18
17
  import {
19
- getAuthoringTypeConstructor,
18
+ checkUncomposedNamespace,
20
19
  lowerDefaultForField,
21
- parsePgvectorLength,
22
- resolveColumnDescriptor,
20
+ reportUncomposedNamespace,
21
+ resolveFieldTypeDescriptor,
23
22
  } from './psl-column-resolution';
24
23
 
25
24
  export type ResolvedField = {
@@ -43,61 +42,137 @@ export type ModelNameMapping = {
43
42
  readonly fieldColumns: Map<string, string>;
44
43
  };
45
44
 
46
- export function collectResolvedFields(
47
- model: PslModel,
48
- mapping: ModelNameMapping,
49
- enumTypeDescriptors: Map<string, ColumnDescriptor>,
50
- namedTypeDescriptors: Map<string, ColumnDescriptor>,
51
- namedTypeBaseTypes: Map<string, string>,
52
- modelNames: Set<string>,
53
- compositeTypeNames: ReadonlySet<string>,
54
- composedExtensions: Set<string>,
55
- authoringContributions: AuthoringContributions | undefined,
56
- defaultFunctionRegistry: ControlMutationDefaultRegistry,
57
- generatorDescriptorById: ReadonlyMap<string, MutationDefaultGeneratorDescriptor>,
58
- diagnostics: ContractSourceDiagnostic[],
59
- sourceId: string,
60
- scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
61
- ): ResolvedField[] {
62
- const resolvedFields: ResolvedField[] = [];
63
- const pgvectorVectorConstructor = getAuthoringTypeConstructor(authoringContributions, [
64
- 'pgvector',
65
- 'vector',
66
- ]);
45
+ export interface CollectResolvedFieldsInput {
46
+ readonly model: PslModel;
47
+ readonly mapping: ModelNameMapping;
48
+ readonly enumTypeDescriptors: Map<string, ColumnDescriptor>;
49
+ readonly namedTypeDescriptors: Map<string, ColumnDescriptor>;
50
+ readonly modelNames: Set<string>;
51
+ readonly compositeTypeNames: ReadonlySet<string>;
52
+ readonly composedExtensions: Set<string>;
53
+ readonly authoringContributions: AuthoringContributions | undefined;
54
+ readonly familyId: string;
55
+ readonly targetId: string;
56
+ readonly defaultFunctionRegistry: ControlMutationDefaultRegistry;
57
+ readonly generatorDescriptorById: ReadonlyMap<string, MutationDefaultGeneratorDescriptor>;
58
+ readonly diagnostics: ContractSourceDiagnostic[];
59
+ readonly sourceId: string;
60
+ readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
61
+ }
67
62
 
68
- for (const field of model.fields) {
69
- if (field.list && modelNames.has(field.typeName)) {
63
+ const BUILTIN_FIELD_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([
64
+ 'id',
65
+ 'unique',
66
+ 'default',
67
+ 'relation',
68
+ 'map',
69
+ ]);
70
+
71
+ function validateFieldAttributes(input: {
72
+ readonly model: PslModel;
73
+ readonly field: PslField;
74
+ readonly composedExtensions: ReadonlySet<string>;
75
+ readonly diagnostics: ContractSourceDiagnostic[];
76
+ readonly sourceId: string;
77
+ readonly familyId: string;
78
+ readonly targetId: string;
79
+ }): void {
80
+ for (const attribute of input.field.attributes) {
81
+ if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) {
70
82
  continue;
71
83
  }
72
84
 
73
- for (const attribute of field.attributes) {
74
- if (
75
- attribute.name === 'id' ||
76
- attribute.name === 'unique' ||
77
- attribute.name === 'default' ||
78
- attribute.name === 'relation' ||
79
- attribute.name === 'map' ||
80
- attribute.name === 'pgvector.column'
81
- ) {
82
- continue;
83
- }
84
- if (attribute.name.startsWith('pgvector.') && !composedExtensions.has('pgvector')) {
85
- diagnostics.push({
86
- code: 'PSL_EXTENSION_NAMESPACE_NOT_COMPOSED',
87
- message: `Attribute "@${attribute.name}" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.`,
88
- sourceId,
89
- span: attribute.span,
90
- });
91
- continue;
92
- }
93
- diagnostics.push({
94
- code: 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE',
95
- message: `Field "${model.name}.${field.name}" uses unsupported attribute "@${attribute.name}"`,
96
- sourceId,
85
+ const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
86
+ familyId: input.familyId,
87
+ targetId: input.targetId,
88
+ });
89
+ if (uncomposedNamespace) {
90
+ reportUncomposedNamespace({
91
+ subjectLabel: `Attribute "@${attribute.name}"`,
92
+ namespace: uncomposedNamespace,
93
+ sourceId: input.sourceId,
97
94
  span: attribute.span,
95
+ diagnostics: input.diagnostics,
98
96
  });
97
+ continue;
99
98
  }
100
99
 
100
+ input.diagnostics.push({
101
+ code: 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE',
102
+ message: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
103
+ sourceId: input.sourceId,
104
+ span: attribute.span,
105
+ });
106
+ }
107
+ }
108
+
109
+ function extractFieldConstraintNames(input: {
110
+ readonly model: PslModel;
111
+ readonly field: PslField;
112
+ readonly sourceId: string;
113
+ readonly diagnostics: ContractSourceDiagnostic[];
114
+ }): {
115
+ readonly idAttribute: PslAttribute | undefined;
116
+ readonly uniqueAttribute: PslAttribute | undefined;
117
+ readonly idName: string | undefined;
118
+ readonly uniqueName: string | undefined;
119
+ } {
120
+ const idAttribute = getAttribute(input.field.attributes, 'id');
121
+ const uniqueAttribute = getAttribute(input.field.attributes, 'unique');
122
+ const idName = parseConstraintMapArgument({
123
+ attribute: idAttribute,
124
+ sourceId: input.sourceId,
125
+ diagnostics: input.diagnostics,
126
+ entityLabel: `Field "${input.model.name}.${input.field.name}" @id`,
127
+ span: input.field.span,
128
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
129
+ });
130
+ const uniqueName = parseConstraintMapArgument({
131
+ attribute: uniqueAttribute,
132
+ sourceId: input.sourceId,
133
+ diagnostics: input.diagnostics,
134
+ entityLabel: `Field "${input.model.name}.${input.field.name}" @unique`,
135
+ span: input.field.span,
136
+ code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
137
+ });
138
+ return { idAttribute, uniqueAttribute, idName, uniqueName };
139
+ }
140
+
141
+ export function collectResolvedFields(input: CollectResolvedFieldsInput): ResolvedField[] {
142
+ const {
143
+ model,
144
+ mapping,
145
+ enumTypeDescriptors,
146
+ namedTypeDescriptors,
147
+ modelNames,
148
+ compositeTypeNames,
149
+ composedExtensions,
150
+ authoringContributions,
151
+ familyId,
152
+ targetId,
153
+ defaultFunctionRegistry,
154
+ generatorDescriptorById,
155
+ diagnostics,
156
+ sourceId,
157
+ scalarTypeDescriptors,
158
+ } = input;
159
+ const resolvedFields: ResolvedField[] = [];
160
+
161
+ for (const field of model.fields) {
162
+ if (field.list && modelNames.has(field.typeName)) {
163
+ continue;
164
+ }
165
+
166
+ validateFieldAttributes({
167
+ model,
168
+ field,
169
+ composedExtensions,
170
+ diagnostics,
171
+ sourceId,
172
+ familyId,
173
+ targetId,
174
+ });
175
+
101
176
  const relationAttribute = getAttribute(field.attributes, 'relation');
102
177
  if (relationAttribute && modelNames.has(field.typeName)) {
103
178
  continue;
@@ -106,96 +181,56 @@ export function collectResolvedFields(
106
181
  const isValueObjectField = compositeTypeNames.has(field.typeName);
107
182
  const isListField = field.list;
108
183
 
109
- const pgvectorOnJsonField = getAttribute(field.attributes, 'pgvector.column');
110
- if (pgvectorOnJsonField && (isValueObjectField || isListField)) {
111
- diagnostics.push({
112
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
113
- message: `Field "${model.name}.${field.name}" uses @pgvector.column on a JSON-backed field (${isValueObjectField ? 'value object' : 'list'}). @pgvector.column is only supported on scalar Bytes fields.`,
114
- sourceId,
115
- span: pgvectorOnJsonField.span,
116
- });
117
- continue;
118
- }
119
-
120
184
  let descriptor: ColumnDescriptor | undefined;
121
185
  let scalarCodecId: string | undefined;
186
+ const resolveInput = {
187
+ field,
188
+ enumTypeDescriptors,
189
+ namedTypeDescriptors,
190
+ scalarTypeDescriptors,
191
+ authoringContributions,
192
+ composedExtensions,
193
+ familyId,
194
+ targetId,
195
+ diagnostics,
196
+ sourceId,
197
+ entityLabel: `Field "${model.name}.${field.name}"`,
198
+ };
122
199
 
123
200
  if (isValueObjectField) {
124
201
  descriptor = scalarTypeDescriptors.get('Json');
125
202
  } else if (isListField) {
126
- const originalDescriptor = resolveColumnDescriptor(
127
- field,
128
- enumTypeDescriptors,
129
- namedTypeDescriptors,
130
- scalarTypeDescriptors,
131
- );
132
- if (!originalDescriptor) {
133
- diagnostics.push({
134
- code: 'PSL_UNSUPPORTED_FIELD_TYPE',
135
- message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
136
- sourceId,
137
- span: field.span,
138
- });
203
+ const resolved = resolveFieldTypeDescriptor(resolveInput);
204
+ if (!resolved.ok) {
205
+ if (!resolved.alreadyReported) {
206
+ diagnostics.push({
207
+ code: 'PSL_UNSUPPORTED_FIELD_TYPE',
208
+ message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
209
+ sourceId,
210
+ span: field.span,
211
+ });
212
+ }
139
213
  continue;
140
214
  }
141
- scalarCodecId = originalDescriptor.codecId;
215
+ scalarCodecId = resolved.descriptor.codecId;
142
216
  descriptor = scalarTypeDescriptors.get('Json');
143
217
  } else {
144
- descriptor = resolveColumnDescriptor(
145
- field,
146
- enumTypeDescriptors,
147
- namedTypeDescriptors,
148
- scalarTypeDescriptors,
149
- );
150
-
151
- const pgvectorColumnAttribute = getAttribute(field.attributes, 'pgvector.column');
152
- if (pgvectorColumnAttribute) {
153
- if (!composedExtensions.has('pgvector')) {
218
+ const resolved = resolveFieldTypeDescriptor(resolveInput);
219
+ if (!resolved.ok) {
220
+ if (!resolved.alreadyReported) {
154
221
  diagnostics.push({
155
- code: 'PSL_EXTENSION_NAMESPACE_NOT_COMPOSED',
156
- message:
157
- 'Attribute "@pgvector.column" uses unrecognized namespace "pgvector". Add extension pack "pgvector" to extensionPacks in prisma-next.config.ts.',
222
+ code: 'PSL_UNSUPPORTED_FIELD_TYPE',
223
+ message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
158
224
  sourceId,
159
- span: pgvectorColumnAttribute.span,
225
+ span: field.span,
160
226
  });
161
- } else {
162
- const isBytesBase =
163
- field.typeName === 'Bytes' ||
164
- namedTypeBaseTypes.get(field.typeRef ?? field.typeName) === 'Bytes';
165
- if (!isBytesBase) {
166
- diagnostics.push({
167
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
168
- message: `Field "${model.name}.${field.name}" uses @pgvector.column on unsupported base type "${field.typeName}"`,
169
- sourceId,
170
- span: pgvectorColumnAttribute.span,
171
- });
172
- } else {
173
- const length = parsePgvectorLength({
174
- attribute: pgvectorColumnAttribute,
175
- diagnostics,
176
- sourceId,
177
- });
178
- if (length !== undefined) {
179
- descriptor = pgvectorVectorConstructor
180
- ? instantiateAuthoringTypeConstructor(pgvectorVectorConstructor, [length])
181
- : {
182
- codecId: 'pg/vector@1',
183
- nativeType: 'vector',
184
- typeParams: { length },
185
- };
186
- }
187
- }
188
227
  }
228
+ continue;
189
229
  }
230
+ descriptor = resolved.descriptor;
190
231
  }
191
232
 
192
233
  if (!descriptor) {
193
- diagnostics.push({
194
- code: 'PSL_UNSUPPORTED_FIELD_TYPE',
195
- message: `Field "${model.name}.${field.name}" type "${field.typeName}" is not supported in SQL PSL provider v1`,
196
- sourceId,
197
- span: field.span,
198
- });
199
234
  continue;
200
235
  }
201
236
 
@@ -235,23 +270,11 @@ export function collectResolvedFields(
235
270
  }
236
271
  }
237
272
  const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
238
- const idAttribute = getAttribute(field.attributes, 'id');
239
- const uniqueAttribute = getAttribute(field.attributes, 'unique');
240
- const idName = parseConstraintMapArgument({
241
- attribute: idAttribute,
242
- sourceId,
243
- diagnostics,
244
- entityLabel: `Field "${model.name}.${field.name}" @id`,
245
- span: field.span,
246
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
247
- });
248
- const uniqueName = parseConstraintMapArgument({
249
- attribute: uniqueAttribute,
273
+ const { idAttribute, uniqueAttribute, idName, uniqueName } = extractFieldConstraintNames({
274
+ model,
275
+ field,
250
276
  sourceId,
251
277
  diagnostics,
252
- entityLabel: `Field "${model.name}.${field.name}" @unique`,
253
- span: field.span,
254
- code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
255
278
  });
256
279
 
257
280
  resolvedFields.push({