@metaobjectsdev/metadata 0.14.2 → 0.15.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.js +22 -1
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/core/attr/meta-attr.d.ts.map +1 -1
- package/dist/core/attr/meta-attr.js +2 -0
- package/dist/core/attr/meta-attr.js.map +1 -1
- package/dist/core/field/field-constants.d.ts +26 -1
- package/dist/core/field/field-constants.d.ts.map +1 -1
- package/dist/core/field/field-constants.js +29 -0
- package/dist/core/field/field-constants.js.map +1 -1
- package/dist/core/field/field-definition.embedded.d.ts.map +1 -1
- package/dist/core/field/field-definition.embedded.js +28 -0
- package/dist/core/field/field-definition.embedded.js.map +1 -1
- package/dist/core/field/meta-field.d.ts +12 -3
- package/dist/core/field/meta-field.d.ts.map +1 -1
- package/dist/core/field/meta-field.js +33 -15
- package/dist/core/field/meta-field.js.map +1 -1
- package/dist/core/field/validate-field-readonly.d.ts.map +1 -1
- package/dist/core/field/validate-field-readonly.js +15 -3
- package/dist/core/field/validate-field-readonly.js.map +1 -1
- package/dist/core/identity/meta-identity.d.ts +2 -0
- package/dist/core/identity/meta-identity.d.ts.map +1 -1
- package/dist/core/identity/meta-identity.js +16 -8
- package/dist/core/identity/meta-identity.js.map +1 -1
- package/dist/core/identity/validate-identity-passthrough.d.ts.map +1 -1
- package/dist/core/identity/validate-identity-passthrough.js +9 -1
- package/dist/core/identity/validate-identity-passthrough.js.map +1 -1
- package/dist/core/object/meta-object.d.ts.map +1 -1
- package/dist/core/object/meta-object.js +15 -5
- package/dist/core/object/meta-object.js.map +1 -1
- package/dist/core/object/validate-discriminator.d.ts.map +1 -1
- package/dist/core/object/validate-discriminator.js +17 -2
- package/dist/core/object/validate-discriminator.js.map +1 -1
- package/dist/core/query/query-constants.d.ts.map +1 -1
- package/dist/core/query/query-constants.js +7 -1
- package/dist/core/query/query-constants.js.map +1 -1
- package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
- package/dist/core/relationship/meta-relationship.js +9 -7
- package/dist/core/relationship/meta-relationship.js.map +1 -1
- package/dist/core/validator/meta-validator.d.ts.map +1 -1
- package/dist/core/validator/meta-validator.js +6 -3
- package/dist/core/validator/meta-validator.js.map +1 -1
- package/dist/core-types.js +2 -2
- package/dist/core-types.js.map +1 -1
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +4 -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 +97 -39
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/loader/validation-registry.d.ts.map +1 -1
- package/dist/loader/validation-registry.js +7 -2
- package/dist/loader/validation-registry.js.map +1 -1
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +6 -3
- package/dist/naming.js.map +1 -1
- package/dist/object-serializer.d.ts.map +1 -1
- package/dist/object-serializer.js +6 -2
- package/dist/object-serializer.js.map +1 -1
- package/dist/parser-core.js +5 -0
- package/dist/parser-core.js.map +1 -1
- package/dist/persistence/db/db-constants.d.ts +20 -9
- package/dist/persistence/db/db-constants.d.ts.map +1 -1
- package/dist/persistence/db/db-constants.js +20 -15
- 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 +19 -1
- package/dist/persistence/db/db-definition.embedded.js.map +1 -1
- package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
- package/dist/persistence/origin/meta-origin.js +6 -0
- package/dist/persistence/origin/meta-origin.js.map +1 -1
- package/dist/persistence/source/meta-source.d.ts.map +1 -1
- package/dist/persistence/source/meta-source.js +11 -5
- package/dist/persistence/source/meta-source.js.map +1 -1
- package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -1
- package/dist/persistence/source/validate-source-parameter-ref.js +14 -5
- package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -1
- package/dist/persistence/source/validate-source-physical-names.d.ts.map +1 -1
- package/dist/persistence/source/validate-source-physical-names.js +9 -1
- package/dist/persistence/source/validate-source-physical-names.js.map +1 -1
- package/dist/persistence/source/validate-source-roles.d.ts.map +1 -1
- package/dist/persistence/source/validate-source-roles.js +5 -1
- package/dist/persistence/source/validate-source-roles.js.map +1 -1
- package/dist/presentation/layout/meta-layout.d.ts.map +1 -1
- package/dist/presentation/layout/meta-layout.js +12 -6
- package/dist/presentation/layout/meta-layout.js.map +1 -1
- package/dist/registry-manifest.d.ts +12 -2
- package/dist/registry-manifest.d.ts.map +1 -1
- package/dist/registry-manifest.js +20 -8
- package/dist/registry-manifest.js.map +1 -1
- package/dist/serializer-json.d.ts.map +1 -1
- package/dist/serializer-json.js +8 -1
- package/dist/serializer-json.js.map +1 -1
- package/dist/shared/meta-data.d.ts +14 -0
- package/dist/shared/meta-data.d.ts.map +1 -1
- package/dist/shared/meta-data.js +30 -0
- package/dist/shared/meta-data.js.map +1 -1
- package/dist/shared/meta-root.d.ts.map +1 -1
- package/dist/shared/meta-root.js +8 -2
- package/dist/shared/meta-root.js.map +1 -1
- package/dist/subtype-rules.js +4 -3
- package/dist/subtype-rules.js.map +1 -1
- package/dist/super-resolve.d.ts.map +1 -1
- package/dist/super-resolve.js +3 -0
- package/dist/super-resolve.js.map +1 -1
- package/dist/validate-max-occurs.js +5 -0
- package/dist/validate-max-occurs.js.map +1 -1
- package/package.json +1 -1
- package/src/attr-schema-validate.ts +24 -1
- package/src/core/attr/meta-attr.ts +2 -0
- package/src/core/field/field-constants.ts +34 -0
- package/src/core/field/field-definition.embedded.ts +28 -0
- package/src/core/field/meta-field.ts +30 -12
- package/src/core/field/validate-field-readonly.ts +15 -3
- package/src/core/identity/meta-identity.ts +16 -8
- package/src/core/identity/validate-identity-passthrough.ts +9 -1
- package/src/core/object/meta-object.ts +5 -0
- package/src/core/object/validate-discriminator.ts +17 -2
- package/src/core/query/query-constants.ts +7 -1
- package/src/core/relationship/meta-relationship.ts +9 -7
- package/src/core/validator/meta-validator.ts +6 -3
- package/src/core-types.ts +2 -2
- package/src/loader/meta-data-loader.ts +4 -0
- package/src/loader/validation-passes.ts +97 -39
- package/src/loader/validation-registry.ts +7 -2
- package/src/naming.ts +6 -3
- package/src/object-serializer.ts +6 -2
- package/src/parser-core.ts +5 -0
- package/src/persistence/db/db-constants.ts +20 -15
- package/src/persistence/db/db-definition.embedded.ts +19 -1
- package/src/persistence/origin/meta-origin.ts +6 -0
- package/src/persistence/source/meta-source.ts +11 -5
- package/src/persistence/source/validate-source-parameter-ref.ts +14 -5
- package/src/persistence/source/validate-source-physical-names.ts +9 -1
- package/src/persistence/source/validate-source-roles.ts +5 -1
- package/src/presentation/layout/meta-layout.ts +12 -6
- package/src/registry-manifest.ts +28 -8
- package/src/serializer-json.ts +8 -1
- package/src/shared/meta-data.ts +30 -0
- package/src/shared/meta-root.ts +4 -0
- package/src/subtype-rules.ts +4 -3
- package/src/super-resolve.ts +3 -0
- package/src/validate-max-occurs.ts +5 -0
|
@@ -107,7 +107,8 @@ import {
|
|
|
107
107
|
|
|
108
108
|
export function validateDataGridSortFields(root: MetaData): ParseError[] {
|
|
109
109
|
const errors: ParseError[] = [];
|
|
110
|
-
|
|
110
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
111
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
111
112
|
// Use children() so inherited fields (via extends:/super:) are
|
|
112
113
|
// visible when validating @defaultSortField references.
|
|
113
114
|
const effective = obj.children();
|
|
@@ -115,7 +116,8 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
|
|
|
115
116
|
effective.filter((c) => c.type === TYPE_FIELD).map((f) => f.name),
|
|
116
117
|
);
|
|
117
118
|
for (const layout of effective.filter((c) => c.type === TYPE_LAYOUT && c.subType === LAYOUT_SUBTYPE_DATA_GRID)) {
|
|
118
|
-
|
|
119
|
+
// ADR-0039: resolving — a layout may inherit its grid attrs via extends.
|
|
120
|
+
const sortField = layout.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
|
|
119
121
|
if (typeof sortField === "string" && !fieldNames.has(sortField)) {
|
|
120
122
|
errors.push(
|
|
121
123
|
new ParseError(
|
|
@@ -144,6 +146,8 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
|
|
|
144
146
|
/** Recursively collect all template.* nodes anywhere in the metadata tree. */
|
|
145
147
|
function allTemplates(node: MetaData): MetaData[] {
|
|
146
148
|
const out: MetaData[] = [];
|
|
149
|
+
// ADR-0039: own — structural walk over the PHYSICAL declaration tree collecting
|
|
150
|
+
// every declared template.* node (each visited once at its declaration site).
|
|
147
151
|
for (const c of node.ownChildren()) {
|
|
148
152
|
if (c.type === TYPE_TEMPLATE) out.push(c);
|
|
149
153
|
out.push(...allTemplates(c));
|
|
@@ -161,9 +165,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
161
165
|
// body). The closed-enum membership of @kind is handled by validateAttrSchema
|
|
162
166
|
// (allowedValues) — here we only enforce the conditional ref presence.
|
|
163
167
|
if (tmpl.subType === TEMPLATE_SUBTYPE_OUTPUT) {
|
|
164
|
-
|
|
168
|
+
// ADR-0039: resolving — a template may inherit its refs/attrs via extends.
|
|
169
|
+
const kind = tmpl.attr(TEMPLATE_ATTR_KIND);
|
|
165
170
|
if (kind === TEMPLATE_KIND_EMAIL) {
|
|
166
|
-
|
|
171
|
+
// ADR-0039: resolving — a template may inherit @subjectRef via extends.
|
|
172
|
+
if (typeof tmpl.attr(TEMPLATE_ATTR_SUBJECT_REF) !== "string") {
|
|
167
173
|
errors.push(
|
|
168
174
|
new ParseError(
|
|
169
175
|
`template "${tmpl.name}" @kind "email" requires @subjectRef`,
|
|
@@ -171,7 +177,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
171
177
|
),
|
|
172
178
|
);
|
|
173
179
|
}
|
|
174
|
-
|
|
180
|
+
// ADR-0039: resolving — a template may inherit @htmlBodyRef via extends.
|
|
181
|
+
if (typeof tmpl.attr(TEMPLATE_ATTR_HTML_BODY_REF) !== "string") {
|
|
175
182
|
errors.push(
|
|
176
183
|
new ParseError(
|
|
177
184
|
`template "${tmpl.name}" @kind "email" requires @htmlBodyRef`,
|
|
@@ -183,7 +190,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
183
190
|
// @kind absent or "document" → require @textRef. (An out-of-enum @kind is
|
|
184
191
|
// separately reported by validateAttrSchema; we still require @textRef so a
|
|
185
192
|
// document is never bodyless.)
|
|
186
|
-
|
|
193
|
+
// ADR-0039: resolving — a template may inherit @textRef via extends.
|
|
194
|
+
if (typeof tmpl.attr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
|
|
187
195
|
errors.push(
|
|
188
196
|
new ParseError(
|
|
189
197
|
`template "${tmpl.name}" @kind "document" requires @textRef`,
|
|
@@ -194,7 +202,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
194
202
|
}
|
|
195
203
|
} else if (tmpl.subType === TEMPLATE_SUBTYPE_PROMPT) {
|
|
196
204
|
// template.prompt always carries a renderable body via @textRef.
|
|
197
|
-
|
|
205
|
+
// ADR-0039: resolving — a template may inherit @textRef via extends.
|
|
206
|
+
if (typeof tmpl.attr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
|
|
198
207
|
errors.push(
|
|
199
208
|
new ParseError(
|
|
200
209
|
`template "${tmpl.name}" requires @textRef`,
|
|
@@ -204,9 +213,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
204
213
|
}
|
|
205
214
|
}
|
|
206
215
|
|
|
207
|
-
|
|
216
|
+
// ADR-0039: resolving — a template may inherit @payloadRef via extends.
|
|
217
|
+
const payloadRef = tmpl.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
208
218
|
if (typeof payloadRef !== "string") continue; // absence handled by the required-attr schema check
|
|
209
|
-
|
|
219
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
220
|
+
const payload = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
|
|
210
221
|
if (!payload || payload.subType !== OBJECT_SUBTYPE_VALUE) {
|
|
211
222
|
// FR5d — @payloadRef is a reference; emit format=resolved with
|
|
212
223
|
// referrer=template FQN, target=the unresolved payloadRef string.
|
|
@@ -221,9 +232,11 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
221
232
|
);
|
|
222
233
|
continue;
|
|
223
234
|
}
|
|
224
|
-
|
|
235
|
+
// ADR-0039: resolving — a template may inherit @responseRef via extends.
|
|
236
|
+
const responseRef = tmpl.attr(TEMPLATE_ATTR_RESPONSE_REF);
|
|
225
237
|
if (typeof responseRef === "string") {
|
|
226
|
-
|
|
238
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
239
|
+
const resVo = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, responseRef));
|
|
227
240
|
if (!resVo || resVo.subType !== OBJECT_SUBTYPE_VALUE) {
|
|
228
241
|
errors.push(
|
|
229
242
|
new ParseError(
|
|
@@ -236,7 +249,8 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
236
249
|
const fieldNames = new Set(
|
|
237
250
|
payload.children().filter((c) => c.type === TYPE_FIELD).map((f) => f.name),
|
|
238
251
|
);
|
|
239
|
-
|
|
252
|
+
// ADR-0039: resolving — a template may inherit @requiredSlots via extends.
|
|
253
|
+
const slots = tmpl.attr(TEMPLATE_ATTR_REQUIRED_SLOTS);
|
|
240
254
|
const slotList = Array.isArray(slots) ? slots : typeof slots === "string" ? [slots] : [];
|
|
241
255
|
for (const slot of slotList) {
|
|
242
256
|
if (typeof slot === "string" && !fieldNames.has(slot)) {
|
|
@@ -265,14 +279,16 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
|
|
|
265
279
|
|
|
266
280
|
export function validateFilterableHasIndex(root: MetaData): string[] {
|
|
267
281
|
const warnings: string[] = [];
|
|
268
|
-
|
|
282
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
283
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
269
284
|
// Use children() so inherited fields and identities (via extends:/super:)
|
|
270
285
|
// are included when checking filterable-without-index.
|
|
271
286
|
const effective = obj.children();
|
|
272
287
|
// Build the set of field names that are part of any identity on this object.
|
|
273
288
|
const indexedFieldNames = new Set<string>();
|
|
274
289
|
for (const identity of effective.filter((c) => c.type === TYPE_IDENTITY)) {
|
|
275
|
-
|
|
290
|
+
// ADR-0039: resolving — an identity may inherit @fields via extends.
|
|
291
|
+
const fields = identity.attr(IDENTITY_ATTR_FIELDS);
|
|
276
292
|
if (typeof fields === "string") {
|
|
277
293
|
for (const name of fields.split(",")) indexedFieldNames.add(name.trim());
|
|
278
294
|
} else if (Array.isArray(fields)) {
|
|
@@ -281,9 +297,11 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
|
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
for (const field of effective.filter((c) => c.type === TYPE_FIELD)) {
|
|
284
|
-
|
|
300
|
+
// ADR-0039: resolving — a concrete field may inherit @filterable via extends.
|
|
301
|
+
const filterable = field.attr(FIELD_ATTR_FILTERABLE);
|
|
285
302
|
if (filterable !== true) continue;
|
|
286
|
-
|
|
303
|
+
// ADR-0039: resolving — a concrete field may inherit @db.indexed via extends.
|
|
304
|
+
if (field.attr(FIELD_ATTR_DB_INDEXED) === true) continue;
|
|
287
305
|
if (indexedFieldNames.has(field.name)) continue;
|
|
288
306
|
warnings.push(
|
|
289
307
|
`[filterable-without-index] field "${obj.name}.${field.name}" has @filterable: true but is not ` +
|
|
@@ -306,10 +324,12 @@ export function validateFilterableHasIndex(root: MetaData): string[] {
|
|
|
306
324
|
|
|
307
325
|
export function validateFilterableHasSupportedOps(root: MetaData): ParseError[] {
|
|
308
326
|
const errors: ParseError[] = [];
|
|
309
|
-
|
|
327
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
328
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
310
329
|
// children() — inherited @filterable fields (via extends:/super:) are visible.
|
|
311
330
|
for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
|
|
312
|
-
|
|
331
|
+
// ADR-0039: resolving — a concrete field may inherit @filterable via extends.
|
|
332
|
+
if (field.attr(FIELD_ATTR_FILTERABLE) !== true) continue;
|
|
313
333
|
if (opsForSubType(field.subType).length > 0) continue;
|
|
314
334
|
errors.push(
|
|
315
335
|
new ParseError(
|
|
@@ -341,7 +361,8 @@ export function validateFilterableHasSupportedOps(root: MetaData): ParseError[]
|
|
|
341
361
|
function _findObject(root: MetaData, name: string): MetaData | undefined {
|
|
342
362
|
// FR-032 — origin heads are FQN-qualified after the desugar/sweep; match on
|
|
343
363
|
// the effective FQN resolution key (with bare back-compat).
|
|
344
|
-
|
|
364
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
365
|
+
return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
|
|
345
366
|
}
|
|
346
367
|
|
|
347
368
|
function _findField(obj: MetaData, name: string): MetaData | undefined {
|
|
@@ -497,7 +518,8 @@ function _validateViaPath(
|
|
|
497
518
|
);
|
|
498
519
|
return undefined;
|
|
499
520
|
}
|
|
500
|
-
|
|
521
|
+
// ADR-0039: resolving — a relationship may inherit @objectRef via extends.
|
|
522
|
+
const refTarget = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
|
|
501
523
|
if (typeof refTarget !== "string" || refTarget === "") {
|
|
502
524
|
errors.push(
|
|
503
525
|
new ParseError(
|
|
@@ -587,6 +609,8 @@ function _deriveBaseEntity(
|
|
|
587
609
|
|
|
588
610
|
// 1) The extended identity anchors the base entity (declared, not inferred).
|
|
589
611
|
// The anchor is the entity NAMED in the ref's owner part — see _refNamedOwner.
|
|
612
|
+
// ADR-0039: own — inspects THIS projection's OWN declared identities to read
|
|
613
|
+
// their extends refs (an inherited identity carries no local superRef to anchor).
|
|
590
614
|
for (const identity of obj.ownChildren().filter((c) => c.type === TYPE_IDENTITY)) {
|
|
591
615
|
const extended = identity.superResolved;
|
|
592
616
|
if (extended !== undefined && extended.type === TYPE_IDENTITY) {
|
|
@@ -600,6 +624,8 @@ function _deriveBaseEntity(
|
|
|
600
624
|
// 2) Fallback: the single distinct entity targeted by plain field-extends —
|
|
601
625
|
// again preferring the ref-named owner over the physical declaring ancestor.
|
|
602
626
|
const targets = new Set<MetaData>();
|
|
627
|
+
// ADR-0039: own — inspects THIS projection's OWN declared fields to read their
|
|
628
|
+
// extends refs (an inherited field carries no local superRef to anchor).
|
|
603
629
|
for (const f of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
604
630
|
const sup = f.superResolved;
|
|
605
631
|
if (sup === undefined) continue;
|
|
@@ -679,7 +705,8 @@ function _inferViaSingleHop(
|
|
|
679
705
|
.children()
|
|
680
706
|
.filter((c) => c.type === TYPE_RELATIONSHIP)
|
|
681
707
|
.filter((rel) => {
|
|
682
|
-
|
|
708
|
+
// ADR-0039: resolving — a relationship may inherit @objectRef via extends.
|
|
709
|
+
const ref = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
|
|
683
710
|
return typeof ref === "string" && stripPackage(ref) === targetEntity.name;
|
|
684
711
|
});
|
|
685
712
|
// FR5d — referrer is `<host-FQN>::<fieldName>`, target is the from/of ref
|
|
@@ -815,15 +842,19 @@ function _checkExtendsOriginAgreement(
|
|
|
815
842
|
|
|
816
843
|
export function validateOriginPaths(root: MetaData): ParseError[] {
|
|
817
844
|
const errors: ParseError[] = [];
|
|
818
|
-
|
|
845
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
846
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
819
847
|
// FR-024 B5: object.value hosts are EXEMPT from @via inference and
|
|
820
848
|
// cardinality checks — a value's origin.passthrough is FR-015 parameter
|
|
821
849
|
// lineage (values are constructed, never assembled; spec §7), not an
|
|
822
850
|
// assembly path. Their @from refs are still resolution-validated.
|
|
823
851
|
const isValueHost = obj.subType === OBJECT_SUBTYPE_VALUE;
|
|
852
|
+
// ADR-0039: own — origin validation operates on the OWN-declared field+origin
|
|
853
|
+
// layer (the established cross-port contract; origin.* never inherits, ADR-0029).
|
|
824
854
|
for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
825
855
|
for (const origin of field.ownChildren().filter((c) => c.type === TYPE_ORIGIN)) {
|
|
826
856
|
if (origin.subType === ORIGIN_SUBTYPE_PASSTHROUGH) {
|
|
857
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029).
|
|
827
858
|
const from = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
|
|
828
859
|
if (typeof from !== "string" || from === "") {
|
|
829
860
|
// Missing-attr (not a reference resolution failure) — keep the
|
|
@@ -842,6 +873,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
|
|
|
842
873
|
if (fromTarget !== undefined) {
|
|
843
874
|
_checkExtendsOriginAgreement(field, fromTarget.field, from, obj, origin.source, errors);
|
|
844
875
|
}
|
|
876
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029).
|
|
845
877
|
const via = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
|
|
846
878
|
if (typeof via === "string" && via !== "") {
|
|
847
879
|
const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
|
|
@@ -864,6 +896,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
|
|
|
864
896
|
}
|
|
865
897
|
}
|
|
866
898
|
} else if (origin.subType === ORIGIN_SUBTYPE_AGGREGATE) {
|
|
899
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029).
|
|
867
900
|
const of_ = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
|
|
868
901
|
if (typeof of_ !== "string" || of_ === "") {
|
|
869
902
|
errors.push(
|
|
@@ -878,6 +911,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
|
|
|
878
911
|
// an aggregate computes something new (count/sum/…); spec §4 defines
|
|
879
912
|
// agreement for passthrough only.
|
|
880
913
|
const ofTarget = _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
|
|
914
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029).
|
|
881
915
|
const via = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
|
|
882
916
|
if (typeof via === "string" && via !== "") {
|
|
883
917
|
const hops = _validateViaPath(via, root, obj, field.name, origin.source, errors);
|
|
@@ -953,14 +987,17 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
|
|
|
953
987
|
|
|
954
988
|
export function validateDerivedFieldProvidability(root: MetaData): ParseError[] {
|
|
955
989
|
const errors: ParseError[] = [];
|
|
990
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
956
991
|
for (const obj of root
|
|
957
|
-
.
|
|
992
|
+
.children()
|
|
958
993
|
.filter((c) => c.type === TYPE_OBJECT && c.subType === OBJECT_SUBTYPE_ENTITY)) {
|
|
959
994
|
const hasReadCapableSource = obj
|
|
960
995
|
.children()
|
|
961
996
|
.filter((c) => c.type === TYPE_SOURCE)
|
|
962
997
|
.some((s) => (s as MetaSource).isReadOnly());
|
|
963
998
|
if (hasReadCapableSource) continue;
|
|
999
|
+
// ADR-0039: own — origin-bearing fields are validated on the OWN-declared
|
|
1000
|
+
// layer (mirrors validateOriginPaths; origin.* never inherits, ADR-0029).
|
|
964
1001
|
for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
965
1002
|
if (!field.ownChildren().some((c) => c.type === TYPE_ORIGIN)) continue;
|
|
966
1003
|
errors.push(
|
|
@@ -994,9 +1031,14 @@ export function validateDerivedFieldProvidability(root: MetaData): ParseError[]
|
|
|
994
1031
|
|
|
995
1032
|
export function validateFieldObjectStorage(root: MetaData): ParseError[] {
|
|
996
1033
|
const errors: ParseError[] = [];
|
|
997
|
-
|
|
1034
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
1035
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
1036
|
+
// ADR-0039: own — the concrete field is an OWN child of this object; its
|
|
1037
|
+
// inheritable attrs (@objectRef/@storage) are read resolving below.
|
|
998
1038
|
for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
999
|
-
|
|
1039
|
+
// ADR-0039: resolving — a concrete field.object may inherit @objectRef from
|
|
1040
|
+
// an abstract base via extends; reading own-only would wrongly reject it.
|
|
1041
|
+
const objectRef = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
1000
1042
|
const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
|
|
1001
1043
|
|
|
1002
1044
|
if (field.subType === FIELD_SUBTYPE_OBJECT && !hasObjectRef) {
|
|
@@ -1011,9 +1053,10 @@ export function validateFieldObjectStorage(root: MetaData): ParseError[] {
|
|
|
1011
1053
|
continue;
|
|
1012
1054
|
}
|
|
1013
1055
|
|
|
1014
|
-
|
|
1056
|
+
// ADR-0039: resolving — @storage and array-ness may be inherited via extends.
|
|
1057
|
+
const storage = field.attr(FIELD_ATTR_STORAGE);
|
|
1015
1058
|
if (storage === undefined || storage === null) continue;
|
|
1016
|
-
if (storage === STORAGE_FLATTENED && field.
|
|
1059
|
+
if (storage === STORAGE_FLATTENED && field.resolvedIsArray()) {
|
|
1017
1060
|
errors.push(
|
|
1018
1061
|
new ParseError(
|
|
1019
1062
|
`field "${obj.name}.${field.name}" sets @storage "flattened" with isArray=true; flattened storage requires a single nested value`,
|
|
@@ -1053,13 +1096,17 @@ const _MAP_SCALAR_VALUE_SUBTYPES = new Set<string>([
|
|
|
1053
1096
|
|
|
1054
1097
|
export function validateFieldMap(root: MetaData): ParseError[] {
|
|
1055
1098
|
const errors: ParseError[] = [];
|
|
1056
|
-
|
|
1099
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
1100
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
1101
|
+
// ADR-0039: own — the concrete field is an OWN child; its inheritable attrs
|
|
1102
|
+
// (@valueType/@objectRef) are read resolving below.
|
|
1057
1103
|
for (const field of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
1058
1104
|
if (field.subType !== FIELD_SUBTYPE_MAP) continue;
|
|
1059
1105
|
|
|
1060
|
-
|
|
1106
|
+
// ADR-0039: resolving — @valueType / @objectRef may be inherited via extends.
|
|
1107
|
+
const valueType = field.attr(FIELD_ATTR_VALUE_TYPE);
|
|
1061
1108
|
const hasValueType = typeof valueType === "string" && valueType.length > 0;
|
|
1062
|
-
const objectRef = field.
|
|
1109
|
+
const objectRef = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
1063
1110
|
const hasObjectRef = typeof objectRef === "string" && objectRef.length > 0;
|
|
1064
1111
|
|
|
1065
1112
|
if (hasValueType === hasObjectRef) {
|
|
@@ -1160,6 +1207,8 @@ export function validateFieldDefaults(root: MetaData): ParseError[] {
|
|
|
1160
1207
|
function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
|
|
1161
1208
|
if (node.type === TYPE_FIELD && node.subType !== FIELD_SUBTYPE_ENUM) {
|
|
1162
1209
|
// Enum @default membership is validated by attr-schema-validate Check 5.
|
|
1210
|
+
// ADR-0039: own — validates the @default declared on THIS node (matches the
|
|
1211
|
+
// @values / FR-011 own-attr passes; an inherited default was gated on its parent).
|
|
1163
1212
|
const raw = node.ownAttr(FIELD_ATTR_DEFAULT);
|
|
1164
1213
|
if (raw !== undefined && raw !== null && !Array.isArray(raw) && typeof raw !== "object") {
|
|
1165
1214
|
const def = _stringifyDefault(raw as string | number | boolean);
|
|
@@ -1179,6 +1228,7 @@ function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
|
|
|
1179
1228
|
}
|
|
1180
1229
|
}
|
|
1181
1230
|
}
|
|
1231
|
+
// ADR-0039: own — structural walk visiting every physical node once at its site.
|
|
1182
1232
|
for (const child of node.ownChildren()) _walkFieldDefaults(child, errors);
|
|
1183
1233
|
}
|
|
1184
1234
|
|
|
@@ -1193,18 +1243,21 @@ function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
|
|
|
1193
1243
|
|
|
1194
1244
|
export function validateDataGridFilterValues(root: MetaData): ParseError[] {
|
|
1195
1245
|
const errors: ParseError[] = [];
|
|
1196
|
-
|
|
1246
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
1247
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
1197
1248
|
const effective = obj.children();
|
|
1198
1249
|
const allow = new Map<string, readonly string[]>();
|
|
1199
1250
|
for (const f of effective.filter((c) => c.type === TYPE_FIELD)) {
|
|
1200
|
-
|
|
1251
|
+
// ADR-0039: resolving — a concrete field may inherit @filterable via extends.
|
|
1252
|
+
if (f.attr(FIELD_ATTR_FILTERABLE) === true) {
|
|
1201
1253
|
allow.set(f.name, opsForSubType(f.subType));
|
|
1202
1254
|
}
|
|
1203
1255
|
}
|
|
1204
1256
|
for (const layout of effective.filter(
|
|
1205
1257
|
(c) => c.type === TYPE_LAYOUT && c.subType === LAYOUT_SUBTYPE_DATA_GRID,
|
|
1206
1258
|
)) {
|
|
1207
|
-
|
|
1259
|
+
// ADR-0039: resolving — a layout may inherit @filter via extends.
|
|
1260
|
+
const filter = layout.attr(LAYOUT_DATA_GRID_ATTR_FILTER);
|
|
1208
1261
|
// Type errors (e.g. legacy string form) are reported by validateAttrSchema.
|
|
1209
1262
|
if (typeof filter !== "object" || filter === null || Array.isArray(filter)) continue;
|
|
1210
1263
|
checkFilterClauses(filter as Record<string, unknown>, allow, obj.name, layout.name, layout.source, errors);
|
|
@@ -1257,13 +1310,18 @@ function _countJunctionReferences(junction: MetaData): number {
|
|
|
1257
1310
|
|
|
1258
1311
|
export function validateRelationships(root: MetaData): ParseError[] {
|
|
1259
1312
|
const errors: ParseError[] = [];
|
|
1260
|
-
|
|
1313
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
1314
|
+
for (const obj of root.children().filter((c) => c.type === TYPE_OBJECT)) {
|
|
1315
|
+
// ADR-0039: own — a relationship is validated on the entity that DECLARES it
|
|
1316
|
+
// (the M:N slim-vocabulary rules apply to own-declared relationships; its
|
|
1317
|
+
// inheritable attrs are read resolving below).
|
|
1261
1318
|
for (const rel of obj.ownChildren().filter((c) => c.type === TYPE_RELATIONSHIP)) {
|
|
1262
|
-
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1319
|
+
// ADR-0039: resolving — a relationship may inherit its M:N attrs via extends.
|
|
1320
|
+
const through = rel.attr(RELATIONSHIP_ATTR_THROUGH);
|
|
1321
|
+
const sourceRefField = rel.attr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
|
|
1322
|
+
const symmetric = rel.attr(RELATIONSHIP_ATTR_SYMMETRIC) === true;
|
|
1323
|
+
const cardinality = rel.attr(RELATIONSHIP_ATTR_CARDINALITY);
|
|
1324
|
+
const objectRef = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
|
|
1267
1325
|
|
|
1268
1326
|
const hasThrough = typeof through === "string" && through !== "";
|
|
1269
1327
|
const hasSourceRefField = typeof sourceRefField === "string" && sourceRefField !== "";
|
|
@@ -19,7 +19,8 @@ class SymbolTableImpl implements SymbolTable {
|
|
|
19
19
|
|
|
20
20
|
static build(root: MetaData): SymbolTableImpl {
|
|
21
21
|
const t = new SymbolTableImpl();
|
|
22
|
-
|
|
22
|
+
// ADR-0039: root has no super; children()==ownChildren() but resolving is the default.
|
|
23
|
+
for (const child of root.children()) {
|
|
23
24
|
if (child.type !== TYPE_OBJECT) continue;
|
|
24
25
|
if (child.name) t.byRef.set(child.name, child);
|
|
25
26
|
t.byRef.set(child.fqn(), child);
|
|
@@ -60,7 +61,9 @@ export function runRegisteredValidation(root: MetaData, registry: TypeRegistry):
|
|
|
60
61
|
const def = registry.find(node.type, node.subType);
|
|
61
62
|
if (def) {
|
|
62
63
|
for (const desc of def.references ?? []) {
|
|
63
|
-
|
|
64
|
+
// ADR-0039: resolving — a reference attr (e.g. @objectRef/@through) may be
|
|
65
|
+
// inherited via extends; read the effective value.
|
|
66
|
+
const raw = node.attr(desc.attr);
|
|
64
67
|
if (typeof raw !== "string" || raw === "") continue; // absence is the required-attr pass's job
|
|
65
68
|
const entityRef = desc.dottedFieldPath ? (raw.split(".")[0] ?? raw) : raw;
|
|
66
69
|
const target = ctx.symbols.resolveObject(entityRef);
|
|
@@ -88,6 +91,8 @@ export function runRegisteredValidation(root: MetaData, registry: TypeRegistry):
|
|
|
88
91
|
}
|
|
89
92
|
def.validate?.(node, ctx);
|
|
90
93
|
}
|
|
94
|
+
// ADR-0039: own — structural walk visiting every physical node once at its
|
|
95
|
+
// declaration site (an inherited child is validated on its declaring parent).
|
|
91
96
|
for (const child of node.ownChildren()) walk(child);
|
|
92
97
|
}
|
|
93
98
|
}
|
package/src/naming.ts
CHANGED
|
@@ -82,7 +82,8 @@ export function resolveColumnName(
|
|
|
82
82
|
field: MetaData,
|
|
83
83
|
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
84
84
|
): string {
|
|
85
|
-
|
|
85
|
+
// ADR-0039: resolving — a concrete field may inherit @column via extends.
|
|
86
|
+
const col = field.attr(FIELD_ATTR_COLUMN);
|
|
86
87
|
if (typeof col === "string" && col) return col;
|
|
87
88
|
return applyColumnNamingStrategy(field.name, strategy);
|
|
88
89
|
}
|
|
@@ -95,12 +96,14 @@ export function resolveColumnName(
|
|
|
95
96
|
* SQLite treats it as the only allowed value (no schema concept).
|
|
96
97
|
*/
|
|
97
98
|
export function resolveTableSchema(entity: MetaData): string | undefined {
|
|
98
|
-
|
|
99
|
+
// ADR-0039: resolving — a concrete entity may inherit its source.rdb via extends.
|
|
100
|
+
const source = entity.children().find(
|
|
99
101
|
(c): c is MetaSource =>
|
|
100
102
|
c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY,
|
|
101
103
|
);
|
|
102
104
|
if (!source) return undefined;
|
|
103
|
-
|
|
105
|
+
// ADR-0039: resolving — an inherited source's @schema lives on the super node.
|
|
106
|
+
const schema = source.attr(SOURCE_ATTR_SCHEMA);
|
|
104
107
|
if (typeof schema === "string" && schema !== "") return schema;
|
|
105
108
|
return undefined;
|
|
106
109
|
}
|
package/src/object-serializer.ts
CHANGED
|
@@ -41,7 +41,8 @@ export function objectToJson(
|
|
|
41
41
|
for (const field of mo.fields()) {
|
|
42
42
|
const raw = instance[field.name];
|
|
43
43
|
if (raw === undefined) continue; // absent field → omit
|
|
44
|
-
|
|
44
|
+
// ADR-0039: resolving — array-ness may be inherited via extends.
|
|
45
|
+
out[field.name] = field.resolvedIsArray()
|
|
45
46
|
? mapArray(raw, (el) => toJsonValue(field, el, mo))
|
|
46
47
|
: toJsonValue(field, raw, mo);
|
|
47
48
|
}
|
|
@@ -57,7 +58,8 @@ export function jsonToObject(
|
|
|
57
58
|
for (const field of mo.fields()) {
|
|
58
59
|
const raw = json[field.name];
|
|
59
60
|
if (raw === undefined) continue;
|
|
60
|
-
|
|
61
|
+
// ADR-0039: resolving — array-ness may be inherited via extends.
|
|
62
|
+
out[field.name] = field.resolvedIsArray()
|
|
61
63
|
? mapArray(raw, (el) => fromJsonValue(field, el, mo))
|
|
62
64
|
: fromJsonValue(field, raw, mo);
|
|
63
65
|
}
|
|
@@ -147,6 +149,8 @@ function coerceNumeric(raw: unknown): unknown {
|
|
|
147
149
|
function resolveObjectRef(field: MetaField, mo: MetaObject): MetaObject | undefined {
|
|
148
150
|
const ref = field.objectRef;
|
|
149
151
|
if (ref === undefined) return undefined;
|
|
152
|
+
// ADR-0039: own — lookup is on the tree ROOT, which has no super chain
|
|
153
|
+
// (it never `extends`), so its own children ARE its effective children.
|
|
150
154
|
const found: MetaData | undefined = mo.root().ownChildByTypeAndName(TYPE_OBJECT, ref);
|
|
151
155
|
if (found === undefined) return undefined;
|
|
152
156
|
return found as MetaObject;
|
package/src/parser-core.ts
CHANGED
|
@@ -672,6 +672,8 @@ function parseNodeInto(
|
|
|
672
672
|
// Snapshot of own attrs (name → value) — used to detect ERR_MERGE_CONFLICT
|
|
673
673
|
// when a new contribution sets an attr the target already declared to a
|
|
674
674
|
// different non-empty value.
|
|
675
|
+
// ADR-0039: own — overlay/merge machinery, operating on the AUTHORED
|
|
676
|
+
// declaration layer (pre super-resolution).
|
|
675
677
|
preMergeAttrSnapshot = new Map(target.ownAttrs());
|
|
676
678
|
}
|
|
677
679
|
|
|
@@ -897,6 +899,8 @@ function createOrFindMetaData(
|
|
|
897
899
|
|
|
898
900
|
// Look up an existing child with (type, name). Skip unnamed nodes — they
|
|
899
901
|
// are always distinct (e.g. inline validators, anonymous attrs).
|
|
902
|
+
// ADR-0039: own — overlay/merge lookup on the AUTHORED declaration layer
|
|
903
|
+
// (an overlay targets a same-file/same-node own child, pre super-resolution).
|
|
900
904
|
const existing = name !== "" ? parent.ownChildByTypeAndName(type, name) : undefined;
|
|
901
905
|
|
|
902
906
|
if (isOverlayNode) {
|
|
@@ -1245,6 +1249,7 @@ function processChildren(
|
|
|
1245
1249
|
childNodePath,
|
|
1246
1250
|
);
|
|
1247
1251
|
|
|
1252
|
+
// ADR-0039: own — tree-building dedup on the AUTHORED declaration layer.
|
|
1248
1253
|
if (childModel !== undefined && !parent.ownChildren().includes(childModel)) {
|
|
1249
1254
|
parent.addChild(childModel);
|
|
1250
1255
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
FIELD_SUBTYPE_STRING,
|
|
5
|
-
FIELD_SUBTYPE_TIMESTAMP,
|
|
6
5
|
} from "../../core/field/field-constants.js";
|
|
7
6
|
|
|
8
7
|
/** Column name override on a field (maps to @column in metadata). */
|
|
@@ -38,20 +37,29 @@ export const FIELD_ATTR_DB_COLUMN_TYPE = "dbColumnType";
|
|
|
38
37
|
export const DB_COLUMN_TYPE_UUID = "uuid";
|
|
39
38
|
/** `@dbColumnType: jsonb` — genuinely-open `jsonb` column (legal on field.string). */
|
|
40
39
|
export const DB_COLUMN_TYPE_JSONB = "jsonb";
|
|
41
|
-
/** `@dbColumnType: timestamp_with_tz` — `timestamp with time zone` (legal on field.timestamp). */
|
|
42
|
-
export const DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ = "timestamp_with_tz";
|
|
43
|
-
/** `@dbColumnType: uuid_array` — native Postgres `uuid[]` column (legal on field.string). */
|
|
44
|
-
export const DB_COLUMN_TYPE_UUID_ARRAY = "uuid_array";
|
|
45
|
-
/** `@dbColumnType: text_array` — native Postgres `text[]` column (legal on field.string). */
|
|
46
|
-
export const DB_COLUMN_TYPE_TEXT_ARRAY = "text_array";
|
|
47
40
|
|
|
48
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* ADR-0036 Wave 2: `@localTime` — a boolean flag on `field.timestamp` marking the
|
|
43
|
+
* rare naive / wall-clock case. When true the column is Postgres `timestamp
|
|
44
|
+
* without time zone`; absent/false (the default) is an absolute instant →
|
|
45
|
+
* `timestamptz`. This replaces the retired `@dbColumnType: timestamp_with_tz`
|
|
46
|
+
* escape hatch — timezone-awareness now lives in the logical field type
|
|
47
|
+
* (instant-by-default) + this opt-out, not in a physical column-type string.
|
|
48
|
+
*/
|
|
49
|
+
export const FIELD_ATTR_LOCAL_TIME = "localTime";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The closed set of legal `@dbColumnType` values (raw-dialect passthrough deferred).
|
|
53
|
+
* dbColumnType slim-and-derive (ADR-0036 Wave 1, decision 4): the native-array
|
|
54
|
+
* overrides `uuid_array`/`text_array` are fully removed — native `uuid[]`/`text[]`
|
|
55
|
+
* are DERIVED from a field subtype + `isArray`, never declared via an escape hatch.
|
|
56
|
+
* Wave 2 (decision 1) retires `timestamp_with_tz` entirely — tz-awareness moves to
|
|
57
|
+
* `field.timestamp` (instant by default) + `@localTime` (the naive opt-out). The
|
|
58
|
+
* legal set is now just `{ uuid, jsonb }`, both on field.string.
|
|
59
|
+
*/
|
|
49
60
|
export const DB_COLUMN_TYPE_VALUES = [
|
|
50
61
|
DB_COLUMN_TYPE_UUID,
|
|
51
62
|
DB_COLUMN_TYPE_JSONB,
|
|
52
|
-
DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ,
|
|
53
|
-
DB_COLUMN_TYPE_UUID_ARRAY,
|
|
54
|
-
DB_COLUMN_TYPE_TEXT_ARRAY,
|
|
55
63
|
] as const;
|
|
56
64
|
export type DbColumnTypeValue = (typeof DB_COLUMN_TYPE_VALUES)[number];
|
|
57
65
|
|
|
@@ -59,12 +67,9 @@ export type DbColumnTypeValue = (typeof DB_COLUMN_TYPE_VALUES)[number];
|
|
|
59
67
|
* Legal `@dbColumnType` value → the field subtypes it may be applied to (ADR-0013,
|
|
60
68
|
* R6 Plan 2b). Any other (subtype × value) pairing — or an unrecognized value — is
|
|
61
69
|
* an ERR_BAD_ATTR_VALUE. Keyed by value; the value-set is enforced by membership in
|
|
62
|
-
* this map. Subtype names are the bare FIELD_SUBTYPE_* constants (string
|
|
70
|
+
* this map. Subtype names are the bare FIELD_SUBTYPE_* constants (string).
|
|
63
71
|
*/
|
|
64
72
|
export const DB_COLUMN_TYPE_LEGAL_SUBTYPES: Readonly<Record<DbColumnTypeValue, readonly string[]>> = {
|
|
65
73
|
[DB_COLUMN_TYPE_UUID]: [FIELD_SUBTYPE_STRING],
|
|
66
74
|
[DB_COLUMN_TYPE_JSONB]: [FIELD_SUBTYPE_STRING],
|
|
67
|
-
[DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ]: [FIELD_SUBTYPE_TIMESTAMP],
|
|
68
|
-
[DB_COLUMN_TYPE_UUID_ARRAY]: [FIELD_SUBTYPE_STRING],
|
|
69
|
-
[DB_COLUMN_TYPE_TEXT_ARRAY]: [FIELD_SUBTYPE_STRING],
|
|
70
75
|
} as const;
|
|
@@ -35,7 +35,25 @@ export const DB_DEFINITION: ProviderDefinition = {
|
|
|
35
35
|
"name": "dbColumnType",
|
|
36
36
|
"min": 0,
|
|
37
37
|
"max": 1,
|
|
38
|
-
"
|
|
38
|
+
"allowedValues": [
|
|
39
|
+
"uuid",
|
|
40
|
+
"jsonb"
|
|
41
|
+
],
|
|
42
|
+
"description": "Physical DB column-type override (ADR-0013 escape hatch). Legal values are uuid | jsonb, both on field.string (uuid = native Postgres uuid column over a string-typed field; jsonb = genuinely-open JSON column). The logical field type and its native binding are unchanged. Native SQL arrays (uuid[]/text[]) are NOT declared here — they are derived from a field subtype + isArray (ADR-0036 Wave 1). The retired timestamp_with_tz value is gone — timezone-awareness lives in field.timestamp (instant by default) + @localTime (the naive opt-out), per ADR-0036 Wave 2."
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "field",
|
|
48
|
+
"subType": "timestamp",
|
|
49
|
+
"children": [
|
|
50
|
+
{
|
|
51
|
+
"type": "attr",
|
|
52
|
+
"subType": "boolean",
|
|
53
|
+
"name": "localTime",
|
|
54
|
+
"min": 0,
|
|
55
|
+
"max": 1,
|
|
56
|
+
"description": "When true, the timestamp is a naive wall-clock value with no timezone (Postgres `timestamp without time zone`); absent/false (the default) = an absolute instant (`timestamptz`). ADR-0036 Wave 2 — replaces the retired @dbColumnType: timestamp_with_tz escape hatch."
|
|
39
57
|
}
|
|
40
58
|
]
|
|
41
59
|
},
|
|
@@ -30,12 +30,14 @@ export class MetaOrigin extends MetaData {}
|
|
|
30
30
|
export class MetaPassthroughOrigin extends MetaOrigin {
|
|
31
31
|
/** The dotted-path cross-entity field reference this origin passes through (e.g. "Program.title"). */
|
|
32
32
|
get from(): string | undefined {
|
|
33
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
33
34
|
const v = this.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
|
|
34
35
|
return typeof v === "string" ? v : undefined;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/** Optional dotted relationship path used to reach the source entity (e.g. "Program.weeks"). */
|
|
38
39
|
get via(): string | undefined {
|
|
40
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
39
41
|
const v = this.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
|
|
40
42
|
return typeof v === "string" ? v : undefined;
|
|
41
43
|
}
|
|
@@ -50,18 +52,21 @@ export class MetaPassthroughOrigin extends MetaOrigin {
|
|
|
50
52
|
export class MetaAggregateOrigin extends MetaOrigin {
|
|
51
53
|
/** The aggregate function (count | sum | avg | min | max). */
|
|
52
54
|
get agg(): AggregateFunction | undefined {
|
|
55
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
53
56
|
const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_AGG);
|
|
54
57
|
return typeof v === "string" ? (v as AggregateFunction) : undefined;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/** The dotted-path target of the aggregate (e.g. "Week.id"). */
|
|
58
61
|
get of(): string | undefined {
|
|
62
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
59
63
|
const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
|
|
60
64
|
return typeof v === "string" ? v : undefined;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/** The dotted relationship path the aggregate walks (e.g. "Program.weeks"). */
|
|
64
68
|
get via(): string | undefined {
|
|
69
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
65
70
|
const v = this.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
|
|
66
71
|
return typeof v === "string" ? v : undefined;
|
|
67
72
|
}
|
|
@@ -76,6 +81,7 @@ export class MetaAggregateOrigin extends MetaOrigin {
|
|
|
76
81
|
export class MetaCollectionOrigin extends MetaOrigin {
|
|
77
82
|
/** The dotted relationship path (or wildcard selector) this collection is sourced from. */
|
|
78
83
|
get via(): string | undefined {
|
|
84
|
+
// ADR-0039: own — origin.* never inherits (ADR-0029), so own is correct.
|
|
79
85
|
const v = this.ownAttr(ORIGIN_COLLECTION_ATTR_VIA);
|
|
80
86
|
return typeof v === "string" ? v : undefined;
|
|
81
87
|
}
|