@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
@@ -3,7 +3,7 @@
3
3
  import { SUBTYPE_BASE } from "../../shared/base-types.js";
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
- // Field subtypes (16)
6
+ // Field subtypes (17)
7
7
  // ---------------------------------------------------------------------------
8
8
 
9
9
  export const FIELD_SUBTYPE_STRING = "string";
@@ -22,6 +22,10 @@ export const FIELD_SUBTYPE_OBJECT = "object";
22
22
  export const FIELD_SUBTYPE_CLASS = "class";
23
23
  export const FIELD_SUBTYPE_CURRENCY = "currency";
24
24
  export const FIELD_SUBTYPE_ENUM = "enum";
25
+ /** R6 Plan 2a: logical UUID identity scalar. Bare scalar (no required attrs, no
26
+ * loader value-validation) — like field.long; native binding is forced to TS
27
+ * `string` (TS has no native UUID type). DB column is Postgres-native `uuid`. */
28
+ export const FIELD_SUBTYPE_UUID = "uuid";
25
29
 
26
30
  export const FIELD_SUBTYPES = [
27
31
  SUBTYPE_BASE,
@@ -41,6 +45,7 @@ export const FIELD_SUBTYPES = [
41
45
  FIELD_SUBTYPE_CLASS,
42
46
  FIELD_SUBTYPE_CURRENCY,
43
47
  FIELD_SUBTYPE_ENUM,
48
+ FIELD_SUBTYPE_UUID,
44
49
  ] as const;
45
50
  export type FieldSubType = (typeof FIELD_SUBTYPES)[number];
46
51
 
@@ -50,6 +55,12 @@ export type FieldSubType = (typeof FIELD_SUBTYPES)[number];
50
55
 
51
56
  export const FIELD_ATTR_REQUIRED = "required";
52
57
  export const FIELD_ATTR_UNIQUE = "unique";
58
+
59
+ /** FR-013: when true, the field is read-only from the application's perspective.
60
+ * Codegen emits no setter; persistence skips the column on INSERT/UPDATE; Zod
61
+ * create/update variants omit the field. Default false (writable). See ADR-0013
62
+ * layer split — this is logical (no DB-introspection round-trip). */
63
+ export const FIELD_ATTR_READ_ONLY = "readOnly";
53
64
  export const FIELD_ATTR_DEFAULT = "default";
54
65
  export const FIELD_ATTR_MAX_LENGTH = "maxLength";
55
66
  export const FIELD_ATTR_PRECISION = "precision";
@@ -119,7 +130,7 @@ export const FIELD_ATTR_VALUES = "values";
119
130
  export const ENUM_MEMBER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
120
131
 
121
132
  /** FR-010: map of off-vocabulary token → canonical enum member, feeding the
122
- * tolerant recover alias-fold. `properties`-shaped; only on field.enum. */
133
+ * tolerant extract alias-fold. `properties`-shaped; only on field.enum. */
123
134
  export const FIELD_ATTR_ENUM_ALIAS = "enumAlias";
124
135
 
125
136
  /** FR-010: map of enum member → human-readable description, shown per-member in
@@ -135,3 +146,27 @@ export const FIELD_ATTR_EXAMPLE = "example";
135
146
 
136
147
  /** FR-010: a short instruction for this field, shown in the generated prompt fragment. */
137
148
  export const FIELD_ATTR_INSTRUCTION = "instruction";
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // FR-011 extract-hardening attrs (enum tolerant extract)
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /** FR-011: fallback enum member used when an LLM sends a present-but-uncoercible
155
+ * value. Must be one of the field's @values (loader-validated). On field.enum only. */
156
+ export const FIELD_ATTR_COERCE_DEFAULT = "coerceDefault";
157
+
158
+ /** FR-011: ASCII normalization mode for tolerant enum extract. Closed enum
159
+ * (none|collapse|strip). On field.enum (per-field) and object.value (default
160
+ * for its enum fields). Resolved field → object → global NORMALIZE_DEFAULT. */
161
+ export const FIELD_ATTR_NORMALIZE = "normalize";
162
+
163
+ /** FR-011: the three ASCII normalization modes (closed set).
164
+ * - none : exact match only.
165
+ * - collapse : ASCII case-fold + trim + collapse runs of [\s_-]+ to one _.
166
+ * - strip : ASCII case-fold + strip everything except [A-Z0-9] (most forgiving). */
167
+ export const NORMALIZE_MODES = ["none", "collapse", "strip"] as const;
168
+ export type NormalizeMode = (typeof NORMALIZE_MODES)[number];
169
+
170
+ /** FR-011: global normalization default when neither the field nor its owning
171
+ * object.value declares @normalize. */
172
+ export const NORMALIZE_DEFAULT: NormalizeMode = "strip";
@@ -15,6 +15,7 @@ import {
15
15
  FIELD_ATTR_STORAGE,
16
16
  STORAGE_VALUES,
17
17
  FIELD_ATTR_REQUIRED,
18
+ FIELD_ATTR_READ_ONLY,
18
19
  FIELD_ATTR_UNIQUE,
19
20
  FIELD_ATTR_DEFAULT,
20
21
  FIELD_ATTR_MAX_LENGTH,
@@ -32,6 +33,10 @@ import {
32
33
  FIELD_ATTR_ENUM_DOC,
33
34
  FIELD_ATTR_EXAMPLE,
34
35
  FIELD_ATTR_INSTRUCTION,
36
+ FIELD_ATTR_COERCE_DEFAULT,
37
+ FIELD_ATTR_NORMALIZE,
38
+ NORMALIZE_MODES,
39
+ NORMALIZE_DEFAULT,
35
40
  } from "./field-constants.js";
36
41
 
37
42
  /** Attrs common to every field subtype (codegen-ts column mapper + Project D filter/sort). */
@@ -62,6 +67,17 @@ export const commonFieldAttrs: AttrSchema[] = [
62
67
  description:
63
68
  "When true, the field is NOT NULL. Equivalent to attaching a validator.required child.",
64
69
  },
70
+ {
71
+ name: FIELD_ATTR_READ_ONLY,
72
+ valueType: ATTR_SUBTYPE_BOOLEAN,
73
+ required: false,
74
+ description:
75
+ "FR-013: when true, the field is read-only — codegen emits no setter / " +
76
+ "writable property, the persistence layer skips the column on INSERT/UPDATE, " +
77
+ "and Zod/Pydantic/class-validator schemas mark it read-only on input variants. " +
78
+ "The value is populated by the database (computed column, default expression, " +
79
+ "trigger), by replication, or by another external owner.",
80
+ },
65
81
  {
66
82
  name: FIELD_ATTR_UNIQUE,
67
83
  valueType: ATTR_SUBTYPE_BOOLEAN,
@@ -167,13 +183,13 @@ export const enumFieldAttr: AttrSchema = {
167
183
  };
168
184
 
169
185
  /** The @enumAlias attr — only on field.enum. Map of off-vocabulary token → canonical
170
- * member, feeding the FR-010 tolerant recover alias-fold (runtime aliases win on conflict). */
186
+ * member, feeding the FR-010 tolerant extract alias-fold (runtime aliases win on conflict). */
171
187
  export const enumAliasAttr: AttrSchema = {
172
188
  name: FIELD_ATTR_ENUM_ALIAS,
173
189
  valueType: ATTR_SUBTYPE_PROPERTIES,
174
190
  required: false,
175
191
  description:
176
- "Map of alternate/off-vocabulary tokens to canonical enum members; feeds the FR-010 tolerant recover alias-fold.",
192
+ "Map of alternate/off-vocabulary tokens to canonical enum members; feeds the FR-010 tolerant extract alias-fold.",
177
193
  };
178
194
 
179
195
  /** The @enumDoc attr — only on field.enum. Map of member → human-readable description,
@@ -185,3 +201,28 @@ export const enumDocAttr: AttrSchema = {
185
201
  description:
186
202
  "Map of enum member to a human-readable description; shown per-member in the FR-010 'guide'-style prompt fragment.",
187
203
  };
204
+
205
+ /** FR-011: the @coerceDefault attr — only on field.enum. String member symbol used as
206
+ * the extract fallback when an LLM sends a present-but-uncoercible value. Loader-validated
207
+ * to be one of the field's @values (ERR_BAD_ATTR_VALUE otherwise). */
208
+ export const coerceDefaultAttr: AttrSchema = {
209
+ name: FIELD_ATTR_COERCE_DEFAULT,
210
+ valueType: ATTR_SUBTYPE_STRING,
211
+ required: false,
212
+ description:
213
+ "Fallback enum member used by tolerant extract when a present value cannot be coerced; must be one of the field's @values.",
214
+ };
215
+
216
+ /** FR-011: the @normalize attr — on field.enum (per-field) and object.value (object default).
217
+ * Closed enum (none|collapse|strip); controls the ASCII normalization applied during tolerant
218
+ * enum extract. Resolved field → owning object.value → global default (strip). */
219
+ export const normalizeAttr: AttrSchema = {
220
+ name: FIELD_ATTR_NORMALIZE,
221
+ valueType: ATTR_SUBTYPE_STRING,
222
+ required: false,
223
+ default: NORMALIZE_DEFAULT,
224
+ allowedValues: [...NORMALIZE_MODES],
225
+ description:
226
+ "ASCII normalization mode for tolerant enum extract (none|collapse|strip, default strip). " +
227
+ "On field.enum it is per-field; on object.value it is the default for the object's enum fields.",
228
+ };
@@ -34,6 +34,7 @@ import {
34
34
  FIELD_SUBTYPE_TIMESTAMP,
35
35
  FIELD_SUBTYPE_OBJECT,
36
36
  FIELD_SUBTYPE_ENUM,
37
+ FIELD_SUBTYPE_UUID,
37
38
  FIELD_ATTR_REQUIRED,
38
39
  FIELD_ATTR_UNIQUE,
39
40
  FIELD_ATTR_DEFAULT,
@@ -46,6 +47,7 @@ import { FIELD_ATTR_COLUMN } from "../../persistence/db/db-constants.js";
46
47
  import { VALIDATOR_SUBTYPE_REQUIRED } from "../validator/validator-constants.js";
47
48
  import type { MetaValidator } from "../validator/meta-validator.js";
48
49
  import type { MetaView } from "../../presentation/view/meta-view.js";
50
+ import { ValueObject } from "../object/value-object.js";
49
51
 
50
52
  /** Field subtype → DataType. Co-located with the class — a provider adding a
51
53
  * field subtype supplies its own dataType the same way. */
@@ -53,6 +55,8 @@ const FIELD_DATA_TYPE: Readonly<Record<string, DataType>> = {
53
55
  [SUBTYPE_BASE]: DATA_TYPE_STRING,
54
56
  [FIELD_SUBTYPE_STRING]: DATA_TYPE_STRING,
55
57
  [FIELD_SUBTYPE_CLASS]: DATA_TYPE_STRING,
58
+ // field.uuid binds to TS `string` (no native UUID type) — DATA_TYPE_STRING.
59
+ [FIELD_SUBTYPE_UUID]: DATA_TYPE_STRING,
56
60
  [FIELD_SUBTYPE_INT]: DATA_TYPE_INT,
57
61
  [FIELD_SUBTYPE_SHORT]: DATA_TYPE_INT,
58
62
  [FIELD_SUBTYPE_BYTE]: DATA_TYPE_INT,
@@ -179,4 +183,39 @@ export class MetaField extends MetaData implements DataTypeAware {
179
183
  resolveSuper(): MetaField | undefined {
180
184
  return this.superData as MetaField | undefined;
181
185
  }
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Runtime value access (Java parity: MetaField.getObject / setObject by name).
189
+ //
190
+ // Minimal backing-object SPI: dispatch on the backing object's shape — a
191
+ // ValueObject uses its map, any other object uses typed property access.
192
+ // Nested OBJECT fields and arrays are driven by the CONSUMER (resolve the
193
+ // child MetaObject via objectRef, newInstance, recurse); these methods read
194
+ // and write whatever value (scalar, nested object, or array) the caller
195
+ // supplies. No coercion here — coerce() is available separately.
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /**
199
+ * Read this field's value from a backing object. `name` defaults to the
200
+ * field's own `name`; pass an override only to read a differently-keyed slot.
201
+ */
202
+ getValue(obj: object, name: string = this.name): unknown {
203
+ if (obj instanceof ValueObject) {
204
+ return obj.get(name);
205
+ }
206
+ // Typed property access — the unavoidable dynamic-property bridge.
207
+ return (obj as Record<string, unknown>)[name];
208
+ }
209
+
210
+ /**
211
+ * Write `value` into a backing object under `name` (defaults to this field's
212
+ * own `name`). ValueObject → map set; any other object → typed property set.
213
+ */
214
+ setValue(obj: object, value: unknown, name: string = this.name): void {
215
+ if (obj instanceof ValueObject) {
216
+ obj.set(name, value);
217
+ return;
218
+ }
219
+ (obj as Record<string, unknown>)[name] = value;
220
+ }
182
221
  }
@@ -0,0 +1,142 @@
1
+ // Validation pass: field-level @readOnly cross-attribute rules (FR-013).
2
+ //
3
+ // Codes:
4
+ // ERR_READONLY_ASSIGNED_PRIMARY — @readOnly: true on a field that is the
5
+ // target of an identity.primary with @generation: "assigned". The application
6
+ // has no path to populate the identity value (no setter; not generated; not
7
+ // defaulted).
8
+ // ERR_READONLY_DOWNGRADE — a concrete subtype declares @readOnly: false on a
9
+ // field whose extends-chain parent declares @readOnly: true. Read-only-ness
10
+ // can only be upgraded, never downgraded.
11
+ // WARN_READONLY_VALUE_OBJECT — @readOnly: true on a field child of object.value.
12
+ // The persistence implication does not apply; the attr is retained for
13
+ // language-specific record/struct treatment.
14
+
15
+ import type { MetaData } from "../../shared/meta-data.js";
16
+ import { ParseError } from "../../errors.js";
17
+ import type { LoaderWarning } from "../../source.js";
18
+ import {
19
+ TYPE_OBJECT,
20
+ TYPE_FIELD,
21
+ TYPE_IDENTITY,
22
+ } from "../../shared/base-types.js";
23
+ import { OBJECT_SUBTYPE_VALUE } from "../object/object-constants.js";
24
+ import {
25
+ IDENTITY_SUBTYPE_PRIMARY,
26
+ IDENTITY_ATTR_GENERATION,
27
+ IDENTITY_ATTR_FIELDS,
28
+ GENERATION_ASSIGNED,
29
+ } from "../identity/identity-constants.js";
30
+ import { FIELD_ATTR_READ_ONLY } from "./field-constants.js";
31
+
32
+ export interface FieldReadOnlyValidationResult {
33
+ errors: ParseError[];
34
+ warnings: LoaderWarning[];
35
+ }
36
+
37
+ export function validateFieldReadOnly(root: MetaData): FieldReadOnlyValidationResult {
38
+ const errors: ParseError[] = [];
39
+ const warnings: LoaderWarning[] = [];
40
+
41
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
42
+ const isValueObject = obj.subType === OBJECT_SUBTYPE_VALUE;
43
+
44
+ // 1) WARN_READONLY_VALUE_OBJECT — any @readOnly field child of an object.value.
45
+ if (isValueObject) {
46
+ for (const child of obj.ownChildren()) {
47
+ if (child.type === TYPE_FIELD && readOnlyFlag(child) === true) {
48
+ warnings.push({
49
+ code: "WARN_READONLY_VALUE_OBJECT",
50
+ message:
51
+ `field "${child.name}" on object.value "${obj.name}" declares ` +
52
+ `@readOnly: true; value-objects have no persistence semantics so ` +
53
+ `the read-only contract is advisory (codegen may use it for record/` +
54
+ `struct treatment).`,
55
+ source: child.source,
56
+ });
57
+ }
58
+ }
59
+ }
60
+
61
+ // 2) ERR_READONLY_DOWNGRADE — read-only-ness can only be upgraded across
62
+ // extends. Compare own field vs. inherited field's effective @readOnly.
63
+ for (const ownField of obj.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
64
+ const ownVal = readOnlyFlag(ownField);
65
+ if (ownVal !== false) continue; // only the explicit downgrade case matters
66
+ const inherited = inheritedField(obj, ownField.name);
67
+ if (inherited !== undefined && readOnlyFlag(inherited) === true) {
68
+ errors.push(
69
+ new ParseError(
70
+ `field "${ownField.name}" on "${obj.name}" sets @readOnly: false, but the ` +
71
+ `extends-chain parent declares @readOnly: true. Read-only-ness can only be ` +
72
+ `upgraded, not downgraded (FR-013).`,
73
+ { code: "ERR_READONLY_DOWNGRADE", source: ownField.source },
74
+ ),
75
+ );
76
+ }
77
+ }
78
+
79
+ // 3) ERR_READONLY_ASSIGNED_PRIMARY — @readOnly: true on a field used in an
80
+ // identity.primary whose @generation is "assigned" (effective tree).
81
+ if (!isValueObject) {
82
+ const primaryAssignedFields = primaryAssignedFieldNames(obj);
83
+ if (primaryAssignedFields.size > 0) {
84
+ for (const field of obj.children().filter((c) => c.type === TYPE_FIELD)) {
85
+ if (!primaryAssignedFields.has(field.name)) continue;
86
+ if (readOnlyFlag(field) !== true) continue;
87
+ errors.push(
88
+ new ParseError(
89
+ `field "${field.name}" on "${obj.name}" is @readOnly: true AND the target ` +
90
+ `of identity.primary with @generation: "assigned"; the application has no ` +
91
+ `path to populate the identity value (FR-013).`,
92
+ { code: "ERR_READONLY_ASSIGNED_PRIMARY", source: field.source },
93
+ ),
94
+ );
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ return { errors, warnings };
101
+ }
102
+
103
+ /** Read the explicit @readOnly value from a field's own attrs. Returns
104
+ * true / false when explicitly set, undefined when absent. */
105
+ function readOnlyFlag(field: MetaData): boolean | undefined {
106
+ const v = field.ownAttr(FIELD_ATTR_READ_ONLY);
107
+ if (typeof v === "boolean") return v;
108
+ return undefined;
109
+ }
110
+
111
+ /** Walk the extends chain looking for a field with the same name; return its
112
+ * declaring node (own attrs preserved) if found. */
113
+ function inheritedField(obj: MetaData, name: string): MetaData | undefined {
114
+ let cursor = obj.superResolved;
115
+ while (cursor !== undefined) {
116
+ const f = cursor.ownChildren().find((c) => c.type === TYPE_FIELD && c.name === name);
117
+ if (f !== undefined) return f;
118
+ cursor = cursor.superResolved;
119
+ }
120
+ return undefined;
121
+ }
122
+
123
+ /** Names of fields participating in any identity.primary with @generation:
124
+ * "assigned" on `obj` or its extends chain. */
125
+ function primaryAssignedFieldNames(obj: MetaData): Set<string> {
126
+ const out = new Set<string>();
127
+ for (const id of obj.children()) {
128
+ if (id.type !== TYPE_IDENTITY) continue;
129
+ if (id.subType !== IDENTITY_SUBTYPE_PRIMARY) continue;
130
+ const gen = id.ownAttr(IDENTITY_ATTR_GENERATION);
131
+ if (gen !== GENERATION_ASSIGNED) continue;
132
+ const fields = id.ownAttr(IDENTITY_ATTR_FIELDS);
133
+ if (Array.isArray(fields)) {
134
+ for (const fName of fields) {
135
+ if (typeof fName === "string") out.add(fName);
136
+ }
137
+ } else if (typeof fields === "string") {
138
+ out.add(fields);
139
+ }
140
+ }
141
+ return out;
142
+ }
@@ -0,0 +1,27 @@
1
+ // MetaObjectAware — the runtime back-reference contract.
2
+ //
3
+ // Idiomatic TS port of Java's MetaObjectAware: a backing object that knows the
4
+ // MetaObject describing it. The default backing type (ValueObject) implements
5
+ // this; generated/registered native types may implement it too so they carry a
6
+ // back-reference after newInstance(). Reflection-free — no reflect-metadata,
7
+ // no runtime type resolution.
8
+
9
+ import type { MetaObject } from "./meta-object.js";
10
+
11
+ /** A backing object that carries a reference to the MetaObject describing it. */
12
+ export interface MetaObjectAware {
13
+ /** The MetaObject describing this instance, or undefined if not yet attached. */
14
+ getMetaData(): MetaObject | undefined;
15
+ /** Attach the MetaObject describing this instance (back-reference). */
16
+ setMetaData(mo: MetaObject): void;
17
+ }
18
+
19
+ /** Structural type guard — true when `o` satisfies the MetaObjectAware contract. */
20
+ export function isMetaObjectAware(o: unknown): o is MetaObjectAware {
21
+ return (
22
+ typeof o === "object" &&
23
+ o !== null &&
24
+ typeof (o as MetaObjectAware).getMetaData === "function" &&
25
+ typeof (o as MetaObjectAware).setMetaData === "function"
26
+ );
27
+ }
@@ -19,6 +19,12 @@ import {
19
19
  OBJECT_SUBTYPE_ENTITY,
20
20
  OBJECT_SUBTYPE_VALUE,
21
21
  } from "./object-constants.js";
22
+ import { ValueObject } from "./value-object.js";
23
+ import { isMetaObjectAware } from "./meta-object-aware.js";
24
+ import {
25
+ ObjectClassRegistry,
26
+ defaultObjectClassRegistry,
27
+ } from "./object-class-registry.js";
22
28
  import {
23
29
  IDENTITY_SUBTYPE_PRIMARY,
24
30
  IDENTITY_SUBTYPE_SECONDARY,
@@ -33,12 +39,17 @@ import type { MetaValidator } from "../validator/meta-validator.js";
33
39
  export class MetaObject extends MetaData {
34
40
  get dbTable(): string | undefined {
35
41
  return this.cached("dbTable", () => {
36
- // The primary writable source carries the physical table name (@table).
42
+ // The primary writable source carries the physical table name. FR-016:
43
+ // physicalName respects per-kind aliases + the four-step resolution rule.
44
+ // We still scope to writable + primary because dbTable is the WRITE target
45
+ // (CQRS read-side via dbView/dbProc is a different accessor).
37
46
  const source = this.children().find(
38
47
  (c): c is MetaSource =>
39
48
  c instanceof MetaSource && c.isWritable() && c.role === SOURCE_ROLE_PRIMARY,
40
49
  );
41
- return source?.tableName;
50
+ if (source === undefined) return undefined;
51
+ const name = source.physicalName;
52
+ return name !== "" ? name : undefined;
42
53
  });
43
54
  }
44
55
 
@@ -148,4 +159,38 @@ export class MetaObject extends MetaData {
148
159
  this.fields().find((f) => f.name === name),
149
160
  );
150
161
  }
162
+
163
+ /** Java parity alias for findField() — locate an effective field by name. */
164
+ getMetaField(name: string): MetaField | undefined {
165
+ return this.findField(name);
166
+ }
167
+
168
+ /**
169
+ * Instantiate a backing object for this MetaObject (Java parity:
170
+ * MetaObject.newInstance()). Resolution order:
171
+ *
172
+ * 1. If `registry.resolve(this.fqn())` yields a factory, call it; if the
173
+ * result is MetaObjectAware, attach this MetaObject as its back-reference.
174
+ * 2. Otherwise create a map-backed ValueObject (the unbound default for
175
+ * `object.value`), which sets its own back-reference via the constructor.
176
+ *
177
+ * The registry key is the object's fully-qualified name as produced by
178
+ * `resolutionKey()` (`<package>::<name>`, folding the file-default package).
179
+ * This is the SAME FQN form used by a nested field's `@objectRef`, so a
180
+ * factory registered for an object's FQN is found whether instantiation is
181
+ * driven top-down or via an object-ref. (An object's bare `fqn()` is reserved
182
+ * for the FR5d cross-port referrer-envelope contract and is NOT the binding
183
+ * key.) Reflection-free: only registered factories are consulted.
184
+ */
185
+ newInstance(registry: ObjectClassRegistry = defaultObjectClassRegistry): object {
186
+ const factory = registry.resolve(this.resolutionKey());
187
+ if (factory !== undefined) {
188
+ const instance = factory(this);
189
+ if (isMetaObjectAware(instance)) {
190
+ instance.setMetaData(this);
191
+ }
192
+ return instance;
193
+ }
194
+ return new ValueObject(this);
195
+ }
151
196
  }
@@ -0,0 +1,48 @@
1
+ // ObjectClassRegistry — FQN → factory binding for runtime instantiation.
2
+ //
3
+ // Idiomatic TS port of Java's ObjectClassRegistry: maps an object's FQN
4
+ // (`package::name`) to a factory that produces a native backing instance.
5
+ // Generated code self-registers at import time (`registry.register(fqn, ...)`)
6
+ // — NO reflection (no Class.forName / Type.GetType / importlib equivalent),
7
+ // AOT/tree-shake-safe per ADR-0001.
8
+ //
9
+ // DISTINCT from the metadata TypeRegistry, which maps metamodel type/subType
10
+ // names to MetaData node classes. This registry maps a DATA object's FQN to a
11
+ // runtime BACKING instance factory.
12
+
13
+ import type { MetaObject } from "./meta-object.js";
14
+
15
+ /** Produces a native backing instance for the given MetaObject. */
16
+ export type ObjectFactory = (mo: MetaObject) => object;
17
+
18
+ export class ObjectClassRegistry {
19
+ private readonly _factories = new Map<string, ObjectFactory>();
20
+
21
+ /** Bind an FQN (`package::name`) to a backing-instance factory. */
22
+ register(fqn: string, factory: ObjectFactory): void {
23
+ this._factories.set(fqn, factory);
24
+ }
25
+
26
+ /** Resolve the factory bound to an FQN, or undefined if none is registered. */
27
+ resolve(fqn: string): ObjectFactory | undefined {
28
+ return this._factories.get(fqn);
29
+ }
30
+
31
+ /** True if a factory is bound to the FQN. */
32
+ has(fqn: string): boolean {
33
+ return this._factories.has(fqn);
34
+ }
35
+
36
+ /** Remove a binding (test/scoping convenience). */
37
+ unregister(fqn: string): boolean {
38
+ return this._factories.delete(fqn);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * The module-level default registry. Generated code self-registers here at
44
+ * import time; `MetaObject.newInstance()` consults it when no explicit registry
45
+ * is passed. Tests should construct their own `new ObjectClassRegistry()` to
46
+ * avoid leaking bindings across cases (scenario 6).
47
+ */
48
+ export const defaultObjectClassRegistry = new ObjectClassRegistry();
@@ -19,3 +19,20 @@ export const OBJECT_SUBTYPES = [
19
19
  OBJECT_SUBTYPE_VALUE,
20
20
  ] as const;
21
21
  export type ObjectSubType = (typeof OBJECT_SUBTYPES)[number];
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // FR-014 — TPH discriminator (Table-per-Hierarchy single-table inheritance)
25
+ // ---------------------------------------------------------------------------
26
+ // Both attrs live on object.entity. Logical layer (ADR-0013): metadata
27
+ // describes the polymorphism; codegen / ORM emit the per-stack idiom
28
+ // (EF HasDiscriminator / JPA @DiscriminatorColumn / SQLAlchemy polymorphic_on
29
+ // / Kotlin sealed class / TS discriminated union).
30
+
31
+ /** FR-014: names the field on this entity (resolvable via extends) that holds
32
+ * the subtype-discriminator value. Subtypes declare @discriminatorValue to
33
+ * bind their rows to a value of that field. */
34
+ export const OBJECT_ATTR_DISCRIMINATOR = "discriminator";
35
+
36
+ /** FR-014: on a subtype of an entity with @discriminator: the value that
37
+ * identifies rows of this subtype in the shared discriminator field. */
38
+ export const OBJECT_ATTR_DISCRIMINATOR_VALUE = "discriminatorValue";
@@ -2,6 +2,34 @@
2
2
  // Consumed by registerCoreTypes().
3
3
 
4
4
  import type { AttrSchema } from "../../registry.js";
5
+ import { ATTR_SUBTYPE_STRING } from "../attr/attr-constants.js";
6
+ import {
7
+ OBJECT_ATTR_DISCRIMINATOR,
8
+ OBJECT_ATTR_DISCRIMINATOR_VALUE,
9
+ } from "./object-constants.js";
5
10
 
6
11
  /** Attrs common to every object subtype. */
7
- export const objectAttrs: AttrSchema[] = [];
12
+ export const objectAttrs: AttrSchema[] = [
13
+ {
14
+ name: OBJECT_ATTR_DISCRIMINATOR,
15
+ valueType: ATTR_SUBTYPE_STRING,
16
+ required: false,
17
+ description:
18
+ "FR-014: names the field on this entity (resolvable via extends:) that " +
19
+ "holds the subtype-discriminator value. Subtypes of this entity declare " +
20
+ "@discriminatorValue to bind their rows to a discriminator value. The " +
21
+ "discriminator field itself is an ordinary field declaration (typically " +
22
+ "field.enum or field.int / field.string).",
23
+ },
24
+ {
25
+ name: OBJECT_ATTR_DISCRIMINATOR_VALUE,
26
+ valueType: ATTR_SUBTYPE_STRING,
27
+ required: false,
28
+ description:
29
+ "FR-014: on a subtype of an entity with @discriminator — the value that " +
30
+ "identifies rows of this subtype in the shared discriminator field. Wire " +
31
+ "form is always a string; the underlying field's subtype (enum / int / " +
32
+ "string) controls codegen + storage coercion. Required on every concrete " +
33
+ "subtype of a discriminated entity.",
34
+ },
35
+ ];