runtypex 0.1.13 → 0.2.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 +112 -278
- package/dist/cjs/core/emitArrayOrTuple.d.ts +6 -0
- package/dist/cjs/core/emitArrayOrTuple.js +40 -0
- package/dist/cjs/core/emitLiteralOrEnum.d.ts +4 -0
- package/dist/cjs/core/emitLiteralOrEnum.js +47 -0
- package/dist/cjs/core/emitMapperFromSpec.d.ts +33 -0
- package/dist/cjs/core/emitMapperFromSpec.js +199 -0
- package/dist/cjs/core/emitObject.d.ts +6 -0
- package/dist/cjs/core/emitObject.js +30 -0
- package/dist/cjs/core/emitPrimitive.d.ts +6 -0
- package/dist/cjs/core/emitPrimitive.js +29 -0
- package/dist/cjs/core/emitUnionOrIntersection.d.ts +6 -0
- package/dist/cjs/core/emitUnionOrIntersection.js +15 -0
- package/dist/cjs/core/index.d.ts +17 -0
- package/dist/cjs/core/index.js +41 -0
- package/dist/cjs/core/path.d.ts +9 -0
- package/dist/cjs/core/path.js +42 -0
- package/dist/cjs/generator/generate-jsdoc.d.ts +13 -0
- package/dist/cjs/generator/generate-jsdoc.js +103 -0
- package/dist/cjs/generator/index.d.ts +1 -0
- package/dist/cjs/generator/index.js +17 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/mapper/index.d.ts +1 -0
- package/dist/cjs/mapper/index.js +17 -0
- package/dist/cjs/runtime/index.d.ts +2 -0
- package/dist/cjs/runtime/index.js +18 -0
- package/dist/cjs/runtime/mapper.d.ts +72 -0
- package/dist/cjs/runtime/mapper.js +79 -0
- package/dist/cjs/runtime/validate.d.ts +11 -0
- package/dist/cjs/runtime/validate.js +18 -0
- package/dist/cjs/transformer/helper.d.ts +2 -0
- package/dist/cjs/transformer/helper.js +63 -0
- package/dist/cjs/transformer/index.d.ts +3 -0
- package/dist/cjs/transformer/index.js +12 -0
- package/dist/cjs/transformer/ts-transformer.d.ts +29 -0
- package/dist/cjs/transformer/ts-transformer.js +109 -0
- package/dist/cjs/transformer/vite-plugin.d.ts +18 -0
- package/dist/cjs/transformer/vite-plugin.js +72 -0
- package/dist/esm/core/emitArrayOrTuple.d.ts +1 -1
- package/dist/esm/core/emitLiteralOrEnum.d.ts +1 -1
- package/dist/esm/core/emitMapperFromSpec.d.ts +33 -0
- package/dist/esm/core/emitMapperFromSpec.js +189 -0
- package/dist/esm/core/emitObject.d.ts +1 -1
- package/dist/esm/core/emitObject.js +4 -2
- package/dist/esm/core/emitPrimitive.d.ts +1 -1
- package/dist/esm/core/emitUnionOrIntersection.d.ts +1 -1
- package/dist/esm/core/path.d.ts +9 -0
- package/dist/esm/core/path.js +36 -0
- package/dist/esm/generator/generate-jsdoc.d.ts +13 -0
- package/dist/esm/generator/generate-jsdoc.js +97 -0
- package/dist/esm/generator/index.d.ts +1 -0
- package/dist/esm/generator/index.js +1 -0
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +1 -0
- package/dist/esm/mapper/index.d.ts +1 -0
- package/dist/esm/mapper/index.js +1 -0
- package/dist/esm/runtime/index.d.ts +2 -0
- package/dist/esm/runtime/index.js +2 -0
- package/dist/esm/runtime/mapper.d.ts +72 -0
- package/dist/esm/runtime/mapper.js +71 -0
- package/dist/esm/transformer/index.d.ts +3 -0
- package/dist/esm/transformer/index.js +3 -0
- package/dist/esm/transformer/ts-transformer.d.ts +8 -4
- package/dist/esm/transformer/ts-transformer.js +51 -10
- package/dist/esm/transformer/vite-plugin.js +7 -32
- package/docs/build-integrations.md +89 -0
- package/docs/jsdoc-generation.md +105 -0
- package/docs/mapper.md +104 -0
- package/docs/mapping-policy.md +78 -0
- package/docs/runtime-validation.md +84 -0
- package/package.json +76 -36
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.emitMapperFromSpec = emitMapperFromSpec;
|
|
7
|
+
exports.readMapRules = readMapRules;
|
|
8
|
+
exports.findMapPolicyViolations = findMapPolicyViolations;
|
|
9
|
+
exports.handleMapPolicyViolations = handleMapPolicyViolations;
|
|
10
|
+
exports.resolveMapSpecObject = resolveMapSpecObject;
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const index_js_1 = require("./index.js");
|
|
13
|
+
const path_js_1 = require("./path.js");
|
|
14
|
+
function emitMapperFromSpec(params) {
|
|
15
|
+
// Resolve the concrete object literal so generated code does not retain DSL calls.
|
|
16
|
+
const specObject = resolveMapSpecObject(params.checker, params.specNode);
|
|
17
|
+
if (!specObject)
|
|
18
|
+
return null;
|
|
19
|
+
handleMapPolicyViolations(findMapPolicyViolations(params.checker, specObject, params.options?.mappingPolicy), params.options?.policyMode ?? "warn");
|
|
20
|
+
const rules = readMapRules(params.checker, specObject);
|
|
21
|
+
const props = params.checker.getPropertiesOfType(params.domainType);
|
|
22
|
+
const fields = [];
|
|
23
|
+
for (const prop of props) {
|
|
24
|
+
const rule = rules.get(prop.name);
|
|
25
|
+
if (!rule)
|
|
26
|
+
return null;
|
|
27
|
+
fields.push(`${JSON.stringify(prop.name)}:R(${JSON.stringify(prop.name)},${(0, path_js_1.emitPathAccess)("input", rule.from)})`);
|
|
28
|
+
}
|
|
29
|
+
const specText = _emitRuntimeSpecText(specObject, params.sourceFile);
|
|
30
|
+
const dtoGuard = params.options?.validateDto === false ? null : (0, index_js_1.emitGuardFromType)(params.checker, params.dtoType);
|
|
31
|
+
const domainGuard = params.options?.validateDomain === false ? null : (0, index_js_1.emitGuardFromType)(params.checker, params.domainType);
|
|
32
|
+
return [
|
|
33
|
+
`(function(){const S=${specText};`,
|
|
34
|
+
dtoGuard ? `const VD=${dtoGuard};` : "",
|
|
35
|
+
domainGuard ? `const VO=${domainGuard};` : "",
|
|
36
|
+
`return(input)=>{`,
|
|
37
|
+
dtoGuard ? `if(!VD(input))throw new TypeError("[runtypex] DTO validation failed.");` : "",
|
|
38
|
+
`const R=(key,raw)=>{const rule=S[key];const value=raw===undefined&&Object.prototype.hasOwnProperty.call(rule,"default")?rule.default:raw;return typeof rule.transform==="function"?rule.transform(value,input):value;};`,
|
|
39
|
+
`const output={${fields.join(",")}};`,
|
|
40
|
+
domainGuard ? `if(!VO(output))throw new TypeError("[runtypex] Domain validation failed.");` : "",
|
|
41
|
+
`return output;};})()`,
|
|
42
|
+
].join("");
|
|
43
|
+
}
|
|
44
|
+
function readMapRules(checker, specNode) {
|
|
45
|
+
const object = resolveMapSpecObject(checker, specNode);
|
|
46
|
+
const rules = new Map();
|
|
47
|
+
if (!object)
|
|
48
|
+
return rules;
|
|
49
|
+
for (const prop of object.properties) {
|
|
50
|
+
if (!typescript_1.default.isPropertyAssignment(prop))
|
|
51
|
+
continue;
|
|
52
|
+
const key = _propertyName(prop.name);
|
|
53
|
+
const rule = _readRule(prop.initializer);
|
|
54
|
+
if (key && rule)
|
|
55
|
+
rules.set(key, { key, ...rule });
|
|
56
|
+
}
|
|
57
|
+
return rules;
|
|
58
|
+
}
|
|
59
|
+
function findMapPolicyViolations(checker, specNode, policyNode) {
|
|
60
|
+
if (!policyNode)
|
|
61
|
+
return [];
|
|
62
|
+
const rules = readMapRules(checker, specNode);
|
|
63
|
+
const policyRules = readMapRules(checker, policyNode);
|
|
64
|
+
const canonicalByPath = new Map();
|
|
65
|
+
const violations = [];
|
|
66
|
+
for (const rule of policyRules.values()) {
|
|
67
|
+
const existing = canonicalByPath.get(rule.from);
|
|
68
|
+
if (existing && existing !== rule.key) {
|
|
69
|
+
violations.push({ from: rule.from, expectedKey: existing, actualKey: rule.key });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
canonicalByPath.set(rule.from, rule.key);
|
|
73
|
+
}
|
|
74
|
+
violations.push(...Array.from(rules.values()).flatMap((rule) => {
|
|
75
|
+
const expected = canonicalByPath.get(rule.from);
|
|
76
|
+
return expected && expected !== rule.key
|
|
77
|
+
? [{ from: rule.from, expectedKey: expected, actualKey: rule.key }]
|
|
78
|
+
: [];
|
|
79
|
+
}));
|
|
80
|
+
return violations;
|
|
81
|
+
}
|
|
82
|
+
function handleMapPolicyViolations(violations, mode) {
|
|
83
|
+
if (!violations.length)
|
|
84
|
+
return;
|
|
85
|
+
const details = violations
|
|
86
|
+
.map((item) => `DTO path "${item.from}" is canonically mapped as "${item.expectedKey}", but this map uses "${item.actualKey}".`)
|
|
87
|
+
.join("\n");
|
|
88
|
+
const message = `[runtypex/mapper] Mapping policy violation:\n${details}`;
|
|
89
|
+
if (mode === "error")
|
|
90
|
+
throw new Error(message);
|
|
91
|
+
console.warn(message);
|
|
92
|
+
}
|
|
93
|
+
/** Finds the mapping object behind inline, defineMap-wrapped, or identifier specs. */
|
|
94
|
+
function resolveMapSpecObject(checker, node) {
|
|
95
|
+
const expr = _skip(node);
|
|
96
|
+
if (typescript_1.default.isObjectLiteralExpression(expr))
|
|
97
|
+
return expr;
|
|
98
|
+
if (typescript_1.default.isCallExpression(expr) && expr.arguments[0]) {
|
|
99
|
+
const arg = _skip(expr.arguments[0]);
|
|
100
|
+
if (typescript_1.default.isObjectLiteralExpression(arg))
|
|
101
|
+
return arg;
|
|
102
|
+
}
|
|
103
|
+
if (typescript_1.default.isCallExpression(expr) && typescript_1.default.isCallExpression(expr.expression)) {
|
|
104
|
+
return resolveMapSpecObject(checker, expr.expression);
|
|
105
|
+
}
|
|
106
|
+
if (typescript_1.default.isIdentifier(expr)) {
|
|
107
|
+
const symbol = checker.getShorthandAssignmentValueSymbol?.(expr) ?? checker.getSymbolAtLocation(expr);
|
|
108
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
109
|
+
if (declaration && typescript_1.default.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
110
|
+
return resolveMapSpecObject(checker, declaration.initializer);
|
|
111
|
+
}
|
|
112
|
+
const variable = _findVariableDeclaration(expr.getSourceFile(), expr.text, expr.getStart(expr.getSourceFile()));
|
|
113
|
+
if (variable?.initializer)
|
|
114
|
+
return resolveMapSpecObject(checker, variable.initializer);
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function _readRule(node) {
|
|
119
|
+
const expr = _skip(node);
|
|
120
|
+
if (typescript_1.default.isObjectLiteralExpression(expr)) {
|
|
121
|
+
return _readRuleObject(expr);
|
|
122
|
+
}
|
|
123
|
+
if (typescript_1.default.isCallExpression(expr) && expr.arguments[0]) {
|
|
124
|
+
const from = _stringValue(expr.arguments[0]);
|
|
125
|
+
const metadata = expr.arguments.length > 2 ? expr.arguments[2] : expr.arguments[1];
|
|
126
|
+
if (!from)
|
|
127
|
+
return null;
|
|
128
|
+
return { from, ..._readMetadata(metadata) };
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function _readRuleObject(object) {
|
|
133
|
+
const from = _readStringProperty(object, "from");
|
|
134
|
+
if (!from)
|
|
135
|
+
return null;
|
|
136
|
+
return {
|
|
137
|
+
from,
|
|
138
|
+
db: _readStringProperty(object, "db") ?? undefined,
|
|
139
|
+
description: _readStringProperty(object, "description") ?? undefined,
|
|
140
|
+
dtoDescription: _readStringProperty(object, "dtoDescription") ?? undefined,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function _readMetadata(node) {
|
|
144
|
+
const expr = node ? _skip(node) : null;
|
|
145
|
+
if (!expr || !typescript_1.default.isObjectLiteralExpression(expr))
|
|
146
|
+
return {};
|
|
147
|
+
return {
|
|
148
|
+
db: _readStringProperty(expr, "db") ?? undefined,
|
|
149
|
+
description: _readStringProperty(expr, "description") ?? undefined,
|
|
150
|
+
dtoDescription: _readStringProperty(expr, "dtoDescription") ?? undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function _readStringProperty(object, name) {
|
|
154
|
+
const prop = object.properties.find((item) => typescript_1.default.isPropertyAssignment(item) && _propertyName(item.name) === name);
|
|
155
|
+
return prop ? _stringValue(prop.initializer) : null;
|
|
156
|
+
}
|
|
157
|
+
function _skip(node) {
|
|
158
|
+
let expr = node;
|
|
159
|
+
while (typescript_1.default.isParenthesizedExpression(expr) || typescript_1.default.isAsExpression(expr) || typescript_1.default.isTypeAssertionExpression(expr)) {
|
|
160
|
+
expr = expr.expression;
|
|
161
|
+
}
|
|
162
|
+
return expr;
|
|
163
|
+
}
|
|
164
|
+
function _propertyName(name) {
|
|
165
|
+
if (typescript_1.default.isIdentifier(name) || typescript_1.default.isStringLiteral(name) || typescript_1.default.isNumericLiteral(name))
|
|
166
|
+
return name.text;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function _stringValue(node) {
|
|
170
|
+
if (typescript_1.default.isStringLiteral(node) || typescript_1.default.isNoSubstitutionTemplateLiteral(node))
|
|
171
|
+
return node.text;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function _findVariableDeclaration(sourceFile, name, before) {
|
|
175
|
+
let found = null;
|
|
176
|
+
const visit = (node) => {
|
|
177
|
+
if (typescript_1.default.isVariableDeclaration(node) &&
|
|
178
|
+
typescript_1.default.isIdentifier(node.name) &&
|
|
179
|
+
node.name.text === name &&
|
|
180
|
+
node.getStart(sourceFile) < before) {
|
|
181
|
+
found = node;
|
|
182
|
+
}
|
|
183
|
+
node.forEachChild(visit);
|
|
184
|
+
};
|
|
185
|
+
visit(sourceFile);
|
|
186
|
+
return found;
|
|
187
|
+
}
|
|
188
|
+
function _emitRuntimeSpecText(specObject, sourceFile) {
|
|
189
|
+
// Remove TypeScript-only syntax from inline transform callbacks before embedding.
|
|
190
|
+
const marker = "__runtypexSpec";
|
|
191
|
+
const output = typescript_1.default.transpileModule(`const ${marker} = ${specObject.getText(sourceFile)};`, {
|
|
192
|
+
compilerOptions: {
|
|
193
|
+
module: typescript_1.default.ModuleKind.ESNext,
|
|
194
|
+
target: typescript_1.default.ScriptTarget.ESNext,
|
|
195
|
+
},
|
|
196
|
+
}).outputText.trim();
|
|
197
|
+
const prefix = `const ${marker} = `;
|
|
198
|
+
return output.startsWith(prefix) ? output.slice(prefix.length).replace(/;$/, "") : specObject.getText(sourceFile);
|
|
199
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.emitObject = emitObject;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const path_js_1 = require("./path.js");
|
|
9
|
+
/**
|
|
10
|
+
* Handles interfaces, classes, and object-like structures.
|
|
11
|
+
*/
|
|
12
|
+
function emitObject(ctx, expr, t) {
|
|
13
|
+
const isObject = (t.getFlags() & typescript_1.default.TypeFlags.Object) !== 0;
|
|
14
|
+
if (!isObject)
|
|
15
|
+
return null;
|
|
16
|
+
const props = ctx.checker.getPropertiesOfType(t);
|
|
17
|
+
const parts = [`typeof ${expr}==="object"`, `${expr}!==null`];
|
|
18
|
+
for (const prop of props) {
|
|
19
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
20
|
+
if (!declaration)
|
|
21
|
+
continue;
|
|
22
|
+
const propType = ctx.checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
23
|
+
const isOptional = (prop.getFlags() & typescript_1.default.SymbolFlags.Optional) !== 0;
|
|
24
|
+
const propExpr = (0, path_js_1.emitPropertyAccess)(expr, prop.name);
|
|
25
|
+
const condition = ctx.emit(propExpr, propType);
|
|
26
|
+
const checkExpr = isOptional ? `(${propExpr}===undefined||${condition})` : condition;
|
|
27
|
+
parts.push(checkExpr);
|
|
28
|
+
}
|
|
29
|
+
return `(${parts.join("&&")})`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.emitPrimitive = emitPrimitive;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
/**
|
|
9
|
+
* Handles primitive types like number, string, boolean...
|
|
10
|
+
*/
|
|
11
|
+
function emitPrimitive(ctx, expr, t) {
|
|
12
|
+
if ((t.flags & (typescript_1.default.TypeFlags.Any | typescript_1.default.TypeFlags.Unknown)) !== 0)
|
|
13
|
+
return "true";
|
|
14
|
+
if (t.flags & typescript_1.default.TypeFlags.Null)
|
|
15
|
+
return `${expr}===null`;
|
|
16
|
+
if (t.flags & typescript_1.default.TypeFlags.Undefined)
|
|
17
|
+
return `${expr}===undefined`;
|
|
18
|
+
if (t.flags & typescript_1.default.TypeFlags.BooleanLike)
|
|
19
|
+
return `typeof ${expr}==="boolean"`;
|
|
20
|
+
if (t.flags & typescript_1.default.TypeFlags.NumberLike)
|
|
21
|
+
return `typeof ${expr}==="number"`;
|
|
22
|
+
if (t.flags & typescript_1.default.TypeFlags.StringLike)
|
|
23
|
+
return `typeof ${expr}==="string"`;
|
|
24
|
+
if (t.flags & typescript_1.default.TypeFlags.BigIntLike)
|
|
25
|
+
return `typeof ${expr}==="bigint"`;
|
|
26
|
+
if (t.flags & typescript_1.default.TypeFlags.ESSymbolLike)
|
|
27
|
+
return `typeof ${expr}==="symbol"`;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emitUnionOrIntersection = emitUnionOrIntersection;
|
|
4
|
+
/**
|
|
5
|
+
* Handles union (A | B) and intersection (A & B) types.
|
|
6
|
+
*/
|
|
7
|
+
function emitUnionOrIntersection(ctx, expr, t) {
|
|
8
|
+
if (t.isUnion()) {
|
|
9
|
+
return `(${t.types.map(tt => ctx.emit(expr, tt)).join("||")})`;
|
|
10
|
+
}
|
|
11
|
+
if (t.isIntersection()) {
|
|
12
|
+
return `(${t.types.map(tt => ctx.emit(expr, tt)).join("&&")})`;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* ✅ emitGuardFromType
|
|
4
|
+
* Converts a TypeScript type to a JavaScript runtime validation function string.
|
|
5
|
+
*/
|
|
6
|
+
export declare function emitGuardFromType(checker: ts.TypeChecker, type: ts.Type): string;
|
|
7
|
+
/**
|
|
8
|
+
* ✅ GenContext
|
|
9
|
+
* Internal helper for converting TypeScript types to JS validation expressions.
|
|
10
|
+
*/
|
|
11
|
+
export declare class GenContext {
|
|
12
|
+
checker: ts.TypeChecker;
|
|
13
|
+
private seen;
|
|
14
|
+
constructor(checker: ts.TypeChecker);
|
|
15
|
+
/** Top-level router — delegates each type to the correct handler. */
|
|
16
|
+
emit(expr: string, t: ts.Type): string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GenContext = void 0;
|
|
4
|
+
exports.emitGuardFromType = emitGuardFromType;
|
|
5
|
+
const emitPrimitive_js_1 = require("./emitPrimitive.js");
|
|
6
|
+
const emitLiteralOrEnum_js_1 = require("./emitLiteralOrEnum.js");
|
|
7
|
+
const emitUnionOrIntersection_js_1 = require("./emitUnionOrIntersection.js");
|
|
8
|
+
const emitArrayOrTuple_js_1 = require("./emitArrayOrTuple.js");
|
|
9
|
+
const emitObject_js_1 = require("./emitObject.js");
|
|
10
|
+
/**
|
|
11
|
+
* ✅ emitGuardFromType
|
|
12
|
+
* Converts a TypeScript type to a JavaScript runtime validation function string.
|
|
13
|
+
*/
|
|
14
|
+
function emitGuardFromType(checker, type) {
|
|
15
|
+
const ctx = new GenContext(checker);
|
|
16
|
+
const condition = ctx.emit("input", type);
|
|
17
|
+
return `(input)=>${condition}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* ✅ GenContext
|
|
21
|
+
* Internal helper for converting TypeScript types to JS validation expressions.
|
|
22
|
+
*/
|
|
23
|
+
class GenContext {
|
|
24
|
+
checker;
|
|
25
|
+
seen = new Map();
|
|
26
|
+
constructor(checker) {
|
|
27
|
+
this.checker = checker;
|
|
28
|
+
}
|
|
29
|
+
/** Top-level router — delegates each type to the correct handler. */
|
|
30
|
+
emit(expr, t) {
|
|
31
|
+
if (this.seen.has(t))
|
|
32
|
+
return this.seen.get(t);
|
|
33
|
+
return ((0, emitPrimitive_js_1.emitPrimitive)(this, expr, t) ??
|
|
34
|
+
(0, emitLiteralOrEnum_js_1.emitLiteralOrEnum)(this, expr, t) ??
|
|
35
|
+
(0, emitUnionOrIntersection_js_1.emitUnionOrIntersection)(this, expr, t) ??
|
|
36
|
+
(0, emitArrayOrTuple_js_1.emitArrayOrTuple)(this, expr, t) ??
|
|
37
|
+
(0, emitObject_js_1.emitObject)(this, expr, t) ??
|
|
38
|
+
"true");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.GenContext = GenContext;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type PathSegment = string | number;
|
|
2
|
+
/** Splits a mapper path into object keys and numeric array indexes. */
|
|
3
|
+
export declare function parsePath(path: string): PathSegment[];
|
|
4
|
+
/** Runtime fallback reader used when mapper calls are not transformed. */
|
|
5
|
+
export declare function getByPath(value: unknown, path: string): unknown;
|
|
6
|
+
/** Emits bracket-only access for user-authored DTO paths. */
|
|
7
|
+
export declare function emitPathAccess(root: string, path: string): string;
|
|
8
|
+
/** Emits compact dot access when safe, with bracket fallback for quoted keys. */
|
|
9
|
+
export declare function emitPropertyAccess(root: string, property: string | number): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePath = parsePath;
|
|
4
|
+
exports.getByPath = getByPath;
|
|
5
|
+
exports.emitPathAccess = emitPathAccess;
|
|
6
|
+
exports.emitPropertyAccess = emitPropertyAccess;
|
|
7
|
+
/** Splits a mapper path into object keys and numeric array indexes. */
|
|
8
|
+
function parsePath(path) {
|
|
9
|
+
if (!path)
|
|
10
|
+
return [];
|
|
11
|
+
return path.split(".").map((segment) => {
|
|
12
|
+
if (/^(0|[1-9]\d*)$/.test(segment))
|
|
13
|
+
return Number(segment);
|
|
14
|
+
return segment;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Runtime fallback reader used when mapper calls are not transformed. */
|
|
18
|
+
function getByPath(value, path) {
|
|
19
|
+
let current = value;
|
|
20
|
+
for (const segment of parsePath(path)) {
|
|
21
|
+
if (current == null)
|
|
22
|
+
return undefined;
|
|
23
|
+
current = current[segment];
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
/** Emits bracket-only access for user-authored DTO paths. */
|
|
28
|
+
function emitPathAccess(root, path) {
|
|
29
|
+
return parsePath(path).reduce((expr, segment) => {
|
|
30
|
+
if (typeof segment === "number")
|
|
31
|
+
return `${expr}[${segment}]`;
|
|
32
|
+
return `${expr}[${JSON.stringify(segment)}]`;
|
|
33
|
+
}, root);
|
|
34
|
+
}
|
|
35
|
+
/** Emits compact dot access when safe, with bracket fallback for quoted keys. */
|
|
36
|
+
function emitPropertyAccess(root, property) {
|
|
37
|
+
if (typeof property === "number")
|
|
38
|
+
return `${root}[${property}]`;
|
|
39
|
+
if (/^[A-Za-z_$][\w$]*$/.test(property))
|
|
40
|
+
return `${root}.${property}`;
|
|
41
|
+
return `${root}[${JSON.stringify(property)}]`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export type GenerateJSDocOptions = {
|
|
3
|
+
name?: string;
|
|
4
|
+
mappingPolicy?: ts.Expression;
|
|
5
|
+
policyMode?: "warn" | "error";
|
|
6
|
+
};
|
|
7
|
+
export declare function generateJSDocFromSpec(params: {
|
|
8
|
+
checker: ts.TypeChecker;
|
|
9
|
+
dtoType: ts.Type;
|
|
10
|
+
domainType: ts.Type;
|
|
11
|
+
specNode: ts.Expression;
|
|
12
|
+
options?: GenerateJSDocOptions;
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateJSDocFromSpec = generateJSDocFromSpec;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const path_js_1 = require("../core/path.js");
|
|
9
|
+
const emitMapperFromSpec_js_1 = require("../core/emitMapperFromSpec.js");
|
|
10
|
+
const JSDOC_CONTENT_WIDTH = 76;
|
|
11
|
+
function generateJSDocFromSpec(params) {
|
|
12
|
+
const checker = params.checker;
|
|
13
|
+
const dtoName = params.dtoType.symbol?.name ?? "Dto";
|
|
14
|
+
const name = params.options?.name ?? params.domainType.symbol?.name ?? "GeneratedDomain";
|
|
15
|
+
const rules = (0, emitMapperFromSpec_js_1.readMapRules)(checker, params.specNode);
|
|
16
|
+
const lines = [`export interface ${name} {`];
|
|
17
|
+
(0, emitMapperFromSpec_js_1.handleMapPolicyViolations)((0, emitMapperFromSpec_js_1.findMapPolicyViolations)(checker, params.specNode, params.options?.mappingPolicy), params.options?.policyMode ?? "warn");
|
|
18
|
+
// Each domain property gets source metadata that editors can show on hover.
|
|
19
|
+
for (const prop of checker.getPropertiesOfType(params.domainType)) {
|
|
20
|
+
const rule = rules.get(prop.name);
|
|
21
|
+
if (!rule)
|
|
22
|
+
throw new Error(`[runtypex/generator] ${name}.${prop.name} is not mapped.`);
|
|
23
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
24
|
+
const domainType = declaration ? checker.getTypeOfSymbolAtLocation(prop, declaration) : checker.getAnyType();
|
|
25
|
+
const dtoPathType = _getTypeAtPath(checker, params.dtoType, rule.from);
|
|
26
|
+
const description = _getDomainDescription(checker, prop) ?? rule.description;
|
|
27
|
+
const optional = (prop.getFlags() & typescript_1.default.SymbolFlags.Optional) !== 0 ? "?" : "";
|
|
28
|
+
lines.push(" /**");
|
|
29
|
+
if (description) {
|
|
30
|
+
_pushJSDocText(lines, _escapeComment(description));
|
|
31
|
+
lines.push(" *");
|
|
32
|
+
}
|
|
33
|
+
_pushJSDocField(lines, "DTO", `${dtoName}.${rule.from}`);
|
|
34
|
+
if (rule.dtoDescription) {
|
|
35
|
+
_pushJSDocText(lines, _escapeComment(rule.dtoDescription), " ");
|
|
36
|
+
}
|
|
37
|
+
_pushJSDocField(lines, "DTO type", dtoPathType ? checker.typeToString(dtoPathType) : "unknown");
|
|
38
|
+
if (rule.db)
|
|
39
|
+
_pushJSDocField(lines, "DB", _escapeComment(rule.db));
|
|
40
|
+
_pushJSDocField(lines, "Domain type", checker.typeToString(domainType));
|
|
41
|
+
lines.push(" */");
|
|
42
|
+
lines.push(` ${_propertyName(prop.name)}${optional}: ${checker.typeToString(domainType)};`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
}
|
|
45
|
+
lines.push("}");
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
/** Resolves the DTO type reached by a mapping path such as profile.name or items.0.id. */
|
|
49
|
+
function _getTypeAtPath(checker, root, path) {
|
|
50
|
+
let current = root;
|
|
51
|
+
for (const segment of (0, path_js_1.parsePath)(path)) {
|
|
52
|
+
if (!current)
|
|
53
|
+
return null;
|
|
54
|
+
if (typeof segment === "number") {
|
|
55
|
+
current =
|
|
56
|
+
checker.getElementTypeOfArrayType?.(current) ??
|
|
57
|
+
current.typeArguments?.[segment] ??
|
|
58
|
+
current.getNumberIndexType?.() ??
|
|
59
|
+
null;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const prop = checker.getPropertyOfType(current, segment);
|
|
63
|
+
const declaration = prop?.valueDeclaration ?? prop?.declarations?.[0];
|
|
64
|
+
current = prop && declaration ? checker.getTypeOfSymbolAtLocation(prop, declaration) : null;
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
68
|
+
function _propertyName(name) {
|
|
69
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
70
|
+
}
|
|
71
|
+
function _escapeComment(value) {
|
|
72
|
+
return value.replace(/\*\//g, "* /");
|
|
73
|
+
}
|
|
74
|
+
function _getDomainDescription(checker, prop) {
|
|
75
|
+
const description = typescript_1.default.displayPartsToString(prop.getDocumentationComment(checker)).trim();
|
|
76
|
+
return description || null;
|
|
77
|
+
}
|
|
78
|
+
function _pushJSDocField(lines, label, value) {
|
|
79
|
+
_pushJSDocText(lines, `${label}: ${value}`, "", " ".repeat(label.length + 2));
|
|
80
|
+
}
|
|
81
|
+
function _pushJSDocText(lines, text, firstIndent = "", continuationIndent = firstIndent) {
|
|
82
|
+
for (const line of _wrapJSDocText(text, firstIndent, continuationIndent)) {
|
|
83
|
+
lines.push(` * ${line}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function _wrapJSDocText(text, firstIndent, continuationIndent) {
|
|
87
|
+
const words = text.replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
|
|
88
|
+
if (!words.length)
|
|
89
|
+
return [firstIndent.trimEnd()];
|
|
90
|
+
const lines = [];
|
|
91
|
+
let current = firstIndent;
|
|
92
|
+
for (const word of words) {
|
|
93
|
+
const candidate = current.trim().length ? `${current} ${word}` : `${current}${word}`;
|
|
94
|
+
if (candidate.length <= JSDOC_CONTENT_WIDTH || !current.trim().length) {
|
|
95
|
+
current = candidate;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
lines.push(current.trimEnd());
|
|
99
|
+
current = `${continuationIndent}${word}`;
|
|
100
|
+
}
|
|
101
|
+
lines.push(current.trimEnd());
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./generate-jsdoc.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./generate-jsdoc.js"), exports);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { makeValidate, makeAssert, type ValidateFn, type AssertFn } from "./runtime/validate.js";
|
|
2
|
+
export { defineMap, defineMappingPolicy, mapperHelpers, makeMapper, source, transform, type DefinedMap, type Mapper, type MapperOptions, type MapperMetadata, type MapRule, type MapSpec, type MappingPolicy, type MappingPolicyMode, type PathOf, } from "./runtime/mapper.js";
|
|
3
|
+
export { default as vitePlugin } from "./transformer/vite-plugin.js";
|
|
4
|
+
export { default as tsTransformer } from "./transformer/ts-transformer.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tsTransformer = exports.vitePlugin = exports.transform = exports.source = exports.makeMapper = exports.mapperHelpers = exports.defineMappingPolicy = exports.defineMap = exports.makeAssert = exports.makeValidate = void 0;
|
|
7
|
+
var validate_js_1 = require("./runtime/validate.js");
|
|
8
|
+
Object.defineProperty(exports, "makeValidate", { enumerable: true, get: function () { return validate_js_1.makeValidate; } });
|
|
9
|
+
Object.defineProperty(exports, "makeAssert", { enumerable: true, get: function () { return validate_js_1.makeAssert; } });
|
|
10
|
+
var mapper_js_1 = require("./runtime/mapper.js");
|
|
11
|
+
Object.defineProperty(exports, "defineMap", { enumerable: true, get: function () { return mapper_js_1.defineMap; } });
|
|
12
|
+
Object.defineProperty(exports, "defineMappingPolicy", { enumerable: true, get: function () { return mapper_js_1.defineMappingPolicy; } });
|
|
13
|
+
Object.defineProperty(exports, "mapperHelpers", { enumerable: true, get: function () { return mapper_js_1.mapperHelpers; } });
|
|
14
|
+
Object.defineProperty(exports, "makeMapper", { enumerable: true, get: function () { return mapper_js_1.makeMapper; } });
|
|
15
|
+
Object.defineProperty(exports, "source", { enumerable: true, get: function () { return mapper_js_1.source; } });
|
|
16
|
+
Object.defineProperty(exports, "transform", { enumerable: true, get: function () { return mapper_js_1.transform; } });
|
|
17
|
+
var vite_plugin_js_1 = require("./transformer/vite-plugin.js");
|
|
18
|
+
Object.defineProperty(exports, "vitePlugin", { enumerable: true, get: function () { return __importDefault(vite_plugin_js_1).default; } });
|
|
19
|
+
var ts_transformer_js_1 = require("./transformer/ts-transformer.js");
|
|
20
|
+
Object.defineProperty(exports, "tsTransformer", { enumerable: true, get: function () { return __importDefault(ts_transformer_js_1).default; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../runtime/mapper.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("../runtime/mapper.js"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./validate.js"), exports);
|
|
18
|
+
__exportStar(require("./mapper.js"), exports);
|