@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.
@@ -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,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` surfaces as
633
- * PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
634
- * PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
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 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)) {
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 descriptor$1 = resolvePslTypeConstructorDescriptor({
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 { executionDefault: lowered.value.generated };
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: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
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
- if (field.list && modelNames.has(field.typeName)) continue;
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
- if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
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
- if (field.optional && loweredDefault.executionDefault) {
1136
- const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
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 (loweredDefault.executionDefault) {
1146
- const generatedDescriptor = generatorDescriptorById.get(loweredDefault.executionDefault.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredDefault.executionDefault });
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", loweredDefault.defaultValue),
1161
- ...ifDefined("executionDefault", loweredDefault.executionDefault),
1162
- isId: Boolean(idAttribute),
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 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`,
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
- messagePrefix: `Model "${model.name}" @@${modelAttribute.name}`
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
- contextLabel: `Model "${model.name}" @@${modelAttribute.name}`
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: `Model "${model.name}" @@${modelAttribute.name}`,
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
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
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
- contextLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
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("executionDefault", resolvedField.executionDefault)
2247
+ ...ifDefined("executionDefaults", resolvedField.executionDefaults)
1937
2248
  })),
1938
- ...primaryKeyColumns.length > 0 ? { id: {
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-iFCRN9nb.mjs.map
2651
+ //# sourceMappingURL=interpreter-BChe-9vN.mjs.map