@prisma-next/framework-components 0.13.0 → 0.14.0-dev.2
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/dist/authoring.d.mts +2 -2
- package/dist/authoring.mjs +1 -1
- package/dist/{codec-DCQAerzB.d.mts → codec-types-yY3eSmi0.d.mts} +90 -67
- package/dist/codec-types-yY3eSmi0.d.mts.map +1 -0
- package/dist/codec.d.mts +31 -2
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +2 -1
- package/dist/codec.mjs.map +1 -1
- package/dist/components.d.mts +1 -1
- package/dist/control.d.mts +13 -25
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +12 -2
- package/dist/control.mjs.map +1 -1
- package/dist/{emission-types-vfpSTe63.d.mts → emission-types-C561PwcN.d.mts} +4 -4
- package/dist/emission-types-C561PwcN.d.mts.map +1 -0
- package/dist/emission.d.mts +1 -1
- package/dist/execution.d.mts +1 -1
- package/dist/{framework-authoring-R0TYCkvG.d.mts → framework-authoring-CJC4jwGo.d.mts} +121 -9
- package/dist/framework-authoring-CJC4jwGo.d.mts.map +1 -0
- package/dist/{framework-authoring-CnwPJCO4.mjs → framework-authoring-Dv5F3EFC.mjs} +51 -24
- package/dist/framework-authoring-Dv5F3EFC.mjs.map +1 -0
- package/dist/{framework-components-DDQXmW0b.d.mts → framework-components-5hA9ij1v.d.mts} +3 -3
- package/dist/{framework-components-DDQXmW0b.d.mts.map → framework-components-5hA9ij1v.d.mts.map} +1 -1
- package/dist/ir.d.mts +34 -4
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +51 -6
- package/dist/ir.mjs.map +1 -1
- package/dist/{psl-ast-Cn50B-UG.d.mts → psl-ast-B-C_9dWr.d.mts} +11 -37
- package/dist/psl-ast-B-C_9dWr.d.mts.map +1 -0
- package/dist/psl-ast.d.mts +4 -4
- package/dist/psl-ast.mjs +18 -32
- package/dist/psl-ast.mjs.map +1 -1
- package/dist/resolve-codec-D8EPZosv.mjs +59 -0
- package/dist/resolve-codec-D8EPZosv.mjs.map +1 -0
- package/dist/runtime-error-B2gWOtgH.mjs +37 -0
- package/dist/runtime-error-B2gWOtgH.mjs.map +1 -0
- package/dist/runtime.d.mts +12 -10
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -33
- package/dist/runtime.mjs.map +1 -1
- package/package.json +7 -7
- package/src/control/control-migration-types.ts +5 -22
- package/src/control/control-stack.ts +28 -3
- package/src/control/emission-types.ts +3 -3
- package/src/control/psl-ast.ts +10 -61
- package/src/control/psl-extension-block-validator.ts +11 -9
- package/src/execution/runtime-error.ts +5 -55
- package/src/exports/authoring.ts +1 -0
- package/src/exports/codec.ts +7 -0
- package/src/exports/control.ts +0 -1
- package/src/exports/ir.ts +3 -1
- package/src/ir/entity-kind.ts +54 -0
- package/src/ir/storage.ts +32 -6
- package/src/shared/codec-descriptor.ts +5 -0
- package/src/shared/codec-types.ts +21 -1
- package/src/shared/framework-authoring.ts +118 -35
- package/src/shared/psl-extension-block.ts +85 -5
- package/src/shared/resolve-codec.ts +86 -0
- package/src/shared/runtime-error.ts +50 -0
- package/dist/codec-DCQAerzB.d.mts.map +0 -1
- package/dist/emission-types-vfpSTe63.d.mts.map +0 -1
- package/dist/framework-authoring-CnwPJCO4.mjs.map +0 -1
- package/dist/framework-authoring-R0TYCkvG.d.mts.map +0 -1
- package/dist/psl-ast-Cn50B-UG.d.mts.map +0 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
11
11
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
12
12
|
import type { Type } from 'arktype';
|
|
13
|
+
import type { CodecLookup } from './codec-types';
|
|
13
14
|
import type { PslBlockParam } from './psl-extension-block';
|
|
14
15
|
|
|
15
16
|
export type AuthoringArgRef = {
|
|
@@ -109,9 +110,30 @@ export type AuthoringFieldNamespace = {
|
|
|
109
110
|
* discover what the factory actually needs to read (codec lookup,
|
|
110
111
|
* namespace registry, …).
|
|
111
112
|
*/
|
|
113
|
+
/**
|
|
114
|
+
* A write-only sink that a factory may push authoring-time diagnostics into.
|
|
115
|
+
* The concrete type pushed must be structurally compatible with whatever the
|
|
116
|
+
* consumer accumulates (typically `ContractSourceDiagnostic[]`); the framework
|
|
117
|
+
* layer deliberately does not depend on that concrete type.
|
|
118
|
+
*/
|
|
119
|
+
export interface AuthoringDiagnosticSink {
|
|
120
|
+
push(d: {
|
|
121
|
+
readonly code: string;
|
|
122
|
+
readonly message: string;
|
|
123
|
+
readonly sourceId: string;
|
|
124
|
+
readonly span?: unknown;
|
|
125
|
+
}): void;
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
export interface AuthoringEntityContext {
|
|
113
129
|
readonly family: string;
|
|
114
130
|
readonly target: string;
|
|
131
|
+
/** Codec registry available to factories that need to validate or decode values. */
|
|
132
|
+
readonly codecLookup?: CodecLookup;
|
|
133
|
+
/** Source file identifier threaded into diagnostics emitted by the factory. */
|
|
134
|
+
readonly sourceId?: string;
|
|
135
|
+
/** Push channel for authoring-time diagnostics emitted by the factory. */
|
|
136
|
+
readonly diagnostics?: AuthoringDiagnosticSink;
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
export interface AuthoringEntityTypeTemplateOutput {
|
|
@@ -186,6 +208,21 @@ export interface AuthoringPslBlockDescriptor {
|
|
|
186
208
|
readonly discriminator: string;
|
|
187
209
|
readonly name: { readonly required: boolean };
|
|
188
210
|
readonly parameters: Record<string, PslBlockParam>;
|
|
211
|
+
/**
|
|
212
|
+
* When `true`, the block body accepts a variadic tail of parameters beyond
|
|
213
|
+
* the declared set. The block body may contain: fields (model-style),
|
|
214
|
+
* `key = value` parameters, and `@@` attributes. With `variadicParameters`,
|
|
215
|
+
* bare identifiers (keys without a `= value`) and undeclared `key = value`
|
|
216
|
+
* pairs flow into the variadic tail — their semantics belong to the
|
|
217
|
+
* lowering, not the parser.
|
|
218
|
+
*
|
|
219
|
+
* A key that IS declared in `parameters` must still be supplied as
|
|
220
|
+
* `key = value`; a bare occurrence of a declared key is a diagnostic.
|
|
221
|
+
*
|
|
222
|
+
* When `false` (default), the validator emits `PSL_EXTENSION_UNKNOWN_PARAMETER`
|
|
223
|
+
* for keys absent from `parameters`.
|
|
224
|
+
*/
|
|
225
|
+
readonly variadicParameters?: boolean;
|
|
189
226
|
}
|
|
190
227
|
|
|
191
228
|
export type AuthoringPslBlockDescriptorNamespace = {
|
|
@@ -329,13 +366,29 @@ export function hasRegisteredFieldNamespace(
|
|
|
329
366
|
return !isAuthoringFieldPresetDescriptor(contributions.field[namespace]);
|
|
330
367
|
}
|
|
331
368
|
|
|
332
|
-
function
|
|
333
|
-
|
|
369
|
+
function isCopyableNamespaceObject(value: unknown): value is Record<string, unknown> {
|
|
370
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
371
|
+
const proto: unknown = Object.getPrototypeOf(value);
|
|
372
|
+
return proto === Object.prototype || proto === null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function deepCopyNamespace(
|
|
376
|
+
source: Record<string, unknown>,
|
|
377
|
+
isLeafDescriptor: (value: unknown) => boolean,
|
|
378
|
+
): Record<string, unknown> {
|
|
379
|
+
const copy: Record<string, unknown> = {};
|
|
380
|
+
for (const [key, value] of Object.entries(source)) {
|
|
381
|
+
copy[key] =
|
|
382
|
+
isCopyableNamespaceObject(value) && !isLeafDescriptor(value)
|
|
383
|
+
? deepCopyNamespace(value, isLeafDescriptor)
|
|
384
|
+
: value;
|
|
385
|
+
}
|
|
386
|
+
return copy;
|
|
334
387
|
}
|
|
335
388
|
|
|
336
389
|
/**
|
|
337
390
|
* Merges `source` into `target` recursively at the descriptor-namespace
|
|
338
|
-
* level. `
|
|
391
|
+
* level. `isLeafDescriptor` decides which values are descriptors (terminal
|
|
339
392
|
* merge points; same-path registrations across components are reported
|
|
340
393
|
* as duplicates) versus sub-namespaces (recursion targets).
|
|
341
394
|
*
|
|
@@ -355,7 +408,7 @@ export function mergeAuthoringNamespaces(
|
|
|
355
408
|
target: Record<string, unknown>,
|
|
356
409
|
source: Record<string, unknown>,
|
|
357
410
|
path: readonly string[],
|
|
358
|
-
|
|
411
|
+
isLeafDescriptor: (value: unknown) => boolean,
|
|
359
412
|
label: string,
|
|
360
413
|
): void {
|
|
361
414
|
const assertSafePath = (currentPath: readonly string[]) => {
|
|
@@ -376,12 +429,19 @@ export function mergeAuthoringNamespaces(
|
|
|
376
429
|
const existingValue = hasExistingValue ? target[key] : undefined;
|
|
377
430
|
|
|
378
431
|
if (!hasExistingValue) {
|
|
379
|
-
|
|
432
|
+
// Deep-copy plain-object sub-namespaces so subsequent merges don't mutate
|
|
433
|
+
// objects owned by source packs. Leaf descriptors and class instances are
|
|
434
|
+
// passed by reference — leaves are identity values; class instances carry
|
|
435
|
+
// prototype getters that spread would destroy.
|
|
436
|
+
target[key] =
|
|
437
|
+
isCopyableNamespaceObject(sourceValue) && !isLeafDescriptor(sourceValue)
|
|
438
|
+
? deepCopyNamespace(sourceValue, isLeafDescriptor)
|
|
439
|
+
: sourceValue;
|
|
380
440
|
continue;
|
|
381
441
|
}
|
|
382
442
|
|
|
383
|
-
const existingIsLeaf =
|
|
384
|
-
const sourceIsLeaf =
|
|
443
|
+
const existingIsLeaf = isLeafDescriptor(existingValue);
|
|
444
|
+
const sourceIsLeaf = isLeafDescriptor(sourceValue);
|
|
385
445
|
|
|
386
446
|
if (existingIsLeaf || sourceIsLeaf) {
|
|
387
447
|
throw new Error(
|
|
@@ -389,17 +449,17 @@ export function mergeAuthoringNamespaces(
|
|
|
389
449
|
);
|
|
390
450
|
}
|
|
391
451
|
|
|
392
|
-
if (!
|
|
452
|
+
if (!isCopyableNamespaceObject(existingValue) || !isCopyableNamespaceObject(sourceValue)) {
|
|
393
453
|
throw new Error(
|
|
394
454
|
`Invalid authoring ${label} helper "${currentPath.join('.')}". Expected a sub-namespace object or a recognized descriptor; received a malformed value.`,
|
|
395
455
|
);
|
|
396
456
|
}
|
|
397
457
|
|
|
398
|
-
mergeAuthoringNamespaces(existingValue, sourceValue, currentPath,
|
|
458
|
+
mergeAuthoringNamespaces(existingValue, sourceValue, currentPath, isLeafDescriptor, label);
|
|
399
459
|
}
|
|
400
460
|
}
|
|
401
461
|
|
|
402
|
-
function
|
|
462
|
+
function collectDescriptorPaths(
|
|
403
463
|
namespace: Readonly<Record<string, unknown>>,
|
|
404
464
|
isLeaf: (value: unknown) => boolean,
|
|
405
465
|
path: readonly string[] = [],
|
|
@@ -413,29 +473,25 @@ function collectAuthoringLeafPaths(
|
|
|
413
473
|
}
|
|
414
474
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
415
475
|
paths.push(
|
|
416
|
-
...
|
|
417
|
-
value as Readonly<Record<string, unknown>>,
|
|
418
|
-
isLeaf,
|
|
419
|
-
currentPath,
|
|
420
|
-
),
|
|
476
|
+
...collectDescriptorPaths(value as Readonly<Record<string, unknown>>, isLeaf, currentPath),
|
|
421
477
|
);
|
|
422
478
|
}
|
|
423
479
|
}
|
|
424
480
|
return paths;
|
|
425
481
|
}
|
|
426
482
|
|
|
427
|
-
interface
|
|
483
|
+
interface DescriptorEntry {
|
|
428
484
|
readonly path: string;
|
|
429
485
|
readonly discriminator: string;
|
|
430
486
|
}
|
|
431
487
|
|
|
432
|
-
function
|
|
488
|
+
function collectDescriptorEntries(
|
|
433
489
|
namespace: Readonly<Record<string, unknown>>,
|
|
434
490
|
isLeaf: (value: unknown) => boolean,
|
|
435
491
|
label: string,
|
|
436
492
|
path: readonly string[] = [],
|
|
437
|
-
):
|
|
438
|
-
const entries:
|
|
493
|
+
): DescriptorEntry[] {
|
|
494
|
+
const entries: DescriptorEntry[] = [];
|
|
439
495
|
for (const [key, value] of Object.entries(namespace)) {
|
|
440
496
|
const currentPath = [...path, key];
|
|
441
497
|
if (isLeaf(value)) {
|
|
@@ -479,7 +535,7 @@ function collectAuthoringLeafDiscriminators(
|
|
|
479
535
|
);
|
|
480
536
|
}
|
|
481
537
|
}
|
|
482
|
-
entries.push(...
|
|
538
|
+
entries.push(...collectDescriptorEntries(record, isLeaf, label, currentPath));
|
|
483
539
|
}
|
|
484
540
|
}
|
|
485
541
|
return entries;
|
|
@@ -491,7 +547,7 @@ function collectAuthoringLeafDiscriminators(
|
|
|
491
547
|
* lowering factory lookup dispatches by discriminator, so one would silently
|
|
492
548
|
* shadow the other. Catch duplicates before building any dispatch map.
|
|
493
549
|
*/
|
|
494
|
-
function assertUniqueDiscriminators(entries: readonly
|
|
550
|
+
function assertUniqueDiscriminators(entries: readonly DescriptorEntry[], label: string): void {
|
|
495
551
|
const seen = new Map<string, string>();
|
|
496
552
|
for (const { path, discriminator } of entries) {
|
|
497
553
|
const existing = seen.get(discriminator);
|
|
@@ -504,23 +560,50 @@ function assertUniqueDiscriminators(entries: readonly AuthoringLeafEntry[], labe
|
|
|
504
560
|
}
|
|
505
561
|
}
|
|
506
562
|
|
|
563
|
+
function collectPslBlockDescriptorEntries(
|
|
564
|
+
namespace: Readonly<Record<string, unknown>>,
|
|
565
|
+
path: readonly string[] = [],
|
|
566
|
+
): DescriptorEntry[] {
|
|
567
|
+
const entries: DescriptorEntry[] = [];
|
|
568
|
+
for (const [key, value] of Object.entries(namespace)) {
|
|
569
|
+
const currentPath = [...path, key];
|
|
570
|
+
if (isAuthoringPslBlockDescriptor(value)) {
|
|
571
|
+
entries.push({
|
|
572
|
+
path: currentPath.join('.'),
|
|
573
|
+
discriminator: value.discriminator,
|
|
574
|
+
});
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
578
|
+
const record = blindCast<
|
|
579
|
+
Readonly<Record<string, unknown>>,
|
|
580
|
+
'walker descends into psl block namespace'
|
|
581
|
+
>(value);
|
|
582
|
+
const hasKind = record['kind'] === 'pslBlock';
|
|
583
|
+
const hasKeyword = typeof record['keyword'] === 'string';
|
|
584
|
+
const hasDiscriminator = typeof record['discriminator'] === 'string';
|
|
585
|
+
if (hasKind || (hasKeyword && hasDiscriminator)) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`Malformed authoring pslBlock contribution at "${currentPath.join('.')}". The value carries descriptor keys (kind/keyword/discriminator) but does not satisfy the pslBlock descriptor shape. Fix the contribution so it is a complete descriptor, or remove the stray keys if it was meant to be a sub-namespace.`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
entries.push(...collectPslBlockDescriptorEntries(record, currentPath));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return entries;
|
|
594
|
+
}
|
|
595
|
+
|
|
507
596
|
/**
|
|
508
|
-
* Every `pslBlockDescriptors` entry
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* — an `entityTypes` factory may stand alone (e.g. `enum`, reachable from
|
|
512
|
-
* the TypeScript builder without any PSL block).
|
|
597
|
+
* Every `pslBlockDescriptors` entry requires a matching `entityTypes` factory
|
|
598
|
+
* with the same discriminator. An `entityTypes` factory may stand alone (e.g.
|
|
599
|
+
* `enum`, reachable from the TypeScript builder without any PSL block).
|
|
513
600
|
*/
|
|
514
601
|
function assertPslBlocksHaveFactories(
|
|
515
602
|
entityTypeNamespace: AuthoringEntityTypeNamespace,
|
|
516
603
|
pslBlockNamespace: AuthoringPslBlockDescriptorNamespace,
|
|
517
604
|
): void {
|
|
518
|
-
const blockEntries =
|
|
519
|
-
|
|
520
|
-
isAuthoringPslBlockDescriptor,
|
|
521
|
-
'pslBlock',
|
|
522
|
-
);
|
|
523
|
-
const entityEntries = collectAuthoringLeafDiscriminators(
|
|
605
|
+
const blockEntries = collectPslBlockDescriptorEntries(pslBlockNamespace);
|
|
606
|
+
const entityEntries = collectDescriptorEntries(
|
|
524
607
|
entityTypeNamespace,
|
|
525
608
|
isAuthoringEntityTypeDescriptor,
|
|
526
609
|
'entityType',
|
|
@@ -547,13 +630,13 @@ export function assertNoCrossRegistryCollisions(
|
|
|
547
630
|
pslBlockNamespace: AuthoringPslBlockDescriptorNamespace = {},
|
|
548
631
|
): void {
|
|
549
632
|
const typePaths = new Set(
|
|
550
|
-
|
|
633
|
+
collectDescriptorPaths(typeNamespace, isAuthoringTypeConstructorDescriptor),
|
|
551
634
|
);
|
|
552
635
|
const fieldPaths = new Set(
|
|
553
|
-
|
|
636
|
+
collectDescriptorPaths(fieldNamespace, isAuthoringFieldPresetDescriptor),
|
|
554
637
|
);
|
|
555
638
|
const entityPaths = new Set(
|
|
556
|
-
|
|
639
|
+
collectDescriptorPaths(entityTypeNamespace, isAuthoringEntityTypeDescriptor),
|
|
557
640
|
);
|
|
558
641
|
// Within-registry duplicate detection is handled upstream by the merge
|
|
559
642
|
// walker (`mergeAuthoringNamespaces` in control-stack.ts and
|
|
@@ -35,6 +35,20 @@ export type PslDiagnosticCode =
|
|
|
35
35
|
| 'PSL_INVALID_ENUM_MEMBER'
|
|
36
36
|
| 'PSL_INVALID_TYPES_MEMBER'
|
|
37
37
|
| 'PSL_INVALID_QUALIFIED_TYPE'
|
|
38
|
+
/**
|
|
39
|
+
* A qualified name (e.g. a dotted type or attribute reference) is structurally
|
|
40
|
+
* invalid, such as an over-qualified or trailing-separator name.
|
|
41
|
+
*/
|
|
42
|
+
| 'PSL_INVALID_QUALIFIED_NAME'
|
|
43
|
+
/**
|
|
44
|
+
* A reserved declaration keyword (`model`/`enum`/`namespace`/`type`) that
|
|
45
|
+
* committed the declaration kind on the keyword alone but is missing its name
|
|
46
|
+
* and/or opening brace. The recursive-descent parser produces a best-effort
|
|
47
|
+
* typed node for the malformed header and reports this code rather than
|
|
48
|
+
* `PSL_UNSUPPORTED_TOP_LEVEL_BLOCK`, which is reserved for a genuinely unknown
|
|
49
|
+
* top-level keyword.
|
|
50
|
+
*/
|
|
51
|
+
| 'PSL_INVALID_DECLARATION'
|
|
38
52
|
/**
|
|
39
53
|
* A malformed line inside an extension-contributed top-level block body, or
|
|
40
54
|
* a structurally invalid element inside a `list` parameter value.
|
|
@@ -46,6 +60,21 @@ export type PslDiagnosticCode =
|
|
|
46
60
|
* extension-block parse errors their own code.
|
|
47
61
|
*/
|
|
48
62
|
| 'PSL_INVALID_EXTENSION_BLOCK_MEMBER'
|
|
63
|
+
/**
|
|
64
|
+
* A malformed JS-like object literal `{ key: value, … }` in value/argument
|
|
65
|
+
* position — a field missing its `:`, a field missing its value, or an
|
|
66
|
+
* unterminated `{`. The recursive-descent parser still produces a best-effort
|
|
67
|
+
* `ObjectLiteralExpr` node (preserving the lossless round-trip) and reports
|
|
68
|
+
* this code anchored on the offending token.
|
|
69
|
+
*/
|
|
70
|
+
| 'PSL_INVALID_OBJECT_LITERAL'
|
|
71
|
+
/**
|
|
72
|
+
* A string literal with no closing quote — the tokenizer stops the literal at
|
|
73
|
+
* a newline or at EOF when no terminating `"` is found, and the
|
|
74
|
+
* recursive-descent parser still consumes the token (preserving the lossless
|
|
75
|
+
* round-trip) but reports this code anchored on the string token's span.
|
|
76
|
+
*/
|
|
77
|
+
| 'PSL_UNTERMINATED_STRING'
|
|
49
78
|
/**
|
|
50
79
|
* An unknown parameter key in an extension-contributed block — a key present
|
|
51
80
|
* in the source block but absent from the descriptor's `parameters` map.
|
|
@@ -70,7 +99,16 @@ export type PslDiagnosticCode =
|
|
|
70
99
|
* A `ref`-kind parameter identifier does not resolve to a declared entity of
|
|
71
100
|
* the required `refKind` within the declared scope.
|
|
72
101
|
*/
|
|
73
|
-
| 'PSL_EXTENSION_UNRESOLVED_REF'
|
|
102
|
+
| 'PSL_EXTENSION_UNRESOLVED_REF'
|
|
103
|
+
/**
|
|
104
|
+
* A parameter key appears more than once in an extension block body.
|
|
105
|
+
* The first occurrence is kept; subsequent occurrences emit this diagnostic.
|
|
106
|
+
*/
|
|
107
|
+
| 'PSL_EXTENSION_DUPLICATE_PARAMETER'
|
|
108
|
+
/**
|
|
109
|
+
* A `@@`-prefixed block-attribute line inside an extension block has invalid syntax.
|
|
110
|
+
*/
|
|
111
|
+
| 'PSL_INVALID_EXTENSION_BLOCK_ATTRIBUTE';
|
|
74
112
|
|
|
75
113
|
/**
|
|
76
114
|
* Descriptor vocabulary for a single parameter on a declared block.
|
|
@@ -118,14 +156,17 @@ export interface PslBlockParamList {
|
|
|
118
156
|
/**
|
|
119
157
|
* The parsed representation of a single parameter value on a uniform
|
|
120
158
|
* extension-block AST node. Mirrors the `PslBlockParam` descriptor
|
|
121
|
-
* vocabulary:
|
|
159
|
+
* vocabulary, plus `bare` for keyonly entries:
|
|
122
160
|
*
|
|
123
161
|
* - `ref` → `PslExtensionBlockParamRef` — a raw identifier string
|
|
124
162
|
* (resolution runs in the validator, not the parser).
|
|
125
|
-
* - `value` → `
|
|
163
|
+
* - `value` → `PslExtensionBlockParamScalarValue` — a raw PSL literal string
|
|
126
164
|
* (codec validation runs in the validator).
|
|
127
165
|
* - `option` → `PslExtensionBlockParamOption` — the chosen token.
|
|
128
166
|
* - `list` → `PslExtensionBlockParamList` — ordered list of the above.
|
|
167
|
+
* - `bare` → `PslExtensionBlockParamBare` — a bare identifier line with no
|
|
168
|
+
* `= value` (e.g. `Low` in an enum block). The name is the key in
|
|
169
|
+
* `parameters`; the interpreting consumer decides the default value.
|
|
129
170
|
*
|
|
130
171
|
* These shapes are intentionally minimal. The validator and lowering refine
|
|
131
172
|
* and consume them; the generic framework parser produces them.
|
|
@@ -134,7 +175,8 @@ export type PslExtensionBlockParamValue =
|
|
|
134
175
|
| PslExtensionBlockParamRef
|
|
135
176
|
| PslExtensionBlockParamScalarValue
|
|
136
177
|
| PslExtensionBlockParamOption
|
|
137
|
-
| PslExtensionBlockParamList
|
|
178
|
+
| PslExtensionBlockParamList
|
|
179
|
+
| PslExtensionBlockParamBare;
|
|
138
180
|
|
|
139
181
|
export interface PslExtensionBlockParamRef {
|
|
140
182
|
readonly kind: 'ref';
|
|
@@ -160,6 +202,38 @@ export interface PslExtensionBlockParamList {
|
|
|
160
202
|
readonly span: PslSpan;
|
|
161
203
|
}
|
|
162
204
|
|
|
205
|
+
/**
|
|
206
|
+
* A bare identifier line inside an extension block — a key with no `= value`.
|
|
207
|
+
* Emitted when a line matches `/^[A-Za-z_]\w*$/` with no assignment. The
|
|
208
|
+
* consumer decides what default value (if any) to apply.
|
|
209
|
+
*/
|
|
210
|
+
export interface PslExtensionBlockParamBare {
|
|
211
|
+
readonly kind: 'bare';
|
|
212
|
+
readonly span: PslSpan;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A positional argument on a block attribute, e.g. the `"pg/text@1"` in
|
|
217
|
+
* `@@type("pg/text@1")`.
|
|
218
|
+
*/
|
|
219
|
+
export interface PslExtensionBlockAttributeArg {
|
|
220
|
+
readonly kind: 'positional';
|
|
221
|
+
readonly value: string;
|
|
222
|
+
readonly span: PslSpan;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A `@@`-prefixed block-level attribute parsed inside an extension block,
|
|
227
|
+
* e.g. `@@type("pg/text@1")`. Block attributes are captured generically
|
|
228
|
+
* — the parser does not validate attribute names or argument shapes; that
|
|
229
|
+
* is a concern of the block's interpreter.
|
|
230
|
+
*/
|
|
231
|
+
export interface PslExtensionBlockAttribute {
|
|
232
|
+
readonly name: string;
|
|
233
|
+
readonly args: readonly PslExtensionBlockAttributeArg[];
|
|
234
|
+
readonly span: PslSpan;
|
|
235
|
+
}
|
|
236
|
+
|
|
163
237
|
/**
|
|
164
238
|
* Base shape for a uniform extension-contributed top-level PSL block
|
|
165
239
|
* node, as produced by the generic framework parser and consumed by the
|
|
@@ -173,12 +247,18 @@ export interface PslExtensionBlockParamList {
|
|
|
173
247
|
* parameter names from the descriptor; values are the parsed parameter
|
|
174
248
|
* representations. Only parameters present in the source are included
|
|
175
249
|
* — absence of a required parameter is a validator concern, not a
|
|
176
|
-
* parser concern.
|
|
250
|
+
* parser concern. Insertion order is preserved; the first occurrence of a
|
|
251
|
+
* duplicate key is retained and subsequent occurrences emit
|
|
252
|
+
* `PSL_EXTENSION_DUPLICATE_PARAMETER`.
|
|
253
|
+
* - `blockAttributes` are `@@`-prefixed attribute lines inside the block, in
|
|
254
|
+
* declaration order. Captured generically — names and args are not validated
|
|
255
|
+
* by the parser.
|
|
177
256
|
* - `span` covers the full block from keyword to closing brace.
|
|
178
257
|
*/
|
|
179
258
|
export interface PslExtensionBlock {
|
|
180
259
|
readonly kind: string;
|
|
181
260
|
readonly name: string;
|
|
182
261
|
readonly parameters: Record<string, PslExtensionBlockParamValue>;
|
|
262
|
+
readonly blockAttributes: readonly PslExtensionBlockAttribute[];
|
|
183
263
|
readonly span: PslSpan;
|
|
184
264
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
2
|
+
import type { Codec } from './codec';
|
|
3
|
+
import type { AnyCodecDescriptor } from './codec-descriptor';
|
|
4
|
+
import type { CodecInstanceContext, CodecRef } from './codec-types';
|
|
5
|
+
import { runtimeError } from './runtime-error';
|
|
6
|
+
|
|
7
|
+
export const CONTRACT_CODEC_DESCRIPTOR_MISSING = 'CONTRACT.CODEC_DESCRIPTOR_MISSING' as const;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Look up a descriptor for `ref.codecId` using `descriptorFor`; throw
|
|
11
|
+
* `code` if none is found. Each plane names its own error path: the control
|
|
12
|
+
* plane resolves contract-stack descriptors (`CONTRACT.*`), the execution
|
|
13
|
+
* plane resolves at query time (`RUNTIME.*`).
|
|
14
|
+
*/
|
|
15
|
+
export function resolveCodecDescriptorOrThrow(
|
|
16
|
+
descriptorFor: (codecId: string) => AnyCodecDescriptor | undefined,
|
|
17
|
+
ref: CodecRef,
|
|
18
|
+
code: 'CONTRACT.CODEC_DESCRIPTOR_MISSING' | 'RUNTIME.CODEC_DESCRIPTOR_MISSING',
|
|
19
|
+
): AnyCodecDescriptor {
|
|
20
|
+
const descriptor = descriptorFor(ref.codecId);
|
|
21
|
+
if (!descriptor) {
|
|
22
|
+
throw runtimeError(code, `No codec descriptor registered for codecId '${ref.codecId}'.`, {
|
|
23
|
+
codecId: ref.codecId,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return descriptor;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates `ref.typeParams` against `descriptor.paramsSchema`.
|
|
31
|
+
*
|
|
32
|
+
* Parameterized codecs that omit `typeParams` have it normalized to `{}` before
|
|
33
|
+
* validation (mirrors `ast-codec-resolver.ts` semantics). Throws
|
|
34
|
+
* `RUNTIME.TYPE_PARAMS_INVALID` when the validator returns a `Promise` or
|
|
35
|
+
* reports issues.
|
|
36
|
+
*/
|
|
37
|
+
export function validateCodecTypeParams(descriptor: AnyCodecDescriptor, ref: CodecRef): unknown {
|
|
38
|
+
const normalized =
|
|
39
|
+
descriptor.isParameterized && ref.typeParams === undefined ? { ...ref, typeParams: {} } : ref;
|
|
40
|
+
|
|
41
|
+
const result = blindCast<
|
|
42
|
+
{ value: unknown } | { issues: ReadonlyArray<{ message: string }> } | Promise<unknown>,
|
|
43
|
+
'Standard Schema validate returns unknown; the spec guarantees this union shape'
|
|
44
|
+
>(descriptor.paramsSchema['~standard'].validate(normalized.typeParams));
|
|
45
|
+
|
|
46
|
+
if (result instanceof Promise) {
|
|
47
|
+
throw runtimeError(
|
|
48
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
49
|
+
`paramsSchema for codec '${ref.codecId}' returned a Promise; runtime validation requires a synchronous Standard Schema validator.`,
|
|
50
|
+
{ codecId: ref.codecId, typeParams: ref.typeParams },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ('issues' in result && result.issues) {
|
|
55
|
+
const messages = result.issues.map((issue) => issue.message).join('; ');
|
|
56
|
+
throw runtimeError(
|
|
57
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
58
|
+
`Invalid typeParams for codec '${ref.codecId}': ${messages}`,
|
|
59
|
+
{ codecId: ref.codecId, typeParams: ref.typeParams },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return blindCast<{ value: unknown }, 'issues guard above rules out the issues branch'>(result)
|
|
64
|
+
.value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves a `Codec` instance: validates `ref.typeParams` via
|
|
69
|
+
* {@link validateCodecTypeParams} then calls `descriptor.factory(validated)(ctx)`.
|
|
70
|
+
*
|
|
71
|
+
* The descriptor's `factory` is typed against its own `P`; the registry erases
|
|
72
|
+
* `P` to `any`, so the factory is narrowed to `(params: unknown) => (ctx) => Codec`
|
|
73
|
+
* at the call boundary. The `paramsSchema` validates the input above before we
|
|
74
|
+
* forward it, so the narrowing is safe by construction.
|
|
75
|
+
*/
|
|
76
|
+
export function materializeCodec(
|
|
77
|
+
descriptor: AnyCodecDescriptor,
|
|
78
|
+
ref: CodecRef,
|
|
79
|
+
ctx: CodecInstanceContext,
|
|
80
|
+
): Codec {
|
|
81
|
+
const validated = validateCodecTypeParams(descriptor, ref);
|
|
82
|
+
return blindCast<
|
|
83
|
+
(params: unknown) => (ctx: CodecInstanceContext) => Codec,
|
|
84
|
+
'registry erases P to any; paramsSchema validates input before forwarding'
|
|
85
|
+
>(descriptor.factory)(validated)(ctx);
|
|
86
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface RuntimeErrorEnvelope extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';
|
|
4
|
+
readonly severity: 'error';
|
|
5
|
+
readonly details?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type guard for the runtime-error envelope produced by `runtimeError`.
|
|
10
|
+
*
|
|
11
|
+
* Prefer this over duck-typing on `error.code` directly so consumers stay
|
|
12
|
+
* insulated from the envelope's internal shape.
|
|
13
|
+
*/
|
|
14
|
+
export function isRuntimeError(error: unknown): error is RuntimeErrorEnvelope {
|
|
15
|
+
return (
|
|
16
|
+
error instanceof Error &&
|
|
17
|
+
'code' in error &&
|
|
18
|
+
typeof (error as { code?: unknown }).code === 'string' &&
|
|
19
|
+
'category' in error &&
|
|
20
|
+
'severity' in error
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runtimeError(
|
|
25
|
+
code: string,
|
|
26
|
+
message: string,
|
|
27
|
+
details?: Record<string, unknown>,
|
|
28
|
+
): RuntimeErrorEnvelope {
|
|
29
|
+
const error = Object.assign(new Error(message), {
|
|
30
|
+
code,
|
|
31
|
+
category: resolveCategory(code),
|
|
32
|
+
severity: 'error' as const,
|
|
33
|
+
...(details !== undefined ? { details } : {}),
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(error, 'name', { value: 'RuntimeError', configurable: true });
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
|
|
40
|
+
const prefix = code.split('.')[0] ?? 'RUNTIME';
|
|
41
|
+
switch (prefix) {
|
|
42
|
+
case 'PLAN':
|
|
43
|
+
case 'CONTRACT':
|
|
44
|
+
case 'LINT':
|
|
45
|
+
case 'BUDGET':
|
|
46
|
+
return prefix;
|
|
47
|
+
default:
|
|
48
|
+
return 'RUNTIME';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codec-DCQAerzB.d.mts","names":[],"sources":["../src/shared/codec-types.ts","../src/shared/codec-descriptor.ts","../src/shared/codec.ts"],"mappings":";;;;KAIY,UAAA;AAAZ;;;;AAAsB;AAWtB;;;;AAXA,UAWiB,QAAA;EAAA,SACN,OAAA;EAAA,SACA,UAAA,GAAa,SAAS;AAAA;AAAA;AAcjC;;;;AAC+B;AAW/B;;;;;AA1BiC,UAchB,gBAAA;EAAA,SACN,MAAA,GAAS,WAAW;AAAA;;;;;;;;;UAWd,WAAA;EACf,GAAA,CAAI,EAAA,WAAa,KAAA;EACjB,cAAA,CAAe,EAAA;EACf,OAAA,CAAQ,EAAA,WAAa,SAAA;EACrB,mBAAA,CAAoB,EAAA,UAAY,MAAA,EAAQ,MAAA;AAAA;AAAA,cAG7B,gBAAA,EAAkB,WAK9B;;;;AAAA;AASD;;;UAAiB,oBAAA;EAAA,SACN,IAAI;AAAA;;;;UAME,SAAA;EAAA,SACN,EAAA,GAAK,MAAM;AAAA;;;AAMyB;cAAlC,gBAAA,EAAkB,gBAAgB;;;;;;;;AA7Dd;AAcjC;;;;AAC+B;UCFd,eAAA;EDaW;EAAA,SCXjB,OAAA;EDYQ;EAAA,SCVR,MAAA,WAAiB,UAAA;EDac;EAAA,SCX/B,WAAA;EDWqC;EAAA,SCTrC,IAAA,GAAO,SAAA;EDMZ;EAAA,SCJK,YAAA,EAAc,gBAAA,CAAiB,CAAA;EDKxC;EAAA,SCHS,eAAA;EDIT;EAAA,SCFS,gBAAA,IAAoB,MAAA,EAAQ,CAAA;EDEhB;EAAA,SCAZ,OAAA,GAAU,MAAA,EAAQ,CAAA,MAAO,GAAA,EAAK,oBAAA,KAAyB,KAAA;AAAA;;;;ADCD;AAGjE;KCKY,kBAAA,GAAqB,eAAe;;;ADA/C;AASD;;;;uBCAsB,mBAAA,4BAA+C,eAAA,CAAgB,OAAA;EAAA,kBACjE,OAAA;EAAA,kBACA,MAAA,WAAiB,UAAA;EAAA,kBACjB,WAAA;EAAA,SACT,IAAA,GAAO,SAAA;EAAA,kBAEE,YAAA,EAAc,gBAAA,CAAiB,OAAA;EDQtC;EAAA,ICLP,eAAA;;EAKJ,gBAAA,EAAkB,MAAA,EAAQ,OAAA;EDAmB;;;EAAA,SCKpC,OAAA,CACP,MAAA,EAAQ,OAAA,IACN,GAAA,EAAK,oBAAA,KAAyB,KAAA,kBAAuB,UAAA;AAAA;;;;;;;;ADpE1B;AAcjC;;;;AAC+B;AAW/B;;;;;;;;UEViB,KAAA,sDAEU,UAAA,cAAwB,UAAA;EFS7C;EAAA,SEJK,EAAA,EAAI,EAAA;EFKb;EAAA,SEHS,aAAA,GAAgB,OAAA;EFIzB;EEFA,MAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,gBAAA,GAAmB,OAAA,CAAQ,KAAA;EFEjC;EEArB,MAAA,CAAO,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,gBAAA,GAAmB,OAAA,CAAQ,MAAA;EFChC;EECpB,UAAA,CAAW,KAAA,EAAO,MAAA,GAAS,SAAA;EFDK;EEGhC,UAAA,CAAW,IAAA,EAAM,SAAA,GAAY,MAAA;AAAA;AFA/B;;;;AAKC;AALD,uBEQsB,SAAA,sDAEK,UAAA,cAAwB,UAAA,kDAGtC,KAAA,CAAM,EAAA,EAAI,OAAA,EAAS,KAAA,EAAO,MAAA;EAAA,SAMT,UAAA,EAAY,eAAA;;;AFJ3B;cEIe,UAAA,EAAY,eAAA;EAAA,IAEpC,EAAA,IAAM,EAAA;EAAA,SAID,MAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,gBAAA,GAAmB,OAAA,CAAQ,KAAA;EAAA,SACtD,MAAA,CAAO,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,gBAAA,GAAmB,OAAA,CAAQ,MAAA;EAAA,SACpD,UAAA,CAAW,KAAA,EAAO,MAAA,GAAS,SAAA;EAAA,SAC3B,UAAA,CAAW,IAAA,EAAM,SAAA,GAAY,MAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"emission-types-vfpSTe63.d.mts","names":[],"sources":["../src/control/emission-types.ts"],"mappings":";;;;UAGiB,4BAAA;EAAA,SACN,yBAAA,GAA4B,aAAa,CAAC,eAAA;AAAA;AAAA,UAGpC,iBAAA;EAAA,SACN,gBAAA,GAAmB,aAAA,CAAc,eAAA;EAAA,SACjC,YAAA,GAAe,aAAA;AAAA;AAAA,UAGT,WAAA;EAAA,SACN,EAAA;EAET,mBAAA,CAAoB,QAAA,EAAU,QAAA,EAAU,mBAAA;EAExC,wBAAA,CAAyB,SAAA,UAAmB,KAAA,EAAO,aAAA;EAEnD,gBAAA;EAEA,oBAAA,CAAqB,OAAA,GAAU,4BAAA;EAE/B,qBAAA;EAEA,kBAAA,CAAmB,gBAAA,UAA0B,YAAA;EAjBjB;;;;;;;;;;AACS;AAGvC;;;EA6BE,sBAAA,EACE,SAAA,UACA,SAAA,UACA,KAAA,EAAO,aAAA,EACP,QAAA,EAAU,QAAA,GACT,MAAA;AAAA"}
|