0061. Generators are Jig templates; a project can export and override them
0061. Generators are Jig templates; a project can export and override them
Status
Accepted
Date
2026-10-05
Deciders
operator (Saulo Vallory)
Context
An emitter is the build step that writes one kind of generated file from the model: types, validators, handlers, the database schema (build pipeline, stage 7). M1 and M2 wrote emitters as TypeScript functions that build text and pass it through a pinned formatter (generated code and the guard). Generated code is committed and guarded, and a hand edit to it fails the build (ADR-0003, ADR-0058).
A project that needs different generated code (a house convention, an extra trace attribute) had no way to get it. The user docs proposed exporting the templates into the project and asked the operator to judge the developer experience (customising generated code). Jig is the operator’s template engine for code generation.
Decision
Operator, 2026-10-05, rulings of 2026-10-04, section “Entity file syntax, continued (2026-10-05 morning, operator)”, row “Generators”:
Mesh’s generators become Jig templates (the operator’s template engine for code generation).
mesh export generatorscopies them into the project; a project template overrides Mesh’s own per template, when it exists. Emitters split in two: TypeScript computes a typed view of the model, the template only renders it.
So each emitter has two halves:
- A view, in TypeScript: a pure function from the model and the configuration to a typed, plain-data object holding exactly what the file needs (names already cased, imports already resolved, input plans already computed). All decisions are here, and they are unit-tested here.
- A template, in Jig: renders the view to text. It makes no decision a test would need to cover.
The output still passes through the pinned formatter and stays deterministic, so the guard is unchanged. mesh export generators copies Mesh’s templates into the project. At build time, for each template, the project’s copy is used when it exists and Mesh’s otherwise. The view type each template receives is the documented contract a project template depends on.
Options considered
Option A: Jig templates over typed views, overridable per template (chosen)
Pros: projects can change generated code without forking Mesh; the logic stays in tested TypeScript; templates are short and readable.
Cons: a project that overrides a template stops receiving Mesh’s fixes to it; the view types become a public contract Mesh must keep stable.
Option B: TypeScript emitters, no override (M1 and M2 as built)
Pros: one language; no template engine dependency.
Cons: no escape hatch; text building mixed with decisions.
Option C: named hooks inside each template, no full override
Pros: upgrades still reach the project.
Cons: every seam must be designed and kept stable; reach is limited to the seams. It can be added later on top of Option A.
Trade-off analysis
Option A gives projects full reach and keeps Mesh’s own emitters easier to test. The upgrade cost falls only on projects that choose to override, and the per-template granularity keeps it to the files they changed.
Consequences
- The Jig port is its own task after the realignment task and before M2 resumes (ADR-0064): the types and validators emitters first.
- The emitter interface the extension host registers in M6 is “a view function plus a template” (extension host).
- Jig becomes a build-time dependency of
@meshfw/compiler, pinned to an exact version like the formatter; it never reaches the run-time library. - Where the exported templates live in a project follows the user docs.
Action items
- Jig port: split the types and validators emitters into view and template; keep the generated bytes identical.
- Jig port:
mesh export generatorsand the per-template lookup. - M2 onward: every new emitter is written as a view and a template.