You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(codegen-ts): fix projection/driver-type codegen and add advisory verify pass (#80)
* fix(codegen-ts,cli): projection + driver-type codegen fixes, verify-as-teacher, model-first guidance
Codegen fixes (the friction that forced agents to abandon MetaObjects and hand-roll):
- queries-file: projections now emit READ-ONLY queries (findById + list from the
view) — no `<Name>InsertSchema` import / create/update. Fixes TS2724 that made a
declared projection fail to compile, forcing a revert to hand-rolled aggregates.
- queries-file: SQLite `Db` accepts BOTH sync (better-sqlite3) and async
(libsql/Turso/D1) drivers (`BaseSQLiteDatabase<"sync" | "async", unknown>`) — the
pinned `<"async">` rejected better-sqlite3, the most common driver.
- queries-file + callable-file: Postgres `Db` is the base `PgDatabase<PgQueryResultHKT,
…>` every PG driver extends (node-postgres, postgres.js, Neon, Vercel, pglite),
not just `NodePgDatabase`.
All three verified by compiling generated query shapes against the real drivers.
verify-as-teacher (cli): `meta verify` + `meta gen` run an advisory pass that flags
hand-rolled aggregates / money-as-float / CHECK-IN enums and names the construct that
models them. Warnings only — never fails the build. `--no-antipatterns` opts out.
Agent-context skills:
- authoring: a model-first / generate-first operating principle (declare metadata,
generate persistence/DAO/API/UI; hand-write only what metadata can't express),
with `meta types` vocabulary search folded in.
- codegen: write-your-own-generators is now a first-class section (real apps rarely
use OOTB codegen as-is) with the accurate `Generator` / `perEntity` API.
- verify: run `meta verify` before calling a build done.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky
* no-mistakes(review): honor both anti-pattern opt-outs on meta gen and verify
* no-mistakes(document): sync docs for driver-type widening and verify-as-teacher pass
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-authoring/SKILL.md
+44-29Lines changed: 44 additions & 29 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,36 +15,51 @@ concept (`meta.commerce.json`, `meta.users.yaml`, …). Each file declares a
15
15
`package` on its root node. Files in the same `package` with the same object
16
16
`name` are merged by the loader.
17
17
18
-
## Find the construct first — model it, don't hand-write it
18
+
## The operating principle: model-first, generate-first
19
+
20
+
You are not hand-writing an application — you are **declaring the model it is
21
+
generated from.** Persistence, data access, validation, APIs, and UI scaffolding are
22
+
**derived from metadata, never authored by hand.** Model-first is the default for
23
+
*every* capability; hand-writing one of these layers is an exception you must
24
+
**justify**, not a convenience you reach for.
25
+
26
+
**This requires thinking differently.** Imperative code asks *"how do I implement
27
+
this endpoint?"* Model-first asks *"what is this resource, and what is true about
28
+
it?"* — and lets codegen own the *how*. **Describe WHAT, not HOW.** The metadata is
29
+
the source of truth; generated code is a disposable, regenerable artifact — delete
30
+
it and `meta gen` restores it identically.
31
+
32
+
**Why model-first wins even when hand-writing is cheaper this once — and it often
33
+
is, this once:**
34
+
-**Hand-writing a layer the metadata could own creates a second source of truth for
35
+
one fact.** A field's type, validation, column, route, and form then change in N
36
+
places and must stay consistent forever — not drift *risk*, but two sources of
37
+
truth for one fact, broken by construction.
38
+
-**The hand-roll saving is paid once; the consistency tax is paid on every future
39
+
change.** Assume the system will grow — it always does. The metadata amortizes
40
+
toward zero as the model is reused across layers and time; the hand-rolled
41
+
liability compounds with every field, refactor, and language port.
42
+
-**One metadata change regenerates persistence + DAO + API + UI consistently** —
43
+
and inherits every future generator improvement. Hand-writing opts out of all of
44
+
it, permanently.
45
+
46
+
**Before you hand-write anything data-shaped, STOP and find the model.** The moment
47
+
you reach for a hand-written query, route, validator, form, relationship, or
48
+
aggregate — that is almost always **metadata you have not declared yet.** In order:
49
+
1.**Search the vocabulary** — `meta types <term>`, or `meta types --all
50
+
<what-it-does>` to search by behavior. There are field subtypes, relationships,
51
+
projections, origins, identities, sources, and attributes you may not know exist.
52
+
Find the construct that models it.
53
+
2.**Declare it and generate** — then *consume* the generated query/type/route;
54
+
never reimplement it alongside.
55
+
3.**Only if no construct can express it** — and you have actually looked —
56
+
hand-write it, wired to generated types. Business algorithms, external
57
+
integrations, and bespoke interactions are legitimately hand-written; CRUD,
58
+
validation, finders, relationships, and derived/aggregate data are not.
59
+
60
+
Rule of thumb: **if the metadata could describe it, declaring it is never the wrong
61
+
call** — even when a one-off hand-write would be faster today.
19
62
20
-
You do **not** know the full vocabulary from memory, and it is larger and more
21
-
powerful than the basics below. Before authoring anything non-trivial, and the
22
-
moment you reach for hand-written data logic, **search the live metamodel**:
23
-
24
-
```
25
-
meta types relationship # find by name
26
-
meta types --all aggregate # find by WHAT IT DOES (searches descriptions)
27
-
meta types origin.aggregate --detail # one construct: description, when to use it, valid @attrs
28
-
```
29
-
30
-
**The rule:** before you hand-write any data logic — a join, a foreign key, a
31
-
derived/aggregate value (count/sum/avg), a uniqueness / format / range / cross-field
32
-
rule, a relationship between entities, a derived read model — run `meta types` and
33
-
check for a construct that **declares** it. If one exists, declare it instead. That
34
-
is the entire point of MetaObjects: declared metadata is generated, typed, and
35
-
regenerates on change; hand-written logic drifts and is the thing this tool exists
36
-
to eliminate. When you catch yourself writing a query, a validator, or an FK by
37
-
hand, stop and search the types first.
38
-
39
-
Two on-disk formats, one shape:
40
-
41
-
-**Canonical JSON** — the on-disk interchange. Every node is a single-key map
42
-
whose key fuses the type and subtype.
43
-
-**YAML** — the sigil-free authoring front-end. Lowered to canonical JSON at load
44
-
time, so it shares the entire downstream pipeline.
45
-
46
-
Author in whichever fits the project. Prefer YAML for new hand-authored metadata
47
-
(it's less noisy); JSON is the format conformance fixtures and tooling pin.
Copy file name to clipboardExpand all lines: fixtures/agent-context-conformance/java-kotlin-react-tanstack/expected/.claude/skills/metaobjects-codegen/SKILL.md
+53-5Lines changed: 53 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,15 +86,63 @@ the data access too.
86
86
`meta migrate` its DB view), and you **call that generated query from your
87
87
route**. Declaring the projection is only half the win — *consuming* its
88
88
generated query is the other half.
89
-
-**Codegen is yours to extend.** A generated file carries the `@generated` header
90
-
and is a normal source file: copy it and customize the copy (three-way merge
91
-
preserves your edits on regen), or write your own `Generator` (the plugin
92
-
interface) and add it to the `generators` array for an artifact the built-ins
93
-
don't cover.
94
89
95
90
`meta gen --list` prints every generator by stable name; the `generators` array in
96
91
`metaobjects.config.ts` is where you opt each one in or out.
97
92
93
+
## Write your own generators — the built-ins rarely fit an app exactly
94
+
95
+
The built-in generators (entity, queries, routes, form, grid, barrel) cover the
96
+
common shape, but **real apps routinely need output the built-ins don't emit as-is**
97
+
— a bespoke REST contract, custom DTO/response shapes, an app-specific service or
98
+
repository layer, a UI the defaults don't produce. When that happens the model-first
99
+
move is **not** to abandon metadata and hand-write the layer. Write a **custom
100
+
generator** that reads the same metadata and emits *your* app's shape.
101
+
102
+
Treat this as a first-class, expected activity — not an escape hatch. A custom
103
+
generator is still model-first: it derives from the metadata spine, so it
104
+
regenerates on change and stays consistent across every entity — the leverage you'd
105
+
forfeit by hand-writing. Hand-rolling *away from* metadata is the anti-pattern;
106
+
generating *your own shape from* metadata is the point.
107
+
108
+
The plugin interface is small (`@metaobjectsdev/codegen-ts`): a `Generator` is
109
+
`{ name, filter?, generate }`, where `generate(ctx)` returns `EmittedFile[]`
110
+
(`{ path, content }`). `perEntity` / `oncePerRun` wrap the common cases:
0 commit comments