@prisma-next/mongo-contract-psl 0.14.0-dev.5 → 0.14.0-dev.7
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/exports/provider.d.mts.map +1 -1
- package/dist/exports/provider.mjs +23 -7
- package/dist/exports/provider.mjs.map +1 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-Cj1vigKn.mjs → interpreter-DCiVHDlW.mjs} +51 -60
- package/dist/interpreter-DCiVHDlW.mjs.map +1 -0
- package/package.json +10 -10
- package/src/interpreter.ts +70 -77
- package/src/provider.ts +35 -6
- package/src/psl-helpers.ts +18 -13
- package/dist/interpreter-Cj1vigKn.mjs.map +0 -1
package/src/interpreter.ts
CHANGED
|
@@ -24,12 +24,16 @@ import {
|
|
|
24
24
|
import { mongoContractCanonicalizationHooks } from '@prisma-next/mongo-contract/canonicalization-hooks';
|
|
25
25
|
import type { CollationOptions } from '@prisma-next/mongo-value/mongodb-types';
|
|
26
26
|
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
CompositeTypeSymbol,
|
|
28
|
+
FieldSymbol,
|
|
29
|
+
ModelSymbol,
|
|
30
|
+
NamespaceSymbol,
|
|
31
31
|
PslSpan,
|
|
32
|
+
ResolvedAttribute,
|
|
33
|
+
SymbolTable,
|
|
32
34
|
} from '@prisma-next/psl-parser';
|
|
35
|
+
import { nodePslSpan } from '@prisma-next/psl-parser';
|
|
36
|
+
import type { SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
33
37
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
34
38
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
35
39
|
import { deriveJsonSchema, derivePolymorphicJsonSchema } from './derive-json-schema';
|
|
@@ -45,41 +49,30 @@ import {
|
|
|
45
49
|
} from './psl-helpers';
|
|
46
50
|
|
|
47
51
|
export interface InterpretPslDocumentToMongoContractInput {
|
|
48
|
-
readonly
|
|
52
|
+
readonly symbolTable: SymbolTable;
|
|
53
|
+
readonly sourceFile: SourceFile;
|
|
54
|
+
readonly sourceId: string;
|
|
49
55
|
readonly scalarTypeDescriptors: ReadonlyMap<string, string>;
|
|
50
56
|
readonly codecLookup?: CodecLookup;
|
|
57
|
+
readonly seedDiagnostics?: readonly ContractSourceDiagnostic[];
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* import from `@prisma-next/framework-components/psl-ast`.
|
|
57
|
-
*/
|
|
58
|
-
const UNSPECIFIED_PSL_NAMESPACE_NAME = '__unspecified__';
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Mongo FR16c validation: Mongo's authoring DSL exposes the connection's
|
|
62
|
-
* database as the only namespace surface today, so the PSL interpreter
|
|
63
|
-
* rejects every explicit `namespace { … }` block. The implicit
|
|
64
|
-
* `__unspecified__` bucket (top-level declarations) is the only
|
|
65
|
-
* namespace Mongo accepts. `namespace unbound { … }` is rejected too —
|
|
66
|
-
* Mongo has no late-binding namespace concept on the PSL surface (the
|
|
67
|
-
* database name comes from the connection string, not from PSL).
|
|
61
|
+
* Mongo's PSL surface binds the database from the connection string, so every
|
|
62
|
+
* explicit namespace block is invalid, including `namespace unbound { … }`.
|
|
68
63
|
*/
|
|
69
64
|
function validateNamespaceBlocksForMongoTarget(input: {
|
|
70
|
-
readonly namespaces: readonly
|
|
65
|
+
readonly namespaces: readonly NamespaceSymbol[];
|
|
71
66
|
readonly sourceId: string;
|
|
67
|
+
readonly sourceFile: SourceFile;
|
|
72
68
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
73
69
|
}): void {
|
|
74
70
|
for (const namespace of input.namespaces) {
|
|
75
|
-
if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_NAME) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
71
|
input.diagnostics.push({
|
|
79
72
|
code: 'PSL_UNSUPPORTED_NAMESPACE_BLOCK',
|
|
80
73
|
message: `Mongo does not support \`namespace ${namespace.name} { … }\` blocks (the database is bound by the connection string; declare models at the document top level instead).`,
|
|
81
74
|
sourceId: input.sourceId,
|
|
82
|
-
span: namespace.
|
|
75
|
+
span: nodePslSpan(namespace.node.syntax, input.sourceFile),
|
|
83
76
|
});
|
|
84
77
|
}
|
|
85
78
|
}
|
|
@@ -101,16 +94,16 @@ function fkRelationPairKey(declaringModel: string, targetModel: string): string
|
|
|
101
94
|
return `${declaringModel}::${targetModel}`;
|
|
102
95
|
}
|
|
103
96
|
|
|
104
|
-
function resolveFieldMappings(model:
|
|
97
|
+
function resolveFieldMappings(model: ModelSymbol): FieldMappings {
|
|
105
98
|
const pslNameToMapped = new Map<string, string>();
|
|
106
|
-
for (const field of model.fields) {
|
|
99
|
+
for (const field of Object.values(model.fields)) {
|
|
107
100
|
const mapped = getMapName(field.attributes) ?? field.name;
|
|
108
101
|
pslNameToMapped.set(field.name, mapped);
|
|
109
102
|
}
|
|
110
103
|
return { pslNameToMapped };
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
function resolveCollectionName(model:
|
|
106
|
+
function resolveCollectionName(model: ModelSymbol): string {
|
|
114
107
|
return getMapName(model.attributes) ?? lowerFirst(model.name);
|
|
115
108
|
}
|
|
116
109
|
|
|
@@ -123,12 +116,12 @@ interface MongoModelEntry {
|
|
|
123
116
|
readonly base?: CrossReference;
|
|
124
117
|
}
|
|
125
118
|
|
|
126
|
-
type DiscriminatorDeclaration = { readonly fieldName: string; readonly span:
|
|
119
|
+
type DiscriminatorDeclaration = { readonly fieldName: string; readonly span: PslSpan };
|
|
127
120
|
type BaseDeclaration = {
|
|
128
121
|
readonly baseName: string;
|
|
129
122
|
readonly value: string;
|
|
130
123
|
readonly collectionName: string;
|
|
131
|
-
readonly span:
|
|
124
|
+
readonly span: PslSpan;
|
|
132
125
|
};
|
|
133
126
|
|
|
134
127
|
function mongoCrossRef(modelName: string): CrossReference {
|
|
@@ -136,7 +129,7 @@ function mongoCrossRef(modelName: string): CrossReference {
|
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
function collectPolymorphismDeclarations(
|
|
139
|
-
|
|
132
|
+
models: readonly ModelSymbol[],
|
|
140
133
|
sourceId: string,
|
|
141
134
|
diagnostics: ContractSourceDiagnostic[],
|
|
142
135
|
): {
|
|
@@ -145,32 +138,31 @@ function collectPolymorphismDeclarations(
|
|
|
145
138
|
} {
|
|
146
139
|
const discriminatorDeclarations = new Map<string, DiscriminatorDeclaration>();
|
|
147
140
|
const baseDeclarations = new Map<string, BaseDeclaration>();
|
|
148
|
-
const allPslModels = document.ast.namespaces.flatMap((ns) => ns.models);
|
|
149
141
|
|
|
150
|
-
for (const
|
|
151
|
-
for (const attr of
|
|
142
|
+
for (const model of models) {
|
|
143
|
+
for (const attr of model.attributes) {
|
|
152
144
|
if (attr.name === 'discriminator') {
|
|
153
145
|
const fieldName = getPositionalArgument(attr);
|
|
154
146
|
if (!fieldName) {
|
|
155
147
|
diagnostics.push({
|
|
156
148
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
157
|
-
message: `Model "${
|
|
149
|
+
message: `Model "${model.name}" @@discriminator requires a field name argument`,
|
|
158
150
|
sourceId,
|
|
159
151
|
span: attr.span,
|
|
160
152
|
});
|
|
161
153
|
continue;
|
|
162
154
|
}
|
|
163
|
-
const discField =
|
|
155
|
+
const discField = model.fields[fieldName];
|
|
164
156
|
if (discField && discField.typeName !== 'String') {
|
|
165
157
|
diagnostics.push({
|
|
166
158
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
167
|
-
message: `Discriminator field "${fieldName}" on model "${
|
|
159
|
+
message: `Discriminator field "${fieldName}" on model "${model.name}" must be of type String, but is "${discField.typeName}"`,
|
|
168
160
|
sourceId,
|
|
169
161
|
span: attr.span,
|
|
170
162
|
});
|
|
171
163
|
continue;
|
|
172
164
|
}
|
|
173
|
-
discriminatorDeclarations.set(
|
|
165
|
+
discriminatorDeclarations.set(model.name, { fieldName, span: attr.span });
|
|
174
166
|
}
|
|
175
167
|
if (attr.name === 'base') {
|
|
176
168
|
const baseName = getPositionalArgument(attr, 0);
|
|
@@ -178,7 +170,7 @@ function collectPolymorphismDeclarations(
|
|
|
178
170
|
if (!baseName || !rawValue) {
|
|
179
171
|
diagnostics.push({
|
|
180
172
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
181
|
-
message: `Model "${
|
|
173
|
+
message: `Model "${model.name}" @@base requires two arguments: base model name and discriminator value`,
|
|
182
174
|
sourceId,
|
|
183
175
|
span: attr.span,
|
|
184
176
|
});
|
|
@@ -188,14 +180,14 @@ function collectPolymorphismDeclarations(
|
|
|
188
180
|
if (value === undefined) {
|
|
189
181
|
diagnostics.push({
|
|
190
182
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
191
|
-
message: `Model "${
|
|
183
|
+
message: `Model "${model.name}" @@base discriminator value must be a quoted string literal`,
|
|
192
184
|
sourceId,
|
|
193
185
|
span: attr.span,
|
|
194
186
|
});
|
|
195
187
|
continue;
|
|
196
188
|
}
|
|
197
|
-
const collectionName = resolveCollectionName(
|
|
198
|
-
baseDeclarations.set(
|
|
189
|
+
const collectionName = resolveCollectionName(model);
|
|
190
|
+
baseDeclarations.set(model.name, { baseName, value, collectionName, span: attr.span });
|
|
199
191
|
}
|
|
200
192
|
}
|
|
201
193
|
}
|
|
@@ -207,7 +199,7 @@ function resolvePolymorphism(input: {
|
|
|
207
199
|
models: Record<string, MongoModelEntry>;
|
|
208
200
|
roots: Record<string, CrossReference>;
|
|
209
201
|
collections: Record<string, Record<string, unknown>>;
|
|
210
|
-
|
|
202
|
+
allModels: readonly ModelSymbol[];
|
|
211
203
|
discriminatorDeclarations: Map<string, DiscriminatorDeclaration>;
|
|
212
204
|
baseDeclarations: Map<string, BaseDeclaration>;
|
|
213
205
|
modelNames: ReadonlySet<string>;
|
|
@@ -225,11 +217,10 @@ function resolvePolymorphism(input: {
|
|
|
225
217
|
baseDeclarations,
|
|
226
218
|
modelNames,
|
|
227
219
|
sourceId,
|
|
228
|
-
|
|
220
|
+
allModels: allModelViews,
|
|
229
221
|
indexSpans,
|
|
230
222
|
modelIndexesByName,
|
|
231
223
|
} = input;
|
|
232
|
-
const allPslModels = document.ast.namespaces.flatMap((ns) => ns.models);
|
|
233
224
|
let patched = input.models;
|
|
234
225
|
let roots = input.roots;
|
|
235
226
|
let collections = input.collections;
|
|
@@ -249,9 +240,9 @@ function resolvePolymorphism(input: {
|
|
|
249
240
|
const model = patched[modelName];
|
|
250
241
|
if (!model) continue;
|
|
251
242
|
|
|
252
|
-
const
|
|
253
|
-
const mappedDiscriminatorField =
|
|
254
|
-
? (resolveFieldMappings(
|
|
243
|
+
const modelView = allModelViews.find((m) => m.name === modelName);
|
|
244
|
+
const mappedDiscriminatorField = modelView
|
|
245
|
+
? (resolveFieldMappings(modelView).pslNameToMapped.get(decl.fieldName) ?? decl.fieldName)
|
|
255
246
|
: decl.fieldName;
|
|
256
247
|
|
|
257
248
|
if (!Object.hasOwn(model.fields, mappedDiscriminatorField)) {
|
|
@@ -312,9 +303,9 @@ function resolvePolymorphism(input: {
|
|
|
312
303
|
}
|
|
313
304
|
|
|
314
305
|
const baseModel = patched[baseDecl.baseName];
|
|
315
|
-
const
|
|
316
|
-
if (!
|
|
317
|
-
const hasExplicitMap = getMapName(
|
|
306
|
+
const variantModelView = allModelViews.find((m) => m.name === variantName);
|
|
307
|
+
if (!variantModelView) continue;
|
|
308
|
+
const hasExplicitMap = getMapName(variantModelView.attributes) !== undefined;
|
|
318
309
|
|
|
319
310
|
if (hasExplicitMap && baseModel && baseDecl.collectionName !== baseModel.storage.collection) {
|
|
320
311
|
diagnostics.push({
|
|
@@ -339,7 +330,7 @@ function resolvePolymorphism(input: {
|
|
|
339
330
|
};
|
|
340
331
|
}
|
|
341
332
|
|
|
342
|
-
const variantCollectionName = resolveCollectionName(
|
|
333
|
+
const variantCollectionName = resolveCollectionName(variantModelView);
|
|
343
334
|
if (roots[variantCollectionName]?.model === variantName) {
|
|
344
335
|
if (variantCollectionName === baseCollection && baseModel) {
|
|
345
336
|
roots = { ...roots, [variantCollectionName]: mongoCrossRef(baseDecl.baseName) };
|
|
@@ -483,9 +474,7 @@ function parseJsonArg(raw: string | undefined): Record<string, unknown> | undefi
|
|
|
483
474
|
return undefined;
|
|
484
475
|
}
|
|
485
476
|
|
|
486
|
-
function parseCollation(
|
|
487
|
-
attr: import('@prisma-next/psl-parser').PslAttribute,
|
|
488
|
-
): CollationOptions | null | undefined {
|
|
477
|
+
function parseCollation(attr: ResolvedAttribute): CollationOptions | null | undefined {
|
|
489
478
|
const locale = stripQuotesHelper(getNamedArgument(attr, 'collationLocale'));
|
|
490
479
|
if (!locale) {
|
|
491
480
|
const hasAnyCollationArg =
|
|
@@ -545,7 +534,7 @@ function parseProjectionList(
|
|
|
545
534
|
}
|
|
546
535
|
|
|
547
536
|
function collectIndexes(
|
|
548
|
-
pslModel:
|
|
537
|
+
pslModel: ModelSymbol,
|
|
549
538
|
fieldMappings: FieldMappings,
|
|
550
539
|
modelNames: ReadonlySet<string>,
|
|
551
540
|
sourceId: string,
|
|
@@ -561,12 +550,12 @@ function collectIndexes(
|
|
|
561
550
|
// rather than fieldMappings.pslNameToMapped because the latter contains
|
|
562
551
|
// every PSL field including relation fields.
|
|
563
552
|
const indexableFieldNames = new Set<string>();
|
|
564
|
-
for (const f of pslModel.fields) {
|
|
553
|
+
for (const f of Object.values(pslModel.fields)) {
|
|
565
554
|
if (modelNames.has(f.typeName)) continue;
|
|
566
555
|
indexableFieldNames.add(f.name);
|
|
567
556
|
}
|
|
568
557
|
|
|
569
|
-
for (const field of pslModel.fields) {
|
|
558
|
+
for (const field of Object.values(pslModel.fields)) {
|
|
570
559
|
if (modelNames.has(field.typeName)) continue;
|
|
571
560
|
const uniqueAttr = getAttribute(field.attributes, 'unique');
|
|
572
561
|
if (!uniqueAttr) continue;
|
|
@@ -800,7 +789,7 @@ function collectIndexes(
|
|
|
800
789
|
return indexes;
|
|
801
790
|
}
|
|
802
791
|
|
|
803
|
-
function isRelationField(field:
|
|
792
|
+
function isRelationField(field: FieldSymbol, modelNames: ReadonlySet<string>): boolean {
|
|
804
793
|
return modelNames.has(field.typeName);
|
|
805
794
|
}
|
|
806
795
|
|
|
@@ -808,14 +797,14 @@ function isRelationField(field: PslField, modelNames: ReadonlySet<string>): bool
|
|
|
808
797
|
const MONGO_OBJECT_ID_PSL_TYPE = 'ObjectId';
|
|
809
798
|
|
|
810
799
|
function resolveFieldCodecId(
|
|
811
|
-
field:
|
|
800
|
+
field: FieldSymbol,
|
|
812
801
|
scalarTypeDescriptors: ReadonlyMap<string, string>,
|
|
813
802
|
): string | undefined {
|
|
814
803
|
return scalarTypeDescriptors.get(field.typeName);
|
|
815
804
|
}
|
|
816
805
|
|
|
817
806
|
function resolveNonRelationField(
|
|
818
|
-
field:
|
|
807
|
+
field: FieldSymbol,
|
|
819
808
|
ownerName: string,
|
|
820
809
|
compositeTypeNames: ReadonlySet<string>,
|
|
821
810
|
scalarTypeDescriptors: ReadonlyMap<string, string>,
|
|
@@ -830,6 +819,11 @@ function resolveNonRelationField(
|
|
|
830
819
|
return field.list ? { ...result, many: true } : result;
|
|
831
820
|
}
|
|
832
821
|
|
|
822
|
+
// Avoid cascading unsupported-type diagnostics after invalid qualification.
|
|
823
|
+
if (field.malformedType) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
|
|
833
827
|
const codecId = resolveFieldCodecId(field, scalarTypeDescriptors);
|
|
834
828
|
if (!codecId) {
|
|
835
829
|
diagnostics.push({
|
|
@@ -851,21 +845,18 @@ function resolveNonRelationField(
|
|
|
851
845
|
export function interpretPslDocumentToMongoContract(
|
|
852
846
|
input: InterpretPslDocumentToMongoContractInput,
|
|
853
847
|
): Result<Contract, ContractSourceDiagnostics> {
|
|
854
|
-
const {
|
|
855
|
-
const sourceId =
|
|
856
|
-
const diagnostics: ContractSourceDiagnostic[] = [];
|
|
848
|
+
const { symbolTable, sourceFile, scalarTypeDescriptors, codecLookup } = input;
|
|
849
|
+
const sourceId = input.sourceId;
|
|
850
|
+
const diagnostics: ContractSourceDiagnostic[] = [...(input.seedDiagnostics ?? [])];
|
|
851
|
+
const topLevel = symbolTable.topLevel;
|
|
857
852
|
validateNamespaceBlocksForMongoTarget({
|
|
858
|
-
namespaces:
|
|
853
|
+
namespaces: Object.values(topLevel.namespaces),
|
|
859
854
|
sourceId,
|
|
855
|
+
sourceFile,
|
|
860
856
|
diagnostics,
|
|
861
857
|
});
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
// collection map remains flat (and the Mongo target represents
|
|
865
|
-
// databases via `MongoTargetDatabase`, populated at compose time by
|
|
866
|
-
// the connection string rather than authoring-time PSL).
|
|
867
|
-
const allModels = document.ast.namespaces.flatMap((ns) => ns.models);
|
|
868
|
-
const allCompositeTypes = document.ast.namespaces.flatMap((ns) => ns.compositeTypes);
|
|
858
|
+
const allModels: ModelSymbol[] = Object.values(topLevel.models);
|
|
859
|
+
const allCompositeTypes: CompositeTypeSymbol[] = Object.values(topLevel.compositeTypes);
|
|
869
860
|
const modelNames = new Set(allModels.map((m) => m.name));
|
|
870
861
|
const compositeTypeNames = new Set(allCompositeTypes.map((ct) => ct.name));
|
|
871
862
|
|
|
@@ -882,7 +873,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
882
873
|
readonly targetModelName: string;
|
|
883
874
|
readonly relationName?: string;
|
|
884
875
|
readonly cardinality: '1:1' | '1:N';
|
|
885
|
-
readonly field:
|
|
876
|
+
readonly field: FieldSymbol;
|
|
886
877
|
}
|
|
887
878
|
const backrelationCandidates: BackrelationCandidate[] = [];
|
|
888
879
|
|
|
@@ -893,7 +884,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
893
884
|
const fields: Record<string, ContractField> = {};
|
|
894
885
|
const relations: Record<string, ContractReferenceRelation> = {};
|
|
895
886
|
|
|
896
|
-
for (const field of pslModel.fields) {
|
|
887
|
+
for (const field of Object.values(pslModel.fields)) {
|
|
897
888
|
if (isRelationField(field, modelNames)) {
|
|
898
889
|
const relation = parseRelationAttribute(field.attributes);
|
|
899
890
|
|
|
@@ -956,7 +947,9 @@ export function interpretPslDocumentToMongoContract(
|
|
|
956
947
|
}
|
|
957
948
|
|
|
958
949
|
const isVariantModel = pslModel.attributes.some((attr) => attr.name === 'base');
|
|
959
|
-
const hasIdField = pslModel.fields.some(
|
|
950
|
+
const hasIdField = Object.values(pslModel.fields).some(
|
|
951
|
+
(f) => getAttribute(f.attributes, 'id') !== undefined,
|
|
952
|
+
);
|
|
960
953
|
// Variant models inherit the base's identity and are validated through their base.
|
|
961
954
|
if (!isVariantModel) {
|
|
962
955
|
if (!hasIdField) {
|
|
@@ -1011,7 +1004,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1011
1004
|
const valueObjects: Record<string, ContractValueObject> = {};
|
|
1012
1005
|
for (const compositeType of allCompositeTypes) {
|
|
1013
1006
|
const fields: Record<string, ContractField> = {};
|
|
1014
|
-
for (const field of compositeType.fields) {
|
|
1007
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
1015
1008
|
const resolved = resolveNonRelationField(
|
|
1016
1009
|
field,
|
|
1017
1010
|
compositeType.name,
|
|
@@ -1078,7 +1071,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1078
1071
|
}
|
|
1079
1072
|
|
|
1080
1073
|
const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(
|
|
1081
|
-
|
|
1074
|
+
allModels,
|
|
1082
1075
|
sourceId,
|
|
1083
1076
|
diagnostics,
|
|
1084
1077
|
);
|
|
@@ -1086,7 +1079,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1086
1079
|
models,
|
|
1087
1080
|
roots,
|
|
1088
1081
|
collections,
|
|
1089
|
-
|
|
1082
|
+
allModels,
|
|
1090
1083
|
discriminatorDeclarations,
|
|
1091
1084
|
baseDeclarations,
|
|
1092
1085
|
modelNames,
|
package/src/provider.ts
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import type { ContractConfig } from '@prisma-next/config/config-types';
|
|
3
|
-
import {
|
|
2
|
+
import type { ContractConfig, ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
3
|
+
import { buildSymbolTable, rangeToPslSpan } from '@prisma-next/psl-parser';
|
|
4
|
+
import type { ParseDiagnostic, SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
5
|
+
import { parse } from '@prisma-next/psl-parser/syntax';
|
|
4
6
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
5
7
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
8
|
+
|
|
6
9
|
import { interpretPslDocumentToMongoContract } from './interpreter';
|
|
7
10
|
|
|
8
11
|
export interface MongoContractOptions {
|
|
9
12
|
readonly output?: string;
|
|
10
13
|
}
|
|
11
14
|
|
|
15
|
+
function mapParseDiagnostics(
|
|
16
|
+
diagnostics: readonly ParseDiagnostic[],
|
|
17
|
+
sourceFile: SourceFile,
|
|
18
|
+
sourceId: string,
|
|
19
|
+
): ContractSourceDiagnostic[] {
|
|
20
|
+
return diagnostics.map((diagnostic) => ({
|
|
21
|
+
code: diagnostic.code,
|
|
22
|
+
message: diagnostic.message,
|
|
23
|
+
sourceId,
|
|
24
|
+
span: rangeToPslSpan(diagnostic.range, sourceFile),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
export function mongoContract(schemaPath: string, options?: MongoContractOptions): ContractConfig {
|
|
13
29
|
return {
|
|
14
30
|
source: {
|
|
@@ -39,13 +55,26 @@ export function mongoContract(schemaPath: string, options?: MongoContractOptions
|
|
|
39
55
|
});
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
const document =
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
const { document, sourceFile, diagnostics: parseDiagnostics } = parse(schema);
|
|
59
|
+
const { table: symbolTable, diagnostics: symbolTableDiagnostics } = buildSymbolTable({
|
|
60
|
+
document,
|
|
61
|
+
sourceFile,
|
|
62
|
+
scalarTypes: [...context.scalarTypeDescriptors.keys()],
|
|
63
|
+
pslBlockDescriptors: context.authoringContributions.pslBlockDescriptors,
|
|
45
64
|
});
|
|
46
65
|
|
|
66
|
+
// Do not short-circuit on provider-level diagnostics; recovered CST can
|
|
67
|
+
// still produce interpreter diagnostics in the same response.
|
|
68
|
+
const seedDiagnostics = [
|
|
69
|
+
...mapParseDiagnostics(parseDiagnostics, sourceFile, schemaPath),
|
|
70
|
+
...mapParseDiagnostics(symbolTableDiagnostics, sourceFile, schemaPath),
|
|
71
|
+
];
|
|
72
|
+
|
|
47
73
|
const interpreted = interpretPslDocumentToMongoContract({
|
|
48
|
-
|
|
74
|
+
symbolTable,
|
|
75
|
+
sourceFile,
|
|
76
|
+
sourceId: schemaPath,
|
|
77
|
+
seedDiagnostics,
|
|
49
78
|
scalarTypeDescriptors: context.scalarTypeDescriptors,
|
|
50
79
|
codecLookup: context.codecLookup,
|
|
51
80
|
});
|
package/src/psl-helpers.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { ResolvedAttribute, ResolvedAttributeArg } from '@prisma-next/psl-parser';
|
|
2
|
+
import { parseQuotedStringLiteral } from '@prisma-next/psl-parser';
|
|
3
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
4
|
|
|
4
|
-
export {
|
|
5
|
+
export { parseQuotedStringLiteral };
|
|
5
6
|
|
|
6
|
-
export function
|
|
7
|
+
export function getPositionalArgument(attr: ResolvedAttribute, index = 0): string | undefined {
|
|
8
|
+
return attr.args.filter((arg) => arg.kind === 'positional')[index]?.value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getNamedArgument(attr: ResolvedAttribute, name: string): string | undefined {
|
|
7
12
|
const arg = attr.args.find((a) => a.kind === 'named' && a.name === name);
|
|
8
13
|
return arg?.value;
|
|
9
14
|
}
|
|
@@ -72,13 +77,13 @@ export function lowerFirst(value: string): string {
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
export function getAttribute(
|
|
75
|
-
attributes: readonly
|
|
80
|
+
attributes: readonly ResolvedAttribute[],
|
|
76
81
|
name: string,
|
|
77
|
-
):
|
|
82
|
+
): ResolvedAttribute | undefined {
|
|
78
83
|
return attributes.find((attr) => attr.name === name);
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
export function getMapName(attributes: readonly
|
|
86
|
+
export function getMapName(attributes: readonly ResolvedAttribute[]): string | undefined {
|
|
82
87
|
const mapAttr = getAttribute(attributes, 'map');
|
|
83
88
|
if (!mapAttr) return undefined;
|
|
84
89
|
const arg = mapAttr.args[0];
|
|
@@ -93,14 +98,14 @@ export interface ParsedRelationAttribute {
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
export function parseRelationAttribute(
|
|
96
|
-
attributes: readonly
|
|
101
|
+
attributes: readonly ResolvedAttribute[],
|
|
97
102
|
): ParsedRelationAttribute | undefined {
|
|
98
103
|
const relationAttr = getAttribute(attributes, 'relation');
|
|
99
104
|
if (!relationAttr) return undefined;
|
|
100
105
|
|
|
101
106
|
let relationName: string | undefined;
|
|
102
|
-
let fieldsArg:
|
|
103
|
-
let referencesArg:
|
|
107
|
+
let fieldsArg: ResolvedAttributeArg | undefined;
|
|
108
|
+
let referencesArg: ResolvedAttributeArg | undefined;
|
|
104
109
|
|
|
105
110
|
for (const arg of relationAttr.args) {
|
|
106
111
|
if (arg.kind === 'positional') {
|
|
@@ -118,9 +123,9 @@ export function parseRelationAttribute(
|
|
|
118
123
|
const references = referencesArg ? parseFieldList(referencesArg.value) : undefined;
|
|
119
124
|
|
|
120
125
|
return {
|
|
121
|
-
...(relationName
|
|
122
|
-
...(fields
|
|
123
|
-
...(references
|
|
126
|
+
...ifDefined('relationName', relationName),
|
|
127
|
+
...ifDefined('fields', fields),
|
|
128
|
+
...ifDefined('references', references),
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
|