@metaobjectsdev/metadata 0.5.0-rc.3 → 0.6.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/attr-schema-validate.d.ts.map +1 -1
  2. package/dist/attr-schema-validate.js +66 -11
  3. package/dist/attr-schema-validate.js.map +1 -1
  4. package/dist/core/documentation/doc-constants.d.ts +11 -0
  5. package/dist/core/documentation/doc-constants.d.ts.map +1 -0
  6. package/dist/core/documentation/doc-constants.js +20 -0
  7. package/dist/core/documentation/doc-constants.js.map +1 -0
  8. package/dist/core/documentation/doc-provider.d.ts +3 -0
  9. package/dist/core/documentation/doc-provider.d.ts.map +1 -0
  10. package/dist/core/documentation/doc-provider.js +10 -0
  11. package/dist/core/documentation/doc-provider.js.map +1 -0
  12. package/dist/core/documentation/doc-schema.d.ts +8 -0
  13. package/dist/core/documentation/doc-schema.d.ts.map +1 -0
  14. package/dist/core/documentation/doc-schema.js +53 -0
  15. package/dist/core/documentation/doc-schema.js.map +1 -0
  16. package/dist/core/field/field-constants.d.ts +25 -1
  17. package/dist/core/field/field-constants.d.ts.map +1 -1
  18. package/dist/core/field/field-constants.js +32 -1
  19. package/dist/core/field/field-constants.js.map +1 -1
  20. package/dist/core/field/field-schema.d.ts +2 -0
  21. package/dist/core/field/field-schema.d.ts.map +1 -1
  22. package/dist/core/field/field-schema.js +20 -2
  23. package/dist/core/field/field-schema.js.map +1 -1
  24. package/dist/core/field/meta-field.d.ts +2 -1
  25. package/dist/core/field/meta-field.d.ts.map +1 -1
  26. package/dist/core/field/meta-field.js +6 -4
  27. package/dist/core/field/meta-field.js.map +1 -1
  28. package/dist/core/object/meta-object.d.ts.map +1 -1
  29. package/dist/core/object/meta-object.js +6 -5
  30. package/dist/core/object/meta-object.js.map +1 -1
  31. package/dist/core/parser-yaml.d.ts.map +1 -1
  32. package/dist/core/parser-yaml.js +9 -2
  33. package/dist/core/parser-yaml.js.map +1 -1
  34. package/dist/core/relationship/meta-relationship.d.ts +4 -0
  35. package/dist/core/relationship/meta-relationship.d.ts.map +1 -1
  36. package/dist/core/relationship/meta-relationship.js +11 -1
  37. package/dist/core/relationship/meta-relationship.js.map +1 -1
  38. package/dist/core/relationship/relationship-constants.d.ts +9 -0
  39. package/dist/core/relationship/relationship-constants.d.ts.map +1 -1
  40. package/dist/core/relationship/relationship-constants.js +15 -0
  41. package/dist/core/relationship/relationship-constants.js.map +1 -1
  42. package/dist/core/relationship/relationship-schema.d.ts.map +1 -1
  43. package/dist/core/relationship/relationship-schema.js +15 -1
  44. package/dist/core/relationship/relationship-schema.js.map +1 -1
  45. package/dist/core/yaml-desugar.d.ts +8 -1
  46. package/dist/core/yaml-desugar.d.ts.map +1 -1
  47. package/dist/core/yaml-desugar.js +166 -15
  48. package/dist/core/yaml-desugar.js.map +1 -1
  49. package/dist/core-types.d.ts.map +1 -1
  50. package/dist/core-types.js +28 -11
  51. package/dist/core-types.js.map +1 -1
  52. package/dist/errors.d.ts +1 -1
  53. package/dist/errors.d.ts.map +1 -1
  54. package/dist/errors.js +21 -0
  55. package/dist/errors.js.map +1 -1
  56. package/dist/index.d.ts +6 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +7 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  61. package/dist/loader/meta-data-loader.js +12 -1
  62. package/dist/loader/meta-data-loader.js.map +1 -1
  63. package/dist/loader/validation-passes.d.ts +2 -0
  64. package/dist/loader/validation-passes.d.ts.map +1 -1
  65. package/dist/loader/validation-passes.js +69 -3
  66. package/dist/loader/validation-passes.js.map +1 -1
  67. package/dist/naming.d.ts +3 -2
  68. package/dist/naming.d.ts.map +1 -1
  69. package/dist/naming.js +14 -12
  70. package/dist/naming.js.map +1 -1
  71. package/dist/parser-core.d.ts.map +1 -1
  72. package/dist/parser-core.js +91 -48
  73. package/dist/parser-core.js.map +1 -1
  74. package/dist/persistence/db/db-constants.d.ts +2 -2
  75. package/dist/persistence/db/db-constants.d.ts.map +1 -1
  76. package/dist/persistence/db/db-constants.js +2 -2
  77. package/dist/persistence/db/db-constants.js.map +1 -1
  78. package/dist/persistence/db/db-provider.d.ts.map +1 -1
  79. package/dist/persistence/db/db-provider.js +10 -13
  80. package/dist/persistence/db/db-provider.js.map +1 -1
  81. package/dist/persistence/db/db-schema.d.ts +2 -4
  82. package/dist/persistence/db/db-schema.d.ts.map +1 -1
  83. package/dist/persistence/db/db-schema.js +5 -13
  84. package/dist/persistence/db/db-schema.js.map +1 -1
  85. package/dist/persistence/origin/meta-origin.d.ts +10 -0
  86. package/dist/persistence/origin/meta-origin.d.ts.map +1 -1
  87. package/dist/persistence/origin/meta-origin.js +14 -1
  88. package/dist/persistence/origin/meta-origin.js.map +1 -1
  89. package/dist/persistence/origin/origin-constants.d.ts +3 -1
  90. package/dist/persistence/origin/origin-constants.d.ts.map +1 -1
  91. package/dist/persistence/origin/origin-constants.js +6 -0
  92. package/dist/persistence/origin/origin-constants.js.map +1 -1
  93. package/dist/persistence/origin/origin-schema.d.ts +1 -1
  94. package/dist/persistence/origin/origin-schema.d.ts.map +1 -1
  95. package/dist/persistence/origin/origin-schema.js +12 -2
  96. package/dist/persistence/origin/origin-schema.js.map +1 -1
  97. package/dist/persistence/source/meta-source.d.ts +11 -9
  98. package/dist/persistence/source/meta-source.d.ts.map +1 -1
  99. package/dist/persistence/source/meta-source.js +23 -15
  100. package/dist/persistence/source/meta-source.js.map +1 -1
  101. package/dist/persistence/source/source-constants.d.ts +32 -11
  102. package/dist/persistence/source/source-constants.d.ts.map +1 -1
  103. package/dist/persistence/source/source-constants.js +55 -16
  104. package/dist/persistence/source/source-constants.js.map +1 -1
  105. package/dist/persistence/source/source-schema.d.ts +4 -0
  106. package/dist/persistence/source/source-schema.d.ts.map +1 -0
  107. package/dist/persistence/source/source-schema.js +37 -0
  108. package/dist/persistence/source/source-schema.js.map +1 -0
  109. package/dist/persistence/source/validate-source-roles.d.ts +12 -0
  110. package/dist/persistence/source/validate-source-roles.d.ts.map +1 -0
  111. package/dist/persistence/source/validate-source-roles.js +38 -0
  112. package/dist/persistence/source/validate-source-roles.js.map +1 -0
  113. package/dist/registry.d.ts +13 -0
  114. package/dist/registry.d.ts.map +1 -1
  115. package/dist/registry.js +26 -0
  116. package/dist/registry.js.map +1 -1
  117. package/dist/shared/base-types.d.ts +2 -1
  118. package/dist/shared/base-types.d.ts.map +1 -1
  119. package/dist/shared/base-types.js +5 -2
  120. package/dist/shared/base-types.js.map +1 -1
  121. package/dist/template/meta-template.d.ts +13 -0
  122. package/dist/template/meta-template.d.ts.map +1 -0
  123. package/dist/template/meta-template.js +13 -0
  124. package/dist/template/meta-template.js.map +1 -0
  125. package/dist/template/template-constants.d.ts +17 -0
  126. package/dist/template/template-constants.d.ts.map +1 -0
  127. package/dist/template/template-constants.js +46 -0
  128. package/dist/template/template-constants.js.map +1 -0
  129. package/dist/template/template-schema.d.ts +3 -0
  130. package/dist/template/template-schema.d.ts.map +1 -0
  131. package/dist/template/template-schema.js +84 -0
  132. package/dist/template/template-schema.js.map +1 -0
  133. package/package.json +1 -1
  134. package/src/attr-schema-validate.ts +89 -9
  135. package/src/core/documentation/doc-constants.ts +22 -0
  136. package/src/core/documentation/doc-provider.ts +12 -0
  137. package/src/core/documentation/doc-schema.ts +64 -0
  138. package/src/core/field/field-constants.ts +41 -1
  139. package/src/core/field/field-schema.ts +25 -0
  140. package/src/core/field/meta-field.ts +6 -3
  141. package/src/core/object/meta-object.ts +6 -6
  142. package/src/core/parser-yaml.ts +11 -3
  143. package/src/core/relationship/meta-relationship.ts +14 -0
  144. package/src/core/relationship/relationship-constants.ts +20 -0
  145. package/src/core/relationship/relationship-schema.ts +18 -0
  146. package/src/core/yaml-desugar.ts +206 -24
  147. package/src/core-types.ts +31 -9
  148. package/src/errors.ts +21 -0
  149. package/src/index.ts +7 -0
  150. package/src/loader/meta-data-loader.ts +15 -1
  151. package/src/loader/validation-passes.ts +98 -1
  152. package/src/naming.ts +15 -13
  153. package/src/parser-core.ts +119 -73
  154. package/src/persistence/db/db-constants.ts +2 -2
  155. package/src/persistence/db/db-provider.ts +10 -16
  156. package/src/persistence/db/db-schema.ts +5 -15
  157. package/src/persistence/origin/meta-origin.ts +15 -0
  158. package/src/persistence/origin/origin-constants.ts +7 -0
  159. package/src/persistence/origin/origin-schema.ts +15 -1
  160. package/src/persistence/source/meta-source.ts +30 -17
  161. package/src/persistence/source/source-constants.ts +66 -17
  162. package/src/persistence/source/source-schema.ts +54 -0
  163. package/src/persistence/source/validate-source-roles.ts +53 -0
  164. package/src/registry.ts +31 -0
  165. package/src/shared/base-types.ts +5 -2
  166. package/src/template/meta-template.ts +12 -0
  167. package/src/template/template-constants.ts +53 -0
  168. package/src/template/template-schema.ts +106 -0
@@ -1,38 +1,51 @@
1
1
  // MetaSource — concrete node class for type=source nodes.
2
2
  // Declares where an object's data lives (Project E).
3
- // dbTable / dbView source subtypes declare the SQL table or view name.
3
+ // source.rdb uses @table/@kind/@role/@schema; read-only is derived from @kind.
4
4
  //
5
5
  // Extends MetaData directly: no model wrapper, no metaOf() indirection.
6
6
 
7
7
  import { MetaData } from "../../shared/meta-data.js";
8
8
  import {
9
- SOURCE_ATTR_NAME,
10
- SOURCE_SUBTYPE_DB_TABLE,
11
- SOURCE_SUBTYPE_DB_VIEW,
9
+ SOURCE_ATTR_TABLE,
10
+ SOURCE_ATTR_KIND,
11
+ SOURCE_ATTR_ROLE,
12
+ SOURCE_READ_ONLY_KINDS,
13
+ DEFAULT_SOURCE_KIND,
14
+ DEFAULT_SOURCE_ROLE,
12
15
  } from "./source-constants.js";
13
16
 
14
17
  export class MetaSource extends MetaData {
15
- /** The SQL table or view name (value of @name attr on the source child). */
16
- get sourceName(): string | undefined {
17
- const v = this.ownAttr(SOURCE_ATTR_NAME);
18
- return typeof v === "string" ? v : undefined;
18
+ /** Physical SQL table/view name from @table (source.rdb). */
19
+ get tableName(): string | undefined {
20
+ const v = this.ownAttr(SOURCE_ATTR_TABLE);
21
+ return typeof v === "string" && v !== "" ? v : undefined;
19
22
  }
20
23
 
21
24
  /**
22
- * True when this source is a `dbTable` (writable).
23
- * Explicitly defined rather than derived from `isReadOnly()` so it remains
24
- * correct if a third source subtype is added in the future.
25
+ * The effective kind for this source: the value of `@kind`, defaulting to
26
+ * `"table"` when omitted (ADR-0007 Rule 3 per-paradigm default).
25
27
  */
26
- isWritable(): boolean {
27
- return this.subType === SOURCE_SUBTYPE_DB_TABLE;
28
+ get effectiveKind(): string {
29
+ const v = this.ownAttr(SOURCE_ATTR_KIND);
30
+ return typeof v === "string" && v !== "" ? v : DEFAULT_SOURCE_KIND;
31
+ }
32
+
33
+ /** The multi-source role for this source (defaults to "primary" when omitted). */
34
+ get role(): string {
35
+ const v = this.ownAttr(SOURCE_ATTR_ROLE);
36
+ return typeof v === "string" && v !== "" ? v : DEFAULT_SOURCE_ROLE;
28
37
  }
29
38
 
30
39
  /**
31
- * True when this source is a `dbView` (read-only projection).
32
- * Explicitly defined rather than derived from `isWritable()` so it remains
33
- * correct if a third source subtype is added in the future.
40
+ * True when this source's effective kind is read-only (view, materializedView,
41
+ * storedProc, tableFunction). Derived from the @kind attr on source.rdb.
34
42
  */
35
43
  isReadOnly(): boolean {
36
- return this.subType === SOURCE_SUBTYPE_DB_VIEW;
44
+ return SOURCE_READ_ONLY_KINDS.has(this.effectiveKind);
45
+ }
46
+
47
+ /** True when this source is writable (i.e. not read-only). */
48
+ isWritable(): boolean {
49
+ return !this.isReadOnly();
37
50
  }
38
51
  }
@@ -4,32 +4,81 @@ import { SUBTYPE_BASE } from "../../shared/base-types.js";
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
6
  // Source type — declares where an object's data lives (Project E).
7
- // dbTable / dbView ship in v1. Multiple sources per object are allowed
8
- // and meaningful (write-through CQRS: dbTable for writes + dbView for reads).
7
+ // source.rdb (ADR-0007): paradigm subtype + @table/@kind/@role/@schema attrs.
8
+ // Multiple sources per object are allowed and meaningful (write-through CQRS:
9
+ // writable rdb[table] for writes + read-only rdb[view] for reads).
9
10
  // ---------------------------------------------------------------------------
10
11
 
11
- export const SOURCE_SUBTYPE_DB_TABLE = "dbTable";
12
- export const SOURCE_SUBTYPE_DB_VIEW = "dbView";
12
+ // --- Source v2 (ADR-0007): paradigm subtype "rdb"; physical name @table; @kind + @role. ---
13
+ export const SOURCE_SUBTYPE_RDB = "rdb";
13
14
 
14
15
  export const SOURCE_SUBTYPES = [
15
16
  SUBTYPE_BASE,
16
- SOURCE_SUBTYPE_DB_TABLE,
17
- SOURCE_SUBTYPE_DB_VIEW,
17
+ SOURCE_SUBTYPE_RDB,
18
18
  ] as const;
19
19
  export type SourceSubType = (typeof SOURCE_SUBTYPES)[number];
20
20
 
21
- // Source attrs both dbTable and dbView use @name for the SQL identifier
22
- // (table name and view name respectively). Same key for ergonomic consistency.
23
- export const SOURCE_DB_TABLE_ATTR_NAME = "name";
24
- export const SOURCE_DB_VIEW_ATTR_NAME = "name";
25
- /** Shared @name attr key for MetaSource (covers both dbTable and dbView). Use this
26
- * in generic source accessors instead of the subtype-specific aliases above. */
27
- export const SOURCE_ATTR_NAME = "name";
28
-
29
- /** Optional DB schema attr on source[dbTable] / source[dbView]. Postgres uses
30
- * this to namespace tables/views. SQLite has no schema concept and rejects
31
- * any non-default value. Default for Postgres: "public". */
21
+ /** Optional DB schema attr on source.rdb. Postgres uses this to namespace
22
+ * tables/views. SQLite has no schema concept and rejects any non-default
23
+ * value. Default for Postgres: "public". */
32
24
  export const SOURCE_ATTR_SCHEMA = "schema";
33
25
 
34
26
  /** Default Postgres schema when @schema is omitted from a source. */
35
27
  export const DEFAULT_DB_SCHEMA_POSTGRES = "public";
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Source v2 (ADR-0007) — rdb paradigm: kind, role, physical table name.
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /** Physical table/view name on source.rdb. */
34
+ export const SOURCE_ATTR_TABLE = "table";
35
+ /** Object kind within the rdb paradigm; read-only-ness is derived from it. */
36
+ export const SOURCE_ATTR_KIND = "kind";
37
+ /** Multi-source role; exactly one primary per object. */
38
+ export const SOURCE_ATTR_ROLE = "role";
39
+
40
+ export const SOURCE_KIND_TABLE = "table";
41
+ export const SOURCE_KIND_VIEW = "view";
42
+ export const SOURCE_KIND_MATERIALIZED_VIEW = "materializedView";
43
+ export const SOURCE_KIND_STORED_PROC = "storedProc";
44
+ export const SOURCE_KIND_TABLE_FUNCTION = "tableFunction";
45
+
46
+ export const SOURCE_RDB_KINDS = [
47
+ SOURCE_KIND_TABLE,
48
+ SOURCE_KIND_VIEW,
49
+ SOURCE_KIND_MATERIALIZED_VIEW,
50
+ SOURCE_KIND_STORED_PROC,
51
+ SOURCE_KIND_TABLE_FUNCTION,
52
+ ] as const;
53
+ export type SourceRdbKind = (typeof SOURCE_RDB_KINDS)[number];
54
+
55
+ /** rdb @kind default when omitted (writable table). */
56
+ export const DEFAULT_SOURCE_KIND = SOURCE_KIND_TABLE;
57
+
58
+ /** Kinds whose source is read-only (codegen emits read-only model/queries/routes). */
59
+ export const SOURCE_READ_ONLY_KINDS: ReadonlySet<string> = new Set([
60
+ SOURCE_KIND_VIEW,
61
+ SOURCE_KIND_MATERIALIZED_VIEW,
62
+ SOURCE_KIND_STORED_PROC,
63
+ SOURCE_KIND_TABLE_FUNCTION,
64
+ ]);
65
+
66
+ export const SOURCE_ROLE_PRIMARY = "primary";
67
+ export const SOURCE_ROLE_REPLICA = "replica";
68
+ export const SOURCE_ROLE_INDEX = "index";
69
+ export const SOURCE_ROLE_CACHE = "cache";
70
+ export const SOURCE_ROLE_PUBLISH = "publish";
71
+ export const SOURCE_ROLE_MIRROR = "mirror";
72
+
73
+ export const SOURCE_ROLES = [
74
+ SOURCE_ROLE_PRIMARY,
75
+ SOURCE_ROLE_REPLICA,
76
+ SOURCE_ROLE_INDEX,
77
+ SOURCE_ROLE_CACHE,
78
+ SOURCE_ROLE_PUBLISH,
79
+ SOURCE_ROLE_MIRROR,
80
+ ] as const;
81
+ export type SourceRole = (typeof SOURCE_ROLES)[number];
82
+
83
+ /** Role when @role is omitted (system of record). */
84
+ export const DEFAULT_SOURCE_ROLE = SOURCE_ROLE_PRIMARY;
@@ -0,0 +1,54 @@
1
+ // Source v2 attribute schemas — registered by dbProvider for source.rdb.
2
+ // Consumed via sourceRdbAttrs in db-provider.ts.
3
+
4
+ import type { AttrSchema } from "../../registry.js";
5
+ import { ATTR_SUBTYPE_STRING } from "../../core/attr/attr-constants.js";
6
+ import {
7
+ SOURCE_ATTR_TABLE,
8
+ SOURCE_ATTR_KIND,
9
+ SOURCE_ATTR_ROLE,
10
+ SOURCE_ATTR_SCHEMA,
11
+ SOURCE_RDB_KINDS,
12
+ SOURCE_ROLES,
13
+ } from "./source-constants.js";
14
+
15
+ /** `@table` — physical SQL table or view name for source.rdb. */
16
+ const tableSchema: AttrSchema = {
17
+ name: SOURCE_ATTR_TABLE,
18
+ valueType: ATTR_SUBTYPE_STRING,
19
+ required: false,
20
+ description:
21
+ "Physical SQL table or view name for this rdb source. Defaults to the object name run through the project's columnNamingStrategy when omitted.",
22
+ };
23
+
24
+ /** `@kind` — object kind within the rdb paradigm; drives read-only derivation. */
25
+ const kindSchema: AttrSchema = {
26
+ name: SOURCE_ATTR_KIND,
27
+ valueType: ATTR_SUBTYPE_STRING,
28
+ required: false,
29
+ allowedValues: [...SOURCE_RDB_KINDS],
30
+ description:
31
+ "The kind of database object this source represents: table (default, writable), view, materializedView, storedProc, or tableFunction. Non-table kinds are read-only.",
32
+ };
33
+
34
+ /** `@role` — multi-source role; exactly one primary per object. */
35
+ const roleSchema: AttrSchema = {
36
+ name: SOURCE_ATTR_ROLE,
37
+ valueType: ATTR_SUBTYPE_STRING,
38
+ required: false,
39
+ allowedValues: [...SOURCE_ROLES],
40
+ description:
41
+ "Role this source plays when an object has multiple sources: primary (default, system of record), replica, index, cache, publish, or mirror.",
42
+ };
43
+
44
+ /** `@schema` — optional DB schema namespace for this source. */
45
+ const schemaSchema: AttrSchema = {
46
+ name: SOURCE_ATTR_SCHEMA,
47
+ valueType: ATTR_SUBTYPE_STRING,
48
+ required: false,
49
+ description:
50
+ "Optional database schema name (e.g. 'catalog', 'public'). Postgres defaults to 'public'; SQLite rejects any non-default value.",
51
+ };
52
+
53
+ /** All attr schemas for source.rdb, to be registered via registry.extend. */
54
+ export const sourceRdbAttrs: AttrSchema[] = [tableSchema, kindSchema, roleSchema, schemaSchema];
@@ -0,0 +1,53 @@
1
+ // Validation pass: one-primary multi-source rule.
2
+ //
3
+ // An object that declares ≥1 source MUST have exactly one with role "primary".
4
+ // Zero sources is allowed (object is not persisted). Violations:
5
+ // ERR_SOURCE_NO_PRIMARY — sources present but none is role "primary"
6
+ // ERR_SOURCE_MULTIPLE_PRIMARY — more than one source has role "primary"
7
+
8
+ import type { MetaData } from "../../shared/meta-data.js";
9
+ import { ParseError } from "../../errors.js";
10
+ import { TYPE_OBJECT, TYPE_SOURCE } from "../../shared/base-types.js";
11
+ import { MetaSource } from "./meta-source.js";
12
+ import { SOURCE_ROLE_PRIMARY } from "./source-constants.js";
13
+
14
+ /**
15
+ * Walks every object in the root and enforces the one-primary rule for
16
+ * multi-source objects:
17
+ * - 0 sources → no error (object is not backed by any store).
18
+ * - 1+ sources, exactly 1 with role "primary" → OK.
19
+ * - 1+ sources, 0 with role "primary" → ERR_SOURCE_NO_PRIMARY.
20
+ * - 1+ sources, 2+ with role "primary" → ERR_SOURCE_MULTIPLE_PRIMARY.
21
+ */
22
+ export function validateSourceRoles(root: MetaData): ParseError[] {
23
+ const errors: ParseError[] = [];
24
+
25
+ for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
26
+ const sources = obj
27
+ .ownChildren()
28
+ .filter((c) => c.type === TYPE_SOURCE)
29
+ .map((c) => c as MetaSource);
30
+
31
+ if (sources.length === 0) continue;
32
+
33
+ const primaryCount = sources.filter((s) => s.role === SOURCE_ROLE_PRIMARY).length;
34
+
35
+ if (primaryCount === 0) {
36
+ errors.push(
37
+ new ParseError(
38
+ `object "${obj.name}" declares ${sources.length} source(s) but none has role "${SOURCE_ROLE_PRIMARY}"`,
39
+ { code: "ERR_SOURCE_NO_PRIMARY" },
40
+ ),
41
+ );
42
+ } else if (primaryCount > 1) {
43
+ errors.push(
44
+ new ParseError(
45
+ `object "${obj.name}" declares ${primaryCount} sources with role "${SOURCE_ROLE_PRIMARY}"; exactly one is required`,
46
+ { code: "ERR_SOURCE_MULTIPLE_PRIMARY" },
47
+ ),
48
+ );
49
+ }
50
+ }
51
+
52
+ return errors;
53
+ }
package/src/registry.ts CHANGED
@@ -76,6 +76,9 @@ export class TypeRegistry {
76
76
  /** Per-type designated default subType (queried by the YAML desugar). */
77
77
  private readonly _defaultSubTypes = new Map<string, string>();
78
78
 
79
+ /** Attrs accepted on every metatype (declared by providers). */
80
+ private readonly _commonAttrs: AttrSchema[] = [];
81
+
79
82
  register(def: TypeDefinition): void {
80
83
  const key = def.typeId.toString();
81
84
  if (this._defs.has(key)) {
@@ -140,6 +143,34 @@ export class TypeRegistry {
140
143
  return [...(this.find(type, subType)?.attributes ?? [])];
141
144
  }
142
145
 
146
+ /**
147
+ * Register attrs that are accepted on every metatype. Callers (providers) use
148
+ * this to declare cross-cutting attrs (e.g. doc attrs). Repeated registration
149
+ * of the same name is silently deduped (first registration wins); conflicts with
150
+ * per-type attrs are resolved at validation time (Task 1.3).
151
+ */
152
+ registerCommonAttrs(attrs: AttrSchema[]): void {
153
+ for (const attr of attrs) {
154
+ if (attr.valueType === SUBTYPE_BASE) {
155
+ throw new Error(
156
+ `TypeRegistry.registerCommonAttrs: attr "${attr.name}" declares valueType "${SUBTYPE_BASE}", ` +
157
+ `which is not valid for attrs. Use no valueType (omit the field) for a polymorphic/untyped attr.`,
158
+ );
159
+ }
160
+ if (this._commonAttrs.some((existing) => existing.name === attr.name)) {
161
+ continue; // dedupe same-name re-registration; conflict-with-per-type-attr is checked at validation time
162
+ }
163
+ this._commonAttrs.push(attr);
164
+ }
165
+ }
166
+
167
+ /** Returns the registered common attrs (attrs accepted on every metatype).
168
+ * Returns a defensive copy so callers cannot mutate internal state, matching
169
+ * the spread-copy pattern used by `attrsOf` and `allSubTypesOf`. */
170
+ getCommonAttrs(): readonly AttrSchema[] {
171
+ return [...this._commonAttrs];
172
+ }
173
+
143
174
  /**
144
175
  * Additively enrich an already-registered (type, subType): append attributes
145
176
  * and/or child rules. Does NOT touch factory / typeId / dataType / default
@@ -1,7 +1,8 @@
1
- // Base type vocabulary — the 11 registered base types + universal/metadata subtypes.
1
+ // Base type vocabulary — the 12 registered base types + universal/metadata subtypes.
2
2
 
3
3
  // ---------------------------------------------------------------------------
4
- // Base type names (the 11 registered base types — Java metaobjects-core vocabulary)
4
+ // Base type names (the registered base types — Java metaobjects-core vocabulary,
5
+ // plus `template`, the TS-first FR-004 addition for prompt construction)
5
6
  // ---------------------------------------------------------------------------
6
7
 
7
8
  export const TYPE_METADATA = "metadata";
@@ -15,6 +16,7 @@ export const TYPE_RELATIONSHIP = "relationship";
15
16
  export const TYPE_LAYOUT = "layout";
16
17
  export const TYPE_SOURCE = "source";
17
18
  export const TYPE_ORIGIN = "origin";
19
+ export const TYPE_TEMPLATE = "template";
18
20
 
19
21
  export const BASE_TYPES = [
20
22
  TYPE_METADATA,
@@ -28,6 +30,7 @@ export const BASE_TYPES = [
28
30
  TYPE_LAYOUT,
29
31
  TYPE_SOURCE,
30
32
  TYPE_ORIGIN,
33
+ TYPE_TEMPLATE,
31
34
  ] as const;
32
35
  export type BaseType = (typeof BASE_TYPES)[number];
33
36
 
@@ -0,0 +1,12 @@
1
+ import { MetaData } from "../shared/meta-data.js";
2
+
3
+ /**
4
+ * `template.*` node — the fourth-pillar metatype (FR-004, R1).
5
+ *
6
+ * A single class backs both subtypes (`template.prompt`, `template.output`),
7
+ * mirroring `MetaSource`: the loader dispatches by subtype and per-subtype
8
+ * attribute schemas (see `template-schema.ts`) drive validation. Typed accessors
9
+ * are deferred to the render-engine / verify work — attr values read generically
10
+ * via `ownAttr(...)` until then, so no untested forward-looking surface ships.
11
+ */
12
+ export class MetaTemplate extends MetaData {}
@@ -0,0 +1,53 @@
1
+ // template.* subtype vocabulary + reserved attribute names (FR-004, R1).
2
+ //
3
+ // `template` is the fourth-pillar base type: a renderable text artifact bound to
4
+ // a typed payload. Two subtypes (by audience/structure, NOT by format):
5
+ // - prompt: LLM-targeted; carries the prompt-overlay attrs and is the home for
6
+ // future structured-prompt (role/turn/tool) divergence.
7
+ // - output: every other rendered artifact (email, export, docs, config).
8
+ //
9
+ // Format is the @format ATTRIBUTE (closed set below), never a subtype — the
10
+ // render engine keys its escaper off @format, so a new format costs one escaper
11
+ // + one enum value, not a new subtype + cross-language port.
12
+
13
+ import { SUBTYPE_BASE } from "../shared/base-types.js";
14
+
15
+ export const TEMPLATE_SUBTYPE_PROMPT = "prompt";
16
+ export const TEMPLATE_SUBTYPE_OUTPUT = "output";
17
+
18
+ export const TEMPLATE_SUBTYPES = [
19
+ SUBTYPE_BASE,
20
+ TEMPLATE_SUBTYPE_PROMPT,
21
+ TEMPLATE_SUBTYPE_OUTPUT,
22
+ ] as const;
23
+ export type TemplateSubType = (typeof TEMPLATE_SUBTYPES)[number];
24
+
25
+ // Generic reserved attrs (both subtypes). The "@" is applied at wire time.
26
+ export const TEMPLATE_ATTR_PAYLOAD_REF = "payloadRef";
27
+ export const TEMPLATE_ATTR_TEXT_REF = "textRef";
28
+ export const TEMPLATE_ATTR_FORMAT = "format";
29
+ export const TEMPLATE_ATTR_MAX_CHARS = "maxChars";
30
+ export const TEMPLATE_ATTR_OWNER = "owner";
31
+ export const TEMPLATE_ATTR_SINCE = "since";
32
+ // Output tags the rendered text must contain (drives the verify output-tag check).
33
+ // Generic — not prompt-only: any rendered artifact (email, export, prompt) can
34
+ // carry a tag contract a downstream parser depends on.
35
+ export const TEMPLATE_ATTR_REQUIRED_TAGS = "requiredTags";
36
+
37
+ // Prompt-overlay attrs (template.prompt only).
38
+ export const TEMPLATE_ATTR_MAX_TOKENS = "maxTokens";
39
+ export const TEMPLATE_ATTR_REQUIRED_SLOTS = "requiredSlots";
40
+ export const TEMPLATE_ATTR_MODEL = "model";
41
+
42
+ // Closed format set — escaping/whitespace behavior is keyed off this in the
43
+ // render engine's escaper registry (FR-004 R7).
44
+ export const TEMPLATE_FORMATS = [
45
+ "text",
46
+ "html",
47
+ "xml",
48
+ "csv",
49
+ "json",
50
+ "markdown",
51
+ "spreadsheet",
52
+ ] as const;
53
+ export type TemplateFormat = (typeof TEMPLATE_FORMATS)[number];
@@ -0,0 +1,106 @@
1
+ // Reserved-attribute schemas per template subtype (FR-004, R1).
2
+ //
3
+ // Both subtypes share the generic attrs; @format is a closed enum (allowedValues)
4
+ // that the render engine keys its escaper off. template.prompt additionally
5
+ // carries the LLM-overlay attrs. References (@payloadRef, @textRef) are plain
6
+ // strings here — resolution against a real payload / text source is render-time
7
+ // `verify` scope (Plan #3), not load-time validation.
8
+
9
+ import type { AttrSchema } from "../registry.js";
10
+ import {
11
+ ATTR_SUBTYPE_STRING,
12
+ ATTR_SUBTYPE_INT,
13
+ ATTR_SUBTYPE_STRINGARRAY,
14
+ } from "../core/attr/attr-constants.js";
15
+ import { SUBTYPE_BASE } from "../shared/base-types.js";
16
+ import {
17
+ TEMPLATE_SUBTYPE_PROMPT,
18
+ TEMPLATE_SUBTYPE_OUTPUT,
19
+ TEMPLATE_ATTR_PAYLOAD_REF,
20
+ TEMPLATE_ATTR_TEXT_REF,
21
+ TEMPLATE_ATTR_FORMAT,
22
+ TEMPLATE_ATTR_MAX_CHARS,
23
+ TEMPLATE_ATTR_OWNER,
24
+ TEMPLATE_ATTR_SINCE,
25
+ TEMPLATE_ATTR_REQUIRED_TAGS,
26
+ TEMPLATE_ATTR_MAX_TOKENS,
27
+ TEMPLATE_ATTR_REQUIRED_SLOTS,
28
+ TEMPLATE_ATTR_MODEL,
29
+ TEMPLATE_FORMATS,
30
+ } from "./template-constants.js";
31
+
32
+ // Generic attrs shared by template.prompt and template.output.
33
+ const genericAttrs: AttrSchema[] = [
34
+ {
35
+ name: TEMPLATE_ATTR_PAYLOAD_REF,
36
+ valueType: ATTR_SUBTYPE_STRING,
37
+ required: true,
38
+ description: "Reference to the payload (a view-object / projection) this template renders against.",
39
+ },
40
+ {
41
+ name: TEMPLATE_ATTR_TEXT_REF,
42
+ valueType: ATTR_SUBTYPE_STRING,
43
+ required: true,
44
+ description: "2-layer logical reference (group/source) to the body text, resolved by a provider at render time.",
45
+ },
46
+ {
47
+ name: TEMPLATE_ATTR_FORMAT,
48
+ valueType: ATTR_SUBTYPE_STRING,
49
+ required: false,
50
+ default: "text",
51
+ allowedValues: [...TEMPLATE_FORMATS],
52
+ description: "Output format; drives the render engine's escaping/whitespace behavior.",
53
+ },
54
+ {
55
+ name: TEMPLATE_ATTR_MAX_CHARS,
56
+ valueType: ATTR_SUBTYPE_INT,
57
+ required: false,
58
+ description: "Size budget for the rendered output, in characters.",
59
+ },
60
+ {
61
+ name: TEMPLATE_ATTR_OWNER,
62
+ valueType: ATTR_SUBTYPE_STRING,
63
+ required: false,
64
+ description: "Governance: the owner of this template.",
65
+ },
66
+ {
67
+ name: TEMPLATE_ATTR_SINCE,
68
+ valueType: ATTR_SUBTYPE_STRING,
69
+ required: false,
70
+ description: "Governance: the version this template was introduced in.",
71
+ },
72
+ {
73
+ name: TEMPLATE_ATTR_REQUIRED_TAGS,
74
+ valueType: ATTR_SUBTYPE_STRINGARRAY,
75
+ required: false,
76
+ description: "Output tags the rendered text must contain (drives the verify output-tag check).",
77
+ },
78
+ ];
79
+
80
+ // LLM-overlay attrs (template.prompt only).
81
+ const promptOverlayAttrs: AttrSchema[] = [
82
+ {
83
+ name: TEMPLATE_ATTR_MAX_TOKENS,
84
+ valueType: ATTR_SUBTYPE_INT,
85
+ required: false,
86
+ description: "Token budget for the rendered prompt (LLM-specific).",
87
+ },
88
+ {
89
+ name: TEMPLATE_ATTR_REQUIRED_SLOTS,
90
+ valueType: ATTR_SUBTYPE_STRINGARRAY,
91
+ required: false,
92
+ description: "Slots that must resolve at render time (drives the verify check).",
93
+ },
94
+ {
95
+ name: TEMPLATE_ATTR_MODEL,
96
+ valueType: ATTR_SUBTYPE_STRING,
97
+ required: false,
98
+ description: "Target model id (LLM-specific).",
99
+ },
100
+ ];
101
+
102
+ export const TEMPLATE_ATTRS_MAP = new Map<string, AttrSchema[]>([
103
+ [SUBTYPE_BASE, []],
104
+ [TEMPLATE_SUBTYPE_PROMPT, [...genericAttrs, ...promptOverlayAttrs]],
105
+ [TEMPLATE_SUBTYPE_OUTPUT, [...genericAttrs]],
106
+ ]);