runtypex 0.1.12 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +112 -69
  2. package/dist/cjs/core/emitArrayOrTuple.d.ts +6 -0
  3. package/dist/cjs/core/emitArrayOrTuple.js +40 -0
  4. package/dist/cjs/core/emitLiteralOrEnum.d.ts +4 -0
  5. package/dist/cjs/core/emitLiteralOrEnum.js +47 -0
  6. package/dist/cjs/core/emitMapperFromSpec.d.ts +32 -0
  7. package/dist/cjs/core/emitMapperFromSpec.js +199 -0
  8. package/dist/cjs/core/emitObject.d.ts +6 -0
  9. package/dist/cjs/core/emitObject.js +30 -0
  10. package/dist/cjs/core/emitPrimitive.d.ts +6 -0
  11. package/dist/cjs/core/emitPrimitive.js +29 -0
  12. package/dist/cjs/core/emitUnionOrIntersection.d.ts +6 -0
  13. package/dist/cjs/core/emitUnionOrIntersection.js +15 -0
  14. package/dist/cjs/core/index.d.ts +17 -0
  15. package/dist/cjs/core/index.js +41 -0
  16. package/dist/cjs/core/path.d.ts +9 -0
  17. package/dist/cjs/core/path.js +42 -0
  18. package/dist/cjs/generator/generate-jsdoc.d.ts +13 -0
  19. package/dist/cjs/generator/generate-jsdoc.js +69 -0
  20. package/dist/cjs/generator/index.d.ts +1 -0
  21. package/dist/cjs/generator/index.js +17 -0
  22. package/dist/cjs/index.d.ts +4 -0
  23. package/dist/cjs/index.js +20 -0
  24. package/dist/cjs/mapper/index.d.ts +1 -0
  25. package/dist/cjs/mapper/index.js +17 -0
  26. package/dist/cjs/runtime/index.d.ts +2 -0
  27. package/dist/cjs/runtime/index.js +18 -0
  28. package/dist/cjs/runtime/mapper.d.ts +71 -0
  29. package/dist/cjs/runtime/mapper.js +79 -0
  30. package/dist/cjs/runtime/validate.d.ts +11 -0
  31. package/dist/cjs/runtime/validate.js +18 -0
  32. package/dist/cjs/transformer/helper.d.ts +2 -0
  33. package/dist/cjs/transformer/helper.js +63 -0
  34. package/dist/cjs/transformer/index.d.ts +3 -0
  35. package/dist/cjs/transformer/index.js +12 -0
  36. package/dist/cjs/transformer/ts-transformer.d.ts +29 -0
  37. package/dist/cjs/transformer/ts-transformer.js +109 -0
  38. package/dist/cjs/transformer/vite-plugin.d.ts +18 -0
  39. package/dist/cjs/transformer/vite-plugin.js +72 -0
  40. package/dist/esm/core/emitArrayOrTuple.d.ts +1 -1
  41. package/dist/esm/core/emitLiteralOrEnum.d.ts +1 -1
  42. package/dist/esm/core/emitMapperFromSpec.d.ts +32 -0
  43. package/dist/esm/core/emitMapperFromSpec.js +189 -0
  44. package/dist/esm/core/emitObject.d.ts +1 -1
  45. package/dist/esm/core/emitObject.js +4 -2
  46. package/dist/esm/core/emitPrimitive.d.ts +1 -1
  47. package/dist/esm/core/emitUnionOrIntersection.d.ts +1 -1
  48. package/dist/esm/core/path.d.ts +9 -0
  49. package/dist/esm/core/path.js +36 -0
  50. package/dist/esm/generator/generate-jsdoc.d.ts +13 -0
  51. package/dist/esm/generator/generate-jsdoc.js +63 -0
  52. package/dist/esm/generator/index.d.ts +1 -0
  53. package/dist/esm/generator/index.js +1 -0
  54. package/dist/esm/index.d.ts +4 -3
  55. package/dist/esm/index.js +1 -0
  56. package/dist/esm/mapper/index.d.ts +1 -0
  57. package/dist/esm/mapper/index.js +1 -0
  58. package/dist/esm/runtime/index.d.ts +2 -0
  59. package/dist/esm/runtime/index.js +2 -0
  60. package/dist/esm/runtime/mapper.d.ts +71 -0
  61. package/dist/esm/runtime/mapper.js +71 -0
  62. package/dist/esm/transformer/helper.d.ts +2 -0
  63. package/dist/esm/transformer/helper.js +57 -0
  64. package/dist/esm/transformer/index.d.ts +3 -0
  65. package/dist/esm/transformer/index.js +3 -0
  66. package/dist/esm/transformer/ts-transformer.d.ts +8 -4
  67. package/dist/esm/transformer/ts-transformer.js +46 -55
  68. package/dist/esm/transformer/vite-plugin.js +7 -93
  69. package/docs/build-integrations.md +89 -0
  70. package/docs/jsdoc-generation.md +88 -0
  71. package/docs/mapper.md +104 -0
  72. package/docs/mapping-policy.md +78 -0
  73. package/docs/runtime-validation.md +84 -0
  74. package/package.json +76 -36
package/README.md CHANGED
@@ -1,69 +1,112 @@
1
- # 🛡️ runtypex
2
-
3
- Runtime type guards compiled from your TypeScript types.
4
- No schemas. No decorators. Just types blazing-fast runtime checks.
5
-
6
-
7
- ## Use
8
- ```ts
9
- import { makeValidate, makeAssert } from "runtypex";
10
-
11
- interface User { id: number; name: string; active: boolean; }
12
-
13
- const isUser = makeValidate<User>();
14
- const assertUser: ReturnType<typeof makeAssert<User>> = makeAssert<User>();
15
-
16
- isUser({ id: 1, name: "Lux", active: true }); // true
17
- assertUser({ id: "bad" }); // throws
18
- toUser({ nope: true }); // → fallback
19
- ```
20
-
21
- ## Vite
22
- ```ts
23
- // vite.config.ts
24
- import { defineConfig } from "vite";
25
- import { vitePlugin as runtypex } from "runtypex";
26
-
27
- export default defineConfig({
28
- plugins: [runtypex()],
29
- });
30
- ```
31
-
32
- To disable runtime checks in production builds, pass the option `{ removeInProd: true }` when initializing the Vite plugin.
33
- ```ts
34
- // vite.config.ts
35
- import { defineConfig } from "vite";
36
- import { vitePlugin as runtypex } from "runtypex";
37
-
38
- export default defineConfig({
39
- plugins: [runtypex({ removeInProd: true })],
40
- });
41
- ```
42
-
43
- ## Webpack (ts-loader)
44
- ```js
45
- // webpack.config.js
46
- const { tsTransformer } = require("runtypex/dist/ts-transformer.js");
47
-
48
- module.exports = {
49
- module: {
50
- rules: [
51
- {
52
- test: /\.tsx?$/,
53
- loader: "ts-loader",
54
- options: {
55
- getCustomTransformers: (program) => ({
56
- before: [ tsTransformer({ program }) ]
57
- })
58
- }
59
- }
60
- ]
61
- }
62
- }
63
- ```
64
-
65
- ## Why runtypex?
66
- - **Fast**: compiled checks, no runtime schema walk
67
- - 🧩 **Simple**: types only, no schema duplication
68
- - 🧱 **Flexible**: Vite or Webpack
69
- - 🛠️ **APIs**: `makeValidate`, `makeAssert`
1
+ # runtypex
2
+
3
+ `runtypex` generates runtime validation and mapping code from TypeScript types.
4
+ It keeps TypeScript types as the source of truth, so you do not need to maintain
5
+ a separate schema just to validate data at runtime.
6
+
7
+ ## What It Solves
8
+
9
+ TypeScript types disappear after compilation. That means data from APIs,
10
+ databases, files, or external modules can still be invalid at runtime even when
11
+ the consuming code is type-safe at build time.
12
+
13
+ `runtypex` closes that gap by using the TypeScript compiler API to generate
14
+ runtime guards and mappers during build.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm i runtypex
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```ts
25
+ import { makeAssert, makeValidate } from "runtypex";
26
+
27
+ interface User {
28
+ id: number;
29
+ name: string;
30
+ active: boolean;
31
+ }
32
+
33
+ const isUser = makeValidate<User>();
34
+ const assertUser = makeAssert<User>();
35
+
36
+ isUser({ id: 1, name: "Lux", active: true }); // true
37
+ assertUser({ id: "bad" }); // throws
38
+ ```
39
+
40
+ ## Vite Setup
41
+
42
+ ```ts
43
+ // vite.config.ts
44
+ import { defineConfig } from "vite";
45
+ import { vitePlugin as runtypex } from "runtypex";
46
+
47
+ export default defineConfig({
48
+ plugins: [runtypex()],
49
+ });
50
+ ```
51
+
52
+ To replace validators with no-op functions in production builds:
53
+
54
+ ```ts
55
+ export default defineConfig({
56
+ plugins: [runtypex({ removeInProd: true })],
57
+ });
58
+ ```
59
+
60
+ ## Feature Docs
61
+
62
+ | Feature | Description |
63
+ | --- | --- |
64
+ | [Runtime validation](docs/runtime-validation.md) | Generate `makeValidate<T>()` and `makeAssert<T>()` implementations from TypeScript types. |
65
+ | [Mapper](docs/mapper.md) | Convert DTO shapes into domain shapes with typed mapping specs. |
66
+ | [Mapping policy](docs/mapping-policy.md) | Keep DTO path to domain field names consistent across multiple mappers. |
67
+ | [JSDoc generation](docs/jsdoc-generation.md) | Generate field documentation from mapper metadata. |
68
+ | [Build integrations](docs/build-integrations.md) | Configure Vite, ts-loader, ESM exports, and build behavior. |
69
+
70
+ ## Mapper Example
71
+
72
+ ```ts
73
+ import { defineMap, makeMapper, source, transform } from "runtypex/mapper";
74
+
75
+ interface UserDto {
76
+ user_id: string;
77
+ profile: { name: string };
78
+ status: "ACTIVE" | "INACTIVE";
79
+ }
80
+
81
+ interface User {
82
+ id: string;
83
+ displayName: string;
84
+ isActive: boolean;
85
+ }
86
+
87
+ const userMap = defineMap<UserDto, User>()({
88
+ id: source("user_id", {
89
+ db: "users.user_id",
90
+ description: "User id",
91
+ dtoDescription: "User identifier from the user DTO.",
92
+ }),
93
+ displayName: source("profile.name"),
94
+ isActive: transform("status", (value) => value === "ACTIVE"),
95
+ });
96
+
97
+ const toUser = makeMapper<UserDto, User>(userMap);
98
+ ```
99
+
100
+ ## Why runtypex?
101
+
102
+ | Goal | How runtypex handles it |
103
+ | --- | --- |
104
+ | Avoid schema duplication | Runtime code is generated from TypeScript types. |
105
+ | Validate external data | Generated guards check values after compilation. |
106
+ | Keep DTO and domain mapping explicit | Mapper specs make field movement visible and typed. |
107
+ | Reduce runtime overhead | Build-time generation avoids dynamic schema parsing. |
108
+
109
+ ## Demo
110
+
111
+ [runtypex-demo](https://github.com/KumJungMin/runtypex-demo) shows TypeScript
112
+ types being transformed into runtime guards during build.
@@ -0,0 +1,6 @@
1
+ import ts from "typescript";
2
+ import type { GenContext } from "./index.js";
3
+ /**
4
+ * Handles array (T[]) and tuple ([A, B]) types.
5
+ */
6
+ export declare function emitArrayOrTuple(ctx: GenContext, expr: string, t: ts.Type): string | null;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitArrayOrTuple = emitArrayOrTuple;
4
+ /**
5
+ * Handles array (T[]) and tuple ([A, B]) types.
6
+ */
7
+ function emitArrayOrTuple(ctx, expr, t) {
8
+ if (ctx.checker.isTupleType(t)) {
9
+ return _emitTuple(t, expr, ctx);
10
+ }
11
+ if (ctx.checker.isArrayType(t)) {
12
+ return _emitArray(ctx, expr, t);
13
+ }
14
+ return null;
15
+ }
16
+ /**
17
+ * Generate validation for Array<T>
18
+ */
19
+ function _emitArray(ctx, expr, t) {
20
+ const arrayCheck = `Array.isArray(${expr})`;
21
+ // Try extracting element type
22
+ const element = ctx.checker.getElementTypeOfArrayType?.(t) ||
23
+ t.typeArguments?.[0] ||
24
+ t.getNumberIndexType?.();
25
+ if (!element)
26
+ return arrayCheck;
27
+ const eachCheck = `${expr}.every(e=>${ctx.emit("e", element)})`;
28
+ return `(${arrayCheck}&&${eachCheck})`;
29
+ }
30
+ /**
31
+ * Generate validation for Tuple [A, B, ...]
32
+ */
33
+ function _emitTuple(ref, expr, ctx) {
34
+ const elements = ref.typeArguments ?? ctx.checker.getTypeArguments?.(ref) ?? [];
35
+ const arrayCheck = `Array.isArray(${expr})`;
36
+ const lenCheck = `${expr}.length===${elements.length}`;
37
+ const elementChecks = elements.map((el, i) => ctx.emit(`${expr}[${i}]`, el));
38
+ const parts = [arrayCheck, lenCheck, ...elementChecks];
39
+ return `(${parts.join("&&")})`;
40
+ }
@@ -0,0 +1,4 @@
1
+ import ts from "typescript";
2
+ import type { GenContext } from "./index.js";
3
+ /** Handles literal types and enum-like types. */
4
+ export declare function emitLiteralOrEnum(_: GenContext, expr: string, t: ts.Type): string | null;
@@ -0,0 +1,47 @@
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.emitLiteralOrEnum = emitLiteralOrEnum;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ /** Handles literal types and enum-like types. */
9
+ function emitLiteralOrEnum(_, expr, t) {
10
+ if (t.isLiteral()) {
11
+ const value = t.value;
12
+ const isString = typeof value === "string";
13
+ const newValue = isString ? JSON.stringify(value) : String(value);
14
+ return `${expr}===${newValue}`;
15
+ }
16
+ const isEnum = t.flags & typescript_1.default.TypeFlags.EnumLike;
17
+ if (isEnum) {
18
+ const enumValues = _extractEnumValues(t);
19
+ if (enumValues.length) {
20
+ return `(${enumValues.map(v => `${expr}===${v}`).join("||")})`;
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ // Extracts numeric or string values from an Enum declaration.
26
+ function _extractEnumValues(t) {
27
+ const symbol = t.getSymbol();
28
+ if (!symbol)
29
+ return [];
30
+ const values = [];
31
+ const declarations = symbol.getDeclarations() ?? [];
32
+ for (const declaration of declarations) {
33
+ const isEnum = typescript_1.default.isEnumDeclaration(declaration);
34
+ if (!isEnum)
35
+ continue;
36
+ for (const member of declaration.members) {
37
+ const init = member.initializer;
38
+ if (!init)
39
+ continue;
40
+ if (typescript_1.default.isStringLiteral(init) || typescript_1.default.isNumericLiteral(init)) {
41
+ const value = typescript_1.default.isStringLiteral(init) ? JSON.stringify(init.text) : init.text;
42
+ values.push(value);
43
+ }
44
+ }
45
+ }
46
+ return values;
47
+ }
@@ -0,0 +1,32 @@
1
+ import ts from "typescript";
2
+ export type MapperEmitOptions = {
3
+ validateDto?: boolean;
4
+ validateDomain?: boolean;
5
+ mappingPolicy?: ts.Expression;
6
+ policyMode?: "warn" | "error";
7
+ };
8
+ export type MapRuleInfo = {
9
+ key: string;
10
+ from: string;
11
+ db?: string;
12
+ description?: string;
13
+ dtoDescription?: string;
14
+ };
15
+ export declare function emitMapperFromSpec(params: {
16
+ checker: ts.TypeChecker;
17
+ dtoType: ts.Type;
18
+ domainType: ts.Type;
19
+ specNode: ts.Expression;
20
+ sourceFile: ts.SourceFile;
21
+ options?: MapperEmitOptions;
22
+ }): string | null;
23
+ export declare function readMapRules(checker: ts.TypeChecker, specNode: ts.Expression): Map<string, MapRuleInfo>;
24
+ export type MapPolicyViolation = {
25
+ from: string;
26
+ expectedKey: string;
27
+ actualKey: string;
28
+ };
29
+ export declare function findMapPolicyViolations(checker: ts.TypeChecker, specNode: ts.Expression, policyNode: ts.Expression | undefined): MapPolicyViolation[];
30
+ export declare function handleMapPolicyViolations(violations: MapPolicyViolation[], mode: "warn" | "error"): void;
31
+ /** Finds the mapping object behind inline, defineMap-wrapped, or identifier specs. */
32
+ export declare function resolveMapSpecObject(checker: ts.TypeChecker, node: ts.Expression): ts.ObjectLiteralExpression | null;
@@ -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,6 @@
1
+ import ts from "typescript";
2
+ import type { GenContext } from "./index.js";
3
+ /**
4
+ * Handles interfaces, classes, and object-like structures.
5
+ */
6
+ export declare function emitObject(ctx: GenContext, expr: string, t: ts.Type): string | null;
@@ -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,6 @@
1
+ import ts from "typescript";
2
+ import type { GenContext } from "./index.js";
3
+ /**
4
+ * Handles primitive types like number, string, boolean...
5
+ */
6
+ export declare function emitPrimitive(ctx: GenContext, expr: string, t: ts.Type): string | null;
@@ -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,6 @@
1
+ import ts from "typescript";
2
+ import type { GenContext } from "./index.js";
3
+ /**
4
+ * Handles union (A | B) and intersection (A & B) types.
5
+ */
6
+ export declare function emitUnionOrIntersection(ctx: GenContext, expr: string, t: ts.Type): string | null;
@@ -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
+ }