@prisma-next/sql-contract-psl 0.14.0-dev.1 → 0.14.0-dev.11
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 +11 -7
- package/dist/index.d.mts +7 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{interpreter-B0BsCLKT.mjs → interpreter-CygvamTk.mjs} +435 -232
- package/dist/interpreter-CygvamTk.mjs.map +1 -0
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +22 -7
- package/dist/provider.mjs.map +1 -1
- package/package.json +12 -12
- package/src/interpreter.ts +151 -323
- package/src/provider.ts +38 -9
- package/src/psl-attribute-parsing.ts +18 -14
- package/src/psl-authoring-arguments.ts +2 -2
- package/src/psl-column-resolution.ts +17 -15
- package/src/psl-field-resolution.ts +28 -20
- package/src/psl-named-type-resolution.ts +250 -0
- package/src/psl-relation-resolution.ts +250 -11
- package/dist/interpreter-B0BsCLKT.mjs.map +0 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { crossRef } from "@prisma-next/contract/types";
|
|
2
|
-
import { hasRegisteredFieldNamespace, instantiateAuthoringEntityType, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
3
|
-
import {
|
|
2
|
+
import { hasRegisteredFieldNamespace, instantiateAuthoringEntityType, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringPslBlockDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
3
|
+
import { keywordPslSpan, nodePslSpan, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
|
|
4
4
|
import { buildSqlContractFromDefinition } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
5
|
+
import { assertDefined, invariant } from "@prisma-next/utils/assertions";
|
|
5
6
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
6
7
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
7
8
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
8
|
-
import { getPositionalArgument, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
|
|
9
|
-
import { assertDefined, invariant } from "@prisma-next/utils/assertions";
|
|
10
9
|
//#region src/psl-attribute-parsing.ts
|
|
10
|
+
function getPositionalArgument(attribute, index = 0) {
|
|
11
|
+
return attribute.args.filter((arg) => arg.kind === "positional")[index]?.value;
|
|
12
|
+
}
|
|
11
13
|
function lowerFirst(value) {
|
|
12
14
|
if (value.length === 0) return value;
|
|
13
15
|
return value[0]?.toLowerCase() + value.slice(1);
|
|
@@ -931,7 +933,7 @@ function resolvePslTypeConstructorDescriptor(input) {
|
|
|
931
933
|
*
|
|
932
934
|
* Symmetric with `instantiatePslTypeConstructor` but richer: a field preset can contribute `default`, `executionDefaults`, `id`, `unique`, and `nullable` in addition to the storage-type triple. PSL → typed-args coercion happens here (via `mapPslHelperArgs`) so that `instantiateAuthoringFieldPreset` itself stays typed-input-only and TS keeps its zero-runtime-validation cost.
|
|
933
935
|
*/
|
|
934
|
-
function
|
|
936
|
+
function instantiateFieldPreset(input) {
|
|
935
937
|
const helperPath = input.call.path.join(".");
|
|
936
938
|
const args = mapPslHelperArgs({
|
|
937
939
|
args: input.call.args,
|
|
@@ -970,10 +972,14 @@ function instantiatePslFieldPreset(input) {
|
|
|
970
972
|
}
|
|
971
973
|
}
|
|
972
974
|
function resolveFieldTypeDescriptor(input) {
|
|
975
|
+
if (input.field.malformedType) return {
|
|
976
|
+
ok: false,
|
|
977
|
+
alreadyReported: true
|
|
978
|
+
};
|
|
973
979
|
if (input.field.typeConstructor) {
|
|
974
980
|
const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
|
|
975
981
|
if (presetDescriptor) {
|
|
976
|
-
const instantiated =
|
|
982
|
+
const instantiated = instantiateFieldPreset({
|
|
977
983
|
call: input.field.typeConstructor,
|
|
978
984
|
descriptor: presetDescriptor,
|
|
979
985
|
diagnostics: input.diagnostics,
|
|
@@ -1301,7 +1307,6 @@ function lowerDefaultForField(input) {
|
|
|
1301
1307
|
return { executionDefaults: { onCreate: lowered.value.generated } };
|
|
1302
1308
|
}
|
|
1303
1309
|
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
|
|
1304
|
-
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
1305
1310
|
if (namedTypeDescriptors.has(field.typeName)) return namedTypeDescriptors.get(field.typeName);
|
|
1306
1311
|
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
1307
1312
|
return scalarTypeDescriptors.get(field.typeName);
|
|
@@ -1309,8 +1314,18 @@ function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptor
|
|
|
1309
1314
|
//#endregion
|
|
1310
1315
|
//#region src/psl-field-resolution.ts
|
|
1311
1316
|
function lowerEnumDefaultForField(input) {
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1317
|
+
const positionalEntries = input.defaultAttribute.args.filter((arg) => arg.kind === "positional");
|
|
1318
|
+
const hasNamedEntries = input.defaultAttribute.args.some((arg) => arg.kind === "named");
|
|
1319
|
+
const expressionEntry = positionalEntries[0];
|
|
1320
|
+
if (hasNamedEntries || positionalEntries.length !== 1 || expressionEntry === void 0) {
|
|
1321
|
+
input.diagnostics.push({
|
|
1322
|
+
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
1323
|
+
message: `Field "${input.modelName}.${input.fieldName}" @default on an enum field expects exactly one positional enum member argument.`,
|
|
1324
|
+
sourceId: input.sourceId,
|
|
1325
|
+
span: input.defaultAttribute.span
|
|
1326
|
+
});
|
|
1327
|
+
return {};
|
|
1328
|
+
}
|
|
1314
1329
|
const raw = expressionEntry.value.trim();
|
|
1315
1330
|
const isQuotedString = /^(['"]).*\1$/.test(raw);
|
|
1316
1331
|
const isFunctionCall = raw.includes("(") && raw.endsWith(")");
|
|
@@ -1414,7 +1429,7 @@ function extractFieldConstraintNames(input) {
|
|
|
1414
1429
|
function collectResolvedFields(input) {
|
|
1415
1430
|
const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors, enumHandles } = input;
|
|
1416
1431
|
const resolvedFields = [];
|
|
1417
|
-
for (const field of model.fields) {
|
|
1432
|
+
for (const field of Object.values(model.fields)) {
|
|
1418
1433
|
const isModelField = modelNames.has(field.typeName);
|
|
1419
1434
|
if (field.list && isModelField) continue;
|
|
1420
1435
|
validateFieldAttributes({
|
|
@@ -1534,7 +1549,7 @@ function collectResolvedFields(input) {
|
|
|
1534
1549
|
});
|
|
1535
1550
|
continue;
|
|
1536
1551
|
}
|
|
1537
|
-
const fieldUsesNamedType =
|
|
1552
|
+
const fieldUsesNamedType = namedTypeDescriptors.has(field.typeName);
|
|
1538
1553
|
if (loweredOnCreate && !fieldUsesNamedType) {
|
|
1539
1554
|
const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
|
|
1540
1555
|
if (generatedDescriptor) descriptor = generatedDescriptor;
|
|
@@ -1571,6 +1586,7 @@ function collectResolvedFields(input) {
|
|
|
1571
1586
|
field,
|
|
1572
1587
|
columnName: mappedColumnName,
|
|
1573
1588
|
descriptor,
|
|
1589
|
+
nullable: presetContributions?.nullable ?? field.optional,
|
|
1574
1590
|
...ifDefined("defaultValue", fieldDefaultValue),
|
|
1575
1591
|
...ifDefined("executionDefaults", fieldExecutionDefaults),
|
|
1576
1592
|
isId: isIdField || Boolean(presetContributions?.id),
|
|
@@ -1596,7 +1612,7 @@ function buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourc
|
|
|
1596
1612
|
span: model.span
|
|
1597
1613
|
});
|
|
1598
1614
|
const fieldColumns = /* @__PURE__ */ new Map();
|
|
1599
|
-
for (const field of model.fields) {
|
|
1615
|
+
for (const field of Object.values(model.fields)) {
|
|
1600
1616
|
const columnName = parseMapName({
|
|
1601
1617
|
attribute: getAttribute(field.attributes, "map"),
|
|
1602
1618
|
defaultValue: field.name,
|
|
@@ -1616,6 +1632,171 @@ function buildModelMappings(modelEntries, defaultNamespaceId, diagnostics, sourc
|
|
|
1616
1632
|
return result;
|
|
1617
1633
|
}
|
|
1618
1634
|
//#endregion
|
|
1635
|
+
//#region src/psl-named-type-resolution.ts
|
|
1636
|
+
function validateNamedTypeAttributes(input) {
|
|
1637
|
+
const [dbNativeTypeAttribute, ...extraDbNativeTypeAttributes] = input.allowDbNativeType ? input.declaration.attributes.filter((attribute) => attribute.name.startsWith("db.")) : [];
|
|
1638
|
+
let hasUnsupportedNamedTypeAttribute = false;
|
|
1639
|
+
for (const extra of extraDbNativeTypeAttributes) {
|
|
1640
|
+
input.diagnostics.push({
|
|
1641
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1642
|
+
message: `Named type "${input.declaration.name}" can declare at most one @db.* attribute`,
|
|
1643
|
+
sourceId: input.sourceId,
|
|
1644
|
+
span: extra.span
|
|
1645
|
+
});
|
|
1646
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1647
|
+
}
|
|
1648
|
+
for (const attribute of input.declaration.attributes) {
|
|
1649
|
+
if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
|
|
1650
|
+
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1651
|
+
familyId: input.familyId,
|
|
1652
|
+
targetId: input.targetId,
|
|
1653
|
+
authoringContributions: input.authoringContributions
|
|
1654
|
+
});
|
|
1655
|
+
if (uncomposedNamespace) {
|
|
1656
|
+
reportUncomposedNamespace({
|
|
1657
|
+
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
1658
|
+
namespace: uncomposedNamespace,
|
|
1659
|
+
sourceId: input.sourceId,
|
|
1660
|
+
span: attribute.span,
|
|
1661
|
+
diagnostics: input.diagnostics
|
|
1662
|
+
});
|
|
1663
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
input.diagnostics.push({
|
|
1667
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
1668
|
+
message: `Named type "${input.declaration.name}" uses unsupported attribute "${attribute.name}"`,
|
|
1669
|
+
sourceId: input.sourceId,
|
|
1670
|
+
span: attribute.span
|
|
1671
|
+
});
|
|
1672
|
+
hasUnsupportedNamedTypeAttribute = true;
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
dbNativeTypeAttribute,
|
|
1676
|
+
hasUnsupportedNamedTypeAttribute
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
function resolveNamedTypeDeclarations(input) {
|
|
1680
|
+
const storageTypeEntries = [];
|
|
1681
|
+
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1682
|
+
for (const declaration of input.declarations) {
|
|
1683
|
+
if (declaration.isConstructor) {
|
|
1684
|
+
const typeConstructor = declaration.typeConstructor;
|
|
1685
|
+
if (typeConstructor === void 0) {
|
|
1686
|
+
input.diagnostics.push({
|
|
1687
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1688
|
+
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
1689
|
+
sourceId: input.sourceId,
|
|
1690
|
+
span: declaration.span
|
|
1691
|
+
});
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
const { hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
1695
|
+
declaration,
|
|
1696
|
+
sourceId: input.sourceId,
|
|
1697
|
+
diagnostics: input.diagnostics,
|
|
1698
|
+
composedExtensions: input.composedExtensions,
|
|
1699
|
+
authoringContributions: input.authoringContributions,
|
|
1700
|
+
allowDbNativeType: false,
|
|
1701
|
+
familyId: input.familyId,
|
|
1702
|
+
targetId: input.targetId
|
|
1703
|
+
});
|
|
1704
|
+
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
1705
|
+
const helperPath = typeConstructor.path.join(".");
|
|
1706
|
+
const descriptor = resolvePslTypeConstructorDescriptor({
|
|
1707
|
+
call: typeConstructor,
|
|
1708
|
+
authoringContributions: input.authoringContributions,
|
|
1709
|
+
composedExtensions: input.composedExtensions,
|
|
1710
|
+
familyId: input.familyId,
|
|
1711
|
+
targetId: input.targetId,
|
|
1712
|
+
diagnostics: input.diagnostics,
|
|
1713
|
+
sourceId: input.sourceId,
|
|
1714
|
+
unsupportedCode: "PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR",
|
|
1715
|
+
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`
|
|
1716
|
+
});
|
|
1717
|
+
if (!descriptor) continue;
|
|
1718
|
+
const storageType = instantiatePslTypeConstructor({
|
|
1719
|
+
call: typeConstructor,
|
|
1720
|
+
descriptor,
|
|
1721
|
+
diagnostics: input.diagnostics,
|
|
1722
|
+
sourceId: input.sourceId,
|
|
1723
|
+
entityLabel: `Named type "${declaration.name}"`
|
|
1724
|
+
});
|
|
1725
|
+
if (!storageType) continue;
|
|
1726
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, storageType));
|
|
1727
|
+
storageTypeEntries.push([declaration.name, {
|
|
1728
|
+
kind: "codec-instance",
|
|
1729
|
+
codecId: storageType.codecId,
|
|
1730
|
+
nativeType: storageType.nativeType,
|
|
1731
|
+
typeParams: storageType.typeParams ?? {}
|
|
1732
|
+
}]);
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
const baseType = declaration.baseType;
|
|
1736
|
+
if (baseType === void 0) {
|
|
1737
|
+
input.diagnostics.push({
|
|
1738
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1739
|
+
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
1740
|
+
sourceId: input.sourceId,
|
|
1741
|
+
span: declaration.span
|
|
1742
|
+
});
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
const baseDescriptor = input.enumTypeDescriptors.get(baseType) ?? input.scalarTypeDescriptors.get(baseType);
|
|
1746
|
+
if (!baseDescriptor) {
|
|
1747
|
+
input.diagnostics.push({
|
|
1748
|
+
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
1749
|
+
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
1750
|
+
sourceId: input.sourceId,
|
|
1751
|
+
span: declaration.span
|
|
1752
|
+
});
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
const { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
1756
|
+
declaration,
|
|
1757
|
+
sourceId: input.sourceId,
|
|
1758
|
+
diagnostics: input.diagnostics,
|
|
1759
|
+
composedExtensions: input.composedExtensions,
|
|
1760
|
+
authoringContributions: input.authoringContributions,
|
|
1761
|
+
allowDbNativeType: true,
|
|
1762
|
+
familyId: input.familyId,
|
|
1763
|
+
targetId: input.targetId
|
|
1764
|
+
});
|
|
1765
|
+
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
1766
|
+
if (dbNativeTypeAttribute) {
|
|
1767
|
+
const descriptor = resolveDbNativeTypeAttribute({
|
|
1768
|
+
attribute: dbNativeTypeAttribute,
|
|
1769
|
+
baseType,
|
|
1770
|
+
baseDescriptor,
|
|
1771
|
+
diagnostics: input.diagnostics,
|
|
1772
|
+
sourceId: input.sourceId,
|
|
1773
|
+
entityLabel: `Named type "${declaration.name}"`
|
|
1774
|
+
});
|
|
1775
|
+
if (!descriptor) continue;
|
|
1776
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor));
|
|
1777
|
+
storageTypeEntries.push([declaration.name, {
|
|
1778
|
+
kind: "codec-instance",
|
|
1779
|
+
codecId: descriptor.codecId,
|
|
1780
|
+
nativeType: descriptor.nativeType,
|
|
1781
|
+
typeParams: descriptor.typeParams ?? {}
|
|
1782
|
+
}]);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
1786
|
+
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
1787
|
+
storageTypeEntries.push([declaration.name, {
|
|
1788
|
+
kind: "codec-instance",
|
|
1789
|
+
codecId: baseDescriptor.codecId,
|
|
1790
|
+
nativeType: baseDescriptor.nativeType,
|
|
1791
|
+
typeParams: {}
|
|
1792
|
+
}]);
|
|
1793
|
+
}
|
|
1794
|
+
return {
|
|
1795
|
+
storageTypes: Object.fromEntries(storageTypeEntries),
|
|
1796
|
+
namedTypeDescriptors
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
//#endregion
|
|
1619
1800
|
//#region src/psl-relation-resolution.ts
|
|
1620
1801
|
const REFERENTIAL_ACTION_MAP = {
|
|
1621
1802
|
NoAction: "noAction",
|
|
@@ -1753,7 +1934,11 @@ function parseRelationAttribute(input) {
|
|
|
1753
1934
|
function indexFkRelations(input) {
|
|
1754
1935
|
const modelRelations = /* @__PURE__ */ new Map();
|
|
1755
1936
|
const fkRelationsByPair = /* @__PURE__ */ new Map();
|
|
1937
|
+
const fkRelationsByDeclaringModel = /* @__PURE__ */ new Map();
|
|
1756
1938
|
for (const relation of input.fkRelationMetadata) {
|
|
1939
|
+
const declaringFkRelations = fkRelationsByDeclaringModel.get(relation.declaringModelName);
|
|
1940
|
+
if (declaringFkRelations) declaringFkRelations.push(relation);
|
|
1941
|
+
else fkRelationsByDeclaringModel.set(relation.declaringModelName, [relation]);
|
|
1757
1942
|
const existing = modelRelations.get(relation.declaringModelName);
|
|
1758
1943
|
const current = existing ?? [];
|
|
1759
1944
|
if (!existing) modelRelations.set(relation.declaringModelName, current);
|
|
@@ -1780,8 +1965,146 @@ function indexFkRelations(input) {
|
|
|
1780
1965
|
}
|
|
1781
1966
|
return {
|
|
1782
1967
|
modelRelations,
|
|
1783
|
-
fkRelationsByPair
|
|
1968
|
+
fkRelationsByPair,
|
|
1969
|
+
fkRelationsByDeclaringModel
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function idColumnsAreExactlyFkPair(idColumns, parentColumns, childColumns) {
|
|
1973
|
+
if (idColumns.length !== parentColumns.length + childColumns.length) return false;
|
|
1974
|
+
const fkColumns = new Set([...parentColumns, ...childColumns]);
|
|
1975
|
+
if (fkColumns.size !== parentColumns.length + childColumns.length) return false;
|
|
1976
|
+
return idColumns.every((column) => fkColumns.has(column));
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Reorders the child FK's junction columns into the target model's id-column
|
|
1980
|
+
* order. Returns undefined unless the FK references exactly the target's full
|
|
1981
|
+
* id, because downstream consumers pair `through.childColumns` positionally
|
|
1982
|
+
* against the target id columns — an FK referencing anything else (a non-id
|
|
1983
|
+
* unique, a partial id) would produce a silently wrong join.
|
|
1984
|
+
*/
|
|
1985
|
+
function childColumnsInTargetIdOrder(childFk, targetIdColumns) {
|
|
1986
|
+
if (childFk.referencedColumns.length !== targetIdColumns.length) return;
|
|
1987
|
+
const localByReferenced = /* @__PURE__ */ new Map();
|
|
1988
|
+
for (const [index, referencedColumn] of childFk.referencedColumns.entries()) {
|
|
1989
|
+
const localColumn = childFk.localColumns[index];
|
|
1990
|
+
if (localColumn === void 0) return;
|
|
1991
|
+
localByReferenced.set(referencedColumn, localColumn);
|
|
1992
|
+
}
|
|
1993
|
+
if (localByReferenced.size !== targetIdColumns.length) return;
|
|
1994
|
+
const ordered = [];
|
|
1995
|
+
for (const idColumn of targetIdColumns) {
|
|
1996
|
+
const localColumn = localByReferenced.get(idColumn);
|
|
1997
|
+
if (localColumn === void 0) return;
|
|
1998
|
+
ordered.push(localColumn);
|
|
1999
|
+
}
|
|
2000
|
+
return ordered;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Finds explicit junction models that connect a bare backrelation list field
|
|
2004
|
+
* to its target model: a model whose composite id columns are exactly the FK
|
|
2005
|
+
* columns of one relation back to the candidate's model (the parent side) and
|
|
2006
|
+
* one relation to the candidate's target model (the child side). The child
|
|
2007
|
+
* FK must reference exactly the target model's id columns; its junction
|
|
2008
|
+
* columns are carried in target-id order on the pair. A relation name on the
|
|
2009
|
+
* list field pins the parent-side FK relation, which is how self-referential
|
|
2010
|
+
* many-to-many sides are disambiguated.
|
|
2011
|
+
*
|
|
2012
|
+
* Alongside the recognised pairs, returns junction-shaped near-misses (models
|
|
2013
|
+
* that link both sides but were declined) so the caller can emit a
|
|
2014
|
+
* junction-specific diagnostic instead of the generic orphaned-list message.
|
|
2015
|
+
*/
|
|
2016
|
+
function findJunctionFkPairs(input) {
|
|
2017
|
+
const targetIdColumns = input.modelIdColumns.get(input.candidate.targetModelName);
|
|
2018
|
+
if (!targetIdColumns || targetIdColumns.length === 0) return {
|
|
2019
|
+
pairs: [],
|
|
2020
|
+
nearMisses: []
|
|
2021
|
+
};
|
|
2022
|
+
const pairs = [];
|
|
2023
|
+
const nearMisses = [];
|
|
2024
|
+
for (const [junctionModelName, junctionFks] of input.fkRelationsByDeclaringModel) {
|
|
2025
|
+
const idColumns = input.modelIdColumns.get(junctionModelName);
|
|
2026
|
+
for (const parentFk of junctionFks) {
|
|
2027
|
+
if (parentFk.targetModelName !== input.candidate.modelName) continue;
|
|
2028
|
+
if (input.candidate.relationName !== void 0 && parentFk.relationName !== input.candidate.relationName) continue;
|
|
2029
|
+
for (const childFk of junctionFks) {
|
|
2030
|
+
if (childFk === parentFk || childFk.targetModelName !== input.candidate.targetModelName) continue;
|
|
2031
|
+
if (!idColumns || !idColumnsAreExactlyFkPair(idColumns, parentFk.localColumns, childFk.localColumns)) {
|
|
2032
|
+
nearMisses.push({
|
|
2033
|
+
junctionModelName,
|
|
2034
|
+
reason: "id-not-fk-covering"
|
|
2035
|
+
});
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
const orderedChildColumns = childColumnsInTargetIdOrder(childFk, targetIdColumns);
|
|
2039
|
+
if (!orderedChildColumns) {
|
|
2040
|
+
nearMisses.push({
|
|
2041
|
+
junctionModelName,
|
|
2042
|
+
reason: "target-fk-not-id"
|
|
2043
|
+
});
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
pairs.push({
|
|
2047
|
+
parentFk,
|
|
2048
|
+
childFk,
|
|
2049
|
+
childColumnsInTargetIdOrder: orderedChildColumns
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return {
|
|
2055
|
+
pairs,
|
|
2056
|
+
nearMisses
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function junctionNearMissDiagnostic(candidate, nearMiss, sourceId) {
|
|
2060
|
+
const listField = `${candidate.modelName}.${candidate.field.name}`;
|
|
2061
|
+
const data = {
|
|
2062
|
+
listField,
|
|
2063
|
+
junctionModel: nearMiss.junctionModelName,
|
|
2064
|
+
targetModel: candidate.targetModelName
|
|
2065
|
+
};
|
|
2066
|
+
if (nearMiss.reason === "target-fk-not-id") return {
|
|
2067
|
+
code: "PSL_JUNCTION_TARGET_FK_NOT_ID",
|
|
2068
|
+
message: `Backrelation list field "${listField}" found junction model "${nearMiss.junctionModelName}", but its foreign key to "${candidate.targetModelName}" does not reference "${candidate.targetModelName}"'s @id. The junction's target-side foreign key must reference "${candidate.targetModelName}"'s full @id columns for many-to-many recognition.`,
|
|
2069
|
+
sourceId,
|
|
2070
|
+
span: candidate.field.span,
|
|
2071
|
+
data
|
|
1784
2072
|
};
|
|
2073
|
+
return {
|
|
2074
|
+
code: "PSL_JUNCTION_ID_NOT_FK_COVERING",
|
|
2075
|
+
message: `Backrelation list field "${listField}" found junction-shaped model "${nearMiss.junctionModelName}" linking "${candidate.modelName}" and "${candidate.targetModelName}", but its id does not cover exactly its foreign-key columns. Declare @@id([...]) on "${nearMiss.junctionModelName}" listing exactly the two foreign-key columns for many-to-many recognition.`,
|
|
2076
|
+
sourceId,
|
|
2077
|
+
span: candidate.field.span,
|
|
2078
|
+
data
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
function manyToManyRelationNode(candidate, pair) {
|
|
2082
|
+
return {
|
|
2083
|
+
fieldName: candidate.field.name,
|
|
2084
|
+
toModel: pair.childFk.targetModelName,
|
|
2085
|
+
toTable: pair.childFk.targetTableName,
|
|
2086
|
+
...ifDefined("toNamespaceId", pair.childFk.targetNamespaceId),
|
|
2087
|
+
cardinality: "N:M",
|
|
2088
|
+
on: {
|
|
2089
|
+
parentTable: candidate.tableName,
|
|
2090
|
+
parentColumns: pair.parentFk.referencedColumns,
|
|
2091
|
+
childTable: pair.parentFk.declaringTableName,
|
|
2092
|
+
childColumns: pair.parentFk.localColumns
|
|
2093
|
+
},
|
|
2094
|
+
through: {
|
|
2095
|
+
table: pair.parentFk.declaringTableName,
|
|
2096
|
+
...ifDefined("namespaceId", pair.parentFk.declaringNamespaceId),
|
|
2097
|
+
parentColumns: pair.parentFk.localColumns,
|
|
2098
|
+
childColumns: pair.childColumnsInTargetIdOrder
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
function relationsForModel(modelRelations, modelName) {
|
|
2103
|
+
const existing = modelRelations.get(modelName);
|
|
2104
|
+
if (existing) return existing;
|
|
2105
|
+
const created = [];
|
|
2106
|
+
modelRelations.set(modelName, created);
|
|
2107
|
+
return created;
|
|
1785
2108
|
}
|
|
1786
2109
|
function applyBackrelationCandidates(input) {
|
|
1787
2110
|
for (const candidate of input.backrelationCandidates) {
|
|
@@ -1789,6 +2112,30 @@ function applyBackrelationCandidates(input) {
|
|
|
1789
2112
|
const pairMatches = input.fkRelationsByPair.get(pairKey) ?? [];
|
|
1790
2113
|
const matches = candidate.relationName ? pairMatches.filter((relation) => relation.relationName === candidate.relationName) : [...pairMatches];
|
|
1791
2114
|
if (matches.length === 0) {
|
|
2115
|
+
const { pairs: junctionPairs, nearMisses } = findJunctionFkPairs({
|
|
2116
|
+
candidate,
|
|
2117
|
+
fkRelationsByDeclaringModel: input.fkRelationsByDeclaringModel,
|
|
2118
|
+
modelIdColumns: input.modelIdColumns
|
|
2119
|
+
});
|
|
2120
|
+
const junctionPair = junctionPairs[0];
|
|
2121
|
+
if (junctionPairs.length === 1 && junctionPair) {
|
|
2122
|
+
relationsForModel(input.modelRelations, candidate.modelName).push(manyToManyRelationNode(candidate, junctionPair));
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
if (junctionPairs.length > 1) {
|
|
2126
|
+
input.diagnostics.push({
|
|
2127
|
+
code: "PSL_AMBIGUOUS_BACKRELATION_LIST",
|
|
2128
|
+
message: `Backrelation list field "${candidate.modelName}.${candidate.field.name}" matches multiple junction FK pairs for a many-to-many relation. Add @relation(name: "...") (or @relation("...")) to the list field and the junction FK-side relation pointing back at "${candidate.modelName}" to disambiguate.`,
|
|
2129
|
+
sourceId: input.sourceId,
|
|
2130
|
+
span: candidate.field.span
|
|
2131
|
+
});
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
const nearMiss = nearMisses[0];
|
|
2135
|
+
if (nearMiss) {
|
|
2136
|
+
input.diagnostics.push(junctionNearMissDiagnostic(candidate, nearMiss, input.sourceId));
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
1792
2139
|
input.diagnostics.push({
|
|
1793
2140
|
code: "PSL_ORPHANED_BACKRELATION_LIST",
|
|
1794
2141
|
message: `Backrelation list field "${candidate.modelName}.${candidate.field.name}" has no matching FK-side relation on model "${candidate.targetModelName}". Add @relation(fields: [...], references: [...]) on the FK-side relation or use an explicit join model for many-to-many.`,
|
|
@@ -1809,13 +2156,11 @@ function applyBackrelationCandidates(input) {
|
|
|
1809
2156
|
invariant(matches.length === 1, "Backrelation matching requires exactly one match");
|
|
1810
2157
|
const matched = matches[0];
|
|
1811
2158
|
assertDefined(matched, "Backrelation matching requires a defined relation match");
|
|
1812
|
-
|
|
1813
|
-
const current = existing ?? [];
|
|
1814
|
-
if (!existing) input.modelRelations.set(candidate.modelName, current);
|
|
1815
|
-
current.push({
|
|
2159
|
+
relationsForModel(input.modelRelations, candidate.modelName).push({
|
|
1816
2160
|
fieldName: candidate.field.name,
|
|
1817
2161
|
toModel: matched.declaringModelName,
|
|
1818
2162
|
toTable: matched.declaringTableName,
|
|
2163
|
+
...ifDefined("toNamespaceId", matched.declaringNamespaceId),
|
|
1819
2164
|
cardinality: "1:N",
|
|
1820
2165
|
on: {
|
|
1821
2166
|
parentTable: candidate.tableName,
|
|
@@ -1869,32 +2214,11 @@ function buildComposedExtensionPackRefs(target, extensionIds, extensionPackRefs
|
|
|
1869
2214
|
version: "0.0.1"
|
|
1870
2215
|
}]));
|
|
1871
2216
|
}
|
|
1872
|
-
function diagnosticDedupKey(diagnostic) {
|
|
1873
|
-
const span = diagnostic.span;
|
|
1874
|
-
const spanKey = span ? `${span.start.offset}:${span.end.offset}:${span.start.line}:${span.end.line}` : "";
|
|
1875
|
-
return `${diagnostic.code}\u0000${diagnostic.sourceId}\u0000${spanKey}\u0000${diagnostic.message}`;
|
|
1876
|
-
}
|
|
1877
|
-
function dedupeDiagnostics(diagnostics) {
|
|
1878
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1879
|
-
for (const diagnostic of diagnostics) {
|
|
1880
|
-
const key = diagnosticDedupKey(diagnostic);
|
|
1881
|
-
if (!seen.has(key)) seen.set(key, diagnostic);
|
|
1882
|
-
}
|
|
1883
|
-
return [...seen.values()];
|
|
1884
|
-
}
|
|
1885
2217
|
function compareStrings(left, right) {
|
|
1886
2218
|
if (left < right) return -1;
|
|
1887
2219
|
if (left > right) return 1;
|
|
1888
2220
|
return 0;
|
|
1889
2221
|
}
|
|
1890
|
-
function mapParserDiagnostics(document) {
|
|
1891
|
-
return document.diagnostics.map((diagnostic) => ({
|
|
1892
|
-
code: diagnostic.code,
|
|
1893
|
-
message: diagnostic.message,
|
|
1894
|
-
sourceId: diagnostic.sourceId,
|
|
1895
|
-
span: diagnostic.span
|
|
1896
|
-
}));
|
|
1897
|
-
}
|
|
1898
2222
|
/**
|
|
1899
2223
|
* Name of the framework-parser synthesised bucket for top-level
|
|
1900
2224
|
* declarations. Re-declared here so the per-target dispatch does not
|
|
@@ -1956,28 +2280,24 @@ function resolveNamespaceIdForSqlTarget(input) {
|
|
|
1956
2280
|
}
|
|
1957
2281
|
function validateNamespaceBlocksForSqlTarget(input) {
|
|
1958
2282
|
if (input.targetId === "sqlite") {
|
|
1959
|
-
for (const namespace of input.namespaces) {
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
span: namespace.span
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
2283
|
+
for (const namespace of input.namespaces) input.diagnostics.push({
|
|
2284
|
+
code: "PSL_UNSUPPORTED_NAMESPACE_BLOCK",
|
|
2285
|
+
message: `SQLite does not support \`namespace ${namespace.name} { … }\` blocks (SQLite has no schema concept; declare models at the document top level instead).`,
|
|
2286
|
+
sourceId: input.sourceId,
|
|
2287
|
+
span: nodePslSpan(namespace.node.syntax, input.sourceFile)
|
|
2288
|
+
});
|
|
1968
2289
|
return;
|
|
1969
2290
|
}
|
|
1970
2291
|
if (input.targetId === "postgres") {
|
|
1971
|
-
const
|
|
1972
|
-
const
|
|
1973
|
-
const hasSibling = namedBlocks.some((ns) => ns.name !== "unbound");
|
|
2292
|
+
const hasUnbound = input.namespaces.some((ns) => ns.name === "unbound");
|
|
2293
|
+
const hasSibling = input.namespaces.some((ns) => ns.name !== "unbound");
|
|
1974
2294
|
if (hasUnbound && hasSibling) {
|
|
1975
|
-
const unboundBlock =
|
|
2295
|
+
const unboundBlock = input.namespaces.find((ns) => ns.name === "unbound");
|
|
1976
2296
|
input.diagnostics.push({
|
|
1977
2297
|
code: "PSL_RESERVED_NAMESPACE_NAME",
|
|
1978
2298
|
message: "Namespace \"unbound\" is reserved for the late-binding sentinel mapping and cannot appear alongside other named namespace blocks. Use `namespace unbound { … }` alone (no sibling named namespaces) for late-binding multi-tenant contracts.",
|
|
1979
2299
|
sourceId: input.sourceId,
|
|
1980
|
-
...
|
|
2300
|
+
...unboundBlock !== void 0 ? { span: nodePslSpan(unboundBlock.node.syntax, input.sourceFile) } : {}
|
|
1981
2301
|
});
|
|
1982
2302
|
}
|
|
1983
2303
|
}
|
|
@@ -2017,158 +2337,12 @@ function processEnumDeclarations(input) {
|
|
|
2017
2337
|
enumTypeDescriptors
|
|
2018
2338
|
};
|
|
2019
2339
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
message: `Named type "${input.declaration.name}" can declare at most one @db.* attribute`,
|
|
2027
|
-
sourceId: input.sourceId,
|
|
2028
|
-
span: extra.span
|
|
2029
|
-
});
|
|
2030
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
2031
|
-
}
|
|
2032
|
-
for (const attribute of input.declaration.attributes) {
|
|
2033
|
-
if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
|
|
2034
|
-
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
2035
|
-
familyId: input.familyId,
|
|
2036
|
-
targetId: input.targetId,
|
|
2037
|
-
authoringContributions: input.authoringContributions
|
|
2038
|
-
});
|
|
2039
|
-
if (uncomposedNamespace) {
|
|
2040
|
-
reportUncomposedNamespace({
|
|
2041
|
-
subjectLabel: `Attribute "@${attribute.name}"`,
|
|
2042
|
-
namespace: uncomposedNamespace,
|
|
2043
|
-
sourceId: input.sourceId,
|
|
2044
|
-
span: attribute.span,
|
|
2045
|
-
diagnostics: input.diagnostics
|
|
2046
|
-
});
|
|
2047
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
2048
|
-
continue;
|
|
2049
|
-
}
|
|
2050
|
-
input.diagnostics.push({
|
|
2051
|
-
code: "PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE",
|
|
2052
|
-
message: `Named type "${input.declaration.name}" uses unsupported attribute "${attribute.name}"`,
|
|
2053
|
-
sourceId: input.sourceId,
|
|
2054
|
-
span: attribute.span
|
|
2055
|
-
});
|
|
2056
|
-
hasUnsupportedNamedTypeAttribute = true;
|
|
2057
|
-
}
|
|
2058
|
-
return {
|
|
2059
|
-
dbNativeTypeAttribute,
|
|
2060
|
-
hasUnsupportedNamedTypeAttribute
|
|
2061
|
-
};
|
|
2062
|
-
}
|
|
2063
|
-
function resolveNamedTypeDeclarations(input) {
|
|
2064
|
-
const storageTypes = {};
|
|
2065
|
-
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
2066
|
-
for (const declaration of input.declarations) {
|
|
2067
|
-
if (declaration.typeConstructor) {
|
|
2068
|
-
const { hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
2069
|
-
declaration,
|
|
2070
|
-
sourceId: input.sourceId,
|
|
2071
|
-
diagnostics: input.diagnostics,
|
|
2072
|
-
composedExtensions: input.composedExtensions,
|
|
2073
|
-
authoringContributions: input.authoringContributions,
|
|
2074
|
-
allowDbNativeType: false,
|
|
2075
|
-
familyId: input.familyId,
|
|
2076
|
-
targetId: input.targetId
|
|
2077
|
-
});
|
|
2078
|
-
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
2079
|
-
const helperPath = declaration.typeConstructor.path.join(".");
|
|
2080
|
-
const typeConstructor = resolvePslTypeConstructorDescriptor({
|
|
2081
|
-
call: declaration.typeConstructor,
|
|
2082
|
-
authoringContributions: input.authoringContributions,
|
|
2083
|
-
composedExtensions: input.composedExtensions,
|
|
2084
|
-
familyId: input.familyId,
|
|
2085
|
-
targetId: input.targetId,
|
|
2086
|
-
diagnostics: input.diagnostics,
|
|
2087
|
-
sourceId: input.sourceId,
|
|
2088
|
-
unsupportedCode: "PSL_UNSUPPORTED_NAMED_TYPE_CONSTRUCTOR",
|
|
2089
|
-
unsupportedMessage: `Named type "${declaration.name}" references unsupported constructor "${helperPath}"`
|
|
2090
|
-
});
|
|
2091
|
-
if (!typeConstructor) continue;
|
|
2092
|
-
const storageType = instantiatePslTypeConstructor({
|
|
2093
|
-
call: declaration.typeConstructor,
|
|
2094
|
-
descriptor: typeConstructor,
|
|
2095
|
-
diagnostics: input.diagnostics,
|
|
2096
|
-
sourceId: input.sourceId,
|
|
2097
|
-
entityLabel: `Named type "${declaration.name}"`
|
|
2098
|
-
});
|
|
2099
|
-
if (!storageType) continue;
|
|
2100
|
-
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, storageType));
|
|
2101
|
-
storageTypes[declaration.name] = {
|
|
2102
|
-
kind: "codec-instance",
|
|
2103
|
-
codecId: storageType.codecId,
|
|
2104
|
-
nativeType: storageType.nativeType,
|
|
2105
|
-
typeParams: storageType.typeParams ?? {}
|
|
2106
|
-
};
|
|
2107
|
-
continue;
|
|
2108
|
-
}
|
|
2109
|
-
if (declaration.baseType === void 0) {
|
|
2110
|
-
input.diagnostics.push({
|
|
2111
|
-
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
2112
|
-
message: `Named type "${declaration.name}" must declare a base type or constructor`,
|
|
2113
|
-
sourceId: input.sourceId,
|
|
2114
|
-
span: declaration.span
|
|
2115
|
-
});
|
|
2116
|
-
continue;
|
|
2117
|
-
}
|
|
2118
|
-
const { baseType } = declaration;
|
|
2119
|
-
const baseDescriptor = input.enumTypeDescriptors.get(baseType) ?? input.scalarTypeDescriptors.get(baseType);
|
|
2120
|
-
if (!baseDescriptor) {
|
|
2121
|
-
input.diagnostics.push({
|
|
2122
|
-
code: "PSL_UNSUPPORTED_NAMED_TYPE_BASE",
|
|
2123
|
-
message: `Named type "${declaration.name}" references unsupported base type "${baseType}"`,
|
|
2124
|
-
sourceId: input.sourceId,
|
|
2125
|
-
span: declaration.span
|
|
2126
|
-
});
|
|
2127
|
-
continue;
|
|
2128
|
-
}
|
|
2129
|
-
const { dbNativeTypeAttribute, hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
2130
|
-
declaration,
|
|
2131
|
-
sourceId: input.sourceId,
|
|
2132
|
-
diagnostics: input.diagnostics,
|
|
2133
|
-
composedExtensions: input.composedExtensions,
|
|
2134
|
-
authoringContributions: input.authoringContributions,
|
|
2135
|
-
allowDbNativeType: true,
|
|
2136
|
-
familyId: input.familyId,
|
|
2137
|
-
targetId: input.targetId
|
|
2138
|
-
});
|
|
2139
|
-
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
2140
|
-
if (dbNativeTypeAttribute) {
|
|
2141
|
-
const descriptor = resolveDbNativeTypeAttribute({
|
|
2142
|
-
attribute: dbNativeTypeAttribute,
|
|
2143
|
-
baseType,
|
|
2144
|
-
baseDescriptor,
|
|
2145
|
-
diagnostics: input.diagnostics,
|
|
2146
|
-
sourceId: input.sourceId,
|
|
2147
|
-
entityLabel: `Named type "${declaration.name}"`
|
|
2148
|
-
});
|
|
2149
|
-
if (!descriptor) continue;
|
|
2150
|
-
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor));
|
|
2151
|
-
storageTypes[declaration.name] = {
|
|
2152
|
-
kind: "codec-instance",
|
|
2153
|
-
codecId: descriptor.codecId,
|
|
2154
|
-
nativeType: descriptor.nativeType,
|
|
2155
|
-
typeParams: descriptor.typeParams ?? {}
|
|
2156
|
-
};
|
|
2157
|
-
continue;
|
|
2158
|
-
}
|
|
2159
|
-
const descriptor = toNamedTypeFieldDescriptor(declaration.name, baseDescriptor);
|
|
2160
|
-
namedTypeDescriptors.set(declaration.name, descriptor);
|
|
2161
|
-
storageTypes[declaration.name] = {
|
|
2162
|
-
kind: "codec-instance",
|
|
2163
|
-
codecId: baseDescriptor.codecId,
|
|
2164
|
-
nativeType: baseDescriptor.nativeType,
|
|
2165
|
-
typeParams: {}
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
return {
|
|
2169
|
-
storageTypes,
|
|
2170
|
-
namedTypeDescriptors
|
|
2171
|
-
};
|
|
2340
|
+
/** Generic top-level blocks are supported only when a composed descriptor claims their keyword. */
|
|
2341
|
+
function composedBlockKeywords(authoringContributions) {
|
|
2342
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
2343
|
+
const descriptors = authoringContributions?.pslBlockDescriptors ?? {};
|
|
2344
|
+
for (const [keyword, value] of Object.entries(descriptors)) if (isAuthoringPslBlockDescriptor(value)) keywords.add(keyword);
|
|
2345
|
+
return keywords;
|
|
2172
2346
|
}
|
|
2173
2347
|
function buildModelNodeFromPsl(input) {
|
|
2174
2348
|
const { model, mapping, sourceId, diagnostics } = input;
|
|
@@ -2208,7 +2382,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2208
2382
|
let controlPolicyDeclared = false;
|
|
2209
2383
|
let controlPolicy;
|
|
2210
2384
|
const resultBackrelationCandidates = [];
|
|
2211
|
-
for (const field of model.fields) {
|
|
2385
|
+
for (const field of Object.values(model.fields)) {
|
|
2212
2386
|
if (!field.list || !input.modelNames.has(field.typeName)) continue;
|
|
2213
2387
|
const attributesValid = validateNavigationListFieldAttributes({
|
|
2214
2388
|
modelName: model.name,
|
|
@@ -2260,7 +2434,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2260
2434
|
...ifDefined("relationName", relationName)
|
|
2261
2435
|
});
|
|
2262
2436
|
}
|
|
2263
|
-
const relationAttributes = model.fields.map((field) => ({
|
|
2437
|
+
const relationAttributes = Object.values(model.fields).map((field) => ({
|
|
2264
2438
|
field,
|
|
2265
2439
|
relation: getAttribute(field.attributes, "relation")
|
|
2266
2440
|
})).filter((entry) => Boolean(entry.relation));
|
|
@@ -2331,7 +2505,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2331
2505
|
});
|
|
2332
2506
|
continue;
|
|
2333
2507
|
}
|
|
2334
|
-
const nullableFieldName = fieldNames.find((name) => model.fields
|
|
2508
|
+
const nullableFieldName = fieldNames.find((name) => model.fields[name]?.optional === true);
|
|
2335
2509
|
if (nullableFieldName !== void 0) {
|
|
2336
2510
|
diagnostics.push({
|
|
2337
2511
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
@@ -2718,6 +2892,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2718
2892
|
declaringModelName: model.name,
|
|
2719
2893
|
declaringFieldName: relationAttribute.field.name,
|
|
2720
2894
|
declaringTableName: tableName,
|
|
2895
|
+
...ifDefined("declaringNamespaceId", input.modelNamespaceIds.get(model.name)),
|
|
2721
2896
|
targetModelName: targetMapping.model.name,
|
|
2722
2897
|
targetTableName: targetMapping.tableName,
|
|
2723
2898
|
...ifDefined("targetNamespaceId", targetNamespaceId),
|
|
@@ -2736,7 +2911,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
2736
2911
|
fieldName: resolvedField.field.name,
|
|
2737
2912
|
columnName: resolvedField.columnName,
|
|
2738
2913
|
descriptor: resolvedField.descriptor,
|
|
2739
|
-
nullable: resolvedField.
|
|
2914
|
+
nullable: resolvedField.nullable,
|
|
2740
2915
|
...ifDefined("default", resolvedField.defaultValue),
|
|
2741
2916
|
...ifDefined("executionDefaults", resolvedField.executionDefaults),
|
|
2742
2917
|
...ifDefined("enumTypeHandle", enumHandle)
|
|
@@ -2760,7 +2935,7 @@ function buildValueObjects(input) {
|
|
|
2760
2935
|
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
2761
2936
|
for (const compositeType of compositeTypes) {
|
|
2762
2937
|
const fields = {};
|
|
2763
|
-
for (const field of compositeType.fields) {
|
|
2938
|
+
for (const field of Object.values(compositeType.fields)) {
|
|
2764
2939
|
if (compositeTypeNames.has(field.typeName)) {
|
|
2765
2940
|
const result = {
|
|
2766
2941
|
type: {
|
|
@@ -2866,7 +3041,7 @@ function collectPolymorphismDeclarations(models, sourceId, diagnostics) {
|
|
|
2866
3041
|
});
|
|
2867
3042
|
continue;
|
|
2868
3043
|
}
|
|
2869
|
-
const discField = model.fields
|
|
3044
|
+
const discField = model.fields[fieldName];
|
|
2870
3045
|
if (discField && discField.typeName !== "String") {
|
|
2871
3046
|
diagnostics.push({
|
|
2872
3047
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
@@ -3171,7 +3346,7 @@ function stripStorageOnlyDomainFields(model, fieldNames) {
|
|
|
3171
3346
|
};
|
|
3172
3347
|
}
|
|
3173
3348
|
function interpretPslDocumentToSqlContract(input) {
|
|
3174
|
-
const sourceId = input.
|
|
3349
|
+
const sourceId = input.sourceId;
|
|
3175
3350
|
if (!input.target) return notOk({
|
|
3176
3351
|
summary: "PSL to SQL contract interpretation failed",
|
|
3177
3352
|
diagnostics: [{
|
|
@@ -3188,22 +3363,27 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3188
3363
|
sourceId
|
|
3189
3364
|
}]
|
|
3190
3365
|
});
|
|
3191
|
-
const
|
|
3366
|
+
const { topLevel } = input.symbolTable;
|
|
3367
|
+
const sourceFile = input.sourceFile;
|
|
3368
|
+
const namespaceSymbols = Object.values(topLevel.namespaces);
|
|
3369
|
+
const diagnostics = [...input.seedDiagnostics ?? []];
|
|
3192
3370
|
validateNamespaceBlocksForSqlTarget({
|
|
3193
|
-
namespaces:
|
|
3371
|
+
namespaces: namespaceSymbols,
|
|
3194
3372
|
targetId: input.target.targetId,
|
|
3195
3373
|
sourceId,
|
|
3374
|
+
sourceFile,
|
|
3196
3375
|
diagnostics
|
|
3197
3376
|
});
|
|
3198
3377
|
const models = [];
|
|
3199
3378
|
const modelEntries = [];
|
|
3200
3379
|
const modelNamespaceIds = /* @__PURE__ */ new Map();
|
|
3201
|
-
|
|
3380
|
+
const compositeTypes = [];
|
|
3381
|
+
const collectScope = (bucketName, scopeModels, scopeCompositeTypes) => {
|
|
3202
3382
|
const resolvedNamespaceId = resolveNamespaceIdForSqlTarget({
|
|
3203
|
-
bucketName
|
|
3383
|
+
bucketName,
|
|
3204
3384
|
targetId: input.target.targetId
|
|
3205
3385
|
});
|
|
3206
|
-
for (const model of
|
|
3386
|
+
for (const model of scopeModels) {
|
|
3207
3387
|
models.push(model);
|
|
3208
3388
|
modelEntries.push({
|
|
3209
3389
|
model,
|
|
@@ -3211,9 +3391,11 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3211
3391
|
});
|
|
3212
3392
|
if (resolvedNamespaceId !== void 0) modelNamespaceIds.set(model.name, resolvedNamespaceId);
|
|
3213
3393
|
}
|
|
3214
|
-
|
|
3394
|
+
for (const compositeType of scopeCompositeTypes) compositeTypes.push(compositeType);
|
|
3395
|
+
};
|
|
3396
|
+
collectScope(UNSPECIFIED_PSL_NAMESPACE_NAME, Object.values(topLevel.models), Object.values(topLevel.compositeTypes));
|
|
3397
|
+
for (const namespace of namespaceSymbols) collectScope(namespace.name, Object.values(namespace.models), Object.values(namespace.compositeTypes));
|
|
3215
3398
|
const defaultNamespaceId = input.target.defaultNamespaceId;
|
|
3216
|
-
const compositeTypes = input.document.ast.namespaces.flatMap((ns) => ns.compositeTypes);
|
|
3217
3399
|
const modelNames = new Set(models.map((model) => model.name));
|
|
3218
3400
|
const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
|
|
3219
3401
|
const composedExtensions = new Set(input.composedExtensionPacks ?? []);
|
|
@@ -3222,17 +3404,34 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3222
3404
|
const generatorDescriptors = input.controlMutationDefaults?.generatorDescriptors ?? [];
|
|
3223
3405
|
const generatorDescriptorById = /* @__PURE__ */ new Map();
|
|
3224
3406
|
for (const descriptor of generatorDescriptors) generatorDescriptorById.set(descriptor.id, descriptor);
|
|
3225
|
-
const
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
code: "PSL_ENUM_NAMESPACE_NOT_SUPPORTED",
|
|
3232
|
-
message: `enum "${decl.name}" inside namespace "${ns.name}" is not supported; declare enum at the top level`,
|
|
3407
|
+
const isEnumBlock = (block) => block.keyword === "enum";
|
|
3408
|
+
const legitimateBlockKeywords = composedBlockKeywords(input.authoringContributions);
|
|
3409
|
+
const reportUnsupportedTopLevelBlock = (block) => {
|
|
3410
|
+
diagnostics.push({
|
|
3411
|
+
code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
|
|
3412
|
+
message: `Unsupported top-level block "${block.keyword}"`,
|
|
3233
3413
|
sourceId,
|
|
3234
|
-
span:
|
|
3414
|
+
span: keywordPslSpan(block.node.syntax, block.keyword, sourceFile)
|
|
3235
3415
|
});
|
|
3416
|
+
};
|
|
3417
|
+
const topLevelEnums = Object.values(topLevel.blocks).filter((block) => {
|
|
3418
|
+
if (!legitimateBlockKeywords.has(block.keyword)) {
|
|
3419
|
+
reportUnsupportedTopLevelBlock(block);
|
|
3420
|
+
return false;
|
|
3421
|
+
}
|
|
3422
|
+
return isEnumBlock(block);
|
|
3423
|
+
}).map((block) => block.block);
|
|
3424
|
+
for (const namespace of namespaceSymbols) for (const block of Object.values(namespace.blocks)) {
|
|
3425
|
+
if (isEnumBlock(block)) {
|
|
3426
|
+
diagnostics.push({
|
|
3427
|
+
code: "PSL_ENUM_NAMESPACE_NOT_SUPPORTED",
|
|
3428
|
+
message: `enum "${block.name}" inside namespace "${namespace.name}" is not supported; declare enum at the top level`,
|
|
3429
|
+
sourceId,
|
|
3430
|
+
span: nodePslSpan(block.node.syntax, sourceFile)
|
|
3431
|
+
});
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
if (!legitimateBlockKeywords.has(block.keyword)) reportUnsupportedTopLevelBlock(block);
|
|
3236
3435
|
}
|
|
3237
3436
|
const enumResult = processEnumDeclarations({
|
|
3238
3437
|
enumBlocks: topLevelEnums,
|
|
@@ -3253,7 +3452,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3253
3452
|
const validEnumHandles = { ...enumResult.enumHandles };
|
|
3254
3453
|
const enumHandlesByName = new Map(Object.entries(validEnumHandles));
|
|
3255
3454
|
const namedTypeResult = resolveNamedTypeDeclarations({
|
|
3256
|
-
declarations:
|
|
3455
|
+
declarations: [...Object.values(topLevel.scalars), ...Object.values(topLevel.typeAliases)],
|
|
3257
3456
|
sourceId,
|
|
3258
3457
|
enumTypeDescriptors: allEnumTypeDescriptors,
|
|
3259
3458
|
scalarTypeDescriptors: input.scalarTypeDescriptors,
|
|
@@ -3310,10 +3509,14 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3310
3509
|
crossSpaceRelationsByModel.set(model.name, [...existing, ...result.crossSpaceRelations]);
|
|
3311
3510
|
}
|
|
3312
3511
|
}
|
|
3313
|
-
const { modelRelations, fkRelationsByPair } = indexFkRelations({ fkRelationMetadata });
|
|
3512
|
+
const { modelRelations, fkRelationsByPair, fkRelationsByDeclaringModel } = indexFkRelations({ fkRelationMetadata });
|
|
3513
|
+
const modelIdColumns = /* @__PURE__ */ new Map();
|
|
3514
|
+
for (const modelNode of modelNodes) if (modelNode.id) modelIdColumns.set(modelNode.modelName, modelNode.id.columns);
|
|
3314
3515
|
applyBackrelationCandidates({
|
|
3315
3516
|
backrelationCandidates,
|
|
3316
3517
|
fkRelationsByPair,
|
|
3518
|
+
fkRelationsByDeclaringModel,
|
|
3519
|
+
modelIdColumns,
|
|
3317
3520
|
modelRelations,
|
|
3318
3521
|
diagnostics,
|
|
3319
3522
|
sourceId
|
|
@@ -3342,7 +3545,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3342
3545
|
});
|
|
3343
3546
|
if (diagnostics.length > 0) return notOk({
|
|
3344
3547
|
summary: "PSL to SQL contract interpretation failed",
|
|
3345
|
-
diagnostics
|
|
3548
|
+
diagnostics
|
|
3346
3549
|
});
|
|
3347
3550
|
const contract = buildSqlContractFromDefinition({
|
|
3348
3551
|
target: input.target,
|
|
@@ -3358,7 +3561,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3358
3561
|
const modelsForPatch = {};
|
|
3359
3562
|
for (const [namespaceId, namespaceSlice] of Object.entries(contract.domain.namespaces)) for (const [modelName, model] of Object.entries(namespaceSlice.models)) {
|
|
3360
3563
|
const coordinate = modelCoordinateKey(namespaceId, modelName);
|
|
3361
|
-
|
|
3564
|
+
invariant(!Object.hasOwn(modelsForPatch, coordinate), `symbol table guarantees coordinate uniqueness; duplicate model "${namespaceId}.${modelName}" reached interpretation`);
|
|
3362
3565
|
modelsForPatch[coordinate] = model;
|
|
3363
3566
|
}
|
|
3364
3567
|
let patchedModels = patchModelDomainFields(modelsForPatch, modelResolvedFields);
|
|
@@ -3384,4 +3587,4 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
3384
3587
|
//#endregion
|
|
3385
3588
|
export { interpretPslDocumentToSqlContract as t };
|
|
3386
3589
|
|
|
3387
|
-
//# sourceMappingURL=interpreter-
|
|
3590
|
+
//# sourceMappingURL=interpreter-CygvamTk.mjs.map
|