@prisma-next/mongo-contract-psl 0.14.0-dev.3 → 0.14.0-dev.31
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 +26 -8
- package/dist/exports/provider.mjs.map +1 -1
- package/dist/index.d.mts +20 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{interpreter-Cj1vigKn.mjs → interpreter-CDnf61pR.mjs} +153 -85
- package/dist/interpreter-CDnf61pR.mjs.map +1 -0
- package/package.json +11 -10
- package/src/derive-json-schema.ts +24 -19
- package/src/exports/index.ts +1 -0
- package/src/interpreter.ts +199 -82
- package/src/provider.ts +37 -6
- package/src/psl-helpers.ts +18 -13
- package/dist/interpreter-Cj1vigKn.mjs.map +0 -1
package/src/interpreter.ts
CHANGED
|
@@ -5,12 +5,24 @@ import type {
|
|
|
5
5
|
import { computeProfileHash, computeStorageHash } from '@prisma-next/contract/hashing';
|
|
6
6
|
import {
|
|
7
7
|
type Contract,
|
|
8
|
+
type ContractEnum,
|
|
8
9
|
type ContractField,
|
|
9
10
|
type ContractReferenceRelation,
|
|
10
11
|
type ContractValueObject,
|
|
11
12
|
type CrossReference,
|
|
12
13
|
crossRef,
|
|
14
|
+
type JsonValue,
|
|
15
|
+
type ValueSetRef,
|
|
13
16
|
} from '@prisma-next/contract/types';
|
|
17
|
+
import type { EnumTypeHandle } from '@prisma-next/contract-authoring';
|
|
18
|
+
import type {
|
|
19
|
+
AuthoringContributions,
|
|
20
|
+
AuthoringEntityContext,
|
|
21
|
+
} from '@prisma-next/framework-components/authoring';
|
|
22
|
+
import {
|
|
23
|
+
instantiateAuthoringEntityType,
|
|
24
|
+
isAuthoringEntityTypeDescriptor,
|
|
25
|
+
} from '@prisma-next/framework-components/authoring';
|
|
14
26
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
15
27
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
16
28
|
import {
|
|
@@ -24,13 +36,19 @@ import {
|
|
|
24
36
|
import { mongoContractCanonicalizationHooks } from '@prisma-next/mongo-contract/canonicalization-hooks';
|
|
25
37
|
import type { CollationOptions } from '@prisma-next/mongo-value/mongodb-types';
|
|
26
38
|
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
CompositeTypeSymbol,
|
|
40
|
+
FieldSymbol,
|
|
41
|
+
ModelSymbol,
|
|
42
|
+
NamespaceSymbol,
|
|
43
|
+
PslExtensionBlock,
|
|
31
44
|
PslSpan,
|
|
45
|
+
ResolvedAttribute,
|
|
46
|
+
SymbolTable,
|
|
32
47
|
} from '@prisma-next/psl-parser';
|
|
48
|
+
import { nodePslSpan } from '@prisma-next/psl-parser';
|
|
49
|
+
import type { SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
33
50
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
51
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
34
52
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
35
53
|
import { deriveJsonSchema, derivePolymorphicJsonSchema } from './derive-json-schema';
|
|
36
54
|
import {
|
|
@@ -45,41 +63,31 @@ import {
|
|
|
45
63
|
} from './psl-helpers';
|
|
46
64
|
|
|
47
65
|
export interface InterpretPslDocumentToMongoContractInput {
|
|
48
|
-
readonly
|
|
66
|
+
readonly symbolTable: SymbolTable;
|
|
67
|
+
readonly sourceFile: SourceFile;
|
|
68
|
+
readonly sourceId: string;
|
|
49
69
|
readonly scalarTypeDescriptors: ReadonlyMap<string, string>;
|
|
50
70
|
readonly codecLookup?: CodecLookup;
|
|
71
|
+
readonly seedDiagnostics?: readonly ContractSourceDiagnostic[];
|
|
72
|
+
readonly authoringContributions?: AuthoringContributions;
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
/**
|
|
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).
|
|
76
|
+
* Mongo's PSL surface binds the database from the connection string, so every
|
|
77
|
+
* explicit namespace block is invalid, including `namespace unbound { … }`.
|
|
68
78
|
*/
|
|
69
79
|
function validateNamespaceBlocksForMongoTarget(input: {
|
|
70
|
-
readonly namespaces: readonly
|
|
80
|
+
readonly namespaces: readonly NamespaceSymbol[];
|
|
71
81
|
readonly sourceId: string;
|
|
82
|
+
readonly sourceFile: SourceFile;
|
|
72
83
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
73
84
|
}): void {
|
|
74
85
|
for (const namespace of input.namespaces) {
|
|
75
|
-
if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_NAME) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
86
|
input.diagnostics.push({
|
|
79
87
|
code: 'PSL_UNSUPPORTED_NAMESPACE_BLOCK',
|
|
80
88
|
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
89
|
sourceId: input.sourceId,
|
|
82
|
-
span: namespace.
|
|
90
|
+
span: nodePslSpan(namespace.node.syntax, input.sourceFile),
|
|
83
91
|
});
|
|
84
92
|
}
|
|
85
93
|
}
|
|
@@ -101,16 +109,16 @@ function fkRelationPairKey(declaringModel: string, targetModel: string): string
|
|
|
101
109
|
return `${declaringModel}::${targetModel}`;
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
function resolveFieldMappings(model:
|
|
112
|
+
function resolveFieldMappings(model: ModelSymbol): FieldMappings {
|
|
105
113
|
const pslNameToMapped = new Map<string, string>();
|
|
106
|
-
for (const field of model.fields) {
|
|
114
|
+
for (const field of Object.values(model.fields)) {
|
|
107
115
|
const mapped = getMapName(field.attributes) ?? field.name;
|
|
108
116
|
pslNameToMapped.set(field.name, mapped);
|
|
109
117
|
}
|
|
110
118
|
return { pslNameToMapped };
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
function resolveCollectionName(model:
|
|
121
|
+
function resolveCollectionName(model: ModelSymbol): string {
|
|
114
122
|
return getMapName(model.attributes) ?? lowerFirst(model.name);
|
|
115
123
|
}
|
|
116
124
|
|
|
@@ -123,12 +131,12 @@ interface MongoModelEntry {
|
|
|
123
131
|
readonly base?: CrossReference;
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
type DiscriminatorDeclaration = { readonly fieldName: string; readonly span:
|
|
134
|
+
type DiscriminatorDeclaration = { readonly fieldName: string; readonly span: PslSpan };
|
|
127
135
|
type BaseDeclaration = {
|
|
128
136
|
readonly baseName: string;
|
|
129
137
|
readonly value: string;
|
|
130
138
|
readonly collectionName: string;
|
|
131
|
-
readonly span:
|
|
139
|
+
readonly span: PslSpan;
|
|
132
140
|
};
|
|
133
141
|
|
|
134
142
|
function mongoCrossRef(modelName: string): CrossReference {
|
|
@@ -136,7 +144,7 @@ function mongoCrossRef(modelName: string): CrossReference {
|
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
function collectPolymorphismDeclarations(
|
|
139
|
-
|
|
147
|
+
models: readonly ModelSymbol[],
|
|
140
148
|
sourceId: string,
|
|
141
149
|
diagnostics: ContractSourceDiagnostic[],
|
|
142
150
|
): {
|
|
@@ -145,32 +153,31 @@ function collectPolymorphismDeclarations(
|
|
|
145
153
|
} {
|
|
146
154
|
const discriminatorDeclarations = new Map<string, DiscriminatorDeclaration>();
|
|
147
155
|
const baseDeclarations = new Map<string, BaseDeclaration>();
|
|
148
|
-
const allPslModels = document.ast.namespaces.flatMap((ns) => ns.models);
|
|
149
156
|
|
|
150
|
-
for (const
|
|
151
|
-
for (const attr of
|
|
157
|
+
for (const model of models) {
|
|
158
|
+
for (const attr of model.attributes) {
|
|
152
159
|
if (attr.name === 'discriminator') {
|
|
153
160
|
const fieldName = getPositionalArgument(attr);
|
|
154
161
|
if (!fieldName) {
|
|
155
162
|
diagnostics.push({
|
|
156
163
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
157
|
-
message: `Model "${
|
|
164
|
+
message: `Model "${model.name}" @@discriminator requires a field name argument`,
|
|
158
165
|
sourceId,
|
|
159
166
|
span: attr.span,
|
|
160
167
|
});
|
|
161
168
|
continue;
|
|
162
169
|
}
|
|
163
|
-
const discField =
|
|
170
|
+
const discField = model.fields[fieldName];
|
|
164
171
|
if (discField && discField.typeName !== 'String') {
|
|
165
172
|
diagnostics.push({
|
|
166
173
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
167
|
-
message: `Discriminator field "${fieldName}" on model "${
|
|
174
|
+
message: `Discriminator field "${fieldName}" on model "${model.name}" must be of type String, but is "${discField.typeName}"`,
|
|
168
175
|
sourceId,
|
|
169
176
|
span: attr.span,
|
|
170
177
|
});
|
|
171
178
|
continue;
|
|
172
179
|
}
|
|
173
|
-
discriminatorDeclarations.set(
|
|
180
|
+
discriminatorDeclarations.set(model.name, { fieldName, span: attr.span });
|
|
174
181
|
}
|
|
175
182
|
if (attr.name === 'base') {
|
|
176
183
|
const baseName = getPositionalArgument(attr, 0);
|
|
@@ -178,7 +185,7 @@ function collectPolymorphismDeclarations(
|
|
|
178
185
|
if (!baseName || !rawValue) {
|
|
179
186
|
diagnostics.push({
|
|
180
187
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
181
|
-
message: `Model "${
|
|
188
|
+
message: `Model "${model.name}" @@base requires two arguments: base model name and discriminator value`,
|
|
182
189
|
sourceId,
|
|
183
190
|
span: attr.span,
|
|
184
191
|
});
|
|
@@ -188,14 +195,14 @@ function collectPolymorphismDeclarations(
|
|
|
188
195
|
if (value === undefined) {
|
|
189
196
|
diagnostics.push({
|
|
190
197
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
191
|
-
message: `Model "${
|
|
198
|
+
message: `Model "${model.name}" @@base discriminator value must be a quoted string literal`,
|
|
192
199
|
sourceId,
|
|
193
200
|
span: attr.span,
|
|
194
201
|
});
|
|
195
202
|
continue;
|
|
196
203
|
}
|
|
197
|
-
const collectionName = resolveCollectionName(
|
|
198
|
-
baseDeclarations.set(
|
|
204
|
+
const collectionName = resolveCollectionName(model);
|
|
205
|
+
baseDeclarations.set(model.name, { baseName, value, collectionName, span: attr.span });
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
}
|
|
@@ -207,7 +214,7 @@ function resolvePolymorphism(input: {
|
|
|
207
214
|
models: Record<string, MongoModelEntry>;
|
|
208
215
|
roots: Record<string, CrossReference>;
|
|
209
216
|
collections: Record<string, Record<string, unknown>>;
|
|
210
|
-
|
|
217
|
+
allModels: readonly ModelSymbol[];
|
|
211
218
|
discriminatorDeclarations: Map<string, DiscriminatorDeclaration>;
|
|
212
219
|
baseDeclarations: Map<string, BaseDeclaration>;
|
|
213
220
|
modelNames: ReadonlySet<string>;
|
|
@@ -225,11 +232,10 @@ function resolvePolymorphism(input: {
|
|
|
225
232
|
baseDeclarations,
|
|
226
233
|
modelNames,
|
|
227
234
|
sourceId,
|
|
228
|
-
|
|
235
|
+
allModels: allModelViews,
|
|
229
236
|
indexSpans,
|
|
230
237
|
modelIndexesByName,
|
|
231
238
|
} = input;
|
|
232
|
-
const allPslModels = document.ast.namespaces.flatMap((ns) => ns.models);
|
|
233
239
|
let patched = input.models;
|
|
234
240
|
let roots = input.roots;
|
|
235
241
|
let collections = input.collections;
|
|
@@ -249,9 +255,9 @@ function resolvePolymorphism(input: {
|
|
|
249
255
|
const model = patched[modelName];
|
|
250
256
|
if (!model) continue;
|
|
251
257
|
|
|
252
|
-
const
|
|
253
|
-
const mappedDiscriminatorField =
|
|
254
|
-
? (resolveFieldMappings(
|
|
258
|
+
const modelView = allModelViews.find((m) => m.name === modelName);
|
|
259
|
+
const mappedDiscriminatorField = modelView
|
|
260
|
+
? (resolveFieldMappings(modelView).pslNameToMapped.get(decl.fieldName) ?? decl.fieldName)
|
|
255
261
|
: decl.fieldName;
|
|
256
262
|
|
|
257
263
|
if (!Object.hasOwn(model.fields, mappedDiscriminatorField)) {
|
|
@@ -312,9 +318,9 @@ function resolvePolymorphism(input: {
|
|
|
312
318
|
}
|
|
313
319
|
|
|
314
320
|
const baseModel = patched[baseDecl.baseName];
|
|
315
|
-
const
|
|
316
|
-
if (!
|
|
317
|
-
const hasExplicitMap = getMapName(
|
|
321
|
+
const variantModelView = allModelViews.find((m) => m.name === variantName);
|
|
322
|
+
if (!variantModelView) continue;
|
|
323
|
+
const hasExplicitMap = getMapName(variantModelView.attributes) !== undefined;
|
|
318
324
|
|
|
319
325
|
if (hasExplicitMap && baseModel && baseDecl.collectionName !== baseModel.storage.collection) {
|
|
320
326
|
diagnostics.push({
|
|
@@ -339,7 +345,7 @@ function resolvePolymorphism(input: {
|
|
|
339
345
|
};
|
|
340
346
|
}
|
|
341
347
|
|
|
342
|
-
const variantCollectionName = resolveCollectionName(
|
|
348
|
+
const variantCollectionName = resolveCollectionName(variantModelView);
|
|
343
349
|
if (roots[variantCollectionName]?.model === variantName) {
|
|
344
350
|
if (variantCollectionName === baseCollection && baseModel) {
|
|
345
351
|
roots = { ...roots, [variantCollectionName]: mongoCrossRef(baseDecl.baseName) };
|
|
@@ -483,9 +489,7 @@ function parseJsonArg(raw: string | undefined): Record<string, unknown> | undefi
|
|
|
483
489
|
return undefined;
|
|
484
490
|
}
|
|
485
491
|
|
|
486
|
-
function parseCollation(
|
|
487
|
-
attr: import('@prisma-next/psl-parser').PslAttribute,
|
|
488
|
-
): CollationOptions | null | undefined {
|
|
492
|
+
function parseCollation(attr: ResolvedAttribute): CollationOptions | null | undefined {
|
|
489
493
|
const locale = stripQuotesHelper(getNamedArgument(attr, 'collationLocale'));
|
|
490
494
|
if (!locale) {
|
|
491
495
|
const hasAnyCollationArg =
|
|
@@ -545,7 +549,7 @@ function parseProjectionList(
|
|
|
545
549
|
}
|
|
546
550
|
|
|
547
551
|
function collectIndexes(
|
|
548
|
-
pslModel:
|
|
552
|
+
pslModel: ModelSymbol,
|
|
549
553
|
fieldMappings: FieldMappings,
|
|
550
554
|
modelNames: ReadonlySet<string>,
|
|
551
555
|
sourceId: string,
|
|
@@ -561,12 +565,12 @@ function collectIndexes(
|
|
|
561
565
|
// rather than fieldMappings.pslNameToMapped because the latter contains
|
|
562
566
|
// every PSL field including relation fields.
|
|
563
567
|
const indexableFieldNames = new Set<string>();
|
|
564
|
-
for (const f of pslModel.fields) {
|
|
568
|
+
for (const f of Object.values(pslModel.fields)) {
|
|
565
569
|
if (modelNames.has(f.typeName)) continue;
|
|
566
570
|
indexableFieldNames.add(f.name);
|
|
567
571
|
}
|
|
568
572
|
|
|
569
|
-
for (const field of pslModel.fields) {
|
|
573
|
+
for (const field of Object.values(pslModel.fields)) {
|
|
570
574
|
if (modelNames.has(field.typeName)) continue;
|
|
571
575
|
const uniqueAttr = getAttribute(field.attributes, 'unique');
|
|
572
576
|
if (!uniqueAttr) continue;
|
|
@@ -800,7 +804,7 @@ function collectIndexes(
|
|
|
800
804
|
return indexes;
|
|
801
805
|
}
|
|
802
806
|
|
|
803
|
-
function isRelationField(field:
|
|
807
|
+
function isRelationField(field: FieldSymbol, modelNames: ReadonlySet<string>): boolean {
|
|
804
808
|
return modelNames.has(field.typeName);
|
|
805
809
|
}
|
|
806
810
|
|
|
@@ -808,17 +812,18 @@ function isRelationField(field: PslField, modelNames: ReadonlySet<string>): bool
|
|
|
808
812
|
const MONGO_OBJECT_ID_PSL_TYPE = 'ObjectId';
|
|
809
813
|
|
|
810
814
|
function resolveFieldCodecId(
|
|
811
|
-
field:
|
|
815
|
+
field: FieldSymbol,
|
|
812
816
|
scalarTypeDescriptors: ReadonlyMap<string, string>,
|
|
813
817
|
): string | undefined {
|
|
814
818
|
return scalarTypeDescriptors.get(field.typeName);
|
|
815
819
|
}
|
|
816
820
|
|
|
817
821
|
function resolveNonRelationField(
|
|
818
|
-
field:
|
|
822
|
+
field: FieldSymbol,
|
|
819
823
|
ownerName: string,
|
|
820
824
|
compositeTypeNames: ReadonlySet<string>,
|
|
821
825
|
scalarTypeDescriptors: ReadonlyMap<string, string>,
|
|
826
|
+
codecIdByEnumName: ReadonlyMap<string, string>,
|
|
822
827
|
sourceId: string,
|
|
823
828
|
diagnostics: ContractSourceDiagnostic[],
|
|
824
829
|
): ContractField | undefined {
|
|
@@ -830,6 +835,29 @@ function resolveNonRelationField(
|
|
|
830
835
|
return field.list ? { ...result, many: true } : result;
|
|
831
836
|
}
|
|
832
837
|
|
|
838
|
+
// If this field's declared type is a known enum name, treat the field as a scalar
|
|
839
|
+
// with that enum's codec and stamp the domain valueSet ref.
|
|
840
|
+
const enumCodecId = codecIdByEnumName.get(field.typeName);
|
|
841
|
+
if (enumCodecId !== undefined) {
|
|
842
|
+
const valueSet: ValueSetRef = {
|
|
843
|
+
plane: 'domain',
|
|
844
|
+
entityKind: 'enum',
|
|
845
|
+
namespaceId: UNBOUND_NAMESPACE_ID,
|
|
846
|
+
entityName: field.typeName,
|
|
847
|
+
};
|
|
848
|
+
const result: ContractField = {
|
|
849
|
+
type: { kind: 'scalar', codecId: enumCodecId },
|
|
850
|
+
nullable: field.optional,
|
|
851
|
+
valueSet,
|
|
852
|
+
};
|
|
853
|
+
return field.list ? { ...result, many: true } : result;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Avoid cascading unsupported-type diagnostics after invalid qualification.
|
|
857
|
+
if (field.malformedType) {
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
|
|
833
861
|
const codecId = resolveFieldCodecId(field, scalarTypeDescriptors);
|
|
834
862
|
if (!codecId) {
|
|
835
863
|
diagnostics.push({
|
|
@@ -848,27 +876,105 @@ function resolveNonRelationField(
|
|
|
848
876
|
return field.list ? { ...result, many: true } : result;
|
|
849
877
|
}
|
|
850
878
|
|
|
879
|
+
function processEnumDeclarations(input: {
|
|
880
|
+
readonly enumBlocks: readonly PslExtensionBlock[];
|
|
881
|
+
readonly sourceId: string;
|
|
882
|
+
readonly authoringContributions: AuthoringContributions | undefined;
|
|
883
|
+
readonly entityContext: AuthoringEntityContext;
|
|
884
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
885
|
+
}): Record<string, ContractEnum> {
|
|
886
|
+
const builtEnums: Record<string, ContractEnum> = {};
|
|
887
|
+
|
|
888
|
+
if (input.enumBlocks.length === 0) return builtEnums;
|
|
889
|
+
|
|
890
|
+
const enumDescriptor =
|
|
891
|
+
input.authoringContributions?.entityTypes?.['enum'] !== undefined &&
|
|
892
|
+
isAuthoringEntityTypeDescriptor(input.authoringContributions.entityTypes['enum'])
|
|
893
|
+
? input.authoringContributions.entityTypes['enum']
|
|
894
|
+
: undefined;
|
|
895
|
+
|
|
896
|
+
if (!enumDescriptor) {
|
|
897
|
+
for (const decl of input.enumBlocks) {
|
|
898
|
+
input.diagnostics.push({
|
|
899
|
+
code: 'PSL_ENUM_MISSING_FACTORY',
|
|
900
|
+
message: `enum "${decl.name}" requires an "enum" entityType factory in the active authoring contributions`,
|
|
901
|
+
sourceId: input.sourceId,
|
|
902
|
+
span: decl.span,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
return builtEnums;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
for (const decl of input.enumBlocks) {
|
|
909
|
+
const handle = instantiateAuthoringEntityType<EnumTypeHandle | undefined>(
|
|
910
|
+
'enum',
|
|
911
|
+
enumDescriptor,
|
|
912
|
+
[decl],
|
|
913
|
+
input.entityContext,
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
if (handle === undefined || handle === null) continue;
|
|
917
|
+
|
|
918
|
+
builtEnums[decl.name] = {
|
|
919
|
+
codecId: handle.codecId,
|
|
920
|
+
members: handle.enumMembers.map((m) => ({
|
|
921
|
+
name: m.name,
|
|
922
|
+
value: blindCast<JsonValue, 'factory-validated enum members are JsonValue-compatible'>(
|
|
923
|
+
m.value,
|
|
924
|
+
),
|
|
925
|
+
})),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return builtEnums;
|
|
930
|
+
}
|
|
931
|
+
|
|
851
932
|
export function interpretPslDocumentToMongoContract(
|
|
852
933
|
input: InterpretPslDocumentToMongoContractInput,
|
|
853
934
|
): Result<Contract, ContractSourceDiagnostics> {
|
|
854
|
-
const {
|
|
855
|
-
const sourceId =
|
|
856
|
-
const diagnostics: ContractSourceDiagnostic[] = [];
|
|
935
|
+
const { symbolTable, sourceFile, scalarTypeDescriptors, codecLookup } = input;
|
|
936
|
+
const sourceId = input.sourceId;
|
|
937
|
+
const diagnostics: ContractSourceDiagnostic[] = [...(input.seedDiagnostics ?? [])];
|
|
938
|
+
const topLevel = symbolTable.topLevel;
|
|
857
939
|
validateNamespaceBlocksForMongoTarget({
|
|
858
|
-
namespaces:
|
|
940
|
+
namespaces: Object.values(topLevel.namespaces),
|
|
859
941
|
sourceId,
|
|
942
|
+
sourceFile,
|
|
860
943
|
diagnostics,
|
|
861
944
|
});
|
|
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);
|
|
945
|
+
const allModels: ModelSymbol[] = Object.values(topLevel.models);
|
|
946
|
+
const allCompositeTypes: CompositeTypeSymbol[] = Object.values(topLevel.compositeTypes);
|
|
869
947
|
const modelNames = new Set(allModels.map((m) => m.name));
|
|
870
948
|
const compositeTypeNames = new Set(allCompositeTypes.map((ct) => ct.name));
|
|
871
949
|
|
|
950
|
+
const topLevelEnumBlocks = Object.values(topLevel.blocks)
|
|
951
|
+
.filter((b) => b.keyword === 'enum')
|
|
952
|
+
.map((b) => b.block);
|
|
953
|
+
|
|
954
|
+
const builtEnums = processEnumDeclarations({
|
|
955
|
+
enumBlocks: topLevelEnumBlocks,
|
|
956
|
+
sourceId,
|
|
957
|
+
authoringContributions: input.authoringContributions,
|
|
958
|
+
entityContext: {
|
|
959
|
+
family: 'mongo',
|
|
960
|
+
target: 'mongo',
|
|
961
|
+
...ifDefined('codecLookup', codecLookup),
|
|
962
|
+
sourceId,
|
|
963
|
+
diagnostics: {
|
|
964
|
+
push: (d) => {
|
|
965
|
+
diagnostics.push(
|
|
966
|
+
blindCast<ContractSourceDiagnostic, 'sink diagnostics are span-compatible'>(d),
|
|
967
|
+
);
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
diagnostics,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
const codecIdByEnumName: Map<string, string> = new Map(
|
|
975
|
+
Object.entries(builtEnums).map(([name, e]) => [name, e.codecId]),
|
|
976
|
+
);
|
|
977
|
+
|
|
872
978
|
const models: Record<string, MongoModelEntry> = {};
|
|
873
979
|
const collections: Record<string, Record<string, unknown>> = {};
|
|
874
980
|
const roots: Record<string, CrossReference> = {};
|
|
@@ -882,7 +988,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
882
988
|
readonly targetModelName: string;
|
|
883
989
|
readonly relationName?: string;
|
|
884
990
|
readonly cardinality: '1:1' | '1:N';
|
|
885
|
-
readonly field:
|
|
991
|
+
readonly field: FieldSymbol;
|
|
886
992
|
}
|
|
887
993
|
const backrelationCandidates: BackrelationCandidate[] = [];
|
|
888
994
|
|
|
@@ -893,7 +999,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
893
999
|
const fields: Record<string, ContractField> = {};
|
|
894
1000
|
const relations: Record<string, ContractReferenceRelation> = {};
|
|
895
1001
|
|
|
896
|
-
for (const field of pslModel.fields) {
|
|
1002
|
+
for (const field of Object.values(pslModel.fields)) {
|
|
897
1003
|
if (isRelationField(field, modelNames)) {
|
|
898
1004
|
const relation = parseRelationAttribute(field.attributes);
|
|
899
1005
|
|
|
@@ -902,9 +1008,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
902
1008
|
modelName: pslModel.name,
|
|
903
1009
|
fieldName: field.name,
|
|
904
1010
|
targetModelName: field.typeName,
|
|
905
|
-
...(relation?.relationName
|
|
906
|
-
? { relationName: relation.relationName }
|
|
907
|
-
: {}),
|
|
1011
|
+
...ifDefined('relationName', relation?.relationName),
|
|
908
1012
|
cardinality: field.list ? '1:N' : '1:1',
|
|
909
1013
|
field,
|
|
910
1014
|
});
|
|
@@ -933,7 +1037,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
933
1037
|
declaringModel: pslModel.name,
|
|
934
1038
|
fieldName: field.name,
|
|
935
1039
|
targetModel: field.typeName,
|
|
936
|
-
...(
|
|
1040
|
+
...ifDefined('relationName', relation.relationName),
|
|
937
1041
|
localFields: localMapped,
|
|
938
1042
|
targetFields: targetMapped,
|
|
939
1043
|
});
|
|
@@ -946,6 +1050,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
946
1050
|
pslModel.name,
|
|
947
1051
|
compositeTypeNames,
|
|
948
1052
|
scalarTypeDescriptors,
|
|
1053
|
+
codecIdByEnumName,
|
|
949
1054
|
sourceId,
|
|
950
1055
|
diagnostics,
|
|
951
1056
|
);
|
|
@@ -956,7 +1061,9 @@ export function interpretPslDocumentToMongoContract(
|
|
|
956
1061
|
}
|
|
957
1062
|
|
|
958
1063
|
const isVariantModel = pslModel.attributes.some((attr) => attr.name === 'base');
|
|
959
|
-
const hasIdField = pslModel.fields.some(
|
|
1064
|
+
const hasIdField = Object.values(pslModel.fields).some(
|
|
1065
|
+
(f) => getAttribute(f.attributes, 'id') !== undefined,
|
|
1066
|
+
);
|
|
960
1067
|
// Variant models inherit the base's identity and are validated through their base.
|
|
961
1068
|
if (!isVariantModel) {
|
|
962
1069
|
if (!hasIdField) {
|
|
@@ -1011,12 +1118,13 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1011
1118
|
const valueObjects: Record<string, ContractValueObject> = {};
|
|
1012
1119
|
for (const compositeType of allCompositeTypes) {
|
|
1013
1120
|
const fields: Record<string, ContractField> = {};
|
|
1014
|
-
for (const field of compositeType.fields) {
|
|
1121
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
1015
1122
|
const resolved = resolveNonRelationField(
|
|
1016
1123
|
field,
|
|
1017
1124
|
compositeType.name,
|
|
1018
1125
|
compositeTypeNames,
|
|
1019
1126
|
scalarTypeDescriptors,
|
|
1127
|
+
codecIdByEnumName,
|
|
1020
1128
|
sourceId,
|
|
1021
1129
|
diagnostics,
|
|
1022
1130
|
);
|
|
@@ -1078,7 +1186,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1078
1186
|
}
|
|
1079
1187
|
|
|
1080
1188
|
const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(
|
|
1081
|
-
|
|
1189
|
+
allModels,
|
|
1082
1190
|
sourceId,
|
|
1083
1191
|
diagnostics,
|
|
1084
1192
|
);
|
|
@@ -1086,7 +1194,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1086
1194
|
models,
|
|
1087
1195
|
roots,
|
|
1088
1196
|
collections,
|
|
1089
|
-
|
|
1197
|
+
allModels,
|
|
1090
1198
|
discriminatorDeclarations,
|
|
1091
1199
|
baseDeclarations,
|
|
1092
1200
|
modelNames,
|
|
@@ -1125,9 +1233,15 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1125
1233
|
variantEntries,
|
|
1126
1234
|
valueObjects,
|
|
1127
1235
|
codecLookup,
|
|
1236
|
+
builtEnums,
|
|
1128
1237
|
);
|
|
1129
1238
|
} else {
|
|
1130
|
-
coll['validator'] = deriveJsonSchema(
|
|
1239
|
+
coll['validator'] = deriveJsonSchema(
|
|
1240
|
+
modelEntry.fields,
|
|
1241
|
+
valueObjects,
|
|
1242
|
+
codecLookup,
|
|
1243
|
+
builtEnums,
|
|
1244
|
+
);
|
|
1131
1245
|
}
|
|
1132
1246
|
}
|
|
1133
1247
|
|
|
@@ -1173,6 +1287,8 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1173
1287
|
}) as Contract['storage'];
|
|
1174
1288
|
const capabilities: Record<string, Record<string, boolean>> = {};
|
|
1175
1289
|
|
|
1290
|
+
const hasEnums = Object.keys(builtEnums).length > 0;
|
|
1291
|
+
|
|
1176
1292
|
return ok({
|
|
1177
1293
|
targetFamily,
|
|
1178
1294
|
target,
|
|
@@ -1182,6 +1298,7 @@ export function interpretPslDocumentToMongoContract(
|
|
|
1182
1298
|
[UNBOUND_NAMESPACE_ID]: {
|
|
1183
1299
|
models: polyResult.models,
|
|
1184
1300
|
...(Object.keys(valueObjects).length > 0 ? { valueObjects } : {}),
|
|
1301
|
+
...(hasEnums ? { enum: builtEnums } : {}),
|
|
1185
1302
|
},
|
|
1186
1303
|
},
|
|
1187
1304
|
},
|
package/src/provider.ts
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
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: {
|
|
31
|
+
sourceFormat: 'psl',
|
|
15
32
|
inputs: [schemaPath],
|
|
16
33
|
load: async (context) => {
|
|
17
34
|
const [absoluteSchemaPath] = context.resolvedInputs;
|
|
@@ -38,15 +55,29 @@ export function mongoContract(schemaPath: string, options?: MongoContractOptions
|
|
|
38
55
|
});
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
const document =
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
44
64
|
});
|
|
45
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
|
+
|
|
46
73
|
const interpreted = interpretPslDocumentToMongoContract({
|
|
47
|
-
|
|
74
|
+
symbolTable,
|
|
75
|
+
sourceFile,
|
|
76
|
+
sourceId: schemaPath,
|
|
77
|
+
seedDiagnostics,
|
|
48
78
|
scalarTypeDescriptors: context.scalarTypeDescriptors,
|
|
49
79
|
codecLookup: context.codecLookup,
|
|
80
|
+
authoringContributions: context.authoringContributions,
|
|
50
81
|
});
|
|
51
82
|
if (!interpreted.ok) {
|
|
52
83
|
return interpreted;
|