@metaobjectsdev/codegen-ts 0.7.0-rc.9 → 0.8.0-rc.1
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/dist/generator.d.ts +9 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts +16 -0
- package/dist/generators/docs-data-builder.d.ts.map +1 -0
- package/dist/generators/docs-data-builder.js +381 -0
- package/dist/generators/docs-data-builder.js.map +1 -0
- package/dist/generators/docs-data.d.ts +98 -0
- package/dist/generators/docs-data.d.ts.map +1 -0
- package/dist/generators/docs-data.js +43 -0
- package/dist/generators/docs-data.js.map +1 -0
- package/dist/generators/docs-file.d.ts +8 -0
- package/dist/generators/docs-file.d.ts.map +1 -0
- package/dist/generators/docs-file.js +77 -0
- package/dist/generators/docs-file.js.map +1 -0
- package/dist/generators/entity-file.d.ts.map +1 -1
- package/dist/generators/entity-file.js +7 -0
- package/dist/generators/entity-file.js.map +1 -1
- package/dist/generators/index.d.ts +5 -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/output-prompt-file.d.ts +9 -0
- package/dist/generators/output-prompt-file.d.ts.map +1 -0
- package/dist/generators/output-prompt-file.js +51 -0
- package/dist/generators/output-prompt-file.js.map +1 -0
- package/dist/generators/template-generator.d.ts +41 -0
- package/dist/generators/template-generator.d.ts.map +1 -0
- package/dist/generators/template-generator.js +62 -0
- package/dist/generators/template-generator.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/instance-artifacts.d.ts +29 -0
- package/dist/instance-artifacts.d.ts.map +1 -0
- package/dist/instance-artifacts.js +57 -0
- package/dist/instance-artifacts.js.map +1 -0
- package/dist/metaobjects-config.d.ts +10 -0
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +1 -0
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/overwrite-policy.d.ts +39 -2
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +233 -13
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/render-context.d.ts +4 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +1 -0
- package/dist/render-context.js.map +1 -1
- package/dist/render-engine/framework-provider.d.ts +28 -0
- package/dist/render-engine/framework-provider.d.ts.map +1 -0
- package/dist/render-engine/framework-provider.js +104 -0
- package/dist/render-engine/framework-provider.js.map +1 -0
- package/dist/runner.d.ts +15 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +45 -6
- package/dist/runner.js.map +1 -1
- package/dist/templates/docs-file.d.ts +17 -0
- package/dist/templates/docs-file.d.ts.map +1 -0
- package/dist/templates/docs-file.js +37 -0
- package/dist/templates/docs-file.js.map +1 -0
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +12 -0
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/fr010-field-mapping.d.ts +28 -0
- package/dist/templates/fr010-field-mapping.d.ts.map +1 -0
- package/dist/templates/fr010-field-mapping.js +170 -0
- package/dist/templates/fr010-field-mapping.js.map +1 -0
- package/dist/templates/output-format-spec-emitter.d.ts +4 -0
- package/dist/templates/output-format-spec-emitter.d.ts.map +1 -0
- package/dist/templates/output-format-spec-emitter.js +60 -0
- package/dist/templates/output-format-spec-emitter.js.map +1 -0
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +69 -4
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/output-prompt.d.ts +10 -0
- package/dist/templates/output-prompt.d.ts.map +1 -0
- package/dist/templates/output-prompt.js +75 -0
- package/dist/templates/output-prompt.js.map +1 -0
- package/dist/templates/recover-schema-emitter.d.ts +8 -0
- package/dist/templates/recover-schema-emitter.d.ts.map +1 -0
- package/dist/templates/recover-schema-emitter.js +64 -0
- package/dist/templates/recover-schema-emitter.js.map +1 -0
- package/package.json +5 -5
- package/src/generator.ts +9 -0
- package/src/generators/docs-data-builder.ts +470 -0
- package/src/generators/docs-data.ts +154 -0
- package/src/generators/docs-file.ts +87 -0
- package/src/generators/entity-file.ts +7 -0
- package/src/generators/index.ts +17 -0
- package/src/generators/output-prompt-file.ts +66 -0
- package/src/generators/template-generator.ts +106 -0
- package/src/index.ts +34 -2
- package/src/instance-artifacts.ts +61 -0
- package/src/metaobjects-config.ts +11 -0
- package/src/overwrite-policy.ts +325 -14
- package/src/render-context.ts +5 -1
- package/src/render-engine/framework-provider.ts +107 -0
- package/src/runner.ts +66 -6
- package/src/templates/docs-file.ts +51 -0
- package/src/templates/entity-file.ts +13 -0
- package/src/templates/fr010-field-mapping.ts +191 -0
- package/src/templates/output-format-spec-emitter.ts +97 -0
- package/src/templates/output-parser.ts +77 -2
- package/src/templates/output-prompt.ts +88 -0
- package/src/templates/recover-schema-emitter.ts +91 -0
- package/templates/docs/entity-page.md.mustache +54 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// docsFile() — emits `<Entity>.md` next to each generated entity module.
|
|
2
|
+
//
|
|
3
|
+
// rc.12+: structured around a shared Mustache template at
|
|
4
|
+
// `templates/docs/entity-page.md.mustache` + a data builder at
|
|
5
|
+
// `docs-data-builder.ts`. Adopters can override the framework template by
|
|
6
|
+
// dropping their own `templates/docs/entity-page.md.mustache` into the
|
|
7
|
+
// project root (resolved via the project-then-framework provider chain).
|
|
8
|
+
//
|
|
9
|
+
// docsFile() calls `render()` directly rather than wrapping
|
|
10
|
+
// `templateGenerator()` because the per-entity output path depends on
|
|
11
|
+
// `GenContext.config.outputLayout`, which the generic templateGenerator
|
|
12
|
+
// `walk(root)` signature doesn't expose. Other future docs-style adopters
|
|
13
|
+
// with ctx-free walks (single-file aggregators, etc.) compose
|
|
14
|
+
// `templateGenerator()` directly.
|
|
15
|
+
//
|
|
16
|
+
// The conformance fixture (`fixtures/conformance/docs-file-basic`) gates
|
|
17
|
+
// byte-identity — the codegen output must match the hand-coded rc.11
|
|
18
|
+
// byte-for-byte. If you're hacking on this and the conformance test
|
|
19
|
+
// breaks, the refactor is the bug, not the fixture.
|
|
20
|
+
|
|
21
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
22
|
+
import { render } from "@metaobjectsdev/render";
|
|
23
|
+
import type { Generator, GeneratorFactory } from "../generator.js";
|
|
24
|
+
import { entityOutputPath } from "../import-path.js";
|
|
25
|
+
import { projectProvider } from "../render-engine/framework-provider.js";
|
|
26
|
+
import { buildEntityDocData } from "./docs-data-builder.js";
|
|
27
|
+
|
|
28
|
+
export interface DocsFileOpts {
|
|
29
|
+
filter?: (entity: MetaObject) => boolean;
|
|
30
|
+
target?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The names of the generators that may emit sibling files for an entity.
|
|
34
|
+
* We always list them in the "Generated code" section — adopters cross-
|
|
35
|
+
* reference their own metaobjects.config.ts to confirm which are wired in.
|
|
36
|
+
* Matches the rc.11 behavior. */
|
|
37
|
+
const KNOWN_SIBLING_GENERATORS = new Set([
|
|
38
|
+
"queries-file",
|
|
39
|
+
"routes-file",
|
|
40
|
+
"routes-file-hono",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const TEMPLATE_REF = "docs/entity-page.md";
|
|
44
|
+
|
|
45
|
+
export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
|
|
46
|
+
const generator: Generator = {
|
|
47
|
+
name: "docs-file",
|
|
48
|
+
async generate(ctx) {
|
|
49
|
+
if (!ctx.renderContext) {
|
|
50
|
+
throw new Error("docs-file: renderContext is required (provided by runGen)");
|
|
51
|
+
}
|
|
52
|
+
const rc = ctx.renderContext;
|
|
53
|
+
const provider = projectProvider(ctx.projectRoot ?? process.cwd());
|
|
54
|
+
const layout = ctx.config.outputLayout ?? "flat";
|
|
55
|
+
return ctx.loadedRoot.objects().filter(ctx.matches).map((entity: MetaObject) => {
|
|
56
|
+
const path = entityOutputPath(layout, entity.package, `${entity.name}.md`);
|
|
57
|
+
const payload = buildEntityDocData(entity, {
|
|
58
|
+
dialect: rc.dialect,
|
|
59
|
+
...(rc.columnNamingStrategy !== undefined && {
|
|
60
|
+
columnNamingStrategy: rc.columnNamingStrategy,
|
|
61
|
+
}),
|
|
62
|
+
loadedRoot: rc.loadedRoot,
|
|
63
|
+
generatorNames: KNOWN_SIBLING_GENERATORS,
|
|
64
|
+
});
|
|
65
|
+
let content: string;
|
|
66
|
+
try {
|
|
67
|
+
content = render({
|
|
68
|
+
ref: TEMPLATE_REF,
|
|
69
|
+
payload,
|
|
70
|
+
provider,
|
|
71
|
+
format: "markdown",
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
throw new Error(
|
|
76
|
+
`docs-file: failed rendering '${TEMPLATE_REF}' for '${path}': ${msg}`,
|
|
77
|
+
{ cause: err instanceof Error ? err : undefined },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return { path, content };
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
if (opts?.filter) generator.filter = opts.filter;
|
|
85
|
+
if (opts?.target) generator.target = opts.target;
|
|
86
|
+
return generator;
|
|
87
|
+
} as GeneratorFactory<DocsFileOpts>;
|
|
@@ -3,6 +3,7 @@ import { perEntity, type Generator, type GeneratorFactory } from "../generator.j
|
|
|
3
3
|
import { renderEntityFile } from "../templates/entity-file.js";
|
|
4
4
|
import { formatTs } from "../format.js";
|
|
5
5
|
import { entityOutputPath } from "../import-path.js";
|
|
6
|
+
import { isAbstract } from "../instance-artifacts.js";
|
|
6
7
|
|
|
7
8
|
export interface EntityFileOpts {
|
|
8
9
|
filter?: (entity: MetaObject) => boolean;
|
|
@@ -33,6 +34,12 @@ export const entityFile = function entityFile(opts?: EntityFileOpts): Generator
|
|
|
33
34
|
if (!ctx.renderContext) {
|
|
34
35
|
throw new Error("entity-file: renderContext is required (provided by runGen)");
|
|
35
36
|
}
|
|
37
|
+
// Abstract entities contribute shape only. When emitAbstractShapes is off
|
|
38
|
+
// (cross-port knob; default on) the entity-file generator emits nothing for
|
|
39
|
+
// them. Instance/write generators skip abstract unconditionally elsewhere.
|
|
40
|
+
if (isAbstract(entity) && !ctx.renderContext.emitAbstractShapes) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
36
43
|
return {
|
|
37
44
|
path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.ts`),
|
|
38
45
|
content: await formatTs(renderEntityFile(entity, ctx.renderContext, { allowlists })),
|
package/src/generators/index.ts
CHANGED
|
@@ -6,3 +6,20 @@ export { barrel, type BarrelOpts } from "./barrel.js";
|
|
|
6
6
|
export { mermaidErDiagram, type MermaidErOptions } from "./mermaid-er.js";
|
|
7
7
|
export { promptRender, type PromptRenderOpts } from "./prompt-render-file.js";
|
|
8
8
|
export { outputParser, type OutputParserOpts } from "./output-parser-file.js";
|
|
9
|
+
export { outputPrompt, type OutputPromptOpts } from "./output-prompt-file.js";
|
|
10
|
+
export { docsFile, type DocsFileOpts } from "./docs-file.js";
|
|
11
|
+
export {
|
|
12
|
+
templateGenerator,
|
|
13
|
+
type TemplateGeneratorOpts,
|
|
14
|
+
type TemplateWalkResult,
|
|
15
|
+
type TemplateFormat,
|
|
16
|
+
} from "./template-generator.js";
|
|
17
|
+
export type {
|
|
18
|
+
EntityDocData,
|
|
19
|
+
StorageFieldDoc,
|
|
20
|
+
IdentityDoc,
|
|
21
|
+
RelationshipDoc,
|
|
22
|
+
UsedByDoc,
|
|
23
|
+
GeneratedFileDoc,
|
|
24
|
+
} from "./docs-data.js";
|
|
25
|
+
export { buildEntityDocData } from "./docs-data-builder.js";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/generators/output-prompt-file.ts
|
|
2
|
+
//
|
|
3
|
+
// FR-010 stock generator that emits one <TemplateName>.prompt.ts file per json/xml
|
|
4
|
+
// template.output node — the output-format prompt fragment ("produce your answer
|
|
5
|
+
// like this"). Wraps renderOutputPrompt() from templates/output-prompt.ts. Skips
|
|
6
|
+
// text-format outputs and outputs whose @payloadRef doesn't resolve to a value-object
|
|
7
|
+
// (same contract as the output-parser generator).
|
|
8
|
+
//
|
|
9
|
+
// Consumer wiring (metaobjects.config.ts):
|
|
10
|
+
// generators: [..., promptRender(), outputParser(), outputPrompt()]
|
|
11
|
+
//
|
|
12
|
+
// Custom output directory:
|
|
13
|
+
// generators: [..., outputPrompt({ outDir: "src/generated/outputs" })]
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
TYPE_OBJECT,
|
|
17
|
+
TYPE_TEMPLATE,
|
|
18
|
+
TEMPLATE_SUBTYPE_OUTPUT,
|
|
19
|
+
TEMPLATE_ATTR_PAYLOAD_REF,
|
|
20
|
+
} from "@metaobjectsdev/metadata";
|
|
21
|
+
import {
|
|
22
|
+
type EmittedFile,
|
|
23
|
+
type Generator,
|
|
24
|
+
type GeneratorFactory,
|
|
25
|
+
oncePerRun,
|
|
26
|
+
} from "../generator.js";
|
|
27
|
+
import { renderOutputPrompt, templateSupportsPrompt } from "../templates/output-prompt.js";
|
|
28
|
+
|
|
29
|
+
export interface OutputPromptOpts {
|
|
30
|
+
/** Output directory prefix relative to the target's outDir. Default: "" (root). */
|
|
31
|
+
outDir?: string;
|
|
32
|
+
/** Optional named output target (registry key). Defaults to "default". */
|
|
33
|
+
target?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const outputPrompt = function outputPrompt(opts?: OutputPromptOpts): Generator {
|
|
37
|
+
const dirPrefix = opts?.outDir ? `${opts.outDir.replace(/\/$/, "")}/` : "";
|
|
38
|
+
const generator: Generator = {
|
|
39
|
+
name: "output-prompt",
|
|
40
|
+
generate: oncePerRun((_entities, ctx) => {
|
|
41
|
+
const root = ctx.loadedRoot;
|
|
42
|
+
const outputs = root
|
|
43
|
+
.ownChildren()
|
|
44
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
45
|
+
const files: EmittedFile[] = [];
|
|
46
|
+
for (const t of outputs) {
|
|
47
|
+
// Only json/xml outputs get a renderable prompt fragment.
|
|
48
|
+
if (!templateSupportsPrompt(t)) continue;
|
|
49
|
+
// @payloadRef must resolve to a value-object (same contract as the parser).
|
|
50
|
+
const payloadRef = t.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
51
|
+
if (typeof payloadRef !== "string") continue;
|
|
52
|
+
const vo = root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === payloadRef);
|
|
53
|
+
if (!vo) continue;
|
|
54
|
+
files.push({
|
|
55
|
+
path: `${dirPrefix}${t.name}.prompt.ts`,
|
|
56
|
+
content: renderOutputPrompt(root, t.name),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
if (opts?.target) {
|
|
63
|
+
generator.target = opts.target;
|
|
64
|
+
}
|
|
65
|
+
return generator;
|
|
66
|
+
} as GeneratorFactory<OutputPromptOpts>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// templateGenerator() — the missing primitive identified by the
|
|
2
|
+
// template-driven-codegen design (2026-05-28). Walks the loaded MetaRoot
|
|
3
|
+
// → renders shared Mustache templates via @metaobjectsdev/render → emits
|
|
4
|
+
// EmittedFile[]. Same Generator interface as the per-port hand-coded
|
|
5
|
+
// generators; just adds the "Mustache template" + "walk that yields a
|
|
6
|
+
// data dict per output" primitives.
|
|
7
|
+
//
|
|
8
|
+
// Design line we adopted (from the design doc):
|
|
9
|
+
// Code → hand-coded generators (ts-poet, idiomatic per-port).
|
|
10
|
+
// Documents → templateGenerator (shared Mustache templates).
|
|
11
|
+
//
|
|
12
|
+
// docsFile() is the first templateGenerator instance (rc.12). OpenAPI specs,
|
|
13
|
+
// Mermaid diagrams, HTML doc sites, etc. follow as templates + a walk
|
|
14
|
+
// function each.
|
|
15
|
+
|
|
16
|
+
import type { MetaRoot, MetaObject } from "@metaobjectsdev/metadata";
|
|
17
|
+
import { render, type Provider, type RenderFormat } from "@metaobjectsdev/render";
|
|
18
|
+
import type { Generator, GenContext, EmittedFile, GeneratorFactory } from "../generator.js";
|
|
19
|
+
import { projectProvider } from "../render-engine/framework-provider.js";
|
|
20
|
+
|
|
21
|
+
export type TemplateFormat = RenderFormat;
|
|
22
|
+
|
|
23
|
+
export interface TemplateWalkResult {
|
|
24
|
+
/** The data dict to render against. Templates reference its keys; the
|
|
25
|
+
* shape is the public-API contract template authors consume. */
|
|
26
|
+
data: object;
|
|
27
|
+
/** Output path RELATIVE to the generator's target outDir. */
|
|
28
|
+
outputPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TemplateGeneratorOpts {
|
|
32
|
+
/** kebab-case identifier; surfaces in diagnostics and the overwrite-policy
|
|
33
|
+
* per-file snapshot key. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Walk the loaded metadata tree and produce `{ data, outputPath }` tuples
|
|
36
|
+
* — one per emitted file. Pattern A (per-entity), pattern B (single
|
|
37
|
+
* aggregator), pattern C (mixed), pattern D (filter inline) all fit. */
|
|
38
|
+
walk: (root: MetaRoot) => TemplateWalkResult[] | Promise<TemplateWalkResult[]>;
|
|
39
|
+
/** Template reference. Resolved by the configured Provider chain — by
|
|
40
|
+
* default the project's `templates/<ref>.mustache` first, then the
|
|
41
|
+
* framework defaults at `codegen-ts/templates/<ref>.mustache`. */
|
|
42
|
+
template: string;
|
|
43
|
+
/** Drives the render engine's escaper. Defaults to "text". */
|
|
44
|
+
format?: TemplateFormat;
|
|
45
|
+
/** Optional per-entity filter for adopters who want to scope a generator
|
|
46
|
+
* via the standard `Generator.filter` plumbing. Not consulted by the
|
|
47
|
+
* default `walk` — adopters apply filters inside their walk function. */
|
|
48
|
+
filter?: (entity: MetaObject) => boolean;
|
|
49
|
+
/** Override the Provider used for template resolution. When omitted the
|
|
50
|
+
* generator resolves via `projectProvider(ctx.projectRoot)`, which layers
|
|
51
|
+
* the project's `templates/` over the framework defaults. (The project
|
|
52
|
+
* root is the directory holding `.metaobjects/config.json`, threaded
|
|
53
|
+
* through `GenContext` by the runner. Adopters needing a different
|
|
54
|
+
* lookup chain can pass an explicit provider.) */
|
|
55
|
+
provider?: Provider;
|
|
56
|
+
/** Optional named target — same as the other generators. */
|
|
57
|
+
target?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const templateGenerator = function templateGenerator(
|
|
61
|
+
opts: TemplateGeneratorOpts,
|
|
62
|
+
): Generator {
|
|
63
|
+
const fmt: TemplateFormat = opts.format ?? "text";
|
|
64
|
+
const generator: Generator = {
|
|
65
|
+
name: opts.name,
|
|
66
|
+
async generate(ctx: GenContext): Promise<EmittedFile[]> {
|
|
67
|
+
let provider: Provider;
|
|
68
|
+
if (opts.provider !== undefined) {
|
|
69
|
+
provider = opts.provider;
|
|
70
|
+
} else if (ctx.projectRoot !== undefined) {
|
|
71
|
+
provider = projectProvider(ctx.projectRoot);
|
|
72
|
+
} else {
|
|
73
|
+
ctx.warn(
|
|
74
|
+
"templateGenerator: ctx.projectRoot is undefined; falling back to process.cwd() for project-template resolution. " +
|
|
75
|
+
"Project-scoped template overrides will resolve relative to the current working directory, which is fragile under " +
|
|
76
|
+
"`meta gen` invoked from a sub-directory. Drive via runGen(opts.projectRoot) to remove this warning.",
|
|
77
|
+
);
|
|
78
|
+
provider = projectProvider(process.cwd());
|
|
79
|
+
}
|
|
80
|
+
const walkRes = await opts.walk(ctx.loadedRoot);
|
|
81
|
+
const files: EmittedFile[] = [];
|
|
82
|
+
for (const { data, outputPath } of walkRes) {
|
|
83
|
+
let content: string;
|
|
84
|
+
try {
|
|
85
|
+
content = render({
|
|
86
|
+
ref: opts.template,
|
|
87
|
+
payload: data,
|
|
88
|
+
provider,
|
|
89
|
+
format: fmt,
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
93
|
+
throw new Error(
|
|
94
|
+
`templateGenerator(${opts.name}) failed rendering '${opts.template}' for '${outputPath}': ${msg}`,
|
|
95
|
+
{ cause: err instanceof Error ? err : undefined },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
files.push({ path: outputPath, content });
|
|
99
|
+
}
|
|
100
|
+
return files;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
if (opts.filter) generator.filter = opts.filter;
|
|
104
|
+
if (opts.target) generator.target = opts.target;
|
|
105
|
+
return generator;
|
|
106
|
+
} as GeneratorFactory<TemplateGeneratorOpts>;
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,14 @@ export { buildRelationMap } from "./relation-resolver.js";
|
|
|
24
24
|
export type { RenderContext } from "./render-context.js";
|
|
25
25
|
export { makeRenderContext } from "./render-context.js";
|
|
26
26
|
|
|
27
|
-
export type {
|
|
28
|
-
|
|
27
|
+
export type {
|
|
28
|
+
WriteStatus,
|
|
29
|
+
WriteResult,
|
|
30
|
+
MergeStrategy,
|
|
31
|
+
BaselineMode,
|
|
32
|
+
DecideAndWriteOpts,
|
|
33
|
+
} from "./overwrite-policy.js";
|
|
34
|
+
export { decideAndWrite, GitMissingError } from "./overwrite-policy.js";
|
|
29
35
|
|
|
30
36
|
export { CodegenError } from "./errors.js";
|
|
31
37
|
export { GENERATED_HEADER, EXTRA_SUFFIX, DEFAULT_OUT_DIR } from "./constants.js";
|
|
@@ -38,6 +44,7 @@ export { packageToPath, entityOutputPath, crossEntitySpecifier, barrelEntrySpeci
|
|
|
38
44
|
export type { OutputLayout, ResolvedTarget } from "./import-path.js";
|
|
39
45
|
|
|
40
46
|
export { isProjection, isWriteThrough } from "./projection/projection-detector.js";
|
|
47
|
+
export { isAbstract, emitsInstanceArtifacts, emitsWriteArtifacts } from "./instance-artifacts.js";
|
|
41
48
|
export { extractViewSpec } from "./projection/extract-view-spec.js";
|
|
42
49
|
export type { ExtractContext } from "./projection/extract-view-spec.js";
|
|
43
50
|
export { emitViewDdl } from "./projection/view-ddl-emit.js";
|
|
@@ -45,3 +52,28 @@ export type { EmitOptions as ViewDdlEmitOptions } from "./projection/view-ddl-em
|
|
|
45
52
|
export type { JoinNode, JoinTree, SelectColumn, SelectSpec, ViewSpec } from "./projection/view-spec.js";
|
|
46
53
|
// Prompt construction (FR-004): typed payload + render-handle codegen.
|
|
47
54
|
export { generatePayloadInterfaces, generatePayloadInterfacesBatch, generateRenderHandle } from "./payload-codegen.js";
|
|
55
|
+
|
|
56
|
+
// Template-driven codegen (rc.12). Factory + framework Provider for adopters
|
|
57
|
+
// who want to wire their own templateGenerator instances. The default
|
|
58
|
+
// docsFile() uses this internally.
|
|
59
|
+
export {
|
|
60
|
+
templateGenerator,
|
|
61
|
+
type TemplateGeneratorOpts,
|
|
62
|
+
type TemplateWalkResult,
|
|
63
|
+
type TemplateFormat,
|
|
64
|
+
} from "./generators/template-generator.js";
|
|
65
|
+
export {
|
|
66
|
+
FileSystemProvider,
|
|
67
|
+
ProviderChain,
|
|
68
|
+
frameworkTemplatesProvider,
|
|
69
|
+
projectProvider,
|
|
70
|
+
} from "./render-engine/framework-provider.js";
|
|
71
|
+
export type {
|
|
72
|
+
EntityDocData,
|
|
73
|
+
StorageFieldDoc,
|
|
74
|
+
IdentityDoc,
|
|
75
|
+
RelationshipDoc,
|
|
76
|
+
UsedByDoc,
|
|
77
|
+
GeneratedFileDoc,
|
|
78
|
+
} from "./generators/docs-data.js";
|
|
79
|
+
export { buildEntityDocData } from "./generators/docs-data-builder.js";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Framework-level guard shared by every generator that emits INSTANCE / WRITE
|
|
2
|
+
// artifacts (write forms, CRUD hooks, grid columns, grid hooks, …).
|
|
3
|
+
//
|
|
4
|
+
// Two MetaData concepts make an entity *not* a source of instance artifacts:
|
|
5
|
+
//
|
|
6
|
+
// 1. Abstract (`@isAbstract: true`) — a fundamental MetaData concept: an
|
|
7
|
+
// abstract type contributes shape via inheritance ONLY. It never has an
|
|
8
|
+
// instantiable representation, so it must never produce a write form,
|
|
9
|
+
// CRUD hooks, columns, a grid, or any instance artifact. It still gets a
|
|
10
|
+
// type-only interface from the entity-file generator (so subclasses and
|
|
11
|
+
// consumers can reference its shape), but nothing that points at an
|
|
12
|
+
// `<E>Insert` schema / `$table` / mutation hook it does not have.
|
|
13
|
+
//
|
|
14
|
+
// 2. Projection (read-only view; see `isProjection`) — instantiable for READ
|
|
15
|
+
// but never for WRITE. It legitimately gets read models + read-only hooks
|
|
16
|
+
// + grids, but NOT a write form. WRITE-only generators (forms) reuse
|
|
17
|
+
// `emitsWriteArtifacts` to skip these; read-capable generators (tanstack
|
|
18
|
+
// hooks/grids) instead branch internally on `isProjection`.
|
|
19
|
+
//
|
|
20
|
+
// Centralizing these as `emitsInstanceArtifacts` / `emitsWriteArtifacts` keeps
|
|
21
|
+
// the rule in one place: a generator composes the matching guard into its
|
|
22
|
+
// `filter` rather than re-deriving "is this abstract / read-only" ad hoc.
|
|
23
|
+
|
|
24
|
+
import type { MetaData } from "@metaobjectsdev/metadata";
|
|
25
|
+
import { isProjection } from "./projection/projection-detector.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* True when `entity` is abstract (`@isAbstract: true`).
|
|
29
|
+
*
|
|
30
|
+
* Thin, framework-level accessor so generators in sibling codegen packages
|
|
31
|
+
* have a single import surface for the abstract concept and don't reach into
|
|
32
|
+
* metadata internals (mirrors how `isProjection` is the shared read-only
|
|
33
|
+
* discriminator). Abstract types contribute shape via inheritance only.
|
|
34
|
+
*/
|
|
35
|
+
export function isAbstract(entity: MetaData): boolean {
|
|
36
|
+
return entity.isAbstract === true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when `entity` should produce INSTANCE artifacts of ANY kind (read OR
|
|
41
|
+
* write): CRUD/read hooks, grid columns, grid hooks. Abstract types are
|
|
42
|
+
* excluded — they have no instantiable representation.
|
|
43
|
+
*
|
|
44
|
+
* Read-capable generators (tanstack hooks/grids) compose this so they skip
|
|
45
|
+
* abstract bases while still serving projections via their own
|
|
46
|
+
* `isProjection` read-only branch.
|
|
47
|
+
*/
|
|
48
|
+
export function emitsInstanceArtifacts(entity: MetaData): boolean {
|
|
49
|
+
return !isAbstract(entity);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when `entity` should produce WRITE artifacts (write forms, mutation
|
|
54
|
+
* surfaces). Excludes both abstract types (no instance at all) and projections
|
|
55
|
+
* (read-only views — instantiable for read, never for write).
|
|
56
|
+
*
|
|
57
|
+
* WRITE-only generators (e.g. the React form generator) compose this.
|
|
58
|
+
*/
|
|
59
|
+
export function emitsWriteArtifacts(entity: MetaData): boolean {
|
|
60
|
+
return !isAbstract(entity) && !isProjection(entity);
|
|
61
|
+
}
|
|
@@ -37,6 +37,15 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
|
|
|
37
37
|
columnNamingStrategy?: ColumnNamingStrategy;
|
|
38
38
|
/** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
|
|
39
39
|
apiPrefix?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether abstract entities (`@isAbstract: true`) emit their shape artifact
|
|
42
|
+
* (the type-only interface / value-object file from the entity-file
|
|
43
|
+
* generator). Defaults to `true`. Instance/write artifacts (forms, CRUD/read
|
|
44
|
+
* hooks, grids) are NEVER emitted for abstract entities regardless of this
|
|
45
|
+
* flag — that invariant lives in `instance-artifacts.ts`. This knob only
|
|
46
|
+
* governs the shape, mirroring the cross-port `emitAbstractShapes` option.
|
|
47
|
+
*/
|
|
48
|
+
emitAbstractShapes?: boolean;
|
|
40
49
|
/** Named output destinations. Generators reference one via `target`. */
|
|
41
50
|
targets?: Record<string, TargetConfig>;
|
|
42
51
|
/** importBase for the default target (top-level outDir). */
|
|
@@ -57,6 +66,7 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
|
|
|
57
66
|
export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets"> {
|
|
58
67
|
columnNamingStrategy: ColumnNamingStrategy;
|
|
59
68
|
apiPrefix: string;
|
|
69
|
+
emitAbstractShapes: boolean;
|
|
60
70
|
outputLayout: OutputLayout;
|
|
61
71
|
targets: Record<string, ResolvedTarget>;
|
|
62
72
|
}
|
|
@@ -98,6 +108,7 @@ export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobj
|
|
|
98
108
|
...config,
|
|
99
109
|
columnNamingStrategy: config.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
100
110
|
apiPrefix: config.apiPrefix ?? "",
|
|
111
|
+
emitAbstractShapes: config.emitAbstractShapes ?? true,
|
|
101
112
|
outputLayout: config.outputLayout ?? "flat",
|
|
102
113
|
targets: resolveTargets(config),
|
|
103
114
|
};
|