@metaobjectsdev/codegen-ts 0.24.3 → 0.24.5
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/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/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +11 -6
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +29 -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/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/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +4 -3
- package/dist/payload-codegen.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 +9 -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/runner.d.ts.map +1 -1
- package/dist/runner.js +6 -0
- package/dist/runner.js.map +1 -1
- 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/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 +35 -19
- package/dist/templates/requirement-test.js.map +1 -1
- package/package.json +6 -6
- package/src/client-directive.ts +36 -0
- package/src/generators/routes-file-hono.ts +11 -9
- package/src/index.ts +6 -1
- package/src/metaobjects-config.ts +30 -0
- package/src/naming.ts +23 -0
- package/src/payload-codegen.ts +4 -3
- package/src/prompt-generator-gate.ts +73 -0
- package/src/reference/barrel.ts +3 -0
- package/src/reference/entity.ts +7 -0
- package/src/reference/queries.ts +5 -0
- package/src/reference/routes-hono.ts +103 -0
- package/src/reference/routes.ts +5 -0
- package/src/reference-templates.ts +61 -21
- package/src/render-context.ts +10 -1
- package/src/runner.ts +7 -0
- package/src/templates/output-parser.ts +54 -7
- package/src/templates/render-helper.ts +3 -1
- package/src/templates/requirement-test.ts +36 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/codegen-ts",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
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.24.
|
|
115
|
-
"@metaobjectsdev/render": "0.24.
|
|
114
|
+
"@metaobjectsdev/metadata": "0.24.5",
|
|
115
|
+
"@metaobjectsdev/render": "0.24.5",
|
|
116
116
|
"@biomejs/js-api": "^0.7.0",
|
|
117
117
|
"@biomejs/wasm-nodejs": "^1.9.4",
|
|
118
118
|
"@toon-format/toon": "^2.3.0",
|
|
@@ -124,9 +124,9 @@
|
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
126
|
"@biomejs/biome": "^1.9.0",
|
|
127
|
-
"@metaobjectsdev/codegen-ts-react": "0.24.
|
|
128
|
-
"@metaobjectsdev/migrate-ts": "0.24.
|
|
129
|
-
"@metaobjectsdev/runtime-ts": "0.24.
|
|
127
|
+
"@metaobjectsdev/codegen-ts-react": "0.24.5",
|
|
128
|
+
"@metaobjectsdev/migrate-ts": "0.24.5",
|
|
129
|
+
"@metaobjectsdev/runtime-ts": "0.24.5",
|
|
130
130
|
"bun-types": "latest",
|
|
131
131
|
"drizzle-orm": "^0.38.0",
|
|
132
132
|
"hono": "^4.6.0",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// FR-040 §6.4 — the module-level client-component directive on generated CLIENT
|
|
2
|
+
// artifacts (forms, hooks, grid columns, grid hooks).
|
|
3
|
+
//
|
|
4
|
+
// One implementation in the shared engine rather than a string literal in each of the
|
|
5
|
+
// four UI generators: the directive must be the FIRST statement in the module to have
|
|
6
|
+
// any effect, and "prepend a line" is the kind of thing four copies get subtly
|
|
7
|
+
// different (a missing blank line, a single-quoted variant, or — the one that actually
|
|
8
|
+
// breaks — emitted after the generated header comment in one and before it in another).
|
|
9
|
+
//
|
|
10
|
+
// Why this is a config knob and not metadata, and why it defaults OFF, is on
|
|
11
|
+
// `MetaobjectsGenConfig.clientDirective`.
|
|
12
|
+
|
|
13
|
+
/** The directive itself. Double-quoted to match the emitted code's own quote style. */
|
|
14
|
+
export const CLIENT_DIRECTIVE = '"use client";';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prepend the client directive to a generated module body when the project asked for it.
|
|
18
|
+
*
|
|
19
|
+
* Placement is the whole point: a directive prologue is only honoured before any other
|
|
20
|
+
* statement, and — unlike a runtime statement — it must also precede the `@generated`
|
|
21
|
+
* header comment for some bundlers to see it, so this goes at absolute position 0.
|
|
22
|
+
* Idempotent: a body that already opens with the directive (a hand edit preserved
|
|
23
|
+
* through three-way merge, or an owned generator that adds its own) is returned
|
|
24
|
+
* unchanged rather than given a second copy.
|
|
25
|
+
*
|
|
26
|
+
* @param body the rendered module source.
|
|
27
|
+
* @param enabled `ctx.clientDirective` — false (the default) returns `body` untouched,
|
|
28
|
+
* so output is byte-identical for every project that does not opt in.
|
|
29
|
+
*/
|
|
30
|
+
export function withClientDirective(body: string, enabled: boolean): string {
|
|
31
|
+
if (!enabled) return body;
|
|
32
|
+
// Tolerate either quote style when checking, so an adopter's own `'use client'`
|
|
33
|
+
// is not doubled — but always EMIT the canonical form.
|
|
34
|
+
if (/^\s*['"]use client['"];?/.test(body)) return body;
|
|
35
|
+
return `${CLIENT_DIRECTIVE}\n\n${body}`;
|
|
36
|
+
}
|
|
@@ -31,6 +31,15 @@ export interface RoutesFileHonoOpts {
|
|
|
31
31
|
*/
|
|
32
32
|
export const routesFileHono = function routesFileHono(opts?: RoutesFileHonoOpts): Generator {
|
|
33
33
|
const userFilter = opts?.filter ?? (() => true);
|
|
34
|
+
// Eligibility, minus the TPH question — stated ONCE so the emit set and the warn set
|
|
35
|
+
// below cannot drift apart. They differ only by `!isTphSubtype` vs `isTphSubtype`, and
|
|
36
|
+
// written out twice a later edit to one silently makes an entity either stop emitting
|
|
37
|
+
// without being named as held back, or get warned about while still emitting.
|
|
38
|
+
const passesOtherGates = (e: MetaObject): boolean =>
|
|
39
|
+
// ADR-0039: resolving — a concrete entity may inherit @emitRoutes via extends.
|
|
40
|
+
e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false
|
|
41
|
+
&& hasAnyRdbSource(e)
|
|
42
|
+
&& userFilter(e);
|
|
34
43
|
const generator: Generator = {
|
|
35
44
|
name: "routes-file-hono",
|
|
36
45
|
// Marks this as the Hono routes generator so the runner can aggregate
|
|
@@ -46,18 +55,11 @@ export const routesFileHono = function routesFileHono(opts?: RoutesFileHonoOpts)
|
|
|
46
55
|
// these to a discriminator-aware renderer; the Hono runtime has no discriminator
|
|
47
56
|
// support yet, so this fails CLOSED and the run says so (see the warning below)
|
|
48
57
|
// rather than shipping an artifact that returns the wrong rows.
|
|
49
|
-
filter: (e: MetaObject) =>
|
|
50
|
-
e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false
|
|
51
|
-
&& hasAnyRdbSource(e)
|
|
52
|
-
&& !isTphSubtype(e)
|
|
53
|
-
&& userFilter(e),
|
|
58
|
+
filter: (e: MetaObject) => passesOtherGates(e) && !isTphSubtype(e),
|
|
54
59
|
generate: async (ctx) => {
|
|
55
60
|
// One note per run naming every TPH subtype held back, so the gap is visible at
|
|
56
61
|
// `meta gen` time rather than discovered as missing endpoints in production.
|
|
57
|
-
const skipped = ctx.entities.filter(
|
|
58
|
-
(e) => e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false
|
|
59
|
-
&& hasAnyRdbSource(e) && isTphSubtype(e) && userFilter(e),
|
|
60
|
-
);
|
|
62
|
+
const skipped = ctx.entities.filter((e) => passesOtherGates(e) && isTphSubtype(e));
|
|
61
63
|
if (skipped.length > 0) {
|
|
62
64
|
ctx.warn(
|
|
63
65
|
`no Hono routes emitted for the TPH subtype(s) ${skipped.map((e) => e.name).join(", ")} — ` +
|
package/src/index.ts
CHANGED
|
@@ -110,7 +110,7 @@ export { renderSharedEnumsFile, SHARED_ENUMS_BASENAME } from "./templates/enums-
|
|
|
110
110
|
|
|
111
111
|
// ADR-0034 scaffold-and-own — reader for the copyable reference generators in
|
|
112
112
|
// `src/reference/*.ts`. `meta init` uses this to copy them into the consumer's repo.
|
|
113
|
-
export { resolveReferenceRoot, readReferenceTemplate, REFERENCE_GENERATOR_NAMES } from "./reference-templates.js";
|
|
113
|
+
export { resolveReferenceRoot, readReferenceTemplate, REFERENCE_GENERATOR_NAMES, makeReferenceReader } from "./reference-templates.js";
|
|
114
114
|
export type { ReferenceGeneratorName } from "./reference-templates.js";
|
|
115
115
|
|
|
116
116
|
// ts-poet composition primitives, re-exported from THIS package's own ts-poet
|
|
@@ -161,6 +161,7 @@ export { renderFilterAllowlist, renderSortAllowlist } from "./templates/filter-a
|
|
|
161
161
|
export { renderEntityConstants, resourcePath } from "./templates/entity-constants.js";
|
|
162
162
|
export { renderQueriesFile } from "./templates/queries-file.js";
|
|
163
163
|
export { renderRoutesFile } from "./templates/routes-file.js";
|
|
164
|
+
export { renderRoutesFileHono } from "./templates/routes-file-hono.js";
|
|
164
165
|
export { renderValueObjectFile } from "./templates/value-object-file.js";
|
|
165
166
|
export { renderProjectionDecl } from "./templates/projection-decl.js";
|
|
166
167
|
export type { ProjectionDeclOpts } from "./templates/projection-decl.js";
|
|
@@ -251,3 +252,7 @@ export type {
|
|
|
251
252
|
SweepOrphansArgs,
|
|
252
253
|
SweepOrphansResult,
|
|
253
254
|
} from "./orphan-sweep.js";
|
|
255
|
+
|
|
256
|
+
// FR-040 §6.4 — the client-component directive for generated CLIENT artifacts.
|
|
257
|
+
// Public so an OWNED generator applies it the same way the built-ins do.
|
|
258
|
+
export { withClientDirective, CLIENT_DIRECTIVE } from "./client-directive.js";
|
|
@@ -108,6 +108,34 @@ export interface MetaobjectsGenConfig extends Omit<ResolvedGenConfig, "dbImport"
|
|
|
108
108
|
* (e.g. `{ AuditLog: "auditLog", LlmTierConfig: "llmTierConfig" }`).
|
|
109
109
|
*/
|
|
110
110
|
collectionNameOverrides?: Record<string, string>;
|
|
111
|
+
/**
|
|
112
|
+
* Prepend a module-level client-component directive — `"use client";` — to every
|
|
113
|
+
* generated CLIENT artifact (forms, hooks, grid columns, grid hooks). Defaults to
|
|
114
|
+
* `false`. FR-040 §6.4.
|
|
115
|
+
*
|
|
116
|
+
* **Why this is config and not a metadata attribute.** It is a fact about the
|
|
117
|
+
* adopter's BUNDLER TOPOLOGY, not about the entity — the metamodel contains nothing
|
|
118
|
+
* that could derive it, and every non-TS port would carry a registration it can never
|
|
119
|
+
* dispatch on, which is the `source.rdb @role` mistake that retired four members in
|
|
120
|
+
* 0.21.0. It sits here beside `extStyle` and `columnNamingStrategy` for the same
|
|
121
|
+
* reason `pluralizeCollections` does: a per-port codegen concern (ADR-0001), so
|
|
122
|
+
* config, and no cross-port conformance cost.
|
|
123
|
+
*
|
|
124
|
+
* **Why it defaults to `false`.** The generated form and hook modules genuinely ARE
|
|
125
|
+
* client components, so the directive is a true statement about them — but it is only
|
|
126
|
+
* REQUIRED by frameworks that compile server and client from one tree (React Server
|
|
127
|
+
* Components). Elsewhere it is inert and provokes module-level-directive warnings in
|
|
128
|
+
* some bundlers, so defaulting it on would put noise in every generated UI file for
|
|
129
|
+
* the majority to save the minority one line. The asymmetry that would argue for
|
|
130
|
+
* defaulting on — a runtime error for RSC adopters versus a build warning for
|
|
131
|
+
* everyone else — is what FR-040 itself removed: before it, an RSC adopter had no
|
|
132
|
+
* seam at all; now it is this flag, or `meta eject form` and a one-line prepend.
|
|
133
|
+
*
|
|
134
|
+
* Flipping the default needs EVIDENCE, not a guess about majorities — the standard
|
|
135
|
+
* `extStyle` was held to in 0.20.1, where the default moved because the documented
|
|
136
|
+
* quickstart provably failed under a stock `tsc --init`.
|
|
137
|
+
*/
|
|
138
|
+
clientDirective?: boolean;
|
|
111
139
|
/**
|
|
112
140
|
* Drizzle timestamp column mode. "string" (default) types timestamp columns as
|
|
113
141
|
* ISO-8601 strings (matches the generated Zod + cross-port wire contract); "date"
|
|
@@ -186,6 +214,7 @@ export interface NormalizedMetaobjectsGenConfig
|
|
|
186
214
|
pluralizeCollections: boolean;
|
|
187
215
|
collectionNameOverrides: Record<string, string>;
|
|
188
216
|
timestampMode: "date" | "string";
|
|
217
|
+
clientDirective: boolean;
|
|
189
218
|
apiPrefix: string;
|
|
190
219
|
emitAbstractShapes: boolean;
|
|
191
220
|
outputLayout: OutputLayout;
|
|
@@ -322,6 +351,7 @@ export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobj
|
|
|
322
351
|
// normalize to "string" on sqlite/D1 at this one choke point so the option
|
|
323
352
|
// can never silently emit a non-compiling column + a disagreeing Zod schema.
|
|
324
353
|
timestampMode: dialect === "sqlite" ? "string" : (config.timestampMode ?? "string"),
|
|
354
|
+
clientDirective: config.clientDirective ?? false,
|
|
325
355
|
apiPrefix: config.apiPrefix ?? "",
|
|
326
356
|
emitAbstractShapes: config.emitAbstractShapes ?? true,
|
|
327
357
|
outputLayout: config.outputLayout ?? "flat",
|
package/src/naming.ts
CHANGED
|
@@ -191,5 +191,28 @@ export function routesHandlerName(entityName: string): string {
|
|
|
191
191
|
return `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Routes`;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The PascalCase base every generated symbol for a `template.*` node is built from —
|
|
197
|
+
* `render<Base>`, `parse<Base>`, `safeParse<Base>`, `<Base>Schema`, `<Base>Data`,
|
|
198
|
+
* `<Base>ValidationError`, `extractLenient<Base>WithLoader`.
|
|
199
|
+
*
|
|
200
|
+
* Kept here, and used by ALL of the template emitters, because they disagreed. Three
|
|
201
|
+
* generators named the same node three ways: `promptRender()`'s handle applied a private
|
|
202
|
+
* `pascal()` (`renderTriageTicket`), while `outputParser()` and the render-helper
|
|
203
|
+
* concatenated the raw name (`parsetriageTicket`, `type triageTicketData`). One `meta gen`
|
|
204
|
+
* run, one template, two conventions — and the skill's own examples use the lower-camel
|
|
205
|
+
* spelling that triggers it, so following the documentation is what produced it.
|
|
206
|
+
*
|
|
207
|
+
* An UpperCamel template name is unaffected (`pascal("TriageTicket") === "TriageTicket"`),
|
|
208
|
+
* so this only moves symbols for lower-camel names, which are exactly the ones that were
|
|
209
|
+
* spelled inconsistently.
|
|
210
|
+
*/
|
|
211
|
+
export function templateSymbolBase(templateName: string): string {
|
|
212
|
+
return templateName.length > 0
|
|
213
|
+
? templateName[0]!.toUpperCase() + templateName.slice(1)
|
|
214
|
+
: templateName;
|
|
215
|
+
}
|
|
216
|
+
|
|
194
217
|
// Re-exported here for callers that import from codegen-ts's naming module.
|
|
195
218
|
export { toKebabCase };
|
package/src/payload-codegen.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
stripPackage,
|
|
42
42
|
} from "@metaobjectsdev/metadata";
|
|
43
43
|
import { enumValues } from "./enum-meta.js";
|
|
44
|
+
import { templateSymbolBase } from "./naming.js";
|
|
44
45
|
import { enumUnionAliasName, enumUnionString } from "./templates/inferred-types.js";
|
|
45
46
|
import { assignEmittedNames } from "./naming/collision-names.js";
|
|
46
47
|
|
|
@@ -258,9 +259,9 @@ export function generatePayloadInterfacesBatch(
|
|
|
258
259
|
return out.length === 0 ? "" : out.join("\n\n") + "\n";
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
// Delegates to the shared authority so this emitter cannot drift from the parser and
|
|
263
|
+
// render-helper again (naming.ts / templateSymbolBase).
|
|
264
|
+
const pascal = templateSymbolBase;
|
|
264
265
|
|
|
265
266
|
/** Emit a typed render handle binding a template's @textRef + @format and typing its payload. */
|
|
266
267
|
export function generateRenderHandle(root: MetaData, templateName: string): string {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { MetaData } from "@metaobjectsdev/metadata";
|
|
2
|
+
import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_ATTR_RESPONSE_REF } from "@metaobjectsdev/metadata";
|
|
3
|
+
import type { Generator } from "./generator.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generators that turn a declared `template.*` into runnable prompt code. Names, not
|
|
7
|
+
* identities, because an adopter may wrap or re-export them (ADR-0034 scaffold-and-own
|
|
8
|
+
* makes owning a copy the encouraged path) and a wrapper keeps the name.
|
|
9
|
+
*/
|
|
10
|
+
const PROMPT_GENERATOR_NAMES: ReadonlySet<string> = new Set([
|
|
11
|
+
"prompt-render",
|
|
12
|
+
"output-parser",
|
|
13
|
+
"render-helper",
|
|
14
|
+
"output-prompt",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A declared prompt with no prompt generator wired produced NOTHING and said NOTHING.
|
|
19
|
+
*
|
|
20
|
+
* `meta gen` emitted the payload value objects (they are `object.value` nodes, which the
|
|
21
|
+
* entity generator picks up) and stopped there: no `render<Name>()`, no parser, no
|
|
22
|
+
* response-format fragment. `meta verify` then reported "1 template(s) clean", which reads
|
|
23
|
+
* as confirmation that the prompt is fine. So the fourth pillar produced two type files and
|
|
24
|
+
* a green gate, and the adopter had neither a send side nor a receive side.
|
|
25
|
+
*
|
|
26
|
+
* The wiring IS documented — but only in the prompts skill's per-language reference
|
|
27
|
+
* fragment, which SKILL.md points at in its final line, while the skill body walks the
|
|
28
|
+
* entire declaration without once saying a generator is required. Found by declaring a
|
|
29
|
+
* `template.prompt` in a from-scratch app exactly as that skill teaches.
|
|
30
|
+
*
|
|
31
|
+
* This follows the `layout.dataGrid` precedent (#287, data-grid-gate.ts): tell the adopter
|
|
32
|
+
* at `meta gen` time rather than adding a doc line that gets missed the same way. It is a
|
|
33
|
+
* WARNING — the exit code is untouched — and it is **self-extinguishing**: wire any one
|
|
34
|
+
* prompt generator and it goes quiet forever, so a project that has made this choice
|
|
35
|
+
* deliberately is never nagged.
|
|
36
|
+
*
|
|
37
|
+
* It must live in the runner rather than in a generator, because the whole condition is
|
|
38
|
+
* that the generator which would speak up is not running.
|
|
39
|
+
*/
|
|
40
|
+
export function warnMissingPromptGenerators(
|
|
41
|
+
root: MetaData,
|
|
42
|
+
generators: readonly Generator[],
|
|
43
|
+
warn: (msg: string) => void,
|
|
44
|
+
): void {
|
|
45
|
+
if (generators.some((g) => PROMPT_GENERATOR_NAMES.has(g.name))) return;
|
|
46
|
+
|
|
47
|
+
// ADR-0039: resolving children — a template may arrive through an overlay or extends.
|
|
48
|
+
const templates = root
|
|
49
|
+
.children()
|
|
50
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT);
|
|
51
|
+
if (templates.length === 0) return;
|
|
52
|
+
|
|
53
|
+
const names = templates.map((t) => t.name).join(", ");
|
|
54
|
+
// A @responseRef is what asks for the inbound tier (ADR-0052), so a responding prompt
|
|
55
|
+
// is missing strictly more than an outbound-only one. Name that, rather than making the
|
|
56
|
+
// adopter infer which half is absent.
|
|
57
|
+
const responding = templates.filter((t) => typeof t.attr(TEMPLATE_ATTR_RESPONSE_REF) === "string");
|
|
58
|
+
const receiveHalf =
|
|
59
|
+
responding.length > 0
|
|
60
|
+
? ` ${responding.length === templates.length ? "All" : `${responding.length} of them`} ` +
|
|
61
|
+
`declare a @responseRef, so the parser and response-format fragment are missing too — ` +
|
|
62
|
+
`add outputParser().`
|
|
63
|
+
: "";
|
|
64
|
+
|
|
65
|
+
warn(
|
|
66
|
+
`${templates.length} declared template.prompt (${names}) generated no prompt code: ` +
|
|
67
|
+
`no prompt generator is wired, so there is no render function to send one and nothing ` +
|
|
68
|
+
`read a reply. Add promptRender() to \`generators\` in metaobjects.config.ts ` +
|
|
69
|
+
`(import it from "@metaobjectsdev/codegen-ts/generators").${receiveHalf} ` +
|
|
70
|
+
`The payload value objects were emitted regardless — those are object.value nodes, ` +
|
|
71
|
+
`which is why this looked like it had worked.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
package/src/reference/barrel.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
7
|
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
8
|
// `Bun is not defined`. Use `node:` builtins instead.
|
|
9
|
+
// targets: nothing framework-specific — it re-exports whatever the other generators
|
|
10
|
+
// emitted. `extStyle` decides whether the re-export specifiers carry a
|
|
11
|
+
// `.js` extension.
|
|
9
12
|
// use-when: you want a single `index.ts` re-exporting every generated entity module.
|
|
10
13
|
// emits: <target>/index.ts with one `export * from "./<Entity>"` per entity, alphabetical.
|
|
11
14
|
// customize: the export form (star vs named), ordering, grouping by package, what to include/exclude.
|
package/src/reference/entity.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
7
|
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
8
|
// `Bun is not defined`. Use `node:` builtins instead.
|
|
9
|
+
// targets: Drizzle ORM + Zod. The emitted module is a Drizzle table plus Zod
|
|
10
|
+
// insert/update schemas; the column mapping follows `dialect`. On the
|
|
11
|
+
// default (vanilla) path, replace the `renderDrizzleSchema` /
|
|
12
|
+
// `renderZodValidators` calls to target a different ORM or validator — the
|
|
13
|
+
// metadata walk that feeds them is ORM-neutral. (The `isWriteThrough`
|
|
14
|
+
// branch calls `renderEntityFile` instead — a narrow #214 read-view case,
|
|
15
|
+
// not the default.)
|
|
9
16
|
// use-when: ALWAYS — this is the entity-module generator. It owns the shape of each
|
|
10
17
|
// generated <Entity>.ts (the Drizzle table, Zod schemas, inferred types,
|
|
11
18
|
// constants, filter allowlists). Start here and adapt the assembly.
|
package/src/reference/queries.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
7
|
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
8
|
// `Bun is not defined`. Use `node:` builtins instead.
|
|
9
|
+
// targets: Drizzle. Emitted helpers take `db` as a PARAMETER rather than importing
|
|
10
|
+
// a module singleton, so they compose with any caller that already holds a
|
|
11
|
+
// connection — including a server-rendered component. Swap the
|
|
12
|
+
// `render<Verb>Fn` primitives (findById/list/create/update/deleteById) and
|
|
13
|
+
// the inline `Db` type block above them to emit for another query builder.
|
|
9
14
|
// use-when: you want generated typed CRUD finders (find<E>ById, list<E>s, create/update/delete)
|
|
10
15
|
// over Drizzle. Drop it if you hand-write your data access.
|
|
11
16
|
// emits: <target>/<Entity>.queries.ts per source-backed object (any source.rdb kind,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// REFERENCE TEMPLATE — copy this into your repo (e.g. codegen/generators/routes-hono.ts) and own it.
|
|
2
|
+
// Then import it LOCALLY in metaobjects.config.ts:
|
|
3
|
+
// import { routesFileHono } from "./codegen/generators/routes-hono.js";
|
|
4
|
+
//
|
|
5
|
+
// RUNTIME: this file executes under whatever runs `meta gen`, and the published CLI's
|
|
6
|
+
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
|
+
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
|
+
// `Bun is not defined`.
|
|
9
|
+
// targets: Hono. The emitted file imports `mountCrudRoutes` from
|
|
10
|
+
// `@metaobjectsdev/runtime-ts/hono` and takes its persistence client as
|
|
11
|
+
// INJECTED DEPS (`register<Entity>Routes(app, { db })`) rather than a
|
|
12
|
+
// module-singleton import — which is what makes it portable to any host
|
|
13
|
+
// that can hand Hono a request. If your framework is not Hono, THIS is the
|
|
14
|
+
// file to retarget: swap the mount helper and the exported signature; the
|
|
15
|
+
// metadata walk above it is framework-neutral and stays as-is.
|
|
16
|
+
// use-when: you want generated Hono CRUD routes per entity.
|
|
17
|
+
// emits: <target>/<Entity>.routes.hono.ts — full CRUD for write-through entities,
|
|
18
|
+
// read-only (GET list + GET :id) for projections. Skipped for any sourceless
|
|
19
|
+
// object and for TPH subtypes.
|
|
20
|
+
// customize: this generator is YOURS — edit it freely. For the emitted route
|
|
21
|
+
// composition, call `renderRoutesFileHono` (exported from the engine) and
|
|
22
|
+
// wrap its result, or replace the call entirely.
|
|
23
|
+
// composes-with: entity.ts (imports the table/schemas/allowlists), queries.ts.
|
|
24
|
+
|
|
25
|
+
import { type MetaObject } from "@metaobjectsdev/metadata";
|
|
26
|
+
import {
|
|
27
|
+
perEntity,
|
|
28
|
+
type Generator,
|
|
29
|
+
type GeneratorFactory,
|
|
30
|
+
renderRoutesFileHono,
|
|
31
|
+
isTphSubtype,
|
|
32
|
+
hasAnyRdbSource,
|
|
33
|
+
formatTs,
|
|
34
|
+
entityOutputPath,
|
|
35
|
+
CODEGEN_ATTR_EMIT_ROUTES,
|
|
36
|
+
} from "@metaobjectsdev/codegen-ts";
|
|
37
|
+
|
|
38
|
+
export interface RoutesFileHonoOpts {
|
|
39
|
+
filter?: (entity: MetaObject) => boolean;
|
|
40
|
+
target?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const routesFileHono = function routesFileHono(opts?: RoutesFileHonoOpts): Generator {
|
|
44
|
+
const userFilter = opts?.filter ?? (() => true);
|
|
45
|
+
// Eligibility, minus the TPH question — stated ONCE so the emit set and the warn set
|
|
46
|
+
// below cannot drift apart. They differ only by `!isTphSubtype` vs `isTphSubtype`, and
|
|
47
|
+
// written out twice a later edit to one silently makes an entity either stop emitting
|
|
48
|
+
// without being named as held back, or get warned about while still emitting.
|
|
49
|
+
// (Same shape as tanstack's grid generator, which factors it the same way.)
|
|
50
|
+
const passesOtherGates = (e: MetaObject): boolean =>
|
|
51
|
+
// ADR-0039: resolving — a concrete entity may inherit @emitRoutes via extends.
|
|
52
|
+
e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false
|
|
53
|
+
&& hasAnyRdbSource(e)
|
|
54
|
+
&& userFilter(e);
|
|
55
|
+
const generator: Generator = {
|
|
56
|
+
name: "routes-file-hono",
|
|
57
|
+
// Marks this as the Hono routes generator so the runner can aggregate
|
|
58
|
+
// `ctx.config.includeHonoRoutes` and api-docs auto-documents the Hono surface.
|
|
59
|
+
emitsHonoRoutes: true,
|
|
60
|
+
// ADR-0039: resolving — a concrete entity may inherit @emitRoutes via extends.
|
|
61
|
+
//
|
|
62
|
+
// TPH subtypes are EXCLUDED, matching the Fastify generator. A TPH subtype shares
|
|
63
|
+
// its base's table, so mounting vanilla CRUD for it produced routes with no
|
|
64
|
+
// discriminator scoping at all: the list returned EVERY subtype's rows, and
|
|
65
|
+
// get/patch/delete by id happily operated on rows belonging to a different
|
|
66
|
+
// subtype. Silently wrong data, which is worse than no route. Fastify dispatches
|
|
67
|
+
// these to a discriminator-aware renderer; the Hono runtime has no discriminator
|
|
68
|
+
// support yet, so this fails CLOSED and the run says so (see the warning below)
|
|
69
|
+
// rather than shipping an artifact that returns the wrong rows.
|
|
70
|
+
filter: (e: MetaObject) => passesOtherGates(e) && !isTphSubtype(e),
|
|
71
|
+
generate: async (ctx) => {
|
|
72
|
+
// One note per run naming every TPH subtype held back, so the gap is visible at
|
|
73
|
+
// `meta gen` time rather than discovered as missing endpoints in production.
|
|
74
|
+
const skipped = ctx.entities.filter((e) => passesOtherGates(e) && isTphSubtype(e));
|
|
75
|
+
if (skipped.length > 0) {
|
|
76
|
+
ctx.warn(
|
|
77
|
+
`no Hono routes emitted for the TPH subtype(s) ${skipped.map((e) => e.name).join(", ")} — ` +
|
|
78
|
+
"the Hono adapter has no discriminator scoping yet, so per-subtype CRUD would " +
|
|
79
|
+
"return and mutate OTHER subtypes' rows. Use routesFile() (Fastify), which " +
|
|
80
|
+
"dispatches TPH correctly, or hand-write the scoped routes.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return emit(ctx);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const emit = perEntity(async (entity, ctx) => {
|
|
87
|
+
if (!ctx.renderContext) {
|
|
88
|
+
throw new Error("routes-file-hono: renderContext is required (provided by runGen)");
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
path: entityOutputPath(
|
|
92
|
+
ctx.config.outputLayout ?? "flat",
|
|
93
|
+
entity.package,
|
|
94
|
+
`${entity.name}.routes.hono.ts`,
|
|
95
|
+
),
|
|
96
|
+
content: await formatTs(renderRoutesFileHono(entity, ctx.renderContext)),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (opts?.target) {
|
|
100
|
+
generator.target = opts.target;
|
|
101
|
+
}
|
|
102
|
+
return generator;
|
|
103
|
+
} as GeneratorFactory<RoutesFileHonoOpts>;
|
package/src/reference/routes.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
7
|
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
8
|
// `Bun is not defined`. Use `node:` builtins instead.
|
|
9
|
+
// targets: Fastify. The emitted file imports `mountCrudRoutes` from
|
|
10
|
+
// `@metaobjectsdev/runtime-ts/drizzle-fastify` and binds a
|
|
11
|
+
// module-singleton `db`. THIS is the file to retarget for another HTTP
|
|
12
|
+
// framework; see also the routes-hono template, whose deps-injected shape
|
|
13
|
+
// ports more easily to hosts that hand you a request.
|
|
9
14
|
// use-when: you want generated Fastify REST routes per entity. Drop it and hand-write routes
|
|
10
15
|
// if you need bespoke endpoints — or keep it and add handlers via <Entity>.extra.ts.
|
|
11
16
|
// emits: <target>/<Entity>.routes.ts — full CRUD for write-through entities, read-only
|
|
@@ -13,37 +13,77 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
|
|
15
15
|
/** Basenames (no extension) of the copyable reference generators shipped in `src/reference/`. */
|
|
16
|
-
export const REFERENCE_GENERATOR_NAMES = ["entity", "queries", "routes", "barrel"] as const;
|
|
16
|
+
export const REFERENCE_GENERATOR_NAMES = ["entity", "queries", "routes", "routes-hono", "barrel"] as const;
|
|
17
17
|
export type ReferenceGeneratorName = (typeof REFERENCE_GENERATOR_NAMES)[number];
|
|
18
18
|
|
|
19
|
-
/** A directory is
|
|
20
|
-
function isReferenceRoot(dir: string): boolean {
|
|
21
|
-
return existsSync(join(dir,
|
|
19
|
+
/** A directory is a reference root iff it holds the first template the reader was told to expect. */
|
|
20
|
+
function isReferenceRoot(dir: string, sentinel: string): boolean {
|
|
21
|
+
return existsSync(join(dir, `${sentinel}.ts`));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* FR-040 §4.1 — build a reference-template reader for ONE package. `moduleUrl` is the
|
|
26
|
+
* calling module's `import.meta.url`, so each package resolves its OWN `src/reference/`.
|
|
27
|
+
* `names[0]` is the sentinel that identifies the directory.
|
|
28
|
+
*
|
|
29
|
+
* Works in dev (module runs from `src/`, templates at `./reference/`) and in a published
|
|
30
|
+
* install (module runs from `dist/`, templates at `../src/reference/`, since `src/` ships
|
|
31
|
+
* alongside `dist/`). Walks up checking both layouts at each level.
|
|
29
32
|
*/
|
|
30
|
-
export function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
export function makeReferenceReader(moduleUrl: string, names: readonly [string, ...string[]]) {
|
|
34
|
+
// A non-empty tuple type, so `names[0]` is `string` outright — this used to need a
|
|
35
|
+
// runtime empty-check plus a re-bound const, because `noUncheckedIndexedAccess`
|
|
36
|
+
// narrowing does not survive into the closures below. Every call site already passes
|
|
37
|
+
// an `as const` tuple or an array literal, so the type costs nothing and deletes both.
|
|
38
|
+
const sentinel: string = names[0];
|
|
39
|
+
|
|
40
|
+
function resolveReferenceRoot(): string {
|
|
41
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
42
|
+
for (let i = 0; i < 8; i++) {
|
|
43
|
+
for (const candidate of [join(dir, "reference"), join(dir, "src", "reference")]) {
|
|
44
|
+
if (isReferenceRoot(candidate, sentinel)) return candidate;
|
|
45
|
+
}
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir) break;
|
|
48
|
+
dir = parent;
|
|
35
49
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
throw new Error(
|
|
51
|
+
"reference templates not found — looked for `reference/` and `src/reference/` " +
|
|
52
|
+
`walking up from ${dir}.`,
|
|
53
|
+
);
|
|
39
54
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
resolveReferenceRoot,
|
|
58
|
+
/**
|
|
59
|
+
* Read one template by name. The name is checked against `names` FIRST: this is a
|
|
60
|
+
* public export, so `name` is an untrusted `string` at runtime (the per-package
|
|
61
|
+
* wrappers narrow to a literal union, but types are erased). Without the check it
|
|
62
|
+
* interpolates straight into a path, so any caller threading a CLI argument through
|
|
63
|
+
* reads whatever file the string points at. The allowlist is already a parameter —
|
|
64
|
+
* enforcing it here makes the guarantee structural instead of something every call
|
|
65
|
+
* site has to remember, and it is the reason `meta eject` needs no check of its own.
|
|
66
|
+
*/
|
|
67
|
+
readReferenceTemplate: (name: string): string => {
|
|
68
|
+
if (!names.includes(name)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`unknown reference template "${name}". Available: ${names.join(", ")}.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return readFileSync(join(resolveReferenceRoot(), `${name}.ts`), "utf8");
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// This package's own reader — the back-compatible named exports `meta init` uses.
|
|
79
|
+
const ownReader = makeReferenceReader(import.meta.url, REFERENCE_GENERATOR_NAMES);
|
|
80
|
+
|
|
81
|
+
/** Resolve the `src/reference/` directory holding this package's reference generators. */
|
|
82
|
+
export function resolveReferenceRoot(): string {
|
|
83
|
+
return ownReader.resolveReferenceRoot();
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
/** Read the raw source of one reference generator (e.g. `"entity"` → the text of `entity.ts`). */
|
|
47
87
|
export function readReferenceTemplate(name: ReferenceGeneratorName): string {
|
|
48
|
-
return
|
|
88
|
+
return ownReader.readReferenceTemplate(name);
|
|
49
89
|
}
|
package/src/render-context.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface RenderContext {
|
|
|
52
52
|
* doc comment on `MetaobjectsGenConfig.timestampMode` in metaobjects-config.ts.
|
|
53
53
|
*/
|
|
54
54
|
timestampMode: "date" | "string";
|
|
55
|
+
/**
|
|
56
|
+
* Prepend `"use client";` to generated CLIENT artifacts (forms, hooks, grid
|
|
57
|
+
* columns, grid hooks). Defaults to false. A bundler-topology fact the adopter
|
|
58
|
+
* declares — see `MetaobjectsGenConfig.clientDirective` for why it is config
|
|
59
|
+
* rather than metadata, and why the default is off. FR-040 §6.4.
|
|
60
|
+
*/
|
|
61
|
+
clientDirective: boolean;
|
|
55
62
|
/** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
|
|
56
63
|
apiPrefix: string;
|
|
57
64
|
/** Whether abstract entities emit their shape artifact (type-only interface / value-object file). Defaults to true. Instance/write artifacts are never emitted for abstract entities regardless. */
|
|
@@ -101,11 +108,12 @@ export interface RenderContext {
|
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
/** Optional shape — `extStyle`, `omImport`, `columnNamingStrategy`, `apiPrefix`, `outputLayout`, and `packageOf` default if omitted. `packageOf` defaults to an empty Map (correct for flat layout; `runGen` always provides the real map). `collectionName` is built from `pluralizeCollections` + `collectionNameOverrides` (both default to always-pluralize). */
|
|
104
|
-
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "timestampMode" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "packageOf" | "valueObjectNames" | "valueObjectEmittedName" | "resolveValueObjectName" | "selfTarget" | "entityModuleTarget" | "collectionName"> & {
|
|
111
|
+
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "timestampMode" | "clientDirective" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "packageOf" | "valueObjectNames" | "valueObjectEmittedName" | "resolveValueObjectName" | "selfTarget" | "entityModuleTarget" | "collectionName"> & {
|
|
105
112
|
extStyle?: ExtStyle;
|
|
106
113
|
omImport?: string;
|
|
107
114
|
columnNamingStrategy?: ColumnNamingStrategy;
|
|
108
115
|
timestampMode?: "date" | "string";
|
|
116
|
+
clientDirective?: boolean;
|
|
109
117
|
apiPrefix?: string;
|
|
110
118
|
emitAbstractShapes?: boolean;
|
|
111
119
|
outputLayout?: OutputLayout;
|
|
@@ -184,6 +192,7 @@ export function makeRenderContext(opts: RenderContextInput): RenderContext {
|
|
|
184
192
|
// a unit test or a generator invoked outside `runGen`, must get the same
|
|
185
193
|
// safe-no-op guarantee). See MetaobjectsGenConfig.timestampMode's doc comment.
|
|
186
194
|
timestampMode: opts.dialect === "sqlite" ? "string" : (opts.timestampMode ?? "string"),
|
|
195
|
+
clientDirective: opts.clientDirective ?? false,
|
|
187
196
|
apiPrefix: opts.apiPrefix ?? "",
|
|
188
197
|
emitAbstractShapes: opts.emitAbstractShapes ?? true,
|
|
189
198
|
outputLayout,
|
package/src/runner.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join, relative, resolve, isAbsolute, dirname } from "node:path";
|
|
2
|
+
import { warnMissingPromptGenerators } from "./prompt-generator-gate.js";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -366,6 +367,11 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
366
367
|
// CRUD surface it actually emits (rather than silently omitting it).
|
|
367
368
|
const includeHonoRoutes = config.generators.some((g) => g.emitsHonoRoutes === true);
|
|
368
369
|
|
|
370
|
+
// A declared template.prompt with no prompt generator wired emits nothing and, before
|
|
371
|
+
// this, said nothing — while `meta verify` reported the template "clean". See
|
|
372
|
+
// prompt-generator-gate.ts. Self-extinguishing; warning only.
|
|
373
|
+
warnMissingPromptGenerators(root, config.generators, (m) => warnings.push(m));
|
|
374
|
+
|
|
369
375
|
// 4. Run each generator with a per-target render context; collect with full path.
|
|
370
376
|
const emitted: { fullPath: string; content: string; generatedBy: string }[] = [];
|
|
371
377
|
// FR-038 §8 — generators that opted into orphan reconciliation, paired with the
|
|
@@ -411,6 +417,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
411
417
|
pluralizeCollections: config.pluralizeCollections,
|
|
412
418
|
collectionNameOverrides: config.collectionNameOverrides,
|
|
413
419
|
timestampMode: config.timestampMode,
|
|
420
|
+
clientDirective: config.clientDirective,
|
|
414
421
|
apiPrefix: config.apiPrefix,
|
|
415
422
|
emitAbstractShapes: config.emitAbstractShapes,
|
|
416
423
|
outputLayout: selfTarget.outputLayout,
|