@prisma-next/sql-contract-psl 0.5.0-dev.7 → 0.5.0-dev.71

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.
@@ -1,10 +1,9 @@
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";
5
5
  import { getPositionalArgument, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
6
6
  import { assertDefined, invariant } from "@prisma-next/utils/assertions";
7
-
8
7
  //#region src/psl-attribute-parsing.ts
9
8
  function lowerFirst(value) {
10
9
  if (value.length === 0) return value;
@@ -149,7 +148,7 @@ function parseAttributeFieldList(input) {
149
148
  if (!raw) {
150
149
  input.diagnostics.push({
151
150
  code: input.code,
152
- message: `${input.messagePrefix} requires fields list argument`,
151
+ message: `${input.entityLabel} requires fields list argument`,
153
152
  sourceId: input.sourceId,
154
153
  span: input.attribute.span
155
154
  });
@@ -159,7 +158,7 @@ function parseAttributeFieldList(input) {
159
158
  if (!fields || fields.length === 0) {
160
159
  input.diagnostics.push({
161
160
  code: input.code,
162
- message: `${input.messagePrefix} requires bracketed field list argument`,
161
+ message: `${input.entityLabel} requires bracketed field list argument`,
163
162
  sourceId: input.sourceId,
164
163
  span: input.attribute.span
165
164
  });
@@ -167,6 +166,13 @@ function parseAttributeFieldList(input) {
167
166
  }
168
167
  return fields;
169
168
  }
169
+ function findDuplicateFieldName(fieldNames) {
170
+ const seen = /* @__PURE__ */ new Set();
171
+ for (const name of fieldNames) {
172
+ if (seen.has(name)) return name;
173
+ seen.add(name);
174
+ }
175
+ }
170
176
  function mapFieldNamesToColumns(input) {
171
177
  const columns = [];
172
178
  for (const fieldName of input.fieldNames) {
@@ -174,7 +180,7 @@ function mapFieldNamesToColumns(input) {
174
180
  if (!columnName) {
175
181
  input.diagnostics.push({
176
182
  code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
177
- message: `${input.contextLabel} references unknown field "${input.modelName}.${fieldName}"`,
183
+ message: `${input.entityLabel} references unknown field "${input.modelName}.${fieldName}"`,
178
184
  sourceId: input.sourceId,
179
185
  span: input.span
180
186
  });
@@ -184,7 +190,6 @@ function mapFieldNamesToColumns(input) {
184
190
  }
185
191
  return columns;
186
192
  }
187
-
188
193
  //#endregion
189
194
  //#region src/default-function-registry.ts
190
195
  function resolveSpanPositionFromBase(base, text, offset) {
@@ -323,7 +328,6 @@ function lowerDefaultFunctionWithRegistry(input) {
323
328
  }
324
329
  };
325
330
  }
326
-
327
331
  //#endregion
328
332
  //#region src/psl-authoring-arguments.ts
329
333
  const INVALID_AUTHORING_ARGUMENT = Symbol("invalidAuthoringArgument");
@@ -401,10 +405,10 @@ function parseJsLikeLiteral(value) {
401
405
  function parseNumber() {
402
406
  const raw = value.slice(index).match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0];
403
407
  if (!raw) return INVALID_AUTHORING_ARGUMENT;
404
- const parsed$1 = Number(raw);
405
- if (!Number.isFinite(parsed$1)) return INVALID_AUTHORING_ARGUMENT;
408
+ const parsed = Number(raw);
409
+ if (!Number.isFinite(parsed)) return INVALID_AUTHORING_ARGUMENT;
406
410
  index += raw.length;
407
- return parsed$1;
411
+ return parsed;
408
412
  }
409
413
  function parseArray() {
410
414
  if (value[index] !== "[") return INVALID_AUTHORING_ARGUMENT;
@@ -562,7 +566,7 @@ function mapPslHelperArgs(input) {
562
566
  mappedArgs[index] = value;
563
567
  }
564
568
  for (const argument of namedArgs) {
565
- const descriptorIndex = input.descriptors.findIndex((descriptor$1) => descriptor$1.name === argument.name);
569
+ const descriptorIndex = input.descriptors.findIndex((descriptor) => descriptor.name === argument.name);
566
570
  if (descriptorIndex < 0) return pushInvalidPslHelperArgument({
567
571
  diagnostics: input.diagnostics,
568
572
  sourceId: input.sourceId,
@@ -601,7 +605,6 @@ function mapPslHelperArgs(input) {
601
605
  }
602
606
  return mappedArgs;
603
607
  }
604
-
605
608
  //#endregion
606
609
  //#region src/psl-column-resolution.ts
607
610
  function toNamedTypeFieldDescriptor(typeRef, descriptor) {
@@ -620,33 +623,40 @@ function getAuthoringTypeConstructor(contributions, path) {
620
623
  return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
621
624
  }
622
625
  /**
623
- * Returns the namespace prefix of `attributeName` if it references an
624
- * unrecognized extension namespace, otherwise `undefined`. A namespace is
625
- * considered recognized when it is:
626
+ * Walks `authoringContributions.field` segment-by-segment and returns the field-preset descriptor at the resolved path, or `undefined` if no descriptor is registered.
627
+ *
628
+ * Symmetric with `getAuthoringTypeConstructor`. Field presets are strictly richer than type constructors — they can contribute `default` / `executionDefaults` / `id` / `unique` / `nullable` in addition to the `codecId` / `nativeType` / `typeParams` triple. PSL resolution tries field presets first, then falls back to type constructors on miss (see `resolveFieldTypeDescriptor`).
629
+ */
630
+ function getAuthoringFieldPreset(contributions, path) {
631
+ let current = contributions?.field;
632
+ for (const segment of path) {
633
+ if (typeof current !== "object" || current === null || Array.isArray(current)) return;
634
+ current = current[segment];
635
+ }
636
+ return isAuthoringFieldPresetDescriptor(current) ? current : void 0;
637
+ }
638
+ /**
639
+ * Returns the namespace prefix of `attributeName` if it references an unrecognized extension namespace, otherwise `undefined`. A namespace is considered recognized when it is:
626
640
  *
627
641
  * - `db` (native-type spec, always allowed),
628
642
  * - the active family id (e.g. `sql`),
629
643
  * - the active target id (e.g. `postgres`),
644
+ * - a registered field-preset namespace (e.g. `temporal`),
630
645
  * - present in `composedExtensions`.
631
646
  *
632
- * Family/target namespaces are exempted so that e.g. `@sql.foo` surfaces as
633
- * PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
634
- * PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
647
+ * Family/target/field-preset namespaces are exempted so that e.g. `@sql.foo` surfaces as PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
635
648
  */
636
649
  function checkUncomposedNamespace(attributeName, composedExtensions, context) {
637
650
  const dotIndex = attributeName.indexOf(".");
638
651
  if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
639
652
  const namespace = attributeName.slice(0, dotIndex);
640
- if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
653
+ if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
641
654
  return namespace;
642
655
  }
643
656
  /**
644
- * Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a
645
- * subject (attribute, model attribute, or type constructor) that references an
646
- * extension namespace which is not composed in the current contract.
657
+ * Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a subject (attribute, model attribute, or type constructor) that references an extension namespace which is not composed in the current contract.
647
658
  *
648
- * The `data` payload carries the missing namespace so machine consumers
649
- * (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
659
+ * The `data` payload carries the missing namespace so machine consumers (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
650
660
  */
651
661
  function reportUncomposedNamespace(input) {
652
662
  input.diagnostics.push({
@@ -660,6 +670,21 @@ function reportUncomposedNamespace(input) {
660
670
  }
661
671
  });
662
672
  }
673
+ /**
674
+ * Pushes the canonical `PSL_UNKNOWN_FIELD_PRESET` diagnostic when a typoed preset name is referenced inside a registered field-preset namespace. The `data` payload exposes the namespace and full helper path so machine consumers (agents, IDE extensions) don't have to parse the prose.
675
+ */
676
+ function reportUnknownFieldPreset(input) {
677
+ input.diagnostics.push({
678
+ code: "PSL_UNKNOWN_FIELD_PRESET",
679
+ message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
680
+ sourceId: input.sourceId,
681
+ span: input.span,
682
+ data: {
683
+ namespace: input.namespace,
684
+ helperPath: input.helperPath
685
+ }
686
+ });
687
+ }
663
688
  function instantiatePslTypeConstructor(input) {
664
689
  const helperPath = input.call.path.join(".");
665
690
  const args = mapPslHelperArgs({
@@ -697,11 +722,15 @@ function pushUnsupportedTypeConstructorDiagnostic(input) {
697
722
  function resolvePslTypeConstructorDescriptor(input) {
698
723
  const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
699
724
  if (descriptor) return descriptor;
700
- const namespace = input.call.path.length > 1 ? input.call.path[0] : void 0;
701
- if (namespace && namespace !== "db" && namespace !== input.familyId && namespace !== input.targetId && !input.composedExtensions.has(namespace)) {
725
+ const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
726
+ familyId: input.familyId,
727
+ targetId: input.targetId,
728
+ authoringContributions: input.authoringContributions
729
+ });
730
+ if (uncomposedNamespace) {
702
731
  reportUncomposedNamespace({
703
732
  subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
704
- namespace,
733
+ namespace: uncomposedNamespace,
705
734
  sourceId: input.sourceId,
706
735
  span: input.call.span,
707
736
  diagnostics: input.diagnostics
@@ -716,10 +745,95 @@ function resolvePslTypeConstructorDescriptor(input) {
716
745
  message: input.unsupportedMessage
717
746
  });
718
747
  }
748
+ /**
749
+ * Instantiates a field-preset call against its descriptor, coercing PSL AST arguments into the descriptor's typed argument shape and returning the preset's full set of contract contributions.
750
+ *
751
+ * 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.
752
+ */
753
+ function instantiatePslFieldPreset(input) {
754
+ const helperPath = input.call.path.join(".");
755
+ const args = mapPslHelperArgs({
756
+ args: input.call.args,
757
+ descriptors: input.descriptor.args ?? [],
758
+ helperLabel: `preset "${helperPath}"`,
759
+ span: input.call.span,
760
+ diagnostics: input.diagnostics,
761
+ sourceId: input.sourceId,
762
+ entityLabel: input.entityLabel
763
+ });
764
+ if (!args) return;
765
+ try {
766
+ validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
767
+ const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
768
+ return {
769
+ descriptor: {
770
+ codecId: instantiated.descriptor.codecId,
771
+ nativeType: instantiated.descriptor.nativeType,
772
+ ...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
773
+ },
774
+ nullable: instantiated.nullable,
775
+ ...instantiated.default !== void 0 ? { default: instantiated.default } : {},
776
+ ...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
777
+ id: instantiated.id,
778
+ unique: instantiated.unique
779
+ };
780
+ } catch (error) {
781
+ const message = error instanceof Error ? error.message : String(error);
782
+ input.diagnostics.push({
783
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
784
+ message: `${input.entityLabel} preset "${helperPath}" ${message}`,
785
+ sourceId: input.sourceId,
786
+ span: input.call.span
787
+ });
788
+ return;
789
+ }
790
+ }
719
791
  function resolveFieldTypeDescriptor(input) {
720
792
  if (input.field.typeConstructor) {
793
+ const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
794
+ if (presetDescriptor) {
795
+ const instantiated = instantiatePslFieldPreset({
796
+ call: input.field.typeConstructor,
797
+ descriptor: presetDescriptor,
798
+ diagnostics: input.diagnostics,
799
+ sourceId: input.sourceId,
800
+ entityLabel: input.entityLabel
801
+ });
802
+ if (!instantiated) return {
803
+ ok: false,
804
+ alreadyReported: true
805
+ };
806
+ const presetContributions = {
807
+ nullable: instantiated.nullable,
808
+ id: instantiated.id,
809
+ unique: instantiated.unique,
810
+ ...instantiated.default !== void 0 ? { default: instantiated.default } : {},
811
+ ...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {}
812
+ };
813
+ return {
814
+ ok: true,
815
+ descriptor: instantiated.descriptor,
816
+ presetContributions
817
+ };
818
+ }
721
819
  const helperPath = input.field.typeConstructor.path.join(".");
722
- const descriptor$1 = resolvePslTypeConstructorDescriptor({
820
+ const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
821
+ const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
822
+ if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
823
+ reportUnknownFieldPreset({
824
+ entityLabel: input.entityLabel,
825
+ namespace: namespacePrefix,
826
+ helperPath,
827
+ sourceId: input.sourceId,
828
+ span: input.field.typeConstructor.span,
829
+ diagnostics: input.diagnostics
830
+ });
831
+ return {
832
+ ok: false,
833
+ alreadyReported: true
834
+ };
835
+ }
836
+ const descriptor = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
723
837
  call: input.field.typeConstructor,
724
838
  authoringContributions: input.authoringContributions,
725
839
  composedExtensions: input.composedExtensions,
@@ -730,13 +844,13 @@ function resolveFieldTypeDescriptor(input) {
730
844
  unsupportedCode: "PSL_UNSUPPORTED_FIELD_TYPE",
731
845
  unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`
732
846
  });
733
- if (!descriptor$1) return {
847
+ if (!descriptor) return {
734
848
  ok: false,
735
849
  alreadyReported: true
736
850
  };
737
851
  const instantiated = instantiatePslTypeConstructor({
738
852
  call: input.field.typeConstructor,
739
- descriptor: descriptor$1,
853
+ descriptor,
740
854
  diagnostics: input.diagnostics,
741
855
  sourceId: input.sourceId,
742
856
  entityLabel: input.entityLabel
@@ -985,6 +1099,15 @@ function lowerDefaultForField(input) {
985
1099
  });
986
1100
  return {};
987
1101
  }
1102
+ if (generatorDescriptor.applicableCodecIds === void 0) {
1103
+ input.diagnostics.push({
1104
+ code: "PSL_INVALID_DEFAULT_APPLICABILITY",
1105
+ 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.`,
1106
+ sourceId: input.sourceId,
1107
+ span: expressionEntry.span
1108
+ });
1109
+ return {};
1110
+ }
988
1111
  if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
989
1112
  input.diagnostics.push({
990
1113
  code: "PSL_INVALID_DEFAULT_APPLICABILITY",
@@ -994,7 +1117,7 @@ function lowerDefaultForField(input) {
994
1117
  });
995
1118
  return {};
996
1119
  }
997
- return { executionDefault: lowered.value.generated };
1120
+ return { executionDefaults: { onCreate: lowered.value.generated } };
998
1121
  }
999
1122
  function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
1000
1123
  if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
@@ -1002,7 +1125,6 @@ function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptor
1002
1125
  if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
1003
1126
  return scalarTypeDescriptors.get(field.typeName);
1004
1127
  }
1005
-
1006
1128
  //#endregion
1007
1129
  //#region src/psl-field-resolution.ts
1008
1130
  const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
@@ -1012,12 +1134,21 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
1012
1134
  "relation",
1013
1135
  "map"
1014
1136
  ]);
1137
+ const REMOVED_ATTRIBUTE_RULES = new Map([["updatedAt", {
1138
+ hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
1139
+ suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
1140
+ }]]);
1141
+ {
1142
+ const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
1143
+ if (overlap.length > 0) throw new Error(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
1144
+ }
1015
1145
  function validateFieldAttributes(input) {
1016
1146
  for (const attribute of input.field.attributes) {
1017
1147
  if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
1018
1148
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1019
1149
  familyId: input.familyId,
1020
- targetId: input.targetId
1150
+ targetId: input.targetId,
1151
+ authoringContributions: input.authoringContributions
1021
1152
  });
1022
1153
  if (uncomposedNamespace) {
1023
1154
  reportUncomposedNamespace({
@@ -1029,9 +1160,12 @@ function validateFieldAttributes(input) {
1029
1160
  });
1030
1161
  continue;
1031
1162
  }
1163
+ const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
1164
+ const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
1165
+ const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
1032
1166
  input.diagnostics.push({
1033
1167
  code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
1034
- message: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
1168
+ message,
1035
1169
  sourceId: input.sourceId,
1036
1170
  span: attribute.span
1037
1171
  });
@@ -1065,21 +1199,25 @@ function collectResolvedFields(input) {
1065
1199
  const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
1066
1200
  const resolvedFields = [];
1067
1201
  for (const field of model.fields) {
1068
- if (field.list && modelNames.has(field.typeName)) continue;
1202
+ const isModelField = modelNames.has(field.typeName);
1203
+ if (field.list && isModelField) continue;
1069
1204
  validateFieldAttributes({
1070
1205
  model,
1071
1206
  field,
1072
1207
  composedExtensions,
1208
+ authoringContributions,
1073
1209
  diagnostics,
1074
1210
  sourceId,
1075
1211
  familyId,
1076
1212
  targetId
1077
1213
  });
1078
- if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
1214
+ const relationAttribute = getAttribute(field.attributes, "relation");
1215
+ if (isModelField && relationAttribute) continue;
1079
1216
  const isValueObjectField = compositeTypeNames.has(field.typeName);
1080
1217
  const isListField = field.list;
1081
1218
  let descriptor;
1082
1219
  let scalarCodecId;
1220
+ let presetContributions;
1083
1221
  const resolveInput = {
1084
1222
  field,
1085
1223
  enumTypeDescriptors,
@@ -1105,6 +1243,15 @@ function collectResolvedFields(input) {
1105
1243
  });
1106
1244
  continue;
1107
1245
  }
1246
+ if (resolved.presetContributions) {
1247
+ diagnostics.push({
1248
+ code: "PSL_PRESET_NOT_LIST",
1249
+ 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.`,
1250
+ sourceId,
1251
+ span: field.span
1252
+ });
1253
+ continue;
1254
+ }
1108
1255
  scalarCodecId = resolved.descriptor.codecId;
1109
1256
  descriptor = scalarTypeDescriptors.get("Json");
1110
1257
  } else {
@@ -1119,9 +1266,28 @@ function collectResolvedFields(input) {
1119
1266
  continue;
1120
1267
  }
1121
1268
  descriptor = resolved.descriptor;
1269
+ presetContributions = resolved.presetContributions;
1122
1270
  }
1123
1271
  if (!descriptor) continue;
1272
+ if (presetContributions && field.optional) {
1273
+ diagnostics.push({
1274
+ code: "PSL_PRESET_NOT_OPTIONAL",
1275
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
1276
+ sourceId,
1277
+ span: field.span
1278
+ });
1279
+ continue;
1280
+ }
1124
1281
  const defaultAttribute = getAttribute(field.attributes, "default");
1282
+ if (presetContributions && defaultAttribute) {
1283
+ diagnostics.push({
1284
+ code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
1285
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
1286
+ sourceId,
1287
+ span: defaultAttribute.span
1288
+ });
1289
+ continue;
1290
+ }
1125
1291
  const loweredDefault = defaultAttribute ? lowerDefaultForField({
1126
1292
  modelName: model.name,
1127
1293
  fieldName: field.name,
@@ -1132,8 +1298,9 @@ function collectResolvedFields(input) {
1132
1298
  defaultFunctionRegistry,
1133
1299
  diagnostics
1134
1300
  }) : {};
1135
- if (field.optional && loweredDefault.executionDefault) {
1136
- const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
1301
+ const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
1302
+ if (field.optional && loweredOnCreate) {
1303
+ const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
1137
1304
  diagnostics.push({
1138
1305
  code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
1139
1306
  message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
@@ -1142,8 +1309,8 @@ function collectResolvedFields(input) {
1142
1309
  });
1143
1310
  continue;
1144
1311
  }
1145
- if (loweredDefault.executionDefault) {
1146
- const generatedDescriptor = generatorDescriptorById.get(loweredDefault.executionDefault.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredDefault.executionDefault });
1312
+ if (loweredOnCreate) {
1313
+ const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
1147
1314
  if (generatedDescriptor) descriptor = generatedDescriptor;
1148
1315
  }
1149
1316
  const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
@@ -1153,14 +1320,35 @@ function collectResolvedFields(input) {
1153
1320
  sourceId,
1154
1321
  diagnostics
1155
1322
  });
1323
+ let isIdField = Boolean(idAttribute);
1324
+ if (idAttribute && field.optional) {
1325
+ diagnostics.push({
1326
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1327
+ message: `Field "${model.name}.${field.name}" @id cannot be optional; primary key columns must be NOT NULL`,
1328
+ sourceId,
1329
+ span: idAttribute.span
1330
+ });
1331
+ isIdField = false;
1332
+ }
1333
+ if (presetContributions && idAttribute && !presetContributions.id) {
1334
+ diagnostics.push({
1335
+ code: "PSL_PRESET_AND_ID_CONFLICT",
1336
+ 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.`,
1337
+ sourceId,
1338
+ span: idAttribute.span
1339
+ });
1340
+ continue;
1341
+ }
1342
+ const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
1343
+ const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
1156
1344
  resolvedFields.push({
1157
1345
  field,
1158
1346
  columnName: mappedColumnName,
1159
1347
  descriptor,
1160
- ...ifDefined("defaultValue", loweredDefault.defaultValue),
1161
- ...ifDefined("executionDefault", loweredDefault.executionDefault),
1162
- isId: Boolean(idAttribute),
1163
- isUnique: Boolean(uniqueAttribute),
1348
+ ...ifDefined("defaultValue", fieldDefaultValue),
1349
+ ...ifDefined("executionDefaults", fieldExecutionDefaults),
1350
+ isId: isIdField || Boolean(presetContributions?.id),
1351
+ isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
1164
1352
  ...ifDefined("idName", idName),
1165
1353
  ...ifDefined("uniqueName", uniqueName),
1166
1354
  ...ifDefined("many", isListField ? true : void 0),
@@ -1201,7 +1389,6 @@ function buildModelMappings(models, diagnostics, sourceId) {
1201
1389
  }
1202
1390
  return result;
1203
1391
  }
1204
-
1205
1392
  //#endregion
1206
1393
  //#region src/psl-relation-resolution.ts
1207
1394
  const REFERENTIAL_ACTION_MAP = {
@@ -1418,7 +1605,8 @@ function validateNavigationListFieldAttributes(input) {
1418
1605
  if (attribute.name === "relation") continue;
1419
1606
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1420
1607
  familyId: input.familyId,
1421
- targetId: input.targetId
1608
+ targetId: input.targetId,
1609
+ authoringContributions: input.authoringContributions
1422
1610
  });
1423
1611
  if (uncomposedNamespace) {
1424
1612
  reportUncomposedNamespace({
@@ -1441,7 +1629,6 @@ function validateNavigationListFieldAttributes(input) {
1441
1629
  }
1442
1630
  return valid;
1443
1631
  }
1444
-
1445
1632
  //#endregion
1446
1633
  //#region src/interpreter.ts
1447
1634
  function buildComposedExtensionPackRefs(target, extensionIds, extensionPackRefs = []) {
@@ -1531,7 +1718,8 @@ function validateNamedTypeAttributes(input) {
1531
1718
  if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
1532
1719
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1533
1720
  familyId: input.familyId,
1534
- targetId: input.targetId
1721
+ targetId: input.targetId,
1722
+ authoringContributions: input.authoringContributions
1535
1723
  });
1536
1724
  if (uncomposedNamespace) {
1537
1725
  reportUncomposedNamespace({
@@ -1562,16 +1750,17 @@ function resolveNamedTypeDeclarations(input) {
1562
1750
  const namedTypeDescriptors = /* @__PURE__ */ new Map();
1563
1751
  for (const declaration of input.declarations) {
1564
1752
  if (declaration.typeConstructor) {
1565
- const { hasUnsupportedNamedTypeAttribute: hasUnsupportedNamedTypeAttribute$1 } = validateNamedTypeAttributes({
1753
+ const { hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
1566
1754
  declaration,
1567
1755
  sourceId: input.sourceId,
1568
1756
  diagnostics: input.diagnostics,
1569
1757
  composedExtensions: input.composedExtensions,
1758
+ authoringContributions: input.authoringContributions,
1570
1759
  allowDbNativeType: false,
1571
1760
  familyId: input.familyId,
1572
1761
  targetId: input.targetId
1573
1762
  });
1574
- if (hasUnsupportedNamedTypeAttribute$1) continue;
1763
+ if (hasUnsupportedNamedTypeAttribute) continue;
1575
1764
  const helperPath = declaration.typeConstructor.path.join(".");
1576
1765
  const typeConstructor = resolvePslTypeConstructorDescriptor({
1577
1766
  call: declaration.typeConstructor,
@@ -1626,13 +1815,14 @@ function resolveNamedTypeDeclarations(input) {
1626
1815
  sourceId: input.sourceId,
1627
1816
  diagnostics: input.diagnostics,
1628
1817
  composedExtensions: input.composedExtensions,
1818
+ authoringContributions: input.authoringContributions,
1629
1819
  allowDbNativeType: true,
1630
1820
  familyId: input.familyId,
1631
1821
  targetId: input.targetId
1632
1822
  });
1633
1823
  if (hasUnsupportedNamedTypeAttribute) continue;
1634
1824
  if (dbNativeTypeAttribute) {
1635
- const descriptor$1 = resolveDbNativeTypeAttribute({
1825
+ const descriptor = resolveDbNativeTypeAttribute({
1636
1826
  attribute: dbNativeTypeAttribute,
1637
1827
  baseType,
1638
1828
  baseDescriptor,
@@ -1640,12 +1830,12 @@ function resolveNamedTypeDeclarations(input) {
1640
1830
  sourceId: input.sourceId,
1641
1831
  entityLabel: `Named type "${declaration.name}"`
1642
1832
  });
1643
- if (!descriptor$1) continue;
1644
- namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor$1));
1833
+ if (!descriptor) continue;
1834
+ namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor));
1645
1835
  storageTypes[declaration.name] = {
1646
- codecId: descriptor$1.codecId,
1647
- nativeType: descriptor$1.nativeType,
1648
- typeParams: descriptor$1.typeParams ?? {}
1836
+ codecId: descriptor.codecId,
1837
+ nativeType: descriptor.nativeType,
1838
+ typeParams: descriptor.typeParams ?? {}
1649
1839
  };
1650
1840
  continue;
1651
1841
  }
@@ -1682,16 +1872,20 @@ function buildModelNodeFromPsl(input) {
1682
1872
  sourceId,
1683
1873
  scalarTypeDescriptors: input.scalarTypeDescriptors
1684
1874
  });
1685
- const primaryKeyFields = resolvedFields.filter((field) => field.isId);
1686
- const primaryKeyColumns = primaryKeyFields.map((field) => field.columnName);
1687
- const primaryKeyName = primaryKeyFields.length === 1 ? primaryKeyFields[0]?.idName : void 0;
1688
- const isVariantModel = model.attributes.some((attr) => attr.name === "base");
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`,
1875
+ const inlineIdFields = resolvedFields.filter((field) => field.isId);
1876
+ if (inlineIdFields.length > 1) diagnostics.push({
1877
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1878
+ message: `Model "${model.name}" cannot declare inline @id on multiple fields; use model-level @@id([...]) for composite identity`,
1692
1879
  sourceId,
1693
1880
  span: model.span
1694
1881
  });
1882
+ const singleInlineIdField = inlineIdFields.length === 1 ? inlineIdFields[0] : void 0;
1883
+ let primaryKey = singleInlineIdField ? {
1884
+ columns: [singleInlineIdField.columnName],
1885
+ ...ifDefined("name", singleInlineIdField.idName)
1886
+ } : void 0;
1887
+ const hasInlinePrimaryKey = primaryKey !== void 0;
1888
+ let blockPrimaryKeyDeclared = false;
1695
1889
  const resultBackrelationCandidates = [];
1696
1890
  for (const field of model.fields) {
1697
1891
  if (!field.list || !input.modelNames.has(field.typeName)) continue;
@@ -1700,6 +1894,7 @@ function buildModelNodeFromPsl(input) {
1700
1894
  field,
1701
1895
  sourceId,
1702
1896
  composedExtensions: input.composedExtensions,
1897
+ authoringContributions: input.authoringContributions,
1703
1898
  diagnostics,
1704
1899
  familyId: input.familyId,
1705
1900
  targetId: input.targetId
@@ -1757,15 +1952,98 @@ function buildModelNodeFromPsl(input) {
1757
1952
  for (const modelAttribute of model.attributes) {
1758
1953
  if (modelAttribute.name === "map") continue;
1759
1954
  if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
1955
+ const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
1956
+ if (modelAttribute.name === "id") {
1957
+ if (blockPrimaryKeyDeclared) {
1958
+ diagnostics.push({
1959
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1960
+ message: `Model "${model.name}" declares @@id more than once`,
1961
+ sourceId,
1962
+ span: modelAttribute.span
1963
+ });
1964
+ continue;
1965
+ }
1966
+ if (hasInlinePrimaryKey) {
1967
+ diagnostics.push({
1968
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1969
+ message: `Model "${model.name}" cannot declare both field-level @id and model-level @@id`,
1970
+ sourceId,
1971
+ span: modelAttribute.span
1972
+ });
1973
+ blockPrimaryKeyDeclared = true;
1974
+ continue;
1975
+ }
1976
+ const fieldNames = parseAttributeFieldList({
1977
+ attribute: modelAttribute,
1978
+ sourceId,
1979
+ diagnostics,
1980
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1981
+ entityLabel: attributeLabel
1982
+ });
1983
+ if (!fieldNames) continue;
1984
+ const duplicateFieldName = findDuplicateFieldName(fieldNames);
1985
+ if (duplicateFieldName !== void 0) {
1986
+ diagnostics.push({
1987
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1988
+ message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
1989
+ sourceId,
1990
+ span: modelAttribute.span
1991
+ });
1992
+ continue;
1993
+ }
1994
+ const nullableFieldName = fieldNames.find((name) => model.fields.find((f) => f.name === name)?.optional === true);
1995
+ if (nullableFieldName !== void 0) {
1996
+ diagnostics.push({
1997
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1998
+ message: `${attributeLabel} cannot include optional field "${nullableFieldName}"; primary key columns must be NOT NULL`,
1999
+ sourceId,
2000
+ span: modelAttribute.span
2001
+ });
2002
+ continue;
2003
+ }
2004
+ const columnNames = mapFieldNamesToColumns({
2005
+ modelName: model.name,
2006
+ fieldNames,
2007
+ mapping,
2008
+ sourceId,
2009
+ diagnostics,
2010
+ span: modelAttribute.span,
2011
+ entityLabel: attributeLabel
2012
+ });
2013
+ if (!columnNames) continue;
2014
+ primaryKey = {
2015
+ columns: columnNames,
2016
+ ...ifDefined("name", parseConstraintMapArgument({
2017
+ attribute: modelAttribute,
2018
+ sourceId,
2019
+ diagnostics,
2020
+ entityLabel: attributeLabel,
2021
+ span: modelAttribute.span,
2022
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
2023
+ }))
2024
+ };
2025
+ blockPrimaryKeyDeclared = true;
2026
+ continue;
2027
+ }
1760
2028
  if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
1761
2029
  const fieldNames = parseAttributeFieldList({
1762
2030
  attribute: modelAttribute,
1763
2031
  sourceId,
1764
2032
  diagnostics,
1765
2033
  code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1766
- messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
2034
+ entityLabel: attributeLabel
1767
2035
  });
1768
2036
  if (!fieldNames) continue;
2037
+ const duplicateFieldName = findDuplicateFieldName(fieldNames);
2038
+ if (duplicateFieldName !== void 0) {
2039
+ diagnostics.push({
2040
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
2041
+ message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
2042
+ sourceId,
2043
+ span: modelAttribute.span
2044
+ });
2045
+ continue;
2046
+ }
1769
2047
  const columnNames = mapFieldNamesToColumns({
1770
2048
  modelName: model.name,
1771
2049
  fieldNames,
@@ -1773,14 +2051,14 @@ function buildModelNodeFromPsl(input) {
1773
2051
  sourceId,
1774
2052
  diagnostics,
1775
2053
  span: modelAttribute.span,
1776
- contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
2054
+ entityLabel: attributeLabel
1777
2055
  });
1778
2056
  if (!columnNames) continue;
1779
2057
  const constraintName = parseConstraintMapArgument({
1780
2058
  attribute: modelAttribute,
1781
2059
  sourceId,
1782
2060
  diagnostics,
1783
- entityLabel: `Model "${model.name}" @@${modelAttribute.name}`,
2061
+ entityLabel: attributeLabel,
1784
2062
  span: modelAttribute.span,
1785
2063
  code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
1786
2064
  });
@@ -1796,7 +2074,8 @@ function buildModelNodeFromPsl(input) {
1796
2074
  }
1797
2075
  const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
1798
2076
  familyId: input.familyId,
1799
- targetId: input.targetId
2077
+ targetId: input.targetId,
2078
+ authoringContributions: input.authoringContributions
1800
2079
  });
1801
2080
  if (uncomposedNamespace) {
1802
2081
  reportUncomposedNamespace({
@@ -1861,7 +2140,7 @@ function buildModelNodeFromPsl(input) {
1861
2140
  sourceId,
1862
2141
  diagnostics,
1863
2142
  span: relationAttribute.relation.span,
1864
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
2143
+ entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
1865
2144
  });
1866
2145
  if (!localColumns) continue;
1867
2146
  const referencedColumns = mapFieldNamesToColumns({
@@ -1871,7 +2150,7 @@ function buildModelNodeFromPsl(input) {
1871
2150
  sourceId,
1872
2151
  diagnostics,
1873
2152
  span: relationAttribute.relation.span,
1874
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
2153
+ entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
1875
2154
  });
1876
2155
  if (!referencedColumns) continue;
1877
2156
  if (localColumns.length !== referencedColumns.length) {
@@ -1933,12 +2212,9 @@ function buildModelNodeFromPsl(input) {
1933
2212
  descriptor: resolvedField.descriptor,
1934
2213
  nullable: resolvedField.field.optional,
1935
2214
  ...ifDefined("default", resolvedField.defaultValue),
1936
- ...ifDefined("executionDefault", resolvedField.executionDefault)
2215
+ ...ifDefined("executionDefaults", resolvedField.executionDefaults)
1937
2216
  })),
1938
- ...primaryKeyColumns.length > 0 ? { id: {
1939
- columns: primaryKeyColumns,
1940
- ...ifDefined("name", primaryKeyName)
1941
- } } : {},
2217
+ ...ifDefined("id", primaryKey),
1942
2218
  ...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
1943
2219
  ...indexNodes.length > 0 ? { indexes: indexNodes } : {},
1944
2220
  ...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
@@ -2337,7 +2613,7 @@ function interpretPslDocumentToSqlContract(input) {
2337
2613
  ...Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
2338
2614
  });
2339
2615
  }
2340
-
2341
2616
  //#endregion
2342
2617
  export { interpretPslDocumentToSqlContract as t };
2343
- //# sourceMappingURL=interpreter-iFCRN9nb.mjs.map
2618
+
2619
+ //# sourceMappingURL=interpreter-C9EP3HJr.mjs.map