@prisma-next/framework-components 0.13.0 → 0.14.0-dev.10
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-Di7Ug2bw.d.mts} +126 -9
- package/dist/framework-authoring-Di7Ug2bw.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-B9k0py6_.d.mts} +3 -3
- package/dist/{framework-components-DDQXmW0b.d.mts.map → framework-components-B9k0py6_.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-CWkHgK17.d.mts} +11 -37
- package/dist/psl-ast-CWkHgK17.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 +90 -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
package/src/control/psl-ast.ts
CHANGED
|
@@ -7,6 +7,9 @@ export type {
|
|
|
7
7
|
PslBlockParamValue,
|
|
8
8
|
PslDiagnosticCode,
|
|
9
9
|
PslExtensionBlock,
|
|
10
|
+
PslExtensionBlockAttribute,
|
|
11
|
+
PslExtensionBlockAttributeArg,
|
|
12
|
+
PslExtensionBlockParamBare,
|
|
10
13
|
PslExtensionBlockParamList,
|
|
11
14
|
PslExtensionBlockParamOption,
|
|
12
15
|
PslExtensionBlockParamRef,
|
|
@@ -130,30 +133,6 @@ export interface PslModel {
|
|
|
130
133
|
readonly comment?: string;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
export interface PslEnumValue {
|
|
134
|
-
readonly kind: 'enumValue';
|
|
135
|
-
readonly name: string;
|
|
136
|
-
/**
|
|
137
|
-
* Optional storage label for the enum member, captured from a trailing
|
|
138
|
-
* `@map("...")` attribute on the member line. The parser populates this
|
|
139
|
-
* when the source PSL carries an explicit `@map`. Producers (e.g.
|
|
140
|
-
* `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`
|
|
141
|
-
* automatically when normalisation would change the printed member name
|
|
142
|
-
* (so an enum value `'in-progress'` becomes `inProgress @map("in-progress")`
|
|
143
|
-
* in PSL, preserving the round-trip).
|
|
144
|
-
*/
|
|
145
|
-
readonly mapName?: string;
|
|
146
|
-
readonly span: PslSpan;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface PslEnum {
|
|
150
|
-
readonly kind: 'enum';
|
|
151
|
-
readonly name: string;
|
|
152
|
-
readonly values: readonly PslEnumValue[];
|
|
153
|
-
readonly attributes: readonly PslAttribute[];
|
|
154
|
-
readonly span: PslSpan;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
136
|
/**
|
|
158
137
|
* A reusable group of fields embedded in a model (a `type Name { … }` block) —
|
|
159
138
|
* e.g. a MongoDB embedded document or a Postgres composite type. Unlike
|
|
@@ -202,7 +181,7 @@ export interface PslTypesBlock {
|
|
|
202
181
|
export const UNSPECIFIED_PSL_NAMESPACE_ID = '__unspecified__';
|
|
203
182
|
|
|
204
183
|
/** A value in {@link PslNamespace.entries}: a built-in entity node or an extension-contributed {@link PslExtensionBlock}. */
|
|
205
|
-
export type PslNamespaceEntry = PslModel |
|
|
184
|
+
export type PslNamespaceEntry = PslModel | PslCompositeType | PslExtensionBlock;
|
|
206
185
|
|
|
207
186
|
/**
|
|
208
187
|
* A namespace block, or the parser's synthesised `__unspecified__` bucket for
|
|
@@ -220,8 +199,6 @@ export interface PslNamespace {
|
|
|
220
199
|
readonly entries: Readonly<Record<string, Readonly<Record<string, PslNamespaceEntry>>>>;
|
|
221
200
|
/** Built-in models, from `entries['model']`. Extension kinds: {@link namespacePslExtensionBlocks}. */
|
|
222
201
|
readonly models: readonly PslModel[];
|
|
223
|
-
/** Built-in enums, from `entries['enum']`. */
|
|
224
|
-
readonly enums: readonly PslEnum[];
|
|
225
202
|
/** Built-in composite types, from `entries['compositeType']`. */
|
|
226
203
|
readonly compositeTypes: readonly PslCompositeType[];
|
|
227
204
|
readonly span: PslSpan;
|
|
@@ -255,12 +232,6 @@ class PslNamespaceNode implements PslNamespace {
|
|
|
255
232
|
);
|
|
256
233
|
}
|
|
257
234
|
|
|
258
|
-
get enums(): readonly PslEnum[] {
|
|
259
|
-
return blindCast<readonly PslEnum[], 'entries[enum] holds only PslEnum by construction'>(
|
|
260
|
-
Object.values(this.entries['enum'] ?? {}),
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
235
|
get compositeTypes(): readonly PslCompositeType[] {
|
|
265
236
|
return blindCast<
|
|
266
237
|
readonly PslCompositeType[],
|
|
@@ -286,7 +257,6 @@ export function makePslNamespace(init: {
|
|
|
286
257
|
*/
|
|
287
258
|
export function makePslNamespaceEntries(
|
|
288
259
|
models: readonly PslModel[],
|
|
289
|
-
enums: readonly PslEnum[],
|
|
290
260
|
compositeTypes: readonly PslCompositeType[],
|
|
291
261
|
extensionBlocks: readonly PslExtensionBlock[],
|
|
292
262
|
): Readonly<Record<string, Readonly<Record<string, PslNamespaceEntry>>>> {
|
|
@@ -300,14 +270,6 @@ export function makePslNamespaceEntries(
|
|
|
300
270
|
container['model'] = Object.freeze(map);
|
|
301
271
|
}
|
|
302
272
|
|
|
303
|
-
if (enums.length > 0) {
|
|
304
|
-
const map: Record<string, PslEnum> = {};
|
|
305
|
-
for (const e of enums) {
|
|
306
|
-
map[e.name] = e;
|
|
307
|
-
}
|
|
308
|
-
container['enum'] = Object.freeze(map);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
273
|
if (compositeTypes.length > 0) {
|
|
312
274
|
const map: Record<string, PslCompositeType> = {};
|
|
313
275
|
for (const ct of compositeTypes) {
|
|
@@ -350,17 +312,6 @@ export function flatPslModels(ast: PslDocumentAst): readonly PslModel[] {
|
|
|
350
312
|
);
|
|
351
313
|
}
|
|
352
314
|
|
|
353
|
-
/**
|
|
354
|
-
* Returns all enums from every namespace in document order.
|
|
355
|
-
*/
|
|
356
|
-
export function flatPslEnums(ast: PslDocumentAst): readonly PslEnum[] {
|
|
357
|
-
return ast.namespaces.flatMap((ns) =>
|
|
358
|
-
blindCast<PslEnum[], 'enum kind map contains only PslEnum by construction'>(
|
|
359
|
-
Object.values(ns.entries['enum'] ?? {}),
|
|
360
|
-
),
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
315
|
/**
|
|
365
316
|
* Returns all composite types from every namespace in document order.
|
|
366
317
|
*/
|
|
@@ -379,20 +330,18 @@ export function flatPslCompositeTypes(ast: PslDocumentAst): readonly PslComposit
|
|
|
379
330
|
* that is **not** in this set was contributed by an extension-block descriptor.
|
|
380
331
|
*
|
|
381
332
|
* Built-in keys match the PSL keyword used on each block type:
|
|
382
|
-
* `'model'`, `'
|
|
333
|
+
* `'model'`, `'compositeType'`. The `'enum'` keyword is claimed by the
|
|
334
|
+
* extension-block grammar via a registered descriptor, so `entries['enum']`
|
|
335
|
+
* holds `PslExtensionBlock` nodes and is returned by `namespacePslExtensionBlocks`.
|
|
383
336
|
*/
|
|
384
|
-
export const BUILTIN_PSL_KIND_KEYS: ReadonlySet<string> = new Set([
|
|
385
|
-
'model',
|
|
386
|
-
'enum',
|
|
387
|
-
'compositeType',
|
|
388
|
-
]);
|
|
337
|
+
export const BUILTIN_PSL_KIND_KEYS: ReadonlySet<string> = new Set(['model', 'compositeType']);
|
|
389
338
|
|
|
390
339
|
/**
|
|
391
340
|
* Returns all extension-contributed blocks in the given namespace, in
|
|
392
341
|
* insertion order (the order the parser encountered them in the source).
|
|
393
342
|
*
|
|
394
|
-
* Reads from `namespace.entries`, skipping the
|
|
395
|
-
* (`'model'`, `'
|
|
343
|
+
* Reads from `namespace.entries`, skipping the built-in kind keys
|
|
344
|
+
* (`'model'`, `'compositeType'`). All remaining kind maps contain
|
|
396
345
|
* only `PslExtensionBlock` nodes by construction (see `makePslNamespaceEntries`).
|
|
397
346
|
*/
|
|
398
347
|
export function namespacePslExtensionBlocks(ns: PslNamespace): readonly PslExtensionBlock[] {
|
|
@@ -107,15 +107,17 @@ export function validateExtensionBlock(
|
|
|
107
107
|
const nodeKeys = new Set(Object.keys(node.parameters));
|
|
108
108
|
|
|
109
109
|
// 1. Unknown parameters — keys in the node not in the descriptor.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
if (!descriptor.variadicParameters) {
|
|
111
|
+
for (const key of nodeKeys) {
|
|
112
|
+
if (!descriptorKeys.has(key)) {
|
|
113
|
+
const captured = node.parameters[key];
|
|
114
|
+
diagnostics.push({
|
|
115
|
+
code: 'PSL_EXTENSION_UNKNOWN_PARAMETER',
|
|
116
|
+
message: `Unknown parameter "${key}" in "${descriptor.keyword}" block "${node.name}". The descriptor does not declare this parameter.`,
|
|
117
|
+
sourceId,
|
|
118
|
+
span: captured?.span ?? node.span,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
1
|
+
import type { RuntimeErrorEnvelope } from '../shared/runtime-error';
|
|
2
|
+
import { runtimeError } from '../shared/runtime-error';
|
|
3
|
+
|
|
4
|
+
export type { RuntimeErrorEnvelope } from '../shared/runtime-error';
|
|
5
|
+
export { isRuntimeError, runtimeError } from '../shared/runtime-error';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Stable code emitted by the runtime when an in-flight `execute()`
|
|
@@ -30,55 +29,6 @@ export type RuntimeAbortedPhase =
|
|
|
30
29
|
| 'afterExecute'
|
|
31
30
|
| 'onRow';
|
|
32
31
|
|
|
33
|
-
/**
|
|
34
|
-
* Type guard for the runtime-error envelope produced by `runtimeError`.
|
|
35
|
-
*
|
|
36
|
-
* Prefer this over duck-typing on `error.code` directly so consumers stay
|
|
37
|
-
* insulated from the envelope's internal shape.
|
|
38
|
-
*/
|
|
39
|
-
export function isRuntimeError(error: unknown): error is RuntimeErrorEnvelope {
|
|
40
|
-
return (
|
|
41
|
-
error instanceof Error &&
|
|
42
|
-
'code' in error &&
|
|
43
|
-
typeof (error as { code?: unknown }).code === 'string' &&
|
|
44
|
-
'category' in error &&
|
|
45
|
-
'severity' in error
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function runtimeError(
|
|
50
|
-
code: string,
|
|
51
|
-
message: string,
|
|
52
|
-
details?: Record<string, unknown>,
|
|
53
|
-
): RuntimeErrorEnvelope {
|
|
54
|
-
const error = new Error(message) as RuntimeErrorEnvelope;
|
|
55
|
-
Object.defineProperty(error, 'name', {
|
|
56
|
-
value: 'RuntimeError',
|
|
57
|
-
configurable: true,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return Object.assign(error, {
|
|
61
|
-
code,
|
|
62
|
-
category: resolveCategory(code),
|
|
63
|
-
severity: 'error' as const,
|
|
64
|
-
message,
|
|
65
|
-
details,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
|
|
70
|
-
const prefix = code.split('.')[0] ?? 'RUNTIME';
|
|
71
|
-
switch (prefix) {
|
|
72
|
-
case 'PLAN':
|
|
73
|
-
case 'CONTRACT':
|
|
74
|
-
case 'LINT':
|
|
75
|
-
case 'BUDGET':
|
|
76
|
-
return prefix;
|
|
77
|
-
default:
|
|
78
|
-
return 'RUNTIME';
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
32
|
/**
|
|
83
33
|
* Construct a `RUNTIME.ABORTED` envelope. Phase distinguishes where the
|
|
84
34
|
* abort was observed — codec call sites (`encode` / `decode` / `stream`)
|
package/src/exports/authoring.ts
CHANGED
package/src/exports/codec.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type {
|
|
|
16
16
|
CodecLookup,
|
|
17
17
|
CodecMeta,
|
|
18
18
|
CodecRef,
|
|
19
|
+
CodecRegistry,
|
|
19
20
|
CodecTrait,
|
|
20
21
|
} from '../shared/codec-types';
|
|
21
22
|
export { emptyCodecLookup, voidParamsSchema } from '../shared/codec-types';
|
|
@@ -26,3 +27,9 @@ export type {
|
|
|
26
27
|
ColumnTypeDescriptor,
|
|
27
28
|
} from '../shared/column-spec';
|
|
28
29
|
export { column } from '../shared/column-spec';
|
|
30
|
+
export {
|
|
31
|
+
CONTRACT_CODEC_DESCRIPTOR_MISSING,
|
|
32
|
+
materializeCodec,
|
|
33
|
+
resolveCodecDescriptorOrThrow,
|
|
34
|
+
validateCodecTypeParams,
|
|
35
|
+
} from '../shared/resolve-codec';
|
package/src/exports/control.ts
CHANGED
package/src/exports/ir.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { domainElementCoordinates } from '../ir/domain';
|
|
2
|
+
export type { AnyEntityKindDescriptor, EntityKindDescriptor } from '../ir/entity-kind';
|
|
3
|
+
export { hydrateNamespaceEntities } from '../ir/entity-kind';
|
|
2
4
|
export type { IRNode } from '../ir/ir-node';
|
|
3
5
|
export { freezeNode, IRNodeBase } from '../ir/ir-node';
|
|
4
6
|
export type { Namespace } from '../ir/namespace';
|
|
5
7
|
export { NamespaceBase, UNBOUND_NAMESPACE_ID } from '../ir/namespace';
|
|
6
8
|
export type { EntityCoordinate, Storage } from '../ir/storage';
|
|
7
|
-
export { elementCoordinates } from '../ir/storage';
|
|
9
|
+
export { elementCoordinates, entityAt, isPlainRecord } from '../ir/storage';
|
|
8
10
|
export type { StorageType } from '../ir/storage-type';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
2
|
+
import type { Type } from 'arktype';
|
|
3
|
+
|
|
4
|
+
export interface EntityKindDescriptor<Input, Node> {
|
|
5
|
+
readonly kind: string;
|
|
6
|
+
// Type<unknown>, not Type<Input>: AnyEntityKindDescriptor widens Input to never, which would force an unusable Type<never>; concrete descriptors still carry their real schema.
|
|
7
|
+
readonly schema: Type<unknown>;
|
|
8
|
+
readonly construct: (input: Input) => Node;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type AnyEntityKindDescriptor = EntityKindDescriptor<never, unknown>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hydrates a namespace's entities from raw JSON maps into IR class instances.
|
|
15
|
+
*
|
|
16
|
+
* For each kind in `entries`: if the descriptor map has a descriptor,
|
|
17
|
+
* construct each inner-map value; otherwise freeze-and-carry (`'carry'`)
|
|
18
|
+
* or throw naming the kind and nsId (`'fail'`).
|
|
19
|
+
*
|
|
20
|
+
* The single boundary cast hands `value` to `descriptor.construct` as its
|
|
21
|
+
* `Input`. The value satisfies the kind's `Input` either by the
|
|
22
|
+
* entries-input contract at authoring time or by prior `validateStorage`
|
|
23
|
+
* validation at hydration time.
|
|
24
|
+
*/
|
|
25
|
+
export function hydrateNamespaceEntities(
|
|
26
|
+
entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>,
|
|
27
|
+
kinds: ReadonlyMap<string, AnyEntityKindDescriptor>,
|
|
28
|
+
onUnknown: 'carry' | 'fail',
|
|
29
|
+
nsId?: string,
|
|
30
|
+
): Record<string, Readonly<Record<string, unknown>>> {
|
|
31
|
+
const result: Record<string, Readonly<Record<string, unknown>>> = {};
|
|
32
|
+
for (const [kind, rawMap] of Object.entries(entries)) {
|
|
33
|
+
const descriptor = kinds.get(kind);
|
|
34
|
+
if (descriptor !== undefined) {
|
|
35
|
+
const built: Record<string, unknown> = {};
|
|
36
|
+
for (const [name, value] of Object.entries(rawMap)) {
|
|
37
|
+
built[name] = descriptor.construct(
|
|
38
|
+
blindCast<
|
|
39
|
+
never,
|
|
40
|
+
"value is this kind's descriptor Input: when authoring, the typed entries-input contract produces it; when hydrating, it was validated against descriptor.schema before this loop. The never target is AnyEntityKindDescriptor's erased Input parameter."
|
|
41
|
+
>(value),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
result[kind] = Object.freeze(built);
|
|
45
|
+
} else if (onUnknown === 'carry') {
|
|
46
|
+
result[kind] = Object.freeze(rawMap);
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Unknown entries key "${kind}" in namespace "${nsId ?? '?'}"; no hydration factory registered for this entity kind`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
package/src/ir/storage.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { isPlainRecord } from '@prisma-next/contract/is-plain-record';
|
|
1
2
|
import type { StorageBase } from '@prisma-next/contract/types';
|
|
3
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
2
4
|
import type { IRNode } from './ir-node';
|
|
3
5
|
import type { Namespace } from './namespace';
|
|
4
6
|
|
|
7
|
+
export { isPlainRecord };
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* Canonical address for a named entity in Contract IR / Schema IR.
|
|
7
11
|
*
|
|
@@ -30,11 +34,11 @@ export interface EntityCoordinate {
|
|
|
30
34
|
* value, yielded as {@link EntityCoordinate} tuples with
|
|
31
35
|
* `plane: 'storage'` (the parameter type binds the plane).
|
|
32
36
|
*
|
|
33
|
-
* Iterates each namespace's `entries`
|
|
37
|
+
* Iterates each namespace's `entries` kind maps structurally. Skips
|
|
34
38
|
* non-object `entries`; `id` and `kind` are not walked (`kind` is
|
|
35
39
|
* non-enumerable on concretions). For every entity-kind key under
|
|
36
40
|
* `entries` whose value is a non-null object, yields one coordinate per
|
|
37
|
-
* entity name in that map. No family-specific
|
|
41
|
+
* entity name in that map. No family-specific kind vocabulary is required.
|
|
38
42
|
*/
|
|
39
43
|
export function* elementCoordinates(
|
|
40
44
|
storage: Pick<StorageBase, 'namespaces'>,
|
|
@@ -42,15 +46,37 @@ export function* elementCoordinates(
|
|
|
42
46
|
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
43
47
|
const entries = ns.entries;
|
|
44
48
|
if (entries === null || typeof entries !== 'object') continue;
|
|
45
|
-
for (const [entityKind,
|
|
46
|
-
if (
|
|
47
|
-
for (const entityName of Object.keys(
|
|
49
|
+
for (const [entityKind, kindMap] of Object.entries(entries)) {
|
|
50
|
+
if (kindMap === null || typeof kindMap !== 'object') continue;
|
|
51
|
+
for (const entityName of Object.keys(kindMap)) {
|
|
48
52
|
yield { plane: 'storage', namespaceId, entityKind, entityName };
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Looks up a single entity in a `Storage`-shaped value by its full coordinate.
|
|
60
|
+
* Returns `undefined` if the namespace, entity kind, or entity name is absent.
|
|
61
|
+
* The type parameter is a caller assertion — the walk itself is structural
|
|
62
|
+
* and cannot verify the entity's shape.
|
|
63
|
+
*/
|
|
64
|
+
export function entityAt<T = unknown>(
|
|
65
|
+
storage: Pick<StorageBase, 'namespaces'>,
|
|
66
|
+
coord: Pick<EntityCoordinate, 'namespaceId' | 'entityKind' | 'entityName'>,
|
|
67
|
+
): T | undefined {
|
|
68
|
+
const ns = storage.namespaces[coord.namespaceId];
|
|
69
|
+
if (ns === undefined) return undefined;
|
|
70
|
+
const entries = ns.entries;
|
|
71
|
+
if (!isPlainRecord(entries)) return undefined;
|
|
72
|
+
const kindMap = entries[coord.entityKind];
|
|
73
|
+
if (!isPlainRecord(kindMap)) return undefined;
|
|
74
|
+
if (!Object.hasOwn(kindMap, coord.entityName)) return undefined;
|
|
75
|
+
return blindCast<T | undefined, 'caller asserts the entity type at this coordinate'>(
|
|
76
|
+
kindMap[coord.entityName],
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
54
80
|
/**
|
|
55
81
|
* Framework-level promise that every Contract IR / Schema IR carries a
|
|
56
82
|
* collection of namespaces keyed by namespace id. Family storage
|
|
@@ -66,7 +92,7 @@ export function* elementCoordinates(
|
|
|
66
92
|
* is honest at every layer.
|
|
67
93
|
*
|
|
68
94
|
* Extends `IRNode` so the framework's IR-walking surfaces (verifiers,
|
|
69
|
-
* serializers) can dispatch on `Storage`-typed
|
|
95
|
+
* serializers) can dispatch on `Storage`-typed fields through the same
|
|
70
96
|
* IR-node alphabet as every other node — the structural dual already
|
|
71
97
|
* holds in code (every concrete storage class extends an IR-node base);
|
|
72
98
|
* the interface promotion makes the typing honest.
|
|
@@ -43,6 +43,8 @@ export interface CodecDescriptor<P = void> {
|
|
|
43
43
|
readonly isParameterized: boolean;
|
|
44
44
|
/** Emit-path string renderer for `contract.d.ts`. Returns the TypeScript output type expression for given params (e.g. `Vector<1536>`). Optional; absent renderers cause the emitter to fall back to the codec's base output type. Non-parameterized codecs typically omit it. */
|
|
45
45
|
readonly renderOutputType?: (params: P) => string | undefined;
|
|
46
|
+
/** Emit-path string renderer for the `contract.d.ts` *input* position (create/update values). Returns the TypeScript input type expression for given params. Optional; absent renderers fall back to the codec's base input type. A codec supplies this when its write type is narrower than the generic codec input — e.g. an enum whose input should be the literal member union, not `string`. */
|
|
47
|
+
readonly renderInputType?: (params: P) => string | undefined;
|
|
46
48
|
/** The curried higher-order codec. For non-parameterized codecs, the factory is constant — every call returns the same shared codec instance. For parameterized codecs, the factory is called once per `storage.types` instance (or once per inline-`typeParams` column), with `ctx` carrying the column set the resulting codec serves. */
|
|
47
49
|
readonly factory: (params: P) => (ctx: CodecInstanceContext) => Codec;
|
|
48
50
|
}
|
|
@@ -78,6 +80,9 @@ export abstract class CodecDescriptorImpl<TParams = void> implements CodecDescri
|
|
|
78
80
|
/** Optional emit-path string renderer for `contract.d.ts`. Returns the TypeScript output type expression for the given params (e.g. `Vector<1536>`). Non-parameterized codecs typically omit it. */
|
|
79
81
|
renderOutputType?(params: TParams): string | undefined;
|
|
80
82
|
|
|
83
|
+
/** Optional emit-path string renderer for the `contract.d.ts` input position. Returns the TypeScript input type expression for the given params; supplied when the write type is narrower than the generic codec input (e.g. an enum's literal member union). */
|
|
84
|
+
renderInputType?(params: TParams): string | undefined;
|
|
85
|
+
|
|
81
86
|
/**
|
|
82
87
|
* Materialize a curried codec factory for the given params. Concrete subclasses override with a typed return type (e.g. `factory<N>(params: { length: N }): (ctx) => VectorCodec<N>`); per-codec helpers read the typed return at the *direct* call site, which is what preserves method-level generics. Type extraction (e.g. `ReturnType<D['factory']>`) widens method generics to their constraint — that's why the column-helper surface is per-codec, not polymorphic.
|
|
83
88
|
*/
|
|
@@ -36,7 +36,7 @@ export interface CodecCallContext {
|
|
|
36
36
|
/**
|
|
37
37
|
* Codec-id-keyed read surface threaded into emit and authoring paths.
|
|
38
38
|
*
|
|
39
|
-
* - `get(id)` returns
|
|
39
|
+
* - `get(id)` returns a representative {@link Codec} instance for the codec id (used by `family.deserializeContract` for `decodeJson` of literal column defaults). For parameterized codecs whose factory requires concrete params, this may return `undefined` — use `CodecRegistry.forCodecRef` instead.
|
|
40
40
|
* - `targetTypesFor(id)` exposes the codec-id-keyed `targetTypes` metadata the runtime instance no longer carries (TML-2357). Returns the same array `CodecDescriptor.targetTypes` would; for Mongo (whose registration doesn't yet resolve through the unified descriptor map — TML-2324) the family-side assembly populates this directly from the contributor's codec metadata.
|
|
41
41
|
* - `metaFor(id)` exposes the codec-id-keyed `meta` (e.g. SQL-side `db.sql.postgres.nativeType`) the runtime instance no longer carries.
|
|
42
42
|
* - `renderOutputTypeFor(id, params)` exposes the codec-id-keyed `renderOutputType` renderer the runtime instance no longer carries. Returns `undefined` when the codec doesn't render a custom type or when the codec id is unknown.
|
|
@@ -46,6 +46,26 @@ export interface CodecLookup {
|
|
|
46
46
|
targetTypesFor(id: string): readonly string[] | undefined;
|
|
47
47
|
metaFor(id: string): CodecMeta | undefined;
|
|
48
48
|
renderOutputTypeFor(id: string, params: Record<string, unknown>): string | undefined;
|
|
49
|
+
/** Codec-id-keyed `renderInputType` renderer for the `contract.d.ts` input position. Optional so existing lookups need not provide it; returns `undefined` when the codec renders no custom input type or the id is unknown. */
|
|
50
|
+
renderInputTypeFor?(id: string, params: Record<string, unknown>): string | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Full codec registry — the read surface of {@link CodecLookup} plus codec resolution by ref or
|
|
55
|
+
* column coordinate. Built once by `extractCodecLookup` and passed by reference to adapters and
|
|
56
|
+
* other consumers that need to materialise codecs at runtime.
|
|
57
|
+
*
|
|
58
|
+
* - `forCodecRef(ref)` materialises a codec from a {@link CodecRef}. Throws
|
|
59
|
+
* `RUNTIME.CODEC_DESCRIPTOR_MISSING` for unknown ids and `RUNTIME.TYPE_PARAMS_INVALID` on param
|
|
60
|
+
* schema rejection.
|
|
61
|
+
* - `forColumn(namespaceId, table, column)` returns the codec for a specific column coordinate, or
|
|
62
|
+
* `undefined` when no column-to-codec mapping is present. This registry is contract-free so it
|
|
63
|
+
* always returns `undefined` — the method exists so the object structurally satisfies the SQL
|
|
64
|
+
* `ContractCodecRegistry` interface.
|
|
65
|
+
*/
|
|
66
|
+
export interface CodecRegistry extends CodecLookup {
|
|
67
|
+
forCodecRef(ref: CodecRef): Codec;
|
|
68
|
+
forColumn(namespaceId: string, table: string, column: string): Codec | undefined;
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
export const emptyCodecLookup: CodecLookup = {
|