@prisma-next/sql-contract-psl 0.12.0-dev.5 → 0.12.0-dev.51

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
@@ -14,7 +14,7 @@ This keeps core/CLI source-agnostic while giving PSL-first SQL users a one-line
14
14
  ## Responsibilities
15
15
 
16
16
  - Interpret `ParsePslDocumentResult` into SQL `Contract`
17
- - Interpret generic PSL attributes into SQL contract semantics (`@id`, `@unique`, `@default`, `@relation`, `@map`, `@@map`)
17
+ - Interpret generic PSL attributes into SQL contract semantics (`@id`, `@unique`, `@default`, `@relation`, `@map`, `@@map`, `@@control`)
18
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
19
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)?`)
20
20
  - Lower supported default functions through composed registry inputs
@@ -71,12 +71,20 @@ Supported timestamp authoring surface:
71
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
72
  - `@createdAt` is not supported as a PSL alias.
73
73
 
74
+ Model-level control policy:
75
+
76
+ - `@@control(<policy>)` lowers to the storage table's `control` field. The argument is one positional lowercase literal: `managed`, `tolerated`, `external`, or `observed`. Omit `@@control` to leave per-table control unset (the framework default applies at runtime).
77
+
78
+ Contract-level default (specifier options bag):
79
+
80
+ - `defaultControlPolicy` on `prismaContract(...)` sets `Contract.defaultControlPolicy` at load time when the interpreted contract does not already define one (source wins when both are present).
81
+
74
82
  ## Public API
75
83
 
76
84
  - `@prisma-next/sql-contract-psl`
77
85
  - `interpretPslDocumentToSqlContract({ document, target, scalarTypeDescriptors, authoringContributions?, controlMutationDefaults?, composedExtensionPacks? })`
78
86
  - `@prisma-next/sql-contract-psl/provider`
79
- - `prismaContract(schemaPath, { output?, target, scalarTypeDescriptors, authoringContributions?, controlMutationDefaults?, composedExtensionPacks? })`
87
+ - `prismaContract(schemaPath, { output?, target, defaultControlPolicy?, scalarTypeDescriptors, authoringContributions?, controlMutationDefaults?, composedExtensionPacks? })`
80
88
  - Provider input is fully preassembled by composition layers (for example `@prisma-next/family-sql/control` helpers).
81
89
 
82
90
  ## Dependencies
package/dist/index.d.mts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Contract } from "@prisma-next/contract/types";
2
2
  import { AuthoringContributions } from "@prisma-next/framework-components/authoring";
3
- import { Namespace } from "@prisma-next/framework-components/ir";
4
3
  import { SqlNamespaceTablesInput } from "@prisma-next/sql-contract/types";
5
4
  import { Result } from "@prisma-next/utils/result";
6
5
  import { ParsePslDocumentResult } from "@prisma-next/psl-parser";
7
6
  import { ControlMutationDefaults, ControlMutationDefaults as ControlMutationDefaults$1, DefaultFunctionLoweringContext, DefaultFunctionLoweringHandler, DefaultFunctionRegistry, DefaultFunctionRegistryEntry, MutationDefaultGeneratorDescriptor } from "@prisma-next/framework-components/control";
8
7
  import { ContractSourceDiagnostics } from "@prisma-next/config/config-types";
9
8
  import { ExtensionPackRef, TargetPackRef } from "@prisma-next/framework-components/components";
9
+ import { Namespace } from "@prisma-next/framework-components/ir";
10
10
 
11
11
  //#region src/psl-column-resolution.d.ts
12
12
  type ColumnDescriptor = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"mappings":";;;;;;;;;;;KAyCY,gBAAA;EAAA,SACD,OAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA,GAAa,MAAM;AAAA;;;UC8Cb,sCAAA;EAAA,SACN,QAAA,EAAU,sBAAA;EAAA,SACV,MAAA,EAAQ,aAAA;EAAA,SACR,qBAAA,EAAuB,WAAA,SAAoB,gBAAA;EAAA,SAC3C,sBAAA;EAAA,SACA,yBAAA,YAAqC,gBAAA;EAAA,SACrC,uBAAA,GAA0B,yBAAA;EAAA,SAC1B,sBAAA,GAAyB,sBAAA;EDrDzB;;;AAAmB;;;;AC8C9B;;ED9CW,SC+DA,eAAA,IAAmB,KAAA,EAAO,uBAAA,KAA4B,SAAA;AAAA;AAAA,iBAwyCjD,iCAAA,CACd,KAAA,EAAO,sCAAA,GACN,MAAA,CAAO,QAAA,EAAU,yBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/psl-column-resolution.ts","../src/interpreter.ts"],"mappings":";;;;;;;;;;;KA0CY,gBAAA;EAAA,SACD,OAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA,GAAa,MAAM;AAAA;;;UCiDb,sCAAA;EAAA,SACN,QAAA,EAAU,sBAAA;EAAA,SACV,MAAA,EAAQ,aAAA;EAAA,SACR,qBAAA,EAAuB,WAAA,SAAoB,gBAAA;EAAA,SAC3C,sBAAA;EAAA,SACA,yBAAA,YAAqC,gBAAA;EAAA,SACrC,uBAAA,GAA0B,yBAAA;EAAA,SAC1B,sBAAA,GAAyB,sBAAA;EDxDzB;;;AAAmB;;;;ACiD9B;;EDjDW,SCkEA,eAAA,IAAmB,KAAA,EAAO,uBAAA,KAA4B,SAAA;AAAA;AAAA,iBAwgDjD,iCAAA,CACd,KAAA,EAAO,sCAAA,GACN,MAAA,CAAO,QAAA,EAAU,yBAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as interpretPslDocumentToSqlContract } from "./interpreter-QE6eZZof.mjs";
1
+ import { t as interpretPslDocumentToSqlContract } from "./interpreter-Rn_Vzvt-.mjs";
2
2
  export { interpretPslDocumentToSqlContract };
@@ -1,8 +1,8 @@
1
1
  import { crossRef } from "@prisma-next/contract/types";
2
2
  import { hasRegisteredFieldNamespace, instantiateAuthoringEntityType, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
3
- import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
4
3
  import { isPostgresEnumStorageEntry } from "@prisma-next/sql-contract/types";
5
4
  import { buildSqlContractFromDefinition } from "@prisma-next/sql-contract-ts/contract-builder";
5
+ import { blindCast } from "@prisma-next/utils/casts";
6
6
  import { ifDefined } from "@prisma-next/utils/defined";
7
7
  import { notOk, ok } from "@prisma-next/utils/result";
8
8
  import { getPositionalArgument, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
@@ -279,6 +279,56 @@ function findDuplicateFieldName(fieldNames) {
279
279
  seen.add(name);
280
280
  }
281
281
  }
282
+ const CONTROL_POLICY_LITERAL_SET = new Set([
283
+ "managed",
284
+ "tolerated",
285
+ "external",
286
+ "observed"
287
+ ]);
288
+ function isControlPolicyLiteral(value) {
289
+ return CONTROL_POLICY_LITERAL_SET.has(value);
290
+ }
291
+ function parseControlPolicyAttribute(input) {
292
+ if (input.attribute.args.filter((arg) => arg.kind === "named").length > 0) {
293
+ input.diagnostics.push({
294
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
295
+ message: "`@@control` does not accept named arguments; pass the policy positionally as `@@control(external)`.",
296
+ sourceId: input.sourceId,
297
+ span: input.attribute.span
298
+ });
299
+ return;
300
+ }
301
+ const positionalArgs = getPositionalArguments(input.attribute);
302
+ if (positionalArgs.length === 0) {
303
+ input.diagnostics.push({
304
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
305
+ message: "`@@control` requires exactly one positional argument: `managed`, `tolerated`, `external`, or `observed`.",
306
+ sourceId: input.sourceId,
307
+ span: input.attribute.span
308
+ });
309
+ return;
310
+ }
311
+ if (positionalArgs.length > 1) {
312
+ input.diagnostics.push({
313
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
314
+ message: `\`@@control\` accepts exactly one positional argument; got ${positionalArgs.length}.`,
315
+ sourceId: input.sourceId,
316
+ span: input.attribute.span
317
+ });
318
+ return;
319
+ }
320
+ const token = unquoteStringLiteral(positionalArgs[0] ?? "").trim();
321
+ if (!isControlPolicyLiteral(token)) {
322
+ input.diagnostics.push({
323
+ code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
324
+ message: `\`@@control\` argument \`${token}\` is not a known policy. Allowed: \`managed\`, \`tolerated\`, \`external\`, \`observed\`.`,
325
+ sourceId: input.sourceId,
326
+ span: input.attribute.span
327
+ });
328
+ return;
329
+ }
330
+ return token;
331
+ }
282
332
  function mapFieldNamesToColumns(input) {
283
333
  const columns = [];
284
334
  for (const fieldName of input.fieldNames) {
@@ -730,7 +780,7 @@ function getAuthoringTypeConstructor(contributions, path) {
730
780
  let current = contributions?.type;
731
781
  for (const segment of path) {
732
782
  if (typeof current !== "object" || current === null || Array.isArray(current)) return;
733
- current = current[segment];
783
+ current = blindCast(current)[segment];
734
784
  }
735
785
  return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
736
786
  }
@@ -749,7 +799,7 @@ function getAuthoringEntity(contributions, path) {
749
799
  let current = contributions?.entityTypes;
750
800
  for (const segment of path) {
751
801
  if (typeof current !== "object" || current === null || Array.isArray(current)) return;
752
- current = current[segment];
802
+ current = blindCast(current)[segment];
753
803
  }
754
804
  return isAuthoringEntityTypeDescriptor(current) ? current : void 0;
755
805
  }
@@ -762,7 +812,7 @@ function getAuthoringFieldPreset(contributions, path) {
762
812
  let current = contributions?.field;
763
813
  for (const segment of path) {
764
814
  if (typeof current !== "object" || current === null || Array.isArray(current)) return;
765
- current = current[segment];
815
+ current = blindCast(current)[segment];
766
816
  }
767
817
  return isAuthoringFieldPresetDescriptor(current) ? current : void 0;
768
818
  }
@@ -1852,9 +1902,6 @@ const UNSPECIFIED_PSL_NAMESPACE_NAME = "__unspecified__";
1852
1902
  * slot empty (which means the late-bound default at the `StorageTable`
1853
1903
  * layer; emitted JSON omits the field).
1854
1904
  */
1855
- function defaultSqlNamespaceIdForTarget(targetId) {
1856
- return targetId === "postgres" ? "public" : UNBOUND_NAMESPACE_ID;
1857
- }
1858
1905
  function resolveNamespaceIdForSqlTarget(input) {
1859
1906
  if (input.targetId !== "postgres") return;
1860
1907
  if (input.bucketName === UNSPECIFIED_PSL_NAMESPACE_NAME) return "public";
@@ -2132,6 +2179,8 @@ function buildModelNodeFromPsl(input) {
2132
2179
  } : void 0;
2133
2180
  const hasInlinePrimaryKey = primaryKey !== void 0;
2134
2181
  let blockPrimaryKeyDeclared = false;
2182
+ let controlPolicyDeclared = false;
2183
+ let controlPolicy;
2135
2184
  const resultBackrelationCandidates = [];
2136
2185
  for (const field of model.fields) {
2137
2186
  if (!field.list || !input.modelNames.has(field.typeName)) continue;
@@ -2198,6 +2247,25 @@ function buildModelNodeFromPsl(input) {
2198
2247
  for (const modelAttribute of model.attributes) {
2199
2248
  if (modelAttribute.name === "map") continue;
2200
2249
  if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
2250
+ if (modelAttribute.name === "control") {
2251
+ if (controlPolicyDeclared) {
2252
+ diagnostics.push({
2253
+ code: "PSL_DUPLICATE_ATTRIBUTE",
2254
+ message: `\`@@control\` declared more than once on model "${model.name}".`,
2255
+ sourceId,
2256
+ span: modelAttribute.span
2257
+ });
2258
+ continue;
2259
+ }
2260
+ controlPolicyDeclared = true;
2261
+ const parsed = parseControlPolicyAttribute({
2262
+ attribute: modelAttribute,
2263
+ sourceId,
2264
+ diagnostics
2265
+ });
2266
+ if (parsed !== void 0) controlPolicy = parsed;
2267
+ continue;
2268
+ }
2201
2269
  const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
2202
2270
  if (modelAttribute.name === "id") {
2203
2271
  if (blockPrimaryKeyDeclared) {
@@ -2520,7 +2588,8 @@ function buildModelNodeFromPsl(input) {
2520
2588
  ...ifDefined("id", primaryKey),
2521
2589
  ...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
2522
2590
  ...indexNodes.length > 0 ? { indexes: indexNodes } : {},
2523
- ...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
2591
+ ...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {},
2592
+ ...ifDefined("control", controlPolicy)
2524
2593
  },
2525
2594
  fkRelationMetadata: resultFkRelationMetadata,
2526
2595
  backrelationCandidates: resultBackrelationCandidates,
@@ -2688,8 +2757,16 @@ function collectPolymorphismDeclarations(models, sourceId, diagnostics) {
2688
2757
  baseDeclarations
2689
2758
  };
2690
2759
  }
2691
- function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, targetId, sourceId, diagnostics) {
2760
+ function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, diagnostics) {
2692
2761
  let patched = models;
2762
+ for (const [baseName, fieldNames] of stiBaseFieldsByBase) {
2763
+ const baseModel = patched[baseName];
2764
+ if (!baseModel || fieldNames.length === 0) continue;
2765
+ patched = {
2766
+ ...patched,
2767
+ [baseName]: stripStorageOnlyDomainFields(baseModel, fieldNames)
2768
+ };
2769
+ }
2693
2770
  for (const [modelName, decl] of discriminatorDeclarations) {
2694
2771
  if (baseDeclarations.has(modelName)) {
2695
2772
  diagnostics.push({
@@ -2771,20 +2848,168 @@ function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations
2771
2848
  const baseMapping = modelMappings.get(baseDecl.baseName);
2772
2849
  const variantMapping = modelMappings.get(variantName);
2773
2850
  const resolvedTable = variantMapping?.model.attributes.some((attr) => attr.name === "map") ?? false ? variantMapping?.tableName : baseMapping?.tableName;
2851
+ const patchedVariant = {
2852
+ ...variantModel,
2853
+ base: crossRef(baseDecl.baseName, modelNamespaceIds.get(baseDecl.baseName) ?? defaultNamespaceId),
2854
+ ...resolvedTable ? { storage: {
2855
+ ...variantModel.storage,
2856
+ table: resolvedTable
2857
+ } } : {}
2858
+ };
2774
2859
  patched = {
2775
2860
  ...patched,
2776
- [variantName]: {
2777
- ...variantModel,
2778
- base: crossRef(baseDecl.baseName, modelNamespaceIds.get(baseDecl.baseName) ?? defaultSqlNamespaceIdForTarget(targetId)),
2779
- ...resolvedTable ? { storage: {
2780
- ...variantModel.storage,
2781
- table: resolvedTable
2782
- } } : {}
2783
- }
2861
+ [variantName]: stripStorageOnlyDomainFields(patchedVariant, syntheticPkFieldsByVariant.get(variantName) ?? [])
2784
2862
  };
2785
2863
  }
2786
2864
  return patched;
2787
2865
  }
2866
+ /**
2867
+ * Multi-table-inheritance variants (`@@base` + their own `@@map`) live in a
2868
+ * separate table from their base. The ORM joins that table to the base on the
2869
+ * shared primary key (`base.id = variant.id`), so the variant storage table
2870
+ * must carry the base PK column even though the variant domain model declares
2871
+ * only its own fields. This enriches each MTI variant's `ModelNode` with that
2872
+ * link column, a primary key on it, and a FK back to the base table.
2873
+ *
2874
+ * The link column is reported back per variant in `syntheticPkFieldsByVariant`
2875
+ * so the domain-model patch can drop it again — keeping the variant's domain
2876
+ * surface thin (its create/read inputs don't gain a redundant `id`) while the
2877
+ * storage table stays joinable. Single-table-inheritance variants (no own
2878
+ * table) are left untouched.
2879
+ */
2880
+ function materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames) {
2881
+ const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
2882
+ const syntheticPkFieldsByVariant = /* @__PURE__ */ new Map();
2883
+ return {
2884
+ modelNodes: modelNodes.map((node) => {
2885
+ const baseDecl = baseDeclarations.get(node.modelName);
2886
+ if (!baseDecl) return node;
2887
+ const baseNode = nodeByModel.get(baseDecl.baseName);
2888
+ if (!baseNode) return node;
2889
+ if (stiVariantNames.has(node.modelName)) return node;
2890
+ const basePrimaryKey = baseNode.id;
2891
+ if (!basePrimaryKey || basePrimaryKey.columns.length === 0) return node;
2892
+ const existingColumns = new Set(node.fields.map((field) => field.columnName));
2893
+ const linkFields = [];
2894
+ for (const pkColumn of basePrimaryKey.columns) {
2895
+ if (existingColumns.has(pkColumn)) continue;
2896
+ const baseField = baseNode.fields.find((field) => "descriptor" in field && field.columnName === pkColumn);
2897
+ if (!baseField) continue;
2898
+ linkFields.push({
2899
+ fieldName: baseField.fieldName,
2900
+ columnName: pkColumn,
2901
+ descriptor: baseField.descriptor,
2902
+ nullable: false
2903
+ });
2904
+ }
2905
+ if (linkFields.length === 0) return node;
2906
+ syntheticPkFieldsByVariant.set(node.modelName, linkFields.map((field) => field.fieldName));
2907
+ const foreignKey = {
2908
+ columns: basePrimaryKey.columns,
2909
+ references: {
2910
+ model: baseNode.modelName,
2911
+ table: baseNode.tableName,
2912
+ columns: basePrimaryKey.columns,
2913
+ ...ifDefined("namespaceId", baseNode.namespaceId)
2914
+ },
2915
+ constraint: true,
2916
+ index: false,
2917
+ onDelete: "cascade"
2918
+ };
2919
+ return {
2920
+ ...node,
2921
+ fields: [...linkFields, ...node.fields],
2922
+ id: { columns: basePrimaryKey.columns },
2923
+ foreignKeys: [...node.foreignKeys ?? [], foreignKey]
2924
+ };
2925
+ }),
2926
+ syntheticPkFieldsByVariant
2927
+ };
2928
+ }
2929
+ /**
2930
+ * Single-table-inheritance variants (`@@base` with no own `@@map`) share the
2931
+ * base table: `resolvePolymorphism` points the variant's `storage.table` at the
2932
+ * base, and the ORM reads variant-declared fields straight off the base table.
2933
+ * For that to validate and round-trip, the base storage table must physically
2934
+ * carry every STI variant's declared columns. This enriches the base
2935
+ * `ModelNode` with those columns.
2936
+ *
2937
+ * The materialised columns are always nullable in storage: the base table hosts
2938
+ * every variant's rows, so a column a variant declares as required is still
2939
+ * NULL on sibling-variant rows. The variant's domain field keeps its declared
2940
+ * nullability — required-in-domain / nullable-in-storage is the intended STI
2941
+ * shape.
2942
+ *
2943
+ * Collisions (two variants declaring the same column, or a variant column name
2944
+ * clashing with a base column) are resolved skip-if-exists here, mirroring the
2945
+ * MTI link guard; surfacing them as diagnostics is tracked separately
2946
+ * (TML-2827).
2947
+ */
2948
+ function materializeStiVariantStorageColumns(modelNodes, baseDeclarations, stiVariantNames) {
2949
+ if (stiVariantNames.size === 0) return {
2950
+ modelNodes: [...modelNodes],
2951
+ stiBaseFieldsByBase: /* @__PURE__ */ new Map()
2952
+ };
2953
+ const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
2954
+ const stiColumnsByBase = /* @__PURE__ */ new Map();
2955
+ for (const variantName of stiVariantNames) {
2956
+ const variantNode = nodeByModel.get(variantName);
2957
+ const baseDecl = baseDeclarations.get(variantName);
2958
+ if (!variantNode || !baseDecl) continue;
2959
+ const baseNode = nodeByModel.get(baseDecl.baseName);
2960
+ if (!baseNode) continue;
2961
+ const baseColumns = new Set(baseNode.fields.map((field) => field.columnName));
2962
+ const claimed = stiColumnsByBase.get(baseDecl.baseName) ?? [];
2963
+ const claimedColumns = new Set(claimed.map((field) => field.columnName));
2964
+ for (const field of variantNode.fields) {
2965
+ if (baseColumns.has(field.columnName) || claimedColumns.has(field.columnName)) continue;
2966
+ claimedColumns.add(field.columnName);
2967
+ claimed.push({
2968
+ ...field,
2969
+ nullable: true
2970
+ });
2971
+ }
2972
+ stiColumnsByBase.set(baseDecl.baseName, claimed);
2973
+ }
2974
+ const stiBaseFieldsByBase = /* @__PURE__ */ new Map();
2975
+ for (const [baseName, columns] of stiColumnsByBase) stiBaseFieldsByBase.set(baseName, columns.map((field) => field.fieldName));
2976
+ return {
2977
+ modelNodes: modelNodes.map((node) => {
2978
+ if (stiVariantNames.has(node.modelName)) return {
2979
+ ...node,
2980
+ sharesBaseTable: true
2981
+ };
2982
+ const stiColumns = stiColumnsByBase.get(node.modelName);
2983
+ if (!stiColumns || stiColumns.length === 0) return node;
2984
+ return {
2985
+ ...node,
2986
+ fields: [...node.fields, ...stiColumns]
2987
+ };
2988
+ }),
2989
+ stiBaseFieldsByBase
2990
+ };
2991
+ }
2992
+ /**
2993
+ * Drop the storage-only link fields (added by
2994
+ * {@link materializeMtiVariantStorageLinks}) from a variant's domain model, so
2995
+ * the domain surface stays thin while the storage table keeps the link column.
2996
+ */
2997
+ function stripStorageOnlyDomainFields(model, fieldNames) {
2998
+ if (fieldNames.length === 0) return model;
2999
+ const fields = { ...model.fields };
3000
+ for (const name of fieldNames) delete fields[name];
3001
+ const storage = blindCast(model.storage);
3002
+ const storageFields = { ...storage.fields };
3003
+ for (const name of fieldNames) delete storageFields[name];
3004
+ return {
3005
+ ...model,
3006
+ fields,
3007
+ storage: {
3008
+ ...storage,
3009
+ fields: storageFields
3010
+ }
3011
+ };
3012
+ }
2788
3013
  function interpretPslDocumentToSqlContract(input) {
2789
3014
  const sourceId = input.document.ast.sourceId;
2790
3015
  if (!input.target) return notOk({
@@ -2929,6 +3154,10 @@ function interpretPslDocumentToSqlContract(input) {
2929
3154
  sourceId
2930
3155
  });
2931
3156
  const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(models, sourceId, diagnostics);
3157
+ const stiVariantNames = /* @__PURE__ */ new Set();
3158
+ for (const variantName of baseDeclarations.keys()) if (!(modelMappings.get(variantName)?.model.attributes.some((attr) => attr.name === "map") ?? false)) stiVariantNames.add(variantName);
3159
+ const { modelNodes: mtiLinkedModelNodes, syntheticPkFieldsByVariant } = materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames);
3160
+ const { modelNodes: stiColumnModelNodes, stiBaseFieldsByBase } = materializeStiVariantStorageColumns(mtiLinkedModelNodes, baseDeclarations, stiVariantNames);
2932
3161
  const valueObjects = buildValueObjects({
2933
3162
  compositeTypes,
2934
3163
  enumTypeDescriptors: allEnumTypeDescriptors,
@@ -2951,7 +3180,7 @@ function interpretPslDocumentToSqlContract(input) {
2951
3180
  ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {},
2952
3181
  ...Object.keys(namespaceEnumStorageTypes).length > 0 ? { namespaceTypes: namespaceEnumStorageTypes } : {},
2953
3182
  ...ifDefined("createNamespace", input.createNamespace),
2954
- models: modelNodes.map((model) => ({
3183
+ models: stiColumnModelNodes.map((model) => ({
2955
3184
  ...model,
2956
3185
  ...modelRelations.has(model.modelName) ? { relations: [...modelRelations.get(model.modelName) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName)) } : {}
2957
3186
  }))
@@ -2963,7 +3192,7 @@ function interpretPslDocumentToSqlContract(input) {
2963
3192
  }
2964
3193
  let patchedModels = patchModelDomainFields(modelsForPatch, modelResolvedFields);
2965
3194
  const polyDiagnostics = [];
2966
- patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, input.target.targetId, sourceId, polyDiagnostics);
3195
+ patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, input.target.defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, polyDiagnostics);
2967
3196
  if (polyDiagnostics.length > 0) return notOk({
2968
3197
  summary: "PSL to SQL contract interpretation failed",
2969
3198
  diagnostics: polyDiagnostics
@@ -2976,11 +3205,11 @@ function interpretPslDocumentToSqlContract(input) {
2976
3205
  domain: { namespaces: Object.fromEntries(Object.entries(contract.domain.namespaces).map(([namespaceId, namespaceSlice]) => [namespaceId, {
2977
3206
  models: Object.fromEntries(Object.entries(namespaceSlice.models).map(([modelName, model]) => [modelName, patchedModels[modelName] ?? model])),
2978
3207
  ...namespaceSlice.valueObjects !== void 0 ? { valueObjects: namespaceSlice.valueObjects } : {},
2979
- ...namespaceId === defaultSqlNamespaceIdForTarget(input.target.targetId) && Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
3208
+ ...namespaceId === input.target.defaultNamespaceId && Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
2980
3209
  }])) }
2981
3210
  });
2982
3211
  }
2983
3212
  //#endregion
2984
3213
  export { interpretPslDocumentToSqlContract as t };
2985
3214
 
2986
- //# sourceMappingURL=interpreter-QE6eZZof.mjs.map
3215
+ //# sourceMappingURL=interpreter-Rn_Vzvt-.mjs.map