@metaobjectsdev/metadata 0.5.0-rc.3 → 0.6.0-rc.1
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/attr-schema-validate.d.ts.map +1 -1
- package/dist/attr-schema-validate.js +66 -11
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/core/documentation/doc-constants.d.ts +11 -0
- package/dist/core/documentation/doc-constants.d.ts.map +1 -0
- package/dist/core/documentation/doc-constants.js +20 -0
- package/dist/core/documentation/doc-constants.js.map +1 -0
- package/dist/core/documentation/doc-provider.d.ts +3 -0
- package/dist/core/documentation/doc-provider.d.ts.map +1 -0
- package/dist/core/documentation/doc-provider.js +10 -0
- package/dist/core/documentation/doc-provider.js.map +1 -0
- package/dist/core/documentation/doc-schema.d.ts +8 -0
- package/dist/core/documentation/doc-schema.d.ts.map +1 -0
- package/dist/core/documentation/doc-schema.js +53 -0
- package/dist/core/documentation/doc-schema.js.map +1 -0
- package/dist/core/field/field-constants.d.ts +25 -1
- package/dist/core/field/field-constants.d.ts.map +1 -1
- package/dist/core/field/field-constants.js +32 -1
- package/dist/core/field/field-constants.js.map +1 -1
- package/dist/core/field/field-schema.d.ts +2 -0
- package/dist/core/field/field-schema.d.ts.map +1 -1
- package/dist/core/field/field-schema.js +20 -2
- package/dist/core/field/field-schema.js.map +1 -1
- package/dist/core/field/meta-field.d.ts +2 -1
- package/dist/core/field/meta-field.d.ts.map +1 -1
- package/dist/core/field/meta-field.js +6 -4
- package/dist/core/field/meta-field.js.map +1 -1
- package/dist/core/object/meta-object.d.ts.map +1 -1
- package/dist/core/object/meta-object.js +6 -5
- package/dist/core/object/meta-object.js.map +1 -1
- package/dist/core/parser-yaml.d.ts.map +1 -1
- package/dist/core/parser-yaml.js +9 -2
- package/dist/core/parser-yaml.js.map +1 -1
- package/dist/core/relationship/meta-relationship.d.ts +4 -0
- package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
- package/dist/core/relationship/meta-relationship.js +11 -1
- package/dist/core/relationship/meta-relationship.js.map +1 -1
- package/dist/core/relationship/relationship-constants.d.ts +9 -0
- package/dist/core/relationship/relationship-constants.d.ts.map +1 -1
- package/dist/core/relationship/relationship-constants.js +15 -0
- package/dist/core/relationship/relationship-constants.js.map +1 -1
- package/dist/core/relationship/relationship-schema.d.ts.map +1 -1
- package/dist/core/relationship/relationship-schema.js +15 -1
- package/dist/core/relationship/relationship-schema.js.map +1 -1
- package/dist/core/yaml-desugar.d.ts +8 -1
- package/dist/core/yaml-desugar.d.ts.map +1 -1
- package/dist/core/yaml-desugar.js +166 -15
- package/dist/core/yaml-desugar.js.map +1 -1
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +28 -11
- package/dist/core-types.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +12 -1
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/validation-passes.d.ts +2 -0
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +69 -3
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/naming.d.ts +3 -2
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +14 -12
- package/dist/naming.js.map +1 -1
- package/dist/parser-core.d.ts.map +1 -1
- package/dist/parser-core.js +91 -48
- package/dist/parser-core.js.map +1 -1
- package/dist/persistence/db/db-constants.d.ts +2 -2
- package/dist/persistence/db/db-constants.d.ts.map +1 -1
- package/dist/persistence/db/db-constants.js +2 -2
- package/dist/persistence/db/db-constants.js.map +1 -1
- package/dist/persistence/db/db-provider.d.ts.map +1 -1
- package/dist/persistence/db/db-provider.js +10 -13
- package/dist/persistence/db/db-provider.js.map +1 -1
- package/dist/persistence/db/db-schema.d.ts +2 -4
- package/dist/persistence/db/db-schema.d.ts.map +1 -1
- package/dist/persistence/db/db-schema.js +5 -13
- package/dist/persistence/db/db-schema.js.map +1 -1
- package/dist/persistence/origin/meta-origin.d.ts +10 -0
- package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
- package/dist/persistence/origin/meta-origin.js +14 -1
- package/dist/persistence/origin/meta-origin.js.map +1 -1
- package/dist/persistence/origin/origin-constants.d.ts +3 -1
- package/dist/persistence/origin/origin-constants.d.ts.map +1 -1
- package/dist/persistence/origin/origin-constants.js +6 -0
- package/dist/persistence/origin/origin-constants.js.map +1 -1
- package/dist/persistence/origin/origin-schema.d.ts +1 -1
- package/dist/persistence/origin/origin-schema.d.ts.map +1 -1
- package/dist/persistence/origin/origin-schema.js +12 -2
- package/dist/persistence/origin/origin-schema.js.map +1 -1
- package/dist/persistence/source/meta-source.d.ts +11 -9
- package/dist/persistence/source/meta-source.d.ts.map +1 -1
- package/dist/persistence/source/meta-source.js +23 -15
- package/dist/persistence/source/meta-source.js.map +1 -1
- package/dist/persistence/source/source-constants.d.ts +32 -11
- package/dist/persistence/source/source-constants.d.ts.map +1 -1
- package/dist/persistence/source/source-constants.js +55 -16
- package/dist/persistence/source/source-constants.js.map +1 -1
- package/dist/persistence/source/source-schema.d.ts +4 -0
- package/dist/persistence/source/source-schema.d.ts.map +1 -0
- package/dist/persistence/source/source-schema.js +37 -0
- package/dist/persistence/source/source-schema.js.map +1 -0
- package/dist/persistence/source/validate-source-roles.d.ts +12 -0
- package/dist/persistence/source/validate-source-roles.d.ts.map +1 -0
- package/dist/persistence/source/validate-source-roles.js +38 -0
- package/dist/persistence/source/validate-source-roles.js.map +1 -0
- package/dist/registry.d.ts +13 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +26 -0
- package/dist/registry.js.map +1 -1
- package/dist/shared/base-types.d.ts +2 -1
- package/dist/shared/base-types.d.ts.map +1 -1
- package/dist/shared/base-types.js +5 -2
- package/dist/shared/base-types.js.map +1 -1
- package/dist/template/meta-template.d.ts +13 -0
- package/dist/template/meta-template.d.ts.map +1 -0
- package/dist/template/meta-template.js +13 -0
- package/dist/template/meta-template.js.map +1 -0
- package/dist/template/template-constants.d.ts +17 -0
- package/dist/template/template-constants.d.ts.map +1 -0
- package/dist/template/template-constants.js +46 -0
- package/dist/template/template-constants.js.map +1 -0
- package/dist/template/template-schema.d.ts +3 -0
- package/dist/template/template-schema.d.ts.map +1 -0
- package/dist/template/template-schema.js +84 -0
- package/dist/template/template-schema.js.map +1 -0
- package/package.json +1 -1
- package/src/attr-schema-validate.ts +89 -9
- package/src/core/documentation/doc-constants.ts +22 -0
- package/src/core/documentation/doc-provider.ts +12 -0
- package/src/core/documentation/doc-schema.ts +64 -0
- package/src/core/field/field-constants.ts +41 -1
- package/src/core/field/field-schema.ts +25 -0
- package/src/core/field/meta-field.ts +6 -3
- package/src/core/object/meta-object.ts +6 -6
- package/src/core/parser-yaml.ts +11 -3
- package/src/core/relationship/meta-relationship.ts +14 -0
- package/src/core/relationship/relationship-constants.ts +20 -0
- package/src/core/relationship/relationship-schema.ts +18 -0
- package/src/core/yaml-desugar.ts +206 -24
- package/src/core-types.ts +31 -9
- package/src/errors.ts +21 -0
- package/src/index.ts +7 -0
- package/src/loader/meta-data-loader.ts +15 -1
- package/src/loader/validation-passes.ts +98 -1
- package/src/naming.ts +15 -13
- package/src/parser-core.ts +119 -73
- package/src/persistence/db/db-constants.ts +2 -2
- package/src/persistence/db/db-provider.ts +10 -16
- package/src/persistence/db/db-schema.ts +5 -15
- package/src/persistence/origin/meta-origin.ts +15 -0
- package/src/persistence/origin/origin-constants.ts +7 -0
- package/src/persistence/origin/origin-schema.ts +15 -1
- package/src/persistence/source/meta-source.ts +30 -17
- package/src/persistence/source/source-constants.ts +66 -17
- package/src/persistence/source/source-schema.ts +54 -0
- package/src/persistence/source/validate-source-roles.ts +53 -0
- package/src/registry.ts +31 -0
- package/src/shared/base-types.ts +5 -2
- package/src/template/meta-template.ts +12 -0
- package/src/template/template-constants.ts +53 -0
- package/src/template/template-schema.ts +106 -0
|
@@ -36,3 +36,23 @@ export const CARDINALITY_MANY = "many";
|
|
|
36
36
|
|
|
37
37
|
export const CARDINALITY_VALUES = [CARDINALITY_ONE, CARDINALITY_MANY] as const;
|
|
38
38
|
export type CardinalityValue = (typeof CARDINALITY_VALUES)[number];
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Referential action attrs (@onDelete / @onUpdate)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
export const RELATIONSHIP_ATTR_ON_DELETE = "onDelete";
|
|
45
|
+
export const RELATIONSHIP_ATTR_ON_UPDATE = "onUpdate";
|
|
46
|
+
|
|
47
|
+
/** Referential actions — the canonical cross-language set (kebab-case, no setDefault).
|
|
48
|
+
* MUST equal migrate-ts's FkAction union (server/typescript/packages/migrate-ts/src/types.ts). */
|
|
49
|
+
export const REFERENTIAL_ACTIONS = ["cascade", "set-null", "restrict", "no-action"] as const;
|
|
50
|
+
export type ReferentialAction = (typeof REFERENTIAL_ACTIONS)[number];
|
|
51
|
+
|
|
52
|
+
/** Default @onDelete per relationship subtype (rollout decided defaults). */
|
|
53
|
+
export const ON_DELETE_DEFAULT_BY_SUBTYPE: Readonly<Record<string, ReferentialAction>> = {
|
|
54
|
+
[RELATIONSHIP_SUBTYPE_COMPOSITION]: "cascade",
|
|
55
|
+
[RELATIONSHIP_SUBTYPE_AGGREGATION]: "set-null",
|
|
56
|
+
[RELATIONSHIP_SUBTYPE_ASSOCIATION]: "restrict",
|
|
57
|
+
};
|
|
58
|
+
export const ON_UPDATE_DEFAULT: ReferentialAction = "cascade";
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
RELATIONSHIP_ATTR_OBJECT_REF,
|
|
12
12
|
RELATIONSHIP_ATTR_JOIN_ENTITY,
|
|
13
13
|
RELATIONSHIP_ATTR_JOIN_FIELDS,
|
|
14
|
+
RELATIONSHIP_ATTR_ON_DELETE,
|
|
15
|
+
RELATIONSHIP_ATTR_ON_UPDATE,
|
|
16
|
+
REFERENTIAL_ACTIONS,
|
|
14
17
|
} from "./relationship-constants.js";
|
|
15
18
|
|
|
16
19
|
/** Attrs common to every relationship subtype. */
|
|
@@ -46,4 +49,19 @@ export const relationshipAttrs: AttrSchema[] = [
|
|
|
46
49
|
required: false,
|
|
47
50
|
description: "Join-table column names for N:M relationships.",
|
|
48
51
|
},
|
|
52
|
+
{
|
|
53
|
+
name: RELATIONSHIP_ATTR_ON_DELETE,
|
|
54
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
55
|
+
required: false,
|
|
56
|
+
allowedValues: [...REFERENTIAL_ACTIONS],
|
|
57
|
+
description:
|
|
58
|
+
"Referential action on parent delete. Default derives from subtype (composition→cascade, aggregation→set-null, association→restrict).",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: RELATIONSHIP_ATTR_ON_UPDATE,
|
|
62
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
63
|
+
required: false,
|
|
64
|
+
allowedValues: [...REFERENTIAL_ACTIONS],
|
|
65
|
+
description: "Referential action on key update. Default cascade.",
|
|
66
|
+
},
|
|
49
67
|
];
|
package/src/core/yaml-desugar.ts
CHANGED
|
@@ -2,37 +2,71 @@
|
|
|
2
2
|
//
|
|
3
3
|
// desugar() turns the sugared authoring object (from yaml.parse) into the
|
|
4
4
|
// canonical-shaped object that buildTree (parser-core.ts) consumes. It applies
|
|
5
|
-
// the
|
|
5
|
+
// the five format-spec sugar rules (ADR-0006):
|
|
6
6
|
// 1. Fused key, subType omittable — a bare `type` key resolves to the type's
|
|
7
7
|
// registry default subType.
|
|
8
8
|
// 2. Scalar-or-map body — a scalar body becomes { name: <scalar> }.
|
|
9
9
|
// 3. Omit empties — absent keys stay absent; the desugar invents nothing.
|
|
10
10
|
// 4. `[]` arrays — a trailing `[]` on the key strips to isArray: true.
|
|
11
|
+
// 5. Sigil-free attributes (ADR-0006 D1) — every body key not in
|
|
12
|
+
// RESERVED_KEYS is treated as an inline attribute and re-prefixed with `@`
|
|
13
|
+
// when lowering to canonical JSON. Keys already prefixed with `@` are
|
|
14
|
+
// left as-is (backward-compat). Reserved structural keywords (name,
|
|
15
|
+
// package, extends, abstract, overlay, isArray, children, value) stay
|
|
16
|
+
// bare. Note: an already-`@`-prefixed reserved word remains an error
|
|
17
|
+
// downstream — parser-core's ERR_RESERVED_ATTR check fires.
|
|
11
18
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
19
|
+
// In addition, the desugar runs the ADR-0006 D2 type-coercion guard: for every
|
|
20
|
+
// inline attr whose owning (type, subType) has a declared schema, if the raw
|
|
21
|
+
// JS value's type does not match the declared valueType AND the JS value is
|
|
22
|
+
// one of YAML 1.2's silently coerced shapes (boolean/number/null), the
|
|
23
|
+
// desugar collects an ERR_YAML_COERCION error telling the author to quote the
|
|
24
|
+
// value. The check protects against the classic YAML footgun (e.g. an unquoted
|
|
25
|
+
// `column: TRUE` becoming the boolean `true` instead of the string "TRUE").
|
|
26
|
+
//
|
|
27
|
+
// Pure and total: it never throws. Malformed fragments are collected as
|
|
28
|
+
// CollectedError entries (a string message + optional stable error code) and a
|
|
29
|
+
// safe placeholder is substituted so buildTree does not double-report.
|
|
15
30
|
|
|
16
|
-
import type { TypeRegistry } from "../registry.js";
|
|
31
|
+
import type { TypeRegistry, AttrSchema } from "../registry.js";
|
|
32
|
+
import type { ErrorCode } from "../errors.js";
|
|
17
33
|
import {
|
|
34
|
+
ATTR_PREFIX,
|
|
35
|
+
RESERVED_KEYS,
|
|
18
36
|
RESERVED_KEY_CHILDREN,
|
|
19
37
|
RESERVED_KEY_NAME,
|
|
20
38
|
RESERVED_KEY_IS_ARRAY,
|
|
21
39
|
TYPE_SUBTYPE_SEPARATOR,
|
|
22
40
|
} from "../shared/structural.js";
|
|
41
|
+
import {
|
|
42
|
+
ATTR_SUBTYPE_STRING,
|
|
43
|
+
ATTR_SUBTYPE_CLASS,
|
|
44
|
+
ATTR_SUBTYPE_INT,
|
|
45
|
+
ATTR_SUBTYPE_LONG,
|
|
46
|
+
ATTR_SUBTYPE_DOUBLE,
|
|
47
|
+
ATTR_SUBTYPE_BOOLEAN,
|
|
48
|
+
ATTR_SUBTYPE_STRINGARRAY,
|
|
49
|
+
} from "./attr/attr-constants.js";
|
|
23
50
|
|
|
24
51
|
const ARRAY_SUFFIX = "[]";
|
|
25
52
|
|
|
53
|
+
/** A collected desugar problem — message plus optional stable error code. */
|
|
54
|
+
export interface CollectedError {
|
|
55
|
+
message: string;
|
|
56
|
+
/** Optional stable code; absent values map to ERR_MALFORMED_YAML in parser-yaml.ts. */
|
|
57
|
+
code?: ErrorCode;
|
|
58
|
+
}
|
|
59
|
+
|
|
26
60
|
export interface DesugarResult {
|
|
27
61
|
/** The canonical-shaped object; `{}` when the document was unusable. */
|
|
28
62
|
canonical: Record<string, unknown>;
|
|
29
63
|
/** Collected desugar problems (never thrown). */
|
|
30
|
-
errors:
|
|
64
|
+
errors: CollectedError[];
|
|
31
65
|
}
|
|
32
66
|
|
|
33
67
|
/** Desugar a parsed-YAML authoring document into a canonical-shaped object. */
|
|
34
68
|
export function desugar(input: unknown, registry: TypeRegistry): DesugarResult {
|
|
35
|
-
const errors:
|
|
69
|
+
const errors: CollectedError[] = [];
|
|
36
70
|
const node = desugarNode(input, registry, errors, "<root>");
|
|
37
71
|
return { canonical: node ?? {}, errors };
|
|
38
72
|
}
|
|
@@ -43,21 +77,22 @@ export function desugar(input: unknown, registry: TypeRegistry): DesugarResult {
|
|
|
43
77
|
function desugarNode(
|
|
44
78
|
input: unknown,
|
|
45
79
|
registry: TypeRegistry,
|
|
46
|
-
errors:
|
|
80
|
+
errors: CollectedError[],
|
|
47
81
|
path: string,
|
|
48
82
|
): Record<string, unknown> | undefined {
|
|
49
83
|
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
50
|
-
errors.push(`Node at ${path} must be a mapping with one type key`);
|
|
84
|
+
errors.push({ message: `Node at ${path} must be a mapping with one type key` });
|
|
51
85
|
return undefined;
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
const entries = Object.keys(input as Record<string, unknown>);
|
|
55
89
|
if (entries.length !== 1) {
|
|
56
|
-
errors.push(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
errors.push({
|
|
91
|
+
message:
|
|
92
|
+
`Node at ${path} must have exactly one type key (found: ${
|
|
93
|
+
entries.length === 0 ? "none" : entries.join(", ")
|
|
94
|
+
})`,
|
|
95
|
+
});
|
|
61
96
|
return undefined;
|
|
62
97
|
}
|
|
63
98
|
|
|
@@ -76,7 +111,7 @@ function desugarNode(
|
|
|
76
111
|
const canonicalKey = resolveKey(key, registry, errors, path);
|
|
77
112
|
|
|
78
113
|
// Rule 2: a scalar body → { name: <scalar> }.
|
|
79
|
-
const body = desugarBody(rawBody, errors, path);
|
|
114
|
+
const body = desugarBody(rawBody, registry, canonicalKey, errors, path);
|
|
80
115
|
|
|
81
116
|
// Rule 4 (cont.): stamp isArray onto the canonical body.
|
|
82
117
|
if (isArray) body[RESERVED_KEY_IS_ARRAY] = true;
|
|
@@ -103,25 +138,36 @@ function desugarNode(
|
|
|
103
138
|
function resolveKey(
|
|
104
139
|
key: string,
|
|
105
140
|
registry: TypeRegistry,
|
|
106
|
-
errors:
|
|
141
|
+
errors: CollectedError[],
|
|
107
142
|
path: string,
|
|
108
143
|
): string {
|
|
109
144
|
if (key.includes(TYPE_SUBTYPE_SEPARATOR)) return key; // already fused
|
|
110
145
|
const subType = registry.defaultSubTypeOf(key);
|
|
111
146
|
if (subType === undefined) {
|
|
112
|
-
errors.push(
|
|
113
|
-
|
|
147
|
+
errors.push({
|
|
148
|
+
message:
|
|
149
|
+
`Cannot resolve subType for bare type key '${key}' at ${path} — ` +
|
|
114
150
|
`type '${key}' has no default subType; write the full 'type.subType'`,
|
|
115
|
-
);
|
|
151
|
+
});
|
|
116
152
|
return key; // pass through; buildTree reports the unknown type
|
|
117
153
|
}
|
|
118
154
|
return `${key}${TYPE_SUBTYPE_SEPARATOR}${subType}`;
|
|
119
155
|
}
|
|
120
156
|
|
|
121
|
-
// Rule 2 — normalize a node body into a canonical mapping.
|
|
157
|
+
// Rule 2 + 5 — normalize a node body into a canonical mapping. Reserved
|
|
158
|
+
// structural keys stay bare; every other key is treated as an inline attribute
|
|
159
|
+
// and `@`-prefixed (Rule 5 / ADR-0006 D1). Keys already starting with `@` are
|
|
160
|
+
// kept as-authored so the awkward "@column: foo" form remains accepted.
|
|
161
|
+
//
|
|
162
|
+
// Also runs the D2 type-coercion guard: for each inline attr that the owning
|
|
163
|
+
// (type, subType) declares with a typed `valueType`, if the raw JS value's
|
|
164
|
+
// type was silently coerced by YAML 1.2 to something incompatible (e.g. a
|
|
165
|
+
// `boolean` for a `string`-declared attr), an ERR_YAML_COERCION is collected.
|
|
122
166
|
function desugarBody(
|
|
123
167
|
rawBody: unknown,
|
|
124
|
-
|
|
168
|
+
registry: TypeRegistry,
|
|
169
|
+
canonicalKey: string,
|
|
170
|
+
errors: CollectedError[],
|
|
125
171
|
path: string,
|
|
126
172
|
): Record<string, unknown> {
|
|
127
173
|
if (
|
|
@@ -136,10 +182,146 @@ function desugarBody(
|
|
|
136
182
|
return {};
|
|
137
183
|
}
|
|
138
184
|
if (Array.isArray(rawBody)) {
|
|
139
|
-
errors.push(
|
|
185
|
+
errors.push({
|
|
186
|
+
message: `Node body at ${path} must be a scalar or mapping, not a list`,
|
|
187
|
+
});
|
|
140
188
|
return {};
|
|
141
189
|
}
|
|
142
190
|
// A mapping — shallow-copy so isArray / children replacement do not mutate
|
|
143
|
-
// the caller's parsed-YAML object
|
|
144
|
-
|
|
191
|
+
// the caller's parsed-YAML object, AND apply Rule 5 (sigil-free attrs) +
|
|
192
|
+
// Rule D2 (type-coercion guard).
|
|
193
|
+
const src = rawBody as Record<string, unknown>;
|
|
194
|
+
const out: Record<string, unknown> = {};
|
|
195
|
+
const schemaIndex = attrSchemaIndex(registry, canonicalKey);
|
|
196
|
+
for (const key of Object.keys(src)) {
|
|
197
|
+
if (RESERVED_KEYS.has(key) || key.startsWith(ATTR_PREFIX)) {
|
|
198
|
+
out[key] = src[key];
|
|
199
|
+
// D2 also applies to author-written @-keys (the awkward form).
|
|
200
|
+
const attrName = key.startsWith(ATTR_PREFIX) ? key.slice(ATTR_PREFIX.length) : "";
|
|
201
|
+
if (attrName !== "" && !RESERVED_KEYS.has(attrName)) {
|
|
202
|
+
checkCoercion(attrName, src[key], schemaIndex, errors, path);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
out[`${ATTR_PREFIX}${key}`] = src[key];
|
|
206
|
+
checkCoercion(key, src[key], schemaIndex, errors, path);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// D2 — YAML type-coercion guard
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
// Build a name → AttrSchema map for the given canonical key (type.subType).
|
|
217
|
+
// Returns undefined when the key has no declared attrs (open schema).
|
|
218
|
+
function attrSchemaIndex(
|
|
219
|
+
registry: TypeRegistry,
|
|
220
|
+
canonicalKey: string,
|
|
221
|
+
): Map<string, AttrSchema> | undefined {
|
|
222
|
+
// canonicalKey is "type.subType" — split on the FIRST dot only so a subType
|
|
223
|
+
// that happens to contain a dot still works.
|
|
224
|
+
const dot = canonicalKey.indexOf(TYPE_SUBTYPE_SEPARATOR);
|
|
225
|
+
if (dot < 0) return undefined;
|
|
226
|
+
const type = canonicalKey.slice(0, dot);
|
|
227
|
+
const subType = canonicalKey.slice(dot + 1);
|
|
228
|
+
const schema = registry.attrsOf(type, subType);
|
|
229
|
+
if (schema.length === 0) return undefined;
|
|
230
|
+
const idx = new Map<string, AttrSchema>();
|
|
231
|
+
for (const spec of schema) idx.set(spec.name, spec);
|
|
232
|
+
return idx;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check a single attr value against its declared schema's `valueType`. Emits
|
|
236
|
+
// an ERR_YAML_COERCION when the JS type was silently changed by YAML 1.2's
|
|
237
|
+
// core schema (boolean/number/null where a string/stringarray was declared,
|
|
238
|
+
// or vice versa for booleans/numbers).
|
|
239
|
+
function checkCoercion(
|
|
240
|
+
attrName: string,
|
|
241
|
+
raw: unknown,
|
|
242
|
+
schemaIndex: Map<string, AttrSchema> | undefined,
|
|
243
|
+
errors: CollectedError[],
|
|
244
|
+
path: string,
|
|
245
|
+
): void {
|
|
246
|
+
if (schemaIndex === undefined) return;
|
|
247
|
+
const spec = schemaIndex.get(attrName);
|
|
248
|
+
if (spec === undefined || spec.valueType === undefined) return;
|
|
249
|
+
|
|
250
|
+
switch (spec.valueType) {
|
|
251
|
+
case ATTR_SUBTYPE_STRING:
|
|
252
|
+
case ATTR_SUBTYPE_CLASS:
|
|
253
|
+
if (typeof raw !== "string") emitCoercion(attrName, raw, "string", errors, path);
|
|
254
|
+
return;
|
|
255
|
+
case ATTR_SUBTYPE_BOOLEAN:
|
|
256
|
+
if (typeof raw !== "boolean") emitCoercion(attrName, raw, "boolean", errors, path);
|
|
257
|
+
return;
|
|
258
|
+
case ATTR_SUBTYPE_INT:
|
|
259
|
+
case ATTR_SUBTYPE_LONG:
|
|
260
|
+
case ATTR_SUBTYPE_DOUBLE:
|
|
261
|
+
if (typeof raw !== "number") emitCoercion(attrName, raw, "number", errors, path);
|
|
262
|
+
return;
|
|
263
|
+
case ATTR_SUBTYPE_STRINGARRAY:
|
|
264
|
+
// A bare string at the value position is the legitimate one-element
|
|
265
|
+
// authoring shorthand for a string-array attr (StringArrayAttr.coerce
|
|
266
|
+
// wraps it into a one-element array). It is NOT a coercion. A
|
|
267
|
+
// non-string non-array scalar (boolean/number/null), however, is.
|
|
268
|
+
if (typeof raw === "string") return;
|
|
269
|
+
if (!Array.isArray(raw)) {
|
|
270
|
+
emitCoercion(attrName, raw, "string-array (or single string)", errors, path);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// For an array, check every element. A non-string element is a YAML
|
|
274
|
+
// coercion (e.g. unquoted `true` in a string-array list).
|
|
275
|
+
for (let i = 0; i < raw.length; i++) {
|
|
276
|
+
if (typeof raw[i] !== "string") {
|
|
277
|
+
emitCoercion(
|
|
278
|
+
`${attrName}[${i}]`,
|
|
279
|
+
raw[i],
|
|
280
|
+
"string (in string-array)",
|
|
281
|
+
errors,
|
|
282
|
+
path,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
default:
|
|
288
|
+
// Object-shaped attrs (properties, filter) — accept any object/array,
|
|
289
|
+
// no YAML coercion path applies.
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Build the "quote this value" error. The shape is intentionally explicit so
|
|
295
|
+
// AI authors can act on it: it identifies the attr, the (coerced) JS value,
|
|
296
|
+
// its JS type, the declared expected type, and a one-line fix hint.
|
|
297
|
+
function emitCoercion(
|
|
298
|
+
attrName: string,
|
|
299
|
+
raw: unknown,
|
|
300
|
+
expected: string,
|
|
301
|
+
errors: CollectedError[],
|
|
302
|
+
path: string,
|
|
303
|
+
): void {
|
|
304
|
+
const actualType = coercedTypeName(raw);
|
|
305
|
+
const literal = literalRepr(raw);
|
|
306
|
+
errors.push({
|
|
307
|
+
message:
|
|
308
|
+
`Attribute '@${attrName}' at ${path}: expected ${expected} but got ${actualType} (${literal}). ` +
|
|
309
|
+
`YAML 1.2 silently coerced an unquoted value — quote it in YAML: ` +
|
|
310
|
+
`'@${attrName}: "${literal}"' not '@${attrName}: ${literal}'.`,
|
|
311
|
+
code: "ERR_YAML_COERCION",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function coercedTypeName(raw: unknown): string {
|
|
316
|
+
if (raw === null) return "null";
|
|
317
|
+
if (Array.isArray(raw)) return "array";
|
|
318
|
+
return typeof raw;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function literalRepr(raw: unknown): string {
|
|
322
|
+
if (raw === null) return "null";
|
|
323
|
+
if (typeof raw === "boolean") return raw ? "true" : "false";
|
|
324
|
+
if (typeof raw === "number") return String(raw);
|
|
325
|
+
if (typeof raw === "string") return raw;
|
|
326
|
+
return JSON.stringify(raw);
|
|
145
327
|
}
|
package/src/core-types.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { TypeId, type AttrSchema, type ChildRule, type TypeDefinition, TypeRegistry } from "./registry.js";
|
|
3
3
|
import type { MetaDataTypeProvider } from "./provider.js";
|
|
4
4
|
import { dbProvider } from "./persistence/db/db-provider.js";
|
|
5
|
+
import { docProvider } from "./core/documentation/doc-provider.js";
|
|
5
6
|
import { type DataType } from "./data-type.js";
|
|
6
7
|
import type { MetaData } from "./shared/meta-data.js";
|
|
7
8
|
import { MetaRoot } from "./shared/meta-root.js";
|
|
@@ -33,8 +34,8 @@ import {
|
|
|
33
34
|
import { MetaRelationship } from "./core/relationship/meta-relationship.js";
|
|
34
35
|
import { MetaLayout } from "./presentation/layout/meta-layout.js";
|
|
35
36
|
import { MetaSource } from "./persistence/source/meta-source.js";
|
|
36
|
-
import { MetaOrigin, MetaPassthroughOrigin, MetaAggregateOrigin } from "./persistence/origin/meta-origin.js";
|
|
37
|
-
import { commonFieldAttrs, currencyFieldAttr } from "./core/field/field-schema.js";
|
|
37
|
+
import { MetaOrigin, MetaPassthroughOrigin, MetaAggregateOrigin, MetaCollectionOrigin } from "./persistence/origin/meta-origin.js";
|
|
38
|
+
import { commonFieldAttrs, currencyFieldAttr, enumFieldAttr } from "./core/field/field-schema.js";
|
|
38
39
|
import { objectAttrs } from "./core/object/object-schema.js";
|
|
39
40
|
import { relationshipAttrs } from "./core/relationship/relationship-schema.js";
|
|
40
41
|
import { identityFieldsAttr, IDENTITY_ATTRS_MAP } from "./core/identity/identity-schema.js";
|
|
@@ -42,6 +43,9 @@ import { VALIDATOR_ATTRS_MAP } from "./core/validator/validator-schema.js";
|
|
|
42
43
|
import { currencyViewAttrs } from "./presentation/view/view-schema.js";
|
|
43
44
|
import { dataGridLayoutAttrs } from "./presentation/layout/layout-schema.js";
|
|
44
45
|
import { ORIGIN_ATTRS_MAP } from "./persistence/origin/origin-schema.js";
|
|
46
|
+
import { MetaTemplate } from "./template/meta-template.js";
|
|
47
|
+
import { TEMPLATE_ATTRS_MAP } from "./template/template-schema.js";
|
|
48
|
+
import { TEMPLATE_SUBTYPES } from "./template/template-constants.js";
|
|
45
49
|
import {
|
|
46
50
|
TYPE_METADATA,
|
|
47
51
|
TYPE_OBJECT,
|
|
@@ -54,11 +58,12 @@ import {
|
|
|
54
58
|
TYPE_LAYOUT,
|
|
55
59
|
TYPE_SOURCE,
|
|
56
60
|
TYPE_ORIGIN,
|
|
61
|
+
TYPE_TEMPLATE,
|
|
57
62
|
SUBTYPE_ROOT,
|
|
58
63
|
} from "./shared/base-types.js";
|
|
59
64
|
import { CHILD_RULE_WILDCARD } from "./shared/structural.js";
|
|
60
65
|
import { OBJECT_SUBTYPES, OBJECT_SUBTYPE_ENTITY } from "./core/object/object-constants.js";
|
|
61
|
-
import { FIELD_SUBTYPES, FIELD_SUBTYPE_CURRENCY } from "./core/field/field-constants.js";
|
|
66
|
+
import { FIELD_SUBTYPES, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_ENUM } from "./core/field/field-constants.js";
|
|
62
67
|
import { ATTR_SUBTYPES } from "./core/attr/attr-constants.js";
|
|
63
68
|
import {
|
|
64
69
|
VALIDATOR_SUBTYPES,
|
|
@@ -85,6 +90,7 @@ import {
|
|
|
85
90
|
ORIGIN_SUBTYPES,
|
|
86
91
|
ORIGIN_SUBTYPE_PASSTHROUGH,
|
|
87
92
|
ORIGIN_SUBTYPE_AGGREGATE,
|
|
93
|
+
ORIGIN_SUBTYPE_COLLECTION,
|
|
88
94
|
} from "./persistence/origin/origin-constants.js";
|
|
89
95
|
|
|
90
96
|
// ---------------------------------------------------------------------------
|
|
@@ -146,6 +152,7 @@ const IDENTITY_CLASS_MAP = new Map<string, NodeConstructor>([
|
|
|
146
152
|
const ORIGIN_CLASS_MAP = new Map<string, NodeConstructor>([
|
|
147
153
|
[ORIGIN_SUBTYPE_PASSTHROUGH, MetaPassthroughOrigin],
|
|
148
154
|
[ORIGIN_SUBTYPE_AGGREGATE, MetaAggregateOrigin],
|
|
155
|
+
[ORIGIN_SUBTYPE_COLLECTION, MetaCollectionOrigin],
|
|
149
156
|
]);
|
|
150
157
|
|
|
151
158
|
// ATTR_CLASS_MAP + attrClassFor live in the leaf module ./attr-class-map.ts
|
|
@@ -161,6 +168,7 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
161
168
|
wildcard(TYPE_FIELD),
|
|
162
169
|
wildcard(TYPE_ATTR),
|
|
163
170
|
wildcard(TYPE_VALIDATOR),
|
|
171
|
+
wildcard(TYPE_TEMPLATE),
|
|
164
172
|
], MetaRoot),
|
|
165
173
|
);
|
|
166
174
|
|
|
@@ -188,12 +196,14 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
188
196
|
wildcard(TYPE_ORIGIN),
|
|
189
197
|
];
|
|
190
198
|
for (const subType of FIELD_SUBTYPES) {
|
|
191
|
-
// field.currency additionally carries @currency;
|
|
192
|
-
// share the common
|
|
199
|
+
// field.currency additionally carries @currency; field.enum additionally
|
|
200
|
+
// carries @values; all other field subtypes share the common attrs only.
|
|
193
201
|
const fieldAttrs =
|
|
194
202
|
subType === FIELD_SUBTYPE_CURRENCY
|
|
195
203
|
? [...commonFieldAttrs, { ...currencyFieldAttr }]
|
|
196
|
-
:
|
|
204
|
+
: subType === FIELD_SUBTYPE_ENUM
|
|
205
|
+
? [...commonFieldAttrs, { ...enumFieldAttr }]
|
|
206
|
+
: [...commonFieldAttrs];
|
|
197
207
|
registry.register(
|
|
198
208
|
def(TYPE_FIELD, subType, `Field of type ${subType}`, fieldRules, MetaField, fieldAttrs,
|
|
199
209
|
new MetaField(new TypeId(TYPE_FIELD, subType), "").dataType),
|
|
@@ -247,9 +257,10 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
247
257
|
);
|
|
248
258
|
}
|
|
249
259
|
|
|
250
|
-
// source — declares where an object's data lives (
|
|
260
|
+
// source — declares where an object's data lives (rdb, ... per ADR-0007).
|
|
251
261
|
// Only attr children; sources carry only configuration, never nested structure.
|
|
252
|
-
//
|
|
262
|
+
// Per-subtype attrs (@table/@kind/@role/@schema for rdb) are added by
|
|
263
|
+
// dbProvider via TypeRegistry.extend.
|
|
253
264
|
for (const subType of SOURCE_SUBTYPES) {
|
|
254
265
|
registry.register(
|
|
255
266
|
def(TYPE_SOURCE, subType, `Source (${subType})`, [wildcard(TYPE_ATTR)], MetaSource, []),
|
|
@@ -268,6 +279,17 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
268
279
|
);
|
|
269
280
|
}
|
|
270
281
|
|
|
282
|
+
// template — renderable text artifacts (FR-004). prompt + output; attr-only
|
|
283
|
+
// children. A single MetaTemplate class backs both subtypes (mirrors source);
|
|
284
|
+
// per-subtype attr schemas drive validation (both require @payloadRef +
|
|
285
|
+
// @textRef; @format is a closed enum; template.prompt adds the LLM overlay).
|
|
286
|
+
for (const subType of TEMPLATE_SUBTYPES) {
|
|
287
|
+
const templateAttrs = TEMPLATE_ATTRS_MAP.get(subType) ?? [];
|
|
288
|
+
registry.register(
|
|
289
|
+
def(TYPE_TEMPLATE, subType, `Template (${subType})`, [wildcard(TYPE_ATTR)], MetaTemplate, templateAttrs),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
271
293
|
// identity — 2 subtypes (no base; Java doesn't register one).
|
|
272
294
|
// Subtype→class dispatch for TYPE_IDENTITY (formerly handled by metaOf()):
|
|
273
295
|
// primary → MetaPrimaryIdentity, secondary → MetaSecondaryIdentity,
|
|
@@ -318,7 +340,7 @@ export const coreTypesProvider: MetaDataTypeProvider = {
|
|
|
318
340
|
|
|
319
341
|
/** The default provider bundle — core metamodel types plus DB-domain attrs.
|
|
320
342
|
* Spread it to add more: `[...coreProviders, mine]`. */
|
|
321
|
-
export const coreProviders: readonly MetaDataTypeProvider[] = [coreTypesProvider, dbProvider];
|
|
343
|
+
export const coreProviders: readonly MetaDataTypeProvider[] = [coreTypesProvider, dbProvider, docProvider];
|
|
322
344
|
|
|
323
345
|
/**
|
|
324
346
|
* Register the core metamodel into an existing registry. Thin convenience
|
package/src/errors.ts
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
// - ERR_MISSING_SUBTYPE: missing subType is resolved to the registry default, never an error.
|
|
8
8
|
// - ERR_INVALID_SUBTYPE_CHILD: no child-rule validation pass exists yet.
|
|
9
9
|
// Cross-language conformance consumers should not expect these codes from the TS adapter.
|
|
10
|
+
//
|
|
11
|
+
// FR-004 build-time `verify` codes (ERR_VAR_NOT_ON_PAYLOAD, ERR_PARTIAL_UNRESOLVED,
|
|
12
|
+
// ERR_REQUIRED_SLOT_UNUSED, ERR_OUTPUT_TAG_MISSING) are emitted by `meta verify` / the
|
|
13
|
+
// zero-core-dependency @metaobjectsdev/render engine — NOT by the loader. They are
|
|
14
|
+
// registered here (and in fixtures/conformance/ERROR-CODES.json) only to keep the code
|
|
15
|
+
// vocabulary single-sourced across languages; render re-declares them locally to avoid
|
|
16
|
+
// importing this package.
|
|
10
17
|
export const ERROR_CODES = [
|
|
11
18
|
"ERR_TOP_LEVEL_NOT_OBJECT",
|
|
12
19
|
"ERR_UNKNOWN_TYPE",
|
|
@@ -28,7 +35,21 @@ export const ERROR_CODES = [
|
|
|
28
35
|
"ERR_OVERLAY_NO_TARGET",
|
|
29
36
|
"ERR_MALFORMED_YAML",
|
|
30
37
|
"ERR_INVALID_ORIGIN",
|
|
38
|
+
"ERR_INVALID_TEMPLATE",
|
|
39
|
+
"ERR_VAR_NOT_ON_PAYLOAD",
|
|
40
|
+
"ERR_PARTIAL_UNRESOLVED",
|
|
41
|
+
"ERR_REQUIRED_SLOT_UNUSED",
|
|
42
|
+
"ERR_OUTPUT_TAG_MISSING",
|
|
31
43
|
"ERR_BAD_ATTR_FILTER",
|
|
44
|
+
"ERR_STORAGE_FLATTENED_ARRAY",
|
|
45
|
+
"ERR_STORAGE_WITHOUT_OBJECT_REF",
|
|
46
|
+
// Source v2 (ADR-0007) error codes — enforcement added during the source-v2 rollout.
|
|
47
|
+
"ERR_RESERVED_ATTR",
|
|
48
|
+
"ERR_SOURCE_NO_PRIMARY",
|
|
49
|
+
"ERR_SOURCE_MULTIPLE_PRIMARY",
|
|
50
|
+
// ADR-0006 D2 — YAML type-coercion guard. Emitted by every port's YAML
|
|
51
|
+
// loader when a coerced scalar mismatches the schema-declared type.
|
|
52
|
+
"ERR_YAML_COERCION",
|
|
32
53
|
"ERR_UNKNOWN",
|
|
33
54
|
] as const;
|
|
34
55
|
|
package/src/index.ts
CHANGED
|
@@ -27,12 +27,14 @@ export * from "./shared/structural.js";
|
|
|
27
27
|
export * from "./core/object/object-constants.js";
|
|
28
28
|
export * from "./core/field/field-constants.js";
|
|
29
29
|
export * from "./core/attr/attr-constants.js";
|
|
30
|
+
export * from "./core/documentation/doc-constants.js";
|
|
30
31
|
export * from "./core/validator/validator-constants.js";
|
|
31
32
|
export * from "./core/identity/identity-constants.js";
|
|
32
33
|
export * from "./core/relationship/relationship-constants.js";
|
|
33
34
|
export * from "./core/query/query-constants.js";
|
|
34
35
|
export * from "./persistence/source/source-constants.js";
|
|
35
36
|
export * from "./persistence/origin/origin-constants.js";
|
|
37
|
+
export * from "./template/template-constants.js";
|
|
36
38
|
export * from "./persistence/db/db-constants.js";
|
|
37
39
|
export * from "./presentation/view/view-constants.js";
|
|
38
40
|
export * from "./presentation/layout/layout-constants.js";
|
|
@@ -78,7 +80,10 @@ export {
|
|
|
78
80
|
MetaOrigin,
|
|
79
81
|
MetaPassthroughOrigin,
|
|
80
82
|
MetaAggregateOrigin,
|
|
83
|
+
MetaCollectionOrigin,
|
|
81
84
|
} from "./persistence/origin/meta-origin.js";
|
|
85
|
+
// Template: single class backs both subtypes (FR-004)
|
|
86
|
+
export { MetaTemplate } from "./template/meta-template.js";
|
|
82
87
|
|
|
83
88
|
// Presentation node classes
|
|
84
89
|
export { MetaView } from "./presentation/view/meta-view.js";
|
|
@@ -108,6 +113,8 @@ export { TypeId, TypeRegistry, childRuleMatches } from "./registry.js";
|
|
|
108
113
|
export type { AttrSchema, ChildRule, TypeDefinition } from "./registry.js";
|
|
109
114
|
export { registerCoreTypes, coreTypesProvider, coreProviders } from "./core-types.js";
|
|
110
115
|
export { dbProvider } from "./persistence/db/db-provider.js";
|
|
116
|
+
export { commonDocAttrs } from "./core/documentation/doc-schema.js";
|
|
117
|
+
export { docProvider } from "./core/documentation/doc-provider.js";
|
|
111
118
|
|
|
112
119
|
// Type provider model
|
|
113
120
|
export { composeRegistry } from "./provider.js";
|
|
@@ -15,7 +15,8 @@ import { composeRegistry } from "../provider.js";
|
|
|
15
15
|
import { TYPE_METADATA, SUBTYPE_ROOT } from "../shared/base-types.js";
|
|
16
16
|
import { ParseError } from "../errors.js";
|
|
17
17
|
import { parseJson } from "../parser-json.js";
|
|
18
|
-
import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues } from "./validation-passes.js";
|
|
18
|
+
import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs } from "./validation-passes.js";
|
|
19
|
+
import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
|
|
19
20
|
import { resolveDeferredSupers } from "../super-resolve.js";
|
|
20
21
|
import { validateSubtypeRules } from "../subtype-rules.js";
|
|
21
22
|
import { validateAttrSchema } from "../attr-schema-validate.js";
|
|
@@ -278,12 +279,25 @@ export class MetaDataLoader {
|
|
|
278
279
|
// Seventh pass: @filter value validation — fields filterable + ops allowed per subtype.
|
|
279
280
|
errors.push(...validateDataGridFilterValues(root));
|
|
280
281
|
|
|
282
|
+
// template.* validation — @payloadRef resolves to a known object;
|
|
283
|
+
// @requiredSlots are real fields on it (FR-004 Plan #3, T2).
|
|
284
|
+
errors.push(...validateTemplatePayloadRefs(root));
|
|
285
|
+
|
|
281
286
|
// Eighth pass: attribute-schema validation (Phase A3) — checks each
|
|
282
287
|
// node's @-attributes against its (type, subType) AttrSchema: required
|
|
283
288
|
// attrs present, declared attrs well-typed, allowedValues honored.
|
|
284
289
|
const attrSchemaResult = validateAttrSchema(root, this._registry);
|
|
285
290
|
errors.push(...attrSchemaResult.errors);
|
|
286
291
|
warnings.push(...attrSchemaResult.warnings);
|
|
292
|
+
|
|
293
|
+
// Ninth pass: @storage cross-attribute validation — @storage requires
|
|
294
|
+
// @objectRef, and @storage "flattened" forbids isArray=true.
|
|
295
|
+
errors.push(...validateFieldObjectStorage(root));
|
|
296
|
+
|
|
297
|
+
// Tenth pass: one-primary multi-source rule — if an object has ≥1 source,
|
|
298
|
+
// exactly one must carry role "primary" (ERR_SOURCE_NO_PRIMARY /
|
|
299
|
+
// ERR_SOURCE_MULTIPLE_PRIMARY).
|
|
300
|
+
errors.push(...validateSourceRoles(root));
|
|
287
301
|
}
|
|
288
302
|
|
|
289
303
|
// If nothing parsed successfully, synthesize an empty root so callers
|