@prisma-next/sql-contract-psl 0.12.0-dev.9 → 0.13.0-dev.1
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 +10 -2
- package/dist/index.d.mts +11 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-QE6eZZof.mjs → interpreter-B_KtZusL.mjs} +439 -52
- package/dist/interpreter-B_KtZusL.mjs.map +1 -0
- package/dist/provider.d.mts +5 -0
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +6 -3
- package/dist/provider.mjs.map +1 -1
- package/package.json +14 -14
- package/src/interpreter.ts +552 -52
- package/src/provider.ts +11 -1
- package/src/psl-attribute-parsing.ts +66 -0
- package/src/psl-column-resolution.ts +10 -3
- package/src/psl-field-resolution.ts +28 -3
- package/src/psl-relation-resolution.ts +6 -4
- package/dist/interpreter-QE6eZZof.mjs.map +0 -1
|
@@ -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
|
}
|
|
@@ -1258,6 +1308,10 @@ function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptor
|
|
|
1258
1308
|
}
|
|
1259
1309
|
//#endregion
|
|
1260
1310
|
//#region src/psl-field-resolution.ts
|
|
1311
|
+
const MODEL_COORDINATE_SEPARATOR = "\0";
|
|
1312
|
+
function modelCoordinateKey(namespaceId, modelName) {
|
|
1313
|
+
return `${namespaceId}${MODEL_COORDINATE_SEPARATOR}${modelName}`;
|
|
1314
|
+
}
|
|
1261
1315
|
const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
|
|
1262
1316
|
"id",
|
|
1263
1317
|
"unique",
|
|
@@ -1344,6 +1398,7 @@ function collectResolvedFields(input) {
|
|
|
1344
1398
|
});
|
|
1345
1399
|
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1346
1400
|
if (isModelField && relationAttribute) continue;
|
|
1401
|
+
if (field.typeContractSpaceId !== void 0 && relationAttribute) continue;
|
|
1347
1402
|
const isValueObjectField = compositeTypeNames.has(field.typeName);
|
|
1348
1403
|
const isListField = field.list;
|
|
1349
1404
|
let descriptor;
|
|
@@ -1489,9 +1544,9 @@ function collectResolvedFields(input) {
|
|
|
1489
1544
|
}
|
|
1490
1545
|
return resolvedFields;
|
|
1491
1546
|
}
|
|
1492
|
-
function buildModelMappings(
|
|
1547
|
+
function buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourceId) {
|
|
1493
1548
|
const result = /* @__PURE__ */ new Map();
|
|
1494
|
-
for (const model of
|
|
1549
|
+
for (const { model, namespaceId } of modelEntries) {
|
|
1495
1550
|
const tableName = parseMapName({
|
|
1496
1551
|
attribute: getAttribute(model.attributes, "map"),
|
|
1497
1552
|
defaultValue: lowerFirst(model.name),
|
|
@@ -1512,7 +1567,7 @@ function buildModelMappings(models, diagnostics, sourceId) {
|
|
|
1512
1567
|
});
|
|
1513
1568
|
fieldColumns.set(field.name, columnName);
|
|
1514
1569
|
}
|
|
1515
|
-
result.set(model.name, {
|
|
1570
|
+
result.set(modelCoordinateKey(namespaceId ?? defaultNamespaceId, model.name), {
|
|
1516
1571
|
model,
|
|
1517
1572
|
tableName,
|
|
1518
1573
|
fieldColumns
|
|
@@ -1666,6 +1721,7 @@ function indexFkRelations(input) {
|
|
|
1666
1721
|
fieldName: relation.declaringFieldName,
|
|
1667
1722
|
toModel: relation.targetModelName,
|
|
1668
1723
|
toTable: relation.targetTableName,
|
|
1724
|
+
...ifDefined("toNamespaceId", relation.targetNamespaceId),
|
|
1669
1725
|
cardinality: "N:1",
|
|
1670
1726
|
on: {
|
|
1671
1727
|
parentTable: relation.declaringTableName,
|
|
@@ -1852,9 +1908,6 @@ const UNSPECIFIED_PSL_NAMESPACE_NAME = "__unspecified__";
|
|
|
1852
1908
|
* slot empty (which means the late-bound default at the `StorageTable`
|
|
1853
1909
|
* layer; emitted JSON omits the field).
|
|
1854
1910
|
*/
|
|
1855
|
-
function defaultSqlNamespaceIdForTarget(targetId) {
|
|
1856
|
-
return targetId === "postgres" ? "public" : UNBOUND_NAMESPACE_ID;
|
|
1857
|
-
}
|
|
1858
1911
|
function resolveNamespaceIdForSqlTarget(input) {
|
|
1859
1912
|
if (input.targetId !== "postgres") return;
|
|
1860
1913
|
if (input.bucketName === UNSPECIFIED_PSL_NAMESPACE_NAME) return "public";
|
|
@@ -2132,6 +2185,8 @@ function buildModelNodeFromPsl(input) {
|
|
|
2132
2185
|
} : void 0;
|
|
2133
2186
|
const hasInlinePrimaryKey = primaryKey !== void 0;
|
|
2134
2187
|
let blockPrimaryKeyDeclared = false;
|
|
2188
|
+
let controlPolicyDeclared = false;
|
|
2189
|
+
let controlPolicy;
|
|
2135
2190
|
const resultBackrelationCandidates = [];
|
|
2136
2191
|
for (const field of model.fields) {
|
|
2137
2192
|
if (!field.list || !input.modelNames.has(field.typeName)) continue;
|
|
@@ -2198,6 +2253,25 @@ function buildModelNodeFromPsl(input) {
|
|
|
2198
2253
|
for (const modelAttribute of model.attributes) {
|
|
2199
2254
|
if (modelAttribute.name === "map") continue;
|
|
2200
2255
|
if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
|
|
2256
|
+
if (modelAttribute.name === "control") {
|
|
2257
|
+
if (controlPolicyDeclared) {
|
|
2258
|
+
diagnostics.push({
|
|
2259
|
+
code: "PSL_DUPLICATE_ATTRIBUTE",
|
|
2260
|
+
message: `\`@@control\` declared more than once on model "${model.name}".`,
|
|
2261
|
+
sourceId,
|
|
2262
|
+
span: modelAttribute.span
|
|
2263
|
+
});
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
controlPolicyDeclared = true;
|
|
2267
|
+
const parsed = parseControlPolicyAttribute({
|
|
2268
|
+
attribute: modelAttribute,
|
|
2269
|
+
sourceId,
|
|
2270
|
+
diagnostics
|
|
2271
|
+
});
|
|
2272
|
+
if (parsed !== void 0) controlPolicy = parsed;
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2201
2275
|
const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
|
|
2202
2276
|
if (modelAttribute.name === "id") {
|
|
2203
2277
|
if (blockPrimaryKeyDeclared) {
|
|
@@ -2383,29 +2457,155 @@ function buildModelNodeFromPsl(input) {
|
|
|
2383
2457
|
});
|
|
2384
2458
|
}
|
|
2385
2459
|
const resultFkRelationMetadata = [];
|
|
2460
|
+
const resultCrossSpaceRelations = [];
|
|
2386
2461
|
for (const relationAttribute of relationAttributes) {
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
code: "PSL_INVALID_RELATION_TARGET",
|
|
2393
|
-
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2462
|
+
const { typeName: fieldTypeName, typeNamespaceId: fieldTypeNamespaceId, typeContractSpaceId: fieldTypeContractSpaceId } = relationAttribute.field;
|
|
2463
|
+
if (relationAttribute.field.list) {
|
|
2464
|
+
if (fieldTypeContractSpaceId !== void 0) diagnostics.push({
|
|
2465
|
+
code: "PSL_UNSUPPORTED_CROSS_SPACE_LIST",
|
|
2466
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" is a cross-space list relation (type "${fieldTypeContractSpaceId}:${fieldTypeNamespaceId !== void 0 ? `${fieldTypeNamespaceId}.` : ""}${fieldTypeName}[]"). Cross-space relations must be singular in v0.1 — list cross-space relations are not supported.`,
|
|
2394
2467
|
sourceId,
|
|
2395
2468
|
span: relationAttribute.field.span
|
|
2396
2469
|
});
|
|
2397
2470
|
continue;
|
|
2398
2471
|
}
|
|
2399
|
-
if (
|
|
2400
|
-
|
|
2472
|
+
if (fieldTypeContractSpaceId !== void 0) {
|
|
2473
|
+
const extContractForSpace = input.composedExtensionContracts.get(fieldTypeContractSpaceId);
|
|
2474
|
+
if (extContractForSpace === void 0) {
|
|
2475
|
+
diagnostics.push({
|
|
2476
|
+
code: "PSL_UNKNOWN_CONTRACT_SPACE",
|
|
2477
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references contract space "${fieldTypeContractSpaceId}" which is not declared in extensionPacks. Add "${fieldTypeContractSpaceId}" to extensionPacks in prisma-next.config.ts.`,
|
|
2478
|
+
sourceId,
|
|
2479
|
+
span: relationAttribute.field.span,
|
|
2480
|
+
data: {
|
|
2481
|
+
space: fieldTypeContractSpaceId,
|
|
2482
|
+
suggestedPack: fieldTypeContractSpaceId
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
const parsedRelation = parseRelationAttribute({
|
|
2488
|
+
attribute: relationAttribute.relation,
|
|
2489
|
+
modelName: model.name,
|
|
2490
|
+
fieldName: relationAttribute.field.name,
|
|
2491
|
+
sourceId,
|
|
2492
|
+
diagnostics
|
|
2493
|
+
});
|
|
2494
|
+
if (!parsedRelation) continue;
|
|
2495
|
+
if (!parsedRelation.fields || !parsedRelation.references) {
|
|
2496
|
+
diagnostics.push({
|
|
2497
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2498
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" requires fields and references arguments`,
|
|
2499
|
+
sourceId,
|
|
2500
|
+
span: relationAttribute.relation.span
|
|
2501
|
+
});
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
const localColumns = mapFieldNamesToColumns({
|
|
2505
|
+
modelName: model.name,
|
|
2506
|
+
fieldNames: parsedRelation.fields,
|
|
2507
|
+
mapping,
|
|
2508
|
+
sourceId,
|
|
2509
|
+
diagnostics,
|
|
2510
|
+
span: relationAttribute.relation.span,
|
|
2511
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
2512
|
+
});
|
|
2513
|
+
if (!localColumns) continue;
|
|
2514
|
+
const referencedColumns = parsedRelation.references;
|
|
2515
|
+
if (localColumns.length !== referencedColumns.length) {
|
|
2516
|
+
diagnostics.push({
|
|
2517
|
+
code: "PSL_INVALID_RELATION_ATTRIBUTE",
|
|
2518
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" must provide the same number of fields and references`,
|
|
2519
|
+
sourceId,
|
|
2520
|
+
span: relationAttribute.relation.span
|
|
2521
|
+
});
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
const onDelete = parsedRelation.onDelete ? normalizeReferentialAction({
|
|
2525
|
+
modelName: model.name,
|
|
2526
|
+
fieldName: relationAttribute.field.name,
|
|
2527
|
+
actionName: "onDelete",
|
|
2528
|
+
actionToken: parsedRelation.onDelete,
|
|
2529
|
+
sourceId,
|
|
2530
|
+
span: relationAttribute.field.span,
|
|
2531
|
+
diagnostics
|
|
2532
|
+
}) : void 0;
|
|
2533
|
+
const onUpdate = parsedRelation.onUpdate ? normalizeReferentialAction({
|
|
2534
|
+
modelName: model.name,
|
|
2535
|
+
fieldName: relationAttribute.field.name,
|
|
2536
|
+
actionName: "onUpdate",
|
|
2537
|
+
actionToken: parsedRelation.onUpdate,
|
|
2538
|
+
sourceId,
|
|
2539
|
+
span: relationAttribute.field.span,
|
|
2540
|
+
diagnostics
|
|
2541
|
+
}) : void 0;
|
|
2542
|
+
const crossTargetNamespaceId = fieldTypeNamespaceId ?? "__unbound__";
|
|
2543
|
+
const extContract = extContractForSpace;
|
|
2544
|
+
const resolvedTable = extContract.domain.namespaces[crossTargetNamespaceId]?.models[fieldTypeName]?.storage["table"];
|
|
2545
|
+
if (typeof resolvedTable !== "string") {
|
|
2546
|
+
const availableModels = Object.keys(extContract.domain.namespaces[crossTargetNamespaceId]?.models ?? {}).join(", ") || "(none)";
|
|
2401
2547
|
diagnostics.push({
|
|
2402
|
-
code: "
|
|
2403
|
-
message: `Relation field "${model.name}.${relationAttribute.field.name}" references
|
|
2548
|
+
code: "PSL_UNKNOWN_CROSS_SPACE_TARGET",
|
|
2549
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references model "${fieldTypeName}" in namespace "${crossTargetNamespaceId}" of space "${fieldTypeContractSpaceId}", but that model was not found in the extension contract. Available models: ${availableModels}`,
|
|
2404
2550
|
sourceId,
|
|
2405
|
-
span: relationAttribute.field.span
|
|
2551
|
+
span: relationAttribute.field.span,
|
|
2552
|
+
data: {
|
|
2553
|
+
space: fieldTypeContractSpaceId,
|
|
2554
|
+
namespace: crossTargetNamespaceId,
|
|
2555
|
+
model: fieldTypeName
|
|
2556
|
+
}
|
|
2406
2557
|
});
|
|
2407
2558
|
continue;
|
|
2408
2559
|
}
|
|
2560
|
+
const crossTargetTableName = resolvedTable;
|
|
2561
|
+
foreignKeyNodes.push({
|
|
2562
|
+
columns: localColumns,
|
|
2563
|
+
references: {
|
|
2564
|
+
model: fieldTypeName,
|
|
2565
|
+
table: crossTargetTableName,
|
|
2566
|
+
columns: referencedColumns,
|
|
2567
|
+
namespaceId: crossTargetNamespaceId,
|
|
2568
|
+
spaceId: fieldTypeContractSpaceId
|
|
2569
|
+
},
|
|
2570
|
+
...ifDefined("name", parsedRelation.constraintName),
|
|
2571
|
+
...ifDefined("onDelete", onDelete),
|
|
2572
|
+
...ifDefined("onUpdate", onUpdate)
|
|
2573
|
+
});
|
|
2574
|
+
resultCrossSpaceRelations.push({
|
|
2575
|
+
fieldName: relationAttribute.field.name,
|
|
2576
|
+
toModel: fieldTypeName,
|
|
2577
|
+
toTable: crossTargetTableName,
|
|
2578
|
+
cardinality: "N:1",
|
|
2579
|
+
spaceId: fieldTypeContractSpaceId,
|
|
2580
|
+
namespaceId: crossTargetNamespaceId,
|
|
2581
|
+
on: {
|
|
2582
|
+
parentTable: tableName,
|
|
2583
|
+
parentColumns: localColumns,
|
|
2584
|
+
childTable: crossTargetTableName,
|
|
2585
|
+
childColumns: referencedColumns
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
const qualifiedTypeName = fieldTypeNamespaceId ? `${fieldTypeNamespaceId}.${fieldTypeName}` : fieldTypeName;
|
|
2591
|
+
if (!input.modelNames.has(fieldTypeName)) {
|
|
2592
|
+
diagnostics.push({
|
|
2593
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
2594
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2595
|
+
sourceId,
|
|
2596
|
+
span: relationAttribute.field.span
|
|
2597
|
+
});
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
const normalizedQualifier = fieldTypeNamespaceId === void 0 ? void 0 : fieldTypeNamespaceId === "unbound" ? "__unbound__" : fieldTypeNamespaceId;
|
|
2601
|
+
if (normalizedQualifier !== void 0 && !input.modelMappingsByCoordinate.has(modelCoordinateKey(normalizedQualifier, fieldTypeName))) {
|
|
2602
|
+
diagnostics.push({
|
|
2603
|
+
code: "PSL_INVALID_RELATION_TARGET",
|
|
2604
|
+
message: `Relation field "${model.name}.${relationAttribute.field.name}" references unknown model "${qualifiedTypeName}"`,
|
|
2605
|
+
sourceId,
|
|
2606
|
+
span: relationAttribute.field.span
|
|
2607
|
+
});
|
|
2608
|
+
continue;
|
|
2409
2609
|
}
|
|
2410
2610
|
const parsedRelation = parseRelationAttribute({
|
|
2411
2611
|
attribute: relationAttribute.relation,
|
|
@@ -2424,7 +2624,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2424
2624
|
});
|
|
2425
2625
|
continue;
|
|
2426
2626
|
}
|
|
2427
|
-
const targetMapping = input.modelMappings.get(fieldTypeName);
|
|
2627
|
+
const targetMapping = normalizedQualifier !== void 0 ? input.modelMappingsByCoordinate.get(modelCoordinateKey(normalizedQualifier, fieldTypeName)) : input.modelMappings.get(fieldTypeName);
|
|
2428
2628
|
if (!targetMapping) {
|
|
2429
2629
|
diagnostics.push({
|
|
2430
2630
|
code: "PSL_INVALID_RELATION_TARGET",
|
|
@@ -2481,7 +2681,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2481
2681
|
span: relationAttribute.field.span,
|
|
2482
2682
|
diagnostics
|
|
2483
2683
|
}) : void 0;
|
|
2484
|
-
const targetNamespaceId = input.modelNamespaceIds.get(targetMapping.model.name);
|
|
2684
|
+
const targetNamespaceId = normalizedQualifier !== void 0 ? normalizedQualifier : input.modelNamespaceIds.get(targetMapping.model.name);
|
|
2485
2685
|
foreignKeyNodes.push({
|
|
2486
2686
|
columns: localColumns,
|
|
2487
2687
|
references: {
|
|
@@ -2500,6 +2700,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2500
2700
|
declaringTableName: tableName,
|
|
2501
2701
|
targetModelName: targetMapping.model.name,
|
|
2502
2702
|
targetTableName: targetMapping.tableName,
|
|
2703
|
+
...ifDefined("targetNamespaceId", targetNamespaceId),
|
|
2503
2704
|
...ifDefined("relationName", parsedRelation.relationName),
|
|
2504
2705
|
localColumns,
|
|
2505
2706
|
referencedColumns
|
|
@@ -2520,9 +2721,11 @@ function buildModelNodeFromPsl(input) {
|
|
|
2520
2721
|
...ifDefined("id", primaryKey),
|
|
2521
2722
|
...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
|
|
2522
2723
|
...indexNodes.length > 0 ? { indexes: indexNodes } : {},
|
|
2523
|
-
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
|
|
2724
|
+
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {},
|
|
2725
|
+
...ifDefined("control", controlPolicy)
|
|
2524
2726
|
},
|
|
2525
2727
|
fkRelationMetadata: resultFkRelationMetadata,
|
|
2728
|
+
crossSpaceRelations: resultCrossSpaceRelations,
|
|
2526
2729
|
backrelationCandidates: resultBackrelationCandidates,
|
|
2527
2730
|
resolvedFields
|
|
2528
2731
|
};
|
|
@@ -2688,8 +2891,18 @@ function collectPolymorphismDeclarations(models, sourceId, diagnostics) {
|
|
|
2688
2891
|
baseDeclarations
|
|
2689
2892
|
};
|
|
2690
2893
|
}
|
|
2691
|
-
function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds,
|
|
2894
|
+
function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, diagnostics) {
|
|
2692
2895
|
let patched = models;
|
|
2896
|
+
const coordinateFor = (modelName) => modelCoordinateKey(modelNamespaceIds.get(modelName) ?? defaultNamespaceId, modelName);
|
|
2897
|
+
for (const [baseName, fieldNames] of stiBaseFieldsByBase) {
|
|
2898
|
+
const baseKey = coordinateFor(baseName);
|
|
2899
|
+
const baseModel = patched[baseKey];
|
|
2900
|
+
if (!baseModel || fieldNames.length === 0) continue;
|
|
2901
|
+
patched = {
|
|
2902
|
+
...patched,
|
|
2903
|
+
[baseKey]: stripStorageOnlyDomainFields(baseModel, fieldNames)
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2693
2906
|
for (const [modelName, decl] of discriminatorDeclarations) {
|
|
2694
2907
|
if (baseDeclarations.has(modelName)) {
|
|
2695
2908
|
diagnostics.push({
|
|
@@ -2700,7 +2913,7 @@ function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations
|
|
|
2700
2913
|
});
|
|
2701
2914
|
continue;
|
|
2702
2915
|
}
|
|
2703
|
-
const model = patched[modelName];
|
|
2916
|
+
const model = patched[coordinateFor(modelName)];
|
|
2704
2917
|
if (!model) continue;
|
|
2705
2918
|
if (!Object.hasOwn(model.fields, decl.fieldName)) {
|
|
2706
2919
|
diagnostics.push({
|
|
@@ -2739,7 +2952,7 @@ function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations
|
|
|
2739
2952
|
}
|
|
2740
2953
|
patched = {
|
|
2741
2954
|
...patched,
|
|
2742
|
-
[modelName]: {
|
|
2955
|
+
[coordinateFor(modelName)]: {
|
|
2743
2956
|
...model,
|
|
2744
2957
|
discriminator: { field: decl.fieldName },
|
|
2745
2958
|
variants
|
|
@@ -2766,25 +2979,173 @@ function resolvePolymorphism(models, discriminatorDeclarations, baseDeclarations
|
|
|
2766
2979
|
continue;
|
|
2767
2980
|
}
|
|
2768
2981
|
if (discriminatorDeclarations.has(variantName)) continue;
|
|
2769
|
-
const variantModel = patched[variantName];
|
|
2982
|
+
const variantModel = patched[coordinateFor(variantName)];
|
|
2770
2983
|
if (!variantModel) continue;
|
|
2771
2984
|
const baseMapping = modelMappings.get(baseDecl.baseName);
|
|
2772
2985
|
const variantMapping = modelMappings.get(variantName);
|
|
2773
2986
|
const resolvedTable = variantMapping?.model.attributes.some((attr) => attr.name === "map") ?? false ? variantMapping?.tableName : baseMapping?.tableName;
|
|
2987
|
+
const patchedVariant = {
|
|
2988
|
+
...variantModel,
|
|
2989
|
+
base: crossRef(baseDecl.baseName, modelNamespaceIds.get(baseDecl.baseName) ?? defaultNamespaceId),
|
|
2990
|
+
...resolvedTable ? { storage: {
|
|
2991
|
+
...variantModel.storage,
|
|
2992
|
+
table: resolvedTable
|
|
2993
|
+
} } : {}
|
|
2994
|
+
};
|
|
2774
2995
|
patched = {
|
|
2775
2996
|
...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
|
-
}
|
|
2997
|
+
[coordinateFor(variantName)]: stripStorageOnlyDomainFields(patchedVariant, syntheticPkFieldsByVariant.get(variantName) ?? [])
|
|
2784
2998
|
};
|
|
2785
2999
|
}
|
|
2786
3000
|
return patched;
|
|
2787
3001
|
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Multi-table-inheritance variants (`@@base` + their own `@@map`) live in a
|
|
3004
|
+
* separate table from their base. The ORM joins that table to the base on the
|
|
3005
|
+
* shared primary key (`base.id = variant.id`), so the variant storage table
|
|
3006
|
+
* must carry the base PK column even though the variant domain model declares
|
|
3007
|
+
* only its own fields. This enriches each MTI variant's `ModelNode` with that
|
|
3008
|
+
* link column, a primary key on it, and a FK back to the base table.
|
|
3009
|
+
*
|
|
3010
|
+
* The link column is reported back per variant in `syntheticPkFieldsByVariant`
|
|
3011
|
+
* so the domain-model patch can drop it again — keeping the variant's domain
|
|
3012
|
+
* surface thin (its create/read inputs don't gain a redundant `id`) while the
|
|
3013
|
+
* storage table stays joinable. Single-table-inheritance variants (no own
|
|
3014
|
+
* table) are left untouched.
|
|
3015
|
+
*/
|
|
3016
|
+
function materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames) {
|
|
3017
|
+
const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
|
|
3018
|
+
const syntheticPkFieldsByVariant = /* @__PURE__ */ new Map();
|
|
3019
|
+
return {
|
|
3020
|
+
modelNodes: modelNodes.map((node) => {
|
|
3021
|
+
const baseDecl = baseDeclarations.get(node.modelName);
|
|
3022
|
+
if (!baseDecl) return node;
|
|
3023
|
+
const baseNode = nodeByModel.get(baseDecl.baseName);
|
|
3024
|
+
if (!baseNode) return node;
|
|
3025
|
+
if (stiVariantNames.has(node.modelName)) return node;
|
|
3026
|
+
const basePrimaryKey = baseNode.id;
|
|
3027
|
+
if (!basePrimaryKey || basePrimaryKey.columns.length === 0) return node;
|
|
3028
|
+
const existingColumns = new Set(node.fields.map((field) => field.columnName));
|
|
3029
|
+
const linkFields = [];
|
|
3030
|
+
for (const pkColumn of basePrimaryKey.columns) {
|
|
3031
|
+
if (existingColumns.has(pkColumn)) continue;
|
|
3032
|
+
const baseField = baseNode.fields.find((field) => "descriptor" in field && field.columnName === pkColumn);
|
|
3033
|
+
if (!baseField) continue;
|
|
3034
|
+
linkFields.push({
|
|
3035
|
+
fieldName: baseField.fieldName,
|
|
3036
|
+
columnName: pkColumn,
|
|
3037
|
+
descriptor: baseField.descriptor,
|
|
3038
|
+
nullable: false
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
if (linkFields.length === 0) return node;
|
|
3042
|
+
syntheticPkFieldsByVariant.set(node.modelName, linkFields.map((field) => field.fieldName));
|
|
3043
|
+
const foreignKey = {
|
|
3044
|
+
columns: basePrimaryKey.columns,
|
|
3045
|
+
references: {
|
|
3046
|
+
model: baseNode.modelName,
|
|
3047
|
+
table: baseNode.tableName,
|
|
3048
|
+
columns: basePrimaryKey.columns,
|
|
3049
|
+
...ifDefined("namespaceId", baseNode.namespaceId)
|
|
3050
|
+
},
|
|
3051
|
+
constraint: true,
|
|
3052
|
+
index: false,
|
|
3053
|
+
onDelete: "cascade"
|
|
3054
|
+
};
|
|
3055
|
+
return {
|
|
3056
|
+
...node,
|
|
3057
|
+
fields: [...linkFields, ...node.fields],
|
|
3058
|
+
id: { columns: basePrimaryKey.columns },
|
|
3059
|
+
foreignKeys: [...node.foreignKeys ?? [], foreignKey]
|
|
3060
|
+
};
|
|
3061
|
+
}),
|
|
3062
|
+
syntheticPkFieldsByVariant
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Single-table-inheritance variants (`@@base` with no own `@@map`) share the
|
|
3067
|
+
* base table: `resolvePolymorphism` points the variant's `storage.table` at the
|
|
3068
|
+
* base, and the ORM reads variant-declared fields straight off the base table.
|
|
3069
|
+
* For that to validate and round-trip, the base storage table must physically
|
|
3070
|
+
* carry every STI variant's declared columns. This enriches the base
|
|
3071
|
+
* `ModelNode` with those columns.
|
|
3072
|
+
*
|
|
3073
|
+
* The materialised columns are always nullable in storage: the base table hosts
|
|
3074
|
+
* every variant's rows, so a column a variant declares as required is still
|
|
3075
|
+
* NULL on sibling-variant rows. The variant's domain field keeps its declared
|
|
3076
|
+
* nullability — required-in-domain / nullable-in-storage is the intended STI
|
|
3077
|
+
* shape.
|
|
3078
|
+
*
|
|
3079
|
+
* Collisions (two variants declaring the same column, or a variant column name
|
|
3080
|
+
* clashing with a base column) are resolved skip-if-exists here, mirroring the
|
|
3081
|
+
* MTI link guard; surfacing them as diagnostics is tracked separately
|
|
3082
|
+
* (TML-2827).
|
|
3083
|
+
*/
|
|
3084
|
+
function materializeStiVariantStorageColumns(modelNodes, baseDeclarations, stiVariantNames) {
|
|
3085
|
+
if (stiVariantNames.size === 0) return {
|
|
3086
|
+
modelNodes: [...modelNodes],
|
|
3087
|
+
stiBaseFieldsByBase: /* @__PURE__ */ new Map()
|
|
3088
|
+
};
|
|
3089
|
+
const nodeByModel = new Map(modelNodes.map((node) => [node.modelName, node]));
|
|
3090
|
+
const stiColumnsByBase = /* @__PURE__ */ new Map();
|
|
3091
|
+
for (const variantName of stiVariantNames) {
|
|
3092
|
+
const variantNode = nodeByModel.get(variantName);
|
|
3093
|
+
const baseDecl = baseDeclarations.get(variantName);
|
|
3094
|
+
if (!variantNode || !baseDecl) continue;
|
|
3095
|
+
const baseNode = nodeByModel.get(baseDecl.baseName);
|
|
3096
|
+
if (!baseNode) continue;
|
|
3097
|
+
const baseColumns = new Set(baseNode.fields.map((field) => field.columnName));
|
|
3098
|
+
const claimed = stiColumnsByBase.get(baseDecl.baseName) ?? [];
|
|
3099
|
+
const claimedColumns = new Set(claimed.map((field) => field.columnName));
|
|
3100
|
+
for (const field of variantNode.fields) {
|
|
3101
|
+
if (baseColumns.has(field.columnName) || claimedColumns.has(field.columnName)) continue;
|
|
3102
|
+
claimedColumns.add(field.columnName);
|
|
3103
|
+
claimed.push({
|
|
3104
|
+
...field,
|
|
3105
|
+
nullable: true
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
stiColumnsByBase.set(baseDecl.baseName, claimed);
|
|
3109
|
+
}
|
|
3110
|
+
const stiBaseFieldsByBase = /* @__PURE__ */ new Map();
|
|
3111
|
+
for (const [baseName, columns] of stiColumnsByBase) stiBaseFieldsByBase.set(baseName, columns.map((field) => field.fieldName));
|
|
3112
|
+
return {
|
|
3113
|
+
modelNodes: modelNodes.map((node) => {
|
|
3114
|
+
if (stiVariantNames.has(node.modelName)) return {
|
|
3115
|
+
...node,
|
|
3116
|
+
sharesBaseTable: true
|
|
3117
|
+
};
|
|
3118
|
+
const stiColumns = stiColumnsByBase.get(node.modelName);
|
|
3119
|
+
if (!stiColumns || stiColumns.length === 0) return node;
|
|
3120
|
+
return {
|
|
3121
|
+
...node,
|
|
3122
|
+
fields: [...node.fields, ...stiColumns]
|
|
3123
|
+
};
|
|
3124
|
+
}),
|
|
3125
|
+
stiBaseFieldsByBase
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
/**
|
|
3129
|
+
* Drop the storage-only link fields (added by
|
|
3130
|
+
* {@link materializeMtiVariantStorageLinks}) from a variant's domain model, so
|
|
3131
|
+
* the domain surface stays thin while the storage table keeps the link column.
|
|
3132
|
+
*/
|
|
3133
|
+
function stripStorageOnlyDomainFields(model, fieldNames) {
|
|
3134
|
+
if (fieldNames.length === 0) return model;
|
|
3135
|
+
const fields = { ...model.fields };
|
|
3136
|
+
for (const name of fieldNames) delete fields[name];
|
|
3137
|
+
const storage = blindCast(model.storage);
|
|
3138
|
+
const storageFields = { ...storage.fields };
|
|
3139
|
+
for (const name of fieldNames) delete storageFields[name];
|
|
3140
|
+
return {
|
|
3141
|
+
...model,
|
|
3142
|
+
fields,
|
|
3143
|
+
storage: {
|
|
3144
|
+
...storage,
|
|
3145
|
+
fields: storageFields
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
2788
3149
|
function interpretPslDocumentToSqlContract(input) {
|
|
2789
3150
|
const sourceId = input.document.ast.sourceId;
|
|
2790
3151
|
if (!input.target) return notOk({
|
|
@@ -2811,6 +3172,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2811
3172
|
diagnostics
|
|
2812
3173
|
});
|
|
2813
3174
|
const models = [];
|
|
3175
|
+
const modelEntries = [];
|
|
2814
3176
|
const modelNamespaceIds = /* @__PURE__ */ new Map();
|
|
2815
3177
|
for (const namespace of input.document.ast.namespaces) {
|
|
2816
3178
|
const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
|
|
@@ -2819,9 +3181,14 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2819
3181
|
});
|
|
2820
3182
|
for (const model of namespace.models) {
|
|
2821
3183
|
models.push(model);
|
|
3184
|
+
modelEntries.push({
|
|
3185
|
+
model,
|
|
3186
|
+
namespaceId: resolvedNamespaceId
|
|
3187
|
+
});
|
|
2822
3188
|
if (resolvedNamespaceId !== void 0) modelNamespaceIds.set(model.name, resolvedNamespaceId);
|
|
2823
3189
|
}
|
|
2824
3190
|
}
|
|
3191
|
+
const defaultNamespaceId = input.target.defaultNamespaceId;
|
|
2825
3192
|
const topLevelEnums = input.document.ast.namespaces.filter((ns) => ns.name === UNSPECIFIED_PSL_NAMESPACE_NAME).flatMap((ns) => ns.enums);
|
|
2826
3193
|
const namedNamespaceEnumsByNsId = /* @__PURE__ */ new Map();
|
|
2827
3194
|
for (const ns of input.document.ast.namespaces) {
|
|
@@ -2838,6 +3205,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2838
3205
|
const modelNames = new Set(models.map((model) => model.name));
|
|
2839
3206
|
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
2840
3207
|
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
3208
|
+
const composedExtensionContracts = input.composedExtensionContracts;
|
|
2841
3209
|
const defaultFunctionRegistry = input.controlMutationDefaults?.defaultFunctionRegistry ?? /* @__PURE__ */ new Map();
|
|
2842
3210
|
const generatorDescriptors = input.controlMutationDefaults?.generatorDescriptors ?? [];
|
|
2843
3211
|
const generatorDescriptorById = /* @__PURE__ */ new Map();
|
|
@@ -2884,23 +3252,29 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2884
3252
|
...enumResult.storageTypes,
|
|
2885
3253
|
...namedTypeResult.storageTypes
|
|
2886
3254
|
};
|
|
2887
|
-
const
|
|
3255
|
+
const modelMappingsByCoordinate = buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourceId);
|
|
3256
|
+
const modelMappings = /* @__PURE__ */ new Map();
|
|
3257
|
+
for (const mapping of modelMappingsByCoordinate.values()) modelMappings.set(mapping.model.name, mapping);
|
|
2888
3258
|
const modelNodes = [];
|
|
2889
3259
|
const fkRelationMetadata = [];
|
|
2890
3260
|
const backrelationCandidates = [];
|
|
2891
3261
|
const modelResolvedFields = /* @__PURE__ */ new Map();
|
|
2892
|
-
|
|
2893
|
-
|
|
3262
|
+
const crossSpaceRelationsByModel = /* @__PURE__ */ new Map();
|
|
3263
|
+
for (const { model, namespaceId } of modelEntries) {
|
|
3264
|
+
const coordinate = modelCoordinateKey(namespaceId ?? defaultNamespaceId, model.name);
|
|
3265
|
+
const mapping = modelMappingsByCoordinate.get(coordinate);
|
|
2894
3266
|
if (!mapping) continue;
|
|
2895
3267
|
const result = buildModelNodeFromPsl({
|
|
2896
3268
|
model,
|
|
2897
3269
|
mapping,
|
|
2898
3270
|
modelMappings,
|
|
3271
|
+
modelMappingsByCoordinate,
|
|
2899
3272
|
modelNames,
|
|
2900
3273
|
compositeTypeNames,
|
|
2901
3274
|
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
2902
3275
|
namedTypeDescriptors: namedTypeResult.namedTypeDescriptors,
|
|
2903
3276
|
composedExtensions,
|
|
3277
|
+
composedExtensionContracts,
|
|
2904
3278
|
familyId: input.target.familyId,
|
|
2905
3279
|
targetId: input.target.targetId,
|
|
2906
3280
|
authoringContributions: input.authoringContributions,
|
|
@@ -2911,14 +3285,17 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2911
3285
|
diagnostics,
|
|
2912
3286
|
modelNamespaceIds
|
|
2913
3287
|
});
|
|
2914
|
-
|
|
2915
|
-
modelNodes.push(resolvedNamespaceId !== void 0 ? {
|
|
3288
|
+
modelNodes.push(namespaceId !== void 0 ? {
|
|
2916
3289
|
...result.modelNode,
|
|
2917
|
-
namespaceId
|
|
3290
|
+
namespaceId
|
|
2918
3291
|
} : result.modelNode);
|
|
2919
3292
|
fkRelationMetadata.push(...result.fkRelationMetadata);
|
|
2920
3293
|
backrelationCandidates.push(...result.backrelationCandidates);
|
|
2921
|
-
modelResolvedFields.set(
|
|
3294
|
+
modelResolvedFields.set(coordinate, result.resolvedFields);
|
|
3295
|
+
if (result.crossSpaceRelations.length > 0) {
|
|
3296
|
+
const existing = crossSpaceRelationsByModel.get(model.name) ?? [];
|
|
3297
|
+
crossSpaceRelationsByModel.set(model.name, [...existing, ...result.crossSpaceRelations]);
|
|
3298
|
+
}
|
|
2922
3299
|
}
|
|
2923
3300
|
const { modelRelations, fkRelationsByPair } = indexFkRelations({ fkRelationMetadata });
|
|
2924
3301
|
applyBackrelationCandidates({
|
|
@@ -2928,7 +3305,16 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2928
3305
|
diagnostics,
|
|
2929
3306
|
sourceId
|
|
2930
3307
|
});
|
|
3308
|
+
for (const [modelName, relations] of crossSpaceRelationsByModel) {
|
|
3309
|
+
const existing = modelRelations.get(modelName);
|
|
3310
|
+
if (existing) existing.push(...relations);
|
|
3311
|
+
else modelRelations.set(modelName, [...relations]);
|
|
3312
|
+
}
|
|
2931
3313
|
const { discriminatorDeclarations, baseDeclarations } = collectPolymorphismDeclarations(models, sourceId, diagnostics);
|
|
3314
|
+
const stiVariantNames = /* @__PURE__ */ new Set();
|
|
3315
|
+
for (const variantName of baseDeclarations.keys()) if (!(modelMappings.get(variantName)?.model.attributes.some((attr) => attr.name === "map") ?? false)) stiVariantNames.add(variantName);
|
|
3316
|
+
const { modelNodes: mtiLinkedModelNodes, syntheticPkFieldsByVariant } = materializeMtiVariantStorageLinks(modelNodes, baseDeclarations, stiVariantNames);
|
|
3317
|
+
const { modelNodes: stiColumnModelNodes, stiBaseFieldsByBase } = materializeStiVariantStorageColumns(mtiLinkedModelNodes, baseDeclarations, stiVariantNames);
|
|
2932
3318
|
const valueObjects = buildValueObjects({
|
|
2933
3319
|
compositeTypes,
|
|
2934
3320
|
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
@@ -2951,19 +3337,20 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2951
3337
|
...Object.keys(storageTypes).length > 0 ? { storageTypes } : {},
|
|
2952
3338
|
...Object.keys(namespaceEnumStorageTypes).length > 0 ? { namespaceTypes: namespaceEnumStorageTypes } : {},
|
|
2953
3339
|
...ifDefined("createNamespace", input.createNamespace),
|
|
2954
|
-
models:
|
|
3340
|
+
models: stiColumnModelNodes.map((model) => ({
|
|
2955
3341
|
...model,
|
|
2956
3342
|
...modelRelations.has(model.modelName) ? { relations: [...modelRelations.get(model.modelName) ?? []].sort((left, right) => compareStrings(left.fieldName, right.fieldName)) } : {}
|
|
2957
3343
|
}))
|
|
2958
3344
|
});
|
|
2959
3345
|
const modelsForPatch = {};
|
|
2960
|
-
for (const namespaceSlice of Object.
|
|
2961
|
-
|
|
2962
|
-
modelsForPatch
|
|
3346
|
+
for (const [namespaceId, namespaceSlice] of Object.entries(contract.domain.namespaces)) for (const [modelName, model] of Object.entries(namespaceSlice.models)) {
|
|
3347
|
+
const coordinate = modelCoordinateKey(namespaceId, modelName);
|
|
3348
|
+
if (Object.hasOwn(modelsForPatch, coordinate)) throw new Error(`duplicate model "${namespaceId}.${modelName}" during PSL interpretation`);
|
|
3349
|
+
modelsForPatch[coordinate] = model;
|
|
2963
3350
|
}
|
|
2964
3351
|
let patchedModels = patchModelDomainFields(modelsForPatch, modelResolvedFields);
|
|
2965
3352
|
const polyDiagnostics = [];
|
|
2966
|
-
patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, input.target.
|
|
3353
|
+
patchedModels = resolvePolymorphism(patchedModels, discriminatorDeclarations, baseDeclarations, modelNames, modelMappings, modelNamespaceIds, input.target.defaultNamespaceId, syntheticPkFieldsByVariant, stiBaseFieldsByBase, sourceId, polyDiagnostics);
|
|
2967
3354
|
if (polyDiagnostics.length > 0) return notOk({
|
|
2968
3355
|
summary: "PSL to SQL contract interpretation failed",
|
|
2969
3356
|
diagnostics: polyDiagnostics
|
|
@@ -2974,13 +3361,13 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2974
3361
|
...contract,
|
|
2975
3362
|
roots: filteredRoots,
|
|
2976
3363
|
domain: { namespaces: Object.fromEntries(Object.entries(contract.domain.namespaces).map(([namespaceId, namespaceSlice]) => [namespaceId, {
|
|
2977
|
-
models: Object.fromEntries(Object.entries(namespaceSlice.models).map(([modelName, model]) => [modelName, patchedModels[modelName] ?? model])),
|
|
3364
|
+
models: Object.fromEntries(Object.entries(namespaceSlice.models).map(([modelName, model]) => [modelName, patchedModels[modelCoordinateKey(namespaceId, modelName)] ?? model])),
|
|
2978
3365
|
...namespaceSlice.valueObjects !== void 0 ? { valueObjects: namespaceSlice.valueObjects } : {},
|
|
2979
|
-
...namespaceId ===
|
|
3366
|
+
...namespaceId === input.target.defaultNamespaceId && Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
|
|
2980
3367
|
}])) }
|
|
2981
3368
|
});
|
|
2982
3369
|
}
|
|
2983
3370
|
//#endregion
|
|
2984
3371
|
export { interpretPslDocumentToSqlContract as t };
|
|
2985
3372
|
|
|
2986
|
-
//# sourceMappingURL=interpreter-
|
|
3373
|
+
//# sourceMappingURL=interpreter-B_KtZusL.mjs.map
|