@prisma-next/framework-components 0.12.0-dev.61 → 0.12.0-dev.63

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.
Files changed (33) hide show
  1. package/dist/authoring.d.mts +2 -2
  2. package/dist/authoring.mjs +2 -2
  3. package/dist/components.d.mts +1 -1
  4. package/dist/control.d.mts +5 -4
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +8 -3
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/execution.d.mts +1 -1
  9. package/dist/{framework-authoring-Szvddbl3.mjs → framework-authoring-CnwPJCO4.mjs} +76 -5
  10. package/dist/framework-authoring-CnwPJCO4.mjs.map +1 -0
  11. package/dist/framework-authoring-Cyde8zSN.d.mts +380 -0
  12. package/dist/framework-authoring-Cyde8zSN.d.mts.map +1 -0
  13. package/dist/{framework-components-Ce_Cdw76.d.mts → framework-components-DdqvMc8S.d.mts} +2 -2
  14. package/dist/{framework-components-Ce_Cdw76.d.mts.map → framework-components-DdqvMc8S.d.mts.map} +1 -1
  15. package/dist/{psl-ast-CTuBYLYj.d.mts → psl-ast-DRzRF9rS.d.mts} +46 -12
  16. package/dist/psl-ast-DRzRF9rS.d.mts.map +1 -0
  17. package/dist/psl-ast.d.mts +37 -2
  18. package/dist/psl-ast.d.mts.map +1 -0
  19. package/dist/psl-ast.mjs +142 -1
  20. package/dist/psl-ast.mjs.map +1 -1
  21. package/package.json +7 -7
  22. package/src/control/control-migration-types.ts +8 -2
  23. package/src/control/control-stack.ts +25 -1
  24. package/src/control/psl-ast.ts +62 -25
  25. package/src/control/psl-extension-block-validator.ts +340 -0
  26. package/src/exports/authoring.ts +16 -0
  27. package/src/exports/psl-ast.ts +2 -0
  28. package/src/shared/framework-authoring.ts +215 -2
  29. package/src/shared/psl-extension-block.ts +184 -0
  30. package/dist/framework-authoring-Cv04iZjB.d.mts +0 -183
  31. package/dist/framework-authoring-Cv04iZjB.d.mts.map +0 -1
  32. package/dist/framework-authoring-Szvddbl3.mjs.map +0 -1
  33. package/dist/psl-ast-CTuBYLYj.d.mts.map +0 -1
package/dist/psl-ast.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { blindCast } from "@prisma-next/utils/casts";
1
2
  //#region src/control/psl-ast.ts
2
3
  /**
3
4
  * Name of the synthesised namespace bucket the framework parser uses for
@@ -32,6 +33,146 @@ function flatPslCompositeTypes(ast) {
32
33
  return ast.namespaces.flatMap((ns) => ns.compositeTypes);
33
34
  }
34
35
  //#endregion
35
- export { UNSPECIFIED_PSL_NAMESPACE_ID, flatPslCompositeTypes, flatPslEnums, flatPslModels };
36
+ //#region src/control/psl-extension-block-validator.ts
37
+ /**
38
+ * Validate a single parsed extension block against its descriptor.
39
+ *
40
+ * Returns an array of {@link PslDiagnostic} objects (possibly empty). The
41
+ * caller is responsible for threading `sourceId` into each returned diagnostic
42
+ * — the returned objects already have `sourceId` set from the `sourceId`
43
+ * parameter.
44
+ *
45
+ * @param node - The parsed block node produced by the generic framework parser.
46
+ * @param descriptor - The descriptor that claims this block's keyword.
47
+ * @param sourceId - The PSL source file identifier (threaded into diagnostics).
48
+ * @param codecLookup - Used to validate `value`-kind parameter literals via
49
+ * `codecLookup.get(codecId)?.decodeJson(JSON.parse(raw))`.
50
+ * @param refCtx - Namespace context for `ref`-kind scope resolution. Required
51
+ * when any descriptor parameter is `kind: 'ref'`; may be omitted if none are.
52
+ */
53
+ function validateExtensionBlock(node, descriptor, sourceId, codecLookup, refCtx) {
54
+ const diagnostics = [];
55
+ const descriptorKeys = new Set(Object.keys(descriptor.parameters));
56
+ const nodeKeys = new Set(Object.keys(node.parameters));
57
+ for (const key of nodeKeys) if (!descriptorKeys.has(key)) {
58
+ const captured = node.parameters[key];
59
+ diagnostics.push({
60
+ code: "PSL_EXTENSION_UNKNOWN_PARAMETER",
61
+ message: `Unknown parameter "${key}" in "${descriptor.keyword}" block "${node.name}". The descriptor does not declare this parameter.`,
62
+ sourceId,
63
+ span: captured?.span ?? node.span
64
+ });
65
+ }
66
+ for (const [key, param] of Object.entries(descriptor.parameters)) if (param.required === true && !nodeKeys.has(key)) diagnostics.push({
67
+ code: "PSL_EXTENSION_MISSING_REQUIRED_PARAMETER",
68
+ message: `Required parameter "${key}" is missing from "${descriptor.keyword}" block "${node.name}".`,
69
+ sourceId,
70
+ span: node.span
71
+ });
72
+ for (const [key, param] of Object.entries(descriptor.parameters)) {
73
+ const captured = node.parameters[key];
74
+ if (captured === void 0) continue;
75
+ validateParam(node, descriptor, key, param, captured, sourceId, codecLookup, refCtx, diagnostics);
76
+ }
77
+ return diagnostics;
78
+ }
79
+ function validateParam(node, descriptor, key, param, captured, sourceId, codecLookup, refCtx, diagnostics) {
80
+ switch (param.kind) {
81
+ case "option":
82
+ if (captured.kind !== "option") return;
83
+ if (!param.values.includes(captured.token)) diagnostics.push({
84
+ code: "PSL_EXTENSION_OPTION_OUT_OF_SET",
85
+ message: `Parameter "${key}" in "${descriptor.keyword}" block "${node.name}" has value "${captured.token}" which is not one of the allowed values: ${param.values.map((v) => `"${v}"`).join(", ")}.`,
86
+ sourceId,
87
+ span: captured.span
88
+ });
89
+ return;
90
+ case "value": {
91
+ if (captured.kind !== "value") return;
92
+ const codec = codecLookup.get(param.codecId);
93
+ if (codec === void 0) {
94
+ diagnostics.push({
95
+ code: "PSL_EXTENSION_INVALID_VALUE",
96
+ message: `Parameter "${key}" in "${descriptor.keyword}" block "${node.name}" references unknown codec "${param.codecId}".`,
97
+ sourceId,
98
+ span: captured.span
99
+ });
100
+ return;
101
+ }
102
+ let jsonValue;
103
+ try {
104
+ jsonValue = JSON.parse(captured.raw);
105
+ } catch {
106
+ diagnostics.push({
107
+ code: "PSL_EXTENSION_INVALID_VALUE",
108
+ message: `Parameter "${key}" in "${descriptor.keyword}" block "${node.name}" is not a valid JSON literal (expected a JSON string, number, boolean, or null): ${captured.raw}`,
109
+ sourceId,
110
+ span: captured.span
111
+ });
112
+ return;
113
+ }
114
+ try {
115
+ codec.decodeJson(blindCast(jsonValue));
116
+ } catch (err) {
117
+ const reason = err instanceof Error ? err.message : String(err);
118
+ diagnostics.push({
119
+ code: "PSL_EXTENSION_INVALID_VALUE",
120
+ message: `Parameter "${key}" in "${descriptor.keyword}" block "${node.name}" was rejected by codec "${param.codecId}": ${reason}`,
121
+ sourceId,
122
+ span: captured.span
123
+ });
124
+ }
125
+ return;
126
+ }
127
+ case "ref":
128
+ if (captured.kind !== "ref") return;
129
+ validateRef(node, descriptor, key, param, captured.identifier, captured.span, sourceId, refCtx, diagnostics);
130
+ return;
131
+ case "list":
132
+ if (captured.kind !== "list") return;
133
+ for (const item of captured.items) validateParam(node, descriptor, key, param.of, item, sourceId, codecLookup, refCtx, diagnostics);
134
+ return;
135
+ }
136
+ }
137
+ function validateRef(node, descriptor, key, param, identifier, span, sourceId, refCtx, diagnostics) {
138
+ if (param.scope === "cross-space") return;
139
+ if (refCtx === void 0) return;
140
+ const namespacesToSearch = param.scope === "same-namespace" ? [refCtx.ownerNamespace] : refCtx.allNamespaces;
141
+ if (!resolveEntityInNamespaces(identifier, param.refKind, namespacesToSearch)) {
142
+ const scopeLabel = param.scope === "same-namespace" ? "the same namespace" : "any namespace in the schema";
143
+ diagnostics.push({
144
+ code: "PSL_EXTENSION_UNRESOLVED_REF",
145
+ message: `Parameter "${key}" in "${descriptor.keyword}" block "${node.name}" refers to "${identifier}" (expected ${param.refKind}), but no entity with that name and kind was found in ${scopeLabel}.`,
146
+ sourceId,
147
+ span
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Returns true when an entity named `name` of kind `refKind` exists in at
153
+ * least one of the given namespaces.
154
+ *
155
+ * Built-in PSL entity kinds are mapped to their `PslNamespace` collection:
156
+ * - `'model'` → `ns.models`
157
+ * - `'enum'` → `ns.enums`
158
+ * - `'compositeType'` → `ns.compositeTypes`
159
+ *
160
+ * Any other `refKind` is resolved against the namespace's `extensionBlocks`
161
+ * array (matching by `block.kind === refKind` and `block.name === name`).
162
+ * This covers extension-contributed entity kinds that reference other
163
+ * extension-contributed blocks (e.g. a policy referencing a role block).
164
+ */
165
+ function resolveEntityInNamespaces(name, refKind, namespaces) {
166
+ for (const ns of namespaces) if (refKind === "model") {
167
+ if (ns.models.some((m) => m.name === name)) return true;
168
+ } else if (refKind === "enum") {
169
+ if (ns.enums.some((e) => e.name === name)) return true;
170
+ } else if (refKind === "compositeType") {
171
+ if (ns.compositeTypes.some((ct) => ct.name === name)) return true;
172
+ } else if (ns.extensionBlocks?.some((b) => b.kind === refKind && b.name === name)) return true;
173
+ return false;
174
+ }
175
+ //#endregion
176
+ export { UNSPECIFIED_PSL_NAMESPACE_ID, flatPslCompositeTypes, flatPslEnums, flatPslModels, validateExtensionBlock };
36
177
 
37
178
  //# sourceMappingURL=psl-ast.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"psl-ast.mjs","names":[],"sources":["../src/control/psl-ast.ts"],"sourcesContent":["export interface PslPosition {\n readonly offset: number;\n readonly line: number;\n readonly column: number;\n}\n\nexport interface PslSpan {\n readonly start: PslPosition;\n readonly end: PslPosition;\n}\n\nexport type PslDiagnosticCode =\n | 'PSL_UNTERMINATED_BLOCK'\n | 'PSL_UNSUPPORTED_TOP_LEVEL_BLOCK'\n | 'PSL_INVALID_NAMESPACE_BLOCK'\n | 'PSL_INVALID_ATTRIBUTE_SYNTAX'\n | 'PSL_INVALID_MODEL_MEMBER'\n | 'PSL_UNSUPPORTED_MODEL_ATTRIBUTE'\n | 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE'\n | 'PSL_INVALID_RELATION_ATTRIBUTE'\n | 'PSL_INVALID_REFERENTIAL_ACTION'\n | 'PSL_INVALID_DEFAULT_VALUE'\n | 'PSL_INVALID_ENUM_MEMBER'\n | 'PSL_INVALID_TYPES_MEMBER'\n | 'PSL_INVALID_QUALIFIED_TYPE';\n\nexport interface PslDiagnostic {\n readonly code: PslDiagnosticCode;\n readonly message: string;\n readonly sourceId: string;\n readonly span: PslSpan;\n}\n\nexport interface PslDefaultFunctionValue {\n readonly kind: 'function';\n readonly name: 'autoincrement' | 'now';\n}\n\nexport interface PslDefaultLiteralValue {\n readonly kind: 'literal';\n readonly value: string | number | boolean;\n}\n\nexport type PslDefaultValue = PslDefaultFunctionValue | PslDefaultLiteralValue;\n\nexport type PslAttributeTarget = 'field' | 'model' | 'enum' | 'namedType';\n\nexport interface PslAttributePositionalArgument {\n readonly kind: 'positional';\n readonly value: string;\n readonly span: PslSpan;\n}\n\nexport interface PslAttributeNamedArgument {\n readonly kind: 'named';\n readonly name: string;\n readonly value: string;\n readonly span: PslSpan;\n}\n\nexport type PslAttributeArgument = PslAttributePositionalArgument | PslAttributeNamedArgument;\n\nexport interface PslTypeConstructorCall {\n readonly kind: 'typeConstructor';\n readonly path: readonly string[];\n readonly args: readonly PslAttributeArgument[];\n readonly span: PslSpan;\n}\n\nexport interface PslAttribute {\n readonly kind: 'attribute';\n readonly target: PslAttributeTarget;\n readonly name: string;\n readonly args: readonly PslAttributeArgument[];\n readonly span: PslSpan;\n}\n\nexport type PslReferentialAction = string;\n\nexport type PslFieldAttribute = PslAttribute;\n\nexport interface PslField {\n readonly kind: 'field';\n readonly name: string;\n /** Unqualified type name, e.g. `\"User\"` for both `User`, `auth.User`, and `supabase:auth.User`. */\n readonly typeName: string;\n /** Namespace qualifier from a dot-qualified type reference, e.g. `\"auth\"` for `auth.User` or `supabase:auth.User`. Absent for unqualified types. */\n readonly typeNamespaceId?: string;\n /**\n * Contract-space qualifier from a colon-prefix type reference, e.g. `\"supabase\"` for\n * `supabase:auth.User` or `supabase:User`. Absent for local (same-space) type references.\n *\n * When present, the field references a model from a different contract space. The namespace\n * (`typeNamespaceId`) and model name (`typeName`) identify the target within that space.\n * Physical table resolution against the extension contract is deferred to the aggregate stage (M3).\n */\n readonly typeContractSpaceId?: string;\n readonly typeConstructor?: PslTypeConstructorCall;\n readonly optional: boolean;\n readonly list: boolean;\n readonly typeRef?: string;\n readonly attributes: readonly PslFieldAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslUniqueConstraint {\n readonly kind: 'unique';\n readonly fields: readonly string[];\n readonly span: PslSpan;\n}\n\nexport interface PslIndexConstraint {\n readonly kind: 'index';\n readonly fields: readonly string[];\n readonly span: PslSpan;\n}\n\nexport type PslModelAttribute = PslAttribute;\n\nexport interface PslModel {\n readonly kind: 'model';\n readonly name: string;\n readonly fields: readonly PslField[];\n readonly attributes: readonly PslModelAttribute[];\n readonly span: PslSpan;\n /**\n * Optional leading comment line emitted above the `model` keyword by the\n * printer. Producers (e.g. `sqlSchemaIrToPslAst`) attach introspection\n * advisories such as \"// WARNING: This table has no primary key in the\n * database\" here. The parser leaves this field unset; round-tripping a\n * parsed schema does not re-attach comments.\n */\n readonly comment?: string;\n}\n\nexport interface PslEnumValue {\n readonly kind: 'enumValue';\n readonly name: string;\n /**\n * Optional storage label for the enum member, captured from a trailing\n * `@map(\"...\")` attribute on the member line. The parser populates this\n * when the source PSL carries an explicit `@map`. Producers (e.g.\n * `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`\n * automatically when normalisation would change the printed member name\n * (so an enum value `'in-progress'` becomes `inProgress @map(\"in-progress\")`\n * in PSL, preserving the round-trip).\n */\n readonly mapName?: string;\n readonly span: PslSpan;\n}\n\nexport interface PslEnum {\n readonly kind: 'enum';\n readonly name: string;\n readonly values: readonly PslEnumValue[];\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslCompositeType {\n readonly kind: 'compositeType';\n readonly name: string;\n readonly fields: readonly PslField[];\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslNamedTypeDeclaration {\n readonly kind: 'namedType';\n readonly name: string;\n /**\n * Parser invariant: exactly one of `baseType` and `typeConstructor` is set.\n * Expressing this as a discriminated union trips TypeScript narrowing when\n * the declaration flows through helpers that accept the full union.\n */\n readonly baseType?: string;\n readonly typeConstructor?: PslTypeConstructorCall;\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslTypesBlock {\n readonly kind: 'types';\n readonly declarations: readonly PslNamedTypeDeclaration[];\n readonly span: PslSpan;\n}\n\n/**\n * Name of the synthesised namespace bucket the framework parser uses for\n * top-level declarations that appear outside any `namespace { … }` block.\n * The double-underscore decoration signals that the identifier is parser-\n * synthesised and never appears in user-authored PSL source — writing\n * `namespace __unspecified__ { … }` is a parse error.\n *\n * Distinct from the IR sentinel `__unbound__`: the PSL bucket describes\n * syntactic absence at the parser layer; the IR sentinel describes a late-\n * bound storage slot at the IR layer. Per-target interpreters decide how\n * (or whether) to map the PSL bucket to the IR sentinel.\n */\nexport const UNSPECIFIED_PSL_NAMESPACE_ID = '__unspecified__';\n\n/**\n * A named namespace block from a PSL document, or the parser's synthesised\n * `__unspecified__` bucket for declarations that appear outside any\n * `namespace { … }` block. Multiple `namespace foo { … }` blocks for the\n * same name across one or more files reopen-merge into a single entry;\n * `span` points at the first opening.\n */\nexport interface PslNamespace {\n readonly kind: 'namespace';\n readonly name: string;\n readonly models: readonly PslModel[];\n readonly enums: readonly PslEnum[];\n readonly compositeTypes: readonly PslCompositeType[];\n readonly span: PslSpan;\n}\n\nexport interface PslDocumentAst {\n readonly kind: 'document';\n readonly sourceId: string;\n readonly namespaces: readonly PslNamespace[];\n readonly types?: PslTypesBlock;\n readonly span: PslSpan;\n}\n\n/**\n * Returns all models from every namespace in document order. Convenience\n * for consumers that don't (yet) need namespace-awareness.\n */\nexport function flatPslModels(ast: PslDocumentAst): readonly PslModel[] {\n return ast.namespaces.flatMap((ns) => ns.models);\n}\n\n/**\n * Returns all enums from every namespace in document order.\n */\nexport function flatPslEnums(ast: PslDocumentAst): readonly PslEnum[] {\n return ast.namespaces.flatMap((ns) => ns.enums);\n}\n\n/**\n * Returns all composite types from every namespace in document order.\n */\nexport function flatPslCompositeTypes(ast: PslDocumentAst): readonly PslCompositeType[] {\n return ast.namespaces.flatMap((ns) => ns.compositeTypes);\n}\n\nexport interface ParsePslDocumentInput {\n readonly schema: string;\n readonly sourceId: string;\n}\n\nexport interface ParsePslDocumentResult {\n readonly ast: PslDocumentAst;\n readonly diagnostics: readonly PslDiagnostic[];\n readonly ok: boolean;\n}\n"],"mappings":";;;;;;;;;;;;;AAuMA,MAAa,+BAA+B;;;;;AA8B5C,SAAgB,cAAc,KAA0C;CACtE,OAAO,IAAI,WAAW,SAAS,OAAO,GAAG,MAAM;AACjD;;;;AAKA,SAAgB,aAAa,KAAyC;CACpE,OAAO,IAAI,WAAW,SAAS,OAAO,GAAG,KAAK;AAChD;;;;AAKA,SAAgB,sBAAsB,KAAkD;CACtF,OAAO,IAAI,WAAW,SAAS,OAAO,GAAG,cAAc;AACzD"}
1
+ {"version":3,"file":"psl-ast.mjs","names":[],"sources":["../src/control/psl-ast.ts","../src/control/psl-extension-block-validator.ts"],"sourcesContent":["export type { AuthoringPslBlockDescriptorNamespace } from '../shared/framework-authoring';\nexport type {\n PslBlockParam,\n PslBlockParamList,\n PslBlockParamOption,\n PslBlockParamRef,\n PslBlockParamValue,\n PslDiagnosticCode,\n PslExtensionBlock,\n PslExtensionBlockParamList,\n PslExtensionBlockParamOption,\n PslExtensionBlockParamRef,\n PslExtensionBlockParamScalarValue,\n PslExtensionBlockParamValue,\n PslPosition,\n PslSpan,\n} from '../shared/psl-extension-block';\n\nimport type { CodecLookup } from '../shared/codec-types';\nimport type { AuthoringPslBlockDescriptorNamespace } from '../shared/framework-authoring';\nimport type { PslDiagnosticCode, PslExtensionBlock, PslSpan } from '../shared/psl-extension-block';\n\nexport interface PslDiagnostic {\n readonly code: PslDiagnosticCode;\n readonly message: string;\n readonly sourceId: string;\n readonly span: PslSpan;\n}\n\nexport interface PslDefaultFunctionValue {\n readonly kind: 'function';\n readonly name: 'autoincrement' | 'now';\n}\n\nexport interface PslDefaultLiteralValue {\n readonly kind: 'literal';\n readonly value: string | number | boolean;\n}\n\nexport type PslDefaultValue = PslDefaultFunctionValue | PslDefaultLiteralValue;\n\nexport type PslAttributeTarget = 'field' | 'model' | 'enum' | 'namedType';\n\nexport interface PslAttributePositionalArgument {\n readonly kind: 'positional';\n readonly value: string;\n readonly span: PslSpan;\n}\n\nexport interface PslAttributeNamedArgument {\n readonly kind: 'named';\n readonly name: string;\n readonly value: string;\n readonly span: PslSpan;\n}\n\nexport type PslAttributeArgument = PslAttributePositionalArgument | PslAttributeNamedArgument;\n\nexport interface PslTypeConstructorCall {\n readonly kind: 'typeConstructor';\n readonly path: readonly string[];\n readonly args: readonly PslAttributeArgument[];\n readonly span: PslSpan;\n}\n\nexport interface PslAttribute {\n readonly kind: 'attribute';\n readonly target: PslAttributeTarget;\n readonly name: string;\n readonly args: readonly PslAttributeArgument[];\n readonly span: PslSpan;\n}\n\nexport type PslReferentialAction = string;\n\nexport type PslFieldAttribute = PslAttribute;\n\nexport interface PslField {\n readonly kind: 'field';\n readonly name: string;\n /** Unqualified type name, e.g. `\"User\"` for both `User`, `auth.User`, and `supabase:auth.User`. */\n readonly typeName: string;\n /** Namespace qualifier from a dot-qualified type reference, e.g. `\"auth\"` for `auth.User` or `supabase:auth.User`. Absent for unqualified types. */\n readonly typeNamespaceId?: string;\n /**\n * Contract-space qualifier from a colon-prefix type reference, e.g. `\"supabase\"` for\n * `supabase:auth.User` or `supabase:User`. Absent for local (same-space) type references.\n *\n * When present, the field references a model from a different contract space. The namespace\n * (`typeNamespaceId`) and model name (`typeName`) identify the target within that space.\n * Physical table resolution against the extension contract is deferred to the aggregate stage (M3).\n */\n readonly typeContractSpaceId?: string;\n readonly typeConstructor?: PslTypeConstructorCall;\n readonly optional: boolean;\n readonly list: boolean;\n readonly typeRef?: string;\n readonly attributes: readonly PslFieldAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslUniqueConstraint {\n readonly kind: 'unique';\n readonly fields: readonly string[];\n readonly span: PslSpan;\n}\n\nexport interface PslIndexConstraint {\n readonly kind: 'index';\n readonly fields: readonly string[];\n readonly span: PslSpan;\n}\n\nexport type PslModelAttribute = PslAttribute;\n\nexport interface PslModel {\n readonly kind: 'model';\n readonly name: string;\n readonly fields: readonly PslField[];\n readonly attributes: readonly PslModelAttribute[];\n readonly span: PslSpan;\n /**\n * Optional leading comment line emitted above the `model` keyword by the\n * printer. Producers (e.g. `sqlSchemaIrToPslAst`) attach introspection\n * advisories such as \"// WARNING: This table has no primary key in the\n * database\" here. The parser leaves this field unset; round-tripping a\n * parsed schema does not re-attach comments.\n */\n readonly comment?: string;\n}\n\nexport interface PslEnumValue {\n readonly kind: 'enumValue';\n readonly name: string;\n /**\n * Optional storage label for the enum member, captured from a trailing\n * `@map(\"...\")` attribute on the member line. The parser populates this\n * when the source PSL carries an explicit `@map`. Producers (e.g.\n * `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`\n * automatically when normalisation would change the printed member name\n * (so an enum value `'in-progress'` becomes `inProgress @map(\"in-progress\")`\n * in PSL, preserving the round-trip).\n */\n readonly mapName?: string;\n readonly span: PslSpan;\n}\n\nexport interface PslEnum {\n readonly kind: 'enum';\n readonly name: string;\n readonly values: readonly PslEnumValue[];\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslCompositeType {\n readonly kind: 'compositeType';\n readonly name: string;\n readonly fields: readonly PslField[];\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslNamedTypeDeclaration {\n readonly kind: 'namedType';\n readonly name: string;\n /**\n * Parser invariant: exactly one of `baseType` and `typeConstructor` is set.\n * Expressing this as a discriminated union trips TypeScript narrowing when\n * the declaration flows through helpers that accept the full union.\n */\n readonly baseType?: string;\n readonly typeConstructor?: PslTypeConstructorCall;\n readonly attributes: readonly PslAttribute[];\n readonly span: PslSpan;\n}\n\nexport interface PslTypesBlock {\n readonly kind: 'types';\n readonly declarations: readonly PslNamedTypeDeclaration[];\n readonly span: PslSpan;\n}\n\n/**\n * Name of the synthesised namespace bucket the framework parser uses for\n * top-level declarations that appear outside any `namespace { … }` block.\n * The double-underscore decoration signals that the identifier is parser-\n * synthesised and never appears in user-authored PSL source — writing\n * `namespace __unspecified__ { … }` is a parse error.\n *\n * Distinct from the IR sentinel `__unbound__`: the PSL bucket describes\n * syntactic absence at the parser layer; the IR sentinel describes a late-\n * bound storage slot at the IR layer. Per-target interpreters decide how\n * (or whether) to map the PSL bucket to the IR sentinel.\n */\nexport const UNSPECIFIED_PSL_NAMESPACE_ID = '__unspecified__';\n\n/**\n * A named namespace block from a PSL document, or the parser's synthesised\n * `__unspecified__` bucket for declarations that appear outside any\n * `namespace { … }` block. Multiple `namespace foo { … }` blocks for the\n * same name across one or more files reopen-merge into a single entry;\n * `span` points at the first opening.\n */\nexport interface PslNamespace {\n readonly kind: 'namespace';\n readonly name: string;\n readonly models: readonly PslModel[];\n readonly enums: readonly PslEnum[];\n readonly compositeTypes: readonly PslCompositeType[];\n /**\n * Extension-contributed top-level blocks parsed inside this namespace.\n * These are the parsed AST nodes produced by the generic framework parser\n * when it encounters a keyword claimed by a registered\n * {@link AuthoringPslBlockDescriptorNamespace} entry.\n *\n * Absent when no extension blocks appear in this namespace. Order matches\n * source order within the namespace; extension-contributed and built-in\n * blocks live in their own slots, so a namespace mixing `model X { … }` and\n * `policy_select Y { … }` keeps the model in `models` and the policy in\n * `extensionBlocks`.\n *\n * Contrast with {@link ParsePslDocumentInput.pslBlockDescriptors}: that\n * field holds the registry of declarative descriptors that teach the parser\n * which keywords to accept; this field holds the resulting parsed nodes.\n */\n readonly extensionBlocks?: readonly PslExtensionBlock[];\n readonly span: PslSpan;\n}\n\nexport interface PslDocumentAst {\n readonly kind: 'document';\n readonly sourceId: string;\n readonly namespaces: readonly PslNamespace[];\n readonly types?: PslTypesBlock;\n readonly span: PslSpan;\n}\n\n/**\n * Returns all models from every namespace in document order. Convenience\n * for consumers that don't (yet) need namespace-awareness.\n */\nexport function flatPslModels(ast: PslDocumentAst): readonly PslModel[] {\n return ast.namespaces.flatMap((ns) => ns.models);\n}\n\n/**\n * Returns all enums from every namespace in document order.\n */\nexport function flatPslEnums(ast: PslDocumentAst): readonly PslEnum[] {\n return ast.namespaces.flatMap((ns) => ns.enums);\n}\n\n/**\n * Returns all composite types from every namespace in document order.\n */\nexport function flatPslCompositeTypes(ast: PslDocumentAst): readonly PslCompositeType[] {\n return ast.namespaces.flatMap((ns) => ns.compositeTypes);\n}\n\nexport interface ParsePslDocumentInput {\n readonly schema: string;\n readonly sourceId: string;\n /**\n * Registry of declarative block descriptors, keyed by arbitrary path\n * segments with {@link AuthoringPslBlockDescriptor} leaves. The registry\n * teaches the parser which top-level keywords belong to extension\n * contributions: when the parser encounters an unknown keyword, it looks\n * it up here and, when found, reads the block generically into a\n * {@link PslExtensionBlock} node. Absent or undefined means no extension\n * blocks are registered and any unknown keyword yields\n * `PSL_UNSUPPORTED_TOP_LEVEL_BLOCK`.\n *\n * Contrast with {@link PslNamespace.extensionBlocks}: that field holds the\n * parsed block nodes in a namespace; this field holds the registry of\n * descriptors that teach the parser how to read those blocks.\n */\n readonly pslBlockDescriptors?: AuthoringPslBlockDescriptorNamespace;\n /**\n * Codec lookup for validating `value`-kind extension block parameters.\n * When provided alongside `pslBlockDescriptors`, the generic validator runs\n * over every parsed extension block after the full AST is assembled,\n * appending any diagnostics to the parse result. Absent or undefined means\n * no codec validation runs; `ref` resolution still runs when namespace\n * context is available (built from the assembled namespaces).\n */\n readonly codecLookup?: CodecLookup;\n}\n\nexport interface ParsePslDocumentResult {\n readonly ast: PslDocumentAst;\n readonly diagnostics: readonly PslDiagnostic[];\n readonly ok: boolean;\n}\n","/**\n * Generic validator for extension-contributed top-level PSL blocks.\n *\n * One function — {@link validateExtensionBlock} — takes a parsed\n * {@link PslExtensionBlock}, its {@link AuthoringPslBlockDescriptor}, a\n * {@link CodecLookup} (for `value` parameters), and the set of\n * {@link PslNamespace} objects from the document (for `ref` resolution), and\n * returns the full list of {@link PslDiagnostic} objects for the block.\n *\n * Detection logic per failure mode:\n *\n * 1. **Unknown parameter** — keys present in `node.parameters` that are absent\n * from `descriptor.parameters` (key-set difference). The parser stores\n * unknown parameters as `kind:'value'` stubs; the validator discovers them\n * by comparing the key sets, not by inspecting the captured kind.\n *\n * 2. **Missing required parameter** — `descriptor.parameters` entries with\n * `required: true` whose key is absent from `node.parameters`.\n *\n * 3. **`option` value outside its set** — the captured `token` is not in\n * `descriptor.values`.\n *\n * 4. **`value` rejected by its codec** — the raw string is first parsed as\n * JSON (`JSON.parse(raw)`). If `JSON.parse` throws, the literal is not valid\n * JSON and a `PSL_EXTENSION_INVALID_VALUE` diagnostic is emitted. If parsing\n * succeeds but `codec.decodeJson(jsonValue)` throws, the JSON value is not\n * acceptable to the codec and a `PSL_EXTENSION_INVALID_VALUE` diagnostic is\n * emitted. If `codecLookup.get(codecId)` returns `undefined` (unknown codec\n * id), a `PSL_EXTENSION_INVALID_VALUE` diagnostic is also emitted.\n *\n * 5. **`ref` that does not resolve within its scope** — the captured\n * `identifier` is looked up in the PSL document's `PslNamespace` objects\n * according to `param.scope`:\n * - `same-namespace`: the referent must be in the same namespace as the\n * block (the namespace containing the block).\n * - `same-space`: the referent may be in any namespace in the document.\n * - `cross-space`: pass-through — enforcement is scoped to first-consumer\n * need (RLS roles). This case is documented and clearly flagged; the\n * caller is responsible for wiring cross-space resolution when needed.\n *\n * 6. **`list`** — each element is validated against `param.of` recursively.\n *\n * ### `char`/`varchar` length\n * Not enforced. RLS `using`/`check` strings are unbounded text and the codec\n * already rejects structurally invalid literals; length constraints are a\n * database-side concern, not a PSL authoring constraint.\n *\n * ### `cross-space` scope\n * Implemented as a documented pass-through. The spec permits scoping\n * cross-space enforcement to first-consumer need (RLS roles). When RLS roles\n * arrive, wire `cross-space` resolution through the cross-contract-space\n * coordinate model `(spaceId, namespaceId, entityKind, entityName)`.\n */\n\nimport type { JsonValue } from '@prisma-next/contract/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport type { CodecLookup } from '../shared/codec-types';\nimport type { AuthoringPslBlockDescriptor } from '../shared/framework-authoring';\nimport type {\n PslBlockParam,\n PslBlockParamRef,\n PslExtensionBlock,\n PslExtensionBlockParamValue,\n PslSpan,\n} from '../shared/psl-extension-block';\nimport type { PslDiagnostic, PslNamespace } from './psl-ast';\n\n/**\n * Context for ref resolution during extension-block validation.\n *\n * - `ownerNamespace` is the `PslNamespace` that contains the block being\n * validated. Used for `same-namespace` scope checks.\n * - `allNamespaces` is every namespace in the document. Used for `same-space`\n * scope checks.\n */\nexport interface ExtensionBlockRefResolutionContext {\n readonly ownerNamespace: PslNamespace;\n readonly allNamespaces: readonly PslNamespace[];\n}\n\n/**\n * Validate a single parsed extension block against its descriptor.\n *\n * Returns an array of {@link PslDiagnostic} objects (possibly empty). The\n * caller is responsible for threading `sourceId` into each returned diagnostic\n * — the returned objects already have `sourceId` set from the `sourceId`\n * parameter.\n *\n * @param node - The parsed block node produced by the generic framework parser.\n * @param descriptor - The descriptor that claims this block's keyword.\n * @param sourceId - The PSL source file identifier (threaded into diagnostics).\n * @param codecLookup - Used to validate `value`-kind parameter literals via\n * `codecLookup.get(codecId)?.decodeJson(JSON.parse(raw))`.\n * @param refCtx - Namespace context for `ref`-kind scope resolution. Required\n * when any descriptor parameter is `kind: 'ref'`; may be omitted if none are.\n */\nexport function validateExtensionBlock(\n node: PslExtensionBlock,\n descriptor: AuthoringPslBlockDescriptor,\n sourceId: string,\n codecLookup: CodecLookup,\n refCtx?: ExtensionBlockRefResolutionContext,\n): readonly PslDiagnostic[] {\n const diagnostics: PslDiagnostic[] = [];\n\n const descriptorKeys = new Set(Object.keys(descriptor.parameters));\n const nodeKeys = new Set(Object.keys(node.parameters));\n\n // 1. Unknown parameters — keys in the node not in the descriptor.\n for (const key of nodeKeys) {\n if (!descriptorKeys.has(key)) {\n const captured = node.parameters[key];\n diagnostics.push({\n code: 'PSL_EXTENSION_UNKNOWN_PARAMETER',\n message: `Unknown parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\". The descriptor does not declare this parameter.`,\n sourceId,\n span: captured?.span ?? node.span,\n });\n }\n }\n\n // 2. Missing required parameters — required descriptor keys absent from the node.\n for (const [key, param] of Object.entries(descriptor.parameters)) {\n if (param.required === true && !nodeKeys.has(key)) {\n diagnostics.push({\n code: 'PSL_EXTENSION_MISSING_REQUIRED_PARAMETER',\n message: `Required parameter \"${key}\" is missing from \"${descriptor.keyword}\" block \"${node.name}\".`,\n sourceId,\n span: node.span,\n });\n }\n }\n\n // 3–5. Per-parameter validation for parameters that are present.\n for (const [key, param] of Object.entries(descriptor.parameters)) {\n const captured = node.parameters[key];\n if (captured === undefined) {\n continue;\n }\n validateParam(\n node,\n descriptor,\n key,\n param,\n captured,\n sourceId,\n codecLookup,\n refCtx,\n diagnostics,\n );\n }\n\n return diagnostics;\n}\n\nfunction validateParam(\n node: PslExtensionBlock,\n descriptor: AuthoringPslBlockDescriptor,\n key: string,\n param: PslBlockParam,\n captured: PslExtensionBlockParamValue,\n sourceId: string,\n codecLookup: CodecLookup,\n refCtx: ExtensionBlockRefResolutionContext | undefined,\n diagnostics: PslDiagnostic[],\n): void {\n switch (param.kind) {\n case 'option': {\n if (captured.kind !== 'option') {\n return;\n }\n if (!param.values.includes(captured.token)) {\n diagnostics.push({\n code: 'PSL_EXTENSION_OPTION_OUT_OF_SET',\n message: `Parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\" has value \"${captured.token}\" which is not one of the allowed values: ${param.values.map((v) => `\"${v}\"`).join(', ')}.`,\n sourceId,\n span: captured.span,\n });\n }\n return;\n }\n\n case 'value': {\n if (captured.kind !== 'value') {\n return;\n }\n const codec = codecLookup.get(param.codecId);\n if (codec === undefined) {\n diagnostics.push({\n code: 'PSL_EXTENSION_INVALID_VALUE',\n message: `Parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\" references unknown codec \"${param.codecId}\".`,\n sourceId,\n span: captured.span,\n });\n return;\n }\n let jsonValue: unknown;\n try {\n jsonValue = JSON.parse(captured.raw);\n } catch {\n diagnostics.push({\n code: 'PSL_EXTENSION_INVALID_VALUE',\n message: `Parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\" is not a valid JSON literal (expected a JSON string, number, boolean, or null): ${captured.raw}`,\n sourceId,\n span: captured.span,\n });\n return;\n }\n try {\n codec.decodeJson(\n blindCast<JsonValue, 'JSON.parse returns a JsonValue-compatible value'>(jsonValue),\n );\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n diagnostics.push({\n code: 'PSL_EXTENSION_INVALID_VALUE',\n message: `Parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\" was rejected by codec \"${param.codecId}\": ${reason}`,\n sourceId,\n span: captured.span,\n });\n }\n return;\n }\n\n case 'ref': {\n if (captured.kind !== 'ref') {\n return;\n }\n validateRef(\n node,\n descriptor,\n key,\n param,\n captured.identifier,\n captured.span,\n sourceId,\n refCtx,\n diagnostics,\n );\n return;\n }\n\n case 'list': {\n if (captured.kind !== 'list') {\n return;\n }\n for (const item of captured.items) {\n validateParam(\n node,\n descriptor,\n key,\n param.of,\n item,\n sourceId,\n codecLookup,\n refCtx,\n diagnostics,\n );\n }\n return;\n }\n }\n}\n\nfunction validateRef(\n node: PslExtensionBlock,\n descriptor: AuthoringPslBlockDescriptor,\n key: string,\n param: PslBlockParamRef,\n identifier: string,\n span: PslSpan,\n sourceId: string,\n refCtx: ExtensionBlockRefResolutionContext | undefined,\n diagnostics: PslDiagnostic[],\n): void {\n if (param.scope === 'cross-space') {\n // cross-space enforcement is a documented pass-through. The spec permits\n // scoping cross-space resolution to first-consumer need (RLS roles). When\n // that consumer arrives, wire resolution here through the\n // cross-contract-space coordinate model\n // (spaceId, namespaceId, entityKind, entityName).\n // For now, cross-space refs pass validation unconditionally.\n return;\n }\n\n if (refCtx === undefined) {\n // If no resolution context was provided, skip ref resolution. This matches\n // the closed-grammar invariant: callers that register ref parameters must\n // provide resolution context; callers without namespaces (e.g. unit tests\n // that only exercise other validation modes) can omit it.\n return;\n }\n\n const namespacesToSearch: readonly PslNamespace[] =\n param.scope === 'same-namespace' ? [refCtx.ownerNamespace] : refCtx.allNamespaces;\n\n if (!resolveEntityInNamespaces(identifier, param.refKind, namespacesToSearch)) {\n const scopeLabel =\n param.scope === 'same-namespace' ? 'the same namespace' : 'any namespace in the schema';\n diagnostics.push({\n code: 'PSL_EXTENSION_UNRESOLVED_REF',\n message: `Parameter \"${key}\" in \"${descriptor.keyword}\" block \"${node.name}\" refers to \"${identifier}\" (expected ${param.refKind}), but no entity with that name and kind was found in ${scopeLabel}.`,\n sourceId,\n span,\n });\n }\n}\n\n/**\n * Returns true when an entity named `name` of kind `refKind` exists in at\n * least one of the given namespaces.\n *\n * Built-in PSL entity kinds are mapped to their `PslNamespace` collection:\n * - `'model'` → `ns.models`\n * - `'enum'` → `ns.enums`\n * - `'compositeType'` → `ns.compositeTypes`\n *\n * Any other `refKind` is resolved against the namespace's `extensionBlocks`\n * array (matching by `block.kind === refKind` and `block.name === name`).\n * This covers extension-contributed entity kinds that reference other\n * extension-contributed blocks (e.g. a policy referencing a role block).\n */\nfunction resolveEntityInNamespaces(\n name: string,\n refKind: string,\n namespaces: readonly PslNamespace[],\n): boolean {\n for (const ns of namespaces) {\n if (refKind === 'model') {\n if (ns.models.some((m) => m.name === name)) return true;\n } else if (refKind === 'enum') {\n if (ns.enums.some((e) => e.name === name)) return true;\n } else if (refKind === 'compositeType') {\n if (ns.compositeTypes.some((ct) => ct.name === name)) return true;\n } else {\n if (ns.extensionBlocks?.some((b) => b.kind === refKind && b.name === name)) return true;\n }\n }\n return false;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@prisma-next/framework-components",
3
- "version": "0.12.0-dev.61",
3
+ "version": "0.12.0-dev.63",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Framework component types, assembly logic, and stack creation for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/contract": "0.12.0-dev.61",
10
- "@prisma-next/operations": "0.12.0-dev.61",
11
- "@prisma-next/ts-render": "0.12.0-dev.61",
12
- "@prisma-next/utils": "0.12.0-dev.61",
9
+ "@prisma-next/contract": "0.12.0-dev.63",
10
+ "@prisma-next/operations": "0.12.0-dev.63",
11
+ "@prisma-next/ts-render": "0.12.0-dev.63",
12
+ "@prisma-next/utils": "0.12.0-dev.63",
13
13
  "@standard-schema/spec": "^1.1.0",
14
14
  "arktype": "^2.2.0"
15
15
  },
16
16
  "devDependencies": {
17
- "@prisma-next/tsconfig": "0.12.0-dev.61",
18
- "@prisma-next/tsdown": "0.12.0-dev.61",
17
+ "@prisma-next/tsconfig": "0.12.0-dev.63",
18
+ "@prisma-next/tsdown": "0.12.0-dev.63",
19
19
  "tsdown": "0.22.1",
20
20
  "typescript": "5.9.3",
21
21
  "vitest": "4.1.7"
@@ -13,7 +13,11 @@ import type { Contract } from '@prisma-next/contract/types';
13
13
  import type { ImportRequirement } from '@prisma-next/ts-render';
14
14
  import type { Result } from '@prisma-next/utils/result';
15
15
  import type { TargetBoundComponentDescriptor } from '../shared/framework-components';
16
- import type { ControlDriverInstance, ControlFamilyInstance } from './control-instances';
16
+ import type {
17
+ ControlAdapterInstance,
18
+ ControlDriverInstance,
19
+ ControlFamilyInstance,
20
+ } from './control-instances';
17
21
  import type { OperationContext } from './control-result-types';
18
22
 
19
23
  // ============================================================================
@@ -537,7 +541,9 @@ export interface TargetMigrationsCapability<
537
541
  unknown
538
542
  >,
539
543
  > {
540
- createPlanner(family: TFamilyInstance): MigrationPlanner<TFamilyId, TTargetId>;
544
+ createPlanner(
545
+ adapter: ControlAdapterInstance<TFamilyId, TTargetId>,
546
+ ): MigrationPlanner<TFamilyId, TTargetId>;
541
547
  createRunner(family: TFamilyInstance): MigrationRunner<TFamilyId, TTargetId>;
542
548
  /**
543
549
  * Synthesizes a family-specific schema IR from a contract for offline planning.
@@ -1,15 +1,18 @@
1
+ import { blindCast } from '@prisma-next/utils/casts';
1
2
  import type { Codec } from '../shared/codec';
2
3
  import type { CodecLookup, CodecMeta } from '../shared/codec-types';
3
4
  import type {
4
5
  AuthoringContributions,
5
6
  AuthoringEntityTypeNamespace,
6
7
  AuthoringFieldNamespace,
8
+ AuthoringPslBlockDescriptorNamespace,
7
9
  AuthoringTypeNamespace,
8
10
  } from '../shared/framework-authoring';
9
11
  import {
10
12
  assertNoCrossRegistryCollisions,
11
13
  isAuthoringEntityTypeDescriptor,
12
14
  isAuthoringFieldPresetDescriptor,
15
+ isAuthoringPslBlockDescriptor,
13
16
  isAuthoringTypeConstructorDescriptor,
14
17
  mergeAuthoringNamespaces,
15
18
  } from '../shared/framework-authoring';
@@ -32,6 +35,7 @@ export interface AssembledAuthoringContributions {
32
35
  readonly field: AuthoringFieldNamespace;
33
36
  readonly type: AuthoringTypeNamespace;
34
37
  readonly entityTypes: AuthoringEntityTypeNamespace;
38
+ readonly pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace;
35
39
  }
36
40
 
37
41
  export interface ControlStack<
@@ -151,6 +155,7 @@ export function assembleAuthoringContributions(
151
155
  const field = {} as Record<string, unknown>;
152
156
  const type = {} as Record<string, unknown>;
153
157
  const entityTypes = {} as Record<string, unknown>;
158
+ const pslBlockDescriptors: Record<string, unknown> = {};
154
159
 
155
160
  for (const descriptor of descriptors) {
156
161
  if (descriptor.authoring?.field) {
@@ -180,17 +185,36 @@ export function assembleAuthoringContributions(
180
185
  'entity',
181
186
  );
182
187
  }
188
+ if (descriptor.authoring?.pslBlockDescriptors) {
189
+ mergeAuthoringNamespaces(
190
+ pslBlockDescriptors,
191
+ descriptor.authoring.pslBlockDescriptors,
192
+ [],
193
+ isAuthoringPslBlockDescriptor,
194
+ 'pslBlock',
195
+ );
196
+ }
183
197
  }
184
198
 
185
199
  const fieldNamespace = field as AuthoringFieldNamespace;
186
200
  const typeNamespace = type as AuthoringTypeNamespace;
187
201
  const entityTypeNamespace = entityTypes as AuthoringEntityTypeNamespace;
188
- assertNoCrossRegistryCollisions(typeNamespace, fieldNamespace, entityTypeNamespace);
202
+ const pslBlockDescriptorNamespace = blindCast<
203
+ AuthoringPslBlockDescriptorNamespace,
204
+ 'merge target accumulator narrows to typed namespace post-merge'
205
+ >(pslBlockDescriptors);
206
+ assertNoCrossRegistryCollisions(
207
+ typeNamespace,
208
+ fieldNamespace,
209
+ entityTypeNamespace,
210
+ pslBlockDescriptorNamespace,
211
+ );
189
212
 
190
213
  return {
191
214
  field: fieldNamespace,
192
215
  type: typeNamespace,
193
216
  entityTypes: entityTypeNamespace,
217
+ pslBlockDescriptors: pslBlockDescriptorNamespace,
194
218
  };
195
219
  }
196
220
 
@@ -1,28 +1,24 @@
1
- export interface PslPosition {
2
- readonly offset: number;
3
- readonly line: number;
4
- readonly column: number;
5
- }
6
-
7
- export interface PslSpan {
8
- readonly start: PslPosition;
9
- readonly end: PslPosition;
10
- }
11
-
12
- export type PslDiagnosticCode =
13
- | 'PSL_UNTERMINATED_BLOCK'
14
- | 'PSL_UNSUPPORTED_TOP_LEVEL_BLOCK'
15
- | 'PSL_INVALID_NAMESPACE_BLOCK'
16
- | 'PSL_INVALID_ATTRIBUTE_SYNTAX'
17
- | 'PSL_INVALID_MODEL_MEMBER'
18
- | 'PSL_UNSUPPORTED_MODEL_ATTRIBUTE'
19
- | 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE'
20
- | 'PSL_INVALID_RELATION_ATTRIBUTE'
21
- | 'PSL_INVALID_REFERENTIAL_ACTION'
22
- | 'PSL_INVALID_DEFAULT_VALUE'
23
- | 'PSL_INVALID_ENUM_MEMBER'
24
- | 'PSL_INVALID_TYPES_MEMBER'
25
- | 'PSL_INVALID_QUALIFIED_TYPE';
1
+ export type { AuthoringPslBlockDescriptorNamespace } from '../shared/framework-authoring';
2
+ export type {
3
+ PslBlockParam,
4
+ PslBlockParamList,
5
+ PslBlockParamOption,
6
+ PslBlockParamRef,
7
+ PslBlockParamValue,
8
+ PslDiagnosticCode,
9
+ PslExtensionBlock,
10
+ PslExtensionBlockParamList,
11
+ PslExtensionBlockParamOption,
12
+ PslExtensionBlockParamRef,
13
+ PslExtensionBlockParamScalarValue,
14
+ PslExtensionBlockParamValue,
15
+ PslPosition,
16
+ PslSpan,
17
+ } from '../shared/psl-extension-block';
18
+
19
+ import type { CodecLookup } from '../shared/codec-types';
20
+ import type { AuthoringPslBlockDescriptorNamespace } from '../shared/framework-authoring';
21
+ import type { PslDiagnosticCode, PslExtensionBlock, PslSpan } from '../shared/psl-extension-block';
26
22
 
27
23
  export interface PslDiagnostic {
28
24
  readonly code: PslDiagnosticCode;
@@ -212,6 +208,23 @@ export interface PslNamespace {
212
208
  readonly models: readonly PslModel[];
213
209
  readonly enums: readonly PslEnum[];
214
210
  readonly compositeTypes: readonly PslCompositeType[];
211
+ /**
212
+ * Extension-contributed top-level blocks parsed inside this namespace.
213
+ * These are the parsed AST nodes produced by the generic framework parser
214
+ * when it encounters a keyword claimed by a registered
215
+ * {@link AuthoringPslBlockDescriptorNamespace} entry.
216
+ *
217
+ * Absent when no extension blocks appear in this namespace. Order matches
218
+ * source order within the namespace; extension-contributed and built-in
219
+ * blocks live in their own slots, so a namespace mixing `model X { … }` and
220
+ * `policy_select Y { … }` keeps the model in `models` and the policy in
221
+ * `extensionBlocks`.
222
+ *
223
+ * Contrast with {@link ParsePslDocumentInput.pslBlockDescriptors}: that
224
+ * field holds the registry of declarative descriptors that teach the parser
225
+ * which keywords to accept; this field holds the resulting parsed nodes.
226
+ */
227
+ readonly extensionBlocks?: readonly PslExtensionBlock[];
215
228
  readonly span: PslSpan;
216
229
  }
217
230
 
@@ -248,6 +261,30 @@ export function flatPslCompositeTypes(ast: PslDocumentAst): readonly PslComposit
248
261
  export interface ParsePslDocumentInput {
249
262
  readonly schema: string;
250
263
  readonly sourceId: string;
264
+ /**
265
+ * Registry of declarative block descriptors, keyed by arbitrary path
266
+ * segments with {@link AuthoringPslBlockDescriptor} leaves. The registry
267
+ * teaches the parser which top-level keywords belong to extension
268
+ * contributions: when the parser encounters an unknown keyword, it looks
269
+ * it up here and, when found, reads the block generically into a
270
+ * {@link PslExtensionBlock} node. Absent or undefined means no extension
271
+ * blocks are registered and any unknown keyword yields
272
+ * `PSL_UNSUPPORTED_TOP_LEVEL_BLOCK`.
273
+ *
274
+ * Contrast with {@link PslNamespace.extensionBlocks}: that field holds the
275
+ * parsed block nodes in a namespace; this field holds the registry of
276
+ * descriptors that teach the parser how to read those blocks.
277
+ */
278
+ readonly pslBlockDescriptors?: AuthoringPslBlockDescriptorNamespace;
279
+ /**
280
+ * Codec lookup for validating `value`-kind extension block parameters.
281
+ * When provided alongside `pslBlockDescriptors`, the generic validator runs
282
+ * over every parsed extension block after the full AST is assembled,
283
+ * appending any diagnostics to the parse result. Absent or undefined means
284
+ * no codec validation runs; `ref` resolution still runs when namespace
285
+ * context is available (built from the assembled namespaces).
286
+ */
287
+ readonly codecLookup?: CodecLookup;
251
288
  }
252
289
 
253
290
  export interface ParsePslDocumentResult {