@metaobjectsdev/codegen-ts 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.
- package/README.md +37 -12
- package/dist/api-surface.d.ts +29 -0
- package/dist/api-surface.d.ts.map +1 -1
- package/dist/api-surface.js +47 -0
- package/dist/api-surface.js.map +1 -1
- package/dist/client-directive.d.ts +18 -0
- package/dist/client-directive.d.ts.map +1 -0
- package/dist/client-directive.js +37 -0
- package/dist/client-directive.js.map +1 -0
- package/dist/column-mapper.d.ts +12 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +1 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +24 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +54 -19
- package/dist/constants.js.map +1 -1
- package/dist/db-emitting.d.ts +6 -0
- package/dist/db-emitting.d.ts.map +1 -0
- package/dist/db-emitting.js +26 -0
- package/dist/db-emitting.js.map +1 -0
- package/dist/generator-registry.d.ts.map +1 -1
- package/dist/generator-registry.js +7 -1
- package/dist/generator-registry.js.map +1 -1
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/agent-docs-file.d.ts +19 -0
- package/dist/generators/agent-docs-file.d.ts.map +1 -0
- package/dist/generators/agent-docs-file.js +257 -0
- package/dist/generators/agent-docs-file.js.map +1 -0
- package/dist/generators/agent-requirements-page.d.ts +22 -0
- package/dist/generators/agent-requirements-page.d.ts.map +1 -0
- package/dist/generators/agent-requirements-page.js +175 -0
- package/dist/generators/agent-requirements-page.js.map +1 -0
- package/dist/generators/agent-schema-input.d.ts +69 -0
- package/dist/generators/agent-schema-input.d.ts.map +1 -0
- package/dist/generators/agent-schema-input.js +23 -0
- package/dist/generators/agent-schema-input.js.map +1 -0
- package/dist/generators/agent-schema-page.d.ts +21 -0
- package/dist/generators/agent-schema-page.d.ts.map +1 -0
- package/dist/generators/agent-schema-page.js +241 -0
- package/dist/generators/agent-schema-page.js.map +1 -0
- package/dist/generators/agent-ui-page.d.ts +22 -0
- package/dist/generators/agent-ui-page.d.ts.map +1 -0
- package/dist/generators/agent-ui-page.js +253 -0
- package/dist/generators/agent-ui-page.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -1
- package/dist/generators/api-docs-file.js +13 -6
- package/dist/generators/api-docs-file.js.map +1 -1
- package/dist/generators/api-model.d.ts +6 -0
- package/dist/generators/api-model.d.ts.map +1 -1
- package/dist/generators/api-model.js +54 -35
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/callable-file.js +1 -1
- package/dist/generators/callable-file.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +47 -2
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +6 -2
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/index.d.ts +3 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +4 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/names-file.d.ts +17 -0
- package/dist/generators/names-file.d.ts.map +1 -0
- package/dist/generators/names-file.js +95 -0
- package/dist/generators/names-file.js.map +1 -0
- package/dist/generators/requirements-markdown.d.ts +18 -1
- package/dist/generators/requirements-markdown.d.ts.map +1 -1
- package/dist/generators/requirements-markdown.js +11 -9
- package/dist/generators/requirements-markdown.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts +17 -2
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +15 -12
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +17 -2
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +10 -8
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +20 -6
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +67 -1
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +10 -1
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/names.d.ts +275 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +434 -0
- package/dist/names.js.map +1 -0
- package/dist/naming.d.ts +17 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +21 -0
- package/dist/naming.js.map +1 -1
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +15 -3
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +4 -3
- package/dist/payload-codegen.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts +25 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +79 -6
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/prompt-generator-gate.d.ts +27 -0
- package/dist/prompt-generator-gate.d.ts.map +1 -0
- package/dist/prompt-generator-gate.js +62 -0
- package/dist/prompt-generator-gate.js.map +1 -0
- package/dist/reference-templates.d.ts +22 -5
- package/dist/reference-templates.d.ts.map +1 -1
- package/dist/reference-templates.js +55 -21
- package/dist/reference-templates.js.map +1 -1
- package/dist/render-context.d.ts +20 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +2 -0
- package/dist/render-context.js.map +1 -1
- package/dist/retired-codegen-attrs.d.ts +32 -0
- package/dist/retired-codegen-attrs.d.ts.map +1 -0
- package/dist/retired-codegen-attrs.js +47 -0
- package/dist/retired-codegen-attrs.js.map +1 -0
- package/dist/routes-expose.d.ts +63 -0
- package/dist/routes-expose.d.ts.map +1 -0
- package/dist/routes-expose.js +57 -0
- package/dist/routes-expose.js.map +1 -0
- package/dist/runner.d.ts +18 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +115 -7
- package/dist/runner.js.map +1 -1
- package/dist/templates/callable-file.d.ts +7 -2
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +54 -9
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +236 -65
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +15 -6
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +85 -161
- package/dist/templates/entity-constants.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +38 -4
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/entity-meta-file.d.ts +15 -1
- package/dist/templates/entity-meta-file.d.ts.map +1 -1
- package/dist/templates/entity-meta-file.js +12 -2
- package/dist/templates/entity-meta-file.js.map +1 -1
- package/dist/templates/entity-ui-descriptor.d.ts +112 -0
- package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
- package/dist/templates/entity-ui-descriptor.js +238 -0
- package/dist/templates/entity-ui-descriptor.js.map +1 -0
- package/dist/templates/field-meta.d.ts +46 -9
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +95 -21
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/names-decl.d.ts +38 -0
- package/dist/templates/names-decl.d.ts.map +1 -0
- package/dist/templates/names-decl.js +104 -0
- package/dist/templates/names-decl.js.map +1 -0
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +54 -8
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/projection-decl.d.ts +21 -0
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +53 -29
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +17 -2
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +3 -1
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/requirement-test.d.ts.map +1 -1
- package/dist/templates/requirement-test.js +48 -21
- package/dist/templates/requirement-test.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts +2 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +11 -2
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts +2 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +54 -21
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +15 -1
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/view-decl.d.ts +37 -1
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +27 -3
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +21 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +40 -5
- package/dist/templates/zod-validators.js.map +1 -1
- package/dist/view-context.d.ts +24 -0
- package/dist/view-context.d.ts.map +1 -0
- package/dist/view-context.js +81 -0
- package/dist/view-context.js.map +1 -0
- package/package.json +6 -6
- package/src/api-surface.ts +53 -0
- package/src/client-directive.ts +36 -0
- package/src/column-mapper.ts +13 -0
- package/src/constants.ts +91 -16
- package/src/db-emitting.ts +31 -0
- package/src/generator-registry.ts +7 -0
- package/src/generator.ts +5 -0
- package/src/generators/agent-docs-file.ts +297 -0
- package/src/generators/agent-requirements-page.ts +199 -0
- package/src/generators/agent-schema-input.ts +88 -0
- package/src/generators/agent-schema-page.ts +297 -0
- package/src/generators/agent-ui-page.ts +290 -0
- package/src/generators/api-docs-file.ts +13 -6
- package/src/generators/api-model.ts +63 -34
- package/src/generators/callable-file.ts +1 -1
- package/src/generators/docs-data-builder.ts +46 -2
- package/src/generators/docs-data.ts +6 -2
- package/src/generators/index.ts +10 -0
- package/src/generators/names-file.ts +98 -0
- package/src/generators/requirements-markdown.ts +36 -2
- package/src/generators/routes-file-hono.ts +26 -15
- package/src/generators/routes-file.ts +20 -7
- package/src/generators/template-generator.ts +20 -6
- package/src/index.ts +63 -5
- package/src/metaobjects-config.ts +78 -2
- package/src/names.ts +630 -0
- package/src/naming.ts +23 -0
- package/src/overwrite-policy.ts +15 -3
- package/src/payload-codegen.ts +4 -3
- package/src/projection/extract-view-spec.ts +85 -6
- package/src/prompt-generator-gate.ts +73 -0
- package/src/reference/barrel.ts +3 -0
- package/src/reference/entity.ts +28 -2
- package/src/reference/names.ts +125 -0
- package/src/reference/queries.ts +14 -2
- package/src/reference/routes-hono.ts +113 -0
- package/src/reference/routes.ts +30 -9
- package/src/reference-templates.ts +61 -21
- package/src/render-context.ts +22 -1
- package/src/retired-codegen-attrs.ts +52 -0
- package/src/routes-expose.ts +86 -0
- package/src/runner.ts +129 -11
- package/src/templates/callable-file.ts +60 -8
- package/src/templates/drizzle-schema.ts +248 -60
- package/src/templates/entity-constants.ts +95 -184
- package/src/templates/entity-file.ts +46 -4
- package/src/templates/entity-meta-file.ts +16 -3
- package/src/templates/entity-ui-descriptor.ts +305 -0
- package/src/templates/field-meta.ts +100 -19
- package/src/templates/names-decl.ts +165 -0
- package/src/templates/output-parser.ts +54 -7
- package/src/templates/projection-decl.ts +72 -31
- package/src/templates/queries-file.ts +17 -2
- package/src/templates/render-helper.ts +3 -1
- package/src/templates/requirement-test.ts +49 -18
- package/src/templates/routes-file-hono.ts +14 -2
- package/src/templates/routes-file.ts +69 -24
- package/src/templates/value-object-file.ts +15 -1
- package/src/templates/view-decl.ts +53 -3
- package/src/templates/zod-validators.ts +41 -5
- package/src/view-context.ts +88 -0
package/src/constants.ts
CHANGED
|
@@ -1,28 +1,103 @@
|
|
|
1
1
|
// Local constants for codegen-ts.
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The header line every emitter puts at the top of a generated file.
|
|
5
|
+
*
|
|
6
|
+
* Informational ON THIS PORT. It used to be documented here as the thing that "drives
|
|
7
|
+
* the overwrite policy", and it does not: `overwrite-policy.ts` decides from the
|
|
8
|
+
* `.gen-state` snapshot body and the committed hash manifest and never reads this
|
|
9
|
+
* string (grep it — no non-template module imports it). Deleting the header from a
|
|
10
|
+
* generated file does not take ownership of it here; it just changes the content, so
|
|
11
|
+
* the hash stops matching and the file is refused like any other edit.
|
|
12
|
+
*
|
|
13
|
+
* The JVM ports are the opposite: there the marker IS the decision
|
|
14
|
+
* (`GeneratedFileWriter`), and deleting it is exactly how you take ownership. Both
|
|
15
|
+
* behaviours are stated in `docs/features/own-your-codegen.md` — do not collapse them
|
|
16
|
+
* into one sentence, because the gesture that protects a file differs per port.
|
|
17
|
+
*/
|
|
4
18
|
export const GENERATED_HEADER = "@generated by @metaobjectsdev/codegen-ts";
|
|
5
19
|
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
// `EXTRA_SUFFIX = ".extra"` was removed here. It was a public export of this package
|
|
21
|
+
// wired to nothing: no generator, orphan sweep or write path ever compared a path
|
|
22
|
+
// against it, so its presence implied tool support for a name that is pure convention.
|
|
23
|
+
// A `<Entity>.extra.ts` sibling is safe because codegen only writes the paths it
|
|
24
|
+
// records in `.gen-state/.hashes.json` — not because of its name — and the generated
|
|
25
|
+
// barrel is built from the model, so it does not re-export it. Say that in prose;
|
|
26
|
+
// do not re-add a constant that suggests the engine knows the name.
|
|
8
27
|
|
|
9
28
|
/** Default outDir used by tests + as a sane default for generate(). */
|
|
10
29
|
export const DEFAULT_OUT_DIR = "./src/db/entities";
|
|
11
30
|
|
|
12
31
|
// ---------------------------------------------------------------------------
|
|
13
|
-
//
|
|
32
|
+
// Retired codegen-control attributes.
|
|
14
33
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
34
|
+
// `@emitRoutes` / `@emitTanstack` / `@emitForm` / `@emitGrid` / `@emitAngular`
|
|
35
|
+
// were read off metadata by generator filters and were NEVER registered
|
|
36
|
+
// metamodel vocabulary. The strict loader rejects every one of them with
|
|
37
|
+
// ERR_UNKNOWN_ATTR — and `meta verify` loads strict — while `meta gen` loads
|
|
38
|
+
// non-strict and honoured them. So an adopter who authored the documented
|
|
39
|
+
// per-entity opt-out got the behaviour AND a red `meta verify`: half-working,
|
|
40
|
+
// which is worse than either half, and exactly the class ADR-0023 §2 names.
|
|
41
|
+
//
|
|
42
|
+
// Registering them was refused. That moves `metamodelVersion` and forces a
|
|
43
|
+
// four-registry publish for a TypeScript-only generator kill switch that four
|
|
44
|
+
// of five ports would never read. The replacement already exists and is
|
|
45
|
+
// verified end to end: decide per generator what you consume — wire only the
|
|
46
|
+
// generators whose output you actually import, and narrow one with its
|
|
47
|
+
// `filter` option. The one clause that was opt-IN rather than opt-out (a TPH
|
|
48
|
+
// subtype's own per-subtype grid) cannot be a `filter`, since a filter is
|
|
49
|
+
// ANDed with the built-in gates and can only NARROW; it is the
|
|
50
|
+
// `tphSubtypeGrids` option on tanstackGrid()/tanstackGridHook() instead.
|
|
51
|
+
//
|
|
52
|
+
// No generator reads any of these names now. They survive HERE and nowhere
|
|
53
|
+
// else, so `meta gen` can recognise a stale one still sitting in an adopter's
|
|
54
|
+
// metadata and say what replaced it (see retired-codegen-attrs.ts). Keeping
|
|
55
|
+
// the name and its replacement in ONE record is the point: a warning whose
|
|
56
|
+
// text lives apart from the list it is warning about drifts from it.
|
|
19
57
|
// ---------------------------------------------------------------------------
|
|
20
58
|
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
59
|
+
/** One retired codegen-control attribute, paired with what an adopter does instead. */
|
|
60
|
+
export interface RetiredCodegenAttr {
|
|
61
|
+
/** The bare attribute name, as it appears in metadata (no `@` sigil). */
|
|
62
|
+
readonly name: string;
|
|
63
|
+
/** What to do instead, phrased as an instruction and naming a real API. */
|
|
64
|
+
readonly replacement: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Every retired codegen-control attribute. Read ONLY by the `meta gen` warning. */
|
|
68
|
+
export const RETIRED_CODEGEN_ATTRS: readonly RetiredCodegenAttr[] = [
|
|
69
|
+
{
|
|
70
|
+
name: "emitRoutes",
|
|
71
|
+
replacement:
|
|
72
|
+
'drop routesFile() / routesFileHono() from `generators` in metaobjects.config.ts if ' +
|
|
73
|
+
'you do not consume REST routes, or narrow it — routesFile({ filter: (e) => e.name !== "Ledger" })',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "emitTanstack",
|
|
77
|
+
replacement:
|
|
78
|
+
'drop tanstackQuery() / tanstackGrid() / tanstackGridHook() from `generators` in ' +
|
|
79
|
+
'metaobjects.config.ts if you do not consume their output, or narrow one — ' +
|
|
80
|
+
'tanstackQuery({ filter: (e) => e.name !== "InternalAudit" })',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "emitForm",
|
|
84
|
+
replacement:
|
|
85
|
+
'drop formFile() from `generators` in metaobjects.config.ts if you do not consume ' +
|
|
86
|
+
'generated forms, or narrow it — formFile({ filter: (e) => e.name !== "InternalAudit" })',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "emitGrid",
|
|
90
|
+
replacement:
|
|
91
|
+
'pass the `tphSubtypeGrids` option — tanstackGrid({ tphSubtypeGrids: (e) => e.name === ' +
|
|
92
|
+
'"CopayAuth" }) — and pass the SAME predicate to tanstackGridHook(), or the emitted ' +
|
|
93
|
+
'<Sub>.grid.ts has no <Sub>.columns.tsx to import. This one is an opt-IN, so a `filter` ' +
|
|
94
|
+
'cannot express it: a filter is ANDed with the built-in gates and can only narrow',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "emitAngular",
|
|
98
|
+
replacement:
|
|
99
|
+
'drop angularServiceFile() / angularFormFile() / angularGridFile() from `generators` in ' +
|
|
100
|
+
'metaobjects.config.ts if you do not consume Angular output, or narrow one — ' +
|
|
101
|
+
'angularServiceFile({ filter: (e) => e.name !== "InternalAudit" })',
|
|
102
|
+
},
|
|
103
|
+
] as const;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Which objects make a project a DATABASE project — and therefore require a `dialect`.
|
|
2
|
+
//
|
|
3
|
+
// THIS IS THE ONE ANSWER, and it is hoisted here because two commands need it and a second
|
|
4
|
+
// copy would be a second answer. `runGen` throws when a model emits database code and the
|
|
5
|
+
// config declares no dialect (see `missingDialectMessage`), and it throws BEFORE
|
|
6
|
+
// `normalizeConfig` fills its defaults on purpose: `DEFAULT_DIALECT` is INERT, existing
|
|
7
|
+
// only so a value-object-only project need not name one. A DB project that forgot it gets
|
|
8
|
+
// a named error rather than silently-defaulted output — "a Postgres project quietly
|
|
9
|
+
// emitting sqlite", as the runner's own comment puts it.
|
|
10
|
+
//
|
|
11
|
+
// `meta docs` needs the same answer for `agent/schema.md`. Reading `?? DEFAULT_DIALECT`
|
|
12
|
+
// there instead would document a sqlite schema for a project `meta gen` refuses to build
|
|
13
|
+
// — asserting an answer the toolchain never gave.
|
|
14
|
+
|
|
15
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
16
|
+
import { hasAnyRdbSource } from "./source-detect.js";
|
|
17
|
+
|
|
18
|
+
/** The concrete objects a run would emit database code for. Empty ⇒ no dialect needed. */
|
|
19
|
+
export function dbEmittingObjects(entities: readonly MetaObject[]): MetaObject[] {
|
|
20
|
+
return entities.filter((e) => !e.isAbstract && hasAnyRdbSource(e));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The one wording for "this model needs a dialect and the config has none". */
|
|
24
|
+
export function missingDialectMessage(dbEmitting: readonly MetaObject[]): string {
|
|
25
|
+
const names = dbEmitting.map((e) => e.name).join(", ");
|
|
26
|
+
return (
|
|
27
|
+
`codegen config is missing dialect — required because this model ` +
|
|
28
|
+
`generates database code for: ${names}. Set dialect in ` +
|
|
29
|
+
`metaobjects.config.ts. (A model of only value objects and/or sourceless projections may omit it.)`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
routesFile,
|
|
26
26
|
routesFileHono,
|
|
27
27
|
barrel,
|
|
28
|
+
namesFile,
|
|
28
29
|
mermaidErDiagram,
|
|
29
30
|
promptRender,
|
|
30
31
|
outputParser,
|
|
@@ -111,6 +112,12 @@ export const generatorRegistry: Record<string, GeneratorRegistryEntry> = {
|
|
|
111
112
|
factory: () => barrel(),
|
|
112
113
|
options: "target?",
|
|
113
114
|
},
|
|
115
|
+
names: {
|
|
116
|
+
name: "names",
|
|
117
|
+
description: "Per-entity physical database name constants (table/view, schema, columns).",
|
|
118
|
+
tier: "native",
|
|
119
|
+
factory: () => namesFile(),
|
|
120
|
+
},
|
|
114
121
|
"prompt-render": {
|
|
115
122
|
name: "prompt-render",
|
|
116
123
|
description: "Per-template prompt-render helper over the render engine.",
|
package/src/generator.ts
CHANGED
|
@@ -63,6 +63,11 @@ export interface Generator {
|
|
|
63
63
|
* that Hono routes are actually being emitted and document them — rather than
|
|
64
64
|
* silently omitting the Hono CRUD registrars whenever the variant is wired. */
|
|
65
65
|
emitsHonoRoutes?: boolean;
|
|
66
|
+
/** §A6 — marks the generator that emits the <Entity>Names artifact. The runner
|
|
67
|
+
* aggregates this across the suite into ResolvedGenConfig.includeNames, which the
|
|
68
|
+
* entity generator reads to decide whether it may reference those constants.
|
|
69
|
+
* Same mechanism as emitsHonoRoutes/includeHonoRoutes. */
|
|
70
|
+
emitsNames?: boolean;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
export type GeneratorFactory<TOpts = void> = TOpts extends void
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// agentDocsFile() — the `agent` docs surface.
|
|
2
|
+
//
|
|
3
|
+
// Three pages under `agent/`, each one an agent reads BEFORE touching a tier:
|
|
4
|
+
//
|
|
5
|
+
// • `agent/schema.md` — before touching persistence
|
|
6
|
+
// • `agent/ui.md` — before touching a form or a grid
|
|
7
|
+
// • `agent/requirements.md` — before adding a capability
|
|
8
|
+
//
|
|
9
|
+
// (`api/AGENT-API.md`, the fourth file the always-on pointer names, is the api surface's
|
|
10
|
+
// and is emitted by `apiDocsFile()`. It is not duplicated here.)
|
|
11
|
+
//
|
|
12
|
+
// EVERY PAGE IS DERIVED FROM AN EXISTING BUILDER, and that is the design constraint
|
|
13
|
+
// rather than an implementation detail. A documentation surface an agent is told to
|
|
14
|
+
// TRUST has to be true, and the only way to keep three more pages true is to give them
|
|
15
|
+
// no derivation of their own:
|
|
16
|
+
//
|
|
17
|
+
// schema ← the expected-schema snapshot `meta migrate` diffs and emits from
|
|
18
|
+
// (injected; see agent-schema-input.ts for why codegen-ts refuses to
|
|
19
|
+
// compute it) + `resolveObjectNames`, the field→column resolver the
|
|
20
|
+
// names artifact and the DDL already share
|
|
21
|
+
// ui ← `buildEntityUiDescriptor`, the same derivation emitted as the
|
|
22
|
+
// `<Entity>` const that `useEntityForm` reads at runtime
|
|
23
|
+
// requirements ← `walkRequirements` + `requirementRows`, the same walk the ledger
|
|
24
|
+
// surface and the generated test stubs are built on
|
|
25
|
+
//
|
|
26
|
+
// CONFIG-GATED, like the api surface. Physical names, the dialect and view dispatch all
|
|
27
|
+
// depend on the gen config, so `meta docs` only selects this surface when it has one.
|
|
28
|
+
// The NEUTRAL model surface stays neutral (ADR-0020); this is a different surface with a
|
|
29
|
+
// different contract, not a relaxation of that one.
|
|
30
|
+
//
|
|
31
|
+
// AN EMPTY PAGE IS NO FILE. Each renderer returns "" when its tier has nothing to
|
|
32
|
+
// describe — no physical schema, no UI, no ledger — and an empty render emits nothing.
|
|
33
|
+
// That is what makes the surface safe to leave on: a headless project with no ledger sees
|
|
34
|
+
// no `agent/` directory rather than three pages of headings.
|
|
35
|
+
//
|
|
36
|
+
// These files are READ, never imported. There is no three-way merge and no hand-edit
|
|
37
|
+
// preservation to think about: regenerate and the page is current.
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
CARDINALITY_MANY,
|
|
41
|
+
CARDINALITY_ONE,
|
|
42
|
+
FIELD_SUBTYPE_ENUM,
|
|
43
|
+
ORIGIN_AGGREGATE_ATTR_AGG,
|
|
44
|
+
ORIGIN_AGGREGATE_ATTR_OF,
|
|
45
|
+
ORIGIN_AGGREGATE_ATTR_VIA,
|
|
46
|
+
ORIGIN_ATTR_ORDER_BY,
|
|
47
|
+
ORIGIN_PASSTHROUGH_ATTR_FROM,
|
|
48
|
+
ORIGIN_PASSTHROUGH_ATTR_VIA,
|
|
49
|
+
ORIGIN_SUBTYPE_AGGREGATE,
|
|
50
|
+
ORIGIN_SUBTYPE_COMPUTED,
|
|
51
|
+
ORIGIN_SUBTYPE_FIRST,
|
|
52
|
+
ORIGIN_SUBTYPE_PASSTHROUGH,
|
|
53
|
+
RELATIONSHIP_ATTR_CARDINALITY,
|
|
54
|
+
RELATIONSHIP_ATTR_OBJECT_REF,
|
|
55
|
+
RELATIONSHIP_ATTR_ON_DELETE,
|
|
56
|
+
RELATIONSHIP_ATTR_THROUGH,
|
|
57
|
+
PHYSICAL_NAME_ATTR_BY_KIND,
|
|
58
|
+
SOURCE_ROLE_PRIMARY,
|
|
59
|
+
TYPE_ORIGIN,
|
|
60
|
+
resolveColumnName,
|
|
61
|
+
} from "@metaobjectsdev/metadata";
|
|
62
|
+
import type { ColumnNamingStrategy, MetaField, MetaObject } from "@metaobjectsdev/metadata";
|
|
63
|
+
import type { EmittedFile, Generator, GeneratorFactory } from "../generator.js";
|
|
64
|
+
import { resolveObjectNames } from "../names.js";
|
|
65
|
+
import { isAbstract } from "../instance-artifacts.js";
|
|
66
|
+
import { enumValues, intValueMapOf } from "../enum-meta.js";
|
|
67
|
+
import { renderAgentSchemaPage } from "./agent-schema-page.js";
|
|
68
|
+
import { renderAgentUiPage } from "./agent-ui-page.js";
|
|
69
|
+
import { renderAgentRequirementsPage } from "./agent-requirements-page.js";
|
|
70
|
+
import type { AgentSchemaInput } from "./agent-schema-input.js";
|
|
71
|
+
|
|
72
|
+
/** All three pages live here, under the docs root. */
|
|
73
|
+
const DEFAULT_AGENT_DIR = "agent";
|
|
74
|
+
|
|
75
|
+
export interface AgentDocsFileOpts {
|
|
76
|
+
/** Output prefix for the agent pages. Default `agent`. */
|
|
77
|
+
subDir?: string;
|
|
78
|
+
/** Optional named output target (registry key). */
|
|
79
|
+
target?: string;
|
|
80
|
+
/**
|
|
81
|
+
* The physical schema, with its resolvers injected by whoever owns them. ABSENT is a
|
|
82
|
+
* supported state — `meta docs` runs without a dialect, and a project with no physical
|
|
83
|
+
* schema gets no schema page rather than a page of unknowns.
|
|
84
|
+
*/
|
|
85
|
+
schema?: AgentSchemaInput;
|
|
86
|
+
/** The project's column naming strategy, for the field→column mapping. */
|
|
87
|
+
columnNamingStrategy?: ColumnNamingStrategy;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One `origin.*` child rendered as a lineage phrase. `origin.*` NEVER inherits, so the
|
|
91
|
+
* own-accessor read here is the correct one rather than an ADR-0039 slip. */
|
|
92
|
+
function lineageOf(field: MetaField): string | undefined {
|
|
93
|
+
const origin = field.ownChildren().find((c) => c.type === TYPE_ORIGIN);
|
|
94
|
+
if (origin === undefined) return undefined;
|
|
95
|
+
const str = (name: string): string | undefined => {
|
|
96
|
+
const v = origin.attr(name);
|
|
97
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
98
|
+
};
|
|
99
|
+
switch (origin.subType) {
|
|
100
|
+
case ORIGIN_SUBTYPE_PASSTHROUGH: {
|
|
101
|
+
const from = str(ORIGIN_PASSTHROUGH_ATTR_FROM);
|
|
102
|
+
const via = str(ORIGIN_PASSTHROUGH_ATTR_VIA);
|
|
103
|
+
return `passthrough from \`${from ?? "?"}\`${via === undefined ? "" : ` via \`${via}\``}`;
|
|
104
|
+
}
|
|
105
|
+
case ORIGIN_SUBTYPE_AGGREGATE: {
|
|
106
|
+
const agg = str(ORIGIN_AGGREGATE_ATTR_AGG) ?? "?";
|
|
107
|
+
const of = str(ORIGIN_AGGREGATE_ATTR_OF);
|
|
108
|
+
const via = str(ORIGIN_AGGREGATE_ATTR_VIA);
|
|
109
|
+
return `\`${agg}\`${of === undefined ? "" : ` of \`${of}\``}${via === undefined ? "" : ` via \`${via}\``}`;
|
|
110
|
+
}
|
|
111
|
+
case ORIGIN_SUBTYPE_FIRST: {
|
|
112
|
+
const of = str(ORIGIN_AGGREGATE_ATTR_OF);
|
|
113
|
+
const via = str(ORIGIN_AGGREGATE_ATTR_VIA);
|
|
114
|
+
const order = str(ORIGIN_ATTR_ORDER_BY);
|
|
115
|
+
return `first${of === undefined ? "" : ` \`${of}\``}${via === undefined ? "" : ` via \`${via}\``}` +
|
|
116
|
+
`${order === undefined ? "" : ` ordered by \`${order}\``}`;
|
|
117
|
+
}
|
|
118
|
+
case ORIGIN_SUBTYPE_COMPUTED:
|
|
119
|
+
// The @expr tree is structured; naming it is enough to route a reader to the
|
|
120
|
+
// declaration, and rendering a tree into a cell would be a second SQL lowering.
|
|
121
|
+
return "computed from a declared `@expr`";
|
|
122
|
+
default:
|
|
123
|
+
return `\`origin.${origin.subType}\``;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The cardinality phrase, from the DECLARING object's side.
|
|
129
|
+
*
|
|
130
|
+
* `@cardinality: one` does NOT mean one-to-one. It means this object holds the foreign
|
|
131
|
+
* key — many rows of it point at one target row — which is what `relation-resolver.ts`
|
|
132
|
+
* builds a belongs-to `one()` relation from. Rendering it as "one-to-one" told a reader
|
|
133
|
+
* the target row is claimed by at most one row here, which is the direction that makes
|
|
134
|
+
* someone write a lookup expecting a single result.
|
|
135
|
+
*
|
|
136
|
+
* `@cardinality` is OPTIONAL vocabulary (min 0), and ABSENT is its own answer: no
|
|
137
|
+
* navigation is generated at all. Folding that into "one-to-one" invented a shape the
|
|
138
|
+
* model never declared.
|
|
139
|
+
*/
|
|
140
|
+
function cardinalityPhrase(cardinality: unknown, through: unknown): string {
|
|
141
|
+
if (cardinality === CARDINALITY_MANY) {
|
|
142
|
+
return typeof through === "string" && through !== "" ? "many-to-many" : "one-to-many";
|
|
143
|
+
}
|
|
144
|
+
if (cardinality === CARDINALITY_ONE) return "many-to-one";
|
|
145
|
+
return "cardinality not declared";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** `- \`Order.lines\` — one-to-many → \`OrderLine\`` */
|
|
149
|
+
function relationshipLines(objects: readonly MetaObject[]): string[] {
|
|
150
|
+
const out: string[] = [];
|
|
151
|
+
for (const obj of objects) {
|
|
152
|
+
for (const rel of obj.relationships()) {
|
|
153
|
+
const cardinality = rel.attr(RELATIONSHIP_ATTR_CARDINALITY);
|
|
154
|
+
const target = rel.attr(RELATIONSHIP_ATTR_OBJECT_REF);
|
|
155
|
+
const through = rel.attr(RELATIONSHIP_ATTR_THROUGH);
|
|
156
|
+
const onDelete = rel.attr(RELATIONSHIP_ATTR_ON_DELETE);
|
|
157
|
+
// The FQN, not the short name: this line is an ADDRESS a reader searches for.
|
|
158
|
+
const parts = [`\`${obj.resolutionKey()}.${rel.name}\``, `\`${rel.subType}\``];
|
|
159
|
+
parts.push(
|
|
160
|
+
`${cardinalityPhrase(cardinality, through)} → \`${String(target ?? "?")}\``,
|
|
161
|
+
);
|
|
162
|
+
if (typeof through === "string" && through !== "") parts.push(`through \`${through}\``);
|
|
163
|
+
if (typeof onDelete === "string" && onDelete !== "") parts.push(`on delete \`${onDelete}\``);
|
|
164
|
+
out.push(`- ${parts.join(" · ")}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* `| \`Order.status\` | OPEN, CLOSED | string-backed |`
|
|
172
|
+
*
|
|
173
|
+
* `objects` IS ONLY THE TABLE-BACKED SUBSET OF THE MODEL — the caller scopes it to the
|
|
174
|
+
* objects the snapshot actually holds as tables. This section sits on the SCHEMA page, so
|
|
175
|
+
* every row it prints is read as a statement about a column. Walking every loaded object
|
|
176
|
+
* printed rows for abstract bases, for `object.value`s that have no column anywhere, and
|
|
177
|
+
* for projections whose "column" is a view expression — three kinds of thing the physical
|
|
178
|
+
* schema does not contain.
|
|
179
|
+
*
|
|
180
|
+
* It also says nothing about a `CHECK`. Whether the database constrains the members is
|
|
181
|
+
* `table.checks`, which is already on this page from the snapshot, and an `@isArray`
|
|
182
|
+
* enum deliberately gets none (`migrate-ts` skips it) — so asserting one here was a
|
|
183
|
+
* SECOND derivation of a fact the page already carries correctly, in the direction that
|
|
184
|
+
* promises the database will refuse a value it will accept.
|
|
185
|
+
*/
|
|
186
|
+
function enumLines(objects: readonly MetaObject[]): string[] {
|
|
187
|
+
const rows: string[] = [];
|
|
188
|
+
for (const obj of objects) {
|
|
189
|
+
for (const field of obj.fields()) {
|
|
190
|
+
if (field.subType !== FIELD_SUBTYPE_ENUM) continue;
|
|
191
|
+
const members = enumValues(field);
|
|
192
|
+
if (members === undefined || members.length === 0) continue;
|
|
193
|
+
const intMap = intValueMapOf(field);
|
|
194
|
+
const backing =
|
|
195
|
+
intMap === undefined
|
|
196
|
+
? "string-backed"
|
|
197
|
+
: `int-backed (${members.map((m) => `${m}=${intMap[m]}`).join(", ")})`;
|
|
198
|
+
rows.push(
|
|
199
|
+
`| \`${obj.resolutionKey()}.${field.name}\` | ${members.map((m) => `\`${m}\``).join(", ")} | ${backing} |`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (rows.length === 0) return [];
|
|
204
|
+
return ["| Field | Members | Storage |", "|---|---|---|", ...rows];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const agentDocsFile = function agentDocsFile(opts?: AgentDocsFileOpts): Generator {
|
|
208
|
+
const dir = (opts?.subDir ?? DEFAULT_AGENT_DIR).replace(/\/$/, "");
|
|
209
|
+
|
|
210
|
+
const generator: Generator = {
|
|
211
|
+
name: "agent-docs",
|
|
212
|
+
generate(ctx) {
|
|
213
|
+
const objects = ctx.loadedRoot.objects();
|
|
214
|
+
const files: EmittedFile[] = [];
|
|
215
|
+
|
|
216
|
+
// ---- schema.md
|
|
217
|
+
if (opts?.schema !== undefined) {
|
|
218
|
+
const schema = opts.schema;
|
|
219
|
+
// The qualified names the snapshot actually holds. An object whose physical name
|
|
220
|
+
// is not one of these contributes nothing to the physical schema (an abstract
|
|
221
|
+
// base, a sourceless value, an `@unmanaged` object) and must not appear on a page
|
|
222
|
+
// describing it — `buildExpectedSchema`'s Pass 1 owns those skip rules and this
|
|
223
|
+
// reads its answer rather than re-deriving them.
|
|
224
|
+
const tableNames = new Set(schema.tables.map((t) => schema.qualify(t)));
|
|
225
|
+
const viewNames = new Set(schema.views.map((v) => schema.qualify(v)));
|
|
226
|
+
// The objects backed by a TABLE in the snapshot — the enum section's scope, since
|
|
227
|
+
// a row there is read as a statement about a column.
|
|
228
|
+
const tableBacked: MetaObject[] = [];
|
|
229
|
+
// column → declaring field, per qualified table name. `resolveObjectNames` is the
|
|
230
|
+
// ONE field→column resolver — the same one the names artifact and the DDL use —
|
|
231
|
+
// so this mapping cannot disagree with the column it labels.
|
|
232
|
+
const declaredBy = new Map<string, Map<string, { field: string; type: string }>>();
|
|
233
|
+
const viewLineage = new Map<string, string[]>();
|
|
234
|
+
for (const obj of objects) {
|
|
235
|
+
const names = resolveObjectNames(obj, opts.columnNamingStrategy);
|
|
236
|
+
// The PRIMARY source's physical name and schema — `names.name` is the object's
|
|
237
|
+
// metamodel name since 0.25.0, which would match nothing in the snapshot.
|
|
238
|
+
const primary = names?.sources[SOURCE_ROLE_PRIMARY];
|
|
239
|
+
const physical = primary === undefined
|
|
240
|
+
? undefined
|
|
241
|
+
: primary[(PHYSICAL_NAME_ATTR_BY_KIND.get(primary.kind) ?? "") as keyof typeof primary];
|
|
242
|
+
if (typeof physical !== "string") continue;
|
|
243
|
+
const key = schema.qualify({ name: physical, schema: primary?.schema });
|
|
244
|
+
const isTable = tableNames.has(key);
|
|
245
|
+
if (!isTable && !viewNames.has(key)) continue;
|
|
246
|
+
// ABSTRACT BASES ARE EXCLUDED, and being in the snapshot does not exempt them:
|
|
247
|
+
// an abstract base that declares the `source.rdb` its concrete child inherits
|
|
248
|
+
// resolves to that child's table, so the qualified-name test admits it and the
|
|
249
|
+
// page printed the base's enum a second time under a different owner.
|
|
250
|
+
// `buildExpectedSchema`'s Pass 1 skips abstracts; this reads the same rule.
|
|
251
|
+
if (isTable && !isAbstract(obj)) tableBacked.push(obj);
|
|
252
|
+
let map = declaredBy.get(key);
|
|
253
|
+
if (map === undefined) {
|
|
254
|
+
map = new Map();
|
|
255
|
+
declaredBy.set(key, map);
|
|
256
|
+
}
|
|
257
|
+
const lineage: string[] = [];
|
|
258
|
+
for (const field of obj.fields()) {
|
|
259
|
+
const column = resolveColumnName(field, opts.columnNamingStrategy);
|
|
260
|
+
// A TPH base and its subtypes share one table: first writer wins, so the
|
|
261
|
+
// base's own column keeps its label rather than being relabelled by whichever
|
|
262
|
+
// subtype was walked last.
|
|
263
|
+
if (!map.has(column)) {
|
|
264
|
+
map.set(column, { field: field.name, type: `${field.type}.${field.subType}` });
|
|
265
|
+
}
|
|
266
|
+
const line = lineageOf(field);
|
|
267
|
+
if (line !== undefined) lineage.push(`| \`${column}\` | ${line} |`);
|
|
268
|
+
}
|
|
269
|
+
if (lineage.length > 0) viewLineage.set(key, lineage);
|
|
270
|
+
}
|
|
271
|
+
const content = renderAgentSchemaPage(schema, {
|
|
272
|
+
declaredBy,
|
|
273
|
+
viewLineage,
|
|
274
|
+
relationships: relationshipLines(objects),
|
|
275
|
+
enums: enumLines(tableBacked),
|
|
276
|
+
});
|
|
277
|
+
if (content !== "") files.push({ path: `${dir}/schema.md`, content });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---- ui.md
|
|
281
|
+
// The apiPrefix is the project's, from the render context the runner built — the
|
|
282
|
+
// same value `routes-file.ts` emits as the SERVER mount prefix. Without it the page
|
|
283
|
+
// names an address nothing serves. (The client descriptor no longer carries it: a
|
|
284
|
+
// browser's base URL is supplied at runtime by the provider's `baseUrl`.)
|
|
285
|
+
const ui = renderAgentUiPage(ctx.loadedRoot, ctx.renderContext?.apiPrefix ?? "");
|
|
286
|
+
if (ui !== "") files.push({ path: `${dir}/ui.md`, content: ui });
|
|
287
|
+
|
|
288
|
+
// ---- requirements.md
|
|
289
|
+
const requirements = renderAgentRequirementsPage(ctx.loadedRoot);
|
|
290
|
+
if (requirements !== "") files.push({ path: `${dir}/requirements.md`, content: requirements });
|
|
291
|
+
|
|
292
|
+
return files;
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
if (opts?.target) generator.target = opts.target;
|
|
296
|
+
return generator;
|
|
297
|
+
} as GeneratorFactory<AgentDocsFileOpts>;
|