@prisma-next/sql-contract-psl 0.5.0-dev.8 → 0.5.0-dev.81

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