@metaobjectsdev/codegen-ts 0.6.0 → 0.7.0-rc.2
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 +161 -4
- package/dist/column-mapper.d.ts +16 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +73 -2
- package/dist/column-mapper.js.map +1 -1
- package/dist/generators/entity-file.d.ts +15 -0
- package/dist/generators/entity-file.d.ts.map +1 -1
- package/dist/generators/entity-file.js +2 -1
- package/dist/generators/entity-file.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 +70 -0
- package/dist/generators/prompt-render-file.js.map +1 -0
- package/dist/generators/queries-file.d.ts +1 -1
- package/dist/generators/queries-file.d.ts.map +1 -1
- package/dist/generators/queries-file.js +11 -3
- package/dist/generators/queries-file.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- 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/payload-codegen.d.ts +8 -0
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +33 -3
- package/dist/payload-codegen.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/source-detect.d.ts +10 -0
- package/dist/source-detect.d.ts.map +1 -0
- package/dist/source-detect.js +30 -0
- package/dist/source-detect.js.map +1 -0
- package/dist/templates/drizzle-schema.js +27 -3
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-file.d.ts +15 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +15 -5
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/inferred-types.d.ts +9 -0
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +88 -2
- package/dist/templates/inferred-types.js.map +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/dist/templates/value-object-file.d.ts +3 -0
- package/dist/templates/value-object-file.d.ts.map +1 -0
- package/dist/templates/value-object-file.js +27 -0
- package/dist/templates/value-object-file.js.map +1 -0
- package/dist/templates/zod-validators.d.ts +10 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +108 -30
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +4 -4
- package/src/column-mapper.ts +86 -1
- package/src/generators/entity-file.ts +17 -1
- package/src/generators/index.ts +2 -0
- package/src/generators/output-parser-file.ts +50 -0
- package/src/generators/prompt-render-file.ts +95 -0
- package/src/generators/queries-file.ts +13 -4
- package/src/index.ts +1 -1
- package/src/metaobjects-config.ts +4 -2
- package/src/naming.ts +22 -46
- package/src/payload-codegen.ts +34 -2
- package/src/projection/extract-view-spec.ts +1 -1
- package/src/source-detect.ts +28 -0
- package/src/templates/drizzle-schema.ts +27 -3
- package/src/templates/entity-file.ts +36 -5
- package/src/templates/inferred-types.ts +117 -3
- 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
- package/src/templates/value-object-file.ts +30 -0
- package/src/templates/zod-validators.ts +121 -35
|
@@ -2,24 +2,68 @@
|
|
|
2
2
|
// Auto-generated PKs are EXCLUDED from InsertSchema (caller doesn't provide them).
|
|
3
3
|
// @autoSet fields: INSERT → .optional().transform(() => new Date().toISOString())
|
|
4
4
|
// UPDATE → onCreate fields omitted entirely; onUpdate gets same transform
|
|
5
|
-
|
|
5
|
+
//
|
|
6
|
+
// field.object isArray:true objectRef:<Ref> — emits z.array(<Ref>InsertSchema)
|
|
7
|
+
// with a cross-module imp() so consumers passing the schema to
|
|
8
|
+
// zod-to-json-schema get a properly element-typed array. Without this, every
|
|
9
|
+
// object-array field collapsed to z.array(z.string()) and the JSON Schema sent
|
|
10
|
+
// downstream (e.g. to LLM tool_use input_schema) lost the nested object shape.
|
|
11
|
+
import { code, joinCode, imp } from "ts-poet";
|
|
6
12
|
import { MetaObject, MetaField } from "@metaobjectsdev/metadata";
|
|
7
|
-
import { FIELD_SUBTYPE_STRING, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_DATE, FIELD_SUBTYPE_TIME, FIELD_SUBTYPE_TIMESTAMP, FIELD_SUBTYPE_ENUM, VALIDATOR_SUBTYPE_REQUIRED, VALIDATOR_SUBTYPE_LENGTH, VALIDATOR_SUBTYPE_REGEX, IDENTITY_ATTR_FIELDS, IDENTITY_ATTR_GENERATION, FIELD_ATTR_REQUIRED, FIELD_ATTR_MAX_LENGTH, FIELD_ATTR_DEFAULT, FIELD_ATTR_AUTO_SET, AUTO_SET_ON_CREATE, AUTO_SET_ON_UPDATE, VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_PATTERN, GENERATION_INCREMENT, GENERATION_UUID, } from "@metaobjectsdev/metadata";
|
|
13
|
+
import { FIELD_SUBTYPE_STRING, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_DATE, FIELD_SUBTYPE_TIME, FIELD_SUBTYPE_TIMESTAMP, FIELD_SUBTYPE_ENUM, FIELD_SUBTYPE_OBJECT, VALIDATOR_SUBTYPE_REQUIRED, VALIDATOR_SUBTYPE_LENGTH, VALIDATOR_SUBTYPE_REGEX, IDENTITY_ATTR_FIELDS, IDENTITY_ATTR_GENERATION, FIELD_ATTR_REQUIRED, FIELD_ATTR_MAX_LENGTH, FIELD_ATTR_DEFAULT, FIELD_ATTR_AUTO_SET, FIELD_ATTR_OBJECT_REF, AUTO_SET_ON_CREATE, AUTO_SET_ON_UPDATE, VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_PATTERN, GENERATION_INCREMENT, GENERATION_UUID, } from "@metaobjectsdev/metadata";
|
|
8
14
|
import { enumValues, zodEnumExpr } from "../enum-meta.js";
|
|
9
15
|
import { renderDocsFor } from "./jsdoc.js";
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/** Auto-generated PK field names that should be omitted from InsertSchema. */
|
|
17
|
+
function autoGenPkFieldNames(obj) {
|
|
18
|
+
const out = new Set();
|
|
12
19
|
const primary = obj.primaryIdentity();
|
|
13
|
-
const autoGenPkFields = new Set();
|
|
14
20
|
if (primary) {
|
|
15
21
|
const generation = primary.ownAttr(IDENTITY_ATTR_GENERATION);
|
|
16
22
|
if (generation === GENERATION_INCREMENT || generation === GENERATION_UUID) {
|
|
17
23
|
const fields = primary.ownAttr(IDENTITY_ATTR_FIELDS);
|
|
18
24
|
const fieldsList = Array.isArray(fields) ? fields : (typeof fields === "string" ? [fields] : []);
|
|
19
25
|
for (const f of fieldsList)
|
|
20
|
-
|
|
26
|
+
out.add(String(f));
|
|
21
27
|
}
|
|
22
28
|
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Emit ONLY the `<Name>InsertSchema`. Used by the value-object file emitter
|
|
33
|
+
* for metaobjects with no writable source.rdb — those have no PATCH/update
|
|
34
|
+
* semantics, so emitting an UpdateSchema would be misleading.
|
|
35
|
+
*
|
|
36
|
+
* The schema name is kept as `<Name>InsertSchema` even for pure value objects
|
|
37
|
+
* so consumer imports don't churn. A future polish PR could add a `<Name>Schema`
|
|
38
|
+
* alias for clarity.
|
|
39
|
+
*/
|
|
40
|
+
export function renderInsertSchemaOnly(obj) {
|
|
41
|
+
const z = imp("z@zod");
|
|
42
|
+
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
43
|
+
const insertFieldLines = [];
|
|
44
|
+
for (const child of obj.fields()) {
|
|
45
|
+
if (autoGenPkFields.has(child.name))
|
|
46
|
+
continue;
|
|
47
|
+
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
48
|
+
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
49
|
+
insertFieldLines.push(code ` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
insertFieldLines.push(code ` ${child.name}: ${zodFieldExpr(child)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const insertSchemaName = `${obj.name}InsertSchema`;
|
|
56
|
+
const docs = renderDocsFor(obj);
|
|
57
|
+
const docsPrefix = docs ? `${docs}\n` : "";
|
|
58
|
+
return code `
|
|
59
|
+
${docsPrefix}export const ${insertSchemaName} = ${z}.object({
|
|
60
|
+
${joinCode(insertFieldLines, { on: ",\n" })}
|
|
61
|
+
});
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
export function renderZodValidators(obj) {
|
|
65
|
+
const z = imp("z@zod");
|
|
66
|
+
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
23
67
|
const insertFieldLines = [];
|
|
24
68
|
const updateFieldLines = [];
|
|
25
69
|
for (const child of obj.fields()) {
|
|
@@ -27,26 +71,25 @@ export function renderZodValidators(obj) {
|
|
|
27
71
|
continue;
|
|
28
72
|
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
29
73
|
// Insert schema: @autoSet fields use transform (always override client input).
|
|
30
|
-
// NOTE: use "z" as a literal string here — these lines are embedded in the
|
|
31
|
-
// `code` template tag below which resolves the imp("z@zod") import.
|
|
32
74
|
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
33
|
-
insertFieldLines.push(` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`);
|
|
75
|
+
insertFieldLines.push(code ` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`);
|
|
34
76
|
}
|
|
35
77
|
else {
|
|
36
|
-
insertFieldLines.push(` ${child.name}: ${zodFieldExpr(child)}`);
|
|
78
|
+
insertFieldLines.push(code ` ${child.name}: ${zodFieldExpr(child)}`);
|
|
37
79
|
}
|
|
38
80
|
// Update schema: @autoSet onCreate → omit entirely; onUpdate → transform
|
|
39
81
|
if (autoSet === AUTO_SET_ON_CREATE) {
|
|
40
82
|
// Omit: creation timestamps cannot be changed after creation
|
|
41
83
|
}
|
|
42
84
|
else if (autoSet === AUTO_SET_ON_UPDATE) {
|
|
43
|
-
updateFieldLines.push(` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`);
|
|
85
|
+
updateFieldLines.push(code ` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`);
|
|
44
86
|
}
|
|
45
87
|
else {
|
|
46
|
-
// All non-autoSet fields are optional in the update schema (PATCH semantics)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
88
|
+
// All non-autoSet fields are optional in the update schema (PATCH semantics).
|
|
89
|
+
// zodFieldExpr already appends .optional() when the field is non-required
|
|
90
|
+
// OR has a default; only append once more when it didn't.
|
|
91
|
+
const baseExpr = zodFieldExpr(child);
|
|
92
|
+
updateFieldLines.push(fieldWillBeOptional(child) ? code ` ${child.name}: ${baseExpr}` : code ` ${child.name}: ${baseExpr}.optional()`);
|
|
50
93
|
}
|
|
51
94
|
}
|
|
52
95
|
const insertSchemaName = `${obj.name}InsertSchema`;
|
|
@@ -55,46 +98,81 @@ export function renderZodValidators(obj) {
|
|
|
55
98
|
const docsPrefix = docs ? `${docs}\n` : "";
|
|
56
99
|
return code `
|
|
57
100
|
${docsPrefix}export const ${insertSchemaName} = ${z}.object({
|
|
58
|
-
${insertFieldLines
|
|
101
|
+
${joinCode(insertFieldLines, { on: ",\n" })}
|
|
59
102
|
});
|
|
60
103
|
|
|
61
104
|
${docsPrefix}export const ${updateSchemaName} = ${z}.object({
|
|
62
|
-
${updateFieldLines
|
|
105
|
+
${joinCode(updateFieldLines, { on: ",\n" })}
|
|
63
106
|
});
|
|
64
107
|
`;
|
|
65
108
|
}
|
|
66
109
|
function zodFieldExpr(field) {
|
|
67
|
-
|
|
110
|
+
// FIELD_SUBTYPE_OBJECT: emit z.array(<Ref>InsertSchema) / <Ref>InsertSchema
|
|
111
|
+
// via an imp() so ts-poet hoists the cross-module import. Without this the
|
|
112
|
+
// field used to collapse to z.string() / z.array(z.string()) and downstream
|
|
113
|
+
// JSON Schema (e.g. LLM tool_use input_schema) lost the nested object shape.
|
|
114
|
+
if (field.subType === FIELD_SUBTYPE_OBJECT) {
|
|
115
|
+
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
|
|
116
|
+
if (typeof ref === "string" && ref.length > 0) {
|
|
117
|
+
const refImp = imp(`${ref}InsertSchema@./${ref}.js`);
|
|
118
|
+
let base = code `${refImp}`;
|
|
119
|
+
if (field.isArray)
|
|
120
|
+
base = code `z.array(${base})`;
|
|
121
|
+
return appendValidatorChain(base, field);
|
|
122
|
+
}
|
|
123
|
+
// No resolvable @objectRef — fall through to z.unknown(); downstream code
|
|
124
|
+
// can still pass a value through but loses validation.
|
|
125
|
+
let base = code `z.unknown()`;
|
|
126
|
+
if (field.isArray)
|
|
127
|
+
base = code `z.array(${base})`;
|
|
128
|
+
return appendValidatorChain(base, field);
|
|
129
|
+
}
|
|
130
|
+
let baseStr;
|
|
68
131
|
switch (field.subType) {
|
|
69
132
|
case FIELD_SUBTYPE_INT:
|
|
70
133
|
case FIELD_SUBTYPE_CURRENCY:
|
|
71
134
|
case FIELD_SUBTYPE_LONG:
|
|
72
|
-
|
|
135
|
+
baseStr = "z.number().int()";
|
|
73
136
|
break;
|
|
74
137
|
case FIELD_SUBTYPE_DOUBLE:
|
|
75
138
|
case FIELD_SUBTYPE_FLOAT:
|
|
76
|
-
|
|
139
|
+
baseStr = "z.number()";
|
|
77
140
|
break;
|
|
78
141
|
case FIELD_SUBTYPE_BOOLEAN:
|
|
79
|
-
|
|
142
|
+
baseStr = "z.boolean()";
|
|
80
143
|
break;
|
|
81
144
|
case FIELD_SUBTYPE_DATE:
|
|
82
145
|
case FIELD_SUBTYPE_TIME:
|
|
83
146
|
case FIELD_SUBTYPE_TIMESTAMP:
|
|
84
|
-
|
|
147
|
+
baseStr = "z.string()";
|
|
85
148
|
break;
|
|
86
149
|
case FIELD_SUBTYPE_ENUM: {
|
|
87
150
|
const values = enumValues(field);
|
|
88
|
-
|
|
151
|
+
baseStr = values !== undefined ? zodEnumExpr(values) : "z.string()";
|
|
89
152
|
break;
|
|
90
153
|
}
|
|
91
154
|
case FIELD_SUBTYPE_STRING:
|
|
92
155
|
default:
|
|
93
|
-
|
|
156
|
+
baseStr = "z.string()";
|
|
94
157
|
break;
|
|
95
158
|
}
|
|
96
159
|
if (field.isArray)
|
|
97
|
-
|
|
160
|
+
baseStr = `z.array(${baseStr})`;
|
|
161
|
+
return appendValidatorChain(code `${baseStr}`, field);
|
|
162
|
+
}
|
|
163
|
+
/** Mirrors the optional-or-not decision inside appendValidatorChain so the update-schema
|
|
164
|
+
* caller can avoid stacking a second `.optional()` onto an already-optional expression. */
|
|
165
|
+
function fieldWillBeOptional(field) {
|
|
166
|
+
let isRequired = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
|
|
167
|
+
for (const child of field.validators()) {
|
|
168
|
+
if (child.subType === VALIDATOR_SUBTYPE_REQUIRED)
|
|
169
|
+
isRequired = true;
|
|
170
|
+
}
|
|
171
|
+
const hasDefault = field.ownAttr(FIELD_ATTR_DEFAULT) !== undefined;
|
|
172
|
+
return !isRequired || hasDefault;
|
|
173
|
+
}
|
|
174
|
+
/** Append .min/.max/.regex/.optional() based on field-level validators + required state. */
|
|
175
|
+
function appendValidatorChain(base, field) {
|
|
98
176
|
let isRequired = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
|
|
99
177
|
let maxLen = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
|
|
100
178
|
let minLen;
|
|
@@ -119,20 +197,20 @@ function zodFieldExpr(field) {
|
|
|
119
197
|
let chain = base;
|
|
120
198
|
if (field.subType === FIELD_SUBTYPE_STRING && !field.isArray) {
|
|
121
199
|
if (minLen !== undefined)
|
|
122
|
-
chain
|
|
200
|
+
chain = code `${chain}.min(${minLen})`;
|
|
123
201
|
else if (isRequired)
|
|
124
|
-
chain
|
|
202
|
+
chain = code `${chain}.min(1)`;
|
|
125
203
|
if (maxLen !== undefined)
|
|
126
|
-
chain
|
|
204
|
+
chain = code `${chain}.max(${maxLen})`;
|
|
127
205
|
if (pattern !== undefined)
|
|
128
|
-
chain
|
|
206
|
+
chain = code `${chain}.regex(new RegExp(${JSON.stringify(pattern)}))`;
|
|
129
207
|
}
|
|
130
208
|
// Fields with DB-level defaults are optional in the InsertSchema: the caller
|
|
131
209
|
// can omit them and the DB will fill in. Otherwise required-with-default
|
|
132
210
|
// would force callers to repeat the default at every call site.
|
|
133
211
|
const hasDefault = field.ownAttr(FIELD_ATTR_DEFAULT) !== undefined;
|
|
134
212
|
if (!isRequired || hasDefault)
|
|
135
|
-
chain
|
|
213
|
+
chain = code `${chain}.optional()`;
|
|
136
214
|
return chain;
|
|
137
215
|
}
|
|
138
216
|
//# sourceMappingURL=zod-validators.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-validators.js","sourceRoot":"","sources":["../../src/templates/zod-validators.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,mFAAmF;AACnF,kFAAkF;AAClF,0FAA0F;
|
|
1
|
+
{"version":3,"file":"zod-validators.js","sourceRoot":"","sources":["../../src/templates/zod-validators.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.2",
|
|
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.2",
|
|
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.2",
|
|
53
|
+
"@metaobjectsdev/render": "0.7.0-rc.2",
|
|
54
54
|
"bun-types": "latest",
|
|
55
55
|
"drizzle-orm": "^0.36.0",
|
|
56
56
|
"typescript": "^5.6.0",
|
package/src/column-mapper.ts
CHANGED
|
@@ -23,10 +23,12 @@ import {
|
|
|
23
23
|
FIELD_ATTR_REQUIRED,
|
|
24
24
|
FIELD_ATTR_UNIQUE,
|
|
25
25
|
FIELD_ATTR_DEFAULT,
|
|
26
|
+
FIELD_ATTR_OBJECT_REF,
|
|
26
27
|
VALIDATOR_ATTR_MAX,
|
|
27
28
|
} from "@metaobjectsdev/metadata";
|
|
28
29
|
import { columnNameFromField } from "./naming.js";
|
|
29
30
|
import { enumValues } from "./enum-meta.js";
|
|
31
|
+
import { DEFAULT_COLUMN_NAMING_STRATEGY } from "@metaobjectsdev/metadata";
|
|
30
32
|
import type { Dialect, ColumnNamingStrategy } from "./metaobjects-config.js";
|
|
31
33
|
|
|
32
34
|
export type { Dialect };
|
|
@@ -62,6 +64,36 @@ function isSqlExprDefault(value: string): boolean {
|
|
|
62
64
|
return SQL_EXPR_PATTERNS.some((re) => re.test(value));
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* For an isArray:true field stored in SQLite as text(...,{mode:"json"}), return
|
|
69
|
+
* the TS element type used in the emitted .$type<E[]>() chain. Returns undefined
|
|
70
|
+
* when the field's subType doesn't have a stable scalar TS mapping (e.g.,
|
|
71
|
+
* field.object — leave the inferred `unknown[]` so the consumer can layer a
|
|
72
|
+
* richer schema on top).
|
|
73
|
+
*/
|
|
74
|
+
function sqliteJsonArrayElementTsType(subType: string): string | undefined {
|
|
75
|
+
switch (subType) {
|
|
76
|
+
case FIELD_SUBTYPE_STRING:
|
|
77
|
+
case FIELD_SUBTYPE_ENUM:
|
|
78
|
+
case FIELD_SUBTYPE_CLASS:
|
|
79
|
+
case FIELD_SUBTYPE_DATE:
|
|
80
|
+
case FIELD_SUBTYPE_TIME:
|
|
81
|
+
case FIELD_SUBTYPE_TIMESTAMP:
|
|
82
|
+
case FIELD_SUBTYPE_DECIMAL:
|
|
83
|
+
return "string";
|
|
84
|
+
case FIELD_SUBTYPE_INT:
|
|
85
|
+
case FIELD_SUBTYPE_LONG:
|
|
86
|
+
case FIELD_SUBTYPE_CURRENCY:
|
|
87
|
+
case FIELD_SUBTYPE_DOUBLE:
|
|
88
|
+
case FIELD_SUBTYPE_FLOAT:
|
|
89
|
+
return "number";
|
|
90
|
+
case FIELD_SUBTYPE_BOOLEAN:
|
|
91
|
+
return "boolean";
|
|
92
|
+
default:
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
65
97
|
/** Map a recognized SQL expression to its canonical raw form (uppercase keywords). */
|
|
66
98
|
function canonicalizeSqlExpr(value: string): string {
|
|
67
99
|
const lower = value.toLowerCase();
|
|
@@ -92,6 +124,17 @@ export interface ColumnSpec {
|
|
|
92
124
|
leadingComment?: string;
|
|
93
125
|
/** Optional CHECK constraint expression for the column (e.g., `status IN ('A', 'B')`). */
|
|
94
126
|
checkConstraint?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Optional `.$type<...>()` chain target. Renderer (drizzle-schema.ts) emits
|
|
129
|
+
* `.$type<TS[]>()` ahead of the modifiers chain, using ts-poet `imp()` for
|
|
130
|
+
* objectRef variants so the cross-module type import auto-hoists.
|
|
131
|
+
* `kind: "scalar"` covers string[]/number[]/boolean[] — no import needed.
|
|
132
|
+
* `kind: "objectRef"` covers SourceLens[]/Dissent[]/etc. — `module` is the
|
|
133
|
+
* relative import path to that entity's emitted module.
|
|
134
|
+
*/
|
|
135
|
+
dollarTypeRef?:
|
|
136
|
+
| { kind: "scalar"; tsType: "string" | "number" | "boolean" }
|
|
137
|
+
| { kind: "objectRef"; name: string; module: string };
|
|
95
138
|
}
|
|
96
139
|
|
|
97
140
|
/** Resolve max length from validator.length child or @maxLength attr.
|
|
@@ -118,7 +161,7 @@ function isRequired(field: MetaField): boolean {
|
|
|
118
161
|
export function mapColumnType(
|
|
119
162
|
field: MetaField,
|
|
120
163
|
dialect: Dialect,
|
|
121
|
-
strategy: ColumnNamingStrategy =
|
|
164
|
+
strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
122
165
|
): ColumnSpec {
|
|
123
166
|
const dbName = field.column ?? columnNameFromField(field.name, strategy);
|
|
124
167
|
const importModule = dialect === "sqlite" ? "drizzle-orm/sqlite-core" : "drizzle-orm/pg-core";
|
|
@@ -219,12 +262,36 @@ export function mapColumnType(
|
|
|
219
262
|
}
|
|
220
263
|
}
|
|
221
264
|
|
|
265
|
+
// Enum literal types: pass the values as `{ enum: [...] as const }` to
|
|
266
|
+
// Drizzle's text(...) so the inferred column type is a literal union
|
|
267
|
+
// ("a" | "b" | ...) instead of bare `string`. Skip when isArray — JSON
|
|
268
|
+
// arrays use { mode: "json" }, and the enum members go through Zod
|
|
269
|
+
// validation at the Insert/Update layer instead. Mirrors the Zod
|
|
270
|
+
// emission, which already uses z.enum([...]).
|
|
271
|
+
if (subType === FIELD_SUBTYPE_ENUM && !isArray && fnName === "text") {
|
|
272
|
+
const values = enumValues(field);
|
|
273
|
+
if (values !== undefined && values.length > 0) {
|
|
274
|
+
fnOptions = { ...(fnOptions ?? {}), enum: values };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
222
278
|
const modifiers: string[] = [];
|
|
223
279
|
|
|
224
280
|
if (dialect === "postgres" && isArray) {
|
|
225
281
|
modifiers.push(".array()");
|
|
226
282
|
}
|
|
227
283
|
|
|
284
|
+
// SQLite stores arrays as JSON in a text column; Drizzle's text(...,{mode:"json"})
|
|
285
|
+
// infers the column as `unknown` without a $type<T>() annotation. Emit the
|
|
286
|
+
// chain via spec.dollarTypeRef so the renderer can hoist a type-only import
|
|
287
|
+
// for object refs (SourceLens[], Dissent[], etc.). Scalars (string/number/
|
|
288
|
+
// boolean) need no import. Postgres uses .array() above which is already
|
|
289
|
+
// element-typed by Drizzle.
|
|
290
|
+
// Determined ABOVE the modifiers chain so the renderer can position
|
|
291
|
+
// `.$type<>()` ahead of `.notNull()` etc.
|
|
292
|
+
// Note: dollarTypeRef is read alongside (and rendered ahead of) `modifiers`
|
|
293
|
+
// by `renderColumn` — see drizzle-schema.ts.
|
|
294
|
+
|
|
228
295
|
if (isRequired(field)) {
|
|
229
296
|
modifiers.push(".notNull()");
|
|
230
297
|
}
|
|
@@ -257,6 +324,23 @@ export function mapColumnType(
|
|
|
257
324
|
}
|
|
258
325
|
}
|
|
259
326
|
|
|
327
|
+
// SQLite isArray columns route through {mode:"json"} above; compute the
|
|
328
|
+
// $type<E[]>() target so the renderer can hoist any cross-module imports.
|
|
329
|
+
let dollarTypeRef: ColumnSpec["dollarTypeRef"];
|
|
330
|
+
if (dialect === "sqlite" && isArray) {
|
|
331
|
+
if (subType === FIELD_SUBTYPE_OBJECT) {
|
|
332
|
+
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
|
|
333
|
+
if (typeof ref === "string" && ref.length > 0) {
|
|
334
|
+
dollarTypeRef = { kind: "objectRef", name: ref, module: `./${ref}.js` };
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
const scalar = sqliteJsonArrayElementTsType(subType);
|
|
338
|
+
if (scalar !== undefined) {
|
|
339
|
+
dollarTypeRef = { kind: "scalar", tsType: scalar as "string" | "number" | "boolean" };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
260
344
|
const result: ColumnSpec = {
|
|
261
345
|
fnName,
|
|
262
346
|
dbName,
|
|
@@ -266,6 +350,7 @@ export function mapColumnType(
|
|
|
266
350
|
};
|
|
267
351
|
if (fnOptions !== undefined) result.fnOptions = fnOptions;
|
|
268
352
|
if (defaultExpr !== undefined) result.defaultExpr = defaultExpr;
|
|
353
|
+
if (dollarTypeRef !== undefined) result.dollarTypeRef = dollarTypeRef;
|
|
269
354
|
if (leadingComment !== undefined) result.leadingComment = leadingComment;
|
|
270
355
|
|
|
271
356
|
// Enum fields: emit a CHECK constraint listing the valid member values.
|
|
@@ -7,9 +7,25 @@ import { entityOutputPath } from "../import-path.js";
|
|
|
7
7
|
export interface EntityFileOpts {
|
|
8
8
|
filter?: (entity: MetaObject) => boolean;
|
|
9
9
|
target?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether generated entity files include the Fastify-flavored
|
|
12
|
+
* `<Entity>FilterAllowlist` + `<Entity>SortAllowlist` blocks (and the
|
|
13
|
+
* `@metaobjectsdev/runtime-ts/drizzle-fastify` type-only imports they
|
|
14
|
+
* require). Default `true` for back-compat.
|
|
15
|
+
*
|
|
16
|
+
* Set to `false` for Worker/Lambda-style consumers that don't mount
|
|
17
|
+
* Fastify-style server routes — the generated entity file then has no
|
|
18
|
+
* `runtime-ts/drizzle-fastify` imports at all and `@metaobjectsdev/runtime-ts`
|
|
19
|
+
* can be omitted from the consumer's dependency tree.
|
|
20
|
+
*
|
|
21
|
+
* The client-side `<Entity>Filter` type is always emitted — it's a pure
|
|
22
|
+
* client-side type with no runtime-ts dependency.
|
|
23
|
+
*/
|
|
24
|
+
allowlists?: boolean;
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export const entityFile = function entityFile(opts?: EntityFileOpts): Generator {
|
|
28
|
+
const allowlists = opts?.allowlists ?? true;
|
|
13
29
|
const generator: Generator = {
|
|
14
30
|
name: "entity-file",
|
|
15
31
|
emitsEntityModule: true,
|
|
@@ -19,7 +35,7 @@ export const entityFile = function entityFile(opts?: EntityFileOpts): Generator
|
|
|
19
35
|
}
|
|
20
36
|
return {
|
|
21
37
|
path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.ts`),
|
|
22
|
-
content: await formatTs(renderEntityFile(entity, ctx.renderContext)),
|
|
38
|
+
content: await formatTs(renderEntityFile(entity, ctx.renderContext, { allowlists })),
|
|
23
39
|
};
|
|
24
40
|
}),
|
|
25
41
|
};
|
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,95 @@
|
|
|
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
|
+
generatePayloadInterfacesBatch,
|
|
20
|
+
generateRenderHandle,
|
|
21
|
+
} from "../payload-codegen.js";
|
|
22
|
+
import { GENERATED_HEADER } from "../constants.js";
|
|
23
|
+
|
|
24
|
+
export interface PromptRenderOpts {
|
|
25
|
+
/** Output file path relative to the target's outDir. Default: "prompts.ts". */
|
|
26
|
+
outFile?: string;
|
|
27
|
+
/** Optional named output target (registry key). Defaults to "default". */
|
|
28
|
+
target?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Hoisted into the emitted file once. generateRenderHandle() emits this line
|
|
32
|
+
// per-handle (for the standalone scenario); we strip its per-handle copies.
|
|
33
|
+
const RENDER_IMPORT = `import { render, type Provider } from "@metaobjectsdev/render";`;
|
|
34
|
+
|
|
35
|
+
// Matches the `import type { ... } from "./payloads.js";` that generateRenderHandle
|
|
36
|
+
// emits for the standalone two-file scenario. In the single-file output here the
|
|
37
|
+
// payload interfaces are already defined above, so the import is dead.
|
|
38
|
+
function isStandalonePayloadImport(line: string): boolean {
|
|
39
|
+
return line.startsWith("import type {") && line.includes('"./payloads.js"');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const promptRender = function promptRender(opts?: PromptRenderOpts): Generator {
|
|
43
|
+
const outFile = opts?.outFile ?? "prompts.ts";
|
|
44
|
+
const generator: Generator = {
|
|
45
|
+
name: "prompt-render",
|
|
46
|
+
generate: oncePerRun((entities, ctx) => {
|
|
47
|
+
const payloads = entities.filter((e) => e.subType === OBJECT_SUBTYPE_VALUE);
|
|
48
|
+
const prompts = ctx.loadedRoot
|
|
49
|
+
.ownChildren()
|
|
50
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT);
|
|
51
|
+
|
|
52
|
+
if (payloads.length === 0 && prompts.length === 0) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parts: string[] = [`// ${GENERATED_HEADER} — DO NOT EDIT.`];
|
|
57
|
+
|
|
58
|
+
// Hoist the @metaobjectsdev/render import once (only when prompts emit handles).
|
|
59
|
+
if (prompts.length > 0) {
|
|
60
|
+
parts.push(RENDER_IMPORT);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Emit payload interfaces with a single shared dedupe set so a lens
|
|
64
|
+
// referenced by multiple payloads appears exactly once.
|
|
65
|
+
const payloadInterfaces = generatePayloadInterfacesBatch(
|
|
66
|
+
ctx.loadedRoot,
|
|
67
|
+
payloads.map((p) => p.name),
|
|
68
|
+
);
|
|
69
|
+
if (payloadInterfaces.length > 0) {
|
|
70
|
+
parts.push(payloadInterfaces);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Append each render handle with its per-handle imports stripped — both
|
|
74
|
+
// the now-hoisted render/Provider import and the standalone payloads.js
|
|
75
|
+
// import. Also drop any leading blank lines left behind by the strip so
|
|
76
|
+
// the joined output doesn't accumulate double blank gaps between parts.
|
|
77
|
+
for (const t of prompts) {
|
|
78
|
+
const lines = generateRenderHandle(ctx.loadedRoot, t.name)
|
|
79
|
+
.split("\n")
|
|
80
|
+
.filter((line) => line !== RENDER_IMPORT && !isStandalonePayloadImport(line));
|
|
81
|
+
while (lines.length > 0 && lines[0]!.trim() === "") lines.shift();
|
|
82
|
+
parts.push(lines.join("\n"));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [{
|
|
86
|
+
path: outFile,
|
|
87
|
+
content: parts.filter((s) => s.length > 0).map((s) => s.trimEnd()).join("\n\n") + "\n",
|
|
88
|
+
}];
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
if (opts?.target) {
|
|
92
|
+
generator.target = opts.target;
|
|
93
|
+
}
|
|
94
|
+
return generator;
|
|
95
|
+
} as GeneratorFactory<PromptRenderOpts>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { OBJECT_SUBTYPE_VALUE, type MetaObject } from "@metaobjectsdev/metadata";
|
|
2
2
|
import { perEntity, type Generator, type GeneratorFactory } from "../generator.js";
|
|
3
3
|
import { renderQueriesFile } from "../templates/queries-file.js";
|
|
4
4
|
import { formatTs } from "../format.js";
|
|
@@ -9,9 +9,21 @@ export interface QueriesFileOpts {
|
|
|
9
9
|
target?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// object.value records have no primary identity, so the rendered queries module
|
|
13
|
+
// emits findById/updateById/deleteById against a non-existent column. Skipping
|
|
14
|
+
// value subtypes is unconditional — the user-supplied filter (if any) is applied
|
|
15
|
+
// on top via boolean AND.
|
|
16
|
+
const skipValueTypes = (e: MetaObject): boolean => e.subType !== OBJECT_SUBTYPE_VALUE;
|
|
17
|
+
|
|
12
18
|
export const queriesFile = function queriesFile(opts?: QueriesFileOpts): Generator {
|
|
19
|
+
const userFilter = opts?.filter;
|
|
20
|
+
const filter: (e: MetaObject) => boolean = userFilter
|
|
21
|
+
? (e) => skipValueTypes(e) && userFilter(e)
|
|
22
|
+
: skipValueTypes;
|
|
23
|
+
|
|
13
24
|
const generator: Generator = {
|
|
14
25
|
name: "queries-file",
|
|
26
|
+
filter,
|
|
15
27
|
generate: perEntity(async (entity, ctx) => {
|
|
16
28
|
if (!ctx.renderContext) {
|
|
17
29
|
throw new Error("queries-file: renderContext is required (provided by runGen)");
|
|
@@ -22,9 +34,6 @@ export const queriesFile = function queriesFile(opts?: QueriesFileOpts): Generat
|
|
|
22
34
|
};
|
|
23
35
|
}),
|
|
24
36
|
};
|
|
25
|
-
if (opts?.filter) {
|
|
26
|
-
generator.filter = opts.filter;
|
|
27
|
-
}
|
|
28
37
|
if (opts?.target) {
|
|
29
38
|
generator.target = opts.target;
|
|
30
39
|
}
|