@metaobjectsdev/metadata 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attr-schema-validate.js +12 -4
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/core/identity/identity-constants.d.ts.map +1 -1
- package/dist/core/identity/identity-constants.js +3 -0
- package/dist/core/identity/identity-constants.js.map +1 -1
- package/dist/core/identity/identity-definition.embedded.d.ts.map +1 -1
- package/dist/core/identity/identity-definition.embedded.js +2 -0
- package/dist/core/identity/identity-definition.embedded.js.map +1 -1
- package/dist/core/identity/meta-identity.d.ts.map +1 -1
- package/dist/core/identity/meta-identity.js +8 -1
- package/dist/core/identity/meta-identity.js.map +1 -1
- package/dist/core/validator/validator-constants.d.ts +14 -1
- package/dist/core/validator/validator-constants.d.ts.map +1 -1
- package/dist/core/validator/validator-constants.js +20 -1
- package/dist/core/validator/validator-constants.js.map +1 -1
- package/dist/core/validator/validator-definition.embedded.d.ts.map +1 -1
- package/dist/core/validator/validator-definition.embedded.js +121 -0
- package/dist/core/validator/validator-definition.embedded.js.map +1 -1
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +25 -2
- package/dist/core-types.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -1
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +10 -0
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +6 -1
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/loader/validation-registry.d.ts +10 -0
- package/dist/loader/validation-registry.d.ts.map +1 -0
- package/dist/loader/validation-registry.js +84 -0
- package/dist/loader/validation-registry.js.map +1 -0
- package/dist/parser-core.js +10 -1
- package/dist/parser-core.js.map +1 -1
- package/dist/persistence/db/db-constants.d.ts +10 -0
- package/dist/persistence/db/db-constants.d.ts.map +1 -1
- package/dist/persistence/db/db-constants.js +14 -0
- package/dist/persistence/db/db-constants.js.map +1 -1
- package/dist/persistence/db/db-definition.embedded.d.ts.map +1 -1
- package/dist/persistence/db/db-definition.embedded.js +57 -0
- package/dist/persistence/db/db-definition.embedded.js.map +1 -1
- package/dist/provider-data.d.ts +15 -0
- package/dist/provider-data.d.ts.map +1 -1
- package/dist/provider-data.js +2 -0
- package/dist/provider-data.js.map +1 -1
- package/dist/registry.d.ts +16 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/validate-max-occurs.d.ts +5 -0
- package/dist/validate-max-occurs.d.ts.map +1 -0
- package/dist/validate-max-occurs.js +28 -0
- package/dist/validate-max-occurs.js.map +1 -0
- package/dist/validation-types.d.ts +36 -0
- package/dist/validation-types.d.ts.map +1 -0
- package/dist/validation-types.js +7 -0
- package/dist/validation-types.js.map +1 -0
- package/package.json +1 -1
- package/src/attr-schema-validate.ts +12 -4
- package/src/core/identity/identity-constants.ts +4 -0
- package/src/core/identity/identity-definition.embedded.ts +2 -0
- package/src/core/identity/meta-identity.ts +8 -1
- package/src/core/validator/validator-constants.ts +22 -1
- package/src/core/validator/validator-definition.embedded.ts +121 -0
- package/src/core-types.ts +26 -1
- package/src/errors.ts +11 -2
- package/src/loader/meta-data-loader.ts +12 -0
- package/src/loader/validation-passes.ts +11 -1
- package/src/loader/validation-registry.ts +93 -0
- package/src/parser-core.ts +10 -1
- package/src/persistence/db/db-constants.ts +16 -0
- package/src/persistence/db/db-definition.embedded.ts +57 -0
- package/src/provider-data.ts +17 -0
- package/src/registry.ts +16 -0
- package/src/validate-max-occurs.ts +39 -0
- package/src/validation-types.ts +57 -0
- package/dist/constants.d.ts +0 -208
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -419
- package/dist/constants.js.map +0 -1
- package/dist/core/documentation/doc-schema.d.ts +0 -8
- package/dist/core/documentation/doc-schema.d.ts.map +0 -1
- package/dist/core/documentation/doc-schema.js +0 -61
- package/dist/core/documentation/doc-schema.js.map +0 -1
- package/dist/core/field/field-schema.d.ts +0 -6
- package/dist/core/field/field-schema.d.ts.map +0 -1
- package/dist/core/field/field-schema.js +0 -23
- package/dist/core/field/field-schema.js.map +0 -1
- package/dist/core/file-meta-data-loader.d.ts +0 -18
- package/dist/core/file-meta-data-loader.d.ts.map +0 -1
- package/dist/core/file-meta-data-loader.js +0 -81
- package/dist/core/file-meta-data-loader.js.map +0 -1
- package/dist/core/file-source.d.ts +0 -12
- package/dist/core/file-source.d.ts.map +0 -1
- package/dist/core/file-source.js +0 -46
- package/dist/core/file-source.js.map +0 -1
- package/dist/core/identity/identity-schema.d.ts +0 -6
- package/dist/core/identity/identity-schema.d.ts.map +0 -1
- package/dist/core/identity/identity-schema.js +0 -56
- package/dist/core/identity/identity-schema.js.map +0 -1
- package/dist/core/object/object-schema.d.ts +0 -4
- package/dist/core/object/object-schema.d.ts.map +0 -1
- package/dist/core/object/object-schema.js +0 -28
- package/dist/core/object/object-schema.js.map +0 -1
- package/dist/core/relationship/relationship-schema.d.ts +0 -4
- package/dist/core/relationship/relationship-schema.d.ts.map +0 -1
- package/dist/core/relationship/relationship-schema.js +0 -57
- package/dist/core/relationship/relationship-schema.js.map +0 -1
- package/dist/core/validator/validator-schema.d.ts +0 -4
- package/dist/core/validator/validator-schema.d.ts.map +0 -1
- package/dist/core/validator/validator-schema.js +0 -38
- package/dist/core/validator/validator-schema.js.map +0 -1
- package/dist/core-attr-schemas.d.ts +0 -22
- package/dist/core-attr-schemas.d.ts.map +0 -1
- package/dist/core-attr-schemas.js +0 -324
- package/dist/core-attr-schemas.js.map +0 -1
- package/dist/db/db-attr-schemas.d.ts +0 -8
- package/dist/db/db-attr-schemas.d.ts.map +0 -1
- package/dist/db/db-attr-schemas.js +0 -26
- package/dist/db/db-attr-schemas.js.map +0 -1
- package/dist/db/db-provider.d.ts +0 -3
- package/dist/db/db-provider.d.ts.map +0 -1
- package/dist/db/db-provider.js +0 -28
- package/dist/db/db-provider.js.map +0 -1
- package/dist/meta/find-reference.d.ts +0 -22
- package/dist/meta/find-reference.d.ts.map +0 -1
- package/dist/meta/find-reference.js +0 -29
- package/dist/meta/find-reference.js.map +0 -1
- package/dist/meta/meta-attr.d.ts +0 -8
- package/dist/meta/meta-attr.d.ts.map +0 -1
- package/dist/meta/meta-attr.js +0 -17
- package/dist/meta/meta-attr.js.map +0 -1
- package/dist/meta/meta-data.d.ts +0 -107
- package/dist/meta/meta-data.d.ts.map +0 -1
- package/dist/meta/meta-data.js +0 -302
- package/dist/meta/meta-data.js.map +0 -1
- package/dist/meta/meta-field.d.ts +0 -48
- package/dist/meta/meta-field.d.ts.map +0 -1
- package/dist/meta/meta-field.js +0 -94
- package/dist/meta/meta-field.js.map +0 -1
- package/dist/meta/meta-identity.d.ts +0 -71
- package/dist/meta/meta-identity.d.ts.map +0 -1
- package/dist/meta/meta-identity.js +0 -129
- package/dist/meta/meta-identity.js.map +0 -1
- package/dist/meta/meta-layout.d.ts +0 -23
- package/dist/meta/meta-layout.d.ts.map +0 -1
- package/dist/meta/meta-layout.js +0 -45
- package/dist/meta/meta-layout.js.map +0 -1
- package/dist/meta/meta-object.d.ts +0 -40
- package/dist/meta/meta-object.d.ts.map +0 -1
- package/dist/meta/meta-object.js +0 -81
- package/dist/meta/meta-object.js.map +0 -1
- package/dist/meta/meta-origin.d.ts +0 -32
- package/dist/meta/meta-origin.d.ts.map +0 -1
- package/dist/meta/meta-origin.js +0 -55
- package/dist/meta/meta-origin.js.map +0 -1
- package/dist/meta/meta-relationship.d.ts +0 -11
- package/dist/meta/meta-relationship.d.ts.map +0 -1
- package/dist/meta/meta-relationship.js +0 -27
- package/dist/meta/meta-relationship.js.map +0 -1
- package/dist/meta/meta-root.d.ts +0 -12
- package/dist/meta/meta-root.d.ts.map +0 -1
- package/dist/meta/meta-root.js +0 -24
- package/dist/meta/meta-root.js.map +0 -1
- package/dist/meta/meta-source.d.ts +0 -18
- package/dist/meta/meta-source.d.ts.map +0 -1
- package/dist/meta/meta-source.js +0 -31
- package/dist/meta/meta-source.js.map +0 -1
- package/dist/meta/meta-validator.d.ts +0 -29
- package/dist/meta/meta-validator.d.ts.map +0 -1
- package/dist/meta/meta-validator.js +0 -49
- package/dist/meta/meta-validator.js.map +0 -1
- package/dist/meta/meta-view.d.ts +0 -4
- package/dist/meta/meta-view.d.ts.map +0 -1
- package/dist/meta/meta-view.js +0 -8
- package/dist/meta/meta-view.js.map +0 -1
- package/dist/persistence/db/db-attr-schemas.d.ts +0 -8
- package/dist/persistence/db/db-attr-schemas.d.ts.map +0 -1
- package/dist/persistence/db/db-attr-schemas.js +0 -28
- package/dist/persistence/db/db-attr-schemas.js.map +0 -1
- package/dist/persistence/db/db-schema.d.ts +0 -28
- package/dist/persistence/db/db-schema.d.ts.map +0 -1
- package/dist/persistence/db/db-schema.js +0 -62
- package/dist/persistence/db/db-schema.js.map +0 -1
- package/dist/persistence/origin/origin-schema.d.ts +0 -4
- package/dist/persistence/origin/origin-schema.d.ts.map +0 -1
- package/dist/persistence/origin/origin-schema.js +0 -63
- package/dist/persistence/origin/origin-schema.js.map +0 -1
- package/dist/persistence/source/source-schema.d.ts +0 -4
- package/dist/persistence/source/source-schema.d.ts.map +0 -1
- package/dist/persistence/source/source-schema.js +0 -98
- package/dist/persistence/source/source-schema.js.map +0 -1
- package/dist/presentation/layout/layout-schema.d.ts +0 -4
- package/dist/presentation/layout/layout-schema.d.ts.map +0 -1
- package/dist/presentation/layout/layout-schema.js +0 -47
- package/dist/presentation/layout/layout-schema.js.map +0 -1
- package/dist/presentation/ui/ui-schema.d.ts +0 -10
- package/dist/presentation/ui/ui-schema.d.ts.map +0 -1
- package/dist/presentation/ui/ui-schema.js +0 -41
- package/dist/presentation/ui/ui-schema.js.map +0 -1
- package/dist/presentation/view/view-schema.d.ts +0 -4
- package/dist/presentation/view/view-schema.d.ts.map +0 -1
- package/dist/presentation/view/view-schema.js +0 -15
- package/dist/presentation/view/view-schema.js.map +0 -1
- package/dist/template/prompt-schema.d.ts +0 -20
- package/dist/template/prompt-schema.d.ts.map +0 -1
- package/dist/template/prompt-schema.js +0 -70
- package/dist/template/prompt-schema.js.map +0 -1
- package/dist/template/template-schema.d.ts +0 -3
- package/dist/template/template-schema.d.ts.map +0 -1
- package/dist/template/template-schema.js +0 -181
- package/dist/template/template-schema.js.map +0 -1
|
@@ -28,6 +28,10 @@ export const IDENTITY_REFERENCE_ATTR_REFERENCES = "references";
|
|
|
28
28
|
/** Identity-reference attr: physical-enforcement flag. Default true → hard FK constraint; false → logical-only reference. */
|
|
29
29
|
export const IDENTITY_REFERENCE_ATTR_ENFORCE = "enforce";
|
|
30
30
|
|
|
31
|
+
// NOTE: physical RDB-only identity attrs (@constraintName on identity.reference;
|
|
32
|
+
// @orders / @where on identity.secondary) are NOT core — they are contributed by
|
|
33
|
+
// the db provider and declared in persistence/db/db-constants.ts.
|
|
34
|
+
|
|
31
35
|
// ---------------------------------------------------------------------------
|
|
32
36
|
// Identity generation strategies (values for IDENTITY_ATTR_GENERATION)
|
|
33
37
|
// ---------------------------------------------------------------------------
|
|
@@ -13,6 +13,8 @@ export const IDENTITY_DEFINITION: ProviderDefinition = {
|
|
|
13
13
|
"type": "identity",
|
|
14
14
|
"subType": "primary",
|
|
15
15
|
"description": "The primary key — one per entity; @fields names its column(s), @generation the value strategy.",
|
|
16
|
+
"maxOccurs": 1,
|
|
17
|
+
"defaultName": "primary",
|
|
16
18
|
"children": [
|
|
17
19
|
{
|
|
18
20
|
"type": "attr",
|
|
@@ -136,7 +136,14 @@ export class MetaReferenceIdentity extends MetaIdentity {
|
|
|
136
136
|
|
|
137
137
|
const targetName = this.targetEntity;
|
|
138
138
|
if (targetName === undefined) return undefined;
|
|
139
|
-
|
|
139
|
+
// targetEntity may be package-qualified (FQN); findObject is keyed by bare
|
|
140
|
+
// name, so fall back to the bare suffix after the last "::". Mirrors the
|
|
141
|
+
// resolveTargetTable fix; without it a cross-package reference resolves no
|
|
142
|
+
// target and the PK column wrongly defaults to "id".
|
|
143
|
+
const targetObj = root.findObject(targetName)
|
|
144
|
+
?? (targetName.includes("::")
|
|
145
|
+
? root.findObject(targetName.slice(targetName.lastIndexOf("::") + 2))
|
|
146
|
+
: undefined);
|
|
140
147
|
if (!targetObj) return undefined;
|
|
141
148
|
|
|
142
149
|
const primary = targetObj.primaryIdentity();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { SUBTYPE_BASE } from "../../shared/base-types.js";
|
|
4
4
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
|
-
// Validator subtypes (
|
|
6
|
+
// Validator subtypes (10)
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
export const VALIDATOR_SUBTYPE_REQUIRED = "required";
|
|
@@ -11,6 +11,11 @@ export const VALIDATOR_SUBTYPE_LENGTH = "length";
|
|
|
11
11
|
export const VALIDATOR_SUBTYPE_REGEX = "regex";
|
|
12
12
|
export const VALIDATOR_SUBTYPE_NUMERIC = "numeric";
|
|
13
13
|
export const VALIDATOR_SUBTYPE_ARRAY = "array";
|
|
14
|
+
// Cross-field validators — entity-scoped, reference sibling fields by name.
|
|
15
|
+
export const VALIDATOR_SUBTYPE_COMPARISON = "comparison";
|
|
16
|
+
export const VALIDATOR_SUBTYPE_REQUIRED_WHEN = "requiredWhen";
|
|
17
|
+
export const VALIDATOR_SUBTYPE_PRESENT_IFF = "presentIff";
|
|
18
|
+
export const VALIDATOR_SUBTYPE_AT_LEAST_ONE = "atLeastOne";
|
|
14
19
|
|
|
15
20
|
export const VALIDATOR_SUBTYPES = [
|
|
16
21
|
SUBTYPE_BASE,
|
|
@@ -19,6 +24,10 @@ export const VALIDATOR_SUBTYPES = [
|
|
|
19
24
|
VALIDATOR_SUBTYPE_REGEX,
|
|
20
25
|
VALIDATOR_SUBTYPE_NUMERIC,
|
|
21
26
|
VALIDATOR_SUBTYPE_ARRAY,
|
|
27
|
+
VALIDATOR_SUBTYPE_COMPARISON,
|
|
28
|
+
VALIDATOR_SUBTYPE_REQUIRED_WHEN,
|
|
29
|
+
VALIDATOR_SUBTYPE_PRESENT_IFF,
|
|
30
|
+
VALIDATOR_SUBTYPE_AT_LEAST_ONE,
|
|
22
31
|
] as const;
|
|
23
32
|
export type ValidatorSubType = (typeof VALIDATOR_SUBTYPES)[number];
|
|
24
33
|
|
|
@@ -29,3 +38,15 @@ export type ValidatorSubType = (typeof VALIDATOR_SUBTYPES)[number];
|
|
|
29
38
|
export const VALIDATOR_ATTR_PATTERN = "pattern";
|
|
30
39
|
export const VALIDATOR_ATTR_MIN = "min";
|
|
31
40
|
export const VALIDATOR_ATTR_MAX = "max";
|
|
41
|
+
// Cross-field validator attrs (field references by name + operator/value).
|
|
42
|
+
export const VALIDATOR_ATTR_LEFT = "left";
|
|
43
|
+
export const VALIDATOR_ATTR_OP = "op";
|
|
44
|
+
export const VALIDATOR_ATTR_RIGHT = "right";
|
|
45
|
+
export const VALIDATOR_ATTR_FIELD = "field";
|
|
46
|
+
export const VALIDATOR_ATTR_WHEN = "when";
|
|
47
|
+
export const VALIDATOR_ATTR_EQUALS = "equals";
|
|
48
|
+
export const VALIDATOR_ATTR_FIELDS = "fields";
|
|
49
|
+
|
|
50
|
+
// Comparison operators (@op allowed values) → relational operator.
|
|
51
|
+
export const VALIDATOR_COMPARISON_OPS = ["gt", "gte", "lt", "lte", "ne", "eq"] as const;
|
|
52
|
+
export type ComparisonOp = (typeof VALIDATOR_COMPARISON_OPS)[number];
|
|
@@ -136,6 +136,127 @@ export const VALIDATOR_DEFINITION: ProviderDefinition = {
|
|
|
136
136
|
"description": "Maximum allowed value (length, numeric value, or array element count depending on the validator subtype)."
|
|
137
137
|
}
|
|
138
138
|
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "validator",
|
|
142
|
+
"subType": "comparison",
|
|
143
|
+
"description": "Cross-field ordering: requires two sibling fields of the owning entity stand in a relational order (@left @op @right), e.g. current_hp <= max_hp or expires_at > created_at. Entity-scoped; references fields by name. Backends derive the rule (CHECK constraint, cross-field assertion) — no raw expression is stored.",
|
|
144
|
+
"rules": "@left and @right must name fields of the owning entity. @op is one of gt/gte/lt/lte/ne/eq. The comparison is null-tolerant where the backend's relational operator is (SQL: a NULL operand yields no violation).",
|
|
145
|
+
"children": [
|
|
146
|
+
{
|
|
147
|
+
"type": "attr",
|
|
148
|
+
"subType": "string",
|
|
149
|
+
"name": "left",
|
|
150
|
+
"min": 1,
|
|
151
|
+
"max": 1,
|
|
152
|
+
"description": "Name of the left-hand field of the owning entity."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"type": "attr",
|
|
156
|
+
"subType": "string",
|
|
157
|
+
"name": "op",
|
|
158
|
+
"min": 1,
|
|
159
|
+
"max": 1,
|
|
160
|
+
"allowedValues": [
|
|
161
|
+
"gt",
|
|
162
|
+
"gte",
|
|
163
|
+
"lt",
|
|
164
|
+
"lte",
|
|
165
|
+
"ne",
|
|
166
|
+
"eq"
|
|
167
|
+
],
|
|
168
|
+
"description": "Relational operator: gt (>), gte (>=), lt (<), lte (<=), ne (<>), eq (=)."
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"type": "attr",
|
|
172
|
+
"subType": "string",
|
|
173
|
+
"name": "right",
|
|
174
|
+
"min": 1,
|
|
175
|
+
"max": 1,
|
|
176
|
+
"description": "Name of the right-hand field of the owning entity."
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"type": "validator",
|
|
182
|
+
"subType": "requiredWhen",
|
|
183
|
+
"description": "One-directional conditional presence: when the gating field (@when) equals @equals, the target field (@field) must be present (NOT NULL); otherwise @field is unconstrained. Mirrors JSON Schema dependentRequired / Rails validates_presence_of :x, if:. Entity-scoped; references fields by name.",
|
|
184
|
+
"rules": "@field and @when must name fields of the owning entity. @equals is the gating value, compared against @when's value (rendered per @when's field subtype — boolean true/false, enum/string literal, numeric literal).",
|
|
185
|
+
"children": [
|
|
186
|
+
{
|
|
187
|
+
"type": "attr",
|
|
188
|
+
"subType": "string",
|
|
189
|
+
"name": "field",
|
|
190
|
+
"min": 1,
|
|
191
|
+
"max": 1,
|
|
192
|
+
"description": "Name of the field that becomes required when the condition holds."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"type": "attr",
|
|
196
|
+
"subType": "string",
|
|
197
|
+
"name": "when",
|
|
198
|
+
"min": 1,
|
|
199
|
+
"max": 1,
|
|
200
|
+
"description": "Name of the gating field whose value triggers the requirement."
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"type": "attr",
|
|
204
|
+
"subType": "string",
|
|
205
|
+
"name": "equals",
|
|
206
|
+
"min": 1,
|
|
207
|
+
"max": 1,
|
|
208
|
+
"description": "The gating value; when @when equals this, @field must be present."
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"type": "validator",
|
|
214
|
+
"subType": "presentIff",
|
|
215
|
+
"description": "Biconditional presence: the target field (@field) is present (NOT NULL) if and only if the gating field (@when) equals @equals. Models paired flag/companion-column invariants, e.g. used_at present iff is_used=true. Entity-scoped; references fields by name.",
|
|
216
|
+
"rules": "@field and @when must name fields of the owning entity. @equals is rendered per @when's field subtype. Stricter than requiredWhen — also forbids @field when the condition is false.",
|
|
217
|
+
"children": [
|
|
218
|
+
{
|
|
219
|
+
"type": "attr",
|
|
220
|
+
"subType": "string",
|
|
221
|
+
"name": "field",
|
|
222
|
+
"min": 1,
|
|
223
|
+
"max": 1,
|
|
224
|
+
"description": "Name of the field whose presence is governed by the condition."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"type": "attr",
|
|
228
|
+
"subType": "string",
|
|
229
|
+
"name": "when",
|
|
230
|
+
"min": 1,
|
|
231
|
+
"max": 1,
|
|
232
|
+
"description": "Name of the gating field."
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"type": "attr",
|
|
236
|
+
"subType": "string",
|
|
237
|
+
"name": "equals",
|
|
238
|
+
"min": 1,
|
|
239
|
+
"max": 1,
|
|
240
|
+
"description": "The gating value; @field is present exactly when @when equals this."
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"type": "validator",
|
|
246
|
+
"subType": "atLeastOne",
|
|
247
|
+
"description": "Cardinality of presence: at least one of the named fields (@fields) must be present (NOT NULL). Entity-scoped; references fields by name (same @fields-by-name pattern as identity.*).",
|
|
248
|
+
"rules": "@fields names two or more fields of the owning entity. Satisfied when any one of them is non-null.",
|
|
249
|
+
"children": [
|
|
250
|
+
{
|
|
251
|
+
"type": "attr",
|
|
252
|
+
"subType": "string",
|
|
253
|
+
"name": "fields",
|
|
254
|
+
"isArray": true,
|
|
255
|
+
"min": 1,
|
|
256
|
+
"max": 1,
|
|
257
|
+
"description": "Names of the candidate fields; at least one must be present."
|
|
258
|
+
}
|
|
259
|
+
]
|
|
139
260
|
}
|
|
140
261
|
]
|
|
141
262
|
};
|
package/src/core-types.ts
CHANGED
|
@@ -84,8 +84,9 @@ import {
|
|
|
84
84
|
IDENTITY_SUBTYPE_PRIMARY,
|
|
85
85
|
IDENTITY_SUBTYPE_SECONDARY,
|
|
86
86
|
IDENTITY_SUBTYPE_REFERENCE,
|
|
87
|
+
IDENTITY_REFERENCE_ATTR_REFERENCES,
|
|
87
88
|
} from "./core/identity/identity-constants.js";
|
|
88
|
-
import { RELATIONSHIP_SUBTYPES } from "./core/relationship/relationship-constants.js";
|
|
89
|
+
import { RELATIONSHIP_SUBTYPES, RELATIONSHIP_ATTR_OBJECT_REF } from "./core/relationship/relationship-constants.js";
|
|
89
90
|
import { LAYOUT_SUBTYPES } from "./presentation/layout/layout-constants.js";
|
|
90
91
|
import { SOURCE_SUBTYPES } from "./persistence/source/source-constants.js";
|
|
91
92
|
import {
|
|
@@ -462,6 +463,30 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
462
463
|
registry.register(relationshipDef);
|
|
463
464
|
}
|
|
464
465
|
|
|
466
|
+
// Declare the core cross-references ON their TypeDefinitions, so the loader's
|
|
467
|
+
// registry-derived validation resolves them generically (a dangling target fails the
|
|
468
|
+
// load). Set on the concrete subtypes the parser produces. (Production moves these into
|
|
469
|
+
// the embedded spec/metamodel JSON once every port's spec reader carries the field.)
|
|
470
|
+
for (const subType of RELATIONSHIP_SUBTYPES) {
|
|
471
|
+
const def = registry.find(TYPE_RELATIONSHIP, subType);
|
|
472
|
+
if (def) {
|
|
473
|
+
def.references = [
|
|
474
|
+
{ attr: RELATIONSHIP_ATTR_OBJECT_REF, targetType: TYPE_OBJECT, errorCode: "ERR_INVALID_RELATIONSHIP" },
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const idRefDef = registry.find(TYPE_IDENTITY, IDENTITY_SUBTYPE_REFERENCE);
|
|
479
|
+
if (idRefDef) {
|
|
480
|
+
idRefDef.references = [
|
|
481
|
+
{
|
|
482
|
+
attr: IDENTITY_REFERENCE_ATTR_REFERENCES,
|
|
483
|
+
targetType: TYPE_OBJECT,
|
|
484
|
+
dottedFieldPath: true,
|
|
485
|
+
errorCode: "ERR_INVALID_REFERENCE",
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
}
|
|
489
|
+
|
|
465
490
|
// Default subTypes for YAML authoring sugar: a bare `metadata:` / `object:`
|
|
466
491
|
// key resolves to these. `metadata` has exactly one subtype (root) so the
|
|
467
492
|
// default is unambiguous; `object` defaults to `entity`, the common case.
|
package/src/errors.ts
CHANGED
|
@@ -30,6 +30,9 @@ export const ERROR_CODES = [
|
|
|
30
30
|
// author-chosen (e.g. "id"), so the dotted by-name extends form can
|
|
31
31
|
// address them.
|
|
32
32
|
"ERR_IDENTITY_NAME_REQUIRED",
|
|
33
|
+
// A `type.subType` declared with `maxOccurs` (e.g. identity.primary,
|
|
34
|
+
// maxOccurs:1) appears more times than allowed under one parent.
|
|
35
|
+
"ERR_TOO_MANY_OCCURRENCES",
|
|
33
36
|
// FR-024 — an identity.* on an object.projection lacks `extends`; a
|
|
34
37
|
// projection identity is a pass-through of an entity identity.
|
|
35
38
|
"ERR_PROJECTION_IDENTITY_NOT_EXTENDED",
|
|
@@ -95,6 +98,9 @@ export const ERROR_CODES = [
|
|
|
95
98
|
// junction declaring two identity.reference children; @sourceRefField must match
|
|
96
99
|
// one of them; M:N attrs are invalid on a 1:N (@cardinality:one / no @through).
|
|
97
100
|
"ERR_INVALID_RELATIONSHIP",
|
|
101
|
+
// identity.reference @references names an FK target that does not resolve to any
|
|
102
|
+
// object in the loaded tree (a dangling cross-reference between metadata).
|
|
103
|
+
"ERR_INVALID_REFERENCE",
|
|
98
104
|
"ERR_VAR_NOT_ON_PAYLOAD",
|
|
99
105
|
"ERR_PARTIAL_UNRESOLVED",
|
|
100
106
|
"ERR_REQUIRED_SLOT_UNUSED",
|
|
@@ -189,7 +195,10 @@ export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
|
189
195
|
* envelope's `jsonPath` and `files` and have been dropped — see CHANGELOG.
|
|
190
196
|
*/
|
|
191
197
|
export class ParseError extends Error implements LoaderError {
|
|
192
|
-
|
|
198
|
+
// Widened from the core ErrorCode union so a DOWNSTREAM provider's validator can emit its
|
|
199
|
+
// own codes (LoaderError.code is `string`; the envelope compares codes as strings). Known
|
|
200
|
+
// core codes still surface in editor suggestions via the `string & {}` idiom.
|
|
201
|
+
readonly code: ErrorCode | (string & {});
|
|
193
202
|
readonly source: ErrorSource;
|
|
194
203
|
readonly suggestions?: string[];
|
|
195
204
|
readonly fixture?: string;
|
|
@@ -198,7 +207,7 @@ export class ParseError extends Error implements LoaderError {
|
|
|
198
207
|
constructor(
|
|
199
208
|
message: string,
|
|
200
209
|
opts: {
|
|
201
|
-
code: ErrorCode;
|
|
210
|
+
code: ErrorCode | (string & {});
|
|
202
211
|
source: ErrorSource;
|
|
203
212
|
suggestions?: string[];
|
|
204
213
|
fixture?: string;
|
|
@@ -18,6 +18,7 @@ import type { LoaderWarning } from "../source.js";
|
|
|
18
18
|
import { codeSource, resolvedSource } from "../source.js";
|
|
19
19
|
import { parseJson } from "../parser-json.js";
|
|
20
20
|
import { validateDataGridSortFields, validateFilterableHasIndex, validateFilterableHasSupportedOps, validateOriginPaths, validateDerivedFieldProvidability, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs, validateFieldDefaults, validateRelationships } from "./validation-passes.js";
|
|
21
|
+
import { runRegisteredValidation } from "./validation-registry.js";
|
|
21
22
|
import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
|
|
22
23
|
import { validateSourcePhysicalNames } from "../persistence/source/validate-source-physical-names.js";
|
|
23
24
|
import { validateSourceParameterRef } from "../persistence/source/validate-source-parameter-ref.js";
|
|
@@ -25,6 +26,7 @@ import { validateFieldReadOnly } from "../core/field/validate-field-readonly.js"
|
|
|
25
26
|
import { validateDiscriminator } from "../core/object/validate-discriminator.js";
|
|
26
27
|
import { resolveDeferredSupers } from "../super-resolve.js";
|
|
27
28
|
import { validateSubtypeRules } from "../subtype-rules.js";
|
|
29
|
+
import { validateMaxOccurs } from "../validate-max-occurs.js";
|
|
28
30
|
import { validateIdentityPassthrough } from "../core/identity/validate-identity-passthrough.js";
|
|
29
31
|
import { validateAttrSchema } from "../attr-schema-validate.js";
|
|
30
32
|
import type { MetaDataFormat, MetaDataSource } from "./meta-data-source.js";
|
|
@@ -452,6 +454,10 @@ export class MetaDataLoader {
|
|
|
452
454
|
errors.push(...ruleResult.errors);
|
|
453
455
|
warnings.push(...ruleResult.warnings);
|
|
454
456
|
|
|
457
|
+
// maxOccurs enforcement (config-driven singleton constraint, e.g. one
|
|
458
|
+
// identity.primary per entity) — the safety complement to defaultName.
|
|
459
|
+
errors.push(...validateMaxOccurs(root, this._registry));
|
|
460
|
+
|
|
455
461
|
// FR-024 B3 — projection identity pass-through + key correspondence
|
|
456
462
|
// (ERR_PROJECTION_IDENTITY_NOT_EXTENDED / ERR_IDENTITY_KEY_MISMATCH).
|
|
457
463
|
errors.push(...validateIdentityPassthrough(root));
|
|
@@ -485,6 +491,12 @@ export class MetaDataLoader {
|
|
|
485
491
|
// are invalid on a 1:N relationship.
|
|
486
492
|
errors.push(...validateRelationships(root));
|
|
487
493
|
|
|
494
|
+
// Phase 2 — validation DERIVED FROM THE TYPE REGISTRY: each node's TypeDefinition
|
|
495
|
+
// carries its reference descriptors + imperative validator, run as one recursive walk
|
|
496
|
+
// over a built-once symbol table. A downstream provider's custom type validates itself
|
|
497
|
+
// simply by being in this registry — no separate wiring.
|
|
498
|
+
errors.push(...runRegisteredValidation(root, this._registry));
|
|
499
|
+
|
|
488
500
|
// template.* validation — @payloadRef resolves to a known object;
|
|
489
501
|
// @requiredSlots are real fields on it (FR-004 Plan #3, T2).
|
|
490
502
|
errors.push(...validateTemplatePayloadRefs(root));
|
|
@@ -66,7 +66,10 @@ import {
|
|
|
66
66
|
FIELD_SUBTYPE_OBJECT,
|
|
67
67
|
} from "../core/field/field-constants.js";
|
|
68
68
|
import { FIELD_ATTR_DB_INDEXED } from "../persistence/db/db-constants.js";
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
IDENTITY_ATTR_FIELDS,
|
|
71
|
+
IDENTITY_SUBTYPE_REFERENCE,
|
|
72
|
+
} from "../core/identity/identity-constants.js";
|
|
70
73
|
import {
|
|
71
74
|
ORIGIN_SUBTYPE_PASSTHROUGH,
|
|
72
75
|
ORIGIN_SUBTYPE_AGGREGATE,
|
|
@@ -1199,6 +1202,10 @@ export function validateRelationships(root: MetaData): ParseError[] {
|
|
|
1199
1202
|
const isMany = cardinality === CARDINALITY_MANY;
|
|
1200
1203
|
const isM2M = hasThrough && isMany;
|
|
1201
1204
|
|
|
1205
|
+
// NOTE: @objectRef existence resolution moved to the validation registry
|
|
1206
|
+
// (defaultValidationRegistry → a declarative reference descriptor). The M:N
|
|
1207
|
+
// slim-vocabulary rules below stay here for now (Phase 3 migrates them too).
|
|
1208
|
+
|
|
1202
1209
|
// Rule (d): M:N-only attrs on a non-M:N relationship.
|
|
1203
1210
|
if (!isM2M) {
|
|
1204
1211
|
if (hasThrough) {
|
|
@@ -1292,6 +1299,9 @@ export function validateRelationships(root: MetaData): ParseError[] {
|
|
|
1292
1299
|
return errors;
|
|
1293
1300
|
}
|
|
1294
1301
|
|
|
1302
|
+
// NOTE: identity.reference @references resolution moved to the validation registry
|
|
1303
|
+
// (defaultValidationRegistry → a declarative reference descriptor with dottedFieldPath).
|
|
1304
|
+
|
|
1295
1305
|
function checkFilterClauses(
|
|
1296
1306
|
filter: Record<string, unknown>,
|
|
1297
1307
|
allow: Map<string, readonly string[]>,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// The validation walk — implementation of the contract in ../validation-types.ts.
|
|
2
|
+
//
|
|
3
|
+
// Validation is DERIVED FROM THE TYPE REGISTRY: each node's TypeDefinition carries its
|
|
4
|
+
// reference descriptors + imperative validator, so a downstream provider's type validates
|
|
5
|
+
// itself with no separate registry and no core changes. One recursive walk over a
|
|
6
|
+
// built-once symbol table: per node, apply declared references, invoke the type's
|
|
7
|
+
// validator, recurse. See docs/superpowers/specs/2026-06-19-metadata-validation-architecture-design.md.
|
|
8
|
+
|
|
9
|
+
import type { MetaData } from "../shared/meta-data.js";
|
|
10
|
+
import { ParseError } from "../errors.js";
|
|
11
|
+
import { refMatchesObject } from "../naming-refs.js";
|
|
12
|
+
import { TYPE_OBJECT } from "../shared/base-types.js";
|
|
13
|
+
import type { TypeRegistry } from "../registry.js";
|
|
14
|
+
import type { LoaderCode, SymbolTable, ValidationContext } from "../validation-types.js";
|
|
15
|
+
|
|
16
|
+
/** A symbol table of every top-level object, built once per load (the binder analogue). */
|
|
17
|
+
class SymbolTableImpl implements SymbolTable {
|
|
18
|
+
private readonly byRef = new Map<string, MetaData>();
|
|
19
|
+
|
|
20
|
+
static build(root: MetaData): SymbolTableImpl {
|
|
21
|
+
const t = new SymbolTableImpl();
|
|
22
|
+
for (const child of root.ownChildren()) {
|
|
23
|
+
if (child.type !== TYPE_OBJECT) continue;
|
|
24
|
+
if (child.name) t.byRef.set(child.name, child);
|
|
25
|
+
t.byRef.set(child.fqn(), child);
|
|
26
|
+
t.byRef.set(child.resolutionKey(), child);
|
|
27
|
+
}
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resolveObject(ref: string): MetaData | undefined {
|
|
32
|
+
const hit = this.byRef.get(ref);
|
|
33
|
+
if (hit) return hit;
|
|
34
|
+
for (const obj of this.byRef.values()) {
|
|
35
|
+
if (refMatchesObject(obj, ref)) return obj;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ValidationContextImpl implements ValidationContext {
|
|
42
|
+
readonly errors: ParseError[] = [];
|
|
43
|
+
constructor(readonly symbols: SymbolTable) {}
|
|
44
|
+
error(code: LoaderCode, node: MetaData, message: string): void {
|
|
45
|
+
this.errors.push(new ParseError(message, { code, source: node.source }));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Run validation derived from `registry` over the tree. Per node: look up its
|
|
51
|
+
* TypeDefinition, apply its declared reference descriptors (resolve against the symbol
|
|
52
|
+
* table), invoke its imperative validator, then recurse into own children.
|
|
53
|
+
*/
|
|
54
|
+
export function runRegisteredValidation(root: MetaData, registry: TypeRegistry): ParseError[] {
|
|
55
|
+
const ctx = new ValidationContextImpl(SymbolTableImpl.build(root));
|
|
56
|
+
walk(root);
|
|
57
|
+
return ctx.errors;
|
|
58
|
+
|
|
59
|
+
function walk(node: MetaData): void {
|
|
60
|
+
const def = registry.find(node.type, node.subType);
|
|
61
|
+
if (def) {
|
|
62
|
+
for (const desc of def.references ?? []) {
|
|
63
|
+
const raw = node.ownAttr(desc.attr);
|
|
64
|
+
if (typeof raw !== "string" || raw === "") continue; // absence is the required-attr pass's job
|
|
65
|
+
const entityRef = desc.dottedFieldPath ? (raw.split(".")[0] ?? raw) : raw;
|
|
66
|
+
const target = ctx.symbols.resolveObject(entityRef);
|
|
67
|
+
// Qualify the node name with its owning entity (e.g. "Order.items") so the error is
|
|
68
|
+
// locatable from the message alone, not just the source envelope.
|
|
69
|
+
const qname = node.parent?.name ? `${node.parent.name}.${node.name}` : node.name;
|
|
70
|
+
if (!target) {
|
|
71
|
+
ctx.error(
|
|
72
|
+
desc.errorCode,
|
|
73
|
+
node,
|
|
74
|
+
`${node.type}.${node.subType} "${qname}" @${desc.attr} "${raw}" does not resolve to an object.`,
|
|
75
|
+
);
|
|
76
|
+
} else if (
|
|
77
|
+
target.type !== desc.targetType ||
|
|
78
|
+
(desc.targetSubType !== undefined && target.subType !== desc.targetSubType)
|
|
79
|
+
) {
|
|
80
|
+
const want = desc.targetSubType ? `${desc.targetType}.${desc.targetSubType}` : desc.targetType;
|
|
81
|
+
ctx.error(
|
|
82
|
+
desc.errorCode,
|
|
83
|
+
node,
|
|
84
|
+
`${node.type}.${node.subType} "${qname}" @${desc.attr} "${raw}" resolves to ` +
|
|
85
|
+
`${target.type}.${target.subType}, not a ${want}.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
def.validate?.(node, ctx);
|
|
90
|
+
}
|
|
91
|
+
for (const child of node.ownChildren()) walk(child);
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/parser-core.ts
CHANGED
|
@@ -519,10 +519,19 @@ function parseNodeFresh(
|
|
|
519
519
|
|
|
520
520
|
// --- Determine name ---
|
|
521
521
|
const rawName = nodeData[RESERVED_KEY_NAME];
|
|
522
|
-
|
|
522
|
+
let name = typeof rawName === "string" ? rawName : "";
|
|
523
523
|
|
|
524
524
|
// --- Create the model ---
|
|
525
525
|
const def = registry.find(type, subType)!;
|
|
526
|
+
// Config-driven default name for a SINGLETON child type: when the node is
|
|
527
|
+
// declared with no name and its type definition is `maxOccurs: 1` with a
|
|
528
|
+
// `defaultName`, assign it (e.g. identity.primary → "primary"). Safe by
|
|
529
|
+
// construction — maxOccurs===1 guarantees no sibling can collide — and keeps
|
|
530
|
+
// the one-and-only node addressable. Multi-cardinality types carry no
|
|
531
|
+
// defaultName, so they still require an explicit name (FR-024).
|
|
532
|
+
if (name === "" && def.maxOccurs === 1 && def.defaultName !== undefined) {
|
|
533
|
+
name = def.defaultName;
|
|
534
|
+
}
|
|
526
535
|
const model = def.factory(def.typeId, name);
|
|
527
536
|
// FR5a — stamp the source provenance envelope using the parser's current
|
|
528
537
|
// JSONPath stack + source id. setSource happens BEFORE freeze (the parser
|
|
@@ -10,6 +10,22 @@ export const FIELD_ATTR_COLUMN = "column";
|
|
|
10
10
|
/** When true, suppress the @filterable-without-index Loader warning (Project D drift check). */
|
|
11
11
|
export const FIELD_ATTR_DB_INDEXED = "db.indexed";
|
|
12
12
|
|
|
13
|
+
// --- Physical RDB index/constraint attrs the db provider adds to identity.* ---
|
|
14
|
+
// These EXTEND core identity subtypes (via registry.extend) rather than living on
|
|
15
|
+
// core, because they are pure physical-storage concerns (index ordering, partial-
|
|
16
|
+
// index predicate, FK constraint naming) with no logical-model meaning.
|
|
17
|
+
|
|
18
|
+
/** identity.secondary: per-field index-key sort direction array ('asc' | 'desc'), positional to @fields. Drives DESC-ordered index keys. */
|
|
19
|
+
export const IDENTITY_ATTR_ORDERS = "orders";
|
|
20
|
+
/** identity.secondary: a partial-index predicate (raw SQL). When set, the index covers only matching rows. */
|
|
21
|
+
export const IDENTITY_ATTR_WHERE = "where";
|
|
22
|
+
/** identity.secondary: a raw key EXPRESSION for a functional/expression index (e.g. "lower(email)"); used instead of @fields. */
|
|
23
|
+
export const IDENTITY_ATTR_EXPR = "expr";
|
|
24
|
+
/** identity.secondary: index access method (e.g. "gin", "gist"); default "btree", which is not rendered. */
|
|
25
|
+
export const IDENTITY_ATTR_USING = "using";
|
|
26
|
+
/** identity.reference: physical FK constraint-name override. Absent → the auto-derived `<table>_<firstFkColumn>_fk`. */
|
|
27
|
+
export const IDENTITY_ATTR_CONSTRAINT_NAME = "constraintName";
|
|
28
|
+
|
|
13
29
|
/**
|
|
14
30
|
* R6 Plan 2b: `@dbColumnType` — physical DB column-type override on a field.
|
|
15
31
|
* Selects the DB column type WITHOUT changing the logical field type or its
|
|
@@ -172,6 +172,63 @@ export const DB_DEFINITION: ProviderDefinition = {
|
|
|
172
172
|
"description": "FR-015: name or FQN of an object.value describing the input shape of this source's callable interface. Permitted on @kind: \"storedProc\" / \"tableFunction\"; rejected on non-callable kinds (table / view / materializedView). Field children of the referenced object.value become the call-site parameter list in declaration order. Symmetric with template.@payloadRef in FR-004 — the typed-input pattern reuses object.value rather than minting a new parameter.* node type."
|
|
173
173
|
}
|
|
174
174
|
]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"type": "identity",
|
|
178
|
+
"subType": "secondary",
|
|
179
|
+
"children": [
|
|
180
|
+
{
|
|
181
|
+
"type": "attr",
|
|
182
|
+
"subType": "string",
|
|
183
|
+
"name": "orders",
|
|
184
|
+
"isArray": true,
|
|
185
|
+
"min": 0,
|
|
186
|
+
"max": 1,
|
|
187
|
+
"allowedValues": [
|
|
188
|
+
"asc",
|
|
189
|
+
"desc"
|
|
190
|
+
],
|
|
191
|
+
"description": "Physical index-key sort direction, positional to @fields ('asc' | 'desc'). Omit for all-ascending (the default); a shorter array leaves trailing keys ascending. Drives DESC-ordered index keys (e.g. a recency index on a timestamp). RDB-physical — contributed by the db provider, not core identity."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"type": "attr",
|
|
195
|
+
"subType": "string",
|
|
196
|
+
"name": "where",
|
|
197
|
+
"min": 0,
|
|
198
|
+
"max": 1,
|
|
199
|
+
"description": "Partial-index predicate (raw SQL, e.g. \"delivered_at IS NULL\"). When set, the index covers only rows matching the predicate — smaller and cheaper for queries that always filter on it. Absent = a full index over every row. RDB-physical — contributed by the db provider."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"type": "attr",
|
|
203
|
+
"subType": "string",
|
|
204
|
+
"name": "expr",
|
|
205
|
+
"min": 0,
|
|
206
|
+
"max": 1,
|
|
207
|
+
"description": "Raw key EXPRESSION for a functional/expression index (e.g. \"lower(email)\"). Used INSTEAD of @fields — the index key is the expression rather than plain columns. RDB-physical — contributed by the db provider."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"type": "attr",
|
|
211
|
+
"subType": "string",
|
|
212
|
+
"name": "using",
|
|
213
|
+
"min": 0,
|
|
214
|
+
"max": 1,
|
|
215
|
+
"description": "Index access method (e.g. \"gin\", \"gist\", \"hash\"); default \"btree\" (not rendered). Pair with @expr for e.g. a GIN index over an array/jsonb expression. RDB-physical — contributed by the db provider."
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"type": "identity",
|
|
221
|
+
"subType": "reference",
|
|
222
|
+
"children": [
|
|
223
|
+
{
|
|
224
|
+
"type": "attr",
|
|
225
|
+
"subType": "string",
|
|
226
|
+
"name": "constraintName",
|
|
227
|
+
"min": 0,
|
|
228
|
+
"max": 1,
|
|
229
|
+
"description": "Physical foreign-key constraint name override. Absent → the backend's auto-derived default (e.g. `<table>_<firstFkColumn>_fk`). Lets a model adopt an existing database whose FK constraints follow a different naming convention without a destructive rename. RDB-physical — contributed by the db provider."
|
|
230
|
+
}
|
|
231
|
+
]
|
|
175
232
|
}
|
|
176
233
|
]
|
|
177
234
|
};
|
package/src/provider-data.ts
CHANGED
|
@@ -97,6 +97,21 @@ export interface TypeDef {
|
|
|
97
97
|
* registered output change) under S0.
|
|
98
98
|
*/
|
|
99
99
|
extendsBase?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Max number of children of this `type.subType` permitted under one parent.
|
|
102
|
+
* `1` marks a singleton child (e.g. `identity.primary` — one per entity). The
|
|
103
|
+
* loader enforces it. A singleton is the ONLY place a static `defaultName` is
|
|
104
|
+
* safe (no sibling can collide), so `defaultName` is honored only when
|
|
105
|
+
* `maxOccurs === 1`. Absent = unbounded.
|
|
106
|
+
*/
|
|
107
|
+
maxOccurs?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Author-omittable name for a singleton child. When a node of this type is
|
|
110
|
+
* declared with no `name` AND `maxOccurs === 1`, the loader assigns
|
|
111
|
+
* `defaultName` (e.g. `identity.primary` → `"primary"`). Keeps the one-and-only
|
|
112
|
+
* node addressable (`Entity.primary`) without forcing a hand-written name.
|
|
113
|
+
*/
|
|
114
|
+
defaultName?: string;
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
/**
|
|
@@ -240,6 +255,8 @@ export function defineProviderFromData(
|
|
|
240
255
|
...(t.rules !== undefined ? { rules: t.rules } : {}),
|
|
241
256
|
...(t.example !== undefined ? { example: t.example } : {}),
|
|
242
257
|
...(t.whenToUse !== undefined ? { whenToUse: t.whenToUse } : {}),
|
|
258
|
+
...(t.maxOccurs !== undefined ? { maxOccurs: t.maxOccurs } : {}),
|
|
259
|
+
...(t.defaultName !== undefined ? { defaultName: t.defaultName } : {}),
|
|
243
260
|
};
|
|
244
261
|
});
|
|
245
262
|
}
|
package/src/registry.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { SUBTYPE_BASE, TYPE_ATTR } from "./shared/base-types.js";
|
|
|
3
3
|
import { CHILD_RULE_WILDCARD } from "./shared/structural.js";
|
|
4
4
|
import { type AttrSubType } from "./core/attr/attr-constants.js";
|
|
5
5
|
import type { DataType } from "./data-type.js";
|
|
6
|
+
import type { ReferenceDescriptor, NodeValidator } from "./validation-types.js";
|
|
6
7
|
import { MetaModelError } from "./errors.js";
|
|
7
8
|
|
|
8
9
|
export class TypeId {
|
|
@@ -107,6 +108,21 @@ export interface TypeDefinition {
|
|
|
107
108
|
example?: string;
|
|
108
109
|
/** FR-033 — guidance on when to reach for this type/subType. Optional. */
|
|
109
110
|
whenToUse?: string;
|
|
111
|
+
/** Max children of this type.subType per parent (`1` = singleton). Loader-enforced. */
|
|
112
|
+
maxOccurs?: number;
|
|
113
|
+
/** Default name for a singleton (`maxOccurs===1`) child declared with no name. */
|
|
114
|
+
defaultName?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Cross-references this node's attrs declare — resolved generically against the symbol
|
|
117
|
+
* table (a dangling/kind-mismatched target fails the load). Carried by the type's
|
|
118
|
+
* registration, so a downstream provider's references validate with no core changes.
|
|
119
|
+
*/
|
|
120
|
+
references?: ReferenceDescriptor[];
|
|
121
|
+
/**
|
|
122
|
+
* The type's imperative validator (logic config can't express). Invoked by the recursive
|
|
123
|
+
* validation walk. Owned by the provider that owns the type.
|
|
124
|
+
*/
|
|
125
|
+
validate?: NodeValidator;
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
export class TypeRegistry {
|