@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/provider.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import type { ContractConfig } from '@prisma-next/config/config-types';
|
|
2
|
+
import type { ContractConfig, ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
3
3
|
import { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';
|
|
4
4
|
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
5
5
|
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
6
6
|
import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
|
|
7
7
|
import type { Namespace } from '@prisma-next/framework-components/ir';
|
|
8
|
-
import {
|
|
8
|
+
import { buildSymbolTable, rangeToPslSpan } from '@prisma-next/psl-parser';
|
|
9
|
+
import type { ParseDiagnostic, SourceFile } from '@prisma-next/psl-parser/syntax';
|
|
10
|
+
import { parse } from '@prisma-next/psl-parser/syntax';
|
|
9
11
|
import type { SqlNamespaceTablesInput } from '@prisma-next/sql-contract/types';
|
|
10
12
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
11
13
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
12
14
|
import { basename, extname } from 'pathe';
|
|
15
|
+
|
|
13
16
|
import { interpretPslDocumentToSqlContract } from './interpreter';
|
|
14
17
|
import type { ColumnDescriptor } from './psl-column-resolution';
|
|
15
18
|
|
|
@@ -42,6 +45,19 @@ function defaultOutputFromSchemaPath(schemaPath: string): string {
|
|
|
42
45
|
return `${base}.json`;
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
function mapParseDiagnostics(
|
|
49
|
+
diagnostics: readonly ParseDiagnostic[],
|
|
50
|
+
sourceFile: SourceFile,
|
|
51
|
+
sourceId: string,
|
|
52
|
+
): ContractSourceDiagnostic[] {
|
|
53
|
+
return diagnostics.map((diagnostic) => ({
|
|
54
|
+
code: diagnostic.code,
|
|
55
|
+
message: diagnostic.message,
|
|
56
|
+
sourceId,
|
|
57
|
+
span: rangeToPslSpan(diagnostic.range, sourceFile),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
function buildColumnDescriptorMap(
|
|
46
62
|
scalarTypeDescriptors: ReadonlyMap<string, string>,
|
|
47
63
|
codecLookup: CodecLookup,
|
|
@@ -58,6 +74,7 @@ function buildColumnDescriptorMap(
|
|
|
58
74
|
export function prismaContract(schemaPath: string, options: PrismaContractOptions): ContractConfig {
|
|
59
75
|
return {
|
|
60
76
|
source: {
|
|
77
|
+
sourceFormat: 'psl',
|
|
61
78
|
inputs: [schemaPath],
|
|
62
79
|
load: async (context) => {
|
|
63
80
|
const [absoluteSchemaPath] = context.resolvedInputs;
|
|
@@ -84,18 +101,31 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
|
|
|
84
101
|
});
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
const document = parsePslDocument({
|
|
88
|
-
schema,
|
|
89
|
-
sourceId: schemaPath,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
104
|
const scalarTypeDescriptors = buildColumnDescriptorMap(
|
|
93
105
|
context.scalarTypeDescriptors,
|
|
94
106
|
context.codecLookup,
|
|
95
107
|
);
|
|
96
108
|
|
|
97
|
-
const
|
|
109
|
+
const { document, sourceFile, diagnostics: parseDiagnostics } = parse(schema);
|
|
110
|
+
const { table: symbolTable, diagnostics: symbolTableDiagnostics } = buildSymbolTable({
|
|
98
111
|
document,
|
|
112
|
+
sourceFile,
|
|
113
|
+
scalarTypes: [...context.scalarTypeDescriptors.keys()],
|
|
114
|
+
pslBlockDescriptors: context.authoringContributions.pslBlockDescriptors,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Do not short-circuit on provider-level diagnostics; recovered CST can
|
|
118
|
+
// still produce interpreter diagnostics in the same response.
|
|
119
|
+
const seedDiagnostics = [
|
|
120
|
+
...mapParseDiagnostics(parseDiagnostics, sourceFile, schemaPath),
|
|
121
|
+
...mapParseDiagnostics(symbolTableDiagnostics, sourceFile, schemaPath),
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const interpreted = interpretPslDocumentToSqlContract({
|
|
125
|
+
symbolTable,
|
|
126
|
+
sourceFile,
|
|
127
|
+
sourceId: schemaPath,
|
|
128
|
+
seedDiagnostics,
|
|
99
129
|
target: options.target,
|
|
100
130
|
authoringContributions: context.authoringContributions,
|
|
101
131
|
scalarTypeDescriptors,
|
|
@@ -114,6 +144,7 @@ export function prismaContract(schemaPath: string, options: PrismaContractOption
|
|
|
114
144
|
),
|
|
115
145
|
controlMutationDefaults: context.controlMutationDefaults,
|
|
116
146
|
...ifDefined('createNamespace', options.createNamespace),
|
|
147
|
+
codecLookup: context.codecLookup,
|
|
117
148
|
});
|
|
118
149
|
if (!interpreted.ok) {
|
|
119
150
|
return interpreted;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
2
2
|
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
3
|
+
import type { PslSpan, ResolvedAttribute } from '@prisma-next/psl-parser';
|
|
4
|
+
import { parseQuotedStringLiteral } from '@prisma-next/psl-parser';
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export { parseQuotedStringLiteral };
|
|
7
|
+
|
|
8
|
+
export function getPositionalArgument(attribute: ResolvedAttribute, index = 0): string | undefined {
|
|
9
|
+
return attribute.args.filter((arg) => arg.kind === 'positional')[index]?.value;
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
export function lowerFirst(value: string): string {
|
|
9
13
|
if (value.length === 0) return value;
|
|
@@ -11,13 +15,13 @@ export function lowerFirst(value: string): string {
|
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export function getAttribute(
|
|
14
|
-
attributes: readonly
|
|
18
|
+
attributes: readonly ResolvedAttribute[] | undefined,
|
|
15
19
|
name: string,
|
|
16
|
-
):
|
|
20
|
+
): ResolvedAttribute | undefined {
|
|
17
21
|
return attributes?.find((attribute) => attribute.name === name);
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export function getNamedArgument(attribute:
|
|
24
|
+
export function getNamedArgument(attribute: ResolvedAttribute, name: string): string | undefined {
|
|
21
25
|
const entry = attribute.args.find((arg) => arg.kind === 'named' && arg.name === name);
|
|
22
26
|
if (!entry || entry.kind !== 'named') {
|
|
23
27
|
return undefined;
|
|
@@ -26,7 +30,7 @@ export function getNamedArgument(attribute: PslAttribute, name: string): string
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export function getPositionalArgumentEntry(
|
|
29
|
-
attribute:
|
|
33
|
+
attribute: ResolvedAttribute,
|
|
30
34
|
index = 0,
|
|
31
35
|
): { value: string; span: PslSpan } | undefined {
|
|
32
36
|
const entries = attribute.args.filter((arg) => arg.kind === 'positional');
|
|
@@ -63,7 +67,7 @@ export function parseFieldList(value: string): readonly string[] | undefined {
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
export function parseMapName(input: {
|
|
66
|
-
readonly attribute:
|
|
70
|
+
readonly attribute: ResolvedAttribute | undefined;
|
|
67
71
|
readonly defaultValue: string;
|
|
68
72
|
readonly sourceId: string;
|
|
69
73
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
@@ -98,7 +102,7 @@ export function parseMapName(input: {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
export function parseConstraintMapArgument(input: {
|
|
101
|
-
readonly attribute:
|
|
105
|
+
readonly attribute: ResolvedAttribute | undefined;
|
|
102
106
|
readonly sourceId: string;
|
|
103
107
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
104
108
|
readonly entityLabel: string;
|
|
@@ -128,7 +132,7 @@ export function parseConstraintMapArgument(input: {
|
|
|
128
132
|
return undefined;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
export function getPositionalArguments(attribute:
|
|
135
|
+
export function getPositionalArguments(attribute: ResolvedAttribute): readonly string[] {
|
|
132
136
|
return attribute.args
|
|
133
137
|
.filter((arg) => arg.kind === 'positional')
|
|
134
138
|
.map((arg) => (arg.kind === 'positional' ? arg.value : ''));
|
|
@@ -276,7 +280,7 @@ export function pushInvalidAttributeArgument(input: {
|
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
export function parseOptionalSingleIntegerArgument(input: {
|
|
279
|
-
readonly attribute:
|
|
283
|
+
readonly attribute: ResolvedAttribute;
|
|
280
284
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
281
285
|
readonly sourceId: string;
|
|
282
286
|
readonly entityLabel: string;
|
|
@@ -319,7 +323,7 @@ export function parseOptionalSingleIntegerArgument(input: {
|
|
|
319
323
|
}
|
|
320
324
|
|
|
321
325
|
export function parseOptionalNumericArguments(input: {
|
|
322
|
-
readonly attribute:
|
|
326
|
+
readonly attribute: ResolvedAttribute;
|
|
323
327
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
324
328
|
readonly sourceId: string;
|
|
325
329
|
readonly entityLabel: string;
|
|
@@ -374,7 +378,7 @@ export function parseOptionalNumericArguments(input: {
|
|
|
374
378
|
}
|
|
375
379
|
|
|
376
380
|
export function parseAttributeFieldList(input: {
|
|
377
|
-
readonly attribute:
|
|
381
|
+
readonly attribute: ResolvedAttribute;
|
|
378
382
|
readonly sourceId: string;
|
|
379
383
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
380
384
|
readonly code: string;
|
|
@@ -426,7 +430,7 @@ function isControlPolicyLiteral(value: string): value is ControlPolicy {
|
|
|
426
430
|
}
|
|
427
431
|
|
|
428
432
|
export function parseControlPolicyAttribute(input: {
|
|
429
|
-
readonly attribute:
|
|
433
|
+
readonly attribute: ResolvedAttribute;
|
|
430
434
|
readonly sourceId: string;
|
|
431
435
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
432
436
|
}): ControlPolicy | undefined {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
2
2
|
import type { AuthoringArgumentDescriptor } from '@prisma-next/framework-components/authoring';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PslSpan, ResolvedAttributeArg } from '@prisma-next/psl-parser';
|
|
4
4
|
import { unquoteStringLiteral } from './psl-attribute-parsing';
|
|
5
5
|
|
|
6
6
|
const INVALID_AUTHORING_ARGUMENT = Symbol('invalidAuthoringArgument');
|
|
@@ -351,7 +351,7 @@ function pushInvalidPslHelperArgument(input: {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
export function mapPslHelperArgs(input: {
|
|
354
|
-
readonly args: readonly
|
|
354
|
+
readonly args: readonly ResolvedAttributeArg[];
|
|
355
355
|
readonly descriptors: readonly AuthoringArgumentDescriptor[];
|
|
356
356
|
readonly helperLabel: string;
|
|
357
357
|
readonly span: PslSpan;
|
|
@@ -20,12 +20,13 @@ import type {
|
|
|
20
20
|
MutationDefaultGeneratorDescriptor,
|
|
21
21
|
} from '@prisma-next/framework-components/control';
|
|
22
22
|
import type {
|
|
23
|
-
|
|
24
|
-
PslField,
|
|
23
|
+
FieldSymbol,
|
|
25
24
|
PslSpan,
|
|
26
|
-
|
|
25
|
+
ResolvedAttribute,
|
|
26
|
+
ResolvedTypeConstructorCall,
|
|
27
27
|
} from '@prisma-next/psl-parser';
|
|
28
28
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
29
|
+
|
|
29
30
|
import {
|
|
30
31
|
lowerDefaultFunctionWithRegistry,
|
|
31
32
|
parseDefaultFunctionCall,
|
|
@@ -207,7 +208,7 @@ export function reportUnknownFieldPreset(input: {
|
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
export function instantiatePslTypeConstructor(input: {
|
|
210
|
-
readonly call:
|
|
211
|
+
readonly call: ResolvedTypeConstructorCall;
|
|
211
212
|
readonly descriptor: AuthoringTypeConstructorDescriptor;
|
|
212
213
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
213
214
|
readonly sourceId: string;
|
|
@@ -265,7 +266,7 @@ function pushUnsupportedTypeConstructorDiagnostic(input: {
|
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
export function resolvePslTypeConstructorDescriptor(input: {
|
|
268
|
-
readonly call:
|
|
269
|
+
readonly call: ResolvedTypeConstructorCall;
|
|
269
270
|
readonly authoringContributions: AuthoringContributions | undefined;
|
|
270
271
|
readonly composedExtensions: ReadonlySet<string>;
|
|
271
272
|
readonly familyId: string;
|
|
@@ -314,8 +315,8 @@ export function resolvePslTypeConstructorDescriptor(input: {
|
|
|
314
315
|
*
|
|
315
316
|
* Symmetric with `instantiatePslTypeConstructor` but richer: a field preset can contribute `default`, `executionDefaults`, `id`, `unique`, and `nullable` in addition to the storage-type triple. PSL → typed-args coercion happens here (via `mapPslHelperArgs`) so that `instantiateAuthoringFieldPreset` itself stays typed-input-only and TS keeps its zero-runtime-validation cost.
|
|
316
317
|
*/
|
|
317
|
-
export function
|
|
318
|
-
readonly call:
|
|
318
|
+
export function instantiateFieldPreset(input: {
|
|
319
|
+
readonly call: ResolvedTypeConstructorCall;
|
|
319
320
|
readonly descriptor: AuthoringFieldPresetDescriptor;
|
|
320
321
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
321
322
|
readonly sourceId: string;
|
|
@@ -395,7 +396,7 @@ export type ResolveFieldTypeResult =
|
|
|
395
396
|
| { readonly ok: false; readonly alreadyReported: boolean };
|
|
396
397
|
|
|
397
398
|
export function resolveFieldTypeDescriptor(input: {
|
|
398
|
-
readonly field:
|
|
399
|
+
readonly field: FieldSymbol;
|
|
399
400
|
readonly enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
400
401
|
readonly namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
401
402
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
@@ -407,6 +408,10 @@ export function resolveFieldTypeDescriptor(input: {
|
|
|
407
408
|
readonly sourceId: string;
|
|
408
409
|
readonly entityLabel: string;
|
|
409
410
|
}): ResolveFieldTypeResult {
|
|
411
|
+
// Avoid cascading unsupported-type diagnostics after invalid qualification.
|
|
412
|
+
if (input.field.malformedType) {
|
|
413
|
+
return { ok: false, alreadyReported: true };
|
|
414
|
+
}
|
|
410
415
|
if (input.field.typeConstructor) {
|
|
411
416
|
// Field presets carry richer semantics than type constructors, so a field preset match is the complete answer. Shared composition rejects exact cross-registry collisions before PSL resolution can observe them.
|
|
412
417
|
const presetDescriptor = getAuthoringFieldPreset(
|
|
@@ -414,7 +419,7 @@ export function resolveFieldTypeDescriptor(input: {
|
|
|
414
419
|
input.field.typeConstructor.path,
|
|
415
420
|
);
|
|
416
421
|
if (presetDescriptor) {
|
|
417
|
-
const instantiated =
|
|
422
|
+
const instantiated = instantiateFieldPreset({
|
|
418
423
|
call: input.field.typeConstructor,
|
|
419
424
|
descriptor: presetDescriptor,
|
|
420
425
|
diagnostics: input.diagnostics,
|
|
@@ -550,7 +555,7 @@ export const NATIVE_TYPE_SPECS: Readonly<Record<string, NativeTypeSpec>> = {
|
|
|
550
555
|
codecId: 'sql/char@1',
|
|
551
556
|
nativeType: 'character',
|
|
552
557
|
},
|
|
553
|
-
'db.Uuid': { args: 'noArgs', baseType: 'String', codecId:
|
|
558
|
+
'db.Uuid': { args: 'noArgs', baseType: 'String', codecId: 'pg/uuid@1', nativeType: 'uuid' },
|
|
554
559
|
'db.SmallInt': { args: 'noArgs', baseType: 'Int', codecId: 'pg/int2@1', nativeType: 'int2' },
|
|
555
560
|
'db.Real': { args: 'noArgs', baseType: 'Float', codecId: 'pg/float4@1', nativeType: 'float4' },
|
|
556
561
|
'db.Numeric': {
|
|
@@ -588,7 +593,7 @@ export const NATIVE_TYPE_SPECS: Readonly<Record<string, NativeTypeSpec>> = {
|
|
|
588
593
|
};
|
|
589
594
|
|
|
590
595
|
export function resolveDbNativeTypeAttribute(input: {
|
|
591
|
-
readonly attribute:
|
|
596
|
+
readonly attribute: ResolvedAttribute;
|
|
592
597
|
readonly baseType: string;
|
|
593
598
|
readonly baseDescriptor: ColumnDescriptor;
|
|
594
599
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
@@ -703,7 +708,7 @@ export function parseDefaultLiteralValue(expression: string): ColumnDefault | un
|
|
|
703
708
|
export function lowerDefaultForField(input: {
|
|
704
709
|
readonly modelName: string;
|
|
705
710
|
readonly fieldName: string;
|
|
706
|
-
readonly defaultAttribute:
|
|
711
|
+
readonly defaultAttribute: ResolvedAttribute;
|
|
707
712
|
readonly columnDescriptor: ColumnDescriptor;
|
|
708
713
|
readonly generatorDescriptorById: ReadonlyMap<string, MutationDefaultGeneratorDescriptor>;
|
|
709
714
|
readonly sourceId: string;
|
|
@@ -809,14 +814,11 @@ export function lowerDefaultForField(input: {
|
|
|
809
814
|
}
|
|
810
815
|
|
|
811
816
|
export function resolveColumnDescriptor(
|
|
812
|
-
field:
|
|
817
|
+
field: FieldSymbol,
|
|
813
818
|
enumTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
814
819
|
namedTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
815
820
|
scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>,
|
|
816
821
|
): ColumnDescriptor | undefined {
|
|
817
|
-
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) {
|
|
818
|
-
return namedTypeDescriptors.get(field.typeRef);
|
|
819
|
-
}
|
|
820
822
|
if (namedTypeDescriptors.has(field.typeName)) {
|
|
821
823
|
return namedTypeDescriptors.get(field.typeName);
|
|
822
824
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { ContractSourceDiagnostic } from '@prisma-next/config/config-types';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ColumnDefault,
|
|
4
|
+
ColumnDefaultLiteralInputValue,
|
|
5
|
+
ExecutionMutationDefaultPhases,
|
|
6
|
+
} from '@prisma-next/contract/types';
|
|
3
7
|
import type { AuthoringContributions } from '@prisma-next/framework-components/authoring';
|
|
4
8
|
import type {
|
|
5
9
|
ControlMutationDefaultRegistry,
|
|
6
10
|
MutationDefaultGeneratorDescriptor,
|
|
7
11
|
} from '@prisma-next/framework-components/control';
|
|
8
|
-
import type {
|
|
12
|
+
import type { FieldSymbol, ModelSymbol, ResolvedAttribute } from '@prisma-next/psl-parser';
|
|
13
|
+
import type { EnumTypeHandle } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
14
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
9
15
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
10
16
|
import {
|
|
11
17
|
getAttribute,
|
|
@@ -21,10 +27,74 @@ import {
|
|
|
21
27
|
resolveFieldTypeDescriptor,
|
|
22
28
|
} from './psl-column-resolution';
|
|
23
29
|
|
|
30
|
+
type LoweredFieldDefault = {
|
|
31
|
+
readonly defaultValue?: ColumnDefault;
|
|
32
|
+
readonly executionDefaults?: ExecutionMutationDefaultPhases;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function lowerEnumDefaultForField(input: {
|
|
36
|
+
readonly modelName: string;
|
|
37
|
+
readonly fieldName: string;
|
|
38
|
+
readonly defaultAttribute: ResolvedAttribute;
|
|
39
|
+
readonly enumHandle: EnumTypeHandle;
|
|
40
|
+
readonly sourceId: string;
|
|
41
|
+
readonly diagnostics: ContractSourceDiagnostic[];
|
|
42
|
+
}): LoweredFieldDefault {
|
|
43
|
+
const positionalEntries = input.defaultAttribute.args.filter((arg) => arg.kind === 'positional');
|
|
44
|
+
const hasNamedEntries = input.defaultAttribute.args.some((arg) => arg.kind === 'named');
|
|
45
|
+
const expressionEntry = positionalEntries[0];
|
|
46
|
+
if (hasNamedEntries || positionalEntries.length !== 1 || expressionEntry === undefined) {
|
|
47
|
+
input.diagnostics.push({
|
|
48
|
+
code: 'PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT',
|
|
49
|
+
message: `Field "${input.modelName}.${input.fieldName}" @default on an enum field expects exactly one positional enum member argument.`,
|
|
50
|
+
sourceId: input.sourceId,
|
|
51
|
+
span: input.defaultAttribute.span,
|
|
52
|
+
});
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const raw = expressionEntry.value.trim();
|
|
57
|
+
const isQuotedString = /^(['"]).*\1$/.test(raw);
|
|
58
|
+
const isFunctionCall = raw.includes('(') && raw.endsWith(')');
|
|
59
|
+
|
|
60
|
+
if (isQuotedString || isFunctionCall) {
|
|
61
|
+
input.diagnostics.push({
|
|
62
|
+
code: 'PSL_ENUM_DEFAULT_MUST_BE_MEMBER_NAME',
|
|
63
|
+
message: `Field "${input.modelName}.${input.fieldName}" @default on an enum field must name a member (e.g. @default(Low)), not a raw value or function.`,
|
|
64
|
+
sourceId: input.sourceId,
|
|
65
|
+
span: input.defaultAttribute.span,
|
|
66
|
+
});
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const match = input.enumHandle.enumMembers.find((m) => m.name === raw);
|
|
71
|
+
if (!match) {
|
|
72
|
+
const validNames = input.enumHandle.enumMembers.map((m) => m.name).join(', ');
|
|
73
|
+
input.diagnostics.push({
|
|
74
|
+
code: 'PSL_ENUM_UNKNOWN_DEFAULT_MEMBER',
|
|
75
|
+
message: `Field "${input.modelName}.${input.fieldName}" @default(${raw}) does not name a member of ${input.enumHandle.enumName}. Valid members: ${validNames}.`,
|
|
76
|
+
sourceId: input.sourceId,
|
|
77
|
+
span: input.defaultAttribute.span,
|
|
78
|
+
});
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
defaultValue: {
|
|
84
|
+
kind: 'literal',
|
|
85
|
+
value: blindCast<
|
|
86
|
+
ColumnDefaultLiteralInputValue,
|
|
87
|
+
'enum member values are codec-validated JsonValue-compatible scalars'
|
|
88
|
+
>(match.value),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
24
93
|
export type ResolvedField = {
|
|
25
|
-
readonly field:
|
|
94
|
+
readonly field: FieldSymbol;
|
|
26
95
|
readonly columnName: string;
|
|
27
96
|
readonly descriptor: ColumnDescriptor;
|
|
97
|
+
readonly nullable: boolean;
|
|
28
98
|
readonly defaultValue?: ColumnDefault;
|
|
29
99
|
readonly executionDefaults?: ExecutionMutationDefaultPhases;
|
|
30
100
|
readonly isId: boolean;
|
|
@@ -37,7 +107,7 @@ export type ResolvedField = {
|
|
|
37
107
|
};
|
|
38
108
|
|
|
39
109
|
export type ModelNameMapping = {
|
|
40
|
-
readonly model:
|
|
110
|
+
readonly model: ModelSymbol;
|
|
41
111
|
readonly tableName: string;
|
|
42
112
|
readonly fieldColumns: Map<string, string>;
|
|
43
113
|
};
|
|
@@ -50,7 +120,7 @@ export type ModelNameMapping = {
|
|
|
50
120
|
* {@link modelCoordinateKey} rather than the bare model name.
|
|
51
121
|
*/
|
|
52
122
|
export type ModelNamespaceEntry = {
|
|
53
|
-
readonly model:
|
|
123
|
+
readonly model: ModelSymbol;
|
|
54
124
|
readonly namespaceId: string | undefined;
|
|
55
125
|
};
|
|
56
126
|
|
|
@@ -61,7 +131,7 @@ export function modelCoordinateKey(namespaceId: string, modelName: string): stri
|
|
|
61
131
|
}
|
|
62
132
|
|
|
63
133
|
export interface CollectResolvedFieldsInput {
|
|
64
|
-
readonly model:
|
|
134
|
+
readonly model: ModelSymbol;
|
|
65
135
|
readonly mapping: ModelNameMapping;
|
|
66
136
|
readonly enumTypeDescriptors: Map<string, ColumnDescriptor>;
|
|
67
137
|
readonly namedTypeDescriptors: Map<string, ColumnDescriptor>;
|
|
@@ -76,6 +146,7 @@ export interface CollectResolvedFieldsInput {
|
|
|
76
146
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
77
147
|
readonly sourceId: string;
|
|
78
148
|
readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
|
|
149
|
+
readonly enumHandles?: ReadonlyMap<string, EnumTypeHandle>;
|
|
79
150
|
}
|
|
80
151
|
|
|
81
152
|
const BUILTIN_FIELD_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([
|
|
@@ -95,12 +166,12 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([
|
|
|
95
166
|
* migrated (so they don't get told to do what they just did).
|
|
96
167
|
*
|
|
97
168
|
* Pairing the suppression predicate with the hint makes each entry
|
|
98
|
-
* self-contained: a future entry for, say, `@id` ↔ `id.
|
|
169
|
+
* self-contained: a future entry for, say, `@id` ↔ `id.uuidv7String()` cannot
|
|
99
170
|
* silently inherit the wrong predicate when added.
|
|
100
171
|
*/
|
|
101
172
|
interface RemovedAttributeRule {
|
|
102
173
|
readonly hint: string;
|
|
103
|
-
readonly suppressWhen: (field:
|
|
174
|
+
readonly suppressWhen: (field: FieldSymbol) => boolean;
|
|
104
175
|
}
|
|
105
176
|
|
|
106
177
|
const REMOVED_ATTRIBUTE_RULES: ReadonlyMap<string, RemovedAttributeRule> = new Map([
|
|
@@ -130,8 +201,8 @@ const REMOVED_ATTRIBUTE_RULES: ReadonlyMap<string, RemovedAttributeRule> = new M
|
|
|
130
201
|
}
|
|
131
202
|
|
|
132
203
|
function validateFieldAttributes(input: {
|
|
133
|
-
readonly model:
|
|
134
|
-
readonly field:
|
|
204
|
+
readonly model: ModelSymbol;
|
|
205
|
+
readonly field: FieldSymbol;
|
|
135
206
|
readonly composedExtensions: ReadonlySet<string>;
|
|
136
207
|
readonly authoringContributions: AuthoringContributions | undefined;
|
|
137
208
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
@@ -177,13 +248,13 @@ function validateFieldAttributes(input: {
|
|
|
177
248
|
}
|
|
178
249
|
|
|
179
250
|
function extractFieldConstraintNames(input: {
|
|
180
|
-
readonly model:
|
|
181
|
-
readonly field:
|
|
251
|
+
readonly model: ModelSymbol;
|
|
252
|
+
readonly field: FieldSymbol;
|
|
182
253
|
readonly sourceId: string;
|
|
183
254
|
readonly diagnostics: ContractSourceDiagnostic[];
|
|
184
255
|
}): {
|
|
185
|
-
readonly idAttribute:
|
|
186
|
-
readonly uniqueAttribute:
|
|
256
|
+
readonly idAttribute: ResolvedAttribute | undefined;
|
|
257
|
+
readonly uniqueAttribute: ResolvedAttribute | undefined;
|
|
187
258
|
readonly idName: string | undefined;
|
|
188
259
|
readonly uniqueName: string | undefined;
|
|
189
260
|
} {
|
|
@@ -225,10 +296,11 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
|
|
|
225
296
|
diagnostics,
|
|
226
297
|
sourceId,
|
|
227
298
|
scalarTypeDescriptors,
|
|
299
|
+
enumHandles,
|
|
228
300
|
} = input;
|
|
229
301
|
const resolvedFields: ResolvedField[] = [];
|
|
230
302
|
|
|
231
|
-
for (const field of model.fields) {
|
|
303
|
+
for (const field of Object.values(model.fields)) {
|
|
232
304
|
const isModelField = modelNames.has(field.typeName);
|
|
233
305
|
|
|
234
306
|
if (field.list && isModelField) {
|
|
@@ -350,17 +422,27 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
|
|
|
350
422
|
});
|
|
351
423
|
continue;
|
|
352
424
|
}
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
425
|
+
const enumHandle = enumHandles?.get(field.typeName);
|
|
426
|
+
const loweredDefault: LoweredFieldDefault = defaultAttribute
|
|
427
|
+
? enumHandle
|
|
428
|
+
? lowerEnumDefaultForField({
|
|
429
|
+
modelName: model.name,
|
|
430
|
+
fieldName: field.name,
|
|
431
|
+
defaultAttribute,
|
|
432
|
+
enumHandle,
|
|
433
|
+
sourceId,
|
|
434
|
+
diagnostics,
|
|
435
|
+
})
|
|
436
|
+
: lowerDefaultForField({
|
|
437
|
+
modelName: model.name,
|
|
438
|
+
fieldName: field.name,
|
|
439
|
+
defaultAttribute,
|
|
440
|
+
columnDescriptor: descriptor,
|
|
441
|
+
generatorDescriptorById,
|
|
442
|
+
sourceId,
|
|
443
|
+
defaultFunctionRegistry,
|
|
444
|
+
diagnostics,
|
|
445
|
+
})
|
|
364
446
|
: {};
|
|
365
447
|
const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
|
|
366
448
|
if (field.optional && loweredOnCreate) {
|
|
@@ -374,7 +456,8 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
|
|
|
374
456
|
});
|
|
375
457
|
continue;
|
|
376
458
|
}
|
|
377
|
-
|
|
459
|
+
const fieldUsesNamedType = namedTypeDescriptors.has(field.typeName);
|
|
460
|
+
if (loweredOnCreate && !fieldUsesNamedType) {
|
|
378
461
|
const generatorDescriptor = generatorDescriptorById.get(loweredOnCreate.id);
|
|
379
462
|
const generatedDescriptor = generatorDescriptor?.resolveGeneratedColumnDescriptor?.({
|
|
380
463
|
generated: loweredOnCreate,
|
|
@@ -426,6 +509,7 @@ export function collectResolvedFields(input: CollectResolvedFieldsInput): Resolv
|
|
|
426
509
|
field,
|
|
427
510
|
columnName: mappedColumnName,
|
|
428
511
|
descriptor,
|
|
512
|
+
nullable: presetContributions?.nullable ?? field.optional,
|
|
429
513
|
...ifDefined('defaultValue', fieldDefaultValue),
|
|
430
514
|
...ifDefined('executionDefaults', fieldExecutionDefaults),
|
|
431
515
|
isId: isIdField || Boolean(presetContributions?.id),
|
|
@@ -459,7 +543,7 @@ export function buildModelMappings(
|
|
|
459
543
|
span: model.span,
|
|
460
544
|
});
|
|
461
545
|
const fieldColumns = new Map<string, string>();
|
|
462
|
-
for (const field of model.fields) {
|
|
546
|
+
for (const field of Object.values(model.fields)) {
|
|
463
547
|
const fieldMapAttribute = getAttribute(field.attributes, 'map');
|
|
464
548
|
const columnName = parseMapName({
|
|
465
549
|
attribute: fieldMapAttribute,
|