@prisma-next/sql-contract-psl 0.5.0-dev.6 → 0.5.0-dev.61

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,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.messagePrefix} requires fields list argument`,
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.messagePrefix} requires bracketed field list argument`,
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.contextLabel} references unknown field "${input.modelName}.${fieldName}"`,
184
+ message: `${input.entityLabel} references unknown field "${input.modelName}.${fieldName}"`,
178
185
  sourceId: input.sourceId,
179
186
  span: input.span
180
187
  });
@@ -620,33 +627,40 @@ function getAuthoringTypeConstructor(contributions, path) {
620
627
  return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
621
628
  }
622
629
  /**
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:
630
+ * Walks `authoringContributions.field` segment-by-segment and returns the field-preset descriptor at the resolved path, or `undefined` if no descriptor is registered.
631
+ *
632
+ * 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`).
633
+ */
634
+ function getAuthoringFieldPreset(contributions, path) {
635
+ let current = contributions?.field;
636
+ for (const segment of path) {
637
+ if (typeof current !== "object" || current === null || Array.isArray(current)) return;
638
+ current = current[segment];
639
+ }
640
+ return isAuthoringFieldPresetDescriptor(current) ? current : void 0;
641
+ }
642
+ /**
643
+ * Returns the namespace prefix of `attributeName` if it references an unrecognized extension namespace, otherwise `undefined`. A namespace is considered recognized when it is:
626
644
  *
627
645
  * - `db` (native-type spec, always allowed),
628
646
  * - the active family id (e.g. `sql`),
629
647
  * - the active target id (e.g. `postgres`),
648
+ * - a registered field-preset namespace (e.g. `temporal`),
630
649
  * - present in `composedExtensions`.
631
650
  *
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).
651
+ * 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
652
  */
636
653
  function checkUncomposedNamespace(attributeName, composedExtensions, context) {
637
654
  const dotIndex = attributeName.indexOf(".");
638
655
  if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
639
656
  const namespace = attributeName.slice(0, dotIndex);
640
- if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
657
+ if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
641
658
  return namespace;
642
659
  }
643
660
  /**
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.
661
+ * 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
662
  *
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.
663
+ * The `data` payload carries the missing namespace so machine consumers (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
650
664
  */
651
665
  function reportUncomposedNamespace(input) {
652
666
  input.diagnostics.push({
@@ -660,6 +674,21 @@ function reportUncomposedNamespace(input) {
660
674
  }
661
675
  });
662
676
  }
677
+ /**
678
+ * 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.
679
+ */
680
+ function reportUnknownFieldPreset(input) {
681
+ input.diagnostics.push({
682
+ code: "PSL_UNKNOWN_FIELD_PRESET",
683
+ message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
684
+ sourceId: input.sourceId,
685
+ span: input.span,
686
+ data: {
687
+ namespace: input.namespace,
688
+ helperPath: input.helperPath
689
+ }
690
+ });
691
+ }
663
692
  function instantiatePslTypeConstructor(input) {
664
693
  const helperPath = input.call.path.join(".");
665
694
  const args = mapPslHelperArgs({
@@ -697,11 +726,15 @@ function pushUnsupportedTypeConstructorDiagnostic(input) {
697
726
  function resolvePslTypeConstructorDescriptor(input) {
698
727
  const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
699
728
  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)) {
729
+ const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
730
+ familyId: input.familyId,
731
+ targetId: input.targetId,
732
+ authoringContributions: input.authoringContributions
733
+ });
734
+ if (uncomposedNamespace) {
702
735
  reportUncomposedNamespace({
703
736
  subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
704
- namespace,
737
+ namespace: uncomposedNamespace,
705
738
  sourceId: input.sourceId,
706
739
  span: input.call.span,
707
740
  diagnostics: input.diagnostics
@@ -716,10 +749,95 @@ function resolvePslTypeConstructorDescriptor(input) {
716
749
  message: input.unsupportedMessage
717
750
  });
718
751
  }
752
+ /**
753
+ * 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.
754
+ *
755
+ * 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.
756
+ */
757
+ function instantiatePslFieldPreset(input) {
758
+ const helperPath = input.call.path.join(".");
759
+ const args = mapPslHelperArgs({
760
+ args: input.call.args,
761
+ descriptors: input.descriptor.args ?? [],
762
+ helperLabel: `preset "${helperPath}"`,
763
+ span: input.call.span,
764
+ diagnostics: input.diagnostics,
765
+ sourceId: input.sourceId,
766
+ entityLabel: input.entityLabel
767
+ });
768
+ if (!args) return;
769
+ try {
770
+ validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
771
+ const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
772
+ return {
773
+ descriptor: {
774
+ codecId: instantiated.descriptor.codecId,
775
+ nativeType: instantiated.descriptor.nativeType,
776
+ ...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
777
+ },
778
+ nullable: instantiated.nullable,
779
+ ...instantiated.default !== void 0 ? { default: instantiated.default } : {},
780
+ ...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
781
+ id: instantiated.id,
782
+ unique: instantiated.unique
783
+ };
784
+ } catch (error) {
785
+ const message = error instanceof Error ? error.message : String(error);
786
+ input.diagnostics.push({
787
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
788
+ message: `${input.entityLabel} preset "${helperPath}" ${message}`,
789
+ sourceId: input.sourceId,
790
+ span: input.call.span
791
+ });
792
+ return;
793
+ }
794
+ }
719
795
  function resolveFieldTypeDescriptor(input) {
720
796
  if (input.field.typeConstructor) {
797
+ const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
798
+ if (presetDescriptor) {
799
+ const instantiated$1 = instantiatePslFieldPreset({
800
+ call: input.field.typeConstructor,
801
+ descriptor: presetDescriptor,
802
+ diagnostics: input.diagnostics,
803
+ sourceId: input.sourceId,
804
+ entityLabel: input.entityLabel
805
+ });
806
+ if (!instantiated$1) return {
807
+ ok: false,
808
+ alreadyReported: true
809
+ };
810
+ const presetContributions = {
811
+ nullable: instantiated$1.nullable,
812
+ id: instantiated$1.id,
813
+ unique: instantiated$1.unique,
814
+ ...instantiated$1.default !== void 0 ? { default: instantiated$1.default } : {},
815
+ ...instantiated$1.executionDefaults !== void 0 ? { executionDefaults: instantiated$1.executionDefaults } : {}
816
+ };
817
+ return {
818
+ ok: true,
819
+ descriptor: instantiated$1.descriptor,
820
+ presetContributions
821
+ };
822
+ }
721
823
  const helperPath = input.field.typeConstructor.path.join(".");
722
- const descriptor$1 = resolvePslTypeConstructorDescriptor({
824
+ const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
825
+ const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
826
+ if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
827
+ reportUnknownFieldPreset({
828
+ entityLabel: input.entityLabel,
829
+ namespace: namespacePrefix,
830
+ helperPath,
831
+ sourceId: input.sourceId,
832
+ span: input.field.typeConstructor.span,
833
+ diagnostics: input.diagnostics
834
+ });
835
+ return {
836
+ ok: false,
837
+ alreadyReported: true
838
+ };
839
+ }
840
+ const descriptor$1 = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
723
841
  call: input.field.typeConstructor,
724
842
  authoringContributions: input.authoringContributions,
725
843
  composedExtensions: input.composedExtensions,
@@ -985,6 +1103,15 @@ function lowerDefaultForField(input) {
985
1103
  });
986
1104
  return {};
987
1105
  }
1106
+ if (generatorDescriptor.applicableCodecIds === void 0) {
1107
+ input.diagnostics.push({
1108
+ code: "PSL_INVALID_DEFAULT_APPLICABILITY",
1109
+ 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.`,
1110
+ sourceId: input.sourceId,
1111
+ span: expressionEntry.span
1112
+ });
1113
+ return {};
1114
+ }
988
1115
  if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
989
1116
  input.diagnostics.push({
990
1117
  code: "PSL_INVALID_DEFAULT_APPLICABILITY",
@@ -994,7 +1121,7 @@ function lowerDefaultForField(input) {
994
1121
  });
995
1122
  return {};
996
1123
  }
997
- return { executionDefault: lowered.value.generated };
1124
+ return { executionDefaults: { onCreate: lowered.value.generated } };
998
1125
  }
999
1126
  function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
1000
1127
  if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
@@ -1012,12 +1139,21 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
1012
1139
  "relation",
1013
1140
  "map"
1014
1141
  ]);
1142
+ const REMOVED_ATTRIBUTE_RULES = new Map([["updatedAt", {
1143
+ hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
1144
+ suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
1145
+ }]]);
1146
+ {
1147
+ const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
1148
+ if (overlap.length > 0) throw new Error(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
1149
+ }
1015
1150
  function validateFieldAttributes(input) {
1016
1151
  for (const attribute of input.field.attributes) {
1017
1152
  if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
1018
1153
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1019
1154
  familyId: input.familyId,
1020
- targetId: input.targetId
1155
+ targetId: input.targetId,
1156
+ authoringContributions: input.authoringContributions
1021
1157
  });
1022
1158
  if (uncomposedNamespace) {
1023
1159
  reportUncomposedNamespace({
@@ -1029,9 +1165,12 @@ function validateFieldAttributes(input) {
1029
1165
  });
1030
1166
  continue;
1031
1167
  }
1168
+ const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
1169
+ const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
1170
+ const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
1032
1171
  input.diagnostics.push({
1033
1172
  code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
1034
- message: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
1173
+ message,
1035
1174
  sourceId: input.sourceId,
1036
1175
  span: attribute.span
1037
1176
  });
@@ -1065,21 +1204,25 @@ function collectResolvedFields(input) {
1065
1204
  const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
1066
1205
  const resolvedFields = [];
1067
1206
  for (const field of model.fields) {
1068
- if (field.list && modelNames.has(field.typeName)) continue;
1207
+ const isModelField = modelNames.has(field.typeName);
1208
+ if (field.list && isModelField) continue;
1069
1209
  validateFieldAttributes({
1070
1210
  model,
1071
1211
  field,
1072
1212
  composedExtensions,
1213
+ authoringContributions,
1073
1214
  diagnostics,
1074
1215
  sourceId,
1075
1216
  familyId,
1076
1217
  targetId
1077
1218
  });
1078
- if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
1219
+ const relationAttribute = getAttribute(field.attributes, "relation");
1220
+ if (isModelField && relationAttribute) continue;
1079
1221
  const isValueObjectField = compositeTypeNames.has(field.typeName);
1080
1222
  const isListField = field.list;
1081
1223
  let descriptor;
1082
1224
  let scalarCodecId;
1225
+ let presetContributions;
1083
1226
  const resolveInput = {
1084
1227
  field,
1085
1228
  enumTypeDescriptors,
@@ -1105,6 +1248,15 @@ function collectResolvedFields(input) {
1105
1248
  });
1106
1249
  continue;
1107
1250
  }
1251
+ if (resolved.presetContributions) {
1252
+ diagnostics.push({
1253
+ code: "PSL_PRESET_NOT_LIST",
1254
+ 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.`,
1255
+ sourceId,
1256
+ span: field.span
1257
+ });
1258
+ continue;
1259
+ }
1108
1260
  scalarCodecId = resolved.descriptor.codecId;
1109
1261
  descriptor = scalarTypeDescriptors.get("Json");
1110
1262
  } else {
@@ -1119,9 +1271,28 @@ function collectResolvedFields(input) {
1119
1271
  continue;
1120
1272
  }
1121
1273
  descriptor = resolved.descriptor;
1274
+ presetContributions = resolved.presetContributions;
1122
1275
  }
1123
1276
  if (!descriptor) continue;
1277
+ if (presetContributions && field.optional) {
1278
+ diagnostics.push({
1279
+ code: "PSL_PRESET_NOT_OPTIONAL",
1280
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
1281
+ sourceId,
1282
+ span: field.span
1283
+ });
1284
+ continue;
1285
+ }
1124
1286
  const defaultAttribute = getAttribute(field.attributes, "default");
1287
+ if (presetContributions && defaultAttribute) {
1288
+ diagnostics.push({
1289
+ code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
1290
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
1291
+ sourceId,
1292
+ span: defaultAttribute.span
1293
+ });
1294
+ continue;
1295
+ }
1125
1296
  const loweredDefault = defaultAttribute ? lowerDefaultForField({
1126
1297
  modelName: model.name,
1127
1298
  fieldName: field.name,
@@ -1132,8 +1303,9 @@ function collectResolvedFields(input) {
1132
1303
  defaultFunctionRegistry,
1133
1304
  diagnostics
1134
1305
  }) : {};
1135
- if (field.optional && loweredDefault.executionDefault) {
1136
- const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
1306
+ const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
1307
+ if (field.optional && loweredOnCreate) {
1308
+ const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
1137
1309
  diagnostics.push({
1138
1310
  code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
1139
1311
  message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
@@ -1142,8 +1314,8 @@ function collectResolvedFields(input) {
1142
1314
  });
1143
1315
  continue;
1144
1316
  }
1145
- if (loweredDefault.executionDefault) {
1146
- const generatedDescriptor = generatorDescriptorById.get(loweredDefault.executionDefault.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredDefault.executionDefault });
1317
+ if (loweredOnCreate) {
1318
+ const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
1147
1319
  if (generatedDescriptor) descriptor = generatedDescriptor;
1148
1320
  }
1149
1321
  const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
@@ -1153,14 +1325,35 @@ function collectResolvedFields(input) {
1153
1325
  sourceId,
1154
1326
  diagnostics
1155
1327
  });
1328
+ let isIdField = Boolean(idAttribute);
1329
+ if (idAttribute && field.optional) {
1330
+ diagnostics.push({
1331
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1332
+ message: `Field "${model.name}.${field.name}" @id cannot be optional; primary key columns must be NOT NULL`,
1333
+ sourceId,
1334
+ span: idAttribute.span
1335
+ });
1336
+ isIdField = false;
1337
+ }
1338
+ if (presetContributions && idAttribute && !presetContributions.id) {
1339
+ diagnostics.push({
1340
+ code: "PSL_PRESET_AND_ID_CONFLICT",
1341
+ 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.`,
1342
+ sourceId,
1343
+ span: idAttribute.span
1344
+ });
1345
+ continue;
1346
+ }
1347
+ const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
1348
+ const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
1156
1349
  resolvedFields.push({
1157
1350
  field,
1158
1351
  columnName: mappedColumnName,
1159
1352
  descriptor,
1160
- ...ifDefined("defaultValue", loweredDefault.defaultValue),
1161
- ...ifDefined("executionDefault", loweredDefault.executionDefault),
1162
- isId: Boolean(idAttribute),
1163
- isUnique: Boolean(uniqueAttribute),
1353
+ ...ifDefined("defaultValue", fieldDefaultValue),
1354
+ ...ifDefined("executionDefaults", fieldExecutionDefaults),
1355
+ isId: isIdField || Boolean(presetContributions?.id),
1356
+ isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
1164
1357
  ...ifDefined("idName", idName),
1165
1358
  ...ifDefined("uniqueName", uniqueName),
1166
1359
  ...ifDefined("many", isListField ? true : void 0),
@@ -1418,7 +1611,8 @@ function validateNavigationListFieldAttributes(input) {
1418
1611
  if (attribute.name === "relation") continue;
1419
1612
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1420
1613
  familyId: input.familyId,
1421
- targetId: input.targetId
1614
+ targetId: input.targetId,
1615
+ authoringContributions: input.authoringContributions
1422
1616
  });
1423
1617
  if (uncomposedNamespace) {
1424
1618
  reportUncomposedNamespace({
@@ -1531,7 +1725,8 @@ function validateNamedTypeAttributes(input) {
1531
1725
  if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
1532
1726
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1533
1727
  familyId: input.familyId,
1534
- targetId: input.targetId
1728
+ targetId: input.targetId,
1729
+ authoringContributions: input.authoringContributions
1535
1730
  });
1536
1731
  if (uncomposedNamespace) {
1537
1732
  reportUncomposedNamespace({
@@ -1567,6 +1762,7 @@ function resolveNamedTypeDeclarations(input) {
1567
1762
  sourceId: input.sourceId,
1568
1763
  diagnostics: input.diagnostics,
1569
1764
  composedExtensions: input.composedExtensions,
1765
+ authoringContributions: input.authoringContributions,
1570
1766
  allowDbNativeType: false,
1571
1767
  familyId: input.familyId,
1572
1768
  targetId: input.targetId
@@ -1626,6 +1822,7 @@ function resolveNamedTypeDeclarations(input) {
1626
1822
  sourceId: input.sourceId,
1627
1823
  diagnostics: input.diagnostics,
1628
1824
  composedExtensions: input.composedExtensions,
1825
+ authoringContributions: input.authoringContributions,
1629
1826
  allowDbNativeType: true,
1630
1827
  familyId: input.familyId,
1631
1828
  targetId: input.targetId
@@ -1682,16 +1879,20 @@ function buildModelNodeFromPsl(input) {
1682
1879
  sourceId,
1683
1880
  scalarTypeDescriptors: input.scalarTypeDescriptors
1684
1881
  });
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`,
1882
+ const inlineIdFields = resolvedFields.filter((field) => field.isId);
1883
+ if (inlineIdFields.length > 1) diagnostics.push({
1884
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1885
+ message: `Model "${model.name}" cannot declare inline @id on multiple fields; use model-level @@id([...]) for composite identity`,
1692
1886
  sourceId,
1693
1887
  span: model.span
1694
1888
  });
1889
+ const singleInlineIdField = inlineIdFields.length === 1 ? inlineIdFields[0] : void 0;
1890
+ let primaryKey = singleInlineIdField ? {
1891
+ columns: [singleInlineIdField.columnName],
1892
+ ...ifDefined("name", singleInlineIdField.idName)
1893
+ } : void 0;
1894
+ const hasInlinePrimaryKey = primaryKey !== void 0;
1895
+ let blockPrimaryKeyDeclared = false;
1695
1896
  const resultBackrelationCandidates = [];
1696
1897
  for (const field of model.fields) {
1697
1898
  if (!field.list || !input.modelNames.has(field.typeName)) continue;
@@ -1700,6 +1901,7 @@ function buildModelNodeFromPsl(input) {
1700
1901
  field,
1701
1902
  sourceId,
1702
1903
  composedExtensions: input.composedExtensions,
1904
+ authoringContributions: input.authoringContributions,
1703
1905
  diagnostics,
1704
1906
  familyId: input.familyId,
1705
1907
  targetId: input.targetId
@@ -1757,15 +1959,98 @@ function buildModelNodeFromPsl(input) {
1757
1959
  for (const modelAttribute of model.attributes) {
1758
1960
  if (modelAttribute.name === "map") continue;
1759
1961
  if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
1962
+ const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
1963
+ if (modelAttribute.name === "id") {
1964
+ if (blockPrimaryKeyDeclared) {
1965
+ diagnostics.push({
1966
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1967
+ message: `Model "${model.name}" declares @@id more than once`,
1968
+ sourceId,
1969
+ span: modelAttribute.span
1970
+ });
1971
+ continue;
1972
+ }
1973
+ if (hasInlinePrimaryKey) {
1974
+ diagnostics.push({
1975
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1976
+ message: `Model "${model.name}" cannot declare both field-level @id and model-level @@id`,
1977
+ sourceId,
1978
+ span: modelAttribute.span
1979
+ });
1980
+ blockPrimaryKeyDeclared = true;
1981
+ continue;
1982
+ }
1983
+ const fieldNames = parseAttributeFieldList({
1984
+ attribute: modelAttribute,
1985
+ sourceId,
1986
+ diagnostics,
1987
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1988
+ entityLabel: attributeLabel
1989
+ });
1990
+ if (!fieldNames) continue;
1991
+ const duplicateFieldName = findDuplicateFieldName(fieldNames);
1992
+ if (duplicateFieldName !== void 0) {
1993
+ diagnostics.push({
1994
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1995
+ message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
1996
+ sourceId,
1997
+ span: modelAttribute.span
1998
+ });
1999
+ continue;
2000
+ }
2001
+ const nullableFieldName = fieldNames.find((name) => model.fields.find((f) => f.name === name)?.optional === true);
2002
+ if (nullableFieldName !== void 0) {
2003
+ diagnostics.push({
2004
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
2005
+ message: `${attributeLabel} cannot include optional field "${nullableFieldName}"; primary key columns must be NOT NULL`,
2006
+ sourceId,
2007
+ span: modelAttribute.span
2008
+ });
2009
+ continue;
2010
+ }
2011
+ const columnNames = mapFieldNamesToColumns({
2012
+ modelName: model.name,
2013
+ fieldNames,
2014
+ mapping,
2015
+ sourceId,
2016
+ diagnostics,
2017
+ span: modelAttribute.span,
2018
+ entityLabel: attributeLabel
2019
+ });
2020
+ if (!columnNames) continue;
2021
+ primaryKey = {
2022
+ columns: columnNames,
2023
+ ...ifDefined("name", parseConstraintMapArgument({
2024
+ attribute: modelAttribute,
2025
+ sourceId,
2026
+ diagnostics,
2027
+ entityLabel: attributeLabel,
2028
+ span: modelAttribute.span,
2029
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
2030
+ }))
2031
+ };
2032
+ blockPrimaryKeyDeclared = true;
2033
+ continue;
2034
+ }
1760
2035
  if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
1761
2036
  const fieldNames = parseAttributeFieldList({
1762
2037
  attribute: modelAttribute,
1763
2038
  sourceId,
1764
2039
  diagnostics,
1765
2040
  code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
1766
- messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
2041
+ entityLabel: attributeLabel
1767
2042
  });
1768
2043
  if (!fieldNames) continue;
2044
+ const duplicateFieldName = findDuplicateFieldName(fieldNames);
2045
+ if (duplicateFieldName !== void 0) {
2046
+ diagnostics.push({
2047
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
2048
+ message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
2049
+ sourceId,
2050
+ span: modelAttribute.span
2051
+ });
2052
+ continue;
2053
+ }
1769
2054
  const columnNames = mapFieldNamesToColumns({
1770
2055
  modelName: model.name,
1771
2056
  fieldNames,
@@ -1773,14 +2058,14 @@ function buildModelNodeFromPsl(input) {
1773
2058
  sourceId,
1774
2059
  diagnostics,
1775
2060
  span: modelAttribute.span,
1776
- contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
2061
+ entityLabel: attributeLabel
1777
2062
  });
1778
2063
  if (!columnNames) continue;
1779
2064
  const constraintName = parseConstraintMapArgument({
1780
2065
  attribute: modelAttribute,
1781
2066
  sourceId,
1782
2067
  diagnostics,
1783
- entityLabel: `Model "${model.name}" @@${modelAttribute.name}`,
2068
+ entityLabel: attributeLabel,
1784
2069
  span: modelAttribute.span,
1785
2070
  code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
1786
2071
  });
@@ -1796,7 +2081,8 @@ function buildModelNodeFromPsl(input) {
1796
2081
  }
1797
2082
  const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
1798
2083
  familyId: input.familyId,
1799
- targetId: input.targetId
2084
+ targetId: input.targetId,
2085
+ authoringContributions: input.authoringContributions
1800
2086
  });
1801
2087
  if (uncomposedNamespace) {
1802
2088
  reportUncomposedNamespace({
@@ -1861,7 +2147,7 @@ function buildModelNodeFromPsl(input) {
1861
2147
  sourceId,
1862
2148
  diagnostics,
1863
2149
  span: relationAttribute.relation.span,
1864
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
2150
+ entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
1865
2151
  });
1866
2152
  if (!localColumns) continue;
1867
2153
  const referencedColumns = mapFieldNamesToColumns({
@@ -1871,7 +2157,7 @@ function buildModelNodeFromPsl(input) {
1871
2157
  sourceId,
1872
2158
  diagnostics,
1873
2159
  span: relationAttribute.relation.span,
1874
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
2160
+ entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
1875
2161
  });
1876
2162
  if (!referencedColumns) continue;
1877
2163
  if (localColumns.length !== referencedColumns.length) {
@@ -1933,12 +2219,9 @@ function buildModelNodeFromPsl(input) {
1933
2219
  descriptor: resolvedField.descriptor,
1934
2220
  nullable: resolvedField.field.optional,
1935
2221
  ...ifDefined("default", resolvedField.defaultValue),
1936
- ...ifDefined("executionDefault", resolvedField.executionDefault)
2222
+ ...ifDefined("executionDefaults", resolvedField.executionDefaults)
1937
2223
  })),
1938
- ...primaryKeyColumns.length > 0 ? { id: {
1939
- columns: primaryKeyColumns,
1940
- ...ifDefined("name", primaryKeyName)
1941
- } } : {},
2224
+ ...ifDefined("id", primaryKey),
1942
2225
  ...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
1943
2226
  ...indexNodes.length > 0 ? { indexes: indexNodes } : {},
1944
2227
  ...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
@@ -2340,4 +2623,4 @@ function interpretPslDocumentToSqlContract(input) {
2340
2623
 
2341
2624
  //#endregion
2342
2625
  export { interpretPslDocumentToSqlContract as t };
2343
- //# sourceMappingURL=interpreter-iFCRN9nb.mjs.map
2626
+ //# sourceMappingURL=interpreter-g4FDWENY.mjs.map