@prisma-next/sql-contract-psl 0.13.0 → 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 +10 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-B_KtZusL.mjs → interpreter-CygvamTk.mjs} +529 -312
- package/dist/interpreter-CygvamTk.mjs.map +1 -0
- package/dist/provider.d.mts +1 -1
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +25 -8
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -12
- package/src/interpreter.ts +215 -426
- package/src/provider.ts +39 -8
- package/src/psl-attribute-parsing.ts +18 -14
- package/src/psl-authoring-arguments.ts +2 -2
- package/src/psl-column-resolution.ts +18 -16
- package/src/psl-field-resolution.ts +112 -28
- package/src/psl-named-type-resolution.ts +250 -0
- package/src/psl-relation-resolution.ts +250 -11
- package/dist/interpreter-B_KtZusL.mjs.map +0 -1
package/src/interpreter.ts
CHANGED
|
@@ -14,9 +14,14 @@ import { crossRef } from '@prisma-next/contract/types';
|
|
|
14
14
|
import type {
|
|
15
15
|
AuthoringContributions,
|
|
16
16
|
AuthoringEntityContext,
|
|
17
|
-
|
|
17
|
+
AuthoringPslBlockDescriptorNamespace,
|
|
18
|
+
PslExtensionBlock,
|
|
18
19
|
} from '@prisma-next/framework-components/authoring';
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
instantiateAuthoringEntityType,
|
|
22
|
+
isAuthoringPslBlockDescriptor,
|
|
23
|
+
} from '@prisma-next/framework-components/authoring';
|
|
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 {
|
|
22
27
|
ControlMutationDefaultRegistry,
|
|
@@ -24,25 +29,24 @@ 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 type {
|
|
28
|
-
ParsePslDocumentResult,
|
|
29
|
-
PslAttribute,
|
|
30
|
-
PslCompositeType,
|
|
31
|
-
PslEnum,
|
|
32
|
-
PslField,
|
|
33
|
-
PslModel,
|
|
34
|
-
PslNamedTypeDeclaration,
|
|
35
|
-
PslNamespace,
|
|
36
|
-
} from '@prisma-next/psl-parser';
|
|
37
32
|
import {
|
|
38
|
-
|
|
39
|
-
type
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
type
|
|
43
|
-
|
|
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,
|
|
44
|
+
} from '@prisma-next/psl-parser';
|
|
45
|
+
import type { SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
46
|
+
import type { SqlModelStorage, SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';
|
|
44
47
|
import {
|
|
45
48
|
buildSqlContractFromDefinition,
|
|
49
|
+
type EnumTypeHandle,
|
|
46
50
|
type FieldNode,
|
|
47
51
|
type ForeignKeyNode,
|
|
48
52
|
type IndexNode,
|
|
@@ -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,
|
|
@@ -63,7 +69,6 @@ import {
|
|
|
63
69
|
parseAttributeFieldList,
|
|
64
70
|
parseConstraintMapArgument,
|
|
65
71
|
parseControlPolicyAttribute,
|
|
66
|
-
parseMapName,
|
|
67
72
|
parseObjectLiteralStringMap,
|
|
68
73
|
parseQuotedStringLiteral,
|
|
69
74
|
} from './psl-attribute-parsing';
|
|
@@ -71,12 +76,8 @@ import type { ColumnDescriptor } from './psl-column-resolution';
|
|
|
71
76
|
import {
|
|
72
77
|
checkUncomposedNamespace,
|
|
73
78
|
getAuthoringEntity,
|
|
74
|
-
instantiatePslTypeConstructor,
|
|
75
79
|
reportUncomposedNamespace,
|
|
76
|
-
resolveDbNativeTypeAttribute,
|
|
77
80
|
resolveFieldTypeDescriptor,
|
|
78
|
-
resolvePslTypeConstructorDescriptor,
|
|
79
|
-
toNamedTypeFieldDescriptor,
|
|
80
81
|
} from './psl-column-resolution';
|
|
81
82
|
import {
|
|
82
83
|
buildModelMappings,
|
|
@@ -86,6 +87,7 @@ import {
|
|
|
86
87
|
modelCoordinateKey,
|
|
87
88
|
type ResolvedField,
|
|
88
89
|
} from './psl-field-resolution';
|
|
90
|
+
import { resolveNamedTypeDeclarations } from './psl-named-type-resolution';
|
|
89
91
|
import {
|
|
90
92
|
applyBackrelationCandidates,
|
|
91
93
|
type FkRelationMetadata,
|
|
@@ -96,8 +98,12 @@ import {
|
|
|
96
98
|
validateNavigationListFieldAttributes,
|
|
97
99
|
} from './psl-relation-resolution';
|
|
98
100
|
|
|
101
|
+
type NamedTypeSymbol = ScalarSymbol | TypeAliasSymbol;
|
|
102
|
+
|
|
99
103
|
export interface InterpretPslDocumentToSqlContractInput {
|
|
100
|
-
readonly
|
|
104
|
+
readonly symbolTable: SymbolTable;
|
|
105
|
+
readonly sourceFile: SourceFile;
|
|
106
|
+
readonly sourceId: string;
|
|
101
107
|
readonly target: TargetPackRef<'sql', string>;
|
|
102
108
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
103
109
|
readonly composedExtensionPacks?: readonly string[];
|
|
@@ -124,6 +130,8 @@ export interface InterpretPslDocumentToSqlContractInput {
|
|
|
124
130
|
* `SqlUnboundNamespace` singleton.
|
|
125
131
|
*/
|
|
126
132
|
readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
|
|
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,337 +241,113 @@ 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
|
}
|
|
302
279
|
}
|
|
303
280
|
|
|
304
281
|
interface ProcessEnumDeclarationsInput {
|
|
305
|
-
readonly
|
|
282
|
+
readonly enumBlocks: readonly PslExtensionBlock[];
|
|
306
283
|
readonly sourceId: string;
|
|
307
|
-
readonly
|
|
284
|
+
readonly authoringContributions: AuthoringContributions | undefined;
|
|
308
285
|
readonly entityContext: AuthoringEntityContext;
|
|
309
286
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
310
287
|
}
|
|
311
288
|
|
|
312
289
|
function processEnumDeclarations(input: ProcessEnumDeclarationsInput): {
|
|
313
|
-
readonly
|
|
290
|
+
readonly enumHandles: Record<string, EnumTypeHandle>;
|
|
314
291
|
readonly enumTypeDescriptors: Map<string, ColumnDescriptor>;
|
|
315
292
|
} {
|
|
316
|
-
const
|
|
293
|
+
const enumHandles: Record<string, EnumTypeHandle> = {};
|
|
317
294
|
const enumTypeDescriptors = new Map<string, ColumnDescriptor>();
|
|
318
295
|
|
|
319
|
-
if (input.
|
|
320
|
-
return {
|
|
296
|
+
if (input.enumBlocks.length === 0) {
|
|
297
|
+
return { enumHandles, enumTypeDescriptors };
|
|
321
298
|
}
|
|
322
299
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// Postgres target pack does so today via
|
|
327
|
-
// `authoring.entityTypes.enum`). Without the contribution we
|
|
328
|
-
// surface a diagnostic per declaration rather than silently
|
|
329
|
-
// swallowing the syntax.
|
|
330
|
-
for (const enumDeclaration of input.enums) {
|
|
300
|
+
const enumDescriptor = getAuthoringEntity(input.authoringContributions, ['enum']);
|
|
301
|
+
if (!enumDescriptor) {
|
|
302
|
+
for (const decl of input.enumBlocks) {
|
|
331
303
|
input.diagnostics.push({
|
|
332
|
-
code: '
|
|
333
|
-
message: `
|
|
304
|
+
code: 'PSL_ENUM_MISSING_FACTORY',
|
|
305
|
+
message: `enum "${decl.name}" requires an "enum" entityType factory in the active authoring contributions`,
|
|
334
306
|
sourceId: input.sourceId,
|
|
335
|
-
span:
|
|
307
|
+
span: decl.span,
|
|
336
308
|
});
|
|
337
309
|
}
|
|
338
|
-
return {
|
|
310
|
+
return { enumHandles, enumTypeDescriptors };
|
|
339
311
|
}
|
|
340
312
|
|
|
341
|
-
for (const
|
|
342
|
-
const
|
|
343
|
-
attribute: getAttribute(enumDeclaration.attributes, 'map'),
|
|
344
|
-
defaultValue: enumDeclaration.name,
|
|
345
|
-
sourceId: input.sourceId,
|
|
346
|
-
diagnostics: input.diagnostics,
|
|
347
|
-
entityLabel: `Enum "${enumDeclaration.name}"`,
|
|
348
|
-
span: enumDeclaration.span,
|
|
349
|
-
});
|
|
350
|
-
const values = enumDeclaration.values.map((value) => value.name);
|
|
351
|
-
const constructed = instantiateAuthoringEntityType(
|
|
313
|
+
for (const decl of input.enumBlocks) {
|
|
314
|
+
const handle = instantiateAuthoringEntityType(
|
|
352
315
|
'enum',
|
|
353
|
-
|
|
354
|
-
[
|
|
316
|
+
enumDescriptor,
|
|
317
|
+
[decl],
|
|
355
318
|
input.entityContext,
|
|
356
319
|
);
|
|
357
|
-
if (!isPostgresEnumStorageEntry(constructed)) {
|
|
358
|
-
input.diagnostics.push({
|
|
359
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_BASE',
|
|
360
|
-
message: `Enum "${enumDeclaration.name}": enum entity-type factory must return a PostgresEnumStorageEntry-shaped value (kind: 'postgres-enum')`,
|
|
361
|
-
sourceId: input.sourceId,
|
|
362
|
-
span: enumDeclaration.span,
|
|
363
|
-
});
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
const descriptor: ColumnDescriptor = {
|
|
367
|
-
codecId: constructed.codecId,
|
|
368
|
-
nativeType: constructed.nativeType,
|
|
369
|
-
typeRef: enumDeclaration.name,
|
|
370
|
-
};
|
|
371
|
-
enumTypeDescriptors.set(enumDeclaration.name, descriptor);
|
|
372
|
-
storageTypes[enumDeclaration.name] = constructed;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return { storageTypes, enumTypeDescriptors };
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
interface ResolveNamedTypeDeclarationsInput {
|
|
379
|
-
readonly declarations: readonly PslNamedTypeDeclaration[];
|
|
380
|
-
readonly sourceId: string;
|
|
381
|
-
readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
382
|
-
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
383
|
-
readonly composedExtensions: ReadonlySet<string>;
|
|
384
|
-
readonly familyId: string;
|
|
385
|
-
readonly targetId: string;
|
|
386
|
-
readonly authoringContributions: AuthoringContributions | undefined;
|
|
387
|
-
readonly diagnostics: ContractSourceDiagnostic[];
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function validateNamedTypeAttributes(input: {
|
|
391
|
-
readonly declaration: PslNamedTypeDeclaration;
|
|
392
|
-
readonly sourceId: string;
|
|
393
|
-
readonly diagnostics: ContractSourceDiagnostic[];
|
|
394
|
-
readonly composedExtensions: ReadonlySet<string>;
|
|
395
|
-
readonly authoringContributions: AuthoringContributions | undefined;
|
|
396
|
-
readonly allowDbNativeType: boolean;
|
|
397
|
-
readonly familyId: string;
|
|
398
|
-
readonly targetId: string;
|
|
399
|
-
}): {
|
|
400
|
-
readonly dbNativeTypeAttribute: PslAttribute | undefined;
|
|
401
|
-
readonly hasUnsupportedNamedTypeAttribute: boolean;
|
|
402
|
-
} {
|
|
403
|
-
const dbNativeTypeAttributes = input.allowDbNativeType
|
|
404
|
-
? input.declaration.attributes.filter((attribute) => attribute.name.startsWith('db.'))
|
|
405
|
-
: [];
|
|
406
|
-
const [dbNativeTypeAttribute, ...extraDbNativeTypeAttributes] = dbNativeTypeAttributes;
|
|
407
|
-
let hasUnsupportedNamedTypeAttribute = false;
|
|
408
|
-
|
|
409
|
-
for (const extra of extraDbNativeTypeAttributes) {
|
|
410
|
-
input.diagnostics.push({
|
|
411
|
-
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
412
|
-
message: `Named type "${input.declaration.name}" can declare at most one @db.* attribute`,
|
|
413
|
-
sourceId: input.sourceId,
|
|
414
|
-
span: extra.span,
|
|
415
|
-
});
|
|
416
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
417
|
-
}
|
|
418
320
|
|
|
419
|
-
|
|
420
|
-
if (input.allowDbNativeType && attribute.name.startsWith('db.')) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
425
|
-
familyId: input.familyId,
|
|
426
|
-
targetId: input.targetId,
|
|
427
|
-
authoringContributions: input.authoringContributions,
|
|
428
|
-
});
|
|
429
|
-
if (uncomposedNamespace) {
|
|
430
|
-
reportUncomposedNamespace({
|
|
431
|
-
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
432
|
-
namespace: uncomposedNamespace,
|
|
433
|
-
sourceId: input.sourceId,
|
|
434
|
-
span: attribute.span,
|
|
435
|
-
diagnostics: input.diagnostics,
|
|
436
|
-
});
|
|
437
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
321
|
+
if (handle === undefined || handle === null) continue;
|
|
440
322
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
323
|
+
const enumHandle = blindCast<EnumTypeHandle, 'enum factory returns EnumTypeHandle'>(handle);
|
|
324
|
+
enumHandles[decl.name] = enumHandle;
|
|
325
|
+
enumTypeDescriptors.set(decl.name, {
|
|
326
|
+
codecId: enumHandle.codecId,
|
|
327
|
+
nativeType: enumHandle.nativeType,
|
|
446
328
|
});
|
|
447
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
448
329
|
}
|
|
449
330
|
|
|
450
|
-
return {
|
|
331
|
+
return { enumHandles, enumTypeDescriptors };
|
|
451
332
|
}
|
|
452
333
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
for (const
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
declaration,
|
|
464
|
-
sourceId: input.sourceId,
|
|
465
|
-
diagnostics: input.diagnostics,
|
|
466
|
-
composedExtensions: input.composedExtensions,
|
|
467
|
-
authoringContributions: input.authoringContributions,
|
|
468
|
-
allowDbNativeType: false,
|
|
469
|
-
familyId: input.familyId,
|
|
470
|
-
targetId: input.targetId,
|
|
471
|
-
});
|
|
472
|
-
if (hasUnsupportedNamedTypeAttribute) {
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const helperPath = declaration.typeConstructor.path.join('.');
|
|
477
|
-
const typeConstructor = resolvePslTypeConstructorDescriptor({
|
|
478
|
-
call: declaration.typeConstructor,
|
|
479
|
-
authoringContributions: input.authoringContributions,
|
|
480
|
-
composedExtensions: input.composedExtensions,
|
|
481
|
-
familyId: input.familyId,
|
|
482
|
-
targetId: input.targetId,
|
|
483
|
-
diagnostics: input.diagnostics,
|
|
484
|
-
sourceId: input.sourceId,
|
|
485
|
-
unsupportedCode: 'PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR',
|
|
486
|
-
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`,
|
|
487
|
-
});
|
|
488
|
-
if (!typeConstructor) {
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const storageType = instantiatePslTypeConstructor({
|
|
493
|
-
call: declaration.typeConstructor,
|
|
494
|
-
descriptor: typeConstructor,
|
|
495
|
-
diagnostics: input.diagnostics,
|
|
496
|
-
sourceId: input.sourceId,
|
|
497
|
-
entityLabel: `Named type "${declaration.name}"`,
|
|
498
|
-
});
|
|
499
|
-
if (!storageType) {
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
namedTypeDescriptors.set(
|
|
504
|
-
declaration.name,
|
|
505
|
-
toNamedTypeFieldDescriptor(declaration.name, storageType),
|
|
506
|
-
);
|
|
507
|
-
storageTypes[declaration.name] = {
|
|
508
|
-
kind: 'codec-instance',
|
|
509
|
-
codecId: storageType.codecId,
|
|
510
|
-
nativeType: storageType.nativeType,
|
|
511
|
-
typeParams: storageType.typeParams ?? {},
|
|
512
|
-
};
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Parser invariant: when typeConstructor is absent, baseType is defined.
|
|
517
|
-
// The check below narrows `baseType` for TypeScript and guards against a
|
|
518
|
-
// parser regression; it is unreachable under a correct parser.
|
|
519
|
-
if (declaration.baseType === undefined) {
|
|
520
|
-
input.diagnostics.push({
|
|
521
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_BASE',
|
|
522
|
-
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
523
|
-
sourceId: input.sourceId,
|
|
524
|
-
span: declaration.span,
|
|
525
|
-
});
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
const { baseType } = declaration;
|
|
529
|
-
const baseDescriptor =
|
|
530
|
-
input.enumTypeDescriptors.get(baseType) ?? input.scalarTypeDescriptors.get(baseType);
|
|
531
|
-
if (!baseDescriptor) {
|
|
532
|
-
input.diagnostics.push({
|
|
533
|
-
code: 'PSL_UNSUPPORTED_NAMED_TYPE_BASE',
|
|
534
|
-
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
535
|
-
sourceId: input.sourceId,
|
|
536
|
-
span: declaration.span,
|
|
537
|
-
});
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes(
|
|
542
|
-
{
|
|
543
|
-
declaration,
|
|
544
|
-
sourceId: input.sourceId,
|
|
545
|
-
diagnostics: input.diagnostics,
|
|
546
|
-
composedExtensions: input.composedExtensions,
|
|
547
|
-
authoringContributions: input.authoringContributions,
|
|
548
|
-
allowDbNativeType: true,
|
|
549
|
-
familyId: input.familyId,
|
|
550
|
-
targetId: input.targetId,
|
|
551
|
-
},
|
|
552
|
-
);
|
|
553
|
-
if (hasUnsupportedNamedTypeAttribute) {
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (dbNativeTypeAttribute) {
|
|
558
|
-
const descriptor = resolveDbNativeTypeAttribute({
|
|
559
|
-
attribute: dbNativeTypeAttribute,
|
|
560
|
-
baseType,
|
|
561
|
-
baseDescriptor,
|
|
562
|
-
diagnostics: input.diagnostics,
|
|
563
|
-
sourceId: input.sourceId,
|
|
564
|
-
entityLabel: `Named type "${declaration.name}"`,
|
|
565
|
-
});
|
|
566
|
-
if (!descriptor) {
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
namedTypeDescriptors.set(
|
|
570
|
-
declaration.name,
|
|
571
|
-
toNamedTypeFieldDescriptor(declaration.name, descriptor),
|
|
572
|
-
);
|
|
573
|
-
storageTypes[declaration.name] = {
|
|
574
|
-
kind: 'codec-instance',
|
|
575
|
-
codecId: descriptor.codecId,
|
|
576
|
-
nativeType: descriptor.nativeType,
|
|
577
|
-
typeParams: descriptor.typeParams ?? {},
|
|
578
|
-
};
|
|
579
|
-
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);
|
|
580
344
|
}
|
|
581
|
-
|
|
582
|
-
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
583
|
-
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
584
|
-
storageTypes[declaration.name] = {
|
|
585
|
-
kind: 'codec-instance',
|
|
586
|
-
codecId: baseDescriptor.codecId,
|
|
587
|
-
nativeType: baseDescriptor.nativeType,
|
|
588
|
-
typeParams: {},
|
|
589
|
-
};
|
|
590
345
|
}
|
|
591
|
-
|
|
592
|
-
return { storageTypes, namedTypeDescriptors };
|
|
346
|
+
return keywords;
|
|
593
347
|
}
|
|
594
348
|
|
|
595
349
|
interface BuildModelNodeInput {
|
|
596
|
-
readonly model:
|
|
350
|
+
readonly model: ModelSymbol;
|
|
597
351
|
readonly mapping: ModelNameMapping;
|
|
598
352
|
readonly modelMappings: ReadonlyMap<string, ModelNameMapping>;
|
|
599
353
|
/**
|
|
@@ -619,6 +373,7 @@ interface BuildModelNodeInput {
|
|
|
619
373
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
620
374
|
/** Resolved namespace id keyed by model name — used to stamp the target namespace on FKs. */
|
|
621
375
|
readonly modelNamespaceIds: ReadonlyMap<string, string>;
|
|
376
|
+
readonly enumHandles?: ReadonlyMap<string, EnumTypeHandle>;
|
|
622
377
|
}
|
|
623
378
|
|
|
624
379
|
interface BuildModelNodeResult {
|
|
@@ -650,6 +405,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
650
405
|
diagnostics,
|
|
651
406
|
sourceId,
|
|
652
407
|
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
408
|
+
...ifDefined('enumHandles', input.enumHandles),
|
|
653
409
|
});
|
|
654
410
|
|
|
655
411
|
const inlineIdFields = resolvedFields.filter((field) => field.isId);
|
|
@@ -674,7 +430,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
674
430
|
let controlPolicy: ControlPolicy | undefined;
|
|
675
431
|
|
|
676
432
|
const resultBackrelationCandidates: ModelBackrelationCandidate[] = [];
|
|
677
|
-
for (const field of model.fields) {
|
|
433
|
+
for (const field of Object.values(model.fields)) {
|
|
678
434
|
if (!field.list || !input.modelNames.has(field.typeName)) {
|
|
679
435
|
continue;
|
|
680
436
|
}
|
|
@@ -734,12 +490,12 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
734
490
|
});
|
|
735
491
|
}
|
|
736
492
|
|
|
737
|
-
const relationAttributes = model.fields
|
|
493
|
+
const relationAttributes = Object.values(model.fields)
|
|
738
494
|
.map((field) => ({
|
|
739
495
|
field,
|
|
740
496
|
relation: getAttribute(field.attributes, 'relation'),
|
|
741
497
|
}))
|
|
742
|
-
.filter((entry): entry is { field:
|
|
498
|
+
.filter((entry): entry is { field: FieldSymbol; relation: ResolvedAttribute } =>
|
|
743
499
|
Boolean(entry.relation),
|
|
744
500
|
);
|
|
745
501
|
const uniqueConstraints: UniqueConstraintNode[] = resolvedFields
|
|
@@ -820,9 +576,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
820
576
|
});
|
|
821
577
|
continue;
|
|
822
578
|
}
|
|
823
|
-
const nullableFieldName = fieldNames.find(
|
|
824
|
-
(name) => model.fields.find((f) => f.name === name)?.optional === true,
|
|
825
|
-
);
|
|
579
|
+
const nullableFieldName = fieldNames.find((name) => model.fields[name]?.optional === true);
|
|
826
580
|
if (nullableFieldName !== undefined) {
|
|
827
581
|
diagnostics.push({
|
|
828
582
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
@@ -1307,6 +1061,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1307
1061
|
declaringModelName: model.name,
|
|
1308
1062
|
declaringFieldName: relationAttribute.field.name,
|
|
1309
1063
|
declaringTableName: tableName,
|
|
1064
|
+
...ifDefined('declaringNamespaceId', input.modelNamespaceIds.get(model.name)),
|
|
1310
1065
|
targetModelName: targetMapping.model.name,
|
|
1311
1066
|
targetTableName: targetMapping.tableName,
|
|
1312
1067
|
...ifDefined('targetNamespaceId', targetNamespaceId),
|
|
@@ -1320,14 +1075,18 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1320
1075
|
modelNode: {
|
|
1321
1076
|
modelName: model.name,
|
|
1322
1077
|
tableName,
|
|
1323
|
-
fields: resolvedFields.map((resolvedField) =>
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1078
|
+
fields: resolvedFields.map((resolvedField) => {
|
|
1079
|
+
const enumHandle = input.enumHandles?.get(resolvedField.field.typeName);
|
|
1080
|
+
return {
|
|
1081
|
+
fieldName: resolvedField.field.name,
|
|
1082
|
+
columnName: resolvedField.columnName,
|
|
1083
|
+
descriptor: resolvedField.descriptor,
|
|
1084
|
+
nullable: resolvedField.nullable,
|
|
1085
|
+
...ifDefined('default', resolvedField.defaultValue),
|
|
1086
|
+
...ifDefined('executionDefaults', resolvedField.executionDefaults),
|
|
1087
|
+
...ifDefined('enumTypeHandle', enumHandle),
|
|
1088
|
+
};
|
|
1089
|
+
}),
|
|
1331
1090
|
...ifDefined('id', primaryKey),
|
|
1332
1091
|
...(uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {}),
|
|
1333
1092
|
...(indexNodes.length > 0 ? { indexes: indexNodes } : {}),
|
|
@@ -1342,7 +1101,7 @@ function buildModelNodeFromPsl(input: BuildModelNodeInput): BuildModelNodeResult
|
|
|
1342
1101
|
}
|
|
1343
1102
|
|
|
1344
1103
|
interface BuildValueObjectsInput {
|
|
1345
|
-
readonly compositeTypes: readonly
|
|
1104
|
+
readonly compositeTypes: readonly CompositeTypeSymbol[];
|
|
1346
1105
|
readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
1347
1106
|
readonly namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
1348
1107
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
@@ -1372,7 +1131,7 @@ function buildValueObjects(input: BuildValueObjectsInput): Record<string, Contra
|
|
|
1372
1131
|
|
|
1373
1132
|
for (const compositeType of compositeTypes) {
|
|
1374
1133
|
const fields: Record<string, ContractField> = {};
|
|
1375
|
-
for (const field of compositeType.fields) {
|
|
1134
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
1376
1135
|
if (compositeTypeNames.has(field.typeName)) {
|
|
1377
1136
|
const result: ContractField = {
|
|
1378
1137
|
type: { kind: 'valueObject', name: field.typeName },
|
|
@@ -1468,7 +1227,7 @@ type BaseDeclaration = {
|
|
|
1468
1227
|
};
|
|
1469
1228
|
|
|
1470
1229
|
function collectPolymorphismDeclarations(
|
|
1471
|
-
models: readonly
|
|
1230
|
+
models: readonly ModelSymbol[],
|
|
1472
1231
|
sourceId: string,
|
|
1473
1232
|
diagnostics: ContractSourceDiagnostic[],
|
|
1474
1233
|
): {
|
|
@@ -1491,7 +1250,7 @@ function collectPolymorphismDeclarations(
|
|
|
1491
1250
|
});
|
|
1492
1251
|
continue;
|
|
1493
1252
|
}
|
|
1494
|
-
const discField = model.fields
|
|
1253
|
+
const discField = model.fields[fieldName];
|
|
1495
1254
|
if (discField && discField.typeName !== 'String') {
|
|
1496
1255
|
diagnostics.push({
|
|
1497
1256
|
code: 'PSL_INVALID_ATTRIBUTE_ARGUMENT',
|
|
@@ -1868,7 +1627,7 @@ function stripStorageOnlyDomainFields(
|
|
|
1868
1627
|
export function interpretPslDocumentToSqlContract(
|
|
1869
1628
|
input: InterpretPslDocumentToSqlContractInput,
|
|
1870
1629
|
): Result<Contract, ContractSourceDiagnostics> {
|
|
1871
|
-
const sourceId = input.
|
|
1630
|
+
const sourceId = input.sourceId;
|
|
1872
1631
|
if (!input.target) {
|
|
1873
1632
|
return notOk({
|
|
1874
1633
|
summary: 'PSL to SQL contract interpretation failed',
|
|
@@ -1894,65 +1653,57 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1894
1653
|
});
|
|
1895
1654
|
}
|
|
1896
1655
|
|
|
1897
|
-
const
|
|
1656
|
+
const { topLevel } = input.symbolTable;
|
|
1657
|
+
const sourceFile = input.sourceFile;
|
|
1658
|
+
const namespaceSymbols = Object.values(topLevel.namespaces);
|
|
1659
|
+
const diagnostics: ContractSourceDiagnostic[] = [...(input.seedDiagnostics ?? [])];
|
|
1898
1660
|
validateNamespaceBlocksForSqlTarget({
|
|
1899
|
-
namespaces:
|
|
1661
|
+
namespaces: namespaceSymbols,
|
|
1900
1662
|
targetId: input.target.targetId,
|
|
1901
1663
|
sourceId,
|
|
1664
|
+
sourceFile,
|
|
1902
1665
|
diagnostics,
|
|
1903
1666
|
});
|
|
1904
|
-
|
|
1905
|
-
// recording every model's resolved `namespaceId` for later threading
|
|
1906
|
-
// into the `ModelNode` build. The resolution rules are target-local
|
|
1907
|
-
// (see `resolveNamespaceIdForSqlTarget`); the flattened model list
|
|
1908
|
-
// remains the input to the rest of the interpreter so non-namespace
|
|
1909
|
-
// concerns stay structurally identical to before.
|
|
1910
|
-
const models: PslModel[] = [];
|
|
1667
|
+
const models: ModelSymbol[] = [];
|
|
1911
1668
|
const modelEntries: ModelNamespaceEntry[] = [];
|
|
1912
1669
|
const modelNamespaceIds = new Map<string, string>();
|
|
1913
|
-
|
|
1670
|
+
const compositeTypes: CompositeTypeSymbol[] = [];
|
|
1671
|
+
|
|
1672
|
+
const collectScope = (
|
|
1673
|
+
bucketName: string,
|
|
1674
|
+
scopeModels: Iterable<ModelSymbol>,
|
|
1675
|
+
scopeCompositeTypes: Iterable<CompositeTypeSymbol>,
|
|
1676
|
+
): void => {
|
|
1914
1677
|
const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
|
|
1915
|
-
bucketName
|
|
1678
|
+
bucketName,
|
|
1916
1679
|
targetId: input.target.targetId,
|
|
1917
1680
|
});
|
|
1918
|
-
for (const model of
|
|
1681
|
+
for (const model of scopeModels) {
|
|
1919
1682
|
models.push(model);
|
|
1920
1683
|
modelEntries.push({ model, namespaceId: resolvedNamespaceId });
|
|
1921
1684
|
if (resolvedNamespaceId !== undefined) {
|
|
1922
1685
|
modelNamespaceIds.set(model.name, resolvedNamespaceId);
|
|
1923
1686
|
}
|
|
1924
1687
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
// Top-level enums (the __unspecified__ bucket) route to `storageTypes`;
|
|
1928
|
-
// enums inside a named namespace block route to `namespaceTypes[nsId]`.
|
|
1929
|
-
const topLevelEnums = input.document.ast.namespaces
|
|
1930
|
-
.filter((ns) => ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME)
|
|
1931
|
-
.flatMap((ns) => ns.enums);
|
|
1932
|
-
const namedNamespaceEnumsByNsId = new Map<string, readonly PslEnum[]>();
|
|
1933
|
-
for (const ns of input.document.ast.namespaces) {
|
|
1934
|
-
if (ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME || ns.enums.length === 0) {
|
|
1935
|
-
continue;
|
|
1688
|
+
for (const compositeType of scopeCompositeTypes) {
|
|
1689
|
+
compositeTypes.push(compositeType);
|
|
1936
1690
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
// resolvedId and the merge degrades to a plain set.
|
|
1951
|
-
const existing = namedNamespaceEnumsByNsId.get(resolvedId) ?? [];
|
|
1952
|
-
namedNamespaceEnumsByNsId.set(resolvedId, [...existing, ...ns.enums]);
|
|
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
|
+
);
|
|
1953
1704
|
}
|
|
1705
|
+
const defaultNamespaceId = input.target.defaultNamespaceId;
|
|
1954
1706
|
|
|
1955
|
-
const compositeTypes = input.document.ast.namespaces.flatMap((ns) => ns.compositeTypes);
|
|
1956
1707
|
const modelNames = new Set(models.map((model) => model.name));
|
|
1957
1708
|
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
1958
1709
|
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
@@ -1966,48 +1717,76 @@ export function interpretPslDocumentToSqlContract(
|
|
|
1966
1717
|
generatorDescriptorById.set(descriptor.id, descriptor);
|
|
1967
1718
|
}
|
|
1968
1719
|
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
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
|
+
});
|
|
1973
1729
|
};
|
|
1974
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
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1975
1757
|
const enumResult = processEnumDeclarations({
|
|
1976
|
-
|
|
1758
|
+
enumBlocks: topLevelEnums,
|
|
1977
1759
|
sourceId,
|
|
1978
|
-
|
|
1979
|
-
entityContext:
|
|
1760
|
+
authoringContributions: input.authoringContributions,
|
|
1761
|
+
entityContext: {
|
|
1762
|
+
family: input.target.familyId,
|
|
1763
|
+
target: input.target.targetId,
|
|
1764
|
+
...ifDefined('codecLookup', input.codecLookup),
|
|
1765
|
+
sourceId,
|
|
1766
|
+
diagnostics: {
|
|
1767
|
+
push: (d) => {
|
|
1768
|
+
diagnostics.push(
|
|
1769
|
+
blindCast<ContractSourceDiagnostic, 'sink diagnostics are span-compatible'>(d),
|
|
1770
|
+
);
|
|
1771
|
+
},
|
|
1772
|
+
},
|
|
1773
|
+
},
|
|
1980
1774
|
diagnostics,
|
|
1981
1775
|
});
|
|
1982
1776
|
|
|
1983
|
-
// Process enums declared in named namespace blocks and collect them into
|
|
1984
|
-
// `namespaceTypes` keyed by the resolved namespace id.
|
|
1985
1777
|
const allEnumTypeDescriptors = new Map(enumResult.enumTypeDescriptors);
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
for (const [name, descriptor] of nsEnumResult.enumTypeDescriptors) {
|
|
1996
|
-
allEnumTypeDescriptors.set(name, descriptor);
|
|
1997
|
-
}
|
|
1998
|
-
const nsEntries: Record<string, PostgresEnumStorageEntry> = {};
|
|
1999
|
-
for (const [name, entry] of Object.entries(nsEnumResult.storageTypes)) {
|
|
2000
|
-
if (isPostgresEnumStorageEntry(entry)) {
|
|
2001
|
-
nsEntries[name] = entry;
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
if (Object.keys(nsEntries).length > 0) {
|
|
2005
|
-
namespaceEnumStorageTypes[nsId] = nsEntries;
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
1778
|
+
|
|
1779
|
+
const validEnumHandles: Record<string, EnumTypeHandle> = { ...enumResult.enumHandles };
|
|
1780
|
+
|
|
1781
|
+
const enumHandlesByName = new Map(Object.entries(validEnumHandles));
|
|
1782
|
+
|
|
1783
|
+
const namedTypeSymbols: readonly NamedTypeSymbol[] = [
|
|
1784
|
+
...Object.values(topLevel.scalars),
|
|
1785
|
+
...Object.values(topLevel.typeAliases),
|
|
1786
|
+
];
|
|
2008
1787
|
|
|
2009
1788
|
const namedTypeResult = resolveNamedTypeDeclarations({
|
|
2010
|
-
declarations:
|
|
1789
|
+
declarations: namedTypeSymbols,
|
|
2011
1790
|
sourceId,
|
|
2012
1791
|
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
2013
1792
|
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
@@ -2018,7 +1797,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2018
1797
|
diagnostics,
|
|
2019
1798
|
});
|
|
2020
1799
|
|
|
2021
|
-
const storageTypes = { ...
|
|
1800
|
+
const storageTypes = { ...namedTypeResult.storageTypes };
|
|
2022
1801
|
|
|
2023
1802
|
const modelMappingsByCoordinate = buildModelMappings(
|
|
2024
1803
|
modelEntries,
|
|
@@ -2068,6 +1847,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2068
1847
|
sourceId,
|
|
2069
1848
|
diagnostics,
|
|
2070
1849
|
modelNamespaceIds,
|
|
1850
|
+
...(enumHandlesByName.size > 0 ? { enumHandles: enumHandlesByName } : {}),
|
|
2071
1851
|
});
|
|
2072
1852
|
modelNodes.push(
|
|
2073
1853
|
namespaceId !== undefined ? { ...result.modelNode, namespaceId } : result.modelNode,
|
|
@@ -2081,10 +1861,20 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2081
1861
|
}
|
|
2082
1862
|
}
|
|
2083
1863
|
|
|
2084
|
-
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
|
+
}
|
|
2085
1873
|
applyBackrelationCandidates({
|
|
2086
1874
|
backrelationCandidates,
|
|
2087
1875
|
fkRelationsByPair,
|
|
1876
|
+
fkRelationsByDeclaringModel,
|
|
1877
|
+
modelIdColumns,
|
|
2088
1878
|
modelRelations,
|
|
2089
1879
|
diagnostics,
|
|
2090
1880
|
sourceId,
|
|
@@ -2144,7 +1934,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2144
1934
|
if (diagnostics.length > 0) {
|
|
2145
1935
|
return notOk({
|
|
2146
1936
|
summary: 'PSL to SQL contract interpretation failed',
|
|
2147
|
-
diagnostics
|
|
1937
|
+
diagnostics,
|
|
2148
1938
|
});
|
|
2149
1939
|
}
|
|
2150
1940
|
|
|
@@ -2159,9 +1949,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2159
1949
|
),
|
|
2160
1950
|
),
|
|
2161
1951
|
...(Object.keys(storageTypes).length > 0 ? { storageTypes } : {}),
|
|
2162
|
-
...(Object.keys(
|
|
2163
|
-
? { namespaceTypes: namespaceEnumStorageTypes }
|
|
2164
|
-
: {}),
|
|
1952
|
+
...(Object.keys(validEnumHandles).length > 0 ? { enums: validEnumHandles } : {}),
|
|
2165
1953
|
...ifDefined('createNamespace', input.createNamespace),
|
|
2166
1954
|
models: stiColumnModelNodes.map((model) => ({
|
|
2167
1955
|
...model,
|
|
@@ -2175,16 +1963,16 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2175
1963
|
})),
|
|
2176
1964
|
});
|
|
2177
1965
|
|
|
2178
|
-
//
|
|
2179
|
-
//
|
|
2180
|
-
// 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.
|
|
2181
1968
|
const modelsForPatch: Record<string, ContractModel> = {};
|
|
2182
1969
|
for (const [namespaceId, namespaceSlice] of Object.entries(contract.domain.namespaces)) {
|
|
2183
1970
|
for (const [modelName, model] of Object.entries(namespaceSlice.models)) {
|
|
2184
1971
|
const coordinate = modelCoordinateKey(namespaceId, modelName);
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
1972
|
+
invariant(
|
|
1973
|
+
!Object.hasOwn(modelsForPatch, coordinate),
|
|
1974
|
+
`symbol table guarantees coordinate uniqueness; duplicate model "${namespaceId}.${modelName}" reached interpretation`,
|
|
1975
|
+
);
|
|
2188
1976
|
modelsForPatch[coordinate] = model;
|
|
2189
1977
|
}
|
|
2190
1978
|
}
|
|
@@ -2233,6 +2021,7 @@ export function interpretPslDocumentToSqlContract(
|
|
|
2233
2021
|
patchedModels[modelCoordinateKey(namespaceId, modelName)] ?? model,
|
|
2234
2022
|
]),
|
|
2235
2023
|
),
|
|
2024
|
+
...(namespaceSlice.enum !== undefined ? { enum: namespaceSlice.enum } : {}),
|
|
2236
2025
|
...(namespaceSlice.valueObjects !== undefined
|
|
2237
2026
|
? { valueObjects: namespaceSlice.valueObjects }
|
|
2238
2027
|
: {}),
|