@metaobjectsdev/codegen-ts 0.12.5-rc.1 → 0.13.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.
Files changed (47) hide show
  1. package/README.md +19 -2
  2. package/dist/generator.d.ts +11 -1
  3. package/dist/generator.d.ts.map +1 -1
  4. package/dist/generator.js +32 -1
  5. package/dist/generator.js.map +1 -1
  6. package/dist/generators/index.d.ts +4 -0
  7. package/dist/generators/index.d.ts.map +1 -1
  8. package/dist/generators/index.js +10 -0
  9. package/dist/generators/index.js.map +1 -1
  10. package/dist/generators/template-generator.d.ts +14 -2
  11. package/dist/generators/template-generator.d.ts.map +1 -1
  12. package/dist/generators/template-generator.js +43 -1
  13. package/dist/generators/template-generator.js.map +1 -1
  14. package/dist/index.d.ts +13 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +18 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/reference-templates.d.ts +13 -0
  19. package/dist/reference-templates.d.ts.map +1 -0
  20. package/dist/reference-templates.js +44 -0
  21. package/dist/reference-templates.js.map +1 -0
  22. package/dist/template-codegen/output-pattern.d.ts +5 -0
  23. package/dist/template-codegen/output-pattern.d.ts.map +1 -0
  24. package/dist/template-codegen/output-pattern.js +32 -0
  25. package/dist/template-codegen/output-pattern.js.map +1 -0
  26. package/dist/template-codegen/template-data.d.ts +45 -0
  27. package/dist/template-codegen/template-data.d.ts.map +1 -0
  28. package/dist/template-codegen/template-data.js +60 -0
  29. package/dist/template-codegen/template-data.js.map +1 -0
  30. package/dist/template-codegen/template-spec.d.ts +20 -0
  31. package/dist/template-codegen/template-spec.d.ts.map +1 -0
  32. package/dist/template-codegen/template-spec.js +57 -0
  33. package/dist/template-codegen/template-spec.js.map +1 -0
  34. package/package.json +6 -6
  35. package/src/generator.ts +36 -1
  36. package/src/generators/index.ts +11 -0
  37. package/src/generators/template-generator.ts +66 -3
  38. package/src/index.ts +44 -1
  39. package/src/reference/barrel.ts +66 -0
  40. package/src/reference/entity.ts +147 -0
  41. package/src/reference/queries.ts +124 -0
  42. package/src/reference/routes.ts +58 -0
  43. package/src/reference-templates.ts +49 -0
  44. package/src/template-codegen/output-pattern.ts +36 -0
  45. package/src/template-codegen/template-data.ts +87 -0
  46. package/src/template-codegen/template-spec.schema.json +27 -0
  47. package/src/template-codegen/template-spec.ts +75 -0
@@ -0,0 +1,60 @@
1
+ import { FIELD_ATTR_VALUES, FIELD_SUBTYPE_ENUM } from "@metaobjectsdev/metadata";
2
+ import { effectivePackage } from "../docs-paths.js";
3
+ /** The effective package of an object — its own package OR the file-default
4
+ * folded into `resolutionKey()`. `entity.package` alone is usually undefined
5
+ * (object fqn() stays bare), so all package reads go through here. */
6
+ export function packageOf(entity) {
7
+ return effectivePackage(entity) ?? "";
8
+ }
9
+ function fieldData(field) {
10
+ const d = {
11
+ name: field.name,
12
+ type: field.subType,
13
+ required: field.isRequired,
14
+ isArray: field.isArray === true,
15
+ };
16
+ if (typeof field.maxLength === "number")
17
+ d.maxLength = field.maxLength;
18
+ if (field.subType === FIELD_SUBTYPE_ENUM) {
19
+ const vals = field.attr(FIELD_ATTR_VALUES);
20
+ if (Array.isArray(vals))
21
+ d.enumValues = vals.map((v) => String(v));
22
+ }
23
+ return d;
24
+ }
25
+ export function buildEntityTemplateData(entity) {
26
+ return {
27
+ name: entity.name,
28
+ package: packageOf(entity),
29
+ fields: entity.fields().map(fieldData),
30
+ identities: entity.identities().map((i) => ({ kind: i.subType, fields: [...i.fields] })),
31
+ relationships: entity.relationships().map((r) => ({
32
+ name: r.name,
33
+ cardinality: r.cardinality ?? "",
34
+ targetRef: r.objectRef ?? "",
35
+ })),
36
+ };
37
+ }
38
+ export function buildPackageTemplateData(pkg, entities) {
39
+ return { package: pkg, entities: entities.map(buildEntityTemplateData) };
40
+ }
41
+ /** Groups concrete (non-abstract) objects by package — packages ascending,
42
+ * entities in `root.objects()` order. Abstract objects never emit instance
43
+ * artifacts, so they are excluded. */
44
+ export function buildModelTemplateData(root) {
45
+ const concrete = root.objects().filter((o) => o.isAbstract !== true);
46
+ const byPkg = new Map();
47
+ for (const o of concrete) {
48
+ const pkg = packageOf(o);
49
+ let bucket = byPkg.get(pkg);
50
+ if (bucket === undefined) {
51
+ bucket = [];
52
+ byPkg.set(pkg, bucket);
53
+ }
54
+ bucket.push(o);
55
+ }
56
+ return {
57
+ packages: [...byPkg.keys()].sort().map((pkg) => buildPackageTemplateData(pkg, byPkg.get(pkg))),
58
+ };
59
+ }
60
+ //# sourceMappingURL=template-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-data.js","sourceRoot":"","sources":["../../src/template-codegen/template-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type RenderFormat } from "@metaobjectsdev/render";
2
+ import type { Generator } from "../generator.js";
3
+ import { type TemplateScope } from "../generators/template-generator.js";
4
+ export interface TemplateSpecEntry {
5
+ name: string;
6
+ template: string;
7
+ scope: TemplateScope;
8
+ outputPattern: string;
9
+ format?: RenderFormat;
10
+ target?: string;
11
+ }
12
+ export interface TemplateSpecFile {
13
+ generators: TemplateSpecEntry[];
14
+ }
15
+ /** Validate + narrow an untyped JSON value into a TemplateSpecFile. Throws on
16
+ * any shape violation (missing/empty required string, bad scope, non-object). */
17
+ export declare function parseTemplateSpec(json: unknown): TemplateSpecFile;
18
+ /** Map a parsed spec into runnable Generators (one templateGenerator per entry). */
19
+ export declare function templateSpecToGenerators(spec: TemplateSpecFile): Generator[];
20
+ //# sourceMappingURL=template-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-spec.d.ts","sourceRoot":"","sources":["../../src/template-codegen/template-spec.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAK5F,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,gBAAgB;IAAG,UAAU,EAAE,iBAAiB,EAAE,CAAC;CAAE;AAMtE;kFACkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,CAkCjE;AAED,oFAAoF;AACpF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,EAAE,CAW5E"}
@@ -0,0 +1,57 @@
1
+ // The declarative JSON template-spec the CLI ports (C#/Python) consume, and TS
2
+ // can spread into `generators`. The JSON shape is the cross-port contract
3
+ // (SP-1 §4); a JSON Schema (template-spec.schema.json) sits beside it.
4
+ import { ESCAPERS } from "@metaobjectsdev/render";
5
+ import { templateGenerator } from "../generators/template-generator.js";
6
+ const SCOPES = ["perEntity", "perPackage", "perModel"];
7
+ const FORMATS = Object.keys(ESCAPERS);
8
+ function isRecord(v) {
9
+ return typeof v === "object" && v !== null && !Array.isArray(v);
10
+ }
11
+ /** Validate + narrow an untyped JSON value into a TemplateSpecFile. Throws on
12
+ * any shape violation (missing/empty required string, bad scope, non-object). */
13
+ export function parseTemplateSpec(json) {
14
+ if (!isRecord(json) || !Array.isArray(json.generators)) {
15
+ throw new Error("template-spec: expected an object with a `generators` array");
16
+ }
17
+ const generators = json.generators.map((raw, i) => {
18
+ if (!isRecord(raw))
19
+ throw new Error(`template-spec generators[${i}]: expected an object`);
20
+ for (const key of ["name", "template", "scope", "outputPattern"]) {
21
+ if (typeof raw[key] !== "string" || raw[key] === "") {
22
+ throw new Error(`template-spec generators[${i}]: missing or empty required string '${key}'`);
23
+ }
24
+ }
25
+ if (!SCOPES.includes(raw.scope)) {
26
+ throw new Error(`template-spec generators[${i}]: scope must be one of ${SCOPES.join(" | ")}, got '${String(raw.scope)}'`);
27
+ }
28
+ const entry = {
29
+ name: raw.name,
30
+ template: raw.template,
31
+ scope: raw.scope,
32
+ outputPattern: raw.outputPattern,
33
+ };
34
+ if (raw.format !== undefined) {
35
+ if (typeof raw.format !== "string" || !FORMATS.includes(raw.format)) {
36
+ throw new Error(`template-spec generators[${i}]: format must be one of ${FORMATS.join(" | ")}, got '${String(raw.format)}'`);
37
+ }
38
+ entry.format = raw.format;
39
+ }
40
+ if (typeof raw.target === "string")
41
+ entry.target = raw.target;
42
+ return entry;
43
+ });
44
+ return { generators };
45
+ }
46
+ /** Map a parsed spec into runnable Generators (one templateGenerator per entry). */
47
+ export function templateSpecToGenerators(spec) {
48
+ return spec.generators.map((e) => templateGenerator({
49
+ name: e.name,
50
+ template: e.template,
51
+ scope: e.scope,
52
+ outputPattern: e.outputPattern,
53
+ ...(e.format !== undefined ? { format: e.format } : {}),
54
+ ...(e.target !== undefined ? { target: e.target } : {}),
55
+ }));
56
+ }
57
+ //# sourceMappingURL=template-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-spec.js","sourceRoot":"","sources":["../../src/template-codegen/template-spec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/codegen-ts",
3
- "version": "0.12.5-rc.1",
3
+ "version": "0.13.0-rc.1",
4
4
  "description": "TypeScript codegen engine for MetaObjects — emits Drizzle, Zod, and Fastify artifacts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -111,8 +111,8 @@
111
111
  "access": "public"
112
112
  },
113
113
  "dependencies": {
114
- "@metaobjectsdev/metadata": "0.12.5-rc.1",
115
- "@metaobjectsdev/render": "0.12.5-rc.1",
114
+ "@metaobjectsdev/metadata": "0.13.0-rc.1",
115
+ "@metaobjectsdev/render": "0.13.0-rc.1",
116
116
  "@biomejs/js-api": "^0.7.0",
117
117
  "@biomejs/wasm-nodejs": "^1.9.4",
118
118
  "ts-poet": "^6.10.0"
@@ -123,9 +123,9 @@
123
123
  },
124
124
  "devDependencies": {
125
125
  "@biomejs/biome": "^1.9.0",
126
- "@metaobjectsdev/codegen-ts-react": "0.12.5-rc.1",
127
- "@metaobjectsdev/migrate-ts": "0.12.5-rc.1",
128
- "@metaobjectsdev/runtime-ts": "0.12.5-rc.1",
126
+ "@metaobjectsdev/codegen-ts-react": "0.13.0-rc.1",
127
+ "@metaobjectsdev/migrate-ts": "0.13.0-rc.1",
128
+ "@metaobjectsdev/runtime-ts": "0.13.0-rc.1",
129
129
  "bun-types": "latest",
130
130
  "drizzle-orm": "^0.36.0",
131
131
  "hono": "^4.6.0",
package/src/generator.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
2
2
  import type { RenderContext } from "./render-context.js";
3
3
  import type { ResolvedGenConfig } from "./metaobjects-config.js";
4
+ import { effectivePackage } from "./docs-paths.js";
4
5
 
5
6
  export interface EmittedFile {
6
7
  /** Path relative to ResolvedGenConfig.outDir. */
@@ -73,7 +74,9 @@ export function perEntity(
73
74
  };
74
75
  }
75
76
 
76
- /** Called once with all matching entities. Use for barrels and cross-entity files. */
77
+ /** Called once with all matching entities. Use for barrels and cross-entity files.
78
+ * @deprecated Use {@link perModel} — "run" is ambiguous under multi-target output
79
+ * (it reads as "per target"); `perModel` names the data scope (the whole model). */
77
80
  export function oncePerRun(
78
81
  fn: (entities: MetaObject[], ctx: GenContext) =>
79
82
  | EmittedFile
@@ -86,3 +89,35 @@ export function oncePerRun(
86
89
  return Array.isArray(result) ? result : [result];
87
90
  };
88
91
  }
92
+
93
+ /** App-scope convenience — run `fn` once over the whole model (all matched
94
+ * entities). The canonical name for the one-shot scope (replaces `oncePerRun`). */
95
+ export const perModel = oncePerRun;
96
+
97
+ /** One-file-per-package convenience. Groups matched entities by `package`, runs
98
+ * `fn` once per package — packages ascending, entities keeping `ctx.entities`
99
+ * order. The package scope from codegen-concepts §10 (object + model already
100
+ * exist via perEntity + perModel). */
101
+ export function perPackage(
102
+ fn: (pkg: string, entities: MetaObject[], ctx: GenContext) =>
103
+ | EmittedFile
104
+ | EmittedFile[]
105
+ | Promise<EmittedFile | EmittedFile[]>,
106
+ ): (ctx: GenContext) => Promise<EmittedFile[]> {
107
+ return async (ctx) => {
108
+ const matched = ctx.entities.filter(ctx.matches);
109
+ const byPkg = new Map<string, MetaObject[]>();
110
+ for (const e of matched) {
111
+ const pkg = effectivePackage(e) ?? "";
112
+ let bucket = byPkg.get(pkg);
113
+ if (bucket === undefined) { bucket = []; byPkg.set(pkg, bucket); }
114
+ bucket.push(e);
115
+ }
116
+ const out: EmittedFile[] = [];
117
+ for (const pkg of [...byPkg.keys()].sort()) {
118
+ const r = await fn(pkg, byPkg.get(pkg)!, ctx);
119
+ out.push(...(Array.isArray(r) ? r : [r]));
120
+ }
121
+ return out;
122
+ };
123
+ }
@@ -1,8 +1,19 @@
1
+ // ADR-0034 scaffold-and-own — these built-in generator factories remain the engine's
2
+ // internal composers, but importing them from `@metaobjectsdev/codegen-ts/generators`
3
+ // into a consumer's `metaobjects.config.ts` is DEPRECATED. The recommended path is to
4
+ // own copyable reference templates in your repo (`meta init` scaffolds them into
5
+ // `codegen/generators/*.ts`) and import those locally. This package export will be
6
+ // removed in a future major. See spec/decisions/ADR-0034-codegen-scaffold-and-own.md.
7
+
8
+ /** @deprecated ADR-0034 — own a copy instead: `import { entityFile } from "./codegen/generators/entity"` (scaffolded by `meta init`). */
1
9
  export { entityFile, type EntityFileOpts } from "./entity-file.js";
10
+ /** @deprecated ADR-0034 — own a copy instead: `import { queriesFile } from "./codegen/generators/queries"` (scaffolded by `meta init`). */
2
11
  export { queriesFile, type QueriesFileOpts } from "./queries-file.js";
3
12
  export { callableFile, type CallableFileOpts } from "./callable-file.js";
13
+ /** @deprecated ADR-0034 — own a copy instead: `import { routesFile } from "./codegen/generators/routes"` (scaffolded by `meta init`). */
4
14
  export { routesFile, type RoutesFileOpts } from "./routes-file.js";
5
15
  export { routesFileHono, type RoutesFileHonoOpts } from "./routes-file-hono.js";
16
+ /** @deprecated ADR-0034 — own a copy instead: `import { barrel } from "./codegen/generators/barrel"` (scaffolded by `meta init`). */
6
17
  export { barrel, type BarrelOpts } from "./barrel.js";
7
18
  /** @deprecated ADR-0021 D1 — neutral artifact owned by `meta docs` (ADR-0020); not part of the recommended `meta gen` suite. */
8
19
  export { mermaidErDiagram, type MermaidErOptions } from "./mermaid-er.js";
@@ -17,6 +17,17 @@ import type { MetaRoot, MetaObject } from "@metaobjectsdev/metadata";
17
17
  import { render, type Provider, type RenderFormat } from "@metaobjectsdev/render";
18
18
  import type { Generator, GenContext, EmittedFile, GeneratorFactory } from "../generator.js";
19
19
  import { projectProvider } from "../render-engine/framework-provider.js";
20
+ import { expandOutputPattern } from "../template-codegen/output-pattern.js";
21
+ import {
22
+ buildEntityTemplateData,
23
+ buildPackageTemplateData,
24
+ buildModelTemplateData,
25
+ packageOf,
26
+ } from "../template-codegen/template-data.js";
27
+
28
+ /** The three built-in walk scopes (SP-1 §3.1). Same vocabulary as the engine
29
+ * helpers perEntity/perPackage/perModel. */
30
+ export type TemplateScope = "perEntity" | "perPackage" | "perModel";
20
31
 
21
32
  export type TemplateFormat = RenderFormat;
22
33
 
@@ -34,8 +45,17 @@ export interface TemplateGeneratorOpts {
34
45
  name: string;
35
46
  /** Walk the loaded metadata tree and produce `{ data, outputPath }` tuples
36
47
  * — 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[]>;
48
+ * aggregator), pattern C (mixed), pattern D (filter inline) all fit.
49
+ * Mutually exclusive with `scope` provide exactly one. The power-user
50
+ * escape hatch; most consumers declare a `scope` + `outputPattern` instead. */
51
+ walk?: (root: MetaRoot) => TemplateWalkResult[] | Promise<TemplateWalkResult[]>;
52
+ /** Built-in walk scope (SP-1 §3.1) — declarative alternative to `walk`. The
53
+ * generator derives the neutral data dict (template-data.ts) per unit and
54
+ * names each file via `outputPattern`. Mutually exclusive with `walk`. */
55
+ scope?: TemplateScope;
56
+ /** Output path pattern for the built-in `scope` walk: `{name}` `{Name}`
57
+ * `{package}` (SP-1 §3.3). Required with `scope`; ignored with `walk`. */
58
+ outputPattern?: string;
39
59
  /** Template reference. Resolved by the configured Provider chain — by
40
60
  * default the project's `templates/<ref>.mustache` first, then the
41
61
  * framework defaults at `codegen-ts/templates/<ref>.mustache`. */
@@ -57,10 +77,53 @@ export interface TemplateGeneratorOpts {
57
77
  target?: string;
58
78
  }
59
79
 
80
+ /** Derive a `walk` from a built-in scope + output pattern. Each scope yields the
81
+ * neutral data dict for its unit and names the file via the pattern. */
82
+ function scopeWalk(
83
+ scope: TemplateScope,
84
+ pattern: string,
85
+ ): (root: MetaRoot) => TemplateWalkResult[] {
86
+ return (root) => {
87
+ const concrete = root.objects().filter((o) => o.isAbstract !== true);
88
+ if (scope === "perEntity") {
89
+ return concrete.map((e) => ({
90
+ data: buildEntityTemplateData(e),
91
+ outputPath: expandOutputPattern(pattern, { name: e.name, package: packageOf(e) }),
92
+ }));
93
+ }
94
+ if (scope === "perPackage") {
95
+ const byPkg = new Map<string, MetaObject[]>();
96
+ for (const o of concrete) {
97
+ const pkg = packageOf(o);
98
+ let bucket = byPkg.get(pkg);
99
+ if (bucket === undefined) { bucket = []; byPkg.set(pkg, bucket); }
100
+ bucket.push(o);
101
+ }
102
+ return [...byPkg.keys()].sort().map((pkg) => ({
103
+ data: buildPackageTemplateData(pkg, byPkg.get(pkg)!),
104
+ outputPath: expandOutputPattern(pattern, { package: pkg }),
105
+ }));
106
+ }
107
+ // perModel — one file over the whole model.
108
+ return [{ data: buildModelTemplateData(root), outputPath: expandOutputPattern(pattern, {}) }];
109
+ };
110
+ }
111
+
60
112
  export const templateGenerator = function templateGenerator(
61
113
  opts: TemplateGeneratorOpts,
62
114
  ): Generator {
63
115
  const fmt: TemplateFormat = opts.format ?? "text";
116
+ const hasWalk = typeof opts.walk === "function";
117
+ const hasScope = opts.scope !== undefined;
118
+ if (hasWalk === hasScope) {
119
+ throw new Error(
120
+ `templateGenerator(${opts.name}): provide exactly one of \`walk\` or (\`scope\` + \`outputPattern\`)`,
121
+ );
122
+ }
123
+ if (hasScope && (opts.outputPattern === undefined || opts.outputPattern === "")) {
124
+ throw new Error(`templateGenerator(${opts.name}): \`scope\` requires a non-empty \`outputPattern\``);
125
+ }
126
+ const walk = hasWalk ? opts.walk! : scopeWalk(opts.scope!, opts.outputPattern!);
64
127
  const generator: Generator = {
65
128
  name: opts.name,
66
129
  async generate(ctx: GenContext): Promise<EmittedFile[]> {
@@ -77,7 +140,7 @@ export const templateGenerator = function templateGenerator(
77
140
  );
78
141
  provider = projectProvider(process.cwd());
79
142
  }
80
- const walkRes = await opts.walk(ctx.loadedRoot);
143
+ const walkRes = await walk(ctx.loadedRoot);
81
144
  const files: EmittedFile[] = [];
82
145
  for (const { data, outputPath } of walkRes) {
83
146
  let content: string;
package/src/index.ts CHANGED
@@ -7,7 +7,27 @@ export { runGen } from "./runner.js";
7
7
  export type { RunGenOpts, RunGenResult } from "./runner.js";
8
8
 
9
9
  export type { Generator, GenContext, EmittedFile, GeneratorFactory } from "./generator.js";
10
- export { perEntity, oncePerRun } from "./generator.js";
10
+ export { perEntity, perPackage, perModel, oncePerRun } from "./generator.js";
11
+
12
+ // SP-1 declarative Mustache template-codegen — scope walks, neutral data dict,
13
+ // output-pattern, and the JSON template-spec the CLI ports reuse.
14
+ export { expandOutputPattern } from "./template-codegen/output-pattern.js";
15
+ export {
16
+ buildEntityTemplateData,
17
+ buildPackageTemplateData,
18
+ buildModelTemplateData,
19
+ } from "./template-codegen/template-data.js";
20
+ export type {
21
+ FieldTemplateData,
22
+ EntityTemplateData,
23
+ IdentityTemplateData,
24
+ RelationshipTemplateData,
25
+ PackageTemplateData,
26
+ ModelTemplateData,
27
+ } from "./template-codegen/template-data.js";
28
+ export { parseTemplateSpec, templateSpecToGenerators } from "./template-codegen/template-spec.js";
29
+ export type { TemplateSpecEntry, TemplateSpecFile } from "./template-codegen/template-spec.js";
30
+ export type { TemplateScope } from "./generators/template-generator.js";
11
31
 
12
32
  // ADR-0021 D3 — stable-name generator registry + discoverability surface.
13
33
  export {
@@ -68,6 +88,29 @@ export { isTphDiscriminatorBase, tphConcreteSubtypes, collectTphSubtypeFields, t
68
88
  export type { TphPlan, TphSubtypePlan } from "./templates/tph-discriminator.js";
69
89
  export { isTphSubtype, tphDiscriminatorPin } from "./templates/zod-validators.js";
70
90
 
91
+ // ADR-0034 reference-template composition helpers. Promoted to the public engine
92
+ // surface so a COPIED reference generator (src/reference/*.ts → consumer's
93
+ // codegen/generators/*.ts) imports only `@metaobjectsdev/codegen-ts`, never a
94
+ // package-internal relative path. These are the assembly pieces the built-in
95
+ // entity/queries composers use; the reference templates relocate that assembly.
96
+ export { renderTphDiscriminatorUnion } from "./templates/tph-discriminator.js";
97
+ export { hasWritableRdbSource } from "./source-detect.js";
98
+ export { renderSharedEnumsFile, SHARED_ENUMS_BASENAME } from "./templates/enums-file.js";
99
+
100
+ // ADR-0034 scaffold-and-own — reader for the copyable reference generators in
101
+ // `src/reference/*.ts`. `meta init` uses this to copy them into the consumer's repo.
102
+ export { resolveReferenceRoot, readReferenceTemplate, REFERENCE_GENERATOR_NAMES } from "./reference-templates.js";
103
+ export type { ReferenceGeneratorName } from "./reference-templates.js";
104
+
105
+ export {
106
+ renderFindByIdFn,
107
+ renderListFn,
108
+ renderCreateFn,
109
+ renderUpdateFn,
110
+ renderDeleteByIdFn,
111
+ getPkInfo,
112
+ } from "./templates/queries.js";
113
+
71
114
  // Built-in template render functions — the composition seam for adopters who
72
115
  // want to call a built-in template, then post-process / append to its output
73
116
  // from their own Generator (added to `generators: [...]`) WITHOUT forking the
@@ -0,0 +1,66 @@
1
+ // REFERENCE TEMPLATE — copy this into your repo (e.g. codegen/generators/barrel.ts) and own it.
2
+ // Then import it LOCALLY in metaobjects.config.ts instead of from the package:
3
+ // import { barrel } from "./codegen/generators/barrel";
4
+ //
5
+ // use-when: you want a single `index.ts` re-exporting every generated entity module.
6
+ // emits: <target>/index.ts with one `export * from "./<Entity>"` per entity, alphabetical.
7
+ // customize: the export form (star vs named), ordering, grouping by package, what to include/exclude.
8
+ // composes-with: entity.ts (this re-exports the files entity.ts emits).
9
+ //
10
+ // Everything below imports ONLY from `@metaobjectsdev/codegen-ts` (the stable engine).
11
+ // The composition (`renderBarrel`) is inlined here so you own it — change it freely.
12
+
13
+ import {
14
+ oncePerRun,
15
+ type Generator,
16
+ type GeneratorFactory,
17
+ type ExtStyle,
18
+ type ResolvedTarget,
19
+ barrelModuleSpecifier,
20
+ formatTs,
21
+ GENERATED_HEADER,
22
+ } from "@metaobjectsdev/codegen-ts";
23
+
24
+ interface BarrelEntry {
25
+ name: string;
26
+ package: string | undefined;
27
+ }
28
+
29
+ // --- composition (OWNED) — the barrel file body. Customize freely. ---
30
+ function renderBarrel(
31
+ entries: BarrelEntry[],
32
+ extStyle: ExtStyle,
33
+ selfTarget: ResolvedTarget,
34
+ entityModuleTarget: ResolvedTarget,
35
+ ): string {
36
+ const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
37
+ const exports = sorted
38
+ .map((e) => `export * from ${JSON.stringify(barrelModuleSpecifier(selfTarget, entityModuleTarget, e.package, e.name, extStyle))};`)
39
+ .join("\n");
40
+ return `// ${GENERATED_HEADER} — DO NOT EDIT.\n${exports}\n`;
41
+ }
42
+
43
+ export interface BarrelOpts {
44
+ target?: string;
45
+ }
46
+
47
+ export const barrel = function barrel(opts?: BarrelOpts): Generator {
48
+ const generator: Generator = {
49
+ name: "barrel",
50
+ generate: oncePerRun(async (entities, ctx) => ({
51
+ path: "index.ts",
52
+ content: await formatTs(
53
+ renderBarrel(
54
+ entities.map((e) => ({ name: e.name, package: e.package })),
55
+ ctx.renderContext!.extStyle,
56
+ ctx.renderContext!.selfTarget,
57
+ ctx.renderContext!.entityModuleTarget,
58
+ ),
59
+ ),
60
+ })),
61
+ };
62
+ if (opts?.target) {
63
+ generator.target = opts.target;
64
+ }
65
+ return generator;
66
+ } as GeneratorFactory<BarrelOpts>;
@@ -0,0 +1,147 @@
1
+ // REFERENCE TEMPLATE — copy this into your repo (e.g. codegen/generators/entity.ts) and own it.
2
+ // Then import it LOCALLY in metaobjects.config.ts:
3
+ // import { entityFile } from "./codegen/generators/entity";
4
+ //
5
+ // use-when: ALWAYS — this is the entity-module generator. It owns the shape of each
6
+ // generated <Entity>.ts (the Drizzle table, Zod schemas, inferred types,
7
+ // constants, filter allowlists). Start here and adapt the assembly.
8
+ // emits: <target>/<Entity>.ts per concrete object — and the shared-enums module once.
9
+ // Dispatches: abstract/value → interface + Zod; projection → read-only view decl;
10
+ // write-through entity → full Drizzle table path.
11
+ // customize: reorder/drop sections in `sections` below; change the header; swap a sub-renderer
12
+ // for your own (each render* is an engine primitive you call). To deeply own one
13
+ // section (e.g. the Drizzle emit), copy/replace that sub-render call with your code.
14
+ // composes-with: queries.ts, routes.ts, barrel.ts (they import the files this emits).
15
+ //
16
+ // The composition (`renderEntity`) is the relocated body of the built-in entity composer —
17
+ // byte-identical to start, now YOURS to change. It imports only public engine primitives.
18
+
19
+ import { joinCode, type Code } from "ts-poet";
20
+ import type { MetaObject } from "@metaobjectsdev/metadata";
21
+ import {
22
+ perEntity,
23
+ type EmittedFile,
24
+ type GenContext,
25
+ type Generator,
26
+ type GeneratorFactory,
27
+ type RenderContext,
28
+ // sub-renderers (engine primitives) — the LEGO blocks this composition assembles:
29
+ renderDrizzleSchema,
30
+ renderInferredTypes,
31
+ renderEnumTypeAliases,
32
+ renderZodValidators,
33
+ renderEntityConstants,
34
+ renderFilterAllowlist,
35
+ renderSortAllowlist,
36
+ renderFilterType,
37
+ renderTphDiscriminatorUnion,
38
+ isTphDiscriminatorBase,
39
+ renderProjectionDecl,
40
+ renderValueObjectFile,
41
+ renderSharedEnumsFile,
42
+ SHARED_ENUMS_BASENAME,
43
+ // predicates + helpers:
44
+ isProjection,
45
+ isAbstract,
46
+ hasWritableRdbSource,
47
+ // engine plumbing:
48
+ formatTs,
49
+ entityOutputPath,
50
+ GENERATED_HEADER,
51
+ } from "@metaobjectsdev/codegen-ts";
52
+
53
+ export interface RenderEntityOpts {
54
+ readonly allowlists?: boolean;
55
+ }
56
+
57
+ // --- composition (OWNED) — assembles one <Entity>.ts. Change this to change the output. ---
58
+ function renderEntity(entity: MetaObject, ctx: RenderContext, opts?: RenderEntityOpts): string {
59
+ const runtime = ctx.selfTarget.runtime;
60
+ const allowlists = runtime ? (opts?.allowlists ?? true) : false;
61
+
62
+ // Abstract → shape only (interface + Zod), never a table.
63
+ if (isAbstract(entity)) {
64
+ return renderValueObjectFile(entity, ctx.apiPrefix, ctx);
65
+ }
66
+ // Projection → read-only view declaration + read schema.
67
+ if (isProjection(entity)) {
68
+ return renderProjectionDecl(entity, ctx.loadedRoot, {
69
+ columnNamingStrategy: ctx.columnNamingStrategy,
70
+ dialect: ctx.dialect,
71
+ apiPrefix: ctx.apiPrefix,
72
+ timestampMode: ctx.timestampMode,
73
+ allowlists,
74
+ ctx,
75
+ includeViewDecl: runtime,
76
+ });
77
+ }
78
+ // Value-only / contract target → interface + Zod, no Drizzle table.
79
+ if (!runtime || !hasWritableRdbSource(entity)) {
80
+ return renderValueObjectFile(entity, ctx.apiPrefix, ctx);
81
+ }
82
+
83
+ // Write-through entity → the full Drizzle table file. Reorder/drop sections freely.
84
+ const enumAliases = renderEnumTypeAliases(entity, ctx);
85
+ const tphBlock = renderTphDiscriminatorUnion(entity, ctx.loadedRoot);
86
+ const tphBase = tphBlock !== null && isTphDiscriminatorBase(entity, ctx.loadedRoot);
87
+ const sections: Code[] = [
88
+ renderDrizzleSchema(entity, ctx),
89
+ renderInferredTypes(entity, tphBase, ctx),
90
+ ...(enumAliases !== null ? [enumAliases] : []),
91
+ renderZodValidators(entity, ctx),
92
+ renderEntityConstants(entity, ctx.apiPrefix),
93
+ ...(allowlists ? [renderFilterAllowlist(entity), renderSortAllowlist(entity)] : []),
94
+ renderFilterType(entity),
95
+ ...(tphBlock !== null ? [tphBlock] : []),
96
+ ];
97
+
98
+ const body = joinCode(sections, { on: "\n" }).toString();
99
+ const header =
100
+ `// ${GENERATED_HEADER} — DO NOT EDIT.\n` +
101
+ `// Source metadata: ${entity.name} (${entity.fqn()})\n` +
102
+ `// Customize via ${entity.name}.extra.ts in this directory.\n`;
103
+ return header + body;
104
+ }
105
+
106
+ export interface EntityFileOpts {
107
+ filter?: (entity: MetaObject) => boolean;
108
+ target?: string;
109
+ allowlists?: boolean;
110
+ }
111
+
112
+ export const entityFile = function entityFile(opts?: EntityFileOpts): Generator {
113
+ const allowlists = opts?.allowlists ?? true;
114
+ const perEntityEmit = perEntity(async (entity, ctx) => {
115
+ if (!ctx.renderContext) {
116
+ throw new Error("entity-file: renderContext is required (provided by runGen)");
117
+ }
118
+ if (isAbstract(entity) && !ctx.renderContext.emitAbstractShapes) {
119
+ return [];
120
+ }
121
+ return {
122
+ path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.ts`),
123
+ content: await formatTs(renderEntity(entity, ctx.renderContext, { allowlists })),
124
+ };
125
+ });
126
+
127
+ const generator: Generator = {
128
+ name: "entity-file",
129
+ emitsEntityModule: true,
130
+ generate: async (ctx: GenContext): Promise<EmittedFile[]> => {
131
+ const files = await perEntityEmit(ctx);
132
+ // FR-019: emit the shared-enums module once per run (null → no file).
133
+ const sharedEnums = renderSharedEnumsFile(ctx.loadedRoot);
134
+ if (sharedEnums !== null) {
135
+ files.push({ path: `${SHARED_ENUMS_BASENAME}.ts`, content: await formatTs(sharedEnums) });
136
+ }
137
+ return files;
138
+ },
139
+ };
140
+ if (opts?.filter) {
141
+ generator.filter = opts.filter;
142
+ }
143
+ if (opts?.target) {
144
+ generator.target = opts.target;
145
+ }
146
+ return generator;
147
+ } as GeneratorFactory<EntityFileOpts>;