@metaobjectsdev/codegen-ts 0.6.0 → 0.7.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/README.md +131 -4
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +2 -1
- package/dist/column-mapper.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/output-parser-file.d.ts +9 -0
- package/dist/generators/output-parser-file.d.ts.map +1 -0
- package/dist/generators/output-parser-file.js +37 -0
- package/dist/generators/output-parser-file.js.map +1 -0
- package/dist/generators/prompt-render-file.d.ts +9 -0
- package/dist/generators/prompt-render-file.d.ts.map +1 -0
- package/dist/generators/prompt-render-file.js +52 -0
- package/dist/generators/prompt-render-file.js.map +1 -0
- package/dist/metaobjects-config.d.ts +3 -1
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +2 -1
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/naming.d.ts +3 -12
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +14 -44
- package/dist/naming.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts +1 -1
- package/dist/projection/extract-view-spec.js +1 -1
- package/dist/templates/output-parser.d.ts +8 -0
- package/dist/templates/output-parser.d.ts.map +1 -0
- package/dist/templates/output-parser.js +129 -0
- package/dist/templates/output-parser.js.map +1 -0
- package/dist/templates/projection-decl.d.ts +1 -1
- package/dist/templates/projection-decl.js +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +15 -4
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +11 -30
- package/dist/templates/queries.js.map +1 -1
- package/package.json +4 -4
- package/src/column-mapper.ts +2 -1
- package/src/generators/index.ts +2 -0
- package/src/generators/output-parser-file.ts +50 -0
- package/src/generators/prompt-render-file.ts +69 -0
- package/src/metaobjects-config.ts +4 -2
- package/src/naming.ts +22 -46
- package/src/projection/extract-view-spec.ts +1 -1
- package/src/templates/output-parser.ts +143 -0
- package/src/templates/projection-decl.ts +1 -1
- package/src/templates/queries-file.ts +18 -4
- package/src/templates/queries.ts +11 -33
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/templates/output-parser.ts
|
|
2
|
+
//
|
|
3
|
+
// Per-template renderer for template.output codegen. Walks the @payloadRef's
|
|
4
|
+
// value-object into a Zod schema and emits a dual-API parser (parse + safeParse)
|
|
5
|
+
// alongside the schema. The emitted file is self-contained: it derives a
|
|
6
|
+
// local data type via `z.infer<typeof Schema>` and exports it as
|
|
7
|
+
// `<TemplateName>Data`. Consumers wiring `promptRender()` get a structurally
|
|
8
|
+
// identical payload-VO interface in `prompts.ts`; either type can be used
|
|
9
|
+
// interchangeably with parse results.
|
|
10
|
+
import { TYPE_OBJECT, TYPE_FIELD, TYPE_TEMPLATE, TEMPLATE_SUBTYPE_OUTPUT, FIELD_SUBTYPE_OBJECT, FIELD_ATTR_OBJECT_REF, TEMPLATE_ATTR_PAYLOAD_REF, } from "@metaobjectsdev/metadata";
|
|
11
|
+
const SCALAR_ZOD = {
|
|
12
|
+
string: "z.string()",
|
|
13
|
+
class: "z.string()",
|
|
14
|
+
int: "z.number().int()",
|
|
15
|
+
short: "z.number().int()",
|
|
16
|
+
byte: "z.number().int()",
|
|
17
|
+
long: "z.number().int()",
|
|
18
|
+
double: "z.number()",
|
|
19
|
+
float: "z.number()",
|
|
20
|
+
boolean: "z.boolean()",
|
|
21
|
+
};
|
|
22
|
+
function findObject(root, name) {
|
|
23
|
+
return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
|
|
24
|
+
}
|
|
25
|
+
function findTemplate(root, name) {
|
|
26
|
+
return root.ownChildren().find((c) => c.type === TYPE_TEMPLATE && c.name === name);
|
|
27
|
+
}
|
|
28
|
+
/** Render the Zod expression for a single field; recurses on @objectRef. */
|
|
29
|
+
function fieldZod(field, root, seen, depth) {
|
|
30
|
+
// isArray is a native (reserved) property on MetaData, not an attr.
|
|
31
|
+
const isArray = field.isArray === true;
|
|
32
|
+
let base;
|
|
33
|
+
if (field.subType === FIELD_SUBTYPE_OBJECT) {
|
|
34
|
+
const refName = field.ownAttr(FIELD_ATTR_OBJECT_REF);
|
|
35
|
+
if (typeof refName !== "string") {
|
|
36
|
+
base = "z.unknown()";
|
|
37
|
+
}
|
|
38
|
+
else if (seen.has(refName)) {
|
|
39
|
+
// Cycle guard — emit unknown for self-references (rare; lazy schemas not in scope for v1).
|
|
40
|
+
base = "z.unknown()";
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const inner = findObject(root, refName);
|
|
44
|
+
base = inner ? renderObjectSchema(inner, root, new Set(seen).add(refName), depth + 1) : "z.unknown()";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
base = SCALAR_ZOD[field.subType] ?? "z.unknown()";
|
|
49
|
+
}
|
|
50
|
+
return isArray ? `z.array(${base})` : base;
|
|
51
|
+
}
|
|
52
|
+
/** Render a `z.object({ ... })` for an object.value node.
|
|
53
|
+
* At depth 0 the schema starts at column 0 (consumer's `const Foo = z.object({`),
|
|
54
|
+
* so fields sit at 2 spaces and the closing `})` at 0 spaces — matching the
|
|
55
|
+
* surrounding `const NameSchema = ...` statement's indent. Nested schemas
|
|
56
|
+
* step in two spaces per depth level. */
|
|
57
|
+
function renderObjectSchema(vo, root, seen, depth) {
|
|
58
|
+
const fields = vo.children().filter((c) => c.type === TYPE_FIELD);
|
|
59
|
+
const fieldIndent = " ".repeat(depth + 1);
|
|
60
|
+
const closeIndent = " ".repeat(depth);
|
|
61
|
+
const lines = fields.map((f) => `${fieldIndent}${f.name}: ${fieldZod(f, root, seen, depth)},`);
|
|
62
|
+
return `z.object({\n${lines.join("\n")}\n${closeIndent}})`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the full output-parser file for one `template.output` node.
|
|
66
|
+
* Throws if the template isn't found, isn't a template.output, or its
|
|
67
|
+
* @payloadRef doesn't resolve to an object.value.
|
|
68
|
+
*/
|
|
69
|
+
export function renderOutputParser(root, templateName) {
|
|
70
|
+
const tmpl = findTemplate(root, templateName);
|
|
71
|
+
if (!tmpl) {
|
|
72
|
+
throw new Error(`template "${templateName}" not found in metadata root`);
|
|
73
|
+
}
|
|
74
|
+
if (tmpl.subType !== TEMPLATE_SUBTYPE_OUTPUT) {
|
|
75
|
+
throw new Error(`template "${templateName}" is not a template.output (got subtype "${tmpl.subType}")`);
|
|
76
|
+
}
|
|
77
|
+
const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
78
|
+
if (typeof payloadRef !== "string") {
|
|
79
|
+
throw new Error(`template "${templateName}" missing @payloadRef`);
|
|
80
|
+
}
|
|
81
|
+
const vo = findObject(root, payloadRef);
|
|
82
|
+
if (!vo) {
|
|
83
|
+
throw new Error(`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`);
|
|
84
|
+
}
|
|
85
|
+
const schema = renderObjectSchema(vo, root, new Set([payloadRef]), 0);
|
|
86
|
+
const schemaName = `${templateName}Schema`;
|
|
87
|
+
const dataName = `${templateName}Data`;
|
|
88
|
+
const errorName = `${templateName}ValidationError`;
|
|
89
|
+
const parseName = `parse${templateName}`;
|
|
90
|
+
const safeParseName = `safeParse${templateName}`;
|
|
91
|
+
return `import { z } from "zod";
|
|
92
|
+
|
|
93
|
+
const ${schemaName} = ${schema};
|
|
94
|
+
|
|
95
|
+
export type ${dataName} = z.infer<typeof ${schemaName}>;
|
|
96
|
+
export type ${errorName} = z.ZodError;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse an LLM response into a typed ${dataName}.
|
|
100
|
+
* @throws ZodError on validation failure.
|
|
101
|
+
*/
|
|
102
|
+
export function ${parseName}(text: string): ${dataName} {
|
|
103
|
+
return ${schemaName}.parse(JSON.parse(text));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse an LLM response with explicit error handling (Result-style).
|
|
108
|
+
* Does not throw on validation failure.
|
|
109
|
+
*/
|
|
110
|
+
export function ${safeParseName}(
|
|
111
|
+
text: string,
|
|
112
|
+
): { success: true; data: ${dataName} } | { success: false; error: ${errorName} } {
|
|
113
|
+
let parsed: unknown;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(text);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: new z.ZodError([{ code: "custom", path: [], message: \`invalid JSON: \${(err as Error).message}\` }]),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const result = ${schemaName}.safeParse(parsed);
|
|
123
|
+
return result.success
|
|
124
|
+
? { success: true, data: result.data }
|
|
125
|
+
: { success: false, error: result.error };
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=output-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-parser.js","sourceRoot":"","sources":["../../src/templates/output-parser.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,yEAAyE;AACzE,iEAAiE;AACjE,6EAA6E;AAC7E,0EAA0E;AAC1E,sCAAsC;AAEtC,OAAO,EAEL,WAAW,EACX,UAAU,EACV,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAElC,MAAM,UAAU,GAA2B;IACzC,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,YAAY;IACnB,GAAG,EAAE,kBAAkB;IACvB,KAAK,EAAE,kBAAkB;IACzB,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,kBAAkB;IACxB,MAAM,EAAE,YAAY;IACpB,KAAK,EAAE,YAAY;IACnB,OAAO,EAAE,aAAa;CACvB,CAAC;AAEF,SAAS,UAAU,CAAC,IAAc,EAAE,IAAY;IAC9C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,YAAY,CAAC,IAAc,EAAE,IAAY;IAChD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACrF,CAAC;AAED,4EAA4E;AAC5E,SAAS,QAAQ,CAAC,KAAe,EAAE,IAAc,EAAE,IAAyB,EAAE,KAAa;IACzF,oEAAoE;IACpE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC;IACvC,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,CAAC,OAAO,KAAK,oBAAoB,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QACrD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,IAAI,GAAG,aAAa,CAAC;QACvB,CAAC;aAAM,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,2FAA2F;YAC3F,IAAI,GAAG,aAAa,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QACxG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC;IACpD,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED;;;;0CAI0C;AAC1C,SAAS,kBAAkB,CAAC,EAAY,EAAE,IAAc,EAAE,IAAyB,EAAE,KAAa;IAChG,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAClE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/F,OAAO,eAAe,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAc,EAAE,YAAoB;IACrE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,aAAa,YAAY,8BAA8B,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,uBAAuB,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,aAAa,YAAY,4CAA4C,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;IACzG,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC3D,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,aAAa,YAAY,uBAAuB,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,aAAa,YAAY,kBAAkB,UAAU,8BAA8B,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,GAAG,YAAY,QAAQ,CAAC;IAC3C,MAAM,QAAQ,GAAG,GAAG,YAAY,MAAM,CAAC;IACvC,MAAM,SAAS,GAAG,GAAG,YAAY,iBAAiB,CAAC;IACnD,MAAM,SAAS,GAAG,QAAQ,YAAY,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,YAAY,YAAY,EAAE,CAAC;IAEjD,OAAO;;QAED,UAAU,MAAM,MAAM;;cAEhB,QAAQ,qBAAqB,UAAU;cACvC,SAAS;;;wCAGiB,QAAQ;;;kBAG9B,SAAS,mBAAmB,QAAQ;WAC3C,UAAU;;;;;;;kBAOH,aAAa;;4BAEH,QAAQ,iCAAiC,SAAS;;;;;;;;;;mBAU3D,UAAU;;;;;CAK5B,CAAC;AACF,CAAC"}
|
|
@@ -14,7 +14,7 @@ export interface ProjectionDeclOpts {
|
|
|
14
14
|
*
|
|
15
15
|
* @param projection The projection entity (has a source[dbView] child).
|
|
16
16
|
* @param root The loader's root (all top-level objects as direct children,
|
|
17
|
-
* from `MetaDataLoader.load()`
|
|
17
|
+
* from `MetaDataLoader.load()` or `MetaDataLoader.fromDirectory()` as `result.root`).
|
|
18
18
|
* @param opts Column naming strategy + dialect.
|
|
19
19
|
*/
|
|
20
20
|
export declare function renderProjectionDecl(projection: MetaObject, root: MetaRoot, opts: ProjectionDeclOpts): string;
|
|
@@ -40,7 +40,7 @@ function pathFromProjectionName(name) {
|
|
|
40
40
|
*
|
|
41
41
|
* @param projection The projection entity (has a source[dbView] child).
|
|
42
42
|
* @param root The loader's root (all top-level objects as direct children,
|
|
43
|
-
* from `MetaDataLoader.load()`
|
|
43
|
+
* from `MetaDataLoader.load()` or `MetaDataLoader.fromDirectory()` as `result.root`).
|
|
44
44
|
* @param opts Column naming strategy + dialect.
|
|
45
45
|
*/
|
|
46
46
|
export function renderProjectionDecl(projection, root, opts) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries-file.d.ts","sourceRoot":"","sources":["../../src/templates/queries-file.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAY1D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"queries-file.d.ts","sourceRoot":"","sources":["../../src/templates/queries-file.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAY1D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAoD7E"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { code, joinCode } from "ts-poet";
|
|
4
4
|
import { MetaObject } from "@metaobjectsdev/metadata";
|
|
5
5
|
import {} from "../render-context.js";
|
|
6
|
-
import { entityModuleSpecifier
|
|
6
|
+
import { entityModuleSpecifier } from "../import-path.js";
|
|
7
7
|
import { renderFindByIdFn, renderListFn, renderCreateFn, renderUpdateFn, renderDeleteByIdFn, } from "./queries.js";
|
|
8
8
|
import { variableNameFromEntity } from "../naming.js";
|
|
9
9
|
import { GENERATED_HEADER } from "../constants.js";
|
|
@@ -12,12 +12,23 @@ export function renderQueriesFile(obj, ctx) {
|
|
|
12
12
|
// Import the entity's own file. Same target → relative "./Entity"; cross
|
|
13
13
|
// target → importBase-qualified package path.
|
|
14
14
|
const entityFileName = entityModuleSpecifier(ctx.selfTarget, ctx.entityModuleTarget, obj.package, entityName, ctx.extStyle);
|
|
15
|
-
const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, obj.package, ctx.dbImport);
|
|
16
15
|
const varName = variableNameFromEntity(entityName);
|
|
17
|
-
//
|
|
16
|
+
// The persistence-context `db` is parameter-passed into every generated CRUD
|
|
17
|
+
// helper (ADR-0008). Emit the dialect-correct Drizzle type alias so the
|
|
18
|
+
// signatures `findXxx(db: Db, ...)` typecheck without the consumer importing
|
|
19
|
+
// anything to construct one. Consumers pass any compatible Drizzle instance.
|
|
20
|
+
const dbTypeImport = ctx.dialect === "postgres"
|
|
21
|
+
? `import type { NodePgDatabase } from "drizzle-orm/node-postgres";`
|
|
22
|
+
: `import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";`;
|
|
23
|
+
const dbTypeAlias = ctx.dialect === "postgres"
|
|
24
|
+
? `type Db = NodePgDatabase<Record<string, never>>;`
|
|
25
|
+
: `type Db = BaseSQLiteDatabase<"async", Record<string, never>>;`;
|
|
26
|
+
// Literal imports (Db type + entity types) live in a code block so they sort
|
|
18
27
|
// alongside ts-poet's hoisted imp() imports at the top of the body.
|
|
19
28
|
const literalImports = code `
|
|
20
|
-
|
|
29
|
+
${dbTypeImport}
|
|
30
|
+
${dbTypeAlias}
|
|
31
|
+
|
|
21
32
|
import { ${varName}, type ${entityName}, ${entityName}InsertSchema } from ${JSON.stringify(entityFileName)};
|
|
22
33
|
`;
|
|
23
34
|
const sections = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries-file.js","sourceRoot":"","sources":["../../src/templates/queries-file.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,kFAAkF;AAElF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAa,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAsB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"queries-file.js","sourceRoot":"","sources":["../../src/templates/queries-file.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,kFAAkF;AAElF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAa,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAsB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,UAAU,iBAAiB,CAAC,GAAe,EAAE,GAAkB;IACnE,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC;IAC5B,yEAAyE;IACzE,8CAA8C;IAC9C,MAAM,cAAc,GAAG,qBAAqB,CAC1C,GAAG,CAAC,UAAU,EACd,GAAG,CAAC,kBAAkB,EACtB,GAAG,CAAC,OAAO,EACX,UAAU,EACV,GAAG,CAAC,QAAQ,CACb,CAAC;IACF,MAAM,OAAO,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IAEnD,6EAA6E;IAC7E,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,YAAY,GAChB,GAAG,CAAC,OAAO,KAAK,UAAU;QACxB,CAAC,CAAC,kEAAkE;QACpE,CAAC,CAAC,oEAAoE,CAAC;IAC3E,MAAM,WAAW,GACf,GAAG,CAAC,OAAO,KAAK,UAAU;QACxB,CAAC,CAAC,kDAAkD;QACpD,CAAC,CAAC,+DAA+D,CAAC;IAEtE,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,cAAc,GAAG,IAAI,CAAA;EAC3B,YAAY;EACZ,WAAW;;WAEF,OAAO,UAAU,UAAU,KAAK,UAAU,uBAAuB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;CACzG,CAAC;IAEA,MAAM,QAAQ,GAAW;QACvB,cAAc;QACd,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC;QAC1B,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC;QACtB,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;QACxB,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;QACxB,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC;KAC7B,CAAC;IAEF,4EAA4E;IAC5E,kCAAkC;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IACzD,MAAM,MAAM,GACV,MAAM,gBAAgB,mBAAmB;QACzC,uBAAuB,UAAU,KAAK,GAAG,CAAC,GAAG,EAAE,KAAK;QACpD,oBAAoB,UAAU,mEAAmE,CAAC;IACpG,OAAO,MAAM,GAAG,IAAI,CAAC;AACvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/templates/queries.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/templates/queries.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAqB1D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAc7E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAe1E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAc5E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAgB3E;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI,CAe/E"}
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
// Each returns a ts-poet Code block; composed into a file by queries-file.ts.
|
|
3
3
|
import { code, imp } from "ts-poet";
|
|
4
4
|
import { IDENTITY_ATTR_FIELDS } from "@metaobjectsdev/metadata";
|
|
5
|
-
import { variableNameFromEntity,
|
|
6
|
-
/** Derive a stable prepared statement name. Deterministic from entity + field names. */
|
|
7
|
-
function prepareName(prefix, entitySnakeName, fieldDbName) {
|
|
8
|
-
return `${prefix}_${entitySnakeName}_by_${fieldDbName}`;
|
|
9
|
-
}
|
|
5
|
+
import { variableNameFromEntity, pluralize } from "../naming.js";
|
|
10
6
|
/** Get the PK field name and its TS type for a given entity. */
|
|
11
7
|
function getPkInfo(entity, ctx) {
|
|
12
8
|
// Use primaryIdentity() to find the primary identity (may be inherited from extends:/super:).
|
|
@@ -27,28 +23,12 @@ export function renderFindByIdFn(entity, ctx) {
|
|
|
27
23
|
const varName = variableNameFromEntity(entity.name);
|
|
28
24
|
const entityName = entity.name;
|
|
29
25
|
const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
30
|
-
const entitySnakeName = toSnakeCase(entityName);
|
|
31
26
|
const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
|
|
32
|
-
const pkSnakeName = toSnakeCase(pkField);
|
|
33
|
-
const prepName = prepareName("find", entitySnakeName, pkSnakeName);
|
|
34
27
|
const fnName = `find${entityName}ById`;
|
|
35
|
-
const prepVarName = `${fnName}Prepared`;
|
|
36
28
|
const eqSym = imp("eq@drizzle-orm");
|
|
37
|
-
const sqlSym = imp("sql@drizzle-orm");
|
|
38
|
-
const baseVarName = `${fnName}Base`;
|
|
39
|
-
// Drizzle's `.prepare()` signature differs by dialect:
|
|
40
|
-
// - Postgres: prepare(name) — the name is used by the pg driver to cache the plan
|
|
41
|
-
// - SQLite: prepare() — no name; the name arg was removed in drizzle-orm 0.41+
|
|
42
|
-
const prepArg = ctx.dialect === "postgres" ? `"${prepName}"` : "";
|
|
43
29
|
return code `
|
|
44
|
-
|
|
45
|
-
.select()
|
|
46
|
-
.from(${varName})
|
|
47
|
-
.where(${eqSym}(${varName}.${pkField}, ${sqlSym}.placeholder(${JSON.stringify(pkField)})));
|
|
48
|
-
const ${prepVarName} = ${baseVarName}.prepare(${prepArg});
|
|
49
|
-
|
|
50
|
-
export async function ${fnName}(${pkField}: ${pkType}): Promise<${entityName} | null> {
|
|
51
|
-
const [${singularVar}] = await ${prepVarName}.execute({ ${pkField} });
|
|
30
|
+
export async function ${fnName}(db: Db, ${pkField}: ${pkType}): Promise<${entityName} | null> {
|
|
31
|
+
const [${singularVar}] = await db.select().from(${varName}).where(${eqSym}(${varName}.${pkField}, ${pkField})).limit(1);
|
|
52
32
|
return ${singularVar} ?? null;
|
|
53
33
|
}
|
|
54
34
|
`;
|
|
@@ -60,7 +40,7 @@ export function renderListFn(entity, _ctx) {
|
|
|
60
40
|
// (e.g., "Category" -> "Categories", not "Categorys").
|
|
61
41
|
const fnName = `list${pluralize(entityName)}`;
|
|
62
42
|
return code `
|
|
63
|
-
export async function ${fnName}(opts?: { limit?: number; offset?: number }): Promise<${entityName}[]> {
|
|
43
|
+
export async function ${fnName}(db: Db, opts?: { limit?: number; offset?: number }): Promise<${entityName}[]> {
|
|
64
44
|
let q = db.select().from(${varName}).$dynamic();
|
|
65
45
|
if (opts?.limit !== undefined) q = q.limit(opts.limit);
|
|
66
46
|
if (opts?.offset !== undefined) q = q.offset(opts.offset);
|
|
@@ -75,7 +55,7 @@ export function renderCreateFn(entity, _ctx) {
|
|
|
75
55
|
const fnName = `create${entityName}`;
|
|
76
56
|
const schemaName = `${entityName}InsertSchema`;
|
|
77
57
|
return code `
|
|
78
|
-
export async function ${fnName}(data: unknown): Promise<${entityName}> {
|
|
58
|
+
export async function ${fnName}(db: Db, data: unknown): Promise<${entityName}> {
|
|
79
59
|
const validated = ${schemaName}.parse(data);
|
|
80
60
|
const [${singularVar}] = await db.insert(${varName}).values(validated).returning();
|
|
81
61
|
return ${singularVar}!;
|
|
@@ -91,7 +71,7 @@ export function renderUpdateFn(entity, ctx) {
|
|
|
91
71
|
const schemaName = `${entityName}InsertSchema`;
|
|
92
72
|
const eqSym = imp("eq@drizzle-orm");
|
|
93
73
|
return code `
|
|
94
|
-
export async function ${fnName}(${pkField}: ${pkType}, data: unknown): Promise<${entityName} | null> {
|
|
74
|
+
export async function ${fnName}(db: Db, ${pkField}: ${pkType}, data: unknown): Promise<${entityName} | null> {
|
|
95
75
|
const validated = ${schemaName}.partial().parse(data);
|
|
96
76
|
const [${singularVar}] = await db.update(${varName}).set(validated).where(${eqSym}(${varName}.${pkField}, ${pkField})).returning();
|
|
97
77
|
return ${singularVar} ?? null;
|
|
@@ -105,10 +85,11 @@ export function renderDeleteByIdFn(entity, ctx) {
|
|
|
105
85
|
const fnName = `delete${entityName}ById`;
|
|
106
86
|
const eqSym = imp("eq@drizzle-orm");
|
|
107
87
|
return code `
|
|
108
|
-
export async function ${fnName}(${pkField}: ${pkType}): Promise<boolean> {
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
88
|
+
export async function ${fnName}(db: Db, ${pkField}: ${pkType}): Promise<boolean> {
|
|
89
|
+
// Use .returning() unconditionally — supported on SQLite ≥3.35 (covers D1, libsql/Turso)
|
|
90
|
+
// and Postgres. Result is an array of deleted rows; presence implies success.
|
|
91
|
+
const deleted = await db.delete(${varName}).where(${eqSym}(${varName}.${pkField}, ${pkField})).returning();
|
|
92
|
+
return deleted.length > 0;
|
|
112
93
|
}
|
|
113
94
|
`;
|
|
114
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/templates/queries.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,8EAA8E;AAE9E,OAAO,EAAE,IAAI,EAAE,GAAG,EAAa,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/templates/queries.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.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@metaobjectsdev/metadata": "0.
|
|
41
|
+
"@metaobjectsdev/metadata": "0.7.0-rc.1",
|
|
42
42
|
"@biomejs/js-api": "^0.7.0",
|
|
43
43
|
"@biomejs/wasm-nodejs": "^1.9.4",
|
|
44
44
|
"ts-poet": "^6.10.0"
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@biomejs/biome": "^1.9.0",
|
|
52
|
-
"@metaobjectsdev/codegen-ts-react": "0.
|
|
53
|
-
"@metaobjectsdev/render": "0.
|
|
52
|
+
"@metaobjectsdev/codegen-ts-react": "0.7.0-rc.1",
|
|
53
|
+
"@metaobjectsdev/render": "0.7.0-rc.1",
|
|
54
54
|
"bun-types": "latest",
|
|
55
55
|
"drizzle-orm": "^0.36.0",
|
|
56
56
|
"typescript": "^5.6.0",
|
package/src/column-mapper.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "@metaobjectsdev/metadata";
|
|
28
28
|
import { columnNameFromField } from "./naming.js";
|
|
29
29
|
import { enumValues } from "./enum-meta.js";
|
|
30
|
+
import { DEFAULT_COLUMN_NAMING_STRATEGY } from "@metaobjectsdev/metadata";
|
|
30
31
|
import type { Dialect, ColumnNamingStrategy } from "./metaobjects-config.js";
|
|
31
32
|
|
|
32
33
|
export type { Dialect };
|
|
@@ -118,7 +119,7 @@ function isRequired(field: MetaField): boolean {
|
|
|
118
119
|
export function mapColumnType(
|
|
119
120
|
field: MetaField,
|
|
120
121
|
dialect: Dialect,
|
|
121
|
-
strategy: ColumnNamingStrategy =
|
|
122
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
122
123
|
): ColumnSpec {
|
|
123
124
|
const dbName = field.column ?? columnNameFromField(field.name, strategy);
|
|
124
125
|
const importModule = dialect === "sqlite" ? "drizzle-orm/sqlite-core" : "drizzle-orm/pg-core";
|
package/src/generators/index.ts
CHANGED
|
@@ -3,3 +3,5 @@ export { queriesFile, type QueriesFileOpts } from "./queries-file.js";
|
|
|
3
3
|
export { routesFile, type RoutesFileOpts } from "./routes-file.js";
|
|
4
4
|
export { barrel, type BarrelOpts } from "./barrel.js";
|
|
5
5
|
export { mermaidErDiagram, type MermaidErOptions } from "./mermaid-er.js";
|
|
6
|
+
export { promptRender, type PromptRenderOpts } from "./prompt-render-file.js";
|
|
7
|
+
export { outputParser, type OutputParserOpts } from "./output-parser-file.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/generators/output-parser-file.ts
|
|
2
|
+
//
|
|
3
|
+
// Stock generator that emits one <TemplateName>.output.ts file per declared
|
|
4
|
+
// template.output node. Wraps renderOutputParser() from templates/output-parser.ts.
|
|
5
|
+
//
|
|
6
|
+
// Consumer wiring (metaobjects.config.ts):
|
|
7
|
+
// generators: [..., promptRender(), outputParser()]
|
|
8
|
+
//
|
|
9
|
+
// Custom output directory:
|
|
10
|
+
// generators: [..., outputParser({ outDir: "src/generated/outputs" })]
|
|
11
|
+
|
|
12
|
+
import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_OUTPUT } from "@metaobjectsdev/metadata";
|
|
13
|
+
import {
|
|
14
|
+
type EmittedFile,
|
|
15
|
+
type Generator,
|
|
16
|
+
type GeneratorFactory,
|
|
17
|
+
oncePerRun,
|
|
18
|
+
} from "../generator.js";
|
|
19
|
+
import { renderOutputParser } from "../templates/output-parser.js";
|
|
20
|
+
|
|
21
|
+
export interface OutputParserOpts {
|
|
22
|
+
/** Output directory prefix relative to the target's outDir. Default: "" (root). */
|
|
23
|
+
outDir?: string;
|
|
24
|
+
/** Optional named output target (registry key). Defaults to "default". */
|
|
25
|
+
target?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const outputParser = function outputParser(opts?: OutputParserOpts): Generator {
|
|
29
|
+
const dirPrefix = opts?.outDir ? `${opts.outDir.replace(/\/$/, "")}/` : "";
|
|
30
|
+
const generator: Generator = {
|
|
31
|
+
name: "output-parser",
|
|
32
|
+
generate: oncePerRun((_entities, ctx) => {
|
|
33
|
+
const outputs = ctx.loadedRoot
|
|
34
|
+
.ownChildren()
|
|
35
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
36
|
+
const files: EmittedFile[] = [];
|
|
37
|
+
for (const t of outputs) {
|
|
38
|
+
files.push({
|
|
39
|
+
path: `${dirPrefix}${t.name}.output.ts`,
|
|
40
|
+
content: renderOutputParser(ctx.loadedRoot, t.name),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return files;
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
if (opts?.target) {
|
|
47
|
+
generator.target = opts.target;
|
|
48
|
+
}
|
|
49
|
+
return generator;
|
|
50
|
+
} as GeneratorFactory<OutputParserOpts>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Stock generator that wraps generatePayloadInterfaces() + generateRenderHandle()
|
|
2
|
+
// from payload-codegen.ts into a Generator factory. Emits ONE file aggregating
|
|
3
|
+
// typed payload interfaces (for object.value entities) and render handles (for
|
|
4
|
+
// template.prompt nodes).
|
|
5
|
+
//
|
|
6
|
+
// Consumer wiring (metaobjects.config.ts):
|
|
7
|
+
// generators: [..., promptRender()]
|
|
8
|
+
//
|
|
9
|
+
// Custom output path:
|
|
10
|
+
// generators: [..., promptRender({ outFile: "src/render/generated/prompts.ts" })]
|
|
11
|
+
|
|
12
|
+
import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_PROMPT, OBJECT_SUBTYPE_VALUE } from "@metaobjectsdev/metadata";
|
|
13
|
+
import {
|
|
14
|
+
type Generator,
|
|
15
|
+
type GeneratorFactory,
|
|
16
|
+
oncePerRun,
|
|
17
|
+
} from "../generator.js";
|
|
18
|
+
import {
|
|
19
|
+
generatePayloadInterfaces,
|
|
20
|
+
generateRenderHandle,
|
|
21
|
+
} from "../payload-codegen.js";
|
|
22
|
+
|
|
23
|
+
export interface PromptRenderOpts {
|
|
24
|
+
/** Output file path relative to the target's outDir. Default: "prompts.ts". */
|
|
25
|
+
outFile?: string;
|
|
26
|
+
/** Optional named output target (registry key). Defaults to "default". */
|
|
27
|
+
target?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const promptRender = function promptRender(opts?: PromptRenderOpts): Generator {
|
|
31
|
+
const outFile = opts?.outFile ?? "prompts.ts";
|
|
32
|
+
const generator: Generator = {
|
|
33
|
+
name: "prompt-render",
|
|
34
|
+
generate: oncePerRun((entities, ctx) => {
|
|
35
|
+
const payloads = entities.filter((e) => e.subType === OBJECT_SUBTYPE_VALUE);
|
|
36
|
+
const prompts = ctx.loadedRoot
|
|
37
|
+
.ownChildren()
|
|
38
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT);
|
|
39
|
+
|
|
40
|
+
if (payloads.length === 0 && prompts.length === 0) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parts: string[] = [];
|
|
45
|
+
for (const p of payloads) {
|
|
46
|
+
parts.push(generatePayloadInterfaces(ctx.loadedRoot, p.name));
|
|
47
|
+
}
|
|
48
|
+
// Strip the `import type { ... } from "./payloads.js"` line that
|
|
49
|
+
// generateRenderHandle() emits for the standalone two-file scenario.
|
|
50
|
+
// In the single-file output here the payload interfaces are already
|
|
51
|
+
// defined above, so the import is a self-reference to a non-existent module.
|
|
52
|
+
for (const t of prompts) {
|
|
53
|
+
const handle = generateRenderHandle(ctx.loadedRoot, t.name)
|
|
54
|
+
.split("\n")
|
|
55
|
+
.filter((line) => !line.startsWith("import type {") || !line.includes("./payloads.js"))
|
|
56
|
+
.join("\n");
|
|
57
|
+
parts.push(handle);
|
|
58
|
+
}
|
|
59
|
+
return [{
|
|
60
|
+
path: outFile,
|
|
61
|
+
content: parts.filter((s) => s.length > 0).map((s) => s.trimEnd()).join("\n\n") + "\n",
|
|
62
|
+
}];
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
if (opts?.target) {
|
|
66
|
+
generator.target = opts.target;
|
|
67
|
+
}
|
|
68
|
+
return generator;
|
|
69
|
+
} as GeneratorFactory<PromptRenderOpts>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { DEFAULT_COLUMN_NAMING_STRATEGY, type ColumnNamingStrategy } from "@metaobjectsdev/metadata";
|
|
1
2
|
import type { Generator } from "./generator.js";
|
|
2
3
|
import type { ExtStyle } from "./render-context.js";
|
|
3
4
|
import type { OutputLayout, ResolvedTarget } from "./import-path.js";
|
|
4
5
|
|
|
5
6
|
export type Dialect = "sqlite" | "postgres";
|
|
6
|
-
|
|
7
|
+
/** Re-exported from metadata so codegen-ts consumers see one canonical type. */
|
|
8
|
+
export type { ColumnNamingStrategy } from "@metaobjectsdev/metadata";
|
|
7
9
|
export type { ExtStyle };
|
|
8
10
|
export type { OutputLayout };
|
|
9
11
|
export type { ResolvedTarget };
|
|
@@ -87,7 +89,7 @@ export function resolveTargets(config: MetaobjectsGenConfig): Record<string, Res
|
|
|
87
89
|
export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobjectsGenConfig {
|
|
88
90
|
return {
|
|
89
91
|
...config,
|
|
90
|
-
columnNamingStrategy: config.columnNamingStrategy ??
|
|
92
|
+
columnNamingStrategy: config.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
91
93
|
apiPrefix: config.apiPrefix ?? "",
|
|
92
94
|
outputLayout: config.outputLayout ?? "flat",
|
|
93
95
|
targets: resolveTargets(config),
|
package/src/naming.ts
CHANGED
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
// Naming helpers — case conversion + pluralization for codegen output.
|
|
2
|
-
// All functions are pure.
|
|
2
|
+
// All functions are pure. The strategy primitives (toSnakeCase, toKebabCase,
|
|
3
|
+
// applyColumnNamingStrategy, pluralize, DEFAULT_COLUMN_NAMING_STRATEGY) are
|
|
4
|
+
// re-exported from @metaobjectsdev/metadata so codegen + runtime + migrate
|
|
5
|
+
// share a single source of truth for how field/table names lower to columns.
|
|
3
6
|
|
|
4
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
applyColumnNamingStrategy,
|
|
9
|
+
DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
10
|
+
pluralize,
|
|
11
|
+
toKebabCase,
|
|
12
|
+
toSnakeCase,
|
|
13
|
+
type ColumnNamingStrategy,
|
|
14
|
+
} from "@metaobjectsdev/metadata";
|
|
5
15
|
|
|
6
|
-
|
|
7
|
-
* Convert PascalCase or camelCase to snake_case.
|
|
8
|
-
* Treats consecutive capitals (e.g., "APIKey") as a single word: "api_key".
|
|
9
|
-
*/
|
|
10
|
-
export function toSnakeCase(s: string): string {
|
|
11
|
-
return s
|
|
12
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
13
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
14
|
-
.toLowerCase();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Convert PascalCase or camelCase to kebab-case. */
|
|
18
|
-
function toKebabCase(s: string): string {
|
|
19
|
-
return toSnakeCase(s).replace(/_/g, "-");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Apply a ColumnNamingStrategy to a name. */
|
|
23
|
-
function applyStrategy(name: string, strategy: ColumnNamingStrategy): string {
|
|
24
|
-
switch (strategy) {
|
|
25
|
-
case "snake_case": return toSnakeCase(name);
|
|
26
|
-
case "literal": return name;
|
|
27
|
-
case "kebab-case": return toKebabCase(name);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
16
|
+
export { pluralize, toSnakeCase } from "@metaobjectsdev/metadata";
|
|
30
17
|
|
|
31
18
|
/**
|
|
32
19
|
* Convert snake_case to camelCase. Preserves already-camelCase input.
|
|
@@ -42,31 +29,20 @@ export function toPascalCase(s: string): string {
|
|
|
42
29
|
return s.length > 0 ? s[0]!.toUpperCase() + s.slice(1) : s;
|
|
43
30
|
}
|
|
44
31
|
|
|
45
|
-
/**
|
|
46
|
-
* Simple English pluralization. Documented imperfection per design §13 #1:
|
|
47
|
-
* irregular plurals (Person → Persons, not People) are not handled.
|
|
48
|
-
* Users override via source[dbTable]@name in metadata.
|
|
49
|
-
*/
|
|
50
|
-
export function pluralize(s: string): string {
|
|
51
|
-
if (/(s|x|z|ch|sh)$/i.test(s)) return s + "es";
|
|
52
|
-
if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + "ies";
|
|
53
|
-
return s + "s";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
32
|
/** PascalCase entity → strategy-applied plural for DB table name. */
|
|
57
33
|
export function tableNameFromEntity(
|
|
58
34
|
entityName: string,
|
|
59
|
-
strategy: ColumnNamingStrategy =
|
|
35
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
60
36
|
): string {
|
|
61
|
-
return
|
|
37
|
+
return applyColumnNamingStrategy(pluralize(entityName), strategy);
|
|
62
38
|
}
|
|
63
39
|
|
|
64
40
|
/** camelCase or PascalCase field → strategy-applied DB column name. */
|
|
65
41
|
export function columnNameFromField(
|
|
66
42
|
fieldName: string,
|
|
67
|
-
strategy: ColumnNamingStrategy =
|
|
43
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
68
44
|
): string {
|
|
69
|
-
return
|
|
45
|
+
return applyColumnNamingStrategy(fieldName, strategy);
|
|
70
46
|
}
|
|
71
47
|
|
|
72
48
|
/**
|
|
@@ -78,14 +54,14 @@ export function viewNameFromProjection(
|
|
|
78
54
|
projectionName: string,
|
|
79
55
|
strategy: ColumnNamingStrategy,
|
|
80
56
|
): string {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
case "literal": return "v_" + projectionName;
|
|
84
|
-
case "kebab-case": return "v-" + toKebabCase(projectionName);
|
|
85
|
-
}
|
|
57
|
+
const sep = strategy === "kebab-case" ? "-" : "_";
|
|
58
|
+
return "v" + sep + applyColumnNamingStrategy(projectionName, strategy);
|
|
86
59
|
}
|
|
87
60
|
|
|
88
61
|
/** PascalCase entity → camelCase plural for the Drizzle table variable. */
|
|
89
62
|
export function variableNameFromEntity(entityName: string): string {
|
|
90
63
|
return pluralize(toCamelCase(entityName.charAt(0).toLowerCase() + entityName.slice(1)));
|
|
91
64
|
}
|
|
65
|
+
|
|
66
|
+
// Re-exported here for callers that import from codegen-ts's naming module.
|
|
67
|
+
export { toKebabCase };
|
|
@@ -348,7 +348,7 @@ function buildGroupBy(spec: SelectSpec): string[] {
|
|
|
348
348
|
* and extends a writable entity).
|
|
349
349
|
* @param root The loader's MetaRoot — all top-level objects are
|
|
350
350
|
* direct children of root (returned by `MetaDataLoader.load()`
|
|
351
|
-
*
|
|
351
|
+
* or `MetaDataLoader.fromDirectory()` as `result.root`).
|
|
352
352
|
* @param ctx Column naming strategy for SQL identifiers.
|
|
353
353
|
*/
|
|
354
354
|
export function extractViewSpec(
|