@prisma-next/sql-contract-psl 0.5.0-dev.6 → 0.5.0-dev.60
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 +9 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-iFCRN9nb.mjs → interpreter-BChe-9vN.mjs} +354 -46
- package/dist/interpreter-BChe-9vN.mjs.map +1 -0
- package/dist/provider.d.mts +2 -2
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -1
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -11
- package/src/interpreter.ts +128 -28
- package/src/provider.ts +2 -2
- package/src/psl-attribute-parsing.ts +14 -5
- package/src/psl-column-resolution.ts +253 -28
- package/src/psl-field-resolution.ts +138 -17
- package/src/psl-relation-resolution.ts +3 -0
- package/dist/interpreter-iFCRN9nb.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { instantiateAuthoringTypeConstructor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
1
|
+
import { hasRegisteredFieldNamespace, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
2
2
|
import { buildSqlContractFromDefinition } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
3
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
4
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
@@ -149,7 +149,7 @@ function parseAttributeFieldList(input) {
|
|
|
149
149
|
if (!raw) {
|
|
150
150
|
input.diagnostics.push({
|
|
151
151
|
code: input.code,
|
|
152
|
-
message: `${input.
|
|
152
|
+
message: `${input.entityLabel} requires fields list argument`,
|
|
153
153
|
sourceId: input.sourceId,
|
|
154
154
|
span: input.attribute.span
|
|
155
155
|
});
|
|
@@ -159,7 +159,7 @@ function parseAttributeFieldList(input) {
|
|
|
159
159
|
if (!fields || fields.length === 0) {
|
|
160
160
|
input.diagnostics.push({
|
|
161
161
|
code: input.code,
|
|
162
|
-
message: `${input.
|
|
162
|
+
message: `${input.entityLabel} requires bracketed field list argument`,
|
|
163
163
|
sourceId: input.sourceId,
|
|
164
164
|
span: input.attribute.span
|
|
165
165
|
});
|
|
@@ -167,6 +167,13 @@ function parseAttributeFieldList(input) {
|
|
|
167
167
|
}
|
|
168
168
|
return fields;
|
|
169
169
|
}
|
|
170
|
+
function findDuplicateFieldName(fieldNames) {
|
|
171
|
+
const seen = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const name of fieldNames) {
|
|
173
|
+
if (seen.has(name)) return name;
|
|
174
|
+
seen.add(name);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
170
177
|
function mapFieldNamesToColumns(input) {
|
|
171
178
|
const columns = [];
|
|
172
179
|
for (const fieldName of input.fieldNames) {
|
|
@@ -174,7 +181,7 @@ function mapFieldNamesToColumns(input) {
|
|
|
174
181
|
if (!columnName) {
|
|
175
182
|
input.diagnostics.push({
|
|
176
183
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
177
|
-
message: `${input.
|
|
184
|
+
message: `${input.entityLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
178
185
|
sourceId: input.sourceId,
|
|
179
186
|
span: input.span
|
|
180
187
|
});
|
|
@@ -620,6 +627,26 @@ function getAuthoringTypeConstructor(contributions, path) {
|
|
|
620
627
|
return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
|
|
621
628
|
}
|
|
622
629
|
/**
|
|
630
|
+
* Walks `authoringContributions.field` segment-by-segment and returns the
|
|
631
|
+
* field-preset descriptor at the resolved path, or `undefined` if no descriptor
|
|
632
|
+
* is registered.
|
|
633
|
+
*
|
|
634
|
+
* Symmetric with `getAuthoringTypeConstructor`. Field presets are strictly
|
|
635
|
+
* richer than type constructors — they can contribute `default` /
|
|
636
|
+
* `executionDefaults` / `id` / `unique` / `nullable` in addition to the
|
|
637
|
+
* `codecId` / `nativeType` / `typeParams` triple. PSL resolution tries field
|
|
638
|
+
* presets first, then falls back to type constructors on miss (see
|
|
639
|
+
* `resolveFieldTypeDescriptor`).
|
|
640
|
+
*/
|
|
641
|
+
function getAuthoringFieldPreset(contributions, path) {
|
|
642
|
+
let current = contributions?.field;
|
|
643
|
+
for (const segment of path) {
|
|
644
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return;
|
|
645
|
+
current = current[segment];
|
|
646
|
+
}
|
|
647
|
+
return isAuthoringFieldPresetDescriptor(current) ? current : void 0;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
623
650
|
* Returns the namespace prefix of `attributeName` if it references an
|
|
624
651
|
* unrecognized extension namespace, otherwise `undefined`. A namespace is
|
|
625
652
|
* considered recognized when it is:
|
|
@@ -627,17 +654,19 @@ function getAuthoringTypeConstructor(contributions, path) {
|
|
|
627
654
|
* - `db` (native-type spec, always allowed),
|
|
628
655
|
* - the active family id (e.g. `sql`),
|
|
629
656
|
* - the active target id (e.g. `postgres`),
|
|
657
|
+
* - a registered field-preset namespace (e.g. `temporal`),
|
|
630
658
|
* - present in `composedExtensions`.
|
|
631
659
|
*
|
|
632
|
-
* Family/target namespaces are exempted so that e.g. `@sql.foo`
|
|
633
|
-
* PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined)
|
|
634
|
-
* PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already
|
|
660
|
+
* Family/target/field-preset namespaces are exempted so that e.g. `@sql.foo`
|
|
661
|
+
* surfaces as PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined)
|
|
662
|
+
* rather than PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already
|
|
663
|
+
* composed).
|
|
635
664
|
*/
|
|
636
665
|
function checkUncomposedNamespace(attributeName, composedExtensions, context) {
|
|
637
666
|
const dotIndex = attributeName.indexOf(".");
|
|
638
667
|
if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
|
|
639
668
|
const namespace = attributeName.slice(0, dotIndex);
|
|
640
|
-
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
|
|
669
|
+
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
|
|
641
670
|
return namespace;
|
|
642
671
|
}
|
|
643
672
|
/**
|
|
@@ -660,6 +689,24 @@ function reportUncomposedNamespace(input) {
|
|
|
660
689
|
}
|
|
661
690
|
});
|
|
662
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Pushes the canonical `PSL_UNKNOWN_FIELD_PRESET` diagnostic when a typoed
|
|
694
|
+
* preset name is referenced inside a registered field-preset namespace. The
|
|
695
|
+
* `data` payload exposes the namespace and full helper path so machine
|
|
696
|
+
* consumers (agents, IDE extensions) don't have to parse the prose.
|
|
697
|
+
*/
|
|
698
|
+
function reportUnknownFieldPreset(input) {
|
|
699
|
+
input.diagnostics.push({
|
|
700
|
+
code: "PSL_UNKNOWN_FIELD_PRESET",
|
|
701
|
+
message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
|
|
702
|
+
sourceId: input.sourceId,
|
|
703
|
+
span: input.span,
|
|
704
|
+
data: {
|
|
705
|
+
namespace: input.namespace,
|
|
706
|
+
helperPath: input.helperPath
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
663
710
|
function instantiatePslTypeConstructor(input) {
|
|
664
711
|
const helperPath = input.call.path.join(".");
|
|
665
712
|
const args = mapPslHelperArgs({
|
|
@@ -697,11 +744,15 @@ function pushUnsupportedTypeConstructorDiagnostic(input) {
|
|
|
697
744
|
function resolvePslTypeConstructorDescriptor(input) {
|
|
698
745
|
const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
|
|
699
746
|
if (descriptor) return descriptor;
|
|
700
|
-
const
|
|
701
|
-
|
|
747
|
+
const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
|
|
748
|
+
familyId: input.familyId,
|
|
749
|
+
targetId: input.targetId,
|
|
750
|
+
authoringContributions: input.authoringContributions
|
|
751
|
+
});
|
|
752
|
+
if (uncomposedNamespace) {
|
|
702
753
|
reportUncomposedNamespace({
|
|
703
754
|
subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
|
|
704
|
-
namespace,
|
|
755
|
+
namespace: uncomposedNamespace,
|
|
705
756
|
sourceId: input.sourceId,
|
|
706
757
|
span: input.call.span,
|
|
707
758
|
diagnostics: input.diagnostics
|
|
@@ -716,10 +767,102 @@ function resolvePslTypeConstructorDescriptor(input) {
|
|
|
716
767
|
message: input.unsupportedMessage
|
|
717
768
|
});
|
|
718
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Instantiates a field-preset call against its descriptor, coercing PSL AST
|
|
772
|
+
* arguments into the descriptor's typed argument shape and returning the
|
|
773
|
+
* preset's full set of contract contributions.
|
|
774
|
+
*
|
|
775
|
+
* Symmetric with `instantiatePslTypeConstructor` but richer: a field preset
|
|
776
|
+
* can contribute `default`, `executionDefaults`, `id`, `unique`, and
|
|
777
|
+
* `nullable` in addition to the storage-type triple. PSL → typed-args
|
|
778
|
+
* coercion happens here (via `mapPslHelperArgs`) so that
|
|
779
|
+
* `instantiateAuthoringFieldPreset` itself stays typed-input-only and TS
|
|
780
|
+
* keeps its zero-runtime-validation cost.
|
|
781
|
+
*/
|
|
782
|
+
function instantiatePslFieldPreset(input) {
|
|
783
|
+
const helperPath = input.call.path.join(".");
|
|
784
|
+
const args = mapPslHelperArgs({
|
|
785
|
+
args: input.call.args,
|
|
786
|
+
descriptors: input.descriptor.args ?? [],
|
|
787
|
+
helperLabel: `preset "${helperPath}"`,
|
|
788
|
+
span: input.call.span,
|
|
789
|
+
diagnostics: input.diagnostics,
|
|
790
|
+
sourceId: input.sourceId,
|
|
791
|
+
entityLabel: input.entityLabel
|
|
792
|
+
});
|
|
793
|
+
if (!args) return;
|
|
794
|
+
try {
|
|
795
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
796
|
+
const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
|
|
797
|
+
return {
|
|
798
|
+
descriptor: {
|
|
799
|
+
codecId: instantiated.descriptor.codecId,
|
|
800
|
+
nativeType: instantiated.descriptor.nativeType,
|
|
801
|
+
...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
|
|
802
|
+
},
|
|
803
|
+
nullable: instantiated.nullable,
|
|
804
|
+
...instantiated.default !== void 0 ? { default: instantiated.default } : {},
|
|
805
|
+
...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
|
|
806
|
+
id: instantiated.id,
|
|
807
|
+
unique: instantiated.unique
|
|
808
|
+
};
|
|
809
|
+
} catch (error) {
|
|
810
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
811
|
+
input.diagnostics.push({
|
|
812
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
813
|
+
message: `${input.entityLabel} preset "${helperPath}" ${message}`,
|
|
814
|
+
sourceId: input.sourceId,
|
|
815
|
+
span: input.call.span
|
|
816
|
+
});
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
719
820
|
function resolveFieldTypeDescriptor(input) {
|
|
720
821
|
if (input.field.typeConstructor) {
|
|
822
|
+
const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
|
|
823
|
+
if (presetDescriptor) {
|
|
824
|
+
const instantiated$1 = instantiatePslFieldPreset({
|
|
825
|
+
call: input.field.typeConstructor,
|
|
826
|
+
descriptor: presetDescriptor,
|
|
827
|
+
diagnostics: input.diagnostics,
|
|
828
|
+
sourceId: input.sourceId,
|
|
829
|
+
entityLabel: input.entityLabel
|
|
830
|
+
});
|
|
831
|
+
if (!instantiated$1) return {
|
|
832
|
+
ok: false,
|
|
833
|
+
alreadyReported: true
|
|
834
|
+
};
|
|
835
|
+
const presetContributions = {
|
|
836
|
+
nullable: instantiated$1.nullable,
|
|
837
|
+
id: instantiated$1.id,
|
|
838
|
+
unique: instantiated$1.unique,
|
|
839
|
+
...instantiated$1.default !== void 0 ? { default: instantiated$1.default } : {},
|
|
840
|
+
...instantiated$1.executionDefaults !== void 0 ? { executionDefaults: instantiated$1.executionDefaults } : {}
|
|
841
|
+
};
|
|
842
|
+
return {
|
|
843
|
+
ok: true,
|
|
844
|
+
descriptor: instantiated$1.descriptor,
|
|
845
|
+
presetContributions
|
|
846
|
+
};
|
|
847
|
+
}
|
|
721
848
|
const helperPath = input.field.typeConstructor.path.join(".");
|
|
722
|
-
const
|
|
849
|
+
const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
|
|
850
|
+
const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
|
|
851
|
+
if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
|
|
852
|
+
reportUnknownFieldPreset({
|
|
853
|
+
entityLabel: input.entityLabel,
|
|
854
|
+
namespace: namespacePrefix,
|
|
855
|
+
helperPath,
|
|
856
|
+
sourceId: input.sourceId,
|
|
857
|
+
span: input.field.typeConstructor.span,
|
|
858
|
+
diagnostics: input.diagnostics
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
ok: false,
|
|
862
|
+
alreadyReported: true
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const descriptor$1 = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
|
|
723
866
|
call: input.field.typeConstructor,
|
|
724
867
|
authoringContributions: input.authoringContributions,
|
|
725
868
|
composedExtensions: input.composedExtensions,
|
|
@@ -985,6 +1128,15 @@ function lowerDefaultForField(input) {
|
|
|
985
1128
|
});
|
|
986
1129
|
return {};
|
|
987
1130
|
}
|
|
1131
|
+
if (generatorDescriptor.applicableCodecIds === void 0) {
|
|
1132
|
+
input.diagnostics.push({
|
|
1133
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
1134
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "@default(...)" lowering. Use the corresponding field preset (e.g. \`temporal.${generatorDescriptor.id === "timestampNow" ? "updatedAt" : generatorDescriptor.id}()\`) instead.`,
|
|
1135
|
+
sourceId: input.sourceId,
|
|
1136
|
+
span: expressionEntry.span
|
|
1137
|
+
});
|
|
1138
|
+
return {};
|
|
1139
|
+
}
|
|
988
1140
|
if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
|
|
989
1141
|
input.diagnostics.push({
|
|
990
1142
|
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
@@ -994,7 +1146,7 @@ function lowerDefaultForField(input) {
|
|
|
994
1146
|
});
|
|
995
1147
|
return {};
|
|
996
1148
|
}
|
|
997
|
-
return {
|
|
1149
|
+
return { executionDefaults: { onCreate: lowered.value.generated } };
|
|
998
1150
|
}
|
|
999
1151
|
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
|
|
1000
1152
|
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
@@ -1012,12 +1164,21 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
|
|
|
1012
1164
|
"relation",
|
|
1013
1165
|
"map"
|
|
1014
1166
|
]);
|
|
1167
|
+
const REMOVED_ATTRIBUTE_RULES = new Map([["updatedAt", {
|
|
1168
|
+
hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
|
|
1169
|
+
suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
|
|
1170
|
+
}]]);
|
|
1171
|
+
{
|
|
1172
|
+
const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
|
|
1173
|
+
if (overlap.length > 0) throw new Error(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
|
|
1174
|
+
}
|
|
1015
1175
|
function validateFieldAttributes(input) {
|
|
1016
1176
|
for (const attribute of input.field.attributes) {
|
|
1017
1177
|
if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
|
|
1018
1178
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1019
1179
|
familyId: input.familyId,
|
|
1020
|
-
targetId: input.targetId
|
|
1180
|
+
targetId: input.targetId,
|
|
1181
|
+
authoringContributions: input.authoringContributions
|
|
1021
1182
|
});
|
|
1022
1183
|
if (uncomposedNamespace) {
|
|
1023
1184
|
reportUncomposedNamespace({
|
|
@@ -1029,9 +1190,12 @@ function validateFieldAttributes(input) {
|
|
|
1029
1190
|
});
|
|
1030
1191
|
continue;
|
|
1031
1192
|
}
|
|
1193
|
+
const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
|
|
1194
|
+
const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
|
|
1195
|
+
const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
|
|
1032
1196
|
input.diagnostics.push({
|
|
1033
1197
|
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1034
|
-
message
|
|
1198
|
+
message,
|
|
1035
1199
|
sourceId: input.sourceId,
|
|
1036
1200
|
span: attribute.span
|
|
1037
1201
|
});
|
|
@@ -1065,21 +1229,25 @@ function collectResolvedFields(input) {
|
|
|
1065
1229
|
const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
|
|
1066
1230
|
const resolvedFields = [];
|
|
1067
1231
|
for (const field of model.fields) {
|
|
1068
|
-
|
|
1232
|
+
const isModelField = modelNames.has(field.typeName);
|
|
1233
|
+
if (field.list && isModelField) continue;
|
|
1069
1234
|
validateFieldAttributes({
|
|
1070
1235
|
model,
|
|
1071
1236
|
field,
|
|
1072
1237
|
composedExtensions,
|
|
1238
|
+
authoringContributions,
|
|
1073
1239
|
diagnostics,
|
|
1074
1240
|
sourceId,
|
|
1075
1241
|
familyId,
|
|
1076
1242
|
targetId
|
|
1077
1243
|
});
|
|
1078
|
-
|
|
1244
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1245
|
+
if (isModelField && relationAttribute) continue;
|
|
1079
1246
|
const isValueObjectField = compositeTypeNames.has(field.typeName);
|
|
1080
1247
|
const isListField = field.list;
|
|
1081
1248
|
let descriptor;
|
|
1082
1249
|
let scalarCodecId;
|
|
1250
|
+
let presetContributions;
|
|
1083
1251
|
const resolveInput = {
|
|
1084
1252
|
field,
|
|
1085
1253
|
enumTypeDescriptors,
|
|
@@ -1105,6 +1273,15 @@ function collectResolvedFields(input) {
|
|
|
1105
1273
|
});
|
|
1106
1274
|
continue;
|
|
1107
1275
|
}
|
|
1276
|
+
if (resolved.presetContributions) {
|
|
1277
|
+
diagnostics.push({
|
|
1278
|
+
code: "PSL_PRESET_NOT_LIST",
|
|
1279
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call as a list element type. Presets cannot be list elements; remove "[]" or use a scalar type.`,
|
|
1280
|
+
sourceId,
|
|
1281
|
+
span: field.span
|
|
1282
|
+
});
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1108
1285
|
scalarCodecId = resolved.descriptor.codecId;
|
|
1109
1286
|
descriptor = scalarTypeDescriptors.get("Json");
|
|
1110
1287
|
} else {
|
|
@@ -1119,9 +1296,28 @@ function collectResolvedFields(input) {
|
|
|
1119
1296
|
continue;
|
|
1120
1297
|
}
|
|
1121
1298
|
descriptor = resolved.descriptor;
|
|
1299
|
+
presetContributions = resolved.presetContributions;
|
|
1122
1300
|
}
|
|
1123
1301
|
if (!descriptor) continue;
|
|
1302
|
+
if (presetContributions && field.optional) {
|
|
1303
|
+
diagnostics.push({
|
|
1304
|
+
code: "PSL_PRESET_NOT_OPTIONAL",
|
|
1305
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
|
|
1306
|
+
sourceId,
|
|
1307
|
+
span: field.span
|
|
1308
|
+
});
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1124
1311
|
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
1312
|
+
if (presetContributions && defaultAttribute) {
|
|
1313
|
+
diagnostics.push({
|
|
1314
|
+
code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
|
|
1315
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
|
|
1316
|
+
sourceId,
|
|
1317
|
+
span: defaultAttribute.span
|
|
1318
|
+
});
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1125
1321
|
const loweredDefault = defaultAttribute ? lowerDefaultForField({
|
|
1126
1322
|
modelName: model.name,
|
|
1127
1323
|
fieldName: field.name,
|
|
@@ -1132,8 +1328,9 @@ function collectResolvedFields(input) {
|
|
|
1132
1328
|
defaultFunctionRegistry,
|
|
1133
1329
|
diagnostics
|
|
1134
1330
|
}) : {};
|
|
1135
|
-
|
|
1136
|
-
|
|
1331
|
+
const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
|
|
1332
|
+
if (field.optional && loweredOnCreate) {
|
|
1333
|
+
const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
|
|
1137
1334
|
diagnostics.push({
|
|
1138
1335
|
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
1139
1336
|
message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
|
|
@@ -1142,8 +1339,8 @@ function collectResolvedFields(input) {
|
|
|
1142
1339
|
});
|
|
1143
1340
|
continue;
|
|
1144
1341
|
}
|
|
1145
|
-
if (
|
|
1146
|
-
const generatedDescriptor = generatorDescriptorById.get(
|
|
1342
|
+
if (loweredOnCreate) {
|
|
1343
|
+
const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
|
|
1147
1344
|
if (generatedDescriptor) descriptor = generatedDescriptor;
|
|
1148
1345
|
}
|
|
1149
1346
|
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
@@ -1153,14 +1350,35 @@ function collectResolvedFields(input) {
|
|
|
1153
1350
|
sourceId,
|
|
1154
1351
|
diagnostics
|
|
1155
1352
|
});
|
|
1353
|
+
let isIdField = Boolean(idAttribute);
|
|
1354
|
+
if (idAttribute && field.optional) {
|
|
1355
|
+
diagnostics.push({
|
|
1356
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1357
|
+
message: `Field "${model.name}.${field.name}" @id cannot be optional; primary key columns must be NOT NULL`,
|
|
1358
|
+
sourceId,
|
|
1359
|
+
span: idAttribute.span
|
|
1360
|
+
});
|
|
1361
|
+
isIdField = false;
|
|
1362
|
+
}
|
|
1363
|
+
if (presetContributions && idAttribute && !presetContributions.id) {
|
|
1364
|
+
diagnostics.push({
|
|
1365
|
+
code: "PSL_PRESET_AND_ID_CONFLICT",
|
|
1366
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @id. Use a preset that contributes id semantics, or drop @id.`,
|
|
1367
|
+
sourceId,
|
|
1368
|
+
span: idAttribute.span
|
|
1369
|
+
});
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
|
|
1373
|
+
const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
|
|
1156
1374
|
resolvedFields.push({
|
|
1157
1375
|
field,
|
|
1158
1376
|
columnName: mappedColumnName,
|
|
1159
1377
|
descriptor,
|
|
1160
|
-
...ifDefined("defaultValue",
|
|
1161
|
-
...ifDefined("
|
|
1162
|
-
isId: Boolean(
|
|
1163
|
-
isUnique: Boolean(uniqueAttribute),
|
|
1378
|
+
...ifDefined("defaultValue", fieldDefaultValue),
|
|
1379
|
+
...ifDefined("executionDefaults", fieldExecutionDefaults),
|
|
1380
|
+
isId: isIdField || Boolean(presetContributions?.id),
|
|
1381
|
+
isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
|
|
1164
1382
|
...ifDefined("idName", idName),
|
|
1165
1383
|
...ifDefined("uniqueName", uniqueName),
|
|
1166
1384
|
...ifDefined("many", isListField ? true : void 0),
|
|
@@ -1418,7 +1636,8 @@ function validateNavigationListFieldAttributes(input) {
|
|
|
1418
1636
|
if (attribute.name === "relation") continue;
|
|
1419
1637
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1420
1638
|
familyId: input.familyId,
|
|
1421
|
-
targetId: input.targetId
|
|
1639
|
+
targetId: input.targetId,
|
|
1640
|
+
authoringContributions: input.authoringContributions
|
|
1422
1641
|
});
|
|
1423
1642
|
if (uncomposedNamespace) {
|
|
1424
1643
|
reportUncomposedNamespace({
|
|
@@ -1531,7 +1750,8 @@ function validateNamedTypeAttributes(input) {
|
|
|
1531
1750
|
if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
|
|
1532
1751
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1533
1752
|
familyId: input.familyId,
|
|
1534
|
-
targetId: input.targetId
|
|
1753
|
+
targetId: input.targetId,
|
|
1754
|
+
authoringContributions: input.authoringContributions
|
|
1535
1755
|
});
|
|
1536
1756
|
if (uncomposedNamespace) {
|
|
1537
1757
|
reportUncomposedNamespace({
|
|
@@ -1567,6 +1787,7 @@ function resolveNamedTypeDeclarations(input) {
|
|
|
1567
1787
|
sourceId: input.sourceId,
|
|
1568
1788
|
diagnostics: input.diagnostics,
|
|
1569
1789
|
composedExtensions: input.composedExtensions,
|
|
1790
|
+
authoringContributions: input.authoringContributions,
|
|
1570
1791
|
allowDbNativeType: false,
|
|
1571
1792
|
familyId: input.familyId,
|
|
1572
1793
|
targetId: input.targetId
|
|
@@ -1626,6 +1847,7 @@ function resolveNamedTypeDeclarations(input) {
|
|
|
1626
1847
|
sourceId: input.sourceId,
|
|
1627
1848
|
diagnostics: input.diagnostics,
|
|
1628
1849
|
composedExtensions: input.composedExtensions,
|
|
1850
|
+
authoringContributions: input.authoringContributions,
|
|
1629
1851
|
allowDbNativeType: true,
|
|
1630
1852
|
familyId: input.familyId,
|
|
1631
1853
|
targetId: input.targetId
|
|
@@ -1682,16 +1904,20 @@ function buildModelNodeFromPsl(input) {
|
|
|
1682
1904
|
sourceId,
|
|
1683
1905
|
scalarTypeDescriptors: input.scalarTypeDescriptors
|
|
1684
1906
|
});
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
if (primaryKeyColumns.length === 0 && !isVariantModel) diagnostics.push({
|
|
1690
|
-
code: "PSL_MISSING_PRIMARY_KEY",
|
|
1691
|
-
message: `Model "${model.name}" must declare at least one @id field for SQL provider`,
|
|
1907
|
+
const inlineIdFields = resolvedFields.filter((field) => field.isId);
|
|
1908
|
+
if (inlineIdFields.length > 1) diagnostics.push({
|
|
1909
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1910
|
+
message: `Model "${model.name}" cannot declare inline @id on multiple fields; use model-level @@id([...]) for composite identity`,
|
|
1692
1911
|
sourceId,
|
|
1693
1912
|
span: model.span
|
|
1694
1913
|
});
|
|
1914
|
+
const singleInlineIdField = inlineIdFields.length === 1 ? inlineIdFields[0] : void 0;
|
|
1915
|
+
let primaryKey = singleInlineIdField ? {
|
|
1916
|
+
columns: [singleInlineIdField.columnName],
|
|
1917
|
+
...ifDefined("name", singleInlineIdField.idName)
|
|
1918
|
+
} : void 0;
|
|
1919
|
+
const hasInlinePrimaryKey = primaryKey !== void 0;
|
|
1920
|
+
let blockPrimaryKeyDeclared = false;
|
|
1695
1921
|
const resultBackrelationCandidates = [];
|
|
1696
1922
|
for (const field of model.fields) {
|
|
1697
1923
|
if (!field.list || !input.modelNames.has(field.typeName)) continue;
|
|
@@ -1700,6 +1926,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1700
1926
|
field,
|
|
1701
1927
|
sourceId,
|
|
1702
1928
|
composedExtensions: input.composedExtensions,
|
|
1929
|
+
authoringContributions: input.authoringContributions,
|
|
1703
1930
|
diagnostics,
|
|
1704
1931
|
familyId: input.familyId,
|
|
1705
1932
|
targetId: input.targetId
|
|
@@ -1757,15 +1984,98 @@ function buildModelNodeFromPsl(input) {
|
|
|
1757
1984
|
for (const modelAttribute of model.attributes) {
|
|
1758
1985
|
if (modelAttribute.name === "map") continue;
|
|
1759
1986
|
if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
|
|
1987
|
+
const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
|
|
1988
|
+
if (modelAttribute.name === "id") {
|
|
1989
|
+
if (blockPrimaryKeyDeclared) {
|
|
1990
|
+
diagnostics.push({
|
|
1991
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1992
|
+
message: `Model "${model.name}" declares @@id more than once`,
|
|
1993
|
+
sourceId,
|
|
1994
|
+
span: modelAttribute.span
|
|
1995
|
+
});
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
if (hasInlinePrimaryKey) {
|
|
1999
|
+
diagnostics.push({
|
|
2000
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2001
|
+
message: `Model "${model.name}" cannot declare both field-level @id and model-level @@id`,
|
|
2002
|
+
sourceId,
|
|
2003
|
+
span: modelAttribute.span
|
|
2004
|
+
});
|
|
2005
|
+
blockPrimaryKeyDeclared = true;
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
const fieldNames = parseAttributeFieldList({
|
|
2009
|
+
attribute: modelAttribute,
|
|
2010
|
+
sourceId,
|
|
2011
|
+
diagnostics,
|
|
2012
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2013
|
+
entityLabel: attributeLabel
|
|
2014
|
+
});
|
|
2015
|
+
if (!fieldNames) continue;
|
|
2016
|
+
const duplicateFieldName = findDuplicateFieldName(fieldNames);
|
|
2017
|
+
if (duplicateFieldName !== void 0) {
|
|
2018
|
+
diagnostics.push({
|
|
2019
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2020
|
+
message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
|
|
2021
|
+
sourceId,
|
|
2022
|
+
span: modelAttribute.span
|
|
2023
|
+
});
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
const nullableFieldName = fieldNames.find((name) => model.fields.find((f) => f.name === name)?.optional === true);
|
|
2027
|
+
if (nullableFieldName !== void 0) {
|
|
2028
|
+
diagnostics.push({
|
|
2029
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2030
|
+
message: `${attributeLabel} cannot include optional field "${nullableFieldName}"; primary key columns must be NOT NULL`,
|
|
2031
|
+
sourceId,
|
|
2032
|
+
span: modelAttribute.span
|
|
2033
|
+
});
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
const columnNames = mapFieldNamesToColumns({
|
|
2037
|
+
modelName: model.name,
|
|
2038
|
+
fieldNames,
|
|
2039
|
+
mapping,
|
|
2040
|
+
sourceId,
|
|
2041
|
+
diagnostics,
|
|
2042
|
+
span: modelAttribute.span,
|
|
2043
|
+
entityLabel: attributeLabel
|
|
2044
|
+
});
|
|
2045
|
+
if (!columnNames) continue;
|
|
2046
|
+
primaryKey = {
|
|
2047
|
+
columns: columnNames,
|
|
2048
|
+
...ifDefined("name", parseConstraintMapArgument({
|
|
2049
|
+
attribute: modelAttribute,
|
|
2050
|
+
sourceId,
|
|
2051
|
+
diagnostics,
|
|
2052
|
+
entityLabel: attributeLabel,
|
|
2053
|
+
span: modelAttribute.span,
|
|
2054
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
2055
|
+
}))
|
|
2056
|
+
};
|
|
2057
|
+
blockPrimaryKeyDeclared = true;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
1760
2060
|
if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
|
|
1761
2061
|
const fieldNames = parseAttributeFieldList({
|
|
1762
2062
|
attribute: modelAttribute,
|
|
1763
2063
|
sourceId,
|
|
1764
2064
|
diagnostics,
|
|
1765
2065
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1766
|
-
|
|
2066
|
+
entityLabel: attributeLabel
|
|
1767
2067
|
});
|
|
1768
2068
|
if (!fieldNames) continue;
|
|
2069
|
+
const duplicateFieldName = findDuplicateFieldName(fieldNames);
|
|
2070
|
+
if (duplicateFieldName !== void 0) {
|
|
2071
|
+
diagnostics.push({
|
|
2072
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2073
|
+
message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
|
|
2074
|
+
sourceId,
|
|
2075
|
+
span: modelAttribute.span
|
|
2076
|
+
});
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
1769
2079
|
const columnNames = mapFieldNamesToColumns({
|
|
1770
2080
|
modelName: model.name,
|
|
1771
2081
|
fieldNames,
|
|
@@ -1773,14 +2083,14 @@ function buildModelNodeFromPsl(input) {
|
|
|
1773
2083
|
sourceId,
|
|
1774
2084
|
diagnostics,
|
|
1775
2085
|
span: modelAttribute.span,
|
|
1776
|
-
|
|
2086
|
+
entityLabel: attributeLabel
|
|
1777
2087
|
});
|
|
1778
2088
|
if (!columnNames) continue;
|
|
1779
2089
|
const constraintName = parseConstraintMapArgument({
|
|
1780
2090
|
attribute: modelAttribute,
|
|
1781
2091
|
sourceId,
|
|
1782
2092
|
diagnostics,
|
|
1783
|
-
entityLabel:
|
|
2093
|
+
entityLabel: attributeLabel,
|
|
1784
2094
|
span: modelAttribute.span,
|
|
1785
2095
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
1786
2096
|
});
|
|
@@ -1796,7 +2106,8 @@ function buildModelNodeFromPsl(input) {
|
|
|
1796
2106
|
}
|
|
1797
2107
|
const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
|
|
1798
2108
|
familyId: input.familyId,
|
|
1799
|
-
targetId: input.targetId
|
|
2109
|
+
targetId: input.targetId,
|
|
2110
|
+
authoringContributions: input.authoringContributions
|
|
1800
2111
|
});
|
|
1801
2112
|
if (uncomposedNamespace) {
|
|
1802
2113
|
reportUncomposedNamespace({
|
|
@@ -1861,7 +2172,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1861
2172
|
sourceId,
|
|
1862
2173
|
diagnostics,
|
|
1863
2174
|
span: relationAttribute.relation.span,
|
|
1864
|
-
|
|
2175
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1865
2176
|
});
|
|
1866
2177
|
if (!localColumns) continue;
|
|
1867
2178
|
const referencedColumns = mapFieldNamesToColumns({
|
|
@@ -1871,7 +2182,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1871
2182
|
sourceId,
|
|
1872
2183
|
diagnostics,
|
|
1873
2184
|
span: relationAttribute.relation.span,
|
|
1874
|
-
|
|
2185
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1875
2186
|
});
|
|
1876
2187
|
if (!referencedColumns) continue;
|
|
1877
2188
|
if (localColumns.length !== referencedColumns.length) {
|
|
@@ -1933,12 +2244,9 @@ function buildModelNodeFromPsl(input) {
|
|
|
1933
2244
|
descriptor: resolvedField.descriptor,
|
|
1934
2245
|
nullable: resolvedField.field.optional,
|
|
1935
2246
|
...ifDefined("default", resolvedField.defaultValue),
|
|
1936
|
-
...ifDefined("
|
|
2247
|
+
...ifDefined("executionDefaults", resolvedField.executionDefaults)
|
|
1937
2248
|
})),
|
|
1938
|
-
...
|
|
1939
|
-
columns: primaryKeyColumns,
|
|
1940
|
-
...ifDefined("name", primaryKeyName)
|
|
1941
|
-
} } : {},
|
|
2249
|
+
...ifDefined("id", primaryKey),
|
|
1942
2250
|
...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
|
|
1943
2251
|
...indexNodes.length > 0 ? { indexes: indexNodes } : {},
|
|
1944
2252
|
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
|
|
@@ -2340,4 +2648,4 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2340
2648
|
|
|
2341
2649
|
//#endregion
|
|
2342
2650
|
export { interpretPslDocumentToSqlContract as t };
|
|
2343
|
-
//# sourceMappingURL=interpreter-
|
|
2651
|
+
//# sourceMappingURL=interpreter-BChe-9vN.mjs.map
|