Skip to content

Commit 10a60c8

Browse files
dmealingclaude
andcommitted
feat(codegen): templateGenerator() factory + docsFile refactor
Adds templateGenerator() as a stock generator in codegen-ts — walks a MetaRoot via a caller-supplied walk function, renders shared Mustache templates against the produced data dicts via the existing @metaobjectsdev/render engine, and writes output in any format (Markdown / HTML / JSON / YAML / text). Establishes the line: code → hand-coded generators (ts-poet, idiomatic per-port); documents → templateGenerator (shared Mustache templates, port-agnostic). Refactors rc.11's docsFile() (~250 LOC of hand-coded markdown string emit) to use the new factory: ~85 LOC of generator + data builder helper + a shared Mustache template under codegen-ts/templates/docs/. Adopters can override by placing same-named templates in their project's templates/ directory (hybrid resolution per the design doc's D1 decision). The EntityDocData TypeScript interface is now an exported public-API contract — template authors get type-checking; the data-dict keys are versioned per MO major (D3 decision). docs/features/codegen-data-shapes.md documents the canonical shapes. Conformance fixture docs-file-basic/expected/Author.md stays byte-identical post-refactor (proves the refactor preserves output). Test counts: codegen-ts 419 → 433 (+14: templateGenerator suite + factory tests + data-builder contract tests + the docsFile conformance still green). metadata / sdk / cli unchanged. Pairs with 19b2c58's three-way merge policy: with strategy (b) now in the write path, the templateGenerator output benefits from the same hand-edit-survival guarantee any other generator provides. Adopters hand-editing entity docs will see their edits merge cleanly on subsequent regen. Per the design doc at metaforge/docs/private/strategy/2026-05-28- template-driven-codegen-design.md (option 1 chosen — combined policy fix + factory refactor in one release cycle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bc0727d commit 10a60c8

12 files changed

Lines changed: 1230 additions & 582 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Codegen data shapes — the public contract for template authors
2+
3+
When you write a custom Mustache template that drives a `templateGenerator()`
4+
instance, the data dict your template receives follows a stable, typed shape
5+
exported from `@metaobjectsdev/codegen-ts/generators`. This page documents
6+
those shapes — they are **public API**. Field keys are versioned per
7+
MetaObjects **major** release; deprecations are announced before removal.
8+
9+
## Why this matters
10+
11+
`templateGenerator()` separates three concerns:
12+
13+
| Layer | Owner | Contract |
14+
|---|---|---|
15+
| **Data extraction** | per-port walker (TS / C# / Python / Java / Kotlin) | typed data dict |
16+
| **Template rendering** | shared Mustache via `@metaobjectsdev/render` | Mustache spec |
17+
| **File emission** | the runner + overwrite policy | three-way merge |
18+
19+
The middle layer is **language-agnostic**: the same Mustache template that
20+
renders `<Entity>.md` from TypeScript walks identically from C# or Python.
21+
That only holds if the data dict shape is stable across ports and across
22+
upgrades — hence this contract.
23+
24+
## Shapes by template
25+
26+
### `docs/entity-page.md``EntityDocData`
27+
28+
The per-entity Markdown documentation page. Populated by
29+
`buildEntityDocData(entity, opts)`.
30+
31+
```ts
32+
interface EntityDocData {
33+
generatedMarker: string; // "<!-- @generated by ... -->"
34+
entity: {
35+
name: string; // "Author"
36+
type: string; // "object.entity"
37+
source?: string; // "meta.blog.json"
38+
package?: string; // "acme::blog"
39+
description?: string;
40+
};
41+
descriptionQuote?: string; // "> ..." (each line of description)
42+
preambleHeader: string; // "**Type:** ...\n**Source:** ...\n**Package:** ..."
43+
storage?: { tableHeader: string; rows: StorageFieldDoc[] };
44+
hasStorage?: boolean;
45+
identities?: IdentityDoc[];
46+
hasIdentities?: boolean;
47+
relationships?: RelationshipDoc[];
48+
hasRelationships?: boolean;
49+
validation: {
50+
insertSchema: string; // "AuthorInsertSchema"
51+
updateSchema: string; // "AuthorUpdateSchema"
52+
entityFile: string; // "Author.ts"
53+
lower: string; // "author"
54+
};
55+
usedBy?: UsedByDoc[];
56+
hasUsedBy?: boolean;
57+
generated: GeneratedFileDoc[]; // "Generated code" bullets
58+
}
59+
```
60+
61+
Each `StorageFieldDoc` / `IdentityDoc` / `RelationshipDoc` / `UsedByDoc`
62+
carries one or more **already-rendered** strings (e.g. `rowLine`, `bullet`)
63+
the template emits via `{{{rowLine}}}`. The escaping rules for Markdown
64+
table pipes, code-spans, etc. live in the **builder** so templates stay
65+
trivial and cross-port walkers don't have to re-derive the rules.
66+
67+
**Adopter override path.** Drop a `templates/docs/entity-page.md.mustache`
68+
file into your project; the project's `templates/` directory takes precedence
69+
over the framework defaults. Same for any partial under `templates/docs/`.
70+
71+
### Section-presence flags (`hasX`)
72+
73+
Mustache cleanly iterates an array (`{{#identities}}...{{/identities}}`) but
74+
has no native "is this array non-empty?" check that doesn't also iterate.
75+
To emit a section header (`## Identity`) ONCE per section, we provide both:
76+
77+
- `identities: IdentityDoc[]` — drive the bullet loop
78+
- `hasIdentities: true` — drive the section-header conditional
79+
80+
```mustache
81+
{{#hasIdentities}}
82+
83+
## Identity
84+
85+
{{#identities}}
86+
- {{{bullet}}}
87+
{{/identities}}
88+
{{/hasIdentities}}
89+
```
90+
91+
Same pattern for `hasStorage`, `hasRelationships`, `hasUsedBy`.
92+
93+
## Versioning policy
94+
95+
- **Adding a new field** is a minor-version event (`0.7.x → 0.8.x`).
96+
Existing templates keep working.
97+
- **Renaming or removing a field** requires a deprecation cycle:
98+
- Major version N: the new name is added; the old name is kept and
99+
emits a deprecation warning at codegen time. The
100+
`meta verify --strict` check fails on use of the deprecated key.
101+
- Major version N+1: the old name is removed.
102+
- **Re-shaping a nested object** (e.g. flattening `validation.lower` to
103+
top-level `lowerName`) is a breaking change requiring the same
104+
deprecation cycle.
105+
106+
The conformance fixtures gate the shapes per port: any port whose walker
107+
emits an out-of-spec shape fails the cross-port conformance suite.
108+
109+
## Where to find the types
110+
111+
```ts
112+
import type {
113+
EntityDocData,
114+
StorageFieldDoc,
115+
IdentityDoc,
116+
RelationshipDoc,
117+
UsedByDoc,
118+
GeneratedFileDoc,
119+
} from "@metaobjectsdev/codegen-ts/generators";
120+
121+
import { buildEntityDocData, templateGenerator } from "@metaobjectsdev/codegen-ts/generators";
122+
```
123+
124+
Each type is intentionally narrow — fields are required iff the corresponding
125+
section MUST appear in every entity's docs output. Optional fields use
126+
`?` and templates check via `{{#fieldName}}...{{/fieldName}}`.
127+
128+
## Adding new template families
129+
130+
To add a new shape (e.g. for an HTML docs site, an OpenAPI spec, or a
131+
Mermaid diagram):
132+
133+
1. Export a typed `interface XxxData` from a sibling module of
134+
`docs-data.ts`.
135+
2. Provide a `buildXxxData(input, opts)` builder.
136+
3. Ship a default template at `templates/<group>/<source>.mustache` inside
137+
`codegen-ts`.
138+
4. Add a conformance fixture that locks the expected output.
139+
5. Document the new shape on this page under a new section.
140+
141+
The `templateGenerator({ walk, template, format })` factory does the rest —
142+
no new generator scaffolding is needed.

server/typescript/packages/codegen-ts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"default": "./dist/generators/index.js"
1818
}
1919
},
20-
"files": ["dist", "src", "README.md", "LICENSE"],
20+
"files": ["dist", "src", "templates", "README.md", "LICENSE"],
2121
"scripts": {
2222
"build": "tsc -p .",
2323
"typecheck": "tsc -p tsconfig.typecheck.json"
@@ -35,6 +35,7 @@
3535
"publishConfig": { "access": "public" },
3636
"dependencies": {
3737
"@metaobjectsdev/metadata": "workspace:*",
38+
"@metaobjectsdev/render": "workspace:*",
3839
"@biomejs/js-api": "^0.7.0",
3940
"@biomejs/wasm-nodejs": "^1.9.4",
4041
"ts-poet": "^6.10.0"
@@ -46,7 +47,6 @@
4647
"devDependencies": {
4748
"@biomejs/biome": "^1.9.0",
4849
"@metaobjectsdev/codegen-ts-react": "workspace:*",
49-
"@metaobjectsdev/render": "workspace:*",
5050
"bun-types": "latest",
5151
"drizzle-orm": "^0.36.0",
5252
"hono": "^4.6.0",

0 commit comments

Comments
 (0)