@prisma-next/sql-contract-psl 0.5.0-dev.51 → 0.5.0-dev.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ This keeps core/CLI source-agnostic while giving PSL-first SQL users a one-line
15
15
 
16
16
  - Interpret `ParsePslDocumentResult` into SQL `Contract`
17
17
  - Interpret generic PSL attributes into SQL contract semantics (`@id`, `@unique`, `@default`, `@relation`, `@map`, `@@map`)
18
+ - Interpret SQL timestamp semantics: `DateTime @default(now())` (or the equivalent `temporal.createdAt()` field-preset call) as a storage default, and `temporal.updatedAt()` as an execution mutation default
18
19
  - Lower shared constructor expressions in both `types {}` blocks and inline field positions (for example `ShortName = sql.String(length: 35)` and `embedding pgvector.Vector(length: 1536)?`)
19
20
  - Lower supported default functions through composed registry inputs
20
21
  - Support selected Postgres native-type attributes on named types for brownfield round-trips (`@db.Char`, `@db.VarChar`, `@db.Numeric`, `@db.Uuid`, `@db.SmallInt`, `@db.Real`, `@db.Timestamp`, `@db.Timestamptz`, `@db.Date`, `@db.Time`, `@db.Timetz`, `@db.Json`)
@@ -42,7 +43,7 @@ The **pure interpreter entrypoint** specifically excludes:
42
43
  - Artifact emission (`contract.json`, `contract.d.ts`) and hashing
43
44
  - CLI or ControlClient orchestration
44
45
 
45
- Current scope is SQL/Postgres-first: callers pass Postgres-oriented scalar descriptors and target context in v1.
46
+ Current scope is SQL target-specific: callers pass scalar descriptors and target context assembled for the active SQL target.
46
47
 
47
48
  Unsupported PSL constructs in v1 (strict errors):
48
49
 
@@ -63,6 +64,13 @@ Supported `@default(...)` surface in v1 when composed contributors provide handl
63
64
  - Explicitly unsupported in v1: `cuid()` (diagnostic suggests `cuid(2)`)
64
65
  - `dbgenerated("...")` preserves the parsed PSL string-literal contents as-is (escaped sequences are not normalized in v1).
65
66
 
67
+ Supported timestamp authoring surface:
68
+
69
+ - `createdAt DateTime @default(now())` and `createdAt temporal.createdAt()` both lower to the target storage default and do not create an execution mutation default.
70
+ - `updatedAt temporal.updatedAt()` lowers to `timestampNow` on create and on non-empty update mutations. This is application-side because update-time semantics are mutation-aware, not a database trigger.
71
+ - The Prisma-flavored `@updatedAt` attribute is not supported; references produce `PSL_UNSUPPORTED_FIELD_ATTRIBUTE` with a migration hint pointing at `temporal.updatedAt()`. The hint is suppressed when the field already declares any `temporal.*` preset.
72
+ - `@createdAt` is not supported as a PSL alias.
73
+
66
74
  ## Public API
67
75
 
68
76
  - `@prisma-next/sql-contract-psl`
package/dist/index.d.mts CHANGED
@@ -17,10 +17,10 @@ type ColumnDescriptor = {
17
17
  //#region src/interpreter.d.ts
18
18
  interface InterpretPslDocumentToSqlContractInput {
19
19
  readonly document: ParsePslDocumentResult;
20
- readonly target: TargetPackRef<'sql', 'postgres'>;
20
+ readonly target: TargetPackRef<'sql', string>;
21
21
  readonly scalarTypeDescriptors: ReadonlyMap<string, ColumnDescriptor>;
22
22
  readonly composedExtensionPacks?: readonly string[];
23
- readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', 'postgres'>[];
23
+ readonly composedExtensionPackRefs?: readonly ExtensionPackRef<'sql', string>[];
24
24
  readonly controlMutationDefaults?: ControlMutationDefaults$1;
25
25
  readonly authoringContributions?: AuthoringContributions;
26
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;KAmCY,gBAAA;;;;EAAA,SAAA,UAAgB,CAAA,EAIJ,MAJI,CAAA,MAIJ,EAAM,OAAA,CAAA;;;;UCsCb,sCAAA;ED1CL,SAAA,QAAA,EC2CS,sBDvCS;mBCwCX;kCACe,oBAAoB;;EAHrC,SAAA,yBAAA,CAAA,EAAA,SAK+B,gBALO,CAAA,KAAA,EAAA,UAAA,CAAA,EAAA;EAClC,SAAA,uBAAA,CAAA,EAKgB,yBALhB;EACF,SAAA,sBAAA,CAAA,EAKiB,sBALjB;;AACe,iBAu/BlB,iCAAA,CAv/BkB,KAAA,EAw/BzB,sCAx/ByB,CAAA,EAy/B/B,MAz/B+B,CAy/BxB,QAz/BwB,EAy/Bd,yBAz/Bc,CAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"sourcesContent":[],"mappings":";;;;;;;;;KAuCY,gBAAA;;;;EAAA,SAAA,UAAgB,CAAA,EAIJ,MAJI,CAAA,MAIJ,EAAM,OAAA,CAAA;;;;UCkCb,sCAAA;EDtCL,SAAA,QAAA,ECuCS,sBDnCS;mBCoCX;kCACe,oBAAoB;;EAHrC,SAAA,yBAAA,CAAA,EAAA,SAK+B,gBALO,CAAA,KAAA,EAAA,MAAA,CAAA,EAAA;EAClC,SAAA,uBAAA,CAAA,EAKgB,yBALhB;EACF,SAAA,sBAAA,CAAA,EAKiB,sBALjB;;AACe,iBAggClB,iCAAA,CAhgCkB,KAAA,EAigCzB,sCAjgCyB,CAAA,EAkgC/B,MAlgC+B,CAkgCxB,QAlgCwB,EAkgCd,yBAlgCc,CAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as interpretPslDocumentToSqlContract } from "./interpreter-iFCRN9nb.mjs";
1
+ import { t as interpretPslDocumentToSqlContract } from "./interpreter-DJrrH8Ee.mjs";
2
2
 
3
3
  export { interpretPslDocumentToSqlContract };
@@ -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";
@@ -620,6 +620,26 @@ function getAuthoringTypeConstructor(contributions, path) {
620
620
  return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
621
621
  }
622
622
  /**
623
+ * Walks `authoringContributions.field` segment-by-segment and returns the
624
+ * field-preset descriptor at the resolved path, or `undefined` if no descriptor
625
+ * is registered.
626
+ *
627
+ * Symmetric with `getAuthoringTypeConstructor`. Field presets are strictly
628
+ * richer than type constructors — they can contribute `default` /
629
+ * `executionDefaults` / `id` / `unique` / `nullable` in addition to the
630
+ * `codecId` / `nativeType` / `typeParams` triple. PSL resolution tries field
631
+ * presets first, then falls back to type constructors on miss (see
632
+ * `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
+ /**
623
643
  * Returns the namespace prefix of `attributeName` if it references an
624
644
  * unrecognized extension namespace, otherwise `undefined`. A namespace is
625
645
  * considered recognized when it is:
@@ -627,17 +647,19 @@ function getAuthoringTypeConstructor(contributions, path) {
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`
654
+ * surfaces as PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined)
655
+ * rather than PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already
656
+ * composed).
635
657
  */
636
658
  function checkUncomposedNamespace(attributeName, composedExtensions, context) {
637
659
  const dotIndex = attributeName.indexOf(".");
638
660
  if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
639
661
  const namespace = attributeName.slice(0, dotIndex);
640
- if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
662
+ if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
641
663
  return namespace;
642
664
  }
643
665
  /**
@@ -660,6 +682,24 @@ function reportUncomposedNamespace(input) {
660
682
  }
661
683
  });
662
684
  }
685
+ /**
686
+ * Pushes the canonical `PSL_UNKNOWN_FIELD_PRESET` diagnostic when a typoed
687
+ * preset name is referenced inside a registered field-preset namespace. The
688
+ * `data` payload exposes the namespace and full helper path so machine
689
+ * consumers (agents, IDE extensions) don't have to parse the prose.
690
+ */
691
+ function reportUnknownFieldPreset(input) {
692
+ input.diagnostics.push({
693
+ code: "PSL_UNKNOWN_FIELD_PRESET",
694
+ message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
695
+ sourceId: input.sourceId,
696
+ span: input.span,
697
+ data: {
698
+ namespace: input.namespace,
699
+ helperPath: input.helperPath
700
+ }
701
+ });
702
+ }
663
703
  function instantiatePslTypeConstructor(input) {
664
704
  const helperPath = input.call.path.join(".");
665
705
  const args = mapPslHelperArgs({
@@ -697,11 +737,15 @@ function pushUnsupportedTypeConstructorDiagnostic(input) {
697
737
  function resolvePslTypeConstructorDescriptor(input) {
698
738
  const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
699
739
  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)) {
740
+ const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
741
+ familyId: input.familyId,
742
+ targetId: input.targetId,
743
+ authoringContributions: input.authoringContributions
744
+ });
745
+ if (uncomposedNamespace) {
702
746
  reportUncomposedNamespace({
703
747
  subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
704
- namespace,
748
+ namespace: uncomposedNamespace,
705
749
  sourceId: input.sourceId,
706
750
  span: input.call.span,
707
751
  diagnostics: input.diagnostics
@@ -716,10 +760,102 @@ function resolvePslTypeConstructorDescriptor(input) {
716
760
  message: input.unsupportedMessage
717
761
  });
718
762
  }
763
+ /**
764
+ * Instantiates a field-preset call against its descriptor, coercing PSL AST
765
+ * arguments into the descriptor's typed argument shape and returning the
766
+ * preset's full set of contract contributions.
767
+ *
768
+ * Symmetric with `instantiatePslTypeConstructor` but richer: a field preset
769
+ * can contribute `default`, `executionDefaults`, `id`, `unique`, and
770
+ * `nullable` in addition to the storage-type triple. PSL → typed-args
771
+ * coercion happens here (via `mapPslHelperArgs`) so that
772
+ * `instantiateAuthoringFieldPreset` itself stays typed-input-only and TS
773
+ * keeps its zero-runtime-validation cost.
774
+ */
775
+ function instantiatePslFieldPreset(input) {
776
+ const helperPath = input.call.path.join(".");
777
+ const args = mapPslHelperArgs({
778
+ args: input.call.args,
779
+ descriptors: input.descriptor.args ?? [],
780
+ helperLabel: `preset "${helperPath}"`,
781
+ span: input.call.span,
782
+ diagnostics: input.diagnostics,
783
+ sourceId: input.sourceId,
784
+ entityLabel: input.entityLabel
785
+ });
786
+ if (!args) return;
787
+ try {
788
+ validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
789
+ const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
790
+ return {
791
+ descriptor: {
792
+ codecId: instantiated.descriptor.codecId,
793
+ nativeType: instantiated.descriptor.nativeType,
794
+ ...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
795
+ },
796
+ nullable: instantiated.nullable,
797
+ ...instantiated.default !== void 0 ? { default: instantiated.default } : {},
798
+ ...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
799
+ id: instantiated.id,
800
+ unique: instantiated.unique
801
+ };
802
+ } catch (error) {
803
+ const message = error instanceof Error ? error.message : String(error);
804
+ input.diagnostics.push({
805
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
806
+ message: `${input.entityLabel} preset "${helperPath}" ${message}`,
807
+ sourceId: input.sourceId,
808
+ span: input.call.span
809
+ });
810
+ return;
811
+ }
812
+ }
719
813
  function resolveFieldTypeDescriptor(input) {
720
814
  if (input.field.typeConstructor) {
815
+ const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
816
+ if (presetDescriptor) {
817
+ const instantiated$1 = instantiatePslFieldPreset({
818
+ call: input.field.typeConstructor,
819
+ descriptor: presetDescriptor,
820
+ diagnostics: input.diagnostics,
821
+ sourceId: input.sourceId,
822
+ entityLabel: input.entityLabel
823
+ });
824
+ if (!instantiated$1) return {
825
+ ok: false,
826
+ alreadyReported: true
827
+ };
828
+ const presetContributions = {
829
+ nullable: instantiated$1.nullable,
830
+ id: instantiated$1.id,
831
+ unique: instantiated$1.unique,
832
+ ...instantiated$1.default !== void 0 ? { default: instantiated$1.default } : {},
833
+ ...instantiated$1.executionDefaults !== void 0 ? { executionDefaults: instantiated$1.executionDefaults } : {}
834
+ };
835
+ return {
836
+ ok: true,
837
+ descriptor: instantiated$1.descriptor,
838
+ presetContributions
839
+ };
840
+ }
721
841
  const helperPath = input.field.typeConstructor.path.join(".");
722
- const descriptor$1 = resolvePslTypeConstructorDescriptor({
842
+ const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
843
+ const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
844
+ if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
845
+ reportUnknownFieldPreset({
846
+ entityLabel: input.entityLabel,
847
+ namespace: namespacePrefix,
848
+ helperPath,
849
+ sourceId: input.sourceId,
850
+ span: input.field.typeConstructor.span,
851
+ diagnostics: input.diagnostics
852
+ });
853
+ return {
854
+ ok: false,
855
+ alreadyReported: true
856
+ };
857
+ }
858
+ const descriptor$1 = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
723
859
  call: input.field.typeConstructor,
724
860
  authoringContributions: input.authoringContributions,
725
861
  composedExtensions: input.composedExtensions,
@@ -985,6 +1121,15 @@ function lowerDefaultForField(input) {
985
1121
  });
986
1122
  return {};
987
1123
  }
1124
+ if (generatorDescriptor.applicableCodecIds === void 0) {
1125
+ input.diagnostics.push({
1126
+ code: "PSL_INVALID_DEFAULT_APPLICABILITY",
1127
+ 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.`,
1128
+ sourceId: input.sourceId,
1129
+ span: expressionEntry.span
1130
+ });
1131
+ return {};
1132
+ }
988
1133
  if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
989
1134
  input.diagnostics.push({
990
1135
  code: "PSL_INVALID_DEFAULT_APPLICABILITY",
@@ -994,7 +1139,7 @@ function lowerDefaultForField(input) {
994
1139
  });
995
1140
  return {};
996
1141
  }
997
- return { executionDefault: lowered.value.generated };
1142
+ return { executionDefaults: { onCreate: lowered.value.generated } };
998
1143
  }
999
1144
  function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
1000
1145
  if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
@@ -1012,12 +1157,21 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
1012
1157
  "relation",
1013
1158
  "map"
1014
1159
  ]);
1160
+ const REMOVED_ATTRIBUTE_RULES = new Map([["updatedAt", {
1161
+ hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
1162
+ suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
1163
+ }]]);
1164
+ {
1165
+ const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
1166
+ if (overlap.length > 0) throw new Error(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
1167
+ }
1015
1168
  function validateFieldAttributes(input) {
1016
1169
  for (const attribute of input.field.attributes) {
1017
1170
  if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
1018
1171
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1019
1172
  familyId: input.familyId,
1020
- targetId: input.targetId
1173
+ targetId: input.targetId,
1174
+ authoringContributions: input.authoringContributions
1021
1175
  });
1022
1176
  if (uncomposedNamespace) {
1023
1177
  reportUncomposedNamespace({
@@ -1029,9 +1183,12 @@ function validateFieldAttributes(input) {
1029
1183
  });
1030
1184
  continue;
1031
1185
  }
1186
+ const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
1187
+ const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
1188
+ const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
1032
1189
  input.diagnostics.push({
1033
1190
  code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
1034
- message: `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`,
1191
+ message,
1035
1192
  sourceId: input.sourceId,
1036
1193
  span: attribute.span
1037
1194
  });
@@ -1065,21 +1222,25 @@ function collectResolvedFields(input) {
1065
1222
  const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
1066
1223
  const resolvedFields = [];
1067
1224
  for (const field of model.fields) {
1068
- if (field.list && modelNames.has(field.typeName)) continue;
1225
+ const isModelField = modelNames.has(field.typeName);
1226
+ if (field.list && isModelField) continue;
1069
1227
  validateFieldAttributes({
1070
1228
  model,
1071
1229
  field,
1072
1230
  composedExtensions,
1231
+ authoringContributions,
1073
1232
  diagnostics,
1074
1233
  sourceId,
1075
1234
  familyId,
1076
1235
  targetId
1077
1236
  });
1078
- if (getAttribute(field.attributes, "relation") && modelNames.has(field.typeName)) continue;
1237
+ const relationAttribute = getAttribute(field.attributes, "relation");
1238
+ if (isModelField && relationAttribute) continue;
1079
1239
  const isValueObjectField = compositeTypeNames.has(field.typeName);
1080
1240
  const isListField = field.list;
1081
1241
  let descriptor;
1082
1242
  let scalarCodecId;
1243
+ let presetContributions;
1083
1244
  const resolveInput = {
1084
1245
  field,
1085
1246
  enumTypeDescriptors,
@@ -1105,6 +1266,15 @@ function collectResolvedFields(input) {
1105
1266
  });
1106
1267
  continue;
1107
1268
  }
1269
+ if (resolved.presetContributions) {
1270
+ diagnostics.push({
1271
+ code: "PSL_PRESET_NOT_LIST",
1272
+ 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.`,
1273
+ sourceId,
1274
+ span: field.span
1275
+ });
1276
+ continue;
1277
+ }
1108
1278
  scalarCodecId = resolved.descriptor.codecId;
1109
1279
  descriptor = scalarTypeDescriptors.get("Json");
1110
1280
  } else {
@@ -1119,9 +1289,28 @@ function collectResolvedFields(input) {
1119
1289
  continue;
1120
1290
  }
1121
1291
  descriptor = resolved.descriptor;
1292
+ presetContributions = resolved.presetContributions;
1122
1293
  }
1123
1294
  if (!descriptor) continue;
1295
+ if (presetContributions && field.optional) {
1296
+ diagnostics.push({
1297
+ code: "PSL_PRESET_NOT_OPTIONAL",
1298
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
1299
+ sourceId,
1300
+ span: field.span
1301
+ });
1302
+ continue;
1303
+ }
1124
1304
  const defaultAttribute = getAttribute(field.attributes, "default");
1305
+ if (presetContributions && defaultAttribute) {
1306
+ diagnostics.push({
1307
+ code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
1308
+ message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
1309
+ sourceId,
1310
+ span: defaultAttribute.span
1311
+ });
1312
+ continue;
1313
+ }
1125
1314
  const loweredDefault = defaultAttribute ? lowerDefaultForField({
1126
1315
  modelName: model.name,
1127
1316
  fieldName: field.name,
@@ -1132,8 +1321,9 @@ function collectResolvedFields(input) {
1132
1321
  defaultFunctionRegistry,
1133
1322
  diagnostics
1134
1323
  }) : {};
1135
- if (field.optional && loweredDefault.executionDefault) {
1136
- const generatorDescription = loweredDefault.executionDefault.kind === "generator" ? `"${loweredDefault.executionDefault.id}"` : "for this field";
1324
+ const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
1325
+ if (field.optional && loweredOnCreate) {
1326
+ const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
1137
1327
  diagnostics.push({
1138
1328
  code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
1139
1329
  message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
@@ -1142,8 +1332,8 @@ function collectResolvedFields(input) {
1142
1332
  });
1143
1333
  continue;
1144
1334
  }
1145
- if (loweredDefault.executionDefault) {
1146
- const generatedDescriptor = generatorDescriptorById.get(loweredDefault.executionDefault.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredDefault.executionDefault });
1335
+ if (loweredOnCreate) {
1336
+ const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
1147
1337
  if (generatedDescriptor) descriptor = generatedDescriptor;
1148
1338
  }
1149
1339
  const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
@@ -1153,14 +1343,25 @@ function collectResolvedFields(input) {
1153
1343
  sourceId,
1154
1344
  diagnostics
1155
1345
  });
1346
+ if (presetContributions && idAttribute && !presetContributions.id) {
1347
+ diagnostics.push({
1348
+ code: "PSL_PRESET_AND_ID_CONFLICT",
1349
+ 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.`,
1350
+ sourceId,
1351
+ span: idAttribute.span
1352
+ });
1353
+ continue;
1354
+ }
1355
+ const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
1356
+ const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
1156
1357
  resolvedFields.push({
1157
1358
  field,
1158
1359
  columnName: mappedColumnName,
1159
1360
  descriptor,
1160
- ...ifDefined("defaultValue", loweredDefault.defaultValue),
1161
- ...ifDefined("executionDefault", loweredDefault.executionDefault),
1162
- isId: Boolean(idAttribute),
1163
- isUnique: Boolean(uniqueAttribute),
1361
+ ...ifDefined("defaultValue", fieldDefaultValue),
1362
+ ...ifDefined("executionDefaults", fieldExecutionDefaults),
1363
+ isId: Boolean(idAttribute) || Boolean(presetContributions?.id),
1364
+ isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
1164
1365
  ...ifDefined("idName", idName),
1165
1366
  ...ifDefined("uniqueName", uniqueName),
1166
1367
  ...ifDefined("many", isListField ? true : void 0),
@@ -1418,7 +1619,8 @@ function validateNavigationListFieldAttributes(input) {
1418
1619
  if (attribute.name === "relation") continue;
1419
1620
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1420
1621
  familyId: input.familyId,
1421
- targetId: input.targetId
1622
+ targetId: input.targetId,
1623
+ authoringContributions: input.authoringContributions
1422
1624
  });
1423
1625
  if (uncomposedNamespace) {
1424
1626
  reportUncomposedNamespace({
@@ -1531,7 +1733,8 @@ function validateNamedTypeAttributes(input) {
1531
1733
  if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
1532
1734
  const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
1533
1735
  familyId: input.familyId,
1534
- targetId: input.targetId
1736
+ targetId: input.targetId,
1737
+ authoringContributions: input.authoringContributions
1535
1738
  });
1536
1739
  if (uncomposedNamespace) {
1537
1740
  reportUncomposedNamespace({
@@ -1567,6 +1770,7 @@ function resolveNamedTypeDeclarations(input) {
1567
1770
  sourceId: input.sourceId,
1568
1771
  diagnostics: input.diagnostics,
1569
1772
  composedExtensions: input.composedExtensions,
1773
+ authoringContributions: input.authoringContributions,
1570
1774
  allowDbNativeType: false,
1571
1775
  familyId: input.familyId,
1572
1776
  targetId: input.targetId
@@ -1626,6 +1830,7 @@ function resolveNamedTypeDeclarations(input) {
1626
1830
  sourceId: input.sourceId,
1627
1831
  diagnostics: input.diagnostics,
1628
1832
  composedExtensions: input.composedExtensions,
1833
+ authoringContributions: input.authoringContributions,
1629
1834
  allowDbNativeType: true,
1630
1835
  familyId: input.familyId,
1631
1836
  targetId: input.targetId
@@ -1700,6 +1905,7 @@ function buildModelNodeFromPsl(input) {
1700
1905
  field,
1701
1906
  sourceId,
1702
1907
  composedExtensions: input.composedExtensions,
1908
+ authoringContributions: input.authoringContributions,
1703
1909
  diagnostics,
1704
1910
  familyId: input.familyId,
1705
1911
  targetId: input.targetId
@@ -1796,7 +2002,8 @@ function buildModelNodeFromPsl(input) {
1796
2002
  }
1797
2003
  const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
1798
2004
  familyId: input.familyId,
1799
- targetId: input.targetId
2005
+ targetId: input.targetId,
2006
+ authoringContributions: input.authoringContributions
1800
2007
  });
1801
2008
  if (uncomposedNamespace) {
1802
2009
  reportUncomposedNamespace({
@@ -1933,7 +2140,7 @@ function buildModelNodeFromPsl(input) {
1933
2140
  descriptor: resolvedField.descriptor,
1934
2141
  nullable: resolvedField.field.optional,
1935
2142
  ...ifDefined("default", resolvedField.defaultValue),
1936
- ...ifDefined("executionDefault", resolvedField.executionDefault)
2143
+ ...ifDefined("executionDefaults", resolvedField.executionDefaults)
1937
2144
  })),
1938
2145
  ...primaryKeyColumns.length > 0 ? { id: {
1939
2146
  columns: primaryKeyColumns,
@@ -2340,4 +2547,4 @@ function interpretPslDocumentToSqlContract(input) {
2340
2547
 
2341
2548
  //#endregion
2342
2549
  export { interpretPslDocumentToSqlContract as t };
2343
- //# sourceMappingURL=interpreter-iFCRN9nb.mjs.map
2550
+ //# sourceMappingURL=interpreter-DJrrH8Ee.mjs.map