@metaobjectsdev/sdk 0.24.4 → 0.25.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 (59) hide show
  1. package/README.md +1 -7
  2. package/agent-context/servers/csharp.meta.json +3 -1
  3. package/agent-context/servers/java.meta.json +3 -1
  4. package/agent-context/servers/kotlin.meta.json +3 -1
  5. package/agent-context/servers/python.meta.json +3 -1
  6. package/agent-context/servers/typescript.meta.json +3 -1
  7. package/agent-context/skills/metaobjects-audit/SKILL.md +15 -8
  8. package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +6 -1
  9. package/agent-context/skills/metaobjects-audit/references/csharp.md +10 -1
  10. package/agent-context/skills/metaobjects-audit/references/java.md +10 -0
  11. package/agent-context/skills/metaobjects-audit/references/kotlin.md +11 -0
  12. package/agent-context/skills/metaobjects-audit/references/python.md +12 -2
  13. package/agent-context/skills/metaobjects-audit/references/typescript.md +7 -2
  14. package/agent-context/skills/metaobjects-authoring/SKILL.md +35 -6
  15. package/agent-context/skills/metaobjects-codegen/SKILL.md +249 -10
  16. package/agent-context/skills/metaobjects-codegen/references/csharp.md +1 -0
  17. package/agent-context/skills/metaobjects-codegen/references/java.md +1 -0
  18. package/agent-context/skills/metaobjects-codegen/references/kotlin.md +1 -0
  19. package/agent-context/skills/metaobjects-codegen/references/python.md +1 -0
  20. package/agent-context/skills/metaobjects-codegen/references/typescript.md +190 -6
  21. package/agent-context/skills/metaobjects-prompts/references/typescript.md +10 -1
  22. package/agent-context/skills/metaobjects-runtime-ui/SKILL.md +22 -0
  23. package/agent-context/skills/metaobjects-runtime-ui/references/csharp.md +15 -0
  24. package/agent-context/skills/metaobjects-runtime-ui/references/java.md +33 -0
  25. package/agent-context/skills/metaobjects-runtime-ui/references/kotlin.md +23 -0
  26. package/agent-context/skills/metaobjects-runtime-ui/references/python.md +25 -0
  27. package/agent-context/skills/metaobjects-runtime-ui/references/tanstack.md +34 -7
  28. package/agent-context/skills/metaobjects-runtime-ui/references/typescript.md +20 -0
  29. package/agent-context/skills/metaobjects-verify/SKILL.md +28 -0
  30. package/agent-context/templates/always-on.md.mustache +45 -4
  31. package/dist/agent-context/assemble.d.ts.map +1 -1
  32. package/dist/agent-context/assemble.js +52 -4
  33. package/dist/agent-context/assemble.js.map +1 -1
  34. package/dist/agent-context/scaffold.d.ts +16 -1
  35. package/dist/agent-context/scaffold.d.ts.map +1 -1
  36. package/dist/agent-context/scaffold.js +81 -4
  37. package/dist/agent-context/scaffold.js.map +1 -1
  38. package/dist/forge-types.d.ts.map +1 -1
  39. package/dist/forge-types.js +19 -1
  40. package/dist/forge-types.js.map +1 -1
  41. package/package.json +4 -9
  42. package/src/agent-context/assemble.ts +86 -6
  43. package/src/agent-context/scaffold.ts +91 -5
  44. package/src/forge-types.ts +21 -0
  45. package/dist/agent-docs/body.d.ts +0 -6
  46. package/dist/agent-docs/body.d.ts.map +0 -1
  47. package/dist/agent-docs/body.js +0 -593
  48. package/dist/agent-docs/body.js.map +0 -1
  49. package/dist/agent-docs/content-hash.d.ts +0 -8
  50. package/dist/agent-docs/content-hash.d.ts.map +0 -1
  51. package/dist/agent-docs/content-hash.js +0 -23
  52. package/dist/agent-docs/content-hash.js.map +0 -1
  53. package/dist/agent-docs/index.d.ts +0 -3
  54. package/dist/agent-docs/index.d.ts.map +0 -1
  55. package/dist/agent-docs/index.js +0 -4
  56. package/dist/agent-docs/index.js.map +0 -1
  57. package/src/agent-docs/body.ts +0 -592
  58. package/src/agent-docs/content-hash.ts +0 -25
  59. package/src/agent-docs/index.ts +0 -8
@@ -24,8 +24,28 @@ You run a `gen` step. The runner:
24
24
  2. Resolves output targets and precomputes shared render state.
25
25
  3. Runs each configured **generator** — most emit one file per entity; some emit a
26
26
  single shared file (a barrel, a DB-context, an app-config).
27
- 4. Refuses to overwrite any file that does NOT carry the `@generated` header;
28
- overwrites the ones that do.
27
+ 4. Decides whether it may overwrite a file and **the rule differs by port**:
28
+ - **TypeScript, C#, Python** — by a committed **hash manifest**
29
+ (`.metaobjects/.gen-state/.hashes.json`). If a file still hashes to what the
30
+ generator recorded writing, it is safe to overwrite; if it was edited, or there is
31
+ no record of it, the write is **refused by name**. TypeScript additionally
32
+ three-way-merges against a snapshot when one is present locally.
33
+ - **Java, Kotlin** — by a bare **`GENERATED`** token in the file's header comment
34
+ (`GeneratedFileWriter.GENERATED_MARKER`). The token is `GENERATED`, **not**
35
+ `@generated`: the matcher allows only whitespace between the comment punctuation
36
+ and the token, so an `@`-prefixed tag does not match it. Remove that token and
37
+ regeneration never touches the file again.
38
+
39
+ **The two rules protect a hand edit in opposite ways, so do not carry a habit across
40
+ ports.** On TypeScript, C# and Python, *editing the content* is what takes ownership —
41
+ that is what breaks the hash — and deleting the header changes nothing except your
42
+ ability to tell what generated the file. On Java and Kotlin the reverse holds: editing
43
+ a file protects it not at all, because the `GENERATED` token is still there and the
44
+ next run overwrites the edit; only removing that token does.
45
+
46
+ (The header text differs per port and none of it is the write decision on the
47
+ hash-manifest ports: TypeScript and Python emit `@generated by …`, C# emits
48
+ `<auto-generated/>`, Java and Kotlin emit the `GENERATED` token above.)
29
49
 
30
50
  The output is normal idiomatic code in your language — you import it and use it
31
51
  like any hand-written module.
@@ -35,8 +55,11 @@ like any hand-written module.
35
55
  Every emitted file carries a `@generated` header. This is load-bearing:
36
56
 
37
57
  - **Never hand-edit a file with a `@generated` header for a change you want to
38
- keep.** The next `gen` run overwrites it. If you need different output, change
39
- the metadata (or the template), not the generated file.
58
+ keep.** The next `gen` run overwrites it. If you need different output, change the
59
+ metadata, or change the generator that emits it.
60
+ - **This rule is about emitted output — it is not a rule about your generators.** A
61
+ generator you own carries no `@generated` header and is edited like any other source
62
+ file in your repo. See the next section before you conclude a shape is unreachable.
40
63
  - **Hand-written regions are preserved by three-way merge.** Where the codegen
41
64
  supports designated hand-editable regions, regeneration runs a three-way merge
42
65
  (base → yours → newly-generated) so your edits survive a regen. Code review is
@@ -54,6 +77,43 @@ for a shape origins can express is drift the drift gate can't even see. A genuin
54
77
  loose either — it goes in the `source.rdb` **`@sql`** escape (#208, ADR-0043) so the
55
78
  tool still registers, fingerprints, and drift-checks it (see the projection bullet).
56
79
 
80
+ ## Your generators are yours — editing one needs no permission
81
+
82
+ A generator in your repo is your code, not a vendor artifact. ADR-0034 is
83
+ **scaffold-and-own**: the generators the scaffolded config wires are copied into your
84
+ repo at init, and every reference template's header says so in its own first line
85
+ ("copy this into your repo … and own it", "now YOURS to change"). None carries a
86
+ `@generated` header. Editing one is ordinary work.
87
+
88
+ **A standing rule not to change the MetaObjects repo is not a rule about your
89
+ generators.** They are different repositories, and you own yours outright. That
90
+ generalisation is the observed failure mode, not a hypothetical: an agent told not to
91
+ touch upstream quietly drops "edit the generator" from the moves available to it and
92
+ hand-writes the layer instead — the single outcome the rest of this skill exists to
93
+ prevent. If you are about to hand-write something data-shaped because the generated
94
+ shape is wrong, the generator is the file to open, and you do not need to ask first.
95
+
96
+ **The order when generated output does not fit:**
97
+
98
+ 1. **The metadata**, if the model is wrong — wrong column type, missing relationship,
99
+ a join that should be a projection. Fix the spine first; it fixes every port at once.
100
+ 2. **Your own generator**, if the model is right and the *emit* is wrong — naming, file
101
+ layout, imports, framework, signatures.
102
+ 3. **Hand-write**, only for what metadata genuinely cannot express — and wire it to the
103
+ generated types.
104
+
105
+ Hand-writing something the metadata already describes is step 3 used as step 1.
106
+
107
+ **The converse, so ownership does not become sprawl:** wire a generator only for output
108
+ you will actually consume. Decide per generator, narrow one with its own `filter`, and
109
+ own the ones you keep — an emitted file nobody imports still reads as an invitation to
110
+ adopt the surface you decided against.
111
+
112
+ How you get a generator's source differs per port — a copy command on TypeScript,
113
+ implementing the port's generator interface elsewhere. Your language reference has the
114
+ mechanism; see also "The commands and config keys that implement the steps above differ
115
+ per port" below.
116
+
57
117
  ## Selecting generators by stable name
58
118
 
59
119
  Codegen is a set of named generators you opt into. Each generator has a **stable
@@ -150,12 +210,13 @@ don't silently churn the existing code.
150
210
 
151
211
  ## Write your own generators — the built-ins rarely fit an app exactly
152
212
 
153
- The built-in generators (entity, queries, routes, form, grid, barrel) cover the
154
- common shape, but **real apps routinely need output the built-ins don't emit as-is**
155
- — a bespoke REST contract, custom DTO/response shapes, an app-specific service or
156
- repository layer, a UI the defaults don't produce. When that happens the model-first
157
- move is **not** to abandon metadata and hand-write the layer. Write a **custom
158
- generator** that reads the same metadata and emits *your* app's shape.
213
+ The built-in generators (entity, queries, routes, routes-hono, barrel, form, hooks,
214
+ grid, grid-hook) cover the common shape, but **real apps routinely need output the
215
+ built-ins don't emit as-is** — a bespoke REST contract, custom DTO/response shapes,
216
+ an app-specific service or repository layer, a UI the defaults don't produce. When
217
+ that happens the model-first move is **not** to abandon metadata and hand-write the
218
+ layer. Write a **custom generator** that reads the same metadata and emits *your*
219
+ app's shape.
159
220
 
160
221
  Treat this as a first-class, expected activity — not an escape hatch. A custom
161
222
  generator is still model-first: it derives from the metadata spine, so it
@@ -163,6 +224,12 @@ regenerates on change and stays consistent across every entity — the leverage
163
224
  forfeit by hand-writing. Hand-rolling *away from* metadata is the anti-pattern;
164
225
  generating *your own shape from* metadata is the point.
165
226
 
227
+ This is for when the *shape* itself needs to change. If a built-in's shape is
228
+ already right and only the *target* is wrong — a different framework than the
229
+ shipped reference emits for — take ownership of that generator instead of writing
230
+ one from scratch; see "Your framework isn't the default" below, and your language
231
+ reference for the command that does it.
232
+
166
233
  The plugin interface is small (`@metaobjectsdev/codegen-ts`): a `Generator` is
167
234
  `{ name, filter?, generate }`, where `generate(ctx)` returns `EmittedFile[]`
168
235
  (`{ path, content }`). `perEntity` / `oncePerRun` wrap the common cases:
@@ -190,6 +257,31 @@ the `generators` array in `metaobjects.config.ts` next to the built-ins — it r
190
257
  the same pass, writes under the same target rules, and carries the `@generated`
191
258
  header so it round-trips like any other.
192
259
 
260
+ ## Your framework isn't the default — the retargeting procedure
261
+
262
+ If the shipped templates do not emit for your stack, retargeting is the **normal first
263
+ move** — not a workaround and not a sign of a bug. Owning a generator is the supported
264
+ path to any framework; MetaObjects does not ship a codegen package per framework and is
265
+ not waiting to.
266
+
267
+ The doctrine, in order of what to try:
268
+
269
+ 1. **Check config first.** Several apparent codegen failures are one config value
270
+ (module-specifier style, output directory, dialect, API prefix). Change it and retest
271
+ before writing any code.
272
+ 2. **Own the generator, not the renderer.** Take a copy of the reference template for the
273
+ artifact that is wrong and edit the one step your framework disagrees about. Each
274
+ template's header names what its emit is coupled to and which call to swap.
275
+ 3. **Compose, do not fork.** Call the exported render function and wrap its result where
276
+ you can, so you keep receiving upstream fixes. Forking a whole renderer is the thing
277
+ to avoid — not owning the generator.
278
+ 4. **Server-tier output is usually already portable.** The entity module and the query
279
+ helpers carry no HTTP-framework coupling; retargeting is usually only needed at the
280
+ routes and UI tiers.
281
+
282
+ Hand-rolling *away from* metadata is the anti-pattern. Generating *your own shape from*
283
+ metadata is the point.
284
+
193
285
  ### Never read metadata through an `own*()` accessor (ADR-0039) — top bug source
194
286
 
195
287
  When writing OR reviewing a generator, **read every field/node property and iterate
@@ -239,6 +331,153 @@ output/template · doesn't fit → write a generator that emits your shape *from
239
331
  metadata* · only the genuinely un-modelable (business algorithms, external calls) is
240
332
  hand-written outside codegen — and it still imports the generated types.
241
333
 
334
+ ## Two ways to author a generator — pick deliberately
335
+
336
+ A generator can be **programmatic** (code that builds the output) or **declarative** (a
337
+ Mustache template plus a scope). Both are first-class, both ship in every port, and they
338
+ are good at different things.
339
+
340
+ | | Programmatic | Declarative template |
341
+ |---|---|---|
342
+ | What you write | a `Generator` in the port's language, using its AST builder (ts-poet, KotlinPoet, …) | a `.mustache` file + `{ template, scope, outputPattern, format? }` |
343
+ | Output shape | expressed in code | **is the file you are editing** |
344
+ | Cross-language | per-port by construction | one template emits for any language — it renders against the neutral, byte-gated data dict |
345
+ | Logic | any | what a template can express: sections, iteration, presence flags |
346
+
347
+ **The rule:** reach for **programmatic** when the logic is gnarly or the run is hot; reach
348
+ for a **template** when the *shape* is what you are iterating on, or when you want the same
349
+ output across languages. `scope` is `perEntity` / `perPackage` / `perModel` — the walk you
350
+ would otherwise hand-write — and `outputPattern` is the output path per item, with
351
+ `{name}` / `{Name}` / `{package}` placeholders (e.g. `"{package}/{Name}Service.java"`).
352
+ Full tradeoff table and the data dict: `docs/features/codegen-concepts.md` §3 and §10.
353
+ **Asking whether a base/extension split or a write-if-absent file exists? That's §5-§7, not
354
+ here.** §5 (*Preserving hand edits*) states MetaObjects ships exactly one hand-edit strategy —
355
+ no shipped generator on any port emits a generated-base + hand-owned-concrete pair, or a
356
+ write-if-absent file; §6 names the `skip-existing` merge strategy `runGen` accepts for
357
+ building that pair yourself, reachable only from a programmatic caller (no CLI flag selects
358
+ it); §7 (*Safety*) is the per-port write-decision mechanism behind "What codegen does" step 4
359
+ above.
360
+
361
+ **A template is not limited to documents.** It emits source as readily as docs — that is
362
+ what the neutral data dict is for.
363
+
364
+ ### Which is available to you depends on the port — check before you plan
365
+
366
+ **TypeScript** has both, and the whole programmatic procedure is documented: `meta eject`,
367
+ the `metaobjects.config.ts` keys, the exported `render*` functions — see this skill's
368
+ `references/typescript.md`. The declarative path is declared in the SAME config: call
369
+ `templateGenerator()` in `generators`, or spread a parsed JSON spec with
370
+ `templateSpecToGenerators(parseTemplateSpec(...))` to reuse one written for C#/Python.
371
+ **There is no `--template-spec` flag on `meta gen` and its absence is not a gap** — the
372
+ config takes generator values, and keeping the declaration there is what keeps
373
+ `meta verify --codegen` regenerating with it.
374
+
375
+ **Java / Kotlin** have both. **No eject command** — a programmatic generator means
376
+ implementing `com.metaobjects.generator.Generator` and naming your class in the Maven
377
+ `<generator>` element, which the plugin loads from the project classpath. The declarative
378
+ path is `TemplateScopeGenerator`, wired the same way with `<template>` / `<scope>` /
379
+ `<outputPattern>` / `<format>` / `<templatesDir>` (plus the standard `<outputDir>`), and
380
+ covers Java and Kotlin alike. No `--template-spec` flag here either, for the same reason:
381
+ `<generator>` already loads a consumer class from the project classpath.
382
+
383
+ **C# and Python: the declarative path is your only option, and it is a real one.** Their
384
+ generator sets are **closed built-in registries** — `--generators` *selects* from what
385
+ ships, and there is no seam to register a `Generator` of your own. (Python's
386
+ `--provider module:symbol` registers **metamodel vocabulary**, not a generator; do not
387
+ reach for it here.) Use `--template-spec <json>` — plus `--templates <dir>` on Python or
388
+ `--template-root <dir>` on C# — and your entries are appended to the default suite. Worked
389
+ examples with the full JSON: `docs/ports/python.md` and `docs/ports/csharp.md`.
390
+
391
+ **The spec is auto-discovered, and that is load-bearing.** With no `--template-spec`, both
392
+ ports read `<projectRoot>/template-spec.json` — projectRoot being the metadata dir's parent.
393
+ Keep it there: `verify --codegen` accepts no `--template-spec` flag, so the conventional path
394
+ is how the drift gate learns your template generators exist. Put the spec somewhere else and
395
+ reach it only by flag, and `verify` regenerates without it and reports its output as stale.
396
+
397
+ So on C#/Python, "I need a shape the built-ins do not emit" is answered by a template, not
398
+ by writing generator code. Do not conclude the port cannot be customized.
399
+
400
+ Each port's `references/` fragment documents what its built-ins emit, which is what you
401
+ compare your own emit against; they do not carry a step-by-step retargeting procedure.
402
+
403
+ ## Never hand-write a physical name — the generator emits them
404
+
405
+ A table name, a column name, a schema — these are declared in metadata and derived by the
406
+ same resolver the migration and the runtime use. A string literal for one is a magic string
407
+ that no compiler checks and no gate catches, and it goes wrong silently: `@column` is
408
+ free-form, so a field named `callPurpose` may map to a column named `purpose_code`, which is
409
+ neither the field name nor any transformation of it. A consumer deriving the column as
410
+ `to_snake_case(<the field's name>)` gets that case wrong and never finds out.
411
+
412
+ Every port emits a per-object names artifact. Reference it:
413
+
414
+ ```ts
415
+ import { ProgramNames } from "./generated/Program.names.js";
416
+
417
+ ProgramNames.name // "Program" — the OBJECT's name
418
+ ProgramNames.sources.primary.table // "programs" — physical table
419
+ ProgramNames.sources.primary.kind // "table" — table | view | proc | …
420
+ ProgramNames.fields.createdAt.name // "createdAt" — logical / wire name
421
+ ProgramNames.fields.createdAt.column // "created_at" — physical column
422
+ ProgramNames.indexes.ix_prog_owner.index // "ix_prog_owner" — database index name
423
+ ```
424
+
425
+ The artifact mirrors the metadata tree: every node carries its own `type`, `subType` and
426
+ `name`, and a physical name sits under the key that says **what kind of database object it
427
+ is**. A view is `sources.primary.view`, a stored proc `sources.primary.proc`, and a
428
+ write-through entity's read view `sources.replica.view` — so `sources.replica.table` is a
429
+ compile error rather than a wrong answer. There is no `readOnly`: it was derived from
430
+ `kind`, never declared, so ask `kind`.
431
+
432
+ The artifact is per-object and the shape is per-language; the guarantee is the same
433
+ everywhere — **each physical name is spelled once, and generated code references it**:
434
+
435
+ | Port | Artifact | Reads as |
436
+ |---|---|---|
437
+ | TypeScript | `<Entity>.names.ts` | `ProgramNames.fields.createdAt.column` |
438
+ | C# | `<Entity>Names.g.cs` | `ProgramNames.CreatedAtColumn` |
439
+ | Java | `<Entity>Names.java` | `ProgramNames.CREATED_AT_COLUMN` |
440
+ | Kotlin | `<Entity>Names.kt` | `ProgramNames.CREATED_AT_COLUMN` |
441
+ | Python | `<entity_snake>_names.py` | `PROGRAM_CREATED_AT_COLUMN` |
442
+
443
+ On TypeScript (`meta init`), C# and Python the names generator is in the default suite; on
444
+ the JVM it is opt-in — add `SpringNamesGenerator` / `KotlinNamesGenerator` to the pom's
445
+ `<generators>`, and the Exposed table binding switches to the constants once it is present.
446
+
447
+ **It follows `extends`.** An object that extends another does not restate what it
448
+ inherits: C# and Java use real class inheritance (`class CopayAuthNames extends
449
+ AuthNames`), TypeScript spreads (`...AuthNames.fields`), and Kotlin and Python re-export
450
+ the parent's constants by reference. An abstract base a persisted object extends gets an
451
+ artifact of its own — columns only, no table name, because it has none. So if you are
452
+ reading a subtype's artifact and its table name is not there, it is on the base, which is
453
+ where it belongs.
454
+
455
+ **Where generated code consumes it, and where it does not.** TypeScript, C# and Kotlin
456
+ bind an ORM (Drizzle, EF Core, Exposed) and so must spell physical names — their generated
457
+ code references these constants, and a cross-port gate proves no generated file spells one
458
+ literally. **Java and Python generate no SQL at all**: their DTOs and models carry logical
459
+ names, and persistence is the repository interface/`Protocol` you implement. There the
460
+ artifact exists *for your code*, which is the only place a physical name appears.
461
+
462
+ Two categories stay literal, deliberately, and the gate pins them as such rather than
463
+ exempting them:
464
+
465
+ - a **flattened value-object column** (`@storage: flattened`) is a composite —
466
+ `<owner field column>_<member column>` — belonging to no single field of either object,
467
+ so there is no one constant to reference;
468
+ - a **write-through entity's replica view name**: the artifact holds the object's PRIMARY
469
+ source's name (its table), and a write-through entity has two physical names.
470
+
471
+ **But prefer a typed handle where one exists — this rule has a real limit.** If the ORM
472
+ gives you a type-checked object for the same thing, use that. Replacing a Drizzle column
473
+ object (`programs.createdAt`, checked against the schema at compile time) with a string
474
+ constant makes the code **worse**: it trades an error the compiler catches for one the
475
+ database raises at runtime. The constants are for the places with no typed handle — raw
476
+ SQL, migration scripts, log lines, an external system's column mapping, a port whose
477
+ generated model carries no persistence binding at all.
478
+
479
+ The rule is *don't invent the string*, not *replace every name with a constant*.
480
+
242
481
  ## Dialects
243
482
 
244
483
  Generated DB schema/DDL targets a SQL **dialect**:
@@ -47,6 +47,7 @@ or run the default set. Output lands under `--namespace` in `--output-dir`.
47
47
  | `callable` | `<Entity>.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). |
48
48
  | `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.prompt` `@payloadRef` / `@responseRef` (each an `object.value`): `@payloadRef` types the REQUEST, `@responseRef` the REPLY the parser/extractor bind to. |
49
49
  | `output-parser` / `extractor` / `output-prompt` / `render-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). The strict parser, the tolerant `extract`, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), and the typed render helper. See the **prompts** reference. |
50
+ | `names` | `<Entity>Names.g.cs` — `public abstract class <Entity>Names` of `const` physical database names, MIRRORING THE METADATA TREE: the object's own `Type`/`SubType`/`Name` (`Name` is the OBJECT's name, never a physical one), then per `source.rdb` keyed by `@role` — `SourcePrimaryType`/`SubType`/`Kind`, `SourcePrimarySchema` when declared, and the physical name under the alias for its `@kind` (`SourcePrimaryTable` / `SourceReplicaView` / `SourcePrimaryProc` / `…Function` / `…MaterializedView`) — then a `<Field>Field`/`<Field>Column` pair per field, then each identity and index (`IdentityPkName`, and `IdentityUqCustEmailIndex` / `IndexIxCustStatusIndex` — the DATABASE name, carried for `identity.secondary` and `index.lookup` only), then a complete `ColumnsByField`. There is no `ReadOnly`: it is a derivation over `@kind`, so ask `SourcePrimaryKind`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (its own identity + columns, and NO source members — it has no table). A class whose object extends another one **inherits** it (`class CopayAuthNames : AuthNames`) rather than restating its constants — a C# `const` is inherited, so `CopayAuthNames.IdColumn` and `CopayAuthNames.SourcePrimaryTable` resolve through the base (`Type`/`SubType`/`Name` are redeclared with `new`, since every artifact carries its own). `abstract`, not `static`, precisely so it can be inherited; it still cannot be instantiated. In the run, `entity` and `db-context` reference the constants (`[Table(OrderNames.SourcePrimaryTable)]`, `[Column(OrderNames.StatusColumn)]`, `.ToView(OrderNames.SourceReplicaView)`); a `--generators` selection omitting `names` falls back to literals. |
50
51
  | `template` | the generic Mustache `templateGenerator()` primitive. |
51
52
 
52
53
  Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the
@@ -106,6 +106,7 @@ separate `metaobjects-codegen-base` module instead.)
106
106
  | `SpringRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
107
107
  | `LlmTraceHelperGenerator` | `<Entity>TraceHelper.java` per concrete entity — the LLM-trace helper |
108
108
  | `SpringFilterAllowlistGenerator` | per-entity filter allowlist |
109
+ | `SpringNamesGenerator` | `<Entity>Names.java` — `public abstract class` of `static final String` names that MIRRORS THE METADATA TREE. Every node carries its own `TYPE`/`SUB_TYPE`/`NAME`, so `AuthorNames.NAME` is the OBJECT's name (`"Author"`), **not** a table name, and a physical name sits under the member that says what it is: `SOURCE_<ROLE>_TABLE` / `_VIEW` / `_MATERIALIZED_VIEW` / `_PROC` / `_FUNCTION`, from the metamodel's own `@kind`-to-alias map, with `<ROLE>` being `PRIMARY` or `REPLICA` (a write-through entity has a member for each). Plus a `<FIELD>_FIELD`/`<FIELD>_COLUMN` pair per field and a complete `COLUMNS_BY_FIELD`; an `identity.secondary` or `index.lookup` also carries `IDENTITY_<NAME>_INDEX` / `INDEX_<NAME>_INDEX`, while `identity.primary` deliberately carries no index member (migrate names a PK by a dialect-conditional formula this artifact must not restate). **There is no `READ_ONLY`** — it was a derivation over `@kind`, never metadata, so ask `SOURCE_<ROLE>_KIND`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (columns only, no source). A class whose object extends another one **extends** it (`class CopayAuthNames extends AuthNames`) rather than restating its constants — Java inherits static members, so `CopayAuthNames.SOURCE_PRIMARY_TABLE` and `CopayAuthNames.ID_COLUMN` both resolve through the base. **This port generates no SQL** (the repository is an interface you implement), so nothing generated consumes these — they exist for your implementation. |
109
110
  | `JavaObjectCodeGenerator` | module `metaobjects-codegen-base` (`com.metaobjects.generator.direct.object.javacode`), a separate module from the Spring generators above. Flavor-selected via the `flavor` generator arg. `flavor=pojoAware` → `class <Name> extends PojoObject` (a concrete `MetaObjectAware` class with a `(MetaObject)` constructor) — its inherited `getMetaData()` back-reference is what breaks a default Jackson/Gson mapper, see "Serializing generated objects" below. `flavor=valueObject` → `class <Name> extends ValueObject` (map-backed; less hostile to a default mapper, but still not the sanctioned serialization path). Either concrete flavor also emits a `<Name>Extractor` plus a self-registering `ObjectClassBindingProvider`. For a plain default-Jackson-friendly type, use the `codegen-spring` record surface instead — never `pojoAware`. |
110
111
 
111
112
  **Projections (read-only views).** An `object.projection` (read-only `source.rdb`
@@ -120,6 +120,7 @@ All live in `metaobjects-codegen-kotlin` under
120
120
  | `KotlinRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
121
121
  | `KotlinValidatorGenerator` | `MetadataStartupValidator.kt` + `ExposedTableValidator.kt` (once per project) |
122
122
  | `KotlinSpringConfigGenerator` | `MetadataExposedConfig.kt` — `@Configuration` wiring `Database.connect()` + the startup validator (once per project) |
123
+ | `KotlinNamesGenerator` | `<Entity>Names.kt` — an `object` of `const val` names that MIRRORS THE METADATA TREE. Every node carries its own `TYPE`/`SUB_TYPE`/`NAME`, so `AuthorNames.NAME` is the OBJECT's name (`"Author"`), **not** a table name, and a physical name sits under the member that says what it is: `SOURCE_<ROLE>_TABLE` / `_VIEW` / `_MATERIALIZED_VIEW` / `_PROC` / `_FUNCTION`, from the metamodel's own `@kind`-to-alias map, with `<ROLE>` being `PRIMARY` or `REPLICA` (a write-through entity has a member for each). Plus a `<FIELD>_FIELD`/`<FIELD>_COLUMN` pair per field and a complete `COLUMNS_BY_FIELD`; an `identity.secondary` or `index.lookup` also carries `IDENTITY_<NAME>_INDEX` / `INDEX_<NAME>_INDEX`, while `identity.primary` deliberately carries no index member (migrate names a PK by a dialect-conditional formula this artifact must not restate). **There is no `READ_ONLY`** — it was a derivation over `@kind`, never metadata, so ask `SOURCE_<ROLE>_KIND`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (columns only, no source). Kotlin has no static inheritance — an `object` cannot extend another — so an artifact whose object extends another **re-exports** the parent's constants by reference (`const val SOURCE_PRIMARY_TABLE: String = AuthNames.SOURCE_PRIMARY_TABLE`) instead of restating the literal. Wire it alongside `KotlinExposedTableGenerator`: the Maven plugin then turns the table binding's constant substitution ON automatically (`AuthorTable : Table(AuthorNames.SOURCE_PRIMARY_TABLE)`); a run without it keeps the literals, so the output still compiles. |
123
124
  | `KotlinStoredProcGenerator` | stored-procedure call wrappers for `source.rdb` `@kind="storedProc"` |
124
125
  | `KotlinFilterAllowlistGenerator` | per-entity filter allowlist |
125
126
 
@@ -61,6 +61,7 @@ a renamed physical column).
61
61
  | `routes` | a **FastAPI `APIRouter`** per writable entity (`source.rdb @kind="table"`) on the cross-port REST contract (`?filter[field][op]=`, `?sort=field:asc`, `?limit`/`?offset`, `?withCount=1` envelope, 400/404 envelopes). The router declares a repository **`Protocol`** you implement and inject. A TPH `@discriminator` base emits ONE polymorphic router: `GET /<base>(+/{id})` plus a per-subtype CRUD set at `/<base>/<discriminatorValue lowercased>` — create injects the discriminator from the URL (never the body); get/update/delete scoped to the subtype (cross-subtype → 404); discriminator immutable. Its repository `Protocol` is subtype-keyed (`subtype=None` for the polymorphic base) so your implementation applies the single-table discriminator scope. |
62
62
  | `filter-allowlist` | per-entity filter allowlist (FR-009 — the server-side field+operator allowlist the routes validate against). |
63
63
  | `payload` / `output-parser` / `output-prompt` / `extractor` / `render-helper` / `trace-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). The payload VO, the strict parser (`<template>_response_parser`), the **output-format prompt fragment** (`<template>_response_format`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), the tolerant `extract`, the typed render helper, and the LLM-trace helper. See the **prompts** reference. |
64
+ | `names` | `<entity_snake>_names.py` — module-level `Final` constants mirroring the object's metadata tree. Every node carries its own `_TYPE`/`_SUB_TYPE`/`_NAME`; `<ENTITY>_NAME` is the OBJECT's name, and a physical name sits under the member naming what it is: `<ENTITY>_SOURCE_<ROLE>_{TABLE,VIEW,MATERIALIZED_VIEW,PROC,FUNCTION}` (`<ROLE>` is `PRIMARY` or `REPLICA`, so a write-through entity's read view has a slot), plus `<ENTITY>_SOURCE_<ROLE>_{KIND,SCHEMA}`, a `<ENTITY>_<FIELD>_FIELD`/`_COLUMN` pair each, `<ENTITY>_IDENTITY_<NAME>_*` / `<ENTITY>_INDEX_<NAME>_*` carrying `_INDEX` (the database index name) for `identity.secondary` and `index.lookup`, and a complete `<ENTITY>_COLUMNS_BY_FIELD`. No `_READ_ONLY` — it was derived from `@kind`, never declared; ask `_SOURCE_<ROLE>_KIND`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (columns and keys only, no `_SOURCE_*` — it has no table and must never acquire one). Python has no static inheritance, so a module whose object extends another **imports and re-exports** the parent's constants (`AUTHOR_CREATED_AT_COLUMN: Final[str] = BASEENTITY_CREATED_AT_COLUMN`) instead of restating the literal; a TPH subtype re-exports `_SOURCE_PRIMARY_*` too, since it shares its base's table. **This port generates no SQL**, so nothing generated consumes these — they exist for the repository `Protocol` implementation you write. |
64
65
  | `template` | the generic Mustache `template` primitive. |
65
66
 
66
67
  **Projections + entity read-views.** An `object.projection` (read-only `source.rdb`
@@ -8,9 +8,11 @@ packages. Codegen runs through the Node `meta` CLI (`@metaobjectsdev/cli`, binar
8
8
  - Install
9
9
  - `metaobjects.config.ts`
10
10
  - The generators
11
+ - Declarative template-codegen (Mustache)
11
12
  - Run
12
13
  - Multiple output targets
13
14
  - Field subtype → column mapping
15
+ - Retargeting to another framework — the TypeScript procedure
14
16
 
15
17
  ## Install
16
18
 
@@ -43,6 +45,7 @@ import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack
43
45
  export default defineConfig({
44
46
  outDir: "src/generated",
45
47
  dialect: "postgres", // "postgres" | "sqlite" | "d1" (D1 is TS-only)
48
+ extStyle: "js", // "js" (default) for Node ESM / plain tsc; "none" for a bundler-resolution toolchain — see SKILL.md "Your framework isn't the default"
46
49
  apiPrefix: "/api", // flows to routes AND client fetch URLs
47
50
  columnNamingStrategy: "snake_case", // "snake_case" (default) | "literal" | "kebab-case"
48
51
  timestampMode: "string", // "string" (default, ISO-8601 wire contract) | "date" (Drizzle native Date)
@@ -80,7 +83,10 @@ PROJECT ROOT that CONTAINS the metadata — never the metadata directory itself.
80
83
 
81
84
  ## The generators
82
85
 
83
- From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral):
86
+ Server-side, framework-neutral. The first four are **scaffolded into your repo** by
87
+ `meta init` and imported from `./codegen/generators/*` (ADR-0034); the rest come from the
88
+ package main entry, `@metaobjectsdev/codegen-ts`. Do **not** import any of them from
89
+ `@metaobjectsdev/codegen-ts/generators` — that subpath is deprecated and removed at 1.0.
84
90
 
85
91
  | Generator | Emits per entity |
86
92
  |---|---|
@@ -91,6 +97,7 @@ From `@metaobjectsdev/codegen-ts/generators` (server-side, framework-neutral):
91
97
  | `promptRender()` | `render<Name>()` per `template.prompt` |
92
98
  | `outputParser()` | `<Name>.response.ts` (`parse*` / `safeParse*`) per **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: this tier is INBOUND; `template.output` is outbound only and emits nothing here). Siblings: `outputPrompt()` → `<Name>.responseFormat.ts` (the FR-010 output-format fragment, presentation via `@promptStyle`), `extractor()` → `<Name>.extractor.ts` (the tolerant `extract` mapper). |
93
99
  | `callableFile()` | `<Entity>.callable.ts` — an FR-015 `call<Entity>` wrapper for a `source.rdb` `@kind: storedProc`/`tableFunction` (args from the `@parameterRef` value object, in declaration order) |
100
+ | `namesFile()` | `<Entity>.names.ts` — `export const <Entity>Names`, mirroring the object's metadata tree. Every node carries its own `type`/`subType`/`name`; `name` is the OBJECT's name, and a physical name sits under the key naming what it is: `sources.<role>.{table,view,materializedView,proc,function}` (`<role>` is `primary` or `replica`, so a write-through entity's read view has a slot), plus `sources.<role>.{kind,schema}`, `fields.<field>.{name,column}`, and `identities.<name>` / `indexes.<name>` carrying `.index` (the database index name) for `identity.secondary` and `index.lookup`. No `readOnly` — it was derived from `kind`, never declared. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (columns only, `sources: {}` — it has no table and must never acquire one). An artifact whose object extends another spreads the parent's collections rather than restating them, and a TPH subtype spreads `...AuthNames.sources` too, since it shares its base's table. With this generator in the run, **no generated TypeScript spells a physical name at all** — table, view, proc, column, schema and index name all travel as references; drop it from the suite and they fall back to literals. |
94
101
 
95
102
  **Projections (read-only views).** For an `object.projection` (a read-only `source.rdb`
96
103
  `@kind: view` child), `entityFile()` emits a `pgView(...)` + read-only Zod + a read-only
@@ -178,8 +185,73 @@ From `@metaobjectsdev/codegen-ts-tanstack`: `tanstackQuery()` → `<Entity>.hook
178
185
  `tanstackGridHook()` → `<Entity>.grid.tsx`.
179
186
 
180
187
  `entityFile({ allowlists: false })` drops the `runtime-ts/drizzle-fastify` import
181
- for edge/worker consumers that don't mount server routes. Per-entity opt-out:
182
- `@emitTanstack: false` on the entity skips its hook + column files.
188
+ for edge/worker consumers that don't mount server routes.
189
+
190
+ **Wire a generator only for output you consume, and narrow it with its `filter`.**
191
+ Every generator factory takes `{ filter?: (entity) => boolean }`, ANDed with the
192
+ generator's built-in gates — so it can only NARROW what emits, never widen it:
193
+ `tanstackQuery({ filter: (e) => e.name !== "InternalAudit" })` emits no hooks for
194
+ that entity. There is no `@emit*` metadata attribute to do this — `@emitTanstack`,
195
+ `@emitRoutes`, `@emitForm`, `@emitGrid` and `@emitAngular` were never registered
196
+ vocabulary, so they passed `meta gen` and failed `meta verify`. If a project carries
197
+ one, `meta upgrade --apply` removes it.
198
+
199
+ The one thing a `filter` can't express is opting a TPH subtype IN to its own
200
+ per-subtype grid (that WIDENS): `tanstackGrid({ tphSubtypeGrids: (e) => … })`,
201
+ default `() => false`. Pass the same predicate to `tanstackGridHook()` or you get
202
+ a `<Sub>.grid.ts` whose `<Sub>.columns.tsx` is never emitted.
203
+
204
+ ## Declarative template-codegen (Mustache)
205
+
206
+ Everything above is the **programmatic** path. A generator can also be **declarative** —
207
+ a Mustache template plus a scope, no generator code — and on TypeScript you have both.
208
+ Pick a template when the output SHAPE is what you are iterating on, or when you want the
209
+ same output across languages; pick programmatic when the logic is gnarly or the run is
210
+ hot.
211
+
212
+ **There is no `--template-spec` flag on `meta gen`.** Do not look for one and do not
213
+ report its absence as a gap. `metaobjects.config.ts` takes generator VALUES, so a
214
+ template generator is declared there like any other — which is also what keeps it
215
+ visible to `meta verify --codegen`, a gate that re-runs the config's generator list.
216
+
217
+ ```ts
218
+ import { templateGenerator } from "@metaobjectsdev/codegen-ts";
219
+
220
+ export default defineConfig({
221
+ generators: [
222
+ entityFile(),
223
+ templateGenerator({
224
+ name: "entity-service",
225
+ template: "service/entity-service", // → templates/service/entity-service.mustache
226
+ scope: "perEntity", // "perEntity" | "perPackage" | "perModel"
227
+ outputPattern: "{package}/{Name}Service.ts",
228
+ }),
229
+ ],
230
+ });
231
+ ```
232
+
233
+ - `template` resolves under the project's `templates/` dir first, then framework defaults.
234
+ - `outputPattern` placeholders: `{name}`, `{Name}`, `{package}` (its `::` segments become
235
+ nested directories). An unknown placeholder throws.
236
+ - `scope` and `walk` are mutually exclusive — supply exactly one. `walk` is the escape
237
+ hatch for a walk none of the three scopes expresses.
238
+ - Abstract objects are excluded from every scope.
239
+
240
+ **Reusing a C#/Python spec.** Those ports declare the same generators as a JSON
241
+ template-spec because their registries are closed and the flag is their only seam. Parse
242
+ it and spread it:
243
+
244
+ ```ts
245
+ import { parseTemplateSpec, templateSpecToGenerators } from "@metaobjectsdev/codegen-ts";
246
+
247
+ const spec = parseTemplateSpec(JSON.parse(readFileSync("./template-spec.json", "utf8")));
248
+ // generators: [entityFile(), ...templateSpecToGenerators(spec)]
249
+ ```
250
+
251
+ Portability runs ONE way: TS also accepts a `target` field that the CLI ports reject, so
252
+ a spec written there always runs here, but not the reverse. Keep `target` out of a shared
253
+ spec. The data dict a template renders against is the cross-port byte-gated contract —
254
+ `docs/features/codegen-data-shapes.md`.
183
255
 
184
256
  ## Run
185
257
 
@@ -189,9 +261,18 @@ npx meta gen --dry-run # preview without writing
189
261
  npx meta gen Author Post # scope to named entities
190
262
  ```
191
263
 
192
- Generated files carry an `@generated by @metaobjectsdev/codegen-ts` header; the
193
- runner overwrites those and refuses to touch files without it. Hand-customizations
194
- that metadata can't express live in sibling `<Entity>.extra.ts` files.
264
+ Generated files carry an `@generated by @metaobjectsdev/codegen-ts` header. It is
265
+ **informational** the write decision never reads it. `.metaobjects/.gen-state/`
266
+ decides: the snapshot body if this machine has one (three-way merge), otherwise the
267
+ committed `.hashes.json` (byte-for-byte what it wrote ⇒ overwrite; anything else ⇒
268
+ refused, path named, exit 1). So the merge is machine-local: a file you edited and
269
+ pushed is REFUSED on a fresh clone or in CI, not merged. Recovery is in
270
+ `docs/features/own-your-codegen.md`.
271
+
272
+ Hand-customizations that metadata can't express go in a sibling module you create and
273
+ import yourself — `<Entity>.extra.ts` by convention. The name carries no tool behaviour:
274
+ the file is safe because codegen writes only the paths it records, and the generated
275
+ barrel (built from the model, not a directory listing) does **not** re-export it.
195
276
 
196
277
  **Output format:** `meta gen` (and the CLI generally) is TTY-aware — human-readable
197
278
  text on a terminal, TOON on a pipe or agent. Override with `--format toon|json|text`.
@@ -231,3 +312,106 @@ The VO type, its Zod `InsertSchema`, and this `.$type<>()` all import the VO fro
231
312
  the same module (layout/package/`extStyle`-aware resolution). An opaque jsonb column
232
313
  (`field.string @dbColumnType: jsonb`) gets no `.$type<>()` — it stays `unknown`,
233
314
  which is the correct shape for freeform payloads with no fixed VO.
315
+
316
+ ## Retargeting to another framework — the TypeScript procedure
317
+
318
+ This is the TypeScript implementation of the retargeting doctrine in SKILL.md
319
+ ("Your framework isn't the default"). Read that first for the order of moves;
320
+ everything below — `meta eject`, `metaobjects.config.ts` keys, the exported
321
+ `render*` functions — is Node-CLI-specific and exists only on this port.
322
+
323
+ The shipped reference templates emit for **Fastify on Node** (plus a Hono variant) with
324
+ Drizzle and Zod. If that is not your stack, retargeting is the **normal first move** — not
325
+ a workaround and not a sign of a bug. Each template's header carries a `targets:` line
326
+ naming exactly what its emit is coupled to and which call to swap.
327
+
328
+ Work the list in order; the first two cost nothing.
329
+
330
+ **1. Check the target-shaped config first.** Several apparent codegen failures are one
331
+ config value in `metaobjects.config.ts`:
332
+
333
+ - **`extStyle`** — `"js"` emits `./Entity.js` specifiers, correct for Node ESM and a plain
334
+ `tsc` with `nodenext`. Bundlers disagree on whether they perform the TypeScript
335
+ `.js`→`.ts` rewrite: it fails outright under **Turbopack** — including between two
336
+ generated files, which makes the whole generated tree unresolvable — while Vite and
337
+ esbuild are documented to accept it and webpack needs `resolve.extensionAlias` to do the
338
+ same. **If a generated import fails to resolve, set `extStyle: "none"` and retest** for
339
+ your toolchain rather than assuming either setting from this list.
340
+ - **`clientDirective`** — `true` prepends `"use client";` to the generated form, hooks,
341
+ columns and grid-hook modules. Defaults to `false`. **Set it if your framework compiles
342
+ server and client from one tree** (React Server Components — Next.js App Router and
343
+ friends); leave it off otherwise, where the directive is inert and some bundlers warn
344
+ about it.
345
+ - **`outDir`** / **`targets`** — where output lands, per generator.
346
+ - **`apiPrefix`**, **`dialect`** — route mounting and column mapping.
347
+
348
+ **2. Ask whether your framework splits the module graph.** Some frameworks compile server
349
+ and client from one source tree and resolve each half under *different export conditions*
350
+ (React Server Components, Angular universal, Qwik). Where they do:
351
+
352
+ - a generated artifact using client-only APIs may need a **marker directive** or a distinct
353
+ import path, and
354
+ - the resulting error frequently **names a package that is installed and present** — because
355
+ resolution failed under the server condition, not because the dependency is missing.
356
+
357
+ Read that error as a *boundary* problem, not a dependency problem. The fix belongs in the
358
+ generator that emits the artifact, which you own.
359
+
360
+ **3. If the emit is wrong for your framework, own the generator.**
361
+
362
+ meta eject --list # every template you can take ownership of
363
+ meta eject form # copies it to codegen/generators/form.ts
364
+
365
+ Then compose the engine and replace only the step that differs. Every generator's renderer
366
+ is exported, so wrapping is available — but **how much that buys you differs by tier, and
367
+ it is worth knowing which one you are in before you start**:
368
+
369
+ - **Entity module (`entity`)** — genuinely composable. `renderDrizzleSchema`,
370
+ `renderZodValidators`, `renderInferredTypes`, `renderFilterAllowlist` and friends are
371
+ separate exported sections the template assembles into a `Code[]`. Swap or drop one and
372
+ keep the rest.
373
+ - **Routes and UI (`routes`, `routes-hono`, `form`, `hooks`, `grid`, `grid-hook`)** — one
374
+ whole-file renderer each, so "replace a step" really means wrap the whole output. That
375
+ is enough for a marker directive, a header, or a post-process, and it is what the RSC
376
+ case below needs. It is **not** enough to retarget the emitted framework: if you need
377
+ Svelte or Angular instead of React, you are writing a renderer, and the honest move is
378
+ to keep the generator's metadata walk and replace the render call entirely.
379
+
380
+ **`"use client"` needs no ejecting at all — it is a config knob.** The generated form,
381
+ hooks, columns and grid-hook modules are client components; React Server Components
382
+ frameworks (Next.js App Router and friends) require the directive saying so. Set it once:
383
+
384
+ ```ts
385
+ export default defineConfig({
386
+ clientDirective: true, // prepend `"use client";` to generated client artifacts
387
+ // ...
388
+ });
389
+ ```
390
+
391
+ Defaults to `false`, because the directive is only *required* under RSC and is inert
392
+ (and warned about by some bundlers) everywhere else. It is applied ahead of the
393
+ `@generated` header, exactly once, and only to the four client artifacts — the entity
394
+ module, the query helpers and `<Entity>.meta.ts` are untouched, since `.meta.ts` is plain
395
+ data and in RSC the boundary is the importing component, not everything it reaches.
396
+
397
+ For the general wrap-the-output case — a directive or header MetaObjects does not model:
398
+
399
+ ```ts
400
+ // codegen/generators/form.ts — OWNED
401
+ import { renderFormFile } from "@metaobjectsdev/codegen-ts-react";
402
+
403
+ // ...inside generate():
404
+ if (!ctx.renderContext) throw new Error("renderContext is required (provided by runGen)");
405
+ const body = renderFormFile(entity, ctx.renderContext);
406
+ return { path, content: `// @my-framework:client\n` + body };
407
+ ```
408
+
409
+ You keep receiving upstream fixes to `renderFormFile` while owning the one line your
410
+ framework cares about. **Forking the whole renderer is the thing to avoid**, not owning the
411
+ generator.
412
+
413
+ **4. Server-tier output is usually already portable.** The entity module (a table plus
414
+ validation schemas) and the query helpers (which take `db` as a parameter rather than
415
+ importing a singleton) carry no HTTP-framework coupling — a server-rendered component can
416
+ call a generated query directly. Retargeting is usually only needed at the routes and UI
417
+ tiers.
@@ -22,7 +22,16 @@ provider/LLM-call layer — you compose the call yourself.
22
22
  ```ts
23
23
  // metaobjects.config.ts
24
24
  import { defineConfig } from "@metaobjectsdev/cli";
25
- import { entityFile, queriesFile, barrel, promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators";
25
+ // The entity trio + barrel come from the OWNED copies `meta init` scaffolded into
26
+ // ./codegen/generators/ (ADR-0034). Importing them from the package instead is the
27
+ // deprecated path, and quietly hands their shape back to the package — so keep these
28
+ // lines as `meta init` wrote them and add only the prompt pair below.
29
+ import { entityFile } from "./codegen/generators/entity.js";
30
+ import { queriesFile } from "./codegen/generators/queries.js";
31
+ import { barrel } from "./codegen/generators/barrel.js";
32
+ // promptRender / outputParser are NOT in the ownable set — the render and parse engines
33
+ // are upstream-owned, so importing them from the package is the supported pattern.
34
+ import { promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators";
26
35
 
27
36
  export default defineConfig({
28
37
  outDir: "src/generated",