@prisma-next/sql-contract-psl 0.14.0-dev.1 → 0.14.0-dev.10
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/README.md +11 -7
- package/dist/index.d.mts +7 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-B0BsCLKT.mjs → interpreter-CygvamTk.mjs} +435 -232
- package/dist/interpreter-CygvamTk.mjs.map +1 -0
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +22 -7
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -12
- package/src/interpreter.ts +151 -323
- package/src/provider.ts +38 -9
- package/src/psl-attribute-parsing.ts +18 -14
- package/src/psl-authoring-arguments.ts +2 -2
- package/src/psl-column-resolution.ts +17 -15
- package/src/psl-field-resolution.ts +28 -20
- package/src/psl-named-type-resolution.ts +250 -0
- package/src/psl-relation-resolution.ts +250 -11
- package/dist/interpreter-B0BsCLKT.mjs.map +0 -1
package/src/interpreter.ts
CHANGED
|
@@ -14,8 +14,13 @@ import { crossRef } from '@prisma-next/contract/types';
|
|
|
14
14
|
import type {
|
|
15
15
|
AuthoringContributions,
|
|
16
16
|
AuthoringEntityContext,
|
|
17
|
+
AuthoringPslBlockDescriptorNamespace,
|
|
18
|
+
PslExtensionBlock,
|
|
19
|
+
} from '@prisma-next/framework-components/authoring';
|
|
20
|
+
import {
|
|
21
|
+
instantiateAuthoringEntityType,
|
|
22
|
+
isAuthoringPslBlockDescriptor,
|
|
17
23
|
} from '@prisma-next/framework-components/authoring';
|
|
18
|
-
import { instantiateAuthoringEntityType } from '@prisma-next/framework-components/authoring';
|
|
19
24
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
20
25
|
import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
|
|
21
26
|
import type {
|
|
@@ -24,22 +29,21 @@ import type {
|
|
|
24
29
|
MutationDefaultGeneratorDescriptor,
|
|
25
30
|
} from '@prisma-next/framework-components/control';
|
|
26
31
|
import type { Namespace } from '@prisma-next/framework-components/ir';
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
import {
|
|
33
|
+
type BlockSymbol,
|
|
34
|
+
type CompositeTypeSymbol,
|
|
35
|
+
type FieldSymbol,
|
|
36
|
+
keywordPslSpan,
|
|
37
|
+
type ModelSymbol,
|
|
38
|
+
type NamespaceSymbol,
|
|
39
|
+
nodePslSpan,
|
|
40
|
+
type ResolvedAttribute,
|
|
41
|
+
type ScalarSymbol,
|
|
42
|
+
type SymbolTable,
|
|
43
|
+
type TypeAliasSymbol,
|
|
37
44
|
} from '@prisma-next/psl-parser';
|
|
38
|
-
import type {
|
|
39
|
-
|
|
40
|
-
SqlNamespaceTablesInput,
|
|
41
|
-
StorageTypeInstance,
|
|
42
|
-
} from '@prisma-next/sql-contract/types';
|
|
45
|
+
import type { SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
46
|
+
import type { SqlModelStorage, SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';
|
|
43
47
|
import {
|
|
44
48
|
buildSqlContractFromDefinition,
|
|
45
49
|
type EnumTypeHandle,
|
|
@@ -51,9 +55,11 @@ import {
|
|
|
51
55
|
type RelationNode,
|
|
52
56
|
type UniqueConstraintNode,
|
|
53
57
|
} from '@prisma-next/sql-contract-ts/contract-builder';
|
|
58
|
+
import { invariant } from '@prisma-next/utils/assertions';
|
|
54
59
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
55
60
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
56
61
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
62
|
+
|
|
57
63
|
import {
|
|
58
64
|
findDuplicateFieldName,
|
|
59
65
|
getAttribute,
|
|
@@ -70,12 +76,8 @@ import type { ColumnDescriptor } from './psl-column-resolution';
|
|
|
70
76
|
import {
|
|
71
77
|
checkUncomposedNamespace,
|
|
72
78
|
getAuthoringEntity,
|
|
73
|
-
instantiatePslTypeConstructor,
|
|
74
79
|
reportUncomposedNamespace,
|
|
75
|
-
resolveDbNativeTypeAttribute,
|
|
76
80
|
resolveFieldTypeDescriptor,
|
|
77
|
-
resolvePslTypeConstructorDescriptor,
|
|
78
|
-
toNamedTypeFieldDescriptor,
|
|
79
81
|
} from './psl-column-resolution';
|
|
80
82
|
import {
|
|
81
83
|
buildModelMappings,
|
|
@@ -85,6 +87,7 @@ import {
|
|
|
85
87
|
modelCoordinateKey,
|
|
86
88
|
type ResolvedField,
|
|
87
89
|
} from './psl-field-resolution';
|
|
90
|
+
import { resolveNamedTypeDeclarations } from './psl-named-type-resolution';
|
|
88
91
|
import {
|
|
89
92
|
applyBackrelationCandidates,
|
|
90
93
|
type FkRelationMetadata,
|
|
@@ -95,8 +98,12 @@ import {
|
|
|
95
98
|
validateNavigationListFieldAttributes,
|
|
96
99
|
} from './psl-relation-resolution';
|
|
97
100
|
|
|
101
|
+
type NamedTypeSymbol = ScalarSymbol | TypeAliasSymbol;
|
|
102
|
+
|
|
98
103
|
export interface InterpretPslDocumentToSqlContractInput {
|
|
99
|
-
readonly
|
|
104
|
+
readonly symbolTable: SymbolTable;
|
|
105
|
+
readonly sourceFile: SourceFile;
|
|
106
|
+
readonly sourceId: string;
|
|
100
107
|
readonly target: TargetPackRef<'sql', string>;
|
|
101
108
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
102
109
|
readonly composedExtensionPacks?: readonly string[];
|
|
@@ -124,6 +131,7 @@ export interface InterpretPslDocumentToSqlContractInput {
|
|
|
124
131
|
*/
|
|
125
132
|
readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
|
|
126
133
|
readonly codecLookup?: CodecLookup;
|
|
134
|
+
readonly seedDiagnostics?: readonly ContractSourceDiagnostic[];
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
function buildComposedExtensionPackRefs(
|
|
@@ -152,27 +160,6 @@ function buildComposedExtensionPackRefs(
|
|
|
152
160
|
);
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
function diagnosticDedupKey(diagnostic: ContractSourceDiagnostic): string {
|
|
156
|
-
const span = diagnostic.span;
|
|
157
|
-
const spanKey = span
|
|
158
|
-
? `${span.start.offset}:${span.end.offset}:${span.start.line}:${span.end.line}`
|
|
159
|
-
: '';
|
|
160
|
-
return `${diagnostic.code}\u0000${diagnostic.sourceId}\u0000${spanKey}\u0000${diagnostic.message}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function dedupeDiagnostics(
|
|
164
|
-
diagnostics: readonly ContractSourceDiagnostic[],
|
|
165
|
-
): ContractSourceDiagnostic[] {
|
|
166
|
-
const seen = new Map<string, ContractSourceDiagnostic>();
|
|
167
|
-
for (const diagnostic of diagnostics) {
|
|
168
|
-
const key = diagnosticDedupKey(diagnostic);
|
|
169
|
-
if (!seen.has(key)) {
|
|
170
|
-
seen.set(key, diagnostic);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return [...seen.values()];
|
|
174
|
-
}
|
|
175
|
-
|
|
176
163
|
function compareStrings(left: string, right: string): -1 | 0 | 1 {
|
|
177
164
|
if (left < right) {
|
|
178
165
|
return -1;
|
|
@@ -183,15 +170,6 @@ function compareStrings(left: string, right: string): -1 | 0 | 1 {
|
|
|
183
170
|
return 0;
|
|
184
171
|
}
|
|
185
172
|
|
|
186
|
-
function mapParserDiagnostics(document: ParsePslDocumentResult): ContractSourceDiagnostic[] {
|
|
187
|
-
return document.diagnostics.map((diagnostic) => ({
|
|
188
|
-
code: diagnostic.code,
|
|
189
|
-
message: diagnostic.message,
|
|
190
|
-
sourceId: diagnostic.sourceId,
|
|
191
|
-
span: diagnostic.span,
|
|
192
|
-
}));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
173
|
/**
|
|
196
174
|
* Name of the framework-parser synthesised bucket for top-level
|
|
197
175
|
* declarations. Re-declared here so the per-target dispatch does not
|
|
@@ -263,39 +241,38 @@ function resolveNamespaceIdForSqlTarget(input: {
|
|
|
263
241
|
}
|
|
264
242
|
|
|
265
243
|
function validateNamespaceBlocksForSqlTarget(input: {
|
|
266
|
-
readonly namespaces: readonly
|
|
244
|
+
readonly namespaces: readonly NamespaceSymbol[];
|
|
267
245
|
readonly targetId: string;
|
|
268
246
|
readonly sourceId: string;
|
|
247
|
+
readonly sourceFile: SourceFile;
|
|
269
248
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
270
249
|
}): void {
|
|
271
250
|
if (input.targetId === 'sqlite') {
|
|
272
251
|
for (const namespace of input.namespaces) {
|
|
273
|
-
if (namespace.name === UNSPECIFIED_PSL_NAMESPACE_NAME) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
252
|
input.diagnostics.push({
|
|
277
253
|
code: 'PSL_UNSUPPORTED_NAMESPACE_BLOCK',
|
|
278
254
|
message: `SQLite does not support \`namespace ${namespace.name} { … }\` blocks (SQLite has no schema concept; declare models at the document top level instead).`,
|
|
279
255
|
sourceId: input.sourceId,
|
|
280
|
-
span: namespace.
|
|
256
|
+
span: nodePslSpan(namespace.node.syntax, input.sourceFile),
|
|
281
257
|
});
|
|
282
258
|
}
|
|
283
259
|
return;
|
|
284
260
|
}
|
|
285
261
|
|
|
286
262
|
if (input.targetId === 'postgres') {
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const hasSibling = namedBlocks.some((ns) => ns.name !== 'unbound');
|
|
263
|
+
const hasUnbound = input.namespaces.some((ns) => ns.name === 'unbound');
|
|
264
|
+
const hasSibling = input.namespaces.some((ns) => ns.name !== 'unbound');
|
|
290
265
|
if (hasUnbound && hasSibling) {
|
|
291
|
-
const unboundBlock =
|
|
266
|
+
const unboundBlock = input.namespaces.find((ns) => ns.name === 'unbound');
|
|
292
267
|
input.diagnostics.push({
|
|
293
268
|
code: 'PSL_RESERVED_NAMESPACE_NAME',
|
|
294
269
|
message:
|
|
295
270
|
'Namespace "unbound" is reserved for the late-binding sentinel mapping and cannot appear alongside other named namespace blocks. ' +
|
|
296
271
|
'Use `namespace unbound { … }` alone (no sibling named namespaces) for late-binding multi-tenant contracts.',
|
|
297
272
|
sourceId: input.sourceId,
|
|
298
|
-
...
|
|
273
|
+
...(unboundBlock !== undefined
|
|
274
|
+
? { span: nodePslSpan(unboundBlock.node.syntax, input.sourceFile) }
|
|
275
|
+
: {}),
|
|
299
276
|
});
|
|
300
277
|
}
|
|
301
278
|
}
|
|
@@ -354,225 +331,23 @@ function processEnumDeclarations(input: ProcessEnumDeclarationsInput): {
|
|
|
354
331
|
return { enumHandles, enumTypeDescriptors };
|
|
355
332
|
}
|
|
356
333
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function validateNamedTypeAttributes(input: {
|
|
370
|
-
readonly declaration: PslNamedTypeDeclaration;
|
|
371
|
-
readonly sourceId: string;
|
|
372
|
-
readonly diagnostics: ContractSourceDiagnostic[];
|
|
373
|
-
readonly composedExtensions: ReadonlySet<string>;
|
|
374
|
-
readonly authoringContributions: AuthoringContributions | undefined;
|
|
375
|
-
readonly allowDbNativeType: boolean;
|
|
376
|
-
readonly familyId: string;
|
|
377
|
-
readonly targetId: string;
|
|
378
|
-
}): {
|
|
379
|
-
readonly dbNativeTypeAttribute: PslAttribute | undefined;
|
|
380
|
-
readonly hasUnsupportedNamedTypeAttribute: boolean;
|
|
381
|
-
} {
|
|
382
|
-
const dbNativeTypeAttributes = input.allowDbNativeType
|
|
383
|
-
? input.declaration.attributes.filter((attribute) => attribute.name.startsWith('db.'))
|
|
384
|
-
: [];
|
|
385
|
-
const [dbNativeTypeAttribute, ...extraDbNativeTypeAttributes] = dbNativeTypeAttributes;
|
|
386
|
-
let hasUnsupportedNamedTypeAttribute = false;
|
|
387
|
-
|
|
388
|
-
for (const extra of extraDbNativeTypeAttributes) {
|
|
389
|
-
input.diagnostics.push({
|
|
390
|
-
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
391
|
-
message: `Named type "${input.declaration.name}" can declare at most one @db.* attribute`,
|
|
392
|
-
sourceId: input.sourceId,
|
|
393
|
-
span: extra.span,
|
|
394
|
-
});
|
|
395
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
for (const attribute of input.declaration.attributes) {
|
|
399
|
-
if (input.allowDbNativeType && attribute.name.startsWith('db.')) {
|
|
400
|
-
continue;
|
|
334
|
+
/** Generic top-level blocks are supported only when a composed descriptor claims their keyword. */
|
|
335
|
+
function composedBlockKeywords(
|
|
336
|
+
authoringContributions: AuthoringContributions | undefined,
|
|
337
|
+
): ReadonlySet<string> {
|
|
338
|
+
const keywords = new Set<string>();
|
|
339
|
+
const descriptors: AuthoringPslBlockDescriptorNamespace =
|
|
340
|
+
authoringContributions?.pslBlockDescriptors ?? {};
|
|
341
|
+
for (const [keyword, value] of Object.entries(descriptors)) {
|
|
342
|
+
if (isAuthoringPslBlockDescriptor(value)) {
|
|
343
|
+
keywords.add(keyword);
|
|
401
344
|
}
|
|
402
|
-
|
|
403
|
-
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
404
|
-
familyId: input.familyId,
|
|
405
|
-
targetId: input.targetId,
|
|
406
|
-
authoringContributions: input.authoringContributions,
|
|
407
|
-
});
|
|
408
|
-
if (uncomposedNamespace) {
|
|
409
|
-
reportUncomposedNamespace({
|
|
410
|
-
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
411
|
-
namespace: uncomposedNamespace,
|
|
412
|
-
sourceId: input.sourceId,
|
|
413
|
-
span: attribute.span,
|
|
414
|
-
diagnostics: input.diagnostics,
|
|
415
|
-
});
|
|
416
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
input.diagnostics.push({
|
|
421
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE',
|
|
422
|
-
message: `Named type "${input.declaration.name}" uses unsupported attribute "${attribute.name}"`,
|
|
423
|
-
sourceId: input.sourceId,
|
|
424
|
-
span: attribute.span,
|
|
425
|
-
});
|
|
426
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
427
345
|
}
|
|
428
|
-
|
|
429
|
-
return { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute };
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function resolveNamedTypeDeclarations(input: ResolveNamedTypeDeclarationsInput): {
|
|
433
|
-
readonly storageTypes: Record<string, StorageTypeInstance>;
|
|
434
|
-
readonly namedTypeDescriptors: Map<string, ColumnDescriptor>;
|
|
435
|
-
} {
|
|
436
|
-
const storageTypes: Record<string, StorageTypeInstance> = {};
|
|
437
|
-
const namedTypeDescriptors = new Map<string, ColumnDescriptor>();
|
|
438
|
-
|
|
439
|
-
for (const declaration of input.declarations) {
|
|
440
|
-
if (declaration.typeConstructor) {
|
|
441
|
-
const { hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
442
|
-
declaration,
|
|
443
|
-
sourceId: input.sourceId,
|
|
444
|
-
diagnostics: input.diagnostics,
|
|
445
|
-
composedExtensions: input.composedExtensions,
|
|
446
|
-
authoringContributions: input.authoringContributions,
|
|
447
|
-
allowDbNativeType: false,
|
|
448
|
-
familyId: input.familyId,
|
|
449
|
-
targetId: input.targetId,
|
|
450
|
-
});
|
|
451
|
-
if (hasUnsupportedNamedTypeAttribute) {
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const helperPath = declaration.typeConstructor.path.join('.');
|
|
456
|
-
const typeConstructor = resolvePslTypeConstructorDescriptor({
|
|
457
|
-
call: declaration.typeConstructor,
|
|
458
|
-
authoringContributions: input.authoringContributions,
|
|
459
|
-
composedExtensions: input.composedExtensions,
|
|
460
|
-
familyId: input.familyId,
|
|
461
|
-
targetId: input.targetId,
|
|
462
|
-
diagnostics: input.diagnostics,
|
|
463
|
-
sourceId: input.sourceId,
|
|
464
|
-
unsupportedCode: 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR',
|
|
465
|
-
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`,
|
|
466
|
-
});
|
|
467
|
-
if (!typeConstructor) {
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const storageType = instantiatePslTypeConstructor({
|
|
472
|
-
call: declaration.typeConstructor,
|
|
473
|
-
descriptor: typeConstructor,
|
|
474
|
-
diagnostics: input.diagnostics,
|
|
475
|
-
sourceId: input.sourceId,
|
|
476
|
-
entityLabel: `Named type "${declaration.name}"`,
|
|
477
|
-
});
|
|
478
|
-
if (!storageType) {
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
namedTypeDescriptors.set(
|
|
483
|
-
declaration.name,
|
|
484
|
-
toNamedTypeFieldDescriptor(declaration.name, storageType),
|
|
485
|
-
);
|
|
486
|
-
storageTypes[declaration.name] = {
|
|
487
|
-
kind: 'codec-instance',
|
|
488
|
-
codecId: storageType.codecId,
|
|
489
|
-
nativeType: storageType.nativeType,
|
|
490
|
-
typeParams: storageType.typeParams ?? {},
|
|
491
|
-
};
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Parser invariant: when typeConstructor is absent, baseType is defined.
|
|
496
|
-
// The check below narrows `baseType` for TypeScript and guards against a
|
|
497
|
-
// parser regression; it is unreachable under a correct parser.
|
|
498
|
-
if (declaration.baseType === undefined) {
|
|
499
|
-
input.diagnostics.push({
|
|
500
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_BASE',
|
|
501
|
-
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
502
|
-
sourceId: input.sourceId,
|
|
503
|
-
span: declaration.span,
|
|
504
|
-
});
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
const { baseType } = declaration;
|
|
508
|
-
const baseDescriptor =
|
|
509
|
-
input.enumTypeDescriptors.get(baseType) ?? input.scalarTypeDescriptors.get(baseType);
|
|
510
|
-
if (!baseDescriptor) {
|
|
511
|
-
input.diagnostics.push({
|
|
512
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_BASE',
|
|
513
|
-
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
514
|
-
sourceId: input.sourceId,
|
|
515
|
-
span: declaration.span,
|
|
516
|
-
});
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes(
|
|
521
|
-
{
|
|
522
|
-
declaration,
|
|
523
|
-
sourceId: input.sourceId,
|
|
524
|
-
diagnostics: input.diagnostics,
|
|
525
|
-
composedExtensions: input.composedExtensions,
|
|
526
|
-
authoringContributions: input.authoringContributions,
|
|
527
|
-
allowDbNativeType: true,
|
|
528
|
-
familyId: input.familyId,
|
|
529
|
-
targetId: input.targetId,
|
|
530
|
-
},
|
|
531
|
-
);
|
|
532
|
-
if (hasUnsupportedNamedTypeAttribute) {
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (dbNativeTypeAttribute) {
|
|
537
|
-
const descriptor = resolveDbNativeTypeAttribute({
|
|
538
|
-
attribute: dbNativeTypeAttribute,
|
|
539
|
-
baseType,
|
|
540
|
-
baseDescriptor,
|
|
541
|
-
diagnostics: input.diagnostics,
|
|
542
|
-
sourceId: input.sourceId,
|
|
543
|
-
entityLabel: `Named type "${declaration.name}"`,
|
|
544
|
-
});
|
|
545
|
-
if (!descriptor) {
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
namedTypeDescriptors.set(
|
|
549
|
-
declaration.name,
|
|
550
|
-
toNamedTypeFieldDescriptor(declaration.name, descriptor),
|
|
551
|
-
);
|
|
552
|
-
storageTypes[declaration.name] = {
|
|
553
|
-
kind: 'codec-instance',
|
|
554
|
-
codecId: descriptor.codecId,
|
|
555
|
-
nativeType: descriptor.nativeType,
|
|
556
|
-
typeParams: descriptor.typeParams ?? {},
|
|
557
|
-
};
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
562
|
-
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
563
|
-
storageTypes[declaration.name] = {
|
|
564
|
-
kind: 'codec-instance',
|
|
565
|
-
codecId: baseDescriptor.codecId,
|
|
566
|
-
nativeType: baseDescriptor.nativeType,
|
|
567
|
-
typeParams: {},
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return { storageTypes, namedTypeDescriptors };
|
|
346
|
+
return keywords;
|
|
572
347
|
}
|
|
573
348
|
|
|
574
349
|
interface BuildModelNodeInput {
|
|
575
|
-
readonly model:
|
|
350
|
+
readonly model: ModelSymbol;
|
|
576
351
|
readonly mapping: ModelNameMapping;
|
|
577
352
|
readonly modelMappings: ReadonlyMap<string, ModelNameMapping>;
|
|
578
353
|
/**
|
|
@@ -655,7 +430,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
655
430
|
let controlPolicy: ControlPolicy | undefined;
|
|
656
431
|
|
|
657
432
|
const resultBackrelationCandidates: ModelBackrelationCandidate[] = [];
|
|
658
|
-
for (const field of model.fields) {
|
|
433
|
+
for (const field of Object.values(model.fields)) {
|
|
659
434
|
if (!field.list || !input.modelNames.has(field.typeName)) {
|
|
660
435
|
continue;
|
|
661
436
|
}
|
|
@@ -715,12 +490,12 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
715
490
|
});
|
|
716
491
|
}
|
|
717
492
|
|
|
718
|
-
const relationAttributes = model.fields
|
|
493
|
+
const relationAttributes = Object.values(model.fields)
|
|
719
494
|
.map((field) => ({
|
|
720
495
|
field,
|
|
721
496
|
relation: getAttribute(field.attributes, 'relation'),
|
|
722
497
|
}))
|
|
723
|
-
.filter((entry): entry is { field:
|
|
498
|
+
.filter((entry): entry is { field: FieldSymbol; relation: ResolvedAttribute } =>
|
|
724
499
|
Boolean(entry.relation),
|
|
725
500
|
);
|
|
726
501
|
const uniqueConstraints: UniqueConstraintNode[] = resolvedFields
|
|
@@ -801,9 +576,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
801
576
|
});
|
|
802
577
|
continue;
|
|
803
578
|
}
|
|
804
|
-
const nullableFieldName = fieldNames.find(
|
|
805
|
-
(name) => model.fields.find((f) => f.name === name)?.optional === true,
|
|
806
|
-
);
|
|
579
|
+
const nullableFieldName = fieldNames.find((name) => model.fields[name]?.optional === true);
|
|
807
580
|
if (nullableFieldName !== undefined) {
|
|
808
581
|
diagnostics.push({
|
|
809
582
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
@@ -1288,6 +1061,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1288
1061
|
declaringModelName: model.name,
|
|
1289
1062
|
declaringFieldName: relationAttribute.field.name,
|
|
1290
1063
|
declaringTableName: tableName,
|
|
1064
|
+
...ifDefined('declaringNamespaceId', input.modelNamespaceIds.get(model.name)),
|
|
1291
1065
|
targetModelName: targetMapping.model.name,
|
|
1292
1066
|
targetTableName: targetMapping.tableName,
|
|
1293
1067
|
...ifDefined('targetNamespaceId', targetNamespaceId),
|
|
@@ -1307,7 +1081,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1307
1081
|
fieldName: resolvedField.field.name,
|
|
1308
1082
|
columnName: resolvedField.columnName,
|
|
1309
1083
|
descriptor: resolvedField.descriptor,
|
|
1310
|
-
nullable: resolvedField.
|
|
1084
|
+
nullable: resolvedField.nullable,
|
|
1311
1085
|
...ifDefined('default', resolvedField.defaultValue),
|
|
1312
1086
|
...ifDefined('executionDefaults', resolvedField.executionDefaults),
|
|
1313
1087
|
...ifDefined('enumTypeHandle', enumHandle),
|
|
@@ -1327,7 +1101,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1327
1101
|
}
|
|
1328
1102
|
|
|
1329
1103
|
interface BuildValueObjectsInput {
|
|
1330
|
-
readonly compositeTypes: readonly
|
|
1104
|
+
readonly compositeTypes: readonly CompositeTypeSymbol[];
|
|
1331
1105
|
readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
1332
1106
|
readonly namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
1333
1107
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
@@ -1357,7 +1131,7 @@ function buildValueObjects(input: BuildValueObjectsInput): Record<string, Contra
|
|
|
1357
1131
|
|
|
1358
1132
|
for (const compositeType of compositeTypes) {
|
|
1359
1133
|
const fields: Record<string, ContractField> = {};
|
|
1360
|
-
for (const field of compositeType.fields) {
|
|
1134
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
1361
1135
|
if (compositeTypeNames.has(field.typeName)) {
|
|
1362
1136
|
const result: ContractField = {
|
|
1363
1137
|
type: { kind: 'valueObject', name: field.typeName },
|
|
@@ -1453,7 +1227,7 @@ type BaseDeclaration = {
|
|
|
1453
1227
|
};
|
|
1454
1228
|
|
|
1455
1229
|
function collectPolymorphismDeclarations(
|
|
1456
|
-
models: readonly
|
|
1230
|
+
models: readonly ModelSymbol[],
|
|
1457
1231
|
sourceId: string,
|
|
1458
1232
|
diagnostics: ContractSourceDiagnostic[],
|
|
1459
1233
|
): {
|
|
@@ -1476,7 +1250,7 @@ function collectPolymorphismDeclarations(
|
|
|
1476
1250
|
});
|
|
1477
1251
|
continue;
|
|
1478
1252
|
}
|
|
1479
|
-
const discField = model.fields
|
|
1253
|
+
const discField = model.fields[fieldName];
|
|
1480
1254
|
if (discField && discField.typeName !== 'String') {
|
|
1481
1255
|
diagnostics.push({
|
|
1482
1256
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
@@ -1853,7 +1627,7 @@ function stripStorageOnlyDomainFields(
|
|
|
1853
1627
|
export function interpretPslDocumentToSqlContract(
|
|
1854
1628
|
input: InterpretPslDocumentToSqlContractInput,
|
|
1855
1629
|
): Result<Contract, ContractSourceDiagnostics> {
|
|
1856
|
-
const sourceId = input.
|
|
1630
|
+
const sourceId = input.sourceId;
|
|
1857
1631
|
if (!input.target) {
|
|
1858
1632
|
return notOk({
|
|
1859
1633
|
summary: 'PSL to SQL contract interpretation failed',
|
|
@@ -1879,38 +1653,57 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1879
1653
|
});
|
|
1880
1654
|
}
|
|
1881
1655
|
|
|
1882
|
-
const
|
|
1656
|
+
const { topLevel } = input.symbolTable;
|
|
1657
|
+
const sourceFile = input.sourceFile;
|
|
1658
|
+
const namespaceSymbols = Object.values(topLevel.namespaces);
|
|
1659
|
+
const diagnostics: ContractSourceDiagnostic[] = [...(input.seedDiagnostics ?? [])];
|
|
1883
1660
|
validateNamespaceBlocksForSqlTarget({
|
|
1884
|
-
namespaces:
|
|
1661
|
+
namespaces: namespaceSymbols,
|
|
1885
1662
|
targetId: input.target.targetId,
|
|
1886
1663
|
sourceId,
|
|
1664
|
+
sourceFile,
|
|
1887
1665
|
diagnostics,
|
|
1888
1666
|
});
|
|
1889
|
-
|
|
1890
|
-
// recording every model's resolved `namespaceId` for later threading
|
|
1891
|
-
// into the `ModelNode` build. The resolution rules are target-local
|
|
1892
|
-
// (see `resolveNamespaceIdForSqlTarget`); the flattened model list
|
|
1893
|
-
// remains the input to the rest of the interpreter so non-namespace
|
|
1894
|
-
// concerns stay structurally identical to before.
|
|
1895
|
-
const models: PslModel[] = [];
|
|
1667
|
+
const models: ModelSymbol[] = [];
|
|
1896
1668
|
const modelEntries: ModelNamespaceEntry[] = [];
|
|
1897
1669
|
const modelNamespaceIds = new Map<string, string>();
|
|
1898
|
-
|
|
1670
|
+
const compositeTypes: CompositeTypeSymbol[] = [];
|
|
1671
|
+
|
|
1672
|
+
const collectScope = (
|
|
1673
|
+
bucketName: string,
|
|
1674
|
+
scopeModels: Iterable<ModelSymbol>,
|
|
1675
|
+
scopeCompositeTypes: Iterable<CompositeTypeSymbol>,
|
|
1676
|
+
): void => {
|
|
1899
1677
|
const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
|
|
1900
|
-
bucketName
|
|
1678
|
+
bucketName,
|
|
1901
1679
|
targetId: input.target.targetId,
|
|
1902
1680
|
});
|
|
1903
|
-
for (const model of
|
|
1681
|
+
for (const model of scopeModels) {
|
|
1904
1682
|
models.push(model);
|
|
1905
1683
|
modelEntries.push({ model, namespaceId: resolvedNamespaceId });
|
|
1906
1684
|
if (resolvedNamespaceId !== undefined) {
|
|
1907
1685
|
modelNamespaceIds.set(model.name, resolvedNamespaceId);
|
|
1908
1686
|
}
|
|
1909
1687
|
}
|
|
1688
|
+
for (const compositeType of scopeCompositeTypes) {
|
|
1689
|
+
compositeTypes.push(compositeType);
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
collectScope(
|
|
1694
|
+
UNSPECIFIED_PSL_NAMESPACE_NAME,
|
|
1695
|
+
Object.values(topLevel.models),
|
|
1696
|
+
Object.values(topLevel.compositeTypes),
|
|
1697
|
+
);
|
|
1698
|
+
for (const namespace of namespaceSymbols) {
|
|
1699
|
+
collectScope(
|
|
1700
|
+
namespace.name,
|
|
1701
|
+
Object.values(namespace.models),
|
|
1702
|
+
Object.values(namespace.compositeTypes),
|
|
1703
|
+
);
|
|
1910
1704
|
}
|
|
1911
1705
|
const defaultNamespaceId = input.target.defaultNamespaceId;
|
|
1912
1706
|
|
|
1913
|
-
const compositeTypes = input.document.ast.namespaces.flatMap((ns) => ns.compositeTypes);
|
|
1914
1707
|
const modelNames = new Set(models.map((model) => model.name));
|
|
1915
1708
|
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
1916
1709
|
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
@@ -1924,20 +1717,40 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1924
1717
|
generatorDescriptorById.set(descriptor.id, descriptor);
|
|
1925
1718
|
}
|
|
1926
1719
|
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1720
|
+
const isEnumBlock = (block: BlockSymbol): boolean => block.keyword === 'enum';
|
|
1721
|
+
const legitimateBlockKeywords = composedBlockKeywords(input.authoringContributions);
|
|
1722
|
+
const reportUnsupportedTopLevelBlock = (block: BlockSymbol): void => {
|
|
1723
|
+
diagnostics.push({
|
|
1724
|
+
code: 'PSL_UNSUPPORTED_TOP_LEVEL_BLOCK',
|
|
1725
|
+
message: `Unsupported top-level block "${block.keyword}"`,
|
|
1726
|
+
sourceId,
|
|
1727
|
+
span: keywordPslSpan(block.node.syntax, block.keyword, sourceFile),
|
|
1728
|
+
});
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
const topLevelEnums = Object.values(topLevel.blocks)
|
|
1732
|
+
.filter((block) => {
|
|
1733
|
+
if (!legitimateBlockKeywords.has(block.keyword)) {
|
|
1734
|
+
reportUnsupportedTopLevelBlock(block);
|
|
1735
|
+
return false;
|
|
1736
|
+
}
|
|
1737
|
+
return isEnumBlock(block);
|
|
1738
|
+
})
|
|
1739
|
+
.map((block) => block.block);
|
|
1740
|
+
for (const namespace of namespaceSymbols) {
|
|
1741
|
+
for (const block of Object.values(namespace.blocks)) {
|
|
1742
|
+
if (isEnumBlock(block)) {
|
|
1743
|
+
diagnostics.push({
|
|
1744
|
+
code: 'PSL_ENUM_NAMESPACE_NOT_SUPPORTED',
|
|
1745
|
+
message: `enum "${block.name}" inside namespace "${namespace.name}" is not supported; declare enum at the top level`,
|
|
1746
|
+
sourceId,
|
|
1747
|
+
span: nodePslSpan(block.node.syntax, sourceFile),
|
|
1748
|
+
});
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
if (!legitimateBlockKeywords.has(block.keyword)) {
|
|
1752
|
+
reportUnsupportedTopLevelBlock(block);
|
|
1753
|
+
}
|
|
1941
1754
|
}
|
|
1942
1755
|
}
|
|
1943
1756
|
|
|
@@ -1967,8 +1780,13 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1967
1780
|
|
|
1968
1781
|
const enumHandlesByName = new Map(Object.entries(validEnumHandles));
|
|
1969
1782
|
|
|
1783
|
+
const namedTypeSymbols: readonly NamedTypeSymbol[] = [
|
|
1784
|
+
...Object.values(topLevel.scalars),
|
|
1785
|
+
...Object.values(topLevel.typeAliases),
|
|
1786
|
+
];
|
|
1787
|
+
|
|
1970
1788
|
const namedTypeResult = resolveNamedTypeDeclarations({
|
|
1971
|
-
declarations:
|
|
1789
|
+
declarations: namedTypeSymbols,
|
|
1972
1790
|
sourceId,
|
|
1973
1791
|
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
1974
1792
|
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
@@ -2043,10 +1861,20 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2043
1861
|
}
|
|
2044
1862
|
}
|
|
2045
1863
|
|
|
2046
|
-
const { modelRelations, fkRelationsByPair } = indexFkRelations({
|
|
1864
|
+
const { modelRelations, fkRelationsByPair, fkRelationsByDeclaringModel } = indexFkRelations({
|
|
1865
|
+
fkRelationMetadata,
|
|
1866
|
+
});
|
|
1867
|
+
const modelIdColumns = new Map<string, readonly string[]>();
|
|
1868
|
+
for (const modelNode of modelNodes) {
|
|
1869
|
+
if (modelNode.id) {
|
|
1870
|
+
modelIdColumns.set(modelNode.modelName, modelNode.id.columns);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
2047
1873
|
applyBackrelationCandidates({
|
|
2048
1874
|
backrelationCandidates,
|
|
2049
1875
|
fkRelationsByPair,
|
|
1876
|
+
fkRelationsByDeclaringModel,
|
|
1877
|
+
modelIdColumns,
|
|
2050
1878
|
modelRelations,
|
|
2051
1879
|
diagnostics,
|
|
2052
1880
|
sourceId,
|
|
@@ -2106,7 +1934,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2106
1934
|
if (diagnostics.length > 0) {
|
|
2107
1935
|
return notOk({
|
|
2108
1936
|
summary: 'PSL to SQL contract interpretation failed',
|
|
2109
|
-
diagnostics
|
|
1937
|
+
diagnostics,
|
|
2110
1938
|
});
|
|
2111
1939
|
}
|
|
2112
1940
|
|
|
@@ -2135,16 +1963,16 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2135
1963
|
})),
|
|
2136
1964
|
});
|
|
2137
1965
|
|
|
2138
|
-
//
|
|
2139
|
-
//
|
|
2140
|
-
// passes; only a genuine same-namespace duplicate is an error.
|
|
1966
|
+
// Include namespace in patch keys so same bare model names across namespaces
|
|
1967
|
+
// stay distinct; same-coordinate duplicates were already collapsed first-wins.
|
|
2141
1968
|
const modelsForPatch: Record<string, ContractModel> = {};
|
|
2142
1969
|
for (const [namespaceId, namespaceSlice] of Object.entries(contract.domain.namespaces)) {
|
|
2143
1970
|
for (const [modelName, model] of Object.entries(namespaceSlice.models)) {
|
|
2144
1971
|
const coordinate = modelCoordinateKey(namespaceId, modelName);
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
1972
|
+
invariant(
|
|
1973
|
+
!Object.hasOwn(modelsForPatch, coordinate),
|
|
1974
|
+
`symbol table guarantees coordinate uniqueness; duplicate model "${namespaceId}.${modelName}" reached interpretation`,
|
|
1975
|
+
);
|
|
2148
1976
|
modelsForPatch[coordinate] = model;
|
|
2149
1977
|
}
|
|
2150
1978
|
}
|