@metaobjectsdev/sdk 0.24.5 → 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.
- package/README.md +1 -7
- package/agent-context/servers/csharp.meta.json +3 -1
- package/agent-context/servers/java.meta.json +3 -1
- package/agent-context/servers/kotlin.meta.json +3 -1
- package/agent-context/servers/python.meta.json +3 -1
- package/agent-context/servers/typescript.meta.json +3 -1
- package/agent-context/skills/metaobjects-audit/SKILL.md +15 -8
- package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +6 -1
- package/agent-context/skills/metaobjects-audit/references/csharp.md +10 -1
- package/agent-context/skills/metaobjects-audit/references/java.md +10 -0
- package/agent-context/skills/metaobjects-audit/references/kotlin.md +11 -0
- package/agent-context/skills/metaobjects-audit/references/python.md +12 -2
- package/agent-context/skills/metaobjects-audit/references/typescript.md +7 -2
- package/agent-context/skills/metaobjects-authoring/SKILL.md +35 -6
- package/agent-context/skills/metaobjects-codegen/SKILL.md +210 -15
- package/agent-context/skills/metaobjects-codegen/references/csharp.md +1 -0
- package/agent-context/skills/metaobjects-codegen/references/java.md +1 -0
- package/agent-context/skills/metaobjects-codegen/references/kotlin.md +1 -0
- package/agent-context/skills/metaobjects-codegen/references/python.md +1 -0
- package/agent-context/skills/metaobjects-codegen/references/typescript.md +85 -6
- package/agent-context/skills/metaobjects-runtime-ui/SKILL.md +22 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/csharp.md +15 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/java.md +33 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/kotlin.md +23 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/python.md +25 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/tanstack.md +28 -6
- package/agent-context/skills/metaobjects-runtime-ui/references/typescript.md +20 -0
- package/agent-context/skills/metaobjects-verify/SKILL.md +28 -0
- package/agent-context/templates/always-on.md.mustache +45 -4
- package/dist/agent-context/assemble.d.ts.map +1 -1
- package/dist/agent-context/assemble.js +52 -4
- package/dist/agent-context/assemble.js.map +1 -1
- package/dist/agent-context/scaffold.d.ts +16 -1
- package/dist/agent-context/scaffold.d.ts.map +1 -1
- package/dist/agent-context/scaffold.js +21 -1
- package/dist/agent-context/scaffold.js.map +1 -1
- package/dist/forge-types.d.ts.map +1 -1
- package/dist/forge-types.js +19 -1
- package/dist/forge-types.js.map +1 -1
- package/package.json +4 -9
- package/src/agent-context/assemble.ts +86 -6
- package/src/agent-context/scaffold.ts +33 -2
- package/src/forge-types.ts +21 -0
- package/dist/agent-docs/body.d.ts +0 -6
- package/dist/agent-docs/body.d.ts.map +0 -1
- package/dist/agent-docs/body.js +0 -593
- package/dist/agent-docs/body.js.map +0 -1
- package/dist/agent-docs/content-hash.d.ts +0 -8
- package/dist/agent-docs/content-hash.d.ts.map +0 -1
- package/dist/agent-docs/content-hash.js +0 -23
- package/dist/agent-docs/content-hash.js.map +0 -1
- package/dist/agent-docs/index.d.ts +0 -3
- package/dist/agent-docs/index.d.ts.map +0 -1
- package/dist/agent-docs/index.js +0 -4
- package/dist/agent-docs/index.js.map +0 -1
- package/src/agent-docs/body.ts +0 -592
- package/src/agent-docs/content-hash.ts +0 -25
- package/src/agent-docs/index.ts +0 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @metaobjectsdev/sdk
|
|
2
2
|
|
|
3
|
-
Programmatic SDK for MetaObjects: workspace memory records, path resolution, project config loading
|
|
3
|
+
Programmatic SDK for MetaObjects: workspace memory records, path resolution, and project config loading. Consumed by the `meta` CLI and by AI-collaboration tooling (MCP exposers, codegen prompts).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -17,12 +17,6 @@ const metaRoot = await resolveMetaRoot(process.cwd());
|
|
|
17
17
|
const config = await loadConfig(metaRoot);
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The canonical agent reference docs (scaffolded by `meta init`) are available via a sub-path:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { AGENT_DOCS_BODY, withContentHash } from "@metaobjectsdev/sdk/agent-docs";
|
|
24
|
-
```
|
|
25
|
-
|
|
26
20
|
## agent-context
|
|
27
21
|
|
|
28
22
|
`@metaobjectsdev/sdk/agent-context` assembles the downstream AI-assistant context
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "C#",
|
|
3
3
|
"install": "install the MetaObjects .NET tool (dotnet tool install) + add the MetaObjects.Codegen package",
|
|
4
|
-
"codegenCommand": "dotnet meta gen"
|
|
4
|
+
"codegenCommand": "dotnet meta gen",
|
|
5
|
+
"docsCommand": "dotnet meta docs",
|
|
6
|
+
"apiSubDir": "api/csharp"
|
|
5
7
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Java",
|
|
3
3
|
"install": "add com.metaobjects:metaobjects-metadata + metaobjects-codegen-spring + metaobjects-maven-plugin to your pom.xml",
|
|
4
|
-
"codegenCommand": "mvn metaobjects:generate"
|
|
4
|
+
"codegenCommand": "mvn metaobjects:generate",
|
|
5
|
+
"docsCommand": "mvn metaobjects:docs",
|
|
6
|
+
"apiSubDir": "api/java"
|
|
5
7
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Kotlin",
|
|
3
3
|
"install": "add com.metaobjects:metaobjects-codegen-kotlin + metaobjects-metadata-ktx + metaobjects-maven-plugin to your pom.xml",
|
|
4
|
-
"codegenCommand": "mvn metaobjects:generate"
|
|
4
|
+
"codegenCommand": "mvn metaobjects:generate",
|
|
5
|
+
"docsCommand": "mvn metaobjects:docs",
|
|
6
|
+
"apiSubDir": "api/kotlin"
|
|
5
7
|
}
|
|
@@ -118,19 +118,25 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
|
|
|
118
118
|
references repo-wide (grep the emitted symbol / module path for importers):
|
|
119
119
|
delete it and stop generating it.
|
|
120
120
|
- **Artifacts an entity doesn't need** — REST routes / TanStack grids / forms /
|
|
121
|
-
hooks emitted for an entity that has no such surface. The
|
|
122
|
-
|
|
123
|
-
`
|
|
124
|
-
|
|
121
|
+
hooks emitted for an entity that has no such surface. The remedy is the
|
|
122
|
+
generator's own `filter` option (`routesFile({ filter: (e) => e.name !== "Audit" })`,
|
|
123
|
+
`tanstackQuery({ filter })`), which is ANDed with the generator's built-in gates
|
|
124
|
+
and so can only NARROW what emits; a grid additionally needs a `layout.dataGrid`
|
|
125
|
+
on the entity at all. Flag the unused artifact and name the filter that would
|
|
126
|
+
suppress it. **Never recommend an `@emit*` attribute** — `@emitRoutes`,
|
|
127
|
+
`@emitTanstack`, `@emitForm`, `@emitGrid` and `@emitAngular` were never
|
|
128
|
+
registered vocabulary, so they passed `meta gen` (open load) and FAILED
|
|
129
|
+
`meta verify` (strict). A project carrying one is a **finding**, not an opt-out:
|
|
130
|
+
report it and route to `meta upgrade --apply` plus the generator config above.
|
|
125
131
|
- **Generators wired but unconsumed** — a generator in `metaobjects.config.ts`
|
|
126
132
|
`generators: [...]` (or the per-port equivalent) whose whole output class no
|
|
127
133
|
code imports: drop the generator rather than generate into the void.
|
|
128
134
|
- **Wrong target / duplicate output** — the same logical artifact emitted to two
|
|
129
135
|
places (a mis-set per-target `outDir`), one of which is orphaned.
|
|
130
|
-
Recommend generating ONLY what is consumed —
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
census).
|
|
136
|
+
Recommend generating ONLY what is consumed — decide per generator: drop the ones
|
|
137
|
+
whose whole output class nothing imports, and narrow the ones that over-emit with
|
|
138
|
+
their `filter`. A smaller, fully-consumed generated surface beats a large one with
|
|
139
|
+
dead files (which also make the leverage ratio lie; discount them from the census).
|
|
134
140
|
- [ ] **G. Runtime-contract anti-patterns.** Module-global `db` vs context-as-parameter
|
|
135
141
|
(ADR-0008); wire-canonicalization in the query path vs native in-process return types
|
|
136
142
|
(ADR-0019); runtime reflection to resolve a type from FQN vs generated static imports /
|
|
@@ -331,6 +337,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
|
|
|
331
337
|
- **Not expressible → carry it in `@sql` or `@unmanaged`, never a hand-edited migration (#208, ADR-0043).** When a NAMED irreducible construct blocks origin authoring — recursive CTE, window function / `OVER`, `UNION` / `INTERSECT` / `EXCEPT`, lateral join — the body still belongs in the metadata: carry the hand-written SQL in the `source.rdb` **`@sql`** escape — a read-only-`@kind` body the tool REGISTERS, fingerprints, and drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`); `@sql` forbids `origin.*` children (two sources of truth). A DB object whose DDL is owned **entirely elsewhere** (Flyway / a hand-migration) → mark its source **`@unmanaged: true`** (legal on any `@kind` incl. `table`); `meta migrate` never creates/drops/drift-checks it and `verify --db` reports it as external. `@sql` and `@unmanaged` are mutually exclusive. **Only a view left *undeclared*** — neither modeled, nor `@sql`, nor `@unmanaged` — is truly *unmanaged*, invisible to `meta verify --db`, so this audit is the only gate that sees it. "It's an aggregation" is NOT an irreducibility justification (plain count/sum/avg/min/max rollups are `origin.aggregate`); nor is a `DISTINCT ON` pick-one-row (`origin.first`) or a non-aggregate expression column (`origin.computed`).
|
|
332
338
|
9. **A closed variant-set hand-modeled per instance** — N sibling modules / classes / config blocks, one per channel / provider / target, sharing a payload + config shape and diverging only by transport. Grep for sibling-file families and switch-on-a-string dispatch; verify the set is closed and recurring (never a one-off). → axis I "New-vocabulary OPPORTUNITY" (VOCAB CANDIDATE, advisory).
|
|
333
339
|
10. **N declarations of one FIELD across objects (same-name-field census)** — the field-level sibling of signature 6. Census field names across `object.*` nodes (`grep -rn 'name: <field>'` the metadata dir); a name recurring in ≥2 objects where a canonical owner exists — one whose name the field embeds (`<owner><Field>`: `wizardId` → `Wizard.id`, `orderTotal` → `Order.total`) or whose type+constraints it matches — is provenance loss → `extends: Owner.field` (dotted child targets, ADR-0029). **VERIFY by diffing the copies' attrs: a `@maxLength` / `@required` / validator divergence across them is drift already shipping — cite it.** Evidence multiplier: `extends` already used elsewhere in the repo raises confidence. Do NOT flag: generic names on unrelated concepts (`id` / `name` / `status` with no owner-embedding name and no matching constraints); required per-node attrs a loader forces (e.g. `payloadRef` / `format` on sibling `template.prompt` nodes — a product constraint, not a copy).
|
|
340
|
+
11. **A physical table / column / schema name spelled as a literal outside its metadata declaration** — in a hand-written repository, raw SQL, a migration script, a log line, or a hand-maintained body-to-column map. Every port emits a per-object names artifact (`<Entity>Names` / `<entity_snake>_names.py`) from the declaration, so a literal is a second spelling of the same fact; and because the physical column is free-form, deriving it from the field name is a guess that fails silently. **No verify subverb sees this** — `--codegen` diffs generated files, `--db` compares schema to metadata — so this audit is the only gate. Remedy: reference the constant; on the JVM the names generator is opt-in, so an un-wired pom is the first finding and wiring `SpringNamesGenerator` / `KotlinNamesGenerator` the first remedy. **Do NOT flag:** a typed ORM handle in its place (a Drizzle column object, an Exposed `Column`, an EF property — replacing one with a string is a regression); a physical name inside a `source.rdb` hand-written SQL body (a metadata document cannot import a constant, which is why that body is fingerprinted); a flattened value-object composite column (it belongs to no single field of either object); a write-through entity's replica view name (the artifact holds the PRIMARY source's); a relationship-synthesized foreign-key column (derived, never declared). A hand-written repository that imports `<Entity>Names` is the gold standard — call it out as good.
|
|
334
341
|
|
|
335
342
|
---
|
|
336
343
|
|
|
@@ -66,7 +66,12 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
|
|
|
66
66
|
## Source — `source.rdb`
|
|
67
67
|
|
|
68
68
|
- **`source.rdb`** (`@table`, `@schema`) — hunt hard-coded physical table/schema names that
|
|
69
|
-
diverge from the default naming the source models
|
|
69
|
+
diverge from the default naming the source models, AND any physical table/column/schema
|
|
70
|
+
name spelled as a literal outside its declaration — raw SQL, a hand-written repository, a
|
|
71
|
+
migration script, a body-to-column map (drift signature 11). Every port emits a per-object
|
|
72
|
+
names artifact from the declaration, so a literal is a second source of truth even when it
|
|
73
|
+
agrees with the naming strategy today. A typed ORM handle in its place is correct; on the
|
|
74
|
+
JVM an un-wired names generator is the finding first.
|
|
70
75
|
- **`@kind` = `view` / `materializedView`** — hunt hand-written SQL views where an authored
|
|
71
76
|
read-only source belongs. Apply the **view-necessity test** (SKILL.md, drift signature 8): a
|
|
72
77
|
hand-written `CREATE VIEW` (or read-only SQL mirroring a read model) is a CODEGEN CANDIDATE when
|
|
@@ -51,6 +51,7 @@ rejected (exit 2).
|
|
|
51
51
|
| `FromSqlInterpolated(` outside `.g.cs` | stored-proc call — candidate for the `callable` generator |
|
|
52
52
|
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
|
|
53
53
|
| `HasPrecision(` hand-coded | `field.decimal` with `@precision`/`@scale` drives this from the `entity` generator |
|
|
54
|
+
| a table/column string in raw-SQL EF calls, or `nameof(Entity.Prop)` standing in for a column | second spelling of a declared physical name — reference `<Entity>Names.g.cs` (`AuthorNames.SourcePrimaryTable` / `<Field>Column`, default suite — `Names.Name` is the OBJECT's name, not the table); an EF property inside LINQ is the typed handle — correct |
|
|
54
55
|
|
|
55
56
|
---
|
|
56
57
|
|
|
@@ -59,7 +60,15 @@ rejected (exit 2).
|
|
|
59
60
|
C# does not scaffold-and-own generators — the generators are provided by
|
|
60
61
|
`MetaObjects.Codegen` and are not meant to be copied into the consumer project.
|
|
61
62
|
There is no analog to the TS `codegen/generators/*.ts` pattern here. Generator
|
|
62
|
-
selection uses stable names via `dotnet meta gen --generators <names
|
|
63
|
+
selection uses stable names via `dotnet meta gen --generators <names>`, over a
|
|
64
|
+
**closed built-in registry** — there is no seam to register a generator of your own.
|
|
65
|
+
|
|
66
|
+
**So do not score a C# project down for "not owning its generators", and do not
|
|
67
|
+
recommend writing one.** The customization path here is the **declarative template**:
|
|
68
|
+
`dotnet meta gen --template-spec <json> --template-root <dir>`, whose entries append to
|
|
69
|
+
the default suite. A finding of the form "the built-ins do not emit the shape this
|
|
70
|
+
project needs" resolves to a template-spec, not to generator code. Worked example with
|
|
71
|
+
the full JSON: `docs/ports/csharp.md`.
|
|
63
72
|
|
|
64
73
|
To re-scaffold the agent-context into a C# project, use the Node `meta` CLI (the
|
|
65
74
|
single agent-docs assembler per ADR-0033):
|
|
@@ -51,6 +51,7 @@ plugin `<configuration>` — that is the source of truth for which generators ru
|
|
|
51
51
|
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
|
|
52
52
|
| `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a responding `template.prompt` (`@responseRef`) exists — output-parser codegen ships in Java |
|
|
53
53
|
| `LIMIT ?` / `OFFSET ?` assembled by hand | generated CRUD routes handle pagination; OMDB `getObjects` accepts `QueryOptions` |
|
|
54
|
+
| a table/column string in a repository implementation — JDBC SQL, jOOQ `table("authors")`, a Spring Data query | second spelling of a declared physical name — reference `<Entity>Names` (`AuthorNames.SOURCE_PRIMARY_TABLE` / `_COLUMN`; opt-in `SpringNamesGenerator`): an un-wired generator is the finding first |
|
|
54
55
|
|
|
55
56
|
---
|
|
56
57
|
|
|
@@ -60,6 +61,15 @@ Java does not scaffold-and-own generators — they are provided by
|
|
|
60
61
|
`metaobjects-codegen-spring` and wired by FQ class name in `pom.xml`. There is no
|
|
61
62
|
analog to the TS `codegen/generators/*.ts` pattern here.
|
|
62
63
|
|
|
64
|
+
Java does, however, have **both** authoring paths, so a project needing a shape the
|
|
65
|
+
built-ins do not emit has two legitimate answers and neither is a finding on its own:
|
|
66
|
+
a **programmatic** generator (implement `com.metaobjects.generator.Generator`, name the
|
|
67
|
+
class in a `<generator>` element — the plugin loads it from the project classpath), or a
|
|
68
|
+
**declarative template** (`TemplateScopeGenerator`, wired with `<template>` / `<scope>` /
|
|
69
|
+
`<outputPattern>` / `<format>` / `<templatesDir>`). Prefer the template when the output
|
|
70
|
+
SHAPE is the thing being iterated on or the same output is wanted in another language;
|
|
71
|
+
prefer code when the logic is gnarly. See `docs/features/codegen-concepts.md` §3.
|
|
72
|
+
|
|
63
73
|
To re-scaffold the agent-context into a Java project, use the Node `meta` CLI (the
|
|
64
74
|
single agent-docs assembler per ADR-0033):
|
|
65
75
|
|
|
@@ -56,6 +56,7 @@ The active generator list is declared in `pom.xml` under the plugin `<configurat
|
|
|
56
56
|
| `@Serializable` on a payload class not in generated sources | `KotlinPayloadGenerator` should own this; check `pom.xml` generator list |
|
|
57
57
|
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
|
|
58
58
|
| `transaction(db) {` bodies that duplicate every CRUD operation | hand-written Exposed transactions are expected (see Calibration), but if they duplicate generated-CRUD logic exactly, audit further |
|
|
59
|
+
| a table/column string in `exec("…")` raw SQL or a Flyway script | reference `<Entity>Names` (`KotlinNamesGenerator`, opt-in); `AuthorTable.name` inside the Exposed DSL is the typed handle — correct, not a finding |
|
|
59
60
|
|
|
60
61
|
---
|
|
61
62
|
|
|
@@ -65,6 +66,16 @@ Kotlin does not scaffold-and-own generators — they are provided by
|
|
|
65
66
|
`metaobjects-codegen-kotlin` and wired by FQ class name in `pom.xml`. There is no
|
|
66
67
|
analog to the TS `codegen/generators/*.ts` pattern here.
|
|
67
68
|
|
|
69
|
+
Kotlin does, however, have **both** authoring paths — it shares the JVM engine — so a
|
|
70
|
+
project needing a shape the built-ins do not emit has two legitimate answers and neither
|
|
71
|
+
is a finding on its own: a **programmatic** generator (implement
|
|
72
|
+
`com.metaobjects.generator.Generator`, name the class in a `<generator>` element), or a
|
|
73
|
+
**declarative template** (`TemplateScopeGenerator`, wired with `<template>` / `<scope>` /
|
|
74
|
+
`<outputPattern>` / `<format>` / `<templatesDir>` — no KotlinPoet involved). Prefer the
|
|
75
|
+
template when the output SHAPE is the thing being iterated on or the same output is
|
|
76
|
+
wanted in another language; prefer code when the logic is gnarly. See
|
|
77
|
+
`docs/features/codegen-concepts.md` §3.
|
|
78
|
+
|
|
68
79
|
To re-scaffold the agent-context into a Kotlin project, use the Node `meta` CLI (the
|
|
69
80
|
single agent-docs assembler per ADR-0033):
|
|
70
81
|
|
|
@@ -52,14 +52,24 @@ regardless of server language — see the migration reference.
|
|
|
52
52
|
| `def get_all_` / `def create_` / `def update_` / `def delete_` in non-generated files | hand-rolled CRUD — compare to the generated router |
|
|
53
53
|
| `# keep in sync with` / `# mirrors the` | second-source-of-truth comment — always a finding |
|
|
54
54
|
| `try: ... except KeyError` / `?? ''` around format strings in prompt code | silent-degradation hack around a prompt payload — flag it |
|
|
55
|
+
| a table/column string in the repository implementation (SQLAlchemy Core, asyncpg/psycopg SQL) | second spelling of a declared physical name — reference `<entity>_names.py` (`AUTHOR_SOURCE_PRIMARY_TABLE` / `AUTHOR_<FIELD>_COLUMN`, default suite); no typed handle exists on this port, so that is never the reason to waive it |
|
|
55
56
|
|
|
56
57
|
---
|
|
57
58
|
|
|
58
59
|
## Owned generators
|
|
59
60
|
|
|
60
61
|
Python does not scaffold-and-own generators — they are provided by the `metaobjects`
|
|
61
|
-
package and selected by stable name via `--generators
|
|
62
|
-
TS `codegen/generators/*.ts` pattern here
|
|
62
|
+
package and selected by stable name via `--generators`, over a **closed built-in
|
|
63
|
+
registry**. There is no analog to the TS `codegen/generators/*.ts` pattern here, and no
|
|
64
|
+
seam to register a generator of your own. (`--provider module:symbol` registers
|
|
65
|
+
**metamodel vocabulary**, not a generator — do not read it as one.)
|
|
66
|
+
|
|
67
|
+
**So do not score a Python project down for "not owning its generators", and do not
|
|
68
|
+
recommend writing one.** The customization path here is the **declarative template**:
|
|
69
|
+
`metaobjects gen --template-spec <json> --templates <dir>`, whose entries append to the
|
|
70
|
+
default suite. A finding of the form "the built-ins do not emit the shape this project
|
|
71
|
+
needs" resolves to a template-spec, not to generator code. Worked example with the full
|
|
72
|
+
JSON: `docs/ports/python.md`.
|
|
63
73
|
|
|
64
74
|
To re-scaffold the agent-context into a Python project, use the Node `meta` CLI (the
|
|
65
75
|
single agent-docs assembler per ADR-0033):
|
|
@@ -14,8 +14,12 @@ Generated files carry the header:
|
|
|
14
14
|
// @generated by @metaobjectsdev/codegen-ts
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
The
|
|
18
|
-
|
|
17
|
+
The header is **informational on this port** — the write decision never reads it. What
|
|
18
|
+
the runner overwrites is decided by `.metaobjects/.gen-state/`: the snapshot body if the
|
|
19
|
+
machine has one (three-way merge), otherwise the committed `.hashes.json` (byte-for-byte
|
|
20
|
+
what it wrote ⇒ overwrite; anything else ⇒ refused, exit 1). A sibling file you created
|
|
21
|
+
(`<Entity>.extra.ts` by convention) is never touched because it is not a path the runner
|
|
22
|
+
records — not because of its name, and not because of the header. Locate generated output at the
|
|
19
23
|
`outDir` (and per-target `outDir` values) declared in `metaobjects.config.ts`. With
|
|
20
24
|
the default single-target config, generated files land at the one configured
|
|
21
25
|
`outDir` (e.g. `src/generated/`).
|
|
@@ -50,6 +54,7 @@ value-sets against the constructs that replace them.
|
|
|
50
54
|
| `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
|
|
51
55
|
| camelCase↔snake_case mapping tables | generated views handle this; diff field by field |
|
|
52
56
|
| `@generated` file with hand edits | three-way merge preserves them; review at codegen; never delete |
|
|
57
|
+
| a table/column string in a `sql` fragment, a Kysely identifier, or a migration/seed script outside the generated dir | second spelling of a declared physical name — reference `<Entity>.names.ts` (`ProgramNames.fields.<f>.column`); a Drizzle column object in its place is correct, not a finding |
|
|
53
58
|
|
|
54
59
|
---
|
|
55
60
|
|
|
@@ -49,10 +49,19 @@ aggregate — that is almost always **metadata you have not declared yet.** In o
|
|
|
49
49
|
1. **Search the vocabulary** — `meta types <term>`, or `meta types --all
|
|
50
50
|
<what-it-does>` to search by behavior. There are field subtypes, relationships,
|
|
51
51
|
projections, origins, identities, sources, and attributes you may not know exist.
|
|
52
|
-
Find the construct that models it.
|
|
52
|
+
Find the construct that models it. Add `--detail` for one construct's valid
|
|
53
|
+
`@attrs`, or `--format json` for the same answer as one machine-readable document
|
|
54
|
+
— that form carries every match (`--limit` never truncates it) with each attr's
|
|
55
|
+
`allowedValues`, so you read the accepted values rather than guessing them.
|
|
53
56
|
2. **Declare it and generate** — then *consume* the generated query/type/route;
|
|
54
57
|
never reimplement it alongside.
|
|
55
|
-
3. **
|
|
58
|
+
3. **If the model is right but the generated OUTPUT is wrong, change your generator.**
|
|
59
|
+
Naming, file layout, imports, framework, signatures are generator concerns, not
|
|
60
|
+
reasons to hand-write. The generators are in *your* repo and are yours to edit — a
|
|
61
|
+
standing rule not to change the MetaObjects repo does not reach them; they are a
|
|
62
|
+
different repository. Editing one is ordinary work, not an escalation. (See
|
|
63
|
+
`metaobjects-codegen` → "Your generators are yours".)
|
|
64
|
+
4. **Only if no construct can express it** — and you have actually looked —
|
|
56
65
|
hand-write it, wired to generated types. Business algorithms, external
|
|
57
66
|
integrations, and bespoke interactions are legitimately hand-written; CRUD,
|
|
58
67
|
validation, finders, relationships, and derived/aggregate data are not.
|
|
@@ -86,10 +95,20 @@ Read the existing code and schema *first*, then model to reproduce them:
|
|
|
86
95
|
|
|
87
96
|
**Customize the CODEGEN to match the existing code before you change the existing code.**
|
|
88
97
|
If generated output doesn't match the code's shape (naming, file layout, imports,
|
|
89
|
-
signatures), **tune the generator/template/config to reproduce it** —
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
signatures), **tune the generator/template/config to reproduce it** — naming strategy
|
|
99
|
+
first, then the generator or the template. **Which lever you have depends on the port,
|
|
100
|
+
so establish that before planning:** on TypeScript and the JVM you can own a generator
|
|
101
|
+
outright (TS scaffolds copies into your repo; the JVM loads your class from the project
|
|
102
|
+
classpath), while on C# and Python the generator registry is closed and a **Mustache
|
|
103
|
+
template** is the customization path — a real one, with `scope` and `outputPattern`
|
|
104
|
+
doing the walk and the file naming. The `metaobjects-codegen` skill has the per-port
|
|
105
|
+
matrix and defines `outputPattern`; read it before concluding a shape is unreachable.
|
|
106
|
+
**Whichever lever you get, it is yours to edit.** A standing instruction not to
|
|
107
|
+
change the MetaObjects repo says nothing about your own generators or templates —
|
|
108
|
+
reading it as though it did is how an adoption ends up hand-written, and it is the most
|
|
109
|
+
common way this step is skipped. Editing a generator or a template here is ordinary
|
|
110
|
+
adoption work: not an escalation, not a hack, and nothing to ask permission for. Reshaping working call sites to satisfy the
|
|
111
|
+
generator's defaults is the *last* resort, not the first.
|
|
93
112
|
|
|
94
113
|
**Minimize churn to code the generator is not replacing.** The ONLY existing code that
|
|
95
114
|
should change is the hand-written layer codegen now **owns** (the hand-rolled
|
|
@@ -743,6 +762,16 @@ Multi-source: multiple `source.rdb` children, each with a `@role`, exactly one
|
|
|
743
762
|
{ "source.rdb": { "@kind": "view", "@view": "v_author", "@schema": "blog" } }
|
|
744
763
|
```
|
|
745
764
|
|
|
765
|
+
**This declaration is the only place a physical name is ever spelled.** Codegen emits a
|
|
766
|
+
per-object names artifact from it (`<Entity>Names` — `ProgramNames.fields.createdAt.column`,
|
|
767
|
+
`ProgramNames.CreatedAtColumn`, `PROGRAM_CREATED_AT_COLUMN`, per port), the generated table
|
|
768
|
+
binding reads that artifact, and hand-written SQL, repositories and migration scripts
|
|
769
|
+
reference it — so a consumer never restates `v_author` or `created_at`, and a rename here
|
|
770
|
+
propagates. Declare `@column` explicitly whenever the physical name is not the naming
|
|
771
|
+
strategy's answer (`callPurpose` → `purpose_code`): nothing downstream can recover that
|
|
772
|
+
mapping by derivation, and the constant is what carries it. (See `metaobjects-codegen` →
|
|
773
|
+
"Never hand-write a physical name".)
|
|
774
|
+
|
|
746
775
|
**An entity's PRIMARY source must be writable** (`table`) — read-only kinds are
|
|
747
776
|
legal only in non-primary roles.
|
|
748
777
|
|
|
@@ -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.
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
@@ -271,17 +331,152 @@ output/template · doesn't fit → write a generator that emits your shape *from
|
|
|
271
331
|
metadata* · only the genuinely un-modelable (business algorithms, external calls) is
|
|
272
332
|
hand-written outside codegen — and it still imports the generated types.
|
|
273
333
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`
|
|
283
|
-
|
|
284
|
-
|
|
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*.
|
|
285
480
|
|
|
286
481
|
## Dialects
|
|
287
482
|
|
|
@@ -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`
|