@prisma-next/sql-contract-psl 0.5.0-dev.9 → 0.5.0
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 +9 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -3
- package/dist/{interpreter-iFCRN9nb.mjs → interpreter-ijCjxhaU.mjs} +507 -80
- package/dist/interpreter-ijCjxhaU.mjs.map +1 -0
- package/dist/provider.d.mts +2 -2
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +3 -6
- package/dist/provider.mjs.map +1 -1
- package/package.json +15 -13
- package/src/interpreter.ts +172 -28
- package/src/provider.ts +3 -5
- package/src/psl-attribute-parsing.ts +140 -5
- package/src/psl-authoring-arguments.ts +6 -0
- package/src/psl-column-resolution.ts +228 -37
- package/src/psl-field-resolution.ts +138 -17
- package/src/psl-relation-resolution.ts +3 -0
- package/dist/interpreter-iFCRN9nb.mjs.map +0 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { instantiateAuthoringTypeConstructor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
1
|
+
import { hasRegisteredFieldNamespace, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
|
|
2
2
|
import { buildSqlContractFromDefinition } from "@prisma-next/sql-contract-ts/contract-builder";
|
|
3
3
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
4
4
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
5
5
|
import { getPositionalArgument, parseQuotedStringLiteral } from "@prisma-next/psl-parser";
|
|
6
6
|
import { assertDefined, invariant } from "@prisma-next/utils/assertions";
|
|
7
|
-
|
|
8
7
|
//#region src/psl-attribute-parsing.ts
|
|
9
8
|
function lowerFirst(value) {
|
|
10
9
|
if (value.length === 0) return value;
|
|
@@ -77,6 +76,109 @@ function parseConstraintMapArgument(input) {
|
|
|
77
76
|
function getPositionalArguments(attribute) {
|
|
78
77
|
return attribute.args.filter((arg) => arg.kind === "positional").map((arg) => arg.kind === "positional" ? arg.value : "");
|
|
79
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Parses a PSL object-literal attribute argument value of the form
|
|
81
|
+
* `{ key1: "value1", key2: "value2" }` into a `Record<string, string>`.
|
|
82
|
+
*
|
|
83
|
+
* V1 admits string literals only as leaf values. Boolean and number
|
|
84
|
+
* literals are rejected. Trailing commas are allowed.
|
|
85
|
+
*
|
|
86
|
+
* Returns the parsed record, or pushes a diagnostic and returns undefined
|
|
87
|
+
* on malformed input or non-string leaves.
|
|
88
|
+
*/
|
|
89
|
+
function parseObjectLiteralStringMap(input) {
|
|
90
|
+
const trimmed = input.raw.trim();
|
|
91
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return pushInvalidAttributeArgument({
|
|
92
|
+
diagnostics: input.diagnostics,
|
|
93
|
+
sourceId: input.sourceId,
|
|
94
|
+
span: input.span,
|
|
95
|
+
message: `${input.entityLabel} expected an object literal value of the form { key: "value", ... }`
|
|
96
|
+
});
|
|
97
|
+
const body = trimmed.slice(1, -1).trim();
|
|
98
|
+
if (body.length === 0) return {};
|
|
99
|
+
const result = {};
|
|
100
|
+
for (const part of splitObjectLiteralEntries(body)) {
|
|
101
|
+
const colonAt = findTopLevelColon(part);
|
|
102
|
+
if (colonAt === -1) return pushInvalidAttributeArgument({
|
|
103
|
+
diagnostics: input.diagnostics,
|
|
104
|
+
sourceId: input.sourceId,
|
|
105
|
+
span: input.span,
|
|
106
|
+
message: `${input.entityLabel} object-literal entry "${part}" is missing a "key: value" colon`
|
|
107
|
+
});
|
|
108
|
+
const key = part.slice(0, colonAt).trim();
|
|
109
|
+
const rawValue = part.slice(colonAt + 1).trim();
|
|
110
|
+
if (key.length === 0 || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return pushInvalidAttributeArgument({
|
|
111
|
+
diagnostics: input.diagnostics,
|
|
112
|
+
sourceId: input.sourceId,
|
|
113
|
+
span: input.span,
|
|
114
|
+
message: `${input.entityLabel} object-literal key "${key}" must be a bare identifier`
|
|
115
|
+
});
|
|
116
|
+
const parsedString = parseQuotedStringLiteral(rawValue);
|
|
117
|
+
if (parsedString === void 0) return pushInvalidAttributeArgument({
|
|
118
|
+
diagnostics: input.diagnostics,
|
|
119
|
+
sourceId: input.sourceId,
|
|
120
|
+
span: input.span,
|
|
121
|
+
message: `${input.entityLabel} object-literal value for "${key}" must be a quoted string literal (V1 PSL @@index options support string leaves only; use the TS authoring surface for non-string options)`
|
|
122
|
+
});
|
|
123
|
+
if (Object.hasOwn(result, key)) return pushInvalidAttributeArgument({
|
|
124
|
+
diagnostics: input.diagnostics,
|
|
125
|
+
sourceId: input.sourceId,
|
|
126
|
+
span: input.span,
|
|
127
|
+
message: `${input.entityLabel} object-literal key "${key}" appears more than once`
|
|
128
|
+
});
|
|
129
|
+
result[key] = parsedString;
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
function splitObjectLiteralEntries(body) {
|
|
134
|
+
const parts = [];
|
|
135
|
+
let depthBrace = 0;
|
|
136
|
+
let depthBracket = 0;
|
|
137
|
+
let depthParen = 0;
|
|
138
|
+
let quote = null;
|
|
139
|
+
let start = 0;
|
|
140
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
141
|
+
const ch = body[index] ?? "";
|
|
142
|
+
if (quote) {
|
|
143
|
+
if (ch === quote && body[index - 1] !== "\\") quote = null;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (ch === "\"" || ch === "'") {
|
|
147
|
+
quote = ch;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (ch === "{") depthBrace += 1;
|
|
151
|
+
else if (ch === "}") depthBrace = Math.max(0, depthBrace - 1);
|
|
152
|
+
else if (ch === "[") depthBracket += 1;
|
|
153
|
+
else if (ch === "]") depthBracket = Math.max(0, depthBracket - 1);
|
|
154
|
+
else if (ch === "(") depthParen += 1;
|
|
155
|
+
else if (ch === ")") depthParen = Math.max(0, depthParen - 1);
|
|
156
|
+
else if (ch === "," && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
|
|
157
|
+
const segment = body.slice(start, index).trim();
|
|
158
|
+
if (segment.length > 0) parts.push(segment);
|
|
159
|
+
start = index + 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const tail = body.slice(start).trim();
|
|
163
|
+
if (tail.length > 0) parts.push(tail);
|
|
164
|
+
return parts;
|
|
165
|
+
}
|
|
166
|
+
function findTopLevelColon(entry) {
|
|
167
|
+
let quote = null;
|
|
168
|
+
for (let index = 0; index < entry.length; index += 1) {
|
|
169
|
+
const ch = entry[index] ?? "";
|
|
170
|
+
if (quote) {
|
|
171
|
+
if (ch === quote && entry[index - 1] !== "\\") quote = null;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === "\"" || ch === "'") {
|
|
175
|
+
quote = ch;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ch === ":") return index;
|
|
179
|
+
}
|
|
180
|
+
return -1;
|
|
181
|
+
}
|
|
80
182
|
function pushInvalidAttributeArgument(input) {
|
|
81
183
|
input.diagnostics.push({
|
|
82
184
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
@@ -149,7 +251,7 @@ function parseAttributeFieldList(input) {
|
|
|
149
251
|
if (!raw) {
|
|
150
252
|
input.diagnostics.push({
|
|
151
253
|
code: input.code,
|
|
152
|
-
message: `${input.
|
|
254
|
+
message: `${input.entityLabel} requires fields list argument`,
|
|
153
255
|
sourceId: input.sourceId,
|
|
154
256
|
span: input.attribute.span
|
|
155
257
|
});
|
|
@@ -159,7 +261,7 @@ function parseAttributeFieldList(input) {
|
|
|
159
261
|
if (!fields || fields.length === 0) {
|
|
160
262
|
input.diagnostics.push({
|
|
161
263
|
code: input.code,
|
|
162
|
-
message: `${input.
|
|
264
|
+
message: `${input.entityLabel} requires bracketed field list argument`,
|
|
163
265
|
sourceId: input.sourceId,
|
|
164
266
|
span: input.attribute.span
|
|
165
267
|
});
|
|
@@ -167,6 +269,13 @@ function parseAttributeFieldList(input) {
|
|
|
167
269
|
}
|
|
168
270
|
return fields;
|
|
169
271
|
}
|
|
272
|
+
function findDuplicateFieldName(fieldNames) {
|
|
273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
274
|
+
for (const name of fieldNames) {
|
|
275
|
+
if (seen.has(name)) return name;
|
|
276
|
+
seen.add(name);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
170
279
|
function mapFieldNamesToColumns(input) {
|
|
171
280
|
const columns = [];
|
|
172
281
|
for (const fieldName of input.fieldNames) {
|
|
@@ -174,7 +283,7 @@ function mapFieldNamesToColumns(input) {
|
|
|
174
283
|
if (!columnName) {
|
|
175
284
|
input.diagnostics.push({
|
|
176
285
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
177
|
-
message: `${input.
|
|
286
|
+
message: `${input.entityLabel} references unknown field "${input.modelName}.${fieldName}"`,
|
|
178
287
|
sourceId: input.sourceId,
|
|
179
288
|
span: input.span
|
|
180
289
|
});
|
|
@@ -184,7 +293,6 @@ function mapFieldNamesToColumns(input) {
|
|
|
184
293
|
}
|
|
185
294
|
return columns;
|
|
186
295
|
}
|
|
187
|
-
|
|
188
296
|
//#endregion
|
|
189
297
|
//#region src/default-function-registry.ts
|
|
190
298
|
function resolveSpanPositionFromBase(base, text, offset) {
|
|
@@ -323,7 +431,6 @@ function lowerDefaultFunctionWithRegistry(input) {
|
|
|
323
431
|
}
|
|
324
432
|
};
|
|
325
433
|
}
|
|
326
|
-
|
|
327
434
|
//#endregion
|
|
328
435
|
//#region src/psl-authoring-arguments.ts
|
|
329
436
|
const INVALID_AUTHORING_ARGUMENT = Symbol("invalidAuthoringArgument");
|
|
@@ -401,10 +508,10 @@ function parseJsLikeLiteral(value) {
|
|
|
401
508
|
function parseNumber() {
|
|
402
509
|
const raw = value.slice(index).match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0];
|
|
403
510
|
if (!raw) return INVALID_AUTHORING_ARGUMENT;
|
|
404
|
-
const parsed
|
|
405
|
-
if (!Number.isFinite(parsed
|
|
511
|
+
const parsed = Number(raw);
|
|
512
|
+
if (!Number.isFinite(parsed)) return INVALID_AUTHORING_ARGUMENT;
|
|
406
513
|
index += raw.length;
|
|
407
|
-
return parsed
|
|
514
|
+
return parsed;
|
|
408
515
|
}
|
|
409
516
|
function parseArray() {
|
|
410
517
|
if (value[index] !== "[") return INVALID_AUTHORING_ARGUMENT;
|
|
@@ -511,6 +618,12 @@ function parsePslObjectLiteral(value) {
|
|
|
511
618
|
function parsePslAuthoringArgumentValue(descriptor, rawValue) {
|
|
512
619
|
switch (descriptor.kind) {
|
|
513
620
|
case "string": return unquoteStringLiteral(rawValue);
|
|
621
|
+
case "boolean": {
|
|
622
|
+
const trimmed = rawValue.trim();
|
|
623
|
+
if (trimmed === "true") return true;
|
|
624
|
+
if (trimmed === "false") return false;
|
|
625
|
+
return INVALID_AUTHORING_ARGUMENT;
|
|
626
|
+
}
|
|
514
627
|
case "number": {
|
|
515
628
|
const parsed = Number(unquoteStringLiteral(rawValue));
|
|
516
629
|
return Number.isNaN(parsed) ? INVALID_AUTHORING_ARGUMENT : parsed;
|
|
@@ -562,7 +675,7 @@ function mapPslHelperArgs(input) {
|
|
|
562
675
|
mappedArgs[index] = value;
|
|
563
676
|
}
|
|
564
677
|
for (const argument of namedArgs) {
|
|
565
|
-
const descriptorIndex = input.descriptors.findIndex((descriptor
|
|
678
|
+
const descriptorIndex = input.descriptors.findIndex((descriptor) => descriptor.name === argument.name);
|
|
566
679
|
if (descriptorIndex < 0) return pushInvalidPslHelperArgument({
|
|
567
680
|
diagnostics: input.diagnostics,
|
|
568
681
|
sourceId: input.sourceId,
|
|
@@ -601,7 +714,6 @@ function mapPslHelperArgs(input) {
|
|
|
601
714
|
}
|
|
602
715
|
return mappedArgs;
|
|
603
716
|
}
|
|
604
|
-
|
|
605
717
|
//#endregion
|
|
606
718
|
//#region src/psl-column-resolution.ts
|
|
607
719
|
function toNamedTypeFieldDescriptor(typeRef, descriptor) {
|
|
@@ -620,33 +732,40 @@ function getAuthoringTypeConstructor(contributions, path) {
|
|
|
620
732
|
return isAuthoringTypeConstructorDescriptor(current) ? current : void 0;
|
|
621
733
|
}
|
|
622
734
|
/**
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
735
|
+
* Walks `authoringContributions.field` segment-by-segment and returns the field-preset descriptor at the resolved path, or `undefined` if no descriptor is registered.
|
|
736
|
+
*
|
|
737
|
+
* Symmetric with `getAuthoringTypeConstructor`. Field presets are strictly richer than type constructors — they can contribute `default` / `executionDefaults` / `id` / `unique` / `nullable` in addition to the `codecId` / `nativeType` / `typeParams` triple. PSL resolution tries field presets first, then falls back to type constructors on miss (see `resolveFieldTypeDescriptor`).
|
|
738
|
+
*/
|
|
739
|
+
function getAuthoringFieldPreset(contributions, path) {
|
|
740
|
+
let current = contributions?.field;
|
|
741
|
+
for (const segment of path) {
|
|
742
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return;
|
|
743
|
+
current = current[segment];
|
|
744
|
+
}
|
|
745
|
+
return isAuthoringFieldPresetDescriptor(current) ? current : void 0;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Returns the namespace prefix of `attributeName` if it references an unrecognized extension namespace, otherwise `undefined`. A namespace is considered recognized when it is:
|
|
626
749
|
*
|
|
627
750
|
* - `db` (native-type spec, always allowed),
|
|
628
751
|
* - the active family id (e.g. `sql`),
|
|
629
752
|
* - the active target id (e.g. `postgres`),
|
|
753
|
+
* - a registered field-preset namespace (e.g. `temporal`),
|
|
630
754
|
* - present in `composedExtensions`.
|
|
631
755
|
*
|
|
632
|
-
* Family/target namespaces are exempted so that e.g. `@sql.foo` surfaces as
|
|
633
|
-
* PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than
|
|
634
|
-
* PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
|
|
756
|
+
* Family/target/field-preset namespaces are exempted so that e.g. `@sql.foo` surfaces as PSL_UNSUPPORTED_*_ATTRIBUTE (the attribute isn't defined) rather than PSL_EXTENSION_NAMESPACE_NOT_COMPOSED (the namespace is already composed).
|
|
635
757
|
*/
|
|
636
758
|
function checkUncomposedNamespace(attributeName, composedExtensions, context) {
|
|
637
759
|
const dotIndex = attributeName.indexOf(".");
|
|
638
760
|
if (dotIndex <= 0 || dotIndex === attributeName.length - 1) return;
|
|
639
761
|
const namespace = attributeName.slice(0, dotIndex);
|
|
640
|
-
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || composedExtensions.has(namespace)) return;
|
|
762
|
+
if (namespace === "db" || namespace === context?.familyId || namespace === context?.targetId || hasRegisteredFieldNamespace(context?.authoringContributions, namespace) || composedExtensions.has(namespace)) return;
|
|
641
763
|
return namespace;
|
|
642
764
|
}
|
|
643
765
|
/**
|
|
644
|
-
* Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a
|
|
645
|
-
* subject (attribute, model attribute, or type constructor) that references an
|
|
646
|
-
* extension namespace which is not composed in the current contract.
|
|
766
|
+
* Pushes the canonical `PSL_EXTENSION_NAMESPACE_NOT_COMPOSED` diagnostic for a subject (attribute, model attribute, or type constructor) that references an extension namespace which is not composed in the current contract.
|
|
647
767
|
*
|
|
648
|
-
* The `data` payload carries the missing namespace so machine consumers
|
|
649
|
-
* (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
|
|
768
|
+
* The `data` payload carries the missing namespace so machine consumers (agents, IDE extensions, CLI auto-fix) don't have to parse the prose.
|
|
650
769
|
*/
|
|
651
770
|
function reportUncomposedNamespace(input) {
|
|
652
771
|
input.diagnostics.push({
|
|
@@ -660,6 +779,21 @@ function reportUncomposedNamespace(input) {
|
|
|
660
779
|
}
|
|
661
780
|
});
|
|
662
781
|
}
|
|
782
|
+
/**
|
|
783
|
+
* Pushes the canonical `PSL_UNKNOWN_FIELD_PRESET` diagnostic when a typoed preset name is referenced inside a registered field-preset namespace. The `data` payload exposes the namespace and full helper path so machine consumers (agents, IDE extensions) don't have to parse the prose.
|
|
784
|
+
*/
|
|
785
|
+
function reportUnknownFieldPreset(input) {
|
|
786
|
+
input.diagnostics.push({
|
|
787
|
+
code: "PSL_UNKNOWN_FIELD_PRESET",
|
|
788
|
+
message: `${input.entityLabel} references unknown field preset "${input.helperPath}". Check the spelling against the available presets in the "${input.namespace}" namespace.`,
|
|
789
|
+
sourceId: input.sourceId,
|
|
790
|
+
span: input.span,
|
|
791
|
+
data: {
|
|
792
|
+
namespace: input.namespace,
|
|
793
|
+
helperPath: input.helperPath
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
663
797
|
function instantiatePslTypeConstructor(input) {
|
|
664
798
|
const helperPath = input.call.path.join(".");
|
|
665
799
|
const args = mapPslHelperArgs({
|
|
@@ -697,11 +831,15 @@ function pushUnsupportedTypeConstructorDiagnostic(input) {
|
|
|
697
831
|
function resolvePslTypeConstructorDescriptor(input) {
|
|
698
832
|
const descriptor = getAuthoringTypeConstructor(input.authoringContributions, input.call.path);
|
|
699
833
|
if (descriptor) return descriptor;
|
|
700
|
-
const
|
|
701
|
-
|
|
834
|
+
const uncomposedNamespace = checkUncomposedNamespace(input.call.path.join("."), input.composedExtensions, {
|
|
835
|
+
familyId: input.familyId,
|
|
836
|
+
targetId: input.targetId,
|
|
837
|
+
authoringContributions: input.authoringContributions
|
|
838
|
+
});
|
|
839
|
+
if (uncomposedNamespace) {
|
|
702
840
|
reportUncomposedNamespace({
|
|
703
841
|
subjectLabel: `Type constructor "${input.call.path.join(".")}"`,
|
|
704
|
-
namespace,
|
|
842
|
+
namespace: uncomposedNamespace,
|
|
705
843
|
sourceId: input.sourceId,
|
|
706
844
|
span: input.call.span,
|
|
707
845
|
diagnostics: input.diagnostics
|
|
@@ -716,10 +854,95 @@ function resolvePslTypeConstructorDescriptor(input) {
|
|
|
716
854
|
message: input.unsupportedMessage
|
|
717
855
|
});
|
|
718
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* Instantiates a field-preset call against its descriptor, coercing PSL AST arguments into the descriptor's typed argument shape and returning the preset's full set of contract contributions.
|
|
859
|
+
*
|
|
860
|
+
* 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.
|
|
861
|
+
*/
|
|
862
|
+
function instantiatePslFieldPreset(input) {
|
|
863
|
+
const helperPath = input.call.path.join(".");
|
|
864
|
+
const args = mapPslHelperArgs({
|
|
865
|
+
args: input.call.args,
|
|
866
|
+
descriptors: input.descriptor.args ?? [],
|
|
867
|
+
helperLabel: `preset "${helperPath}"`,
|
|
868
|
+
span: input.call.span,
|
|
869
|
+
diagnostics: input.diagnostics,
|
|
870
|
+
sourceId: input.sourceId,
|
|
871
|
+
entityLabel: input.entityLabel
|
|
872
|
+
});
|
|
873
|
+
if (!args) return;
|
|
874
|
+
try {
|
|
875
|
+
validateAuthoringHelperArguments(helperPath, input.descriptor.args, args);
|
|
876
|
+
const instantiated = instantiateAuthoringFieldPreset(input.descriptor, args);
|
|
877
|
+
return {
|
|
878
|
+
descriptor: {
|
|
879
|
+
codecId: instantiated.descriptor.codecId,
|
|
880
|
+
nativeType: instantiated.descriptor.nativeType,
|
|
881
|
+
...instantiated.descriptor.typeParams !== void 0 ? { typeParams: instantiated.descriptor.typeParams } : {}
|
|
882
|
+
},
|
|
883
|
+
nullable: instantiated.nullable,
|
|
884
|
+
...instantiated.default !== void 0 ? { default: instantiated.default } : {},
|
|
885
|
+
...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {},
|
|
886
|
+
id: instantiated.id,
|
|
887
|
+
unique: instantiated.unique
|
|
888
|
+
};
|
|
889
|
+
} catch (error) {
|
|
890
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
891
|
+
input.diagnostics.push({
|
|
892
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
893
|
+
message: `${input.entityLabel} preset "${helperPath}" ${message}`,
|
|
894
|
+
sourceId: input.sourceId,
|
|
895
|
+
span: input.call.span
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
719
900
|
function resolveFieldTypeDescriptor(input) {
|
|
720
901
|
if (input.field.typeConstructor) {
|
|
902
|
+
const presetDescriptor = getAuthoringFieldPreset(input.authoringContributions, input.field.typeConstructor.path);
|
|
903
|
+
if (presetDescriptor) {
|
|
904
|
+
const instantiated = instantiatePslFieldPreset({
|
|
905
|
+
call: input.field.typeConstructor,
|
|
906
|
+
descriptor: presetDescriptor,
|
|
907
|
+
diagnostics: input.diagnostics,
|
|
908
|
+
sourceId: input.sourceId,
|
|
909
|
+
entityLabel: input.entityLabel
|
|
910
|
+
});
|
|
911
|
+
if (!instantiated) return {
|
|
912
|
+
ok: false,
|
|
913
|
+
alreadyReported: true
|
|
914
|
+
};
|
|
915
|
+
const presetContributions = {
|
|
916
|
+
nullable: instantiated.nullable,
|
|
917
|
+
id: instantiated.id,
|
|
918
|
+
unique: instantiated.unique,
|
|
919
|
+
...instantiated.default !== void 0 ? { default: instantiated.default } : {},
|
|
920
|
+
...instantiated.executionDefaults !== void 0 ? { executionDefaults: instantiated.executionDefaults } : {}
|
|
921
|
+
};
|
|
922
|
+
return {
|
|
923
|
+
ok: true,
|
|
924
|
+
descriptor: instantiated.descriptor,
|
|
925
|
+
presetContributions
|
|
926
|
+
};
|
|
927
|
+
}
|
|
721
928
|
const helperPath = input.field.typeConstructor.path.join(".");
|
|
722
|
-
const
|
|
929
|
+
const namespacePrefix = input.field.typeConstructor.path.length > 1 ? input.field.typeConstructor.path[0] : void 0;
|
|
930
|
+
const typeDescriptor = getAuthoringTypeConstructor(input.authoringContributions, input.field.typeConstructor.path);
|
|
931
|
+
if (!typeDescriptor && namespacePrefix && hasRegisteredFieldNamespace(input.authoringContributions, namespacePrefix)) {
|
|
932
|
+
reportUnknownFieldPreset({
|
|
933
|
+
entityLabel: input.entityLabel,
|
|
934
|
+
namespace: namespacePrefix,
|
|
935
|
+
helperPath,
|
|
936
|
+
sourceId: input.sourceId,
|
|
937
|
+
span: input.field.typeConstructor.span,
|
|
938
|
+
diagnostics: input.diagnostics
|
|
939
|
+
});
|
|
940
|
+
return {
|
|
941
|
+
ok: false,
|
|
942
|
+
alreadyReported: true
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const descriptor = typeDescriptor ?? resolvePslTypeConstructorDescriptor({
|
|
723
946
|
call: input.field.typeConstructor,
|
|
724
947
|
authoringContributions: input.authoringContributions,
|
|
725
948
|
composedExtensions: input.composedExtensions,
|
|
@@ -730,13 +953,13 @@ function resolveFieldTypeDescriptor(input) {
|
|
|
730
953
|
unsupportedCode: "PSL_UNSUPPORTED_FIELD_TYPE",
|
|
731
954
|
unsupportedMessage: `${input.entityLabel} type constructor "${helperPath}" is not supported in SQL PSL provider v1`
|
|
732
955
|
});
|
|
733
|
-
if (!descriptor
|
|
956
|
+
if (!descriptor) return {
|
|
734
957
|
ok: false,
|
|
735
958
|
alreadyReported: true
|
|
736
959
|
};
|
|
737
960
|
const instantiated = instantiatePslTypeConstructor({
|
|
738
961
|
call: input.field.typeConstructor,
|
|
739
|
-
descriptor
|
|
962
|
+
descriptor,
|
|
740
963
|
diagnostics: input.diagnostics,
|
|
741
964
|
sourceId: input.sourceId,
|
|
742
965
|
entityLabel: input.entityLabel
|
|
@@ -985,6 +1208,15 @@ function lowerDefaultForField(input) {
|
|
|
985
1208
|
});
|
|
986
1209
|
return {};
|
|
987
1210
|
}
|
|
1211
|
+
if (generatorDescriptor.applicableCodecIds === void 0) {
|
|
1212
|
+
input.diagnostics.push({
|
|
1213
|
+
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
1214
|
+
message: `Default generator "${generatorDescriptor.id}" is not applicable to "@default(...)" lowering. Use the corresponding field preset (e.g. \`temporal.${generatorDescriptor.id === "timestampNow" ? "updatedAt" : generatorDescriptor.id}()\`) instead.`,
|
|
1215
|
+
sourceId: input.sourceId,
|
|
1216
|
+
span: expressionEntry.span
|
|
1217
|
+
});
|
|
1218
|
+
return {};
|
|
1219
|
+
}
|
|
988
1220
|
if (!generatorDescriptor.applicableCodecIds.includes(input.columnDescriptor.codecId)) {
|
|
989
1221
|
input.diagnostics.push({
|
|
990
1222
|
code: "PSL_INVALID_DEFAULT_APPLICABILITY",
|
|
@@ -994,7 +1226,7 @@ function lowerDefaultForField(input) {
|
|
|
994
1226
|
});
|
|
995
1227
|
return {};
|
|
996
1228
|
}
|
|
997
|
-
return {
|
|
1229
|
+
return { executionDefaults: { onCreate: lowered.value.generated } };
|
|
998
1230
|
}
|
|
999
1231
|
function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptors, scalarTypeDescriptors) {
|
|
1000
1232
|
if (field.typeRef && namedTypeDescriptors.has(field.typeRef)) return namedTypeDescriptors.get(field.typeRef);
|
|
@@ -1002,7 +1234,6 @@ function resolveColumnDescriptor(field, enumTypeDescriptors, namedTypeDescriptor
|
|
|
1002
1234
|
if (enumTypeDescriptors.has(field.typeName)) return enumTypeDescriptors.get(field.typeName);
|
|
1003
1235
|
return scalarTypeDescriptors.get(field.typeName);
|
|
1004
1236
|
}
|
|
1005
|
-
|
|
1006
1237
|
//#endregion
|
|
1007
1238
|
//#region src/psl-field-resolution.ts
|
|
1008
1239
|
const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
|
|
@@ -1012,12 +1243,21 @@ const BUILTIN_FIELD_ATTRIBUTE_NAMES = new Set([
|
|
|
1012
1243
|
"relation",
|
|
1013
1244
|
"map"
|
|
1014
1245
|
]);
|
|
1246
|
+
const REMOVED_ATTRIBUTE_RULES = new Map([["updatedAt", {
|
|
1247
|
+
hint: "Use `temporal.updatedAt()` as a field-preset call instead.",
|
|
1248
|
+
suppressWhen: (field) => field.typeConstructor?.path[0] === "temporal"
|
|
1249
|
+
}]]);
|
|
1250
|
+
{
|
|
1251
|
+
const overlap = [...REMOVED_ATTRIBUTE_RULES.keys()].filter((name) => BUILTIN_FIELD_ATTRIBUTE_NAMES.has(name));
|
|
1252
|
+
if (overlap.length > 0) throw new Error(`BUILTIN_FIELD_ATTRIBUTE_NAMES and REMOVED_ATTRIBUTE_RULES must not overlap. Names in both: ${overlap.join(", ")}`);
|
|
1253
|
+
}
|
|
1015
1254
|
function validateFieldAttributes(input) {
|
|
1016
1255
|
for (const attribute of input.field.attributes) {
|
|
1017
1256
|
if (BUILTIN_FIELD_ATTRIBUTE_NAMES.has(attribute.name)) continue;
|
|
1018
1257
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1019
1258
|
familyId: input.familyId,
|
|
1020
|
-
targetId: input.targetId
|
|
1259
|
+
targetId: input.targetId,
|
|
1260
|
+
authoringContributions: input.authoringContributions
|
|
1021
1261
|
});
|
|
1022
1262
|
if (uncomposedNamespace) {
|
|
1023
1263
|
reportUncomposedNamespace({
|
|
@@ -1029,9 +1269,12 @@ function validateFieldAttributes(input) {
|
|
|
1029
1269
|
});
|
|
1030
1270
|
continue;
|
|
1031
1271
|
}
|
|
1272
|
+
const baseMessage = `Field "${input.model.name}.${input.field.name}" uses unsupported attribute "@${attribute.name}"`;
|
|
1273
|
+
const removedRule = REMOVED_ATTRIBUTE_RULES.get(attribute.name);
|
|
1274
|
+
const message = removedRule && !removedRule.suppressWhen(input.field) ? `${baseMessage}. ${removedRule.hint}` : baseMessage;
|
|
1032
1275
|
input.diagnostics.push({
|
|
1033
1276
|
code: "PSL_UNSUPPORTED_FIELD_ATTRIBUTE",
|
|
1034
|
-
message
|
|
1277
|
+
message,
|
|
1035
1278
|
sourceId: input.sourceId,
|
|
1036
1279
|
span: attribute.span
|
|
1037
1280
|
});
|
|
@@ -1065,21 +1308,25 @@ function collectResolvedFields(input) {
|
|
|
1065
1308
|
const { model, mapping, enumTypeDescriptors, namedTypeDescriptors, modelNames, compositeTypeNames, composedExtensions, authoringContributions, familyId, targetId, defaultFunctionRegistry, generatorDescriptorById, diagnostics, sourceId, scalarTypeDescriptors } = input;
|
|
1066
1309
|
const resolvedFields = [];
|
|
1067
1310
|
for (const field of model.fields) {
|
|
1068
|
-
|
|
1311
|
+
const isModelField = modelNames.has(field.typeName);
|
|
1312
|
+
if (field.list && isModelField) continue;
|
|
1069
1313
|
validateFieldAttributes({
|
|
1070
1314
|
model,
|
|
1071
1315
|
field,
|
|
1072
1316
|
composedExtensions,
|
|
1317
|
+
authoringContributions,
|
|
1073
1318
|
diagnostics,
|
|
1074
1319
|
sourceId,
|
|
1075
1320
|
familyId,
|
|
1076
1321
|
targetId
|
|
1077
1322
|
});
|
|
1078
|
-
|
|
1323
|
+
const relationAttribute = getAttribute(field.attributes, "relation");
|
|
1324
|
+
if (isModelField && relationAttribute) continue;
|
|
1079
1325
|
const isValueObjectField = compositeTypeNames.has(field.typeName);
|
|
1080
1326
|
const isListField = field.list;
|
|
1081
1327
|
let descriptor;
|
|
1082
1328
|
let scalarCodecId;
|
|
1329
|
+
let presetContributions;
|
|
1083
1330
|
const resolveInput = {
|
|
1084
1331
|
field,
|
|
1085
1332
|
enumTypeDescriptors,
|
|
@@ -1105,6 +1352,15 @@ function collectResolvedFields(input) {
|
|
|
1105
1352
|
});
|
|
1106
1353
|
continue;
|
|
1107
1354
|
}
|
|
1355
|
+
if (resolved.presetContributions) {
|
|
1356
|
+
diagnostics.push({
|
|
1357
|
+
code: "PSL_PRESET_NOT_LIST",
|
|
1358
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call as a list element type. Presets cannot be list elements; remove "[]" or use a scalar type.`,
|
|
1359
|
+
sourceId,
|
|
1360
|
+
span: field.span
|
|
1361
|
+
});
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1108
1364
|
scalarCodecId = resolved.descriptor.codecId;
|
|
1109
1365
|
descriptor = scalarTypeDescriptors.get("Json");
|
|
1110
1366
|
} else {
|
|
@@ -1119,9 +1375,28 @@ function collectResolvedFields(input) {
|
|
|
1119
1375
|
continue;
|
|
1120
1376
|
}
|
|
1121
1377
|
descriptor = resolved.descriptor;
|
|
1378
|
+
presetContributions = resolved.presetContributions;
|
|
1122
1379
|
}
|
|
1123
1380
|
if (!descriptor) continue;
|
|
1381
|
+
if (presetContributions && field.optional) {
|
|
1382
|
+
diagnostics.push({
|
|
1383
|
+
code: "PSL_PRESET_NOT_OPTIONAL",
|
|
1384
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot be optional. Remove "?" or use a different field type.`,
|
|
1385
|
+
sourceId,
|
|
1386
|
+
span: field.span
|
|
1387
|
+
});
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1124
1390
|
const defaultAttribute = getAttribute(field.attributes, "default");
|
|
1391
|
+
if (presetContributions && defaultAttribute) {
|
|
1392
|
+
diagnostics.push({
|
|
1393
|
+
code: "PSL_PRESET_AND_DEFAULT_CONFLICT",
|
|
1394
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @default(...). The preset already specifies the default value.`,
|
|
1395
|
+
sourceId,
|
|
1396
|
+
span: defaultAttribute.span
|
|
1397
|
+
});
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1125
1400
|
const loweredDefault = defaultAttribute ? lowerDefaultForField({
|
|
1126
1401
|
modelName: model.name,
|
|
1127
1402
|
fieldName: field.name,
|
|
@@ -1132,8 +1407,9 @@ function collectResolvedFields(input) {
|
|
|
1132
1407
|
defaultFunctionRegistry,
|
|
1133
1408
|
diagnostics
|
|
1134
1409
|
}) : {};
|
|
1135
|
-
|
|
1136
|
-
|
|
1410
|
+
const loweredOnCreate = loweredDefault.executionDefaults?.onCreate;
|
|
1411
|
+
if (field.optional && loweredOnCreate) {
|
|
1412
|
+
const generatorDescription = loweredOnCreate.kind === "generator" ? `"${loweredOnCreate.id}"` : "for this field";
|
|
1137
1413
|
diagnostics.push({
|
|
1138
1414
|
code: "PSL_INVALID_DEFAULT_FUNCTION_ARGUMENT",
|
|
1139
1415
|
message: `Field "${model.name}.${field.name}" cannot be optional when using execution default ${generatorDescription}. Remove "?" or use a storage default.`,
|
|
@@ -1142,8 +1418,8 @@ function collectResolvedFields(input) {
|
|
|
1142
1418
|
});
|
|
1143
1419
|
continue;
|
|
1144
1420
|
}
|
|
1145
|
-
if (
|
|
1146
|
-
const generatedDescriptor = generatorDescriptorById.get(
|
|
1421
|
+
if (loweredOnCreate) {
|
|
1422
|
+
const generatedDescriptor = generatorDescriptorById.get(loweredOnCreate.id)?.resolveGeneratedColumnDescriptor?.({ generated: loweredOnCreate });
|
|
1147
1423
|
if (generatedDescriptor) descriptor = generatedDescriptor;
|
|
1148
1424
|
}
|
|
1149
1425
|
const mappedColumnName = mapping.fieldColumns.get(field.name) ?? field.name;
|
|
@@ -1153,14 +1429,35 @@ function collectResolvedFields(input) {
|
|
|
1153
1429
|
sourceId,
|
|
1154
1430
|
diagnostics
|
|
1155
1431
|
});
|
|
1432
|
+
let isIdField = Boolean(idAttribute);
|
|
1433
|
+
if (idAttribute && field.optional) {
|
|
1434
|
+
diagnostics.push({
|
|
1435
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1436
|
+
message: `Field "${model.name}.${field.name}" @id cannot be optional; primary key columns must be NOT NULL`,
|
|
1437
|
+
sourceId,
|
|
1438
|
+
span: idAttribute.span
|
|
1439
|
+
});
|
|
1440
|
+
isIdField = false;
|
|
1441
|
+
}
|
|
1442
|
+
if (presetContributions && idAttribute && !presetContributions.id) {
|
|
1443
|
+
diagnostics.push({
|
|
1444
|
+
code: "PSL_PRESET_AND_ID_CONFLICT",
|
|
1445
|
+
message: `Field "${model.name}.${field.name}" uses a field-preset call and cannot also declare @id. Use a preset that contributes id semantics, or drop @id.`,
|
|
1446
|
+
sourceId,
|
|
1447
|
+
span: idAttribute.span
|
|
1448
|
+
});
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
const fieldExecutionDefaults = presetContributions?.executionDefaults ?? loweredDefault.executionDefaults;
|
|
1452
|
+
const fieldDefaultValue = presetContributions?.default ?? loweredDefault.defaultValue;
|
|
1156
1453
|
resolvedFields.push({
|
|
1157
1454
|
field,
|
|
1158
1455
|
columnName: mappedColumnName,
|
|
1159
1456
|
descriptor,
|
|
1160
|
-
...ifDefined("defaultValue",
|
|
1161
|
-
...ifDefined("
|
|
1162
|
-
isId: Boolean(
|
|
1163
|
-
isUnique: Boolean(uniqueAttribute),
|
|
1457
|
+
...ifDefined("defaultValue", fieldDefaultValue),
|
|
1458
|
+
...ifDefined("executionDefaults", fieldExecutionDefaults),
|
|
1459
|
+
isId: isIdField || Boolean(presetContributions?.id),
|
|
1460
|
+
isUnique: Boolean(uniqueAttribute) || Boolean(presetContributions?.unique),
|
|
1164
1461
|
...ifDefined("idName", idName),
|
|
1165
1462
|
...ifDefined("uniqueName", uniqueName),
|
|
1166
1463
|
...ifDefined("many", isListField ? true : void 0),
|
|
@@ -1201,7 +1498,6 @@ function buildModelMappings(models, diagnostics, sourceId) {
|
|
|
1201
1498
|
}
|
|
1202
1499
|
return result;
|
|
1203
1500
|
}
|
|
1204
|
-
|
|
1205
1501
|
//#endregion
|
|
1206
1502
|
//#region src/psl-relation-resolution.ts
|
|
1207
1503
|
const REFERENTIAL_ACTION_MAP = {
|
|
@@ -1418,7 +1714,8 @@ function validateNavigationListFieldAttributes(input) {
|
|
|
1418
1714
|
if (attribute.name === "relation") continue;
|
|
1419
1715
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1420
1716
|
familyId: input.familyId,
|
|
1421
|
-
targetId: input.targetId
|
|
1717
|
+
targetId: input.targetId,
|
|
1718
|
+
authoringContributions: input.authoringContributions
|
|
1422
1719
|
});
|
|
1423
1720
|
if (uncomposedNamespace) {
|
|
1424
1721
|
reportUncomposedNamespace({
|
|
@@ -1441,7 +1738,6 @@ function validateNavigationListFieldAttributes(input) {
|
|
|
1441
1738
|
}
|
|
1442
1739
|
return valid;
|
|
1443
1740
|
}
|
|
1444
|
-
|
|
1445
1741
|
//#endregion
|
|
1446
1742
|
//#region src/interpreter.ts
|
|
1447
1743
|
function buildComposedExtensionPackRefs(target, extensionIds, extensionPackRefs = []) {
|
|
@@ -1531,7 +1827,8 @@ function validateNamedTypeAttributes(input) {
|
|
|
1531
1827
|
if (input.allowDbNativeType && attribute.name.startsWith("db.")) continue;
|
|
1532
1828
|
const uncomposedNamespace = checkUncomposedNamespace(attribute.name, input.composedExtensions, {
|
|
1533
1829
|
familyId: input.familyId,
|
|
1534
|
-
targetId: input.targetId
|
|
1830
|
+
targetId: input.targetId,
|
|
1831
|
+
authoringContributions: input.authoringContributions
|
|
1535
1832
|
});
|
|
1536
1833
|
if (uncomposedNamespace) {
|
|
1537
1834
|
reportUncomposedNamespace({
|
|
@@ -1562,16 +1859,17 @@ function resolveNamedTypeDeclarations(input) {
|
|
|
1562
1859
|
const namedTypeDescriptors = /* @__PURE__ */ new Map();
|
|
1563
1860
|
for (const declaration of input.declarations) {
|
|
1564
1861
|
if (declaration.typeConstructor) {
|
|
1565
|
-
const { hasUnsupportedNamedTypeAttribute
|
|
1862
|
+
const { hasUnsupportedNamedTypeAttribute } = validateNamedTypeAttributes({
|
|
1566
1863
|
declaration,
|
|
1567
1864
|
sourceId: input.sourceId,
|
|
1568
1865
|
diagnostics: input.diagnostics,
|
|
1569
1866
|
composedExtensions: input.composedExtensions,
|
|
1867
|
+
authoringContributions: input.authoringContributions,
|
|
1570
1868
|
allowDbNativeType: false,
|
|
1571
1869
|
familyId: input.familyId,
|
|
1572
1870
|
targetId: input.targetId
|
|
1573
1871
|
});
|
|
1574
|
-
if (hasUnsupportedNamedTypeAttribute
|
|
1872
|
+
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
1575
1873
|
const helperPath = declaration.typeConstructor.path.join(".");
|
|
1576
1874
|
const typeConstructor = resolvePslTypeConstructorDescriptor({
|
|
1577
1875
|
call: declaration.typeConstructor,
|
|
@@ -1626,13 +1924,14 @@ function resolveNamedTypeDeclarations(input) {
|
|
|
1626
1924
|
sourceId: input.sourceId,
|
|
1627
1925
|
diagnostics: input.diagnostics,
|
|
1628
1926
|
composedExtensions: input.composedExtensions,
|
|
1927
|
+
authoringContributions: input.authoringContributions,
|
|
1629
1928
|
allowDbNativeType: true,
|
|
1630
1929
|
familyId: input.familyId,
|
|
1631
1930
|
targetId: input.targetId
|
|
1632
1931
|
});
|
|
1633
1932
|
if (hasUnsupportedNamedTypeAttribute) continue;
|
|
1634
1933
|
if (dbNativeTypeAttribute) {
|
|
1635
|
-
const descriptor
|
|
1934
|
+
const descriptor = resolveDbNativeTypeAttribute({
|
|
1636
1935
|
attribute: dbNativeTypeAttribute,
|
|
1637
1936
|
baseType,
|
|
1638
1937
|
baseDescriptor,
|
|
@@ -1640,12 +1939,12 @@ function resolveNamedTypeDeclarations(input) {
|
|
|
1640
1939
|
sourceId: input.sourceId,
|
|
1641
1940
|
entityLabel: `Named type "${declaration.name}"`
|
|
1642
1941
|
});
|
|
1643
|
-
if (!descriptor
|
|
1644
|
-
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor
|
|
1942
|
+
if (!descriptor) continue;
|
|
1943
|
+
namedTypeDescriptors.set(declaration.name, toNamedTypeFieldDescriptor(declaration.name, descriptor));
|
|
1645
1944
|
storageTypes[declaration.name] = {
|
|
1646
|
-
codecId: descriptor
|
|
1647
|
-
nativeType: descriptor
|
|
1648
|
-
typeParams: descriptor
|
|
1945
|
+
codecId: descriptor.codecId,
|
|
1946
|
+
nativeType: descriptor.nativeType,
|
|
1947
|
+
typeParams: descriptor.typeParams ?? {}
|
|
1649
1948
|
};
|
|
1650
1949
|
continue;
|
|
1651
1950
|
}
|
|
@@ -1682,16 +1981,20 @@ function buildModelNodeFromPsl(input) {
|
|
|
1682
1981
|
sourceId,
|
|
1683
1982
|
scalarTypeDescriptors: input.scalarTypeDescriptors
|
|
1684
1983
|
});
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
if (primaryKeyColumns.length === 0 && !isVariantModel) diagnostics.push({
|
|
1690
|
-
code: "PSL_MISSING_PRIMARY_KEY",
|
|
1691
|
-
message: `Model "${model.name}" must declare at least one @id field for SQL provider`,
|
|
1984
|
+
const inlineIdFields = resolvedFields.filter((field) => field.isId);
|
|
1985
|
+
if (inlineIdFields.length > 1) diagnostics.push({
|
|
1986
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1987
|
+
message: `Model "${model.name}" cannot declare inline @id on multiple fields; use model-level @@id([...]) for composite identity`,
|
|
1692
1988
|
sourceId,
|
|
1693
1989
|
span: model.span
|
|
1694
1990
|
});
|
|
1991
|
+
const singleInlineIdField = inlineIdFields.length === 1 ? inlineIdFields[0] : void 0;
|
|
1992
|
+
let primaryKey = singleInlineIdField ? {
|
|
1993
|
+
columns: [singleInlineIdField.columnName],
|
|
1994
|
+
...ifDefined("name", singleInlineIdField.idName)
|
|
1995
|
+
} : void 0;
|
|
1996
|
+
const hasInlinePrimaryKey = primaryKey !== void 0;
|
|
1997
|
+
let blockPrimaryKeyDeclared = false;
|
|
1695
1998
|
const resultBackrelationCandidates = [];
|
|
1696
1999
|
for (const field of model.fields) {
|
|
1697
2000
|
if (!field.list || !input.modelNames.has(field.typeName)) continue;
|
|
@@ -1700,6 +2003,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1700
2003
|
field,
|
|
1701
2004
|
sourceId,
|
|
1702
2005
|
composedExtensions: input.composedExtensions,
|
|
2006
|
+
authoringContributions: input.authoringContributions,
|
|
1703
2007
|
diagnostics,
|
|
1704
2008
|
familyId: input.familyId,
|
|
1705
2009
|
targetId: input.targetId
|
|
@@ -1757,15 +2061,98 @@ function buildModelNodeFromPsl(input) {
|
|
|
1757
2061
|
for (const modelAttribute of model.attributes) {
|
|
1758
2062
|
if (modelAttribute.name === "map") continue;
|
|
1759
2063
|
if (modelAttribute.name === "discriminator" || modelAttribute.name === "base") continue;
|
|
2064
|
+
const attributeLabel = `Model "${model.name}" @@${modelAttribute.name}`;
|
|
2065
|
+
if (modelAttribute.name === "id") {
|
|
2066
|
+
if (blockPrimaryKeyDeclared) {
|
|
2067
|
+
diagnostics.push({
|
|
2068
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2069
|
+
message: `Model "${model.name}" declares @@id more than once`,
|
|
2070
|
+
sourceId,
|
|
2071
|
+
span: modelAttribute.span
|
|
2072
|
+
});
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
if (hasInlinePrimaryKey) {
|
|
2076
|
+
diagnostics.push({
|
|
2077
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2078
|
+
message: `Model "${model.name}" cannot declare both field-level @id and model-level @@id`,
|
|
2079
|
+
sourceId,
|
|
2080
|
+
span: modelAttribute.span
|
|
2081
|
+
});
|
|
2082
|
+
blockPrimaryKeyDeclared = true;
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
const fieldNames = parseAttributeFieldList({
|
|
2086
|
+
attribute: modelAttribute,
|
|
2087
|
+
sourceId,
|
|
2088
|
+
diagnostics,
|
|
2089
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2090
|
+
entityLabel: attributeLabel
|
|
2091
|
+
});
|
|
2092
|
+
if (!fieldNames) continue;
|
|
2093
|
+
const duplicateFieldName = findDuplicateFieldName(fieldNames);
|
|
2094
|
+
if (duplicateFieldName !== void 0) {
|
|
2095
|
+
diagnostics.push({
|
|
2096
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2097
|
+
message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
|
|
2098
|
+
sourceId,
|
|
2099
|
+
span: modelAttribute.span
|
|
2100
|
+
});
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
const nullableFieldName = fieldNames.find((name) => model.fields.find((f) => f.name === name)?.optional === true);
|
|
2104
|
+
if (nullableFieldName !== void 0) {
|
|
2105
|
+
diagnostics.push({
|
|
2106
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2107
|
+
message: `${attributeLabel} cannot include optional field "${nullableFieldName}"; primary key columns must be NOT NULL`,
|
|
2108
|
+
sourceId,
|
|
2109
|
+
span: modelAttribute.span
|
|
2110
|
+
});
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
const columnNames = mapFieldNamesToColumns({
|
|
2114
|
+
modelName: model.name,
|
|
2115
|
+
fieldNames,
|
|
2116
|
+
mapping,
|
|
2117
|
+
sourceId,
|
|
2118
|
+
diagnostics,
|
|
2119
|
+
span: modelAttribute.span,
|
|
2120
|
+
entityLabel: attributeLabel
|
|
2121
|
+
});
|
|
2122
|
+
if (!columnNames) continue;
|
|
2123
|
+
primaryKey = {
|
|
2124
|
+
columns: columnNames,
|
|
2125
|
+
...ifDefined("name", parseConstraintMapArgument({
|
|
2126
|
+
attribute: modelAttribute,
|
|
2127
|
+
sourceId,
|
|
2128
|
+
diagnostics,
|
|
2129
|
+
entityLabel: attributeLabel,
|
|
2130
|
+
span: modelAttribute.span,
|
|
2131
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
2132
|
+
}))
|
|
2133
|
+
};
|
|
2134
|
+
blockPrimaryKeyDeclared = true;
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
1760
2137
|
if (modelAttribute.name === "unique" || modelAttribute.name === "index") {
|
|
1761
2138
|
const fieldNames = parseAttributeFieldList({
|
|
1762
2139
|
attribute: modelAttribute,
|
|
1763
2140
|
sourceId,
|
|
1764
2141
|
diagnostics,
|
|
1765
2142
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
1766
|
-
|
|
2143
|
+
entityLabel: attributeLabel
|
|
1767
2144
|
});
|
|
1768
2145
|
if (!fieldNames) continue;
|
|
2146
|
+
const duplicateFieldName = findDuplicateFieldName(fieldNames);
|
|
2147
|
+
if (duplicateFieldName !== void 0) {
|
|
2148
|
+
diagnostics.push({
|
|
2149
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2150
|
+
message: `${attributeLabel} list contains duplicate field "${duplicateFieldName}"`,
|
|
2151
|
+
sourceId,
|
|
2152
|
+
span: modelAttribute.span
|
|
2153
|
+
});
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
1769
2156
|
const columnNames = mapFieldNamesToColumns({
|
|
1770
2157
|
modelName: model.name,
|
|
1771
2158
|
fieldNames,
|
|
@@ -1773,14 +2160,14 @@ function buildModelNodeFromPsl(input) {
|
|
|
1773
2160
|
sourceId,
|
|
1774
2161
|
diagnostics,
|
|
1775
2162
|
span: modelAttribute.span,
|
|
1776
|
-
|
|
2163
|
+
entityLabel: attributeLabel
|
|
1777
2164
|
});
|
|
1778
2165
|
if (!columnNames) continue;
|
|
1779
2166
|
const constraintName = parseConstraintMapArgument({
|
|
1780
2167
|
attribute: modelAttribute,
|
|
1781
2168
|
sourceId,
|
|
1782
2169
|
diagnostics,
|
|
1783
|
-
entityLabel:
|
|
2170
|
+
entityLabel: attributeLabel,
|
|
1784
2171
|
span: modelAttribute.span,
|
|
1785
2172
|
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT"
|
|
1786
2173
|
});
|
|
@@ -1788,15 +2175,58 @@ function buildModelNodeFromPsl(input) {
|
|
|
1788
2175
|
columns: columnNames,
|
|
1789
2176
|
...ifDefined("name", constraintName)
|
|
1790
2177
|
});
|
|
1791
|
-
else
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2178
|
+
else {
|
|
2179
|
+
const indexEntityLabel = `Model "${model.name}" @@index`;
|
|
2180
|
+
const rawTypeArg = getNamedArgument(modelAttribute, "type");
|
|
2181
|
+
let indexType;
|
|
2182
|
+
if (rawTypeArg !== void 0) {
|
|
2183
|
+
const parsed = parseQuotedStringLiteral(rawTypeArg);
|
|
2184
|
+
if (parsed === void 0) {
|
|
2185
|
+
diagnostics.push({
|
|
2186
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2187
|
+
message: `${indexEntityLabel} type argument must be a quoted string literal`,
|
|
2188
|
+
sourceId,
|
|
2189
|
+
span: modelAttribute.span
|
|
2190
|
+
});
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
indexType = parsed;
|
|
2194
|
+
}
|
|
2195
|
+
const rawOptionsArg = getNamedArgument(modelAttribute, "options");
|
|
2196
|
+
let indexOptions;
|
|
2197
|
+
if (rawOptionsArg !== void 0) {
|
|
2198
|
+
if (indexType === void 0) {
|
|
2199
|
+
diagnostics.push({
|
|
2200
|
+
code: "PSL_INVALID_ATTRIBUTE_ARGUMENT",
|
|
2201
|
+
message: `${indexEntityLabel} options argument requires a type argument`,
|
|
2202
|
+
sourceId,
|
|
2203
|
+
span: modelAttribute.span
|
|
2204
|
+
});
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
const parsed = parseObjectLiteralStringMap({
|
|
2208
|
+
raw: rawOptionsArg,
|
|
2209
|
+
diagnostics,
|
|
2210
|
+
sourceId,
|
|
2211
|
+
span: modelAttribute.span,
|
|
2212
|
+
entityLabel: indexEntityLabel
|
|
2213
|
+
});
|
|
2214
|
+
if (parsed === void 0) continue;
|
|
2215
|
+
indexOptions = parsed;
|
|
2216
|
+
}
|
|
2217
|
+
indexNodes.push({
|
|
2218
|
+
columns: columnNames,
|
|
2219
|
+
...ifDefined("name", constraintName),
|
|
2220
|
+
...ifDefined("type", indexType),
|
|
2221
|
+
...ifDefined("options", indexOptions)
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
1795
2224
|
continue;
|
|
1796
2225
|
}
|
|
1797
2226
|
const uncomposedNamespace = checkUncomposedNamespace(modelAttribute.name, input.composedExtensions, {
|
|
1798
2227
|
familyId: input.familyId,
|
|
1799
|
-
targetId: input.targetId
|
|
2228
|
+
targetId: input.targetId,
|
|
2229
|
+
authoringContributions: input.authoringContributions
|
|
1800
2230
|
});
|
|
1801
2231
|
if (uncomposedNamespace) {
|
|
1802
2232
|
reportUncomposedNamespace({
|
|
@@ -1861,7 +2291,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1861
2291
|
sourceId,
|
|
1862
2292
|
diagnostics,
|
|
1863
2293
|
span: relationAttribute.relation.span,
|
|
1864
|
-
|
|
2294
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1865
2295
|
});
|
|
1866
2296
|
if (!localColumns) continue;
|
|
1867
2297
|
const referencedColumns = mapFieldNamesToColumns({
|
|
@@ -1871,7 +2301,7 @@ function buildModelNodeFromPsl(input) {
|
|
|
1871
2301
|
sourceId,
|
|
1872
2302
|
diagnostics,
|
|
1873
2303
|
span: relationAttribute.relation.span,
|
|
1874
|
-
|
|
2304
|
+
entityLabel: `Relation field "${model.name}.${relationAttribute.field.name}"`
|
|
1875
2305
|
});
|
|
1876
2306
|
if (!referencedColumns) continue;
|
|
1877
2307
|
if (localColumns.length !== referencedColumns.length) {
|
|
@@ -1933,12 +2363,9 @@ function buildModelNodeFromPsl(input) {
|
|
|
1933
2363
|
descriptor: resolvedField.descriptor,
|
|
1934
2364
|
nullable: resolvedField.field.optional,
|
|
1935
2365
|
...ifDefined("default", resolvedField.defaultValue),
|
|
1936
|
-
...ifDefined("
|
|
2366
|
+
...ifDefined("executionDefaults", resolvedField.executionDefaults)
|
|
1937
2367
|
})),
|
|
1938
|
-
...
|
|
1939
|
-
columns: primaryKeyColumns,
|
|
1940
|
-
...ifDefined("name", primaryKeyName)
|
|
1941
|
-
} } : {},
|
|
2368
|
+
...ifDefined("id", primaryKey),
|
|
1942
2369
|
...uniqueConstraints.length > 0 ? { uniques: uniqueConstraints } : {},
|
|
1943
2370
|
...indexNodes.length > 0 ? { indexes: indexNodes } : {},
|
|
1944
2371
|
...foreignKeyNodes.length > 0 ? { foreignKeys: foreignKeyNodes } : {}
|
|
@@ -2337,7 +2764,7 @@ function interpretPslDocumentToSqlContract(input) {
|
|
|
2337
2764
|
...Object.keys(valueObjects).length > 0 ? { valueObjects } : {}
|
|
2338
2765
|
});
|
|
2339
2766
|
}
|
|
2340
|
-
|
|
2341
2767
|
//#endregion
|
|
2342
2768
|
export { interpretPslDocumentToSqlContract as t };
|
|
2343
|
-
|
|
2769
|
+
|
|
2770
|
+
//# sourceMappingURL=interpreter-ijCjxhaU.mjs.map
|