@metaobjectsdev/metadata 0.8.1 → 0.9.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.
Files changed (220) hide show
  1. package/dist/attr-schema-validate.d.ts.map +1 -1
  2. package/dist/attr-schema-validate.js +49 -1
  3. package/dist/attr-schema-validate.js.map +1 -1
  4. package/dist/core/field/field-constants.d.ts +27 -2
  5. package/dist/core/field/field-constants.d.ts.map +1 -1
  6. package/dist/core/field/field-constants.js +30 -2
  7. package/dist/core/field/field-constants.js.map +1 -1
  8. package/dist/core/field/field-schema.d.ts +9 -1
  9. package/dist/core/field/field-schema.d.ts.map +1 -1
  10. package/dist/core/field/field-schema.js +34 -3
  11. package/dist/core/field/field-schema.js.map +1 -1
  12. package/dist/core/field/meta-field.d.ts +10 -0
  13. package/dist/core/field/meta-field.d.ts.map +1 -1
  14. package/dist/core/field/meta-field.js +36 -1
  15. package/dist/core/field/meta-field.js.map +1 -1
  16. package/dist/core/field/validate-field-readonly.d.ts +9 -0
  17. package/dist/core/field/validate-field-readonly.d.ts.map +1 -0
  18. package/dist/core/field/validate-field-readonly.js +116 -0
  19. package/dist/core/field/validate-field-readonly.js.map +1 -0
  20. package/dist/core/object/meta-object-aware.d.ts +11 -0
  21. package/dist/core/object/meta-object-aware.d.ts.map +1 -0
  22. package/dist/core/object/meta-object-aware.js +15 -0
  23. package/dist/core/object/meta-object-aware.js.map +1 -0
  24. package/dist/core/object/meta-object.d.ts +21 -0
  25. package/dist/core/object/meta-object.d.ts.map +1 -1
  26. package/dist/core/object/meta-object.js +43 -2
  27. package/dist/core/object/meta-object.js.map +1 -1
  28. package/dist/core/object/object-class-registry.d.ts +22 -0
  29. package/dist/core/object/object-class-registry.d.ts.map +1 -0
  30. package/dist/core/object/object-class-registry.js +38 -0
  31. package/dist/core/object/object-class-registry.js.map +1 -0
  32. package/dist/core/object/object-constants.d.ts +7 -0
  33. package/dist/core/object/object-constants.d.ts.map +1 -1
  34. package/dist/core/object/object-constants.js +14 -0
  35. package/dist/core/object/object-constants.js.map +1 -1
  36. package/dist/core/object/object-schema.d.ts.map +1 -1
  37. package/dist/core/object/object-schema.js +24 -1
  38. package/dist/core/object/object-schema.js.map +1 -1
  39. package/dist/core/object/validate-discriminator.d.ts +4 -0
  40. package/dist/core/object/validate-discriminator.d.ts.map +1 -0
  41. package/dist/core/object/validate-discriminator.js +145 -0
  42. package/dist/core/object/validate-discriminator.js.map +1 -0
  43. package/dist/core/object/value-object.d.ts +23 -0
  44. package/dist/core/object/value-object.d.ts.map +1 -0
  45. package/dist/core/object/value-object.js +51 -0
  46. package/dist/core/object/value-object.js.map +1 -0
  47. package/dist/core/query/query-constants.d.ts.map +1 -1
  48. package/dist/core/query/query-constants.js +4 -0
  49. package/dist/core/query/query-constants.js.map +1 -1
  50. package/dist/core-types.d.ts.map +1 -1
  51. package/dist/core-types.js +16 -4
  52. package/dist/core-types.js.map +1 -1
  53. package/dist/errors.d.ts +2 -2
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +23 -0
  56. package/dist/errors.js.map +1 -1
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  62. package/dist/loader/meta-data-loader.js +26 -1
  63. package/dist/loader/meta-data-loader.js.map +1 -1
  64. package/dist/loader/validation-passes.d.ts +1 -0
  65. package/dist/loader/validation-passes.d.ts.map +1 -1
  66. package/dist/loader/validation-passes.js +122 -2
  67. package/dist/loader/validation-passes.js.map +1 -1
  68. package/dist/naming.d.ts.map +1 -1
  69. package/dist/naming.js +7 -6
  70. package/dist/naming.js.map +1 -1
  71. package/dist/persistence/db/db-constants.d.ts +23 -0
  72. package/dist/persistence/db/db-constants.d.ts.map +1 -1
  73. package/dist/persistence/db/db-constants.js +31 -0
  74. package/dist/persistence/db/db-constants.js.map +1 -1
  75. package/dist/persistence/db/db-provider.js +3 -3
  76. package/dist/persistence/db/db-provider.js.map +1 -1
  77. package/dist/persistence/db/db-schema.d.ts +8 -0
  78. package/dist/persistence/db/db-schema.d.ts.map +1 -1
  79. package/dist/persistence/db/db-schema.js +17 -1
  80. package/dist/persistence/db/db-schema.js.map +1 -1
  81. package/dist/persistence/source/meta-source.d.ts +15 -1
  82. package/dist/persistence/source/meta-source.d.ts.map +1 -1
  83. package/dist/persistence/source/meta-source.js +55 -3
  84. package/dist/persistence/source/meta-source.js.map +1 -1
  85. package/dist/persistence/source/source-constants.d.ts +20 -1
  86. package/dist/persistence/source/source-constants.d.ts.map +1 -1
  87. package/dist/persistence/source/source-constants.js +36 -1
  88. package/dist/persistence/source/source-constants.js.map +1 -1
  89. package/dist/persistence/source/source-schema.d.ts.map +1 -1
  90. package/dist/persistence/source/source-schema.js +65 -4
  91. package/dist/persistence/source/source-schema.js.map +1 -1
  92. package/dist/persistence/source/validate-source-parameter-ref.d.ts +4 -0
  93. package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -0
  94. package/dist/persistence/source/validate-source-parameter-ref.js +96 -0
  95. package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -0
  96. package/dist/persistence/source/validate-source-physical-names.d.ts +9 -0
  97. package/dist/persistence/source/validate-source-physical-names.d.ts.map +1 -0
  98. package/dist/persistence/source/validate-source-physical-names.js +79 -0
  99. package/dist/persistence/source/validate-source-physical-names.js.map +1 -0
  100. package/dist/serializer-json.d.ts.map +1 -1
  101. package/dist/serializer-json.js +43 -0
  102. package/dist/serializer-json.js.map +1 -1
  103. package/dist/template/template-constants.d.ts +9 -0
  104. package/dist/template/template-constants.d.ts.map +1 -1
  105. package/dist/template/template-constants.js +20 -0
  106. package/dist/template/template-constants.js.map +1 -1
  107. package/dist/template/template-schema.d.ts.map +1 -1
  108. package/dist/template/template-schema.js +42 -3
  109. package/dist/template/template-schema.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/attr-schema-validate.ts +70 -0
  112. package/src/core/field/field-constants.ts +37 -2
  113. package/src/core/field/field-schema.ts +43 -2
  114. package/src/core/field/meta-field.ts +39 -0
  115. package/src/core/field/validate-field-readonly.ts +142 -0
  116. package/src/core/object/meta-object-aware.ts +27 -0
  117. package/src/core/object/meta-object.ts +47 -2
  118. package/src/core/object/object-class-registry.ts +48 -0
  119. package/src/core/object/object-constants.ts +17 -0
  120. package/src/core/object/object-schema.ts +29 -1
  121. package/src/core/object/validate-discriminator.ts +195 -0
  122. package/src/core/object/value-object.ts +65 -0
  123. package/src/core/query/query-constants.ts +5 -0
  124. package/src/core-types.ts +17 -4
  125. package/src/errors.ts +23 -0
  126. package/src/index.ts +10 -0
  127. package/src/loader/meta-data-loader.ts +31 -1
  128. package/src/loader/validation-passes.ts +161 -0
  129. package/src/naming.ts +6 -5
  130. package/src/persistence/db/db-constants.ts +40 -0
  131. package/src/persistence/db/db-provider.ts +3 -3
  132. package/src/persistence/db/db-schema.ts +20 -0
  133. package/src/persistence/source/meta-source.ts +64 -2
  134. package/src/persistence/source/source-constants.ts +40 -1
  135. package/src/persistence/source/source-schema.ts +78 -3
  136. package/src/persistence/source/validate-source-parameter-ref.ts +143 -0
  137. package/src/persistence/source/validate-source-physical-names.ts +123 -0
  138. package/src/serializer-json.ts +50 -0
  139. package/src/template/template-constants.ts +23 -0
  140. package/src/template/template-schema.ts +49 -2
  141. package/dist/constants.d.ts +0 -208
  142. package/dist/constants.d.ts.map +0 -1
  143. package/dist/constants.js +0 -419
  144. package/dist/constants.js.map +0 -1
  145. package/dist/core/file-meta-data-loader.d.ts +0 -18
  146. package/dist/core/file-meta-data-loader.d.ts.map +0 -1
  147. package/dist/core/file-meta-data-loader.js +0 -81
  148. package/dist/core/file-meta-data-loader.js.map +0 -1
  149. package/dist/core/file-source.d.ts +0 -12
  150. package/dist/core/file-source.d.ts.map +0 -1
  151. package/dist/core/file-source.js +0 -46
  152. package/dist/core/file-source.js.map +0 -1
  153. package/dist/core-attr-schemas.d.ts +0 -22
  154. package/dist/core-attr-schemas.d.ts.map +0 -1
  155. package/dist/core-attr-schemas.js +0 -324
  156. package/dist/core-attr-schemas.js.map +0 -1
  157. package/dist/db/db-attr-schemas.d.ts +0 -8
  158. package/dist/db/db-attr-schemas.d.ts.map +0 -1
  159. package/dist/db/db-attr-schemas.js +0 -26
  160. package/dist/db/db-attr-schemas.js.map +0 -1
  161. package/dist/db/db-provider.d.ts +0 -3
  162. package/dist/db/db-provider.d.ts.map +0 -1
  163. package/dist/db/db-provider.js +0 -28
  164. package/dist/db/db-provider.js.map +0 -1
  165. package/dist/meta/find-reference.d.ts +0 -22
  166. package/dist/meta/find-reference.d.ts.map +0 -1
  167. package/dist/meta/find-reference.js +0 -29
  168. package/dist/meta/find-reference.js.map +0 -1
  169. package/dist/meta/meta-attr.d.ts +0 -8
  170. package/dist/meta/meta-attr.d.ts.map +0 -1
  171. package/dist/meta/meta-attr.js +0 -17
  172. package/dist/meta/meta-attr.js.map +0 -1
  173. package/dist/meta/meta-data.d.ts +0 -107
  174. package/dist/meta/meta-data.d.ts.map +0 -1
  175. package/dist/meta/meta-data.js +0 -302
  176. package/dist/meta/meta-data.js.map +0 -1
  177. package/dist/meta/meta-field.d.ts +0 -48
  178. package/dist/meta/meta-field.d.ts.map +0 -1
  179. package/dist/meta/meta-field.js +0 -94
  180. package/dist/meta/meta-field.js.map +0 -1
  181. package/dist/meta/meta-identity.d.ts +0 -71
  182. package/dist/meta/meta-identity.d.ts.map +0 -1
  183. package/dist/meta/meta-identity.js +0 -129
  184. package/dist/meta/meta-identity.js.map +0 -1
  185. package/dist/meta/meta-layout.d.ts +0 -23
  186. package/dist/meta/meta-layout.d.ts.map +0 -1
  187. package/dist/meta/meta-layout.js +0 -45
  188. package/dist/meta/meta-layout.js.map +0 -1
  189. package/dist/meta/meta-object.d.ts +0 -40
  190. package/dist/meta/meta-object.d.ts.map +0 -1
  191. package/dist/meta/meta-object.js +0 -81
  192. package/dist/meta/meta-object.js.map +0 -1
  193. package/dist/meta/meta-origin.d.ts +0 -32
  194. package/dist/meta/meta-origin.d.ts.map +0 -1
  195. package/dist/meta/meta-origin.js +0 -55
  196. package/dist/meta/meta-origin.js.map +0 -1
  197. package/dist/meta/meta-relationship.d.ts +0 -11
  198. package/dist/meta/meta-relationship.d.ts.map +0 -1
  199. package/dist/meta/meta-relationship.js +0 -27
  200. package/dist/meta/meta-relationship.js.map +0 -1
  201. package/dist/meta/meta-root.d.ts +0 -12
  202. package/dist/meta/meta-root.d.ts.map +0 -1
  203. package/dist/meta/meta-root.js +0 -24
  204. package/dist/meta/meta-root.js.map +0 -1
  205. package/dist/meta/meta-source.d.ts +0 -18
  206. package/dist/meta/meta-source.d.ts.map +0 -1
  207. package/dist/meta/meta-source.js +0 -31
  208. package/dist/meta/meta-source.js.map +0 -1
  209. package/dist/meta/meta-validator.d.ts +0 -29
  210. package/dist/meta/meta-validator.d.ts.map +0 -1
  211. package/dist/meta/meta-validator.js +0 -49
  212. package/dist/meta/meta-validator.js.map +0 -1
  213. package/dist/meta/meta-view.d.ts +0 -4
  214. package/dist/meta/meta-view.d.ts.map +0 -1
  215. package/dist/meta/meta-view.js +0 -8
  216. package/dist/meta/meta-view.js.map +0 -1
  217. package/dist/persistence/db/db-attr-schemas.d.ts +0 -8
  218. package/dist/persistence/db/db-attr-schemas.d.ts.map +0 -1
  219. package/dist/persistence/db/db-attr-schemas.js +0 -28
  220. package/dist/persistence/db/db-attr-schemas.js.map +0 -1
@@ -0,0 +1,195 @@
1
+ // Validation pass: TPH discriminator cross-attribute rules (FR-014).
2
+ //
3
+ // Codes (all errors):
4
+ // ERR_DISCRIMINATOR_FIELD_NOT_FOUND — @discriminator names a field that
5
+ // does not exist on the entity (own
6
+ // or via extends chain).
7
+ // ERR_DISCRIMINATOR_VALUE_DUPLICATE — two subtypes of the same
8
+ // @discriminator-bearing root claim
9
+ // the same @discriminatorValue.
10
+ // ERR_DISCRIMINATOR_VALUE_MISSING — a concrete (non-abstract) entity
11
+ // extends a chain whose root carries
12
+ // @discriminator but the subtype lacks
13
+ // @discriminatorValue.
14
+ // ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH — @discriminatorValue cannot be
15
+ // coerced to the discriminator field's
16
+ // subtype (enum: not in @values;
17
+ // int: not numeric; string: always OK).
18
+
19
+ import type { MetaData } from "../../shared/meta-data.js";
20
+ import { ParseError } from "../../errors.js";
21
+ import { TYPE_OBJECT, TYPE_FIELD } from "../../shared/base-types.js";
22
+ import {
23
+ OBJECT_ATTR_DISCRIMINATOR,
24
+ OBJECT_ATTR_DISCRIMINATOR_VALUE,
25
+ OBJECT_SUBTYPE_ENTITY,
26
+ } from "./object-constants.js";
27
+ import {
28
+ FIELD_SUBTYPE_ENUM,
29
+ FIELD_SUBTYPE_INT,
30
+ FIELD_SUBTYPE_LONG,
31
+ FIELD_SUBTYPE_SHORT,
32
+ FIELD_SUBTYPE_BYTE,
33
+ FIELD_SUBTYPE_STRING,
34
+ FIELD_ATTR_VALUES,
35
+ } from "../field/field-constants.js";
36
+
37
+ const NUMERIC_DISCRIMINATOR_SUBTYPES = new Set<string>([
38
+ FIELD_SUBTYPE_INT,
39
+ FIELD_SUBTYPE_LONG,
40
+ FIELD_SUBTYPE_SHORT,
41
+ FIELD_SUBTYPE_BYTE,
42
+ ]);
43
+
44
+ export function validateDiscriminator(root: MetaData): ParseError[] {
45
+ const errors: ParseError[] = [];
46
+
47
+ const entities = root
48
+ .ownChildren()
49
+ .filter((c) => c.type === TYPE_OBJECT && c.subType === OBJECT_SUBTYPE_ENTITY);
50
+
51
+ // Pass 1: @discriminator on bases — name resolution (own + inherited fields).
52
+ for (const obj of entities) {
53
+ const disc = obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR);
54
+ if (typeof disc !== "string" || disc === "") continue;
55
+
56
+ const field = findFieldOnEntity(obj, disc);
57
+ if (field === undefined) {
58
+ errors.push(
59
+ new ParseError(
60
+ `object.entity "${obj.name}" @discriminator: "${disc}" does not name a field on this entity ` +
61
+ `(checked own children and the extends chain)`,
62
+ { code: "ERR_DISCRIMINATOR_FIELD_NOT_FOUND", source: obj.source },
63
+ ),
64
+ );
65
+ }
66
+ }
67
+
68
+ // Pass 2: @discriminatorValue validation — for every entity that declares it,
69
+ // walk up to find the @discriminator-bearing root, then check field membership
70
+ // and uniqueness within that root's subtype set.
71
+ type SubtypeBinding = {
72
+ subtype: MetaData;
73
+ value: string;
74
+ discField: MetaData;
75
+ };
76
+ const bindingsByRoot = new Map<MetaData, SubtypeBinding[]>();
77
+
78
+ for (const obj of entities) {
79
+ const value = obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE);
80
+ if (typeof value !== "string" || value === "") continue;
81
+
82
+ const { root: discRoot, fieldName } = findDiscriminatorRoot(obj);
83
+ if (discRoot === undefined || fieldName === undefined) continue;
84
+
85
+ const field = findFieldOnEntity(discRoot, fieldName);
86
+ if (field === undefined) continue; // root's own ERR_DISCRIMINATOR_FIELD_NOT_FOUND already fires
87
+
88
+ // Type-match check.
89
+ if (field.subType === FIELD_SUBTYPE_ENUM) {
90
+ const enumValues = field.ownAttr(FIELD_ATTR_VALUES);
91
+ const list = Array.isArray(enumValues) ? enumValues.map(String) : [];
92
+ if (!list.includes(value)) {
93
+ errors.push(
94
+ new ParseError(
95
+ `object.entity "${obj.name}" @discriminatorValue: "${value}" is not a member of the ` +
96
+ `discriminator enum field "${fieldName}" @values [${list.join(", ")}]`,
97
+ { code: "ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH", source: obj.source },
98
+ ),
99
+ );
100
+ }
101
+ } else if (NUMERIC_DISCRIMINATOR_SUBTYPES.has(field.subType)) {
102
+ if (!/^-?\d+$/.test(value)) {
103
+ errors.push(
104
+ new ParseError(
105
+ `object.entity "${obj.name}" @discriminatorValue: "${value}" does not coerce to ` +
106
+ `numeric discriminator field "${fieldName}" (field.${field.subType})`,
107
+ { code: "ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH", source: obj.source },
108
+ ),
109
+ );
110
+ }
111
+ } else if (field.subType !== FIELD_SUBTYPE_STRING) {
112
+ // Discriminator fields outside {enum, integer-family, string} are
113
+ // unusual; accept silently (a future refinement may add a warning).
114
+ }
115
+
116
+ let list = bindingsByRoot.get(discRoot);
117
+ if (list === undefined) {
118
+ list = [];
119
+ bindingsByRoot.set(discRoot, list);
120
+ }
121
+ list.push({ subtype: obj, value, discField: field });
122
+ }
123
+
124
+ // Pass 3: ERR_DISCRIMINATOR_VALUE_DUPLICATE within each root's subtypes.
125
+ for (const [, bindings] of bindingsByRoot) {
126
+ const seen = new Map<string, MetaData>();
127
+ for (const b of bindings) {
128
+ const prev = seen.get(b.value);
129
+ if (prev !== undefined) {
130
+ errors.push(
131
+ new ParseError(
132
+ `object.entity "${b.subtype.name}" @discriminatorValue: "${b.value}" ` +
133
+ `duplicates the value already claimed by "${prev.name}"`,
134
+ { code: "ERR_DISCRIMINATOR_VALUE_DUPLICATE", source: b.subtype.source },
135
+ ),
136
+ );
137
+ } else {
138
+ seen.set(b.value, b.subtype);
139
+ }
140
+ }
141
+ }
142
+
143
+ // Pass 4: ERR_DISCRIMINATOR_VALUE_MISSING — every concrete (non-abstract)
144
+ // entity that extends a @discriminator-bearing root must declare a value.
145
+ for (const obj of entities) {
146
+ if (obj.isAbstract === true) continue;
147
+ if (typeof obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE) === "string") continue;
148
+ if (typeof obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR) === "string") continue; // a root, not a subtype
149
+ const { root: discRoot } = findDiscriminatorRoot(obj);
150
+ if (discRoot === undefined) continue;
151
+ if (discRoot === obj) continue; // safety: same node, shouldn't happen
152
+ errors.push(
153
+ new ParseError(
154
+ `object.entity "${obj.name}" extends the @discriminator-bearing root "${discRoot.name}" ` +
155
+ `but is missing @discriminatorValue (required on every concrete subtype)`,
156
+ { code: "ERR_DISCRIMINATOR_VALUE_MISSING", source: obj.source },
157
+ ),
158
+ );
159
+ }
160
+
161
+ return errors;
162
+ }
163
+
164
+ /** Find a field with the given name on `entity` — own first, then via the
165
+ * resolved extends chain. Returns the declaring field node (own attrs intact). */
166
+ function findFieldOnEntity(entity: MetaData, name: string): MetaData | undefined {
167
+ for (const child of entity.ownChildren()) {
168
+ if (child.type === TYPE_FIELD && child.name === name) return child;
169
+ }
170
+ let cursor = entity.superResolved;
171
+ while (cursor !== undefined) {
172
+ for (const child of cursor.ownChildren()) {
173
+ if (child.type === TYPE_FIELD && child.name === name) return child;
174
+ }
175
+ cursor = cursor.superResolved;
176
+ }
177
+ return undefined;
178
+ }
179
+
180
+ /** Walk the extends chain to find the first ancestor (or self) carrying
181
+ * @discriminator. Returns {root, fieldName} when found; both undefined
182
+ * otherwise. */
183
+ function findDiscriminatorRoot(
184
+ entity: MetaData,
185
+ ): { root: MetaData | undefined; fieldName: string | undefined } {
186
+ let cursor: MetaData | undefined = entity;
187
+ while (cursor !== undefined) {
188
+ const v = cursor.ownAttr(OBJECT_ATTR_DISCRIMINATOR);
189
+ if (typeof v === "string" && v !== "") {
190
+ return { root: cursor, fieldName: v };
191
+ }
192
+ cursor = cursor.superResolved;
193
+ }
194
+ return { root: undefined, fieldName: undefined };
195
+ }
@@ -0,0 +1,65 @@
1
+ // ValueObject — the map-backed default backing object for `object.value`.
2
+ //
3
+ // Idiomatic TS port of Java's ValueObject: a string-keyed map that holds its
4
+ // MetaObject back-reference and supports both declared fields and arbitrary
5
+ // overflow keys. THE unbound default produced by MetaObject.newInstance() when
6
+ // no native type is registered for the object's FQN. Reflection-free.
7
+
8
+ import type { MetaObject } from "./meta-object.js";
9
+ import type { MetaObjectAware } from "./meta-object-aware.js";
10
+
11
+ export class ValueObject implements MetaObjectAware {
12
+ private readonly _values = new Map<string, unknown>();
13
+ private _metaData: MetaObject | undefined = undefined;
14
+
15
+ /** Construct over an optional MetaObject back-reference (set at newInstance). */
16
+ constructor(mo?: MetaObject) {
17
+ this._metaData = mo;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // MetaObjectAware
22
+ // ---------------------------------------------------------------------------
23
+
24
+ getMetaData(): MetaObject | undefined {
25
+ return this._metaData;
26
+ }
27
+
28
+ setMetaData(mo: MetaObject): void {
29
+ this._metaData = mo;
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Map-backed value access — declared fields AND arbitrary overflow keys.
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Read a value by key. Returns undefined for an unset key. */
37
+ get(name: string): unknown {
38
+ return this._values.get(name);
39
+ }
40
+
41
+ /** Write a value by key. Any string key is accepted (overflow-friendly). */
42
+ set(name: string, value: unknown): void {
43
+ this._values.set(name, value);
44
+ }
45
+
46
+ /** True if the key has been set (including to undefined). */
47
+ has(name: string): boolean {
48
+ return this._values.has(name);
49
+ }
50
+
51
+ /** Remove a key. */
52
+ delete(name: string): boolean {
53
+ return this._values.delete(name);
54
+ }
55
+
56
+ /** Insertion-ordered keys currently held. */
57
+ keys(): string[] {
58
+ return [...this._values.keys()];
59
+ }
60
+
61
+ /** A plain-object snapshot of the backing map (insertion-ordered). */
62
+ toObject(): Record<string, unknown> {
63
+ return Object.fromEntries(this._values);
64
+ }
65
+ }
@@ -1,3 +1,5 @@
1
+ import { FIELD_SUBTYPE_UUID } from "../field/field-constants.js";
2
+
1
3
  // Query concern constants — filter operators, sort order values.
2
4
  //
3
5
  // NOTE: `query` is NOT a metamodel node type — it has no subtype, schema, or
@@ -48,6 +50,9 @@ export const OPS_BY_SUBTYPE: Readonly<Record<string, readonly FilterOp[]>> = {
48
50
  date: [FILTER_OP_EQ, FILTER_OP_NE, FILTER_OP_GT, FILTER_OP_GTE, FILTER_OP_LT, FILTER_OP_LTE, FILTER_OP_IN, FILTER_OP_IS_NULL],
49
51
  time: [FILTER_OP_EQ, FILTER_OP_NE, FILTER_OP_GT, FILTER_OP_GTE, FILTER_OP_LT, FILTER_OP_LTE, FILTER_OP_IN, FILTER_OP_IS_NULL],
50
52
  timestamp: [FILTER_OP_EQ, FILTER_OP_NE, FILTER_OP_GT, FILTER_OP_GTE, FILTER_OP_LT, FILTER_OP_LTE, FILTER_OP_IN, FILTER_OP_IS_NULL],
53
+ // uuid: identity-comparison ops only — no `like` (not free-text) and no
54
+ // ordering (`gt`/`lt` are meaningless on a UUID).
55
+ [FIELD_SUBTYPE_UUID]: [FILTER_OP_EQ, FILTER_OP_NE, FILTER_OP_IN, FILTER_OP_IS_NULL],
51
56
  } as const;
52
57
 
53
58
  export function opsForSubType(subType: string): readonly FilterOp[] {
package/src/core-types.ts CHANGED
@@ -35,7 +35,7 @@ import { MetaRelationship } from "./core/relationship/meta-relationship.js";
35
35
  import { MetaLayout } from "./presentation/layout/meta-layout.js";
36
36
  import { MetaSource } from "./persistence/source/meta-source.js";
37
37
  import { MetaOrigin, MetaPassthroughOrigin, MetaAggregateOrigin, MetaCollectionOrigin } from "./persistence/origin/meta-origin.js";
38
- import { commonFieldAttrs, currencyFieldAttr, enumFieldAttr, enumAliasAttr, enumDocAttr } from "./core/field/field-schema.js";
38
+ import { commonFieldAttrs, currencyFieldAttr, enumFieldAttr, enumAliasAttr, enumDocAttr, coerceDefaultAttr, normalizeAttr } from "./core/field/field-schema.js";
39
39
  import { objectAttrs } from "./core/object/object-schema.js";
40
40
  import { relationshipAttrs } from "./core/relationship/relationship-schema.js";
41
41
  import { identityFieldsAttr, IDENTITY_ATTRS_MAP } from "./core/identity/identity-schema.js";
@@ -62,7 +62,7 @@ import {
62
62
  SUBTYPE_ROOT,
63
63
  } from "./shared/base-types.js";
64
64
  import { CHILD_RULE_WILDCARD } from "./shared/structural.js";
65
- import { OBJECT_SUBTYPES, OBJECT_SUBTYPE_ENTITY } from "./core/object/object-constants.js";
65
+ import { OBJECT_SUBTYPES, OBJECT_SUBTYPE_ENTITY, OBJECT_SUBTYPE_VALUE } from "./core/object/object-constants.js";
66
66
  import { FIELD_SUBTYPES, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_ENUM } from "./core/field/field-constants.js";
67
67
  import { ATTR_SUBTYPES } from "./core/attr/attr-constants.js";
68
68
  import {
@@ -183,8 +183,14 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
183
183
  wildcard(TYPE_ATTR),
184
184
  ];
185
185
  for (const subType of OBJECT_SUBTYPES) {
186
+ // FR-011: object.value additionally carries @normalize — the object-level
187
+ // default normalization mode for its enum fields' tolerant extract.
188
+ const subTypeObjectAttrs =
189
+ subType === OBJECT_SUBTYPE_VALUE
190
+ ? [...objectAttrs, { ...normalizeAttr }]
191
+ : [...objectAttrs];
186
192
  registry.register(
187
- def(TYPE_OBJECT, subType, `Object/entity (${subType})`, objectRules, MetaObject, [...objectAttrs]),
193
+ def(TYPE_OBJECT, subType, `Object/entity (${subType})`, objectRules, MetaObject, subTypeObjectAttrs),
188
194
  );
189
195
  }
190
196
 
@@ -202,7 +208,14 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
202
208
  subType === FIELD_SUBTYPE_CURRENCY
203
209
  ? [...commonFieldAttrs, { ...currencyFieldAttr }]
204
210
  : subType === FIELD_SUBTYPE_ENUM
205
- ? [...commonFieldAttrs, { ...enumFieldAttr }, { ...enumAliasAttr }, { ...enumDocAttr }]
211
+ ? [
212
+ ...commonFieldAttrs,
213
+ { ...enumFieldAttr },
214
+ { ...enumAliasAttr },
215
+ { ...enumDocAttr },
216
+ { ...coerceDefaultAttr },
217
+ { ...normalizeAttr },
218
+ ]
206
219
  : [...commonFieldAttrs];
207
220
  registry.register(
208
221
  def(TYPE_FIELD, subType, `Field of type ${subType}`, fieldRules, MetaField, fieldAttrs,
package/src/errors.ts CHANGED
@@ -49,6 +49,22 @@ export const ERROR_CODES = [
49
49
  "ERR_RESERVED_ATTR",
50
50
  "ERR_SOURCE_NO_PRIMARY",
51
51
  "ERR_SOURCE_MULTIPLE_PRIMARY",
52
+ // FR-016 / ADR-0018 — per-kind physical-name alias validation on source.rdb.
53
+ "ERR_PHYSICAL_NAME_KIND_MISMATCH",
54
+ "ERR_PHYSICAL_NAME_MULTIPLE",
55
+ // FR-013 — field-level @readOnly cross-attribute validation.
56
+ "ERR_READONLY_ASSIGNED_PRIMARY",
57
+ "ERR_READONLY_DOWNGRADE",
58
+ // FR-015 — source.rdb @parameterRef typed-input validation.
59
+ "ERR_PARAMETER_REF_UNRESOLVED",
60
+ "ERR_PARAMETER_REF_NOT_VALUE_OBJECT",
61
+ "ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND",
62
+ "ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH",
63
+ // FR-014 — TPH discriminator cross-attribute validation.
64
+ "ERR_DISCRIMINATOR_FIELD_NOT_FOUND",
65
+ "ERR_DISCRIMINATOR_VALUE_DUPLICATE",
66
+ "ERR_DISCRIMINATOR_VALUE_MISSING",
67
+ "ERR_DISCRIMINATOR_VALUE_TYPE_MISMATCH",
52
68
  // ADR-0006 D2 — YAML type-coercion guard. Emitted by every port's YAML
53
69
  // loader when a coerced scalar mismatches the schema-declared type.
54
70
  "ERR_YAML_COERCION",
@@ -67,6 +83,13 @@ export const WARNING_CODES = [
67
83
  // strings; wrapped at the loader boundary into the envelope shape with
68
84
  // this code. Retired as those sites are migrated to envelopes.
69
85
  "WARN_LEGACY",
86
+ // FR-016 / ADR-0018 — pre-1.0 legacy @table spelling on a non-table @kind.
87
+ // Loader accepts; canonical-serializer rewrites to the kind-matching alias.
88
+ "WARN_LEGACY_PHYSICAL_NAME_ALIAS",
89
+ // FR-013 — @readOnly on a field child of object.value. The persistence
90
+ // implication does not apply to value-objects; the attr is retained for
91
+ // language-specific record/struct treatment (e.g. Kotlin `val` vs `var`).
92
+ "WARN_READONLY_VALUE_OBJECT",
70
93
  ] as const;
71
94
  export type WarningCode = (typeof WARNING_CODES)[number];
72
95
 
package/src/index.ts CHANGED
@@ -50,6 +50,16 @@ export { MetaRoot } from "./shared/meta-root.js";
50
50
  export { MetaObject } from "./core/object/meta-object.js";
51
51
  export { MetaField } from "./core/field/meta-field.js";
52
52
  export { MetaAttr } from "./core/attr/meta-attr.js";
53
+
54
+ // Runtime object model — backing objects + FQN→factory binding (Phase A).
55
+ export { ValueObject } from "./core/object/value-object.js";
56
+ export { isMetaObjectAware } from "./core/object/meta-object-aware.js";
57
+ export type { MetaObjectAware } from "./core/object/meta-object-aware.js";
58
+ export {
59
+ ObjectClassRegistry,
60
+ defaultObjectClassRegistry,
61
+ } from "./core/object/object-class-registry.js";
62
+ export type { ObjectFactory } from "./core/object/object-class-registry.js";
53
63
  // Identity: base + subtype-specific
54
64
  export {
55
65
  MetaIdentity,
@@ -17,8 +17,12 @@ import { ParseError } from "../errors.js";
17
17
  import type { LoaderWarning } from "../source.js";
18
18
  import { codeSource, resolvedSource } from "../source.js";
19
19
  import { parseJson } from "../parser-json.js";
20
- import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs } from "./validation-passes.js";
20
+ import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs, validateFieldDefaults } from "./validation-passes.js";
21
21
  import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
22
+ import { validateSourcePhysicalNames } from "../persistence/source/validate-source-physical-names.js";
23
+ import { validateSourceParameterRef } from "../persistence/source/validate-source-parameter-ref.js";
24
+ import { validateFieldReadOnly } from "../core/field/validate-field-readonly.js";
25
+ import { validateDiscriminator } from "../core/object/validate-discriminator.js";
22
26
  import { resolveDeferredSupers } from "../super-resolve.js";
23
27
  import { validateSubtypeRules } from "../subtype-rules.js";
24
28
  import { validateAttrSchema } from "../attr-schema-validate.js";
@@ -441,6 +445,32 @@ export class MetaDataLoader {
441
445
  // exactly one must carry role "primary" (ERR_SOURCE_NO_PRIMARY /
442
446
  // ERR_SOURCE_MULTIPLE_PRIMARY).
443
447
  errors.push(...validateSourceRoles(root));
448
+
449
+ // FR-016 / ADR-0018 — per-kind physical-name alias validation on
450
+ // source.rdb (kind-matching alias, no multiples, legacy @table warning).
451
+ const physicalNameResult = validateSourcePhysicalNames(root);
452
+ errors.push(...physicalNameResult.errors);
453
+ envelopeWarnings.push(...physicalNameResult.warnings);
454
+
455
+ // FR-013 — field-level @readOnly cross-attribute rules
456
+ // (ERR_READONLY_DOWNGRADE / ERR_READONLY_ASSIGNED_PRIMARY /
457
+ // WARN_READONLY_VALUE_OBJECT).
458
+ const readOnlyResult = validateFieldReadOnly(root);
459
+ errors.push(...readOnlyResult.errors);
460
+ envelopeWarnings.push(...readOnlyResult.warnings);
461
+
462
+ // FR-015 — source.rdb @parameterRef typed-input validation.
463
+ errors.push(...validateSourceParameterRef(root));
464
+
465
+ // FR-014 — TPH discriminator (@discriminator / @discriminatorValue)
466
+ // cross-attribute validation.
467
+ errors.push(...validateDiscriminator(root));
468
+
469
+ // Eleventh pass: per-type @default coercibility — a field's @default value
470
+ // must coerce to the field's type (int/long → integer, double/float/decimal →
471
+ // finite number, boolean → true|false). Enum @default membership is validated
472
+ // by validateAttrSchema (Check 5). Cross-port parity with Java/Python/C#.
473
+ errors.push(...validateFieldDefaults(root));
444
474
  }
445
475
 
446
476
  // If nothing parsed successfully, synthesize an empty root so callers
@@ -24,6 +24,13 @@ import {
24
24
  import {
25
25
  TEMPLATE_ATTR_PAYLOAD_REF,
26
26
  TEMPLATE_ATTR_REQUIRED_SLOTS,
27
+ TEMPLATE_ATTR_TEXT_REF,
28
+ TEMPLATE_ATTR_KIND,
29
+ TEMPLATE_KIND_EMAIL,
30
+ TEMPLATE_ATTR_SUBJECT_REF,
31
+ TEMPLATE_ATTR_HTML_BODY_REF,
32
+ TEMPLATE_SUBTYPE_OUTPUT,
33
+ TEMPLATE_SUBTYPE_PROMPT,
27
34
  } from "../template/template-constants.js";
28
35
  import { OBJECT_SUBTYPE_VALUE } from "../core/object/object-constants.js";
29
36
  import {
@@ -36,6 +43,15 @@ import {
36
43
  FIELD_ATTR_OBJECT_REF,
37
44
  FIELD_ATTR_STORAGE,
38
45
  STORAGE_FLATTENED,
46
+ FIELD_ATTR_DEFAULT,
47
+ FIELD_SUBTYPE_INT,
48
+ FIELD_SUBTYPE_LONG,
49
+ FIELD_SUBTYPE_CURRENCY,
50
+ FIELD_SUBTYPE_DOUBLE,
51
+ FIELD_SUBTYPE_FLOAT,
52
+ FIELD_SUBTYPE_DECIMAL,
53
+ FIELD_SUBTYPE_BOOLEAN,
54
+ FIELD_SUBTYPE_ENUM,
39
55
  } from "../core/field/field-constants.js";
40
56
  import { FIELD_ATTR_DB_INDEXED } from "../persistence/db/db-constants.js";
41
57
  import { IDENTITY_ATTR_FIELDS } from "../core/identity/identity-constants.js";
@@ -97,6 +113,56 @@ export function validateDataGridSortFields(root: MetaData): ParseError[] {
97
113
  export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
98
114
  const errors: ParseError[] = [];
99
115
  for (const tmpl of root.ownChildren().filter((c) => c.type === TYPE_TEMPLATE)) {
116
+ // --- @kind / textRef / email part-ref cross-field rules ---
117
+ // template.output is either a document (@kind absent/"document" → @textRef
118
+ // required) or an email (@kind="email" → @subjectRef + @htmlBodyRef required,
119
+ // @textRef unused). template.prompt always requires @textRef (the renderable
120
+ // body). The closed-enum membership of @kind is handled by validateAttrSchema
121
+ // (allowedValues) — here we only enforce the conditional ref presence.
122
+ if (tmpl.subType === TEMPLATE_SUBTYPE_OUTPUT) {
123
+ const kind = tmpl.ownAttr(TEMPLATE_ATTR_KIND);
124
+ if (kind === TEMPLATE_KIND_EMAIL) {
125
+ if (typeof tmpl.ownAttr(TEMPLATE_ATTR_SUBJECT_REF) !== "string") {
126
+ errors.push(
127
+ new ParseError(
128
+ `template "${tmpl.name}" @kind "email" requires @subjectRef`,
129
+ { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
130
+ ),
131
+ );
132
+ }
133
+ if (typeof tmpl.ownAttr(TEMPLATE_ATTR_HTML_BODY_REF) !== "string") {
134
+ errors.push(
135
+ new ParseError(
136
+ `template "${tmpl.name}" @kind "email" requires @htmlBodyRef`,
137
+ { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
138
+ ),
139
+ );
140
+ }
141
+ } else {
142
+ // @kind absent or "document" → require @textRef. (An out-of-enum @kind is
143
+ // separately reported by validateAttrSchema; we still require @textRef so a
144
+ // document is never bodyless.)
145
+ if (typeof tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
146
+ errors.push(
147
+ new ParseError(
148
+ `template "${tmpl.name}" @kind "document" requires @textRef`,
149
+ { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
150
+ ),
151
+ );
152
+ }
153
+ }
154
+ } else if (tmpl.subType === TEMPLATE_SUBTYPE_PROMPT) {
155
+ // template.prompt always carries a renderable body via @textRef.
156
+ if (typeof tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF) !== "string") {
157
+ errors.push(
158
+ new ParseError(
159
+ `template "${tmpl.name}" requires @textRef`,
160
+ { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
161
+ ),
162
+ );
163
+ }
164
+ }
165
+
100
166
  const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
101
167
  if (typeof payloadRef !== "string") continue; // absence handled by the required-attr schema check
102
168
  const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === payloadRef);
@@ -454,6 +520,101 @@ export function validateFieldObjectStorage(root: MetaData): ParseError[] {
454
520
  return errors;
455
521
  }
456
522
 
523
+ // ---------------------------------------------------------------------------
524
+ // Per-type @default coercibility validation (Phase B — generalized @default)
525
+ //
526
+ // The @default attr is registered on the field base, so any field subtype may
527
+ // declare it. Its value must coerce to the field's type (cross-port parity with
528
+ // Java ValidationPhase.validateFieldDefaults, Python _validate_field_defaults,
529
+ // C# ValidateFieldDefaults):
530
+ // - int / long / currency → ASCII integer parse (or finite decimal that
531
+ // truncates — matches the engine's Coerce INT/LONG fallback)
532
+ // - double / float / decimal → finite-number parse (ASCII)
533
+ // - boolean → exactly "true" | "false"
534
+ // - enum → member of @values (handled by attr-schema-validate
535
+ // Check 5; SKIPPED here to avoid double-emit)
536
+ // - string / date / time / object / others → any value allowed
537
+ // A violation emits ERR_BAD_ATTR_VALUE on the field node's source.
538
+ //
539
+ // Own-only: validates @default declared on THIS node (ownAttr), matching the
540
+ // @values / FR-011 own-attr passes. Numeric gates are ASCII-only — they reject
541
+ // "1_000" separators, "0x.."/radix literals, and unicode digits (JS Number()
542
+ // would accept some of these). This mirrors Java's Long.parseLong / Double.parseDouble
543
+ // strictness exactly.
544
+ //
545
+ // The @default value is type-preserved by the parser (a JSON true/false → boolean,
546
+ // a JSON number → number, a JSON string → string), so it is stringified to the
547
+ // canonical form before the per-type gate (lower-case bool, plain number).
548
+ // ---------------------------------------------------------------------------
549
+
550
+ const _INT_DEFAULT_SUBTYPES = new Set<string>([
551
+ FIELD_SUBTYPE_INT,
552
+ FIELD_SUBTYPE_LONG,
553
+ FIELD_SUBTYPE_CURRENCY,
554
+ ]);
555
+ const _NUM_DEFAULT_SUBTYPES = new Set<string>([
556
+ FIELD_SUBTYPE_DOUBLE,
557
+ FIELD_SUBTYPE_FLOAT,
558
+ FIELD_SUBTYPE_DECIMAL,
559
+ ]);
560
+
561
+ // ASCII-only integer: optional sign, then digits. No separators, radix, unicode.
562
+ const _ASCII_INT = /^[+-]?\d+$/;
563
+ // ASCII-only decimal: optional sign, digits with optional fraction/exponent.
564
+ // No separators, no hex, no Infinity/NaN.
565
+ const _ASCII_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
566
+
567
+ /** Canonical string form of a type-preserved @default value (lower-case bool, plain number). */
568
+ function _stringifyDefault(value: string | number | boolean): string {
569
+ if (typeof value === "boolean") return value ? "true" : "false";
570
+ return String(value);
571
+ }
572
+
573
+ /** ASCII integer, or a finite ASCII decimal that truncates to an integer (Java parsesAsLong parity). */
574
+ function _parsesAsLong(s: string): boolean {
575
+ const t = s.trim();
576
+ if (_ASCII_INT.test(t)) return true;
577
+ // accept a finite decimal that truncates to an integer value
578
+ return _ASCII_NUMBER.test(t) && Number.isFinite(Number(t));
579
+ }
580
+
581
+ /** Finite ASCII number (Java parsesAsFiniteNumber parity). */
582
+ function _parsesAsFiniteNumber(s: string): boolean {
583
+ const t = s.trim();
584
+ return _ASCII_NUMBER.test(t) && Number.isFinite(Number(t));
585
+ }
586
+
587
+ export function validateFieldDefaults(root: MetaData): ParseError[] {
588
+ const errors: ParseError[] = [];
589
+ _walkFieldDefaults(root, errors);
590
+ return errors;
591
+ }
592
+
593
+ function _walkFieldDefaults(node: MetaData, errors: ParseError[]): void {
594
+ if (node.type === TYPE_FIELD && node.subType !== FIELD_SUBTYPE_ENUM) {
595
+ // Enum @default membership is validated by attr-schema-validate Check 5.
596
+ const raw = node.ownAttr(FIELD_ATTR_DEFAULT);
597
+ if (raw !== undefined && raw !== null && !Array.isArray(raw) && typeof raw !== "object") {
598
+ const def = _stringifyDefault(raw as string | number | boolean);
599
+ const sub = node.subType;
600
+ let ok: boolean;
601
+ if (_INT_DEFAULT_SUBTYPES.has(sub)) ok = _parsesAsLong(def);
602
+ else if (_NUM_DEFAULT_SUBTYPES.has(sub)) ok = _parsesAsFiniteNumber(def);
603
+ else if (sub === FIELD_SUBTYPE_BOOLEAN) ok = def === "true" || def === "false";
604
+ else ok = true; // string / date / time / object / others — any value allowed
605
+ if (!ok) {
606
+ errors.push(
607
+ new ParseError(
608
+ `field.${sub} "${node.name}" @${FIELD_ATTR_DEFAULT} "${def}" is not coercible to the field's type`,
609
+ { code: "ERR_BAD_ATTR_VALUE", source: node.source },
610
+ ),
611
+ );
612
+ }
613
+ }
614
+ }
615
+ for (const child of node.ownChildren()) _walkFieldDefaults(child, errors);
616
+ }
617
+
457
618
  // ---------------------------------------------------------------------------
458
619
  // Layout dataGrid @filter value validation
459
620
  //
package/src/naming.ts CHANGED
@@ -60,14 +60,15 @@ export function pluralize(s: string): string {
60
60
  }
61
61
 
62
62
  export function resolveTableName(entity: MetaData): string {
63
- // Primary source carries the physical table/view name (@table). Writability
64
- // (table vs view/storedProc/tableFunction) only affects write-routing — for
65
- // SELECT-side name resolution, a read-only primary source is the right answer.
63
+ // FR-016: primary source's `physicalName` implements the four-step rule
64
+ // (kind-matching alias → legacy @table source.name entity-name fallback),
65
+ // so this helper now just delegates. Writability (table vs view/storedProc/
66
+ // tableFunction) only affects write-routing — for SELECT-side name resolution,
67
+ // a read-only primary source is the right answer.
66
68
  const source = entity.ownChildren().find(
67
69
  (c): c is MetaSource => c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY,
68
70
  );
69
- const name = source?.tableName;
70
- if (typeof name === "string" && name !== "") return name;
71
+ if (source !== undefined) return source.physicalName;
71
72
  return pluralize(toSnakeCase(entity.name));
72
73
  }
73
74