@typokit/transform-typia 0.1.4

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.
@@ -0,0 +1,22 @@
1
+ import type { TypeMetadata } from "@typokit/types";
2
+ /**
3
+ * Error thrown when validator generation fails for a type.
4
+ */
5
+ export declare class ValidatorGenerationError extends Error {
6
+ readonly typeName: string;
7
+ readonly reason: string;
8
+ constructor(typeName: string, reason: string);
9
+ }
10
+ /**
11
+ * Generate a runtime validator function as a code string from type metadata.
12
+ * The generated function validates input against the type's shape and returns
13
+ * `{ success: true, data }` or `{ success: false, errors }`.
14
+ */
15
+ export declare function generateValidator(typeMetadata: TypeMetadata): string;
16
+ /**
17
+ * Generate validators for multiple types in batch.
18
+ * Types can cross-reference each other for nested validation.
19
+ * Returns Map<typeName, generatedCode>.
20
+ */
21
+ export declare function generateValidatorBatch(types: TypeMetadata[]): Map<string, string>;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAEnB,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAM7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CASpE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EAAE,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBrB"}
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ // @typokit/transform-typia — Typia Validation Bridge
2
+ //
3
+ // Generates runtime validation code from type metadata.
4
+ // Typia is installed as a dependency for transformer integration;
5
+ // this module bridges Rust-extracted TypeMetadata to generated validators.
6
+ /**
7
+ * Error thrown when validator generation fails for a type.
8
+ */
9
+ export class ValidatorGenerationError extends Error {
10
+ typeName;
11
+ reason;
12
+ constructor(typeName, reason) {
13
+ super(`Failed to generate validator for type "${typeName}": ${reason}`);
14
+ this.typeName = typeName;
15
+ this.reason = reason;
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ }
19
+ /**
20
+ * Generate a runtime validator function as a code string from type metadata.
21
+ * The generated function validates input against the type's shape and returns
22
+ * `{ success: true, data }` or `{ success: false, errors }`.
23
+ */
24
+ export function generateValidator(typeMetadata) {
25
+ try {
26
+ assertValidMetadata(typeMetadata);
27
+ return buildValidatorCode(typeMetadata);
28
+ }
29
+ catch (err) {
30
+ if (err instanceof ValidatorGenerationError)
31
+ throw err;
32
+ const reason = err instanceof Error ? err.message : String(err);
33
+ throw new ValidatorGenerationError(typeMetadata.name, reason);
34
+ }
35
+ }
36
+ /**
37
+ * Generate validators for multiple types in batch.
38
+ * Types can cross-reference each other for nested validation.
39
+ * Returns Map<typeName, generatedCode>.
40
+ */
41
+ export function generateValidatorBatch(types) {
42
+ const result = new Map();
43
+ const registry = new Map();
44
+ for (const t of types) {
45
+ registry.set(t.name, t);
46
+ }
47
+ for (const metadata of types) {
48
+ try {
49
+ assertValidMetadata(metadata);
50
+ const code = buildValidatorCode(metadata, registry);
51
+ result.set(metadata.name, code);
52
+ }
53
+ catch (err) {
54
+ if (err instanceof ValidatorGenerationError)
55
+ throw err;
56
+ const reason = err instanceof Error ? err.message : String(err);
57
+ throw new ValidatorGenerationError(metadata.name, reason);
58
+ }
59
+ }
60
+ return result;
61
+ }
62
+ // ── Internal helpers ─────────────────────────────────────────────
63
+ function assertValidMetadata(metadata) {
64
+ if (!metadata.name || typeof metadata.name !== "string") {
65
+ throw new ValidatorGenerationError(metadata.name ?? "<unnamed>", "TypeMetadata must have a non-empty string name");
66
+ }
67
+ if (!metadata.properties || typeof metadata.properties !== "object") {
68
+ throw new ValidatorGenerationError(metadata.name, "TypeMetadata must have a properties object");
69
+ }
70
+ }
71
+ function buildValidatorCode(metadata, registry) {
72
+ const fnName = `validate${metadata.name}`;
73
+ const propEntries = Object.entries(metadata.properties);
74
+ const lines = [];
75
+ lines.push(`function ${fnName}(input) {`);
76
+ lines.push(` if (typeof input !== "object" || input === null) {`);
77
+ lines.push(` return { success: false, errors: [{ path: "$input", expected: "object", actual: input === null ? "null" : typeof input }] };`);
78
+ lines.push(` }`);
79
+ if (propEntries.length === 0) {
80
+ lines.push(` return { success: true, data: input };`);
81
+ lines.push(`}`);
82
+ return lines.join("\n");
83
+ }
84
+ lines.push(` var errors = [];`);
85
+ for (const [propName, propDef] of propEntries) {
86
+ const accessor = `input[${JSON.stringify(propName)}]`;
87
+ const typeCheck = generateTypeCheck(accessor, propDef.type, registry);
88
+ if (propDef.optional) {
89
+ lines.push(` if (${accessor} !== undefined) {`);
90
+ lines.push(` if (!(${typeCheck})) {`);
91
+ lines.push(` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: typeof ${accessor} });`);
92
+ lines.push(` }`);
93
+ lines.push(` }`);
94
+ }
95
+ else {
96
+ lines.push(` if (${accessor} === undefined) {`);
97
+ lines.push(` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: "undefined" });`);
98
+ lines.push(` } else if (!(${typeCheck})) {`);
99
+ lines.push(` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: typeof ${accessor} });`);
100
+ lines.push(` }`);
101
+ }
102
+ }
103
+ lines.push(` return errors.length === 0 ? { success: true, data: input } : { success: false, errors: errors };`);
104
+ lines.push(`}`);
105
+ return lines.join("\n");
106
+ }
107
+ function generateTypeCheck(expr, typeStr, registry) {
108
+ typeStr = typeStr.trim();
109
+ // Union types: "string | number"
110
+ const unionParts = splitUnionType(typeStr);
111
+ if (unionParts.length > 1) {
112
+ const checks = unionParts.map((p) => generateTypeCheck(expr, p.trim(), registry));
113
+ return `(${checks.join(" || ")})`;
114
+ }
115
+ // Array shorthand: "string[]"
116
+ if (typeStr.endsWith("[]")) {
117
+ const elementType = typeStr.slice(0, -2).trim();
118
+ return `(Array.isArray(${expr}) && ${expr}.every(function(item) { return ${generateTypeCheck("item", elementType, registry)}; }))`;
119
+ }
120
+ // Array generic: "Array<string>"
121
+ const arrayGenericMatch = typeStr.match(/^Array<(.+)>$/);
122
+ if (arrayGenericMatch) {
123
+ const elementType = arrayGenericMatch[1].trim();
124
+ return `(Array.isArray(${expr}) && ${expr}.every(function(item) { return ${generateTypeCheck("item", elementType, registry)}; }))`;
125
+ }
126
+ // Record<K, V>
127
+ const recordMatch = typeStr.match(/^Record<(.+),\s*(.+)>$/);
128
+ if (recordMatch) {
129
+ const valueType = recordMatch[2].trim();
130
+ return `(typeof ${expr} === "object" && ${expr} !== null && !Array.isArray(${expr}) && Object.values(${expr}).every(function(v) { return ${generateTypeCheck("v", valueType, registry)}; }))`;
131
+ }
132
+ // Template literal types: `prefix_${string}`
133
+ if (typeStr.startsWith("`") && typeStr.endsWith("`")) {
134
+ const pattern = buildTemplateLiteralRegex(typeStr.slice(1, -1));
135
+ return `(typeof ${expr} === "string" && /^${pattern}$/.test(${expr}))`;
136
+ }
137
+ // Primitive types
138
+ switch (typeStr) {
139
+ case "string":
140
+ return `typeof ${expr} === "string"`;
141
+ case "number":
142
+ return `(typeof ${expr} === "number" && !isNaN(${expr}))`;
143
+ case "boolean":
144
+ return `typeof ${expr} === "boolean"`;
145
+ case "null":
146
+ return `${expr} === null`;
147
+ case "undefined":
148
+ case "void":
149
+ return `${expr} === undefined`;
150
+ case "any":
151
+ case "unknown":
152
+ return `true`;
153
+ case "never":
154
+ return `false`;
155
+ case "bigint":
156
+ return `typeof ${expr} === "bigint"`;
157
+ case "symbol":
158
+ return `typeof ${expr} === "symbol"`;
159
+ case "object":
160
+ return `(typeof ${expr} === "object" && ${expr} !== null)`;
161
+ case "Date":
162
+ return `(${expr} instanceof Date)`;
163
+ default:
164
+ // Reference type or unknown — validate as non-null object
165
+ return `(typeof ${expr} === "object" && ${expr} !== null)`;
166
+ }
167
+ }
168
+ /**
169
+ * Split a type string on top-level `|` characters,
170
+ * respecting generics `<>`, parens `()`, braces `{}`, and backtick literals.
171
+ */
172
+ function splitUnionType(typeStr) {
173
+ const parts = [];
174
+ let current = "";
175
+ let depth = 0;
176
+ let inBacktick = false;
177
+ for (let i = 0; i < typeStr.length; i++) {
178
+ const ch = typeStr[i];
179
+ if (ch === "`") {
180
+ inBacktick = !inBacktick;
181
+ }
182
+ if (!inBacktick) {
183
+ if (ch === "<" || ch === "(" || ch === "{")
184
+ depth++;
185
+ if (ch === ">" || ch === ")" || ch === "}")
186
+ depth--;
187
+ if (ch === "|" && depth === 0) {
188
+ parts.push(current.trim());
189
+ current = "";
190
+ continue;
191
+ }
192
+ }
193
+ current += ch;
194
+ }
195
+ if (current.trim()) {
196
+ parts.push(current.trim());
197
+ }
198
+ return parts;
199
+ }
200
+ /**
201
+ * Convert a template literal body (without surrounding backticks)
202
+ * into a JavaScript RegExp pattern string.
203
+ * Handles `${string}`, `${number}`, and literal characters.
204
+ */
205
+ function buildTemplateLiteralRegex(inner) {
206
+ let pattern = "";
207
+ let i = 0;
208
+ while (i < inner.length) {
209
+ if (inner[i] === "$" && inner[i + 1] === "{") {
210
+ const end = inner.indexOf("}", i + 2);
211
+ if (end !== -1) {
212
+ const placeholder = inner.slice(i + 2, end);
213
+ switch (placeholder) {
214
+ case "string":
215
+ pattern += ".*";
216
+ break;
217
+ case "number":
218
+ pattern += "[0-9]+(?:\\.[0-9]+)?";
219
+ break;
220
+ default:
221
+ pattern += ".*";
222
+ break;
223
+ }
224
+ i = end + 1;
225
+ continue;
226
+ }
227
+ }
228
+ // Escape regex-special characters
229
+ if (/[.*+?^${}()|[\]\\]/.test(inner[i])) {
230
+ pattern += "\\" + inner[i];
231
+ }
232
+ else {
233
+ pattern += inner[i];
234
+ }
235
+ i++;
236
+ }
237
+ return pattern;
238
+ }
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@typokit/transform-typia",
3
+ "exports": {
4
+ ".": {
5
+ "import": "./dist/index.js",
6
+ "types": "./dist/index.d.ts"
7
+ }
8
+ },
9
+ "version": "0.1.4",
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "dependencies": {
18
+ "typia": "11.0.3",
19
+ "@typokit/types": "0.1.4"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/KyleBastien/typokit",
24
+ "directory": "packages/transform-typia"
25
+ },
26
+ "scripts": {
27
+ "test": "rstest run --passWithNoTests"
28
+ }
29
+ }
@@ -0,0 +1,287 @@
1
+ import { describe, it, expect } from "@rstest/core";
2
+ import {
3
+ generateValidator,
4
+ generateValidatorBatch,
5
+ ValidatorGenerationError,
6
+ } from "./index.js";
7
+
8
+ import type { TypeMetadata } from "@typokit/types";
9
+
10
+ // Helper: evaluate generated validator code and run it against input
11
+ function run(code: string, fnName: string, input: unknown): unknown {
12
+ const fn = new Function("input", code + `\nreturn ${fnName}(input);`);
13
+ return fn(input);
14
+ }
15
+
16
+ describe("generateValidator", () => {
17
+ it("generates validator for simple types", () => {
18
+ const metadata: TypeMetadata = {
19
+ name: "User",
20
+ properties: {
21
+ name: { type: "string", optional: false },
22
+ age: { type: "number", optional: false },
23
+ active: { type: "boolean", optional: false },
24
+ },
25
+ };
26
+
27
+ const code = generateValidator(metadata);
28
+ expect(code).toContain("function validateUser(input)");
29
+
30
+ const valid = run(code, "validateUser", {
31
+ name: "John",
32
+ age: 30,
33
+ active: true,
34
+ }) as { success: boolean };
35
+ expect(valid.success).toBe(true);
36
+
37
+ const invalid = run(code, "validateUser", {
38
+ name: 123,
39
+ age: "thirty",
40
+ active: "yes",
41
+ }) as { success: boolean; errors: unknown[] };
42
+ expect(invalid.success).toBe(false);
43
+ expect(invalid.errors.length).toBe(3);
44
+ });
45
+
46
+ it("generates validator for union types", () => {
47
+ const metadata: TypeMetadata = {
48
+ name: "FlexId",
49
+ properties: {
50
+ id: { type: "string | number", optional: false },
51
+ },
52
+ };
53
+
54
+ const code = generateValidator(metadata);
55
+ const check = (v: unknown) =>
56
+ run(code, "validateFlexId", v) as { success: boolean };
57
+
58
+ expect(check({ id: "abc" }).success).toBe(true);
59
+ expect(check({ id: 42 }).success).toBe(true);
60
+ expect(check({ id: true }).success).toBe(false);
61
+ });
62
+
63
+ it("generates validator for nested object types", () => {
64
+ const metadata: TypeMetadata = {
65
+ name: "Profile",
66
+ properties: {
67
+ address: { type: "Address", optional: false },
68
+ },
69
+ };
70
+
71
+ const code = generateValidator(metadata);
72
+ const check = (v: unknown) =>
73
+ run(code, "validateProfile", v) as { success: boolean };
74
+
75
+ expect(check({ address: { street: "123 Main" } }).success).toBe(true);
76
+ expect(check({ address: null }).success).toBe(false);
77
+ expect(check({ address: "not object" }).success).toBe(false);
78
+ });
79
+
80
+ it("generates validator for array types", () => {
81
+ const metadata: TypeMetadata = {
82
+ name: "TagList",
83
+ properties: {
84
+ tags: { type: "string[]", optional: false },
85
+ scores: { type: "number[]", optional: false },
86
+ },
87
+ };
88
+
89
+ const code = generateValidator(metadata);
90
+ const check = (v: unknown) =>
91
+ run(code, "validateTagList", v) as { success: boolean };
92
+
93
+ expect(check({ tags: ["a", "b"], scores: [1, 2] }).success).toBe(true);
94
+ expect(check({ tags: [1, 2], scores: [1, 2] }).success).toBe(false);
95
+ expect(check({ tags: "not array", scores: [1] }).success).toBe(false);
96
+ });
97
+
98
+ it("generates validator for optional fields", () => {
99
+ const metadata: TypeMetadata = {
100
+ name: "UpdateUser",
101
+ properties: {
102
+ name: { type: "string", optional: true },
103
+ age: { type: "number", optional: true },
104
+ email: { type: "string", optional: false },
105
+ },
106
+ };
107
+
108
+ const code = generateValidator(metadata);
109
+ const check = (v: unknown) =>
110
+ run(code, "validateUpdateUser", v) as { success: boolean };
111
+
112
+ expect(check({ name: "John", age: 30, email: "j@e.com" }).success).toBe(
113
+ true,
114
+ );
115
+ expect(check({ email: "j@e.com" }).success).toBe(true);
116
+ expect(check({ name: "John" }).success).toBe(false);
117
+ expect(check({ email: "j@e.com", name: 123 }).success).toBe(false);
118
+ });
119
+
120
+ it("generates validator for empty properties", () => {
121
+ const metadata: TypeMetadata = {
122
+ name: "Empty",
123
+ properties: {},
124
+ };
125
+
126
+ const code = generateValidator(metadata);
127
+ const check = (v: unknown) =>
128
+ run(code, "validateEmpty", v) as { success: boolean };
129
+
130
+ expect(check({}).success).toBe(true);
131
+ expect(check(null).success).toBe(false);
132
+ });
133
+
134
+ it("rejects non-object input in generated validators", () => {
135
+ const metadata: TypeMetadata = {
136
+ name: "Test",
137
+ properties: { x: { type: "string", optional: false } },
138
+ };
139
+
140
+ const code = generateValidator(metadata);
141
+ const check = (v: unknown) =>
142
+ run(code, "validateTest", v) as { success: boolean };
143
+
144
+ expect(check(null).success).toBe(false);
145
+ expect(check(undefined).success).toBe(false);
146
+ expect(check("string").success).toBe(false);
147
+ expect(check(42).success).toBe(false);
148
+ });
149
+
150
+ it("generates validator for nullable union types", () => {
151
+ const metadata: TypeMetadata = {
152
+ name: "Nullable",
153
+ properties: {
154
+ value: { type: "string | null", optional: false },
155
+ },
156
+ };
157
+
158
+ const code = generateValidator(metadata);
159
+ const check = (v: unknown) =>
160
+ run(code, "validateNullable", v) as { success: boolean };
161
+
162
+ expect(check({ value: "hello" }).success).toBe(true);
163
+ expect(check({ value: null }).success).toBe(true);
164
+ expect(check({ value: 42 }).success).toBe(false);
165
+ });
166
+
167
+ it("generates validator for template literal types", () => {
168
+ const metadata: TypeMetadata = {
169
+ name: "EventName",
170
+ properties: {
171
+ event: { type: "`on_${string}`", optional: false },
172
+ },
173
+ };
174
+
175
+ const code = generateValidator(metadata);
176
+ const check = (v: unknown) =>
177
+ run(code, "validateEventName", v) as { success: boolean };
178
+
179
+ expect(check({ event: "on_click" }).success).toBe(true);
180
+ expect(check({ event: "on_" }).success).toBe(true);
181
+ expect(check({ event: "off_click" }).success).toBe(false);
182
+ expect(check({ event: 42 }).success).toBe(false);
183
+ });
184
+
185
+ it("generates validator for recursive types", () => {
186
+ const metadata: TypeMetadata = {
187
+ name: "TreeNode",
188
+ properties: {
189
+ value: { type: "number", optional: false },
190
+ children: { type: "TreeNode[]", optional: true },
191
+ },
192
+ };
193
+
194
+ const code = generateValidator(metadata);
195
+ expect(code).toContain("function validateTreeNode");
196
+ expect(code).toContain("Array.isArray");
197
+
198
+ const check = (v: unknown) =>
199
+ run(code, "validateTreeNode", v) as { success: boolean };
200
+ expect(check({ value: 1 }).success).toBe(true);
201
+ expect(check({ value: 1, children: [{ value: 2 }] }).success).toBe(true);
202
+ expect(check({ value: "not a number" }).success).toBe(false);
203
+ });
204
+
205
+ it("throws ValidatorGenerationError on empty name", () => {
206
+ const metadata = { name: "", properties: {} } as TypeMetadata;
207
+ expect(() => generateValidator(metadata)).toThrow(ValidatorGenerationError);
208
+ });
209
+
210
+ it("error includes type name and reason", () => {
211
+ try {
212
+ generateValidator({ name: "", properties: {} });
213
+ expect(true).toBe(false); // should not reach here
214
+ } catch (err) {
215
+ expect(err).toBeInstanceOf(ValidatorGenerationError);
216
+ const vge = err as ValidatorGenerationError;
217
+ expect(vge.reason).toContain("non-empty");
218
+ }
219
+ });
220
+ });
221
+
222
+ describe("generateValidatorBatch", () => {
223
+ it("generates validators for multiple types", () => {
224
+ const types: TypeMetadata[] = [
225
+ {
226
+ name: "User",
227
+ properties: {
228
+ name: { type: "string", optional: false },
229
+ age: { type: "number", optional: false },
230
+ },
231
+ },
232
+ {
233
+ name: "Post",
234
+ properties: {
235
+ title: { type: "string", optional: false },
236
+ author: { type: "User", optional: false },
237
+ },
238
+ },
239
+ ];
240
+
241
+ const result = generateValidatorBatch(types);
242
+ expect(result.size).toBe(2);
243
+ expect(result.has("User")).toBe(true);
244
+ expect(result.has("Post")).toBe(true);
245
+ expect(result.get("User")).toContain("function validateUser");
246
+ expect(result.get("Post")).toContain("function validatePost");
247
+ });
248
+
249
+ it("cross-references types in the batch", () => {
250
+ const types: TypeMetadata[] = [
251
+ {
252
+ name: "Address",
253
+ properties: {
254
+ street: { type: "string", optional: false },
255
+ },
256
+ },
257
+ {
258
+ name: "Person",
259
+ properties: {
260
+ name: { type: "string", optional: false },
261
+ address: { type: "Address", optional: false },
262
+ },
263
+ },
264
+ ];
265
+
266
+ const result = generateValidatorBatch(types);
267
+ const personCode = result.get("Person")!;
268
+ const check = (v: unknown) =>
269
+ run(personCode, "validatePerson", v) as { success: boolean };
270
+
271
+ expect(check({ name: "Jane", address: { street: "Elm" } }).success).toBe(
272
+ true,
273
+ );
274
+ expect(check({ name: "Jane", address: null }).success).toBe(false);
275
+ });
276
+
277
+ it("throws ValidatorGenerationError for invalid type in batch", () => {
278
+ const types: TypeMetadata[] = [
279
+ { name: "Good", properties: { x: { type: "string", optional: false } } },
280
+ { name: "", properties: {} },
281
+ ];
282
+
283
+ expect(() => generateValidatorBatch(types)).toThrow(
284
+ ValidatorGenerationError,
285
+ );
286
+ });
287
+ });
package/src/index.ts ADDED
@@ -0,0 +1,284 @@
1
+ // @typokit/transform-typia — Typia Validation Bridge
2
+ //
3
+ // Generates runtime validation code from type metadata.
4
+ // Typia is installed as a dependency for transformer integration;
5
+ // this module bridges Rust-extracted TypeMetadata to generated validators.
6
+
7
+ import type { TypeMetadata } from "@typokit/types";
8
+
9
+ /**
10
+ * Error thrown when validator generation fails for a type.
11
+ */
12
+ export class ValidatorGenerationError extends Error {
13
+ public readonly typeName: string;
14
+ public readonly reason: string;
15
+
16
+ constructor(typeName: string, reason: string) {
17
+ super(`Failed to generate validator for type "${typeName}": ${reason}`);
18
+ this.typeName = typeName;
19
+ this.reason = reason;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Generate a runtime validator function as a code string from type metadata.
26
+ * The generated function validates input against the type's shape and returns
27
+ * `{ success: true, data }` or `{ success: false, errors }`.
28
+ */
29
+ export function generateValidator(typeMetadata: TypeMetadata): string {
30
+ try {
31
+ assertValidMetadata(typeMetadata);
32
+ return buildValidatorCode(typeMetadata);
33
+ } catch (err) {
34
+ if (err instanceof ValidatorGenerationError) throw err;
35
+ const reason = err instanceof Error ? err.message : String(err);
36
+ throw new ValidatorGenerationError(typeMetadata.name, reason);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Generate validators for multiple types in batch.
42
+ * Types can cross-reference each other for nested validation.
43
+ * Returns Map<typeName, generatedCode>.
44
+ */
45
+ export function generateValidatorBatch(
46
+ types: TypeMetadata[],
47
+ ): Map<string, string> {
48
+ const result = new Map<string, string>();
49
+ const registry = new Map<string, TypeMetadata>();
50
+ for (const t of types) {
51
+ registry.set(t.name, t);
52
+ }
53
+
54
+ for (const metadata of types) {
55
+ try {
56
+ assertValidMetadata(metadata);
57
+ const code = buildValidatorCode(metadata, registry);
58
+ result.set(metadata.name, code);
59
+ } catch (err) {
60
+ if (err instanceof ValidatorGenerationError) throw err;
61
+ const reason = err instanceof Error ? err.message : String(err);
62
+ throw new ValidatorGenerationError(metadata.name, reason);
63
+ }
64
+ }
65
+
66
+ return result;
67
+ }
68
+
69
+ // ── Internal helpers ─────────────────────────────────────────────
70
+
71
+ function assertValidMetadata(metadata: TypeMetadata): void {
72
+ if (!metadata.name || typeof metadata.name !== "string") {
73
+ throw new ValidatorGenerationError(
74
+ metadata.name ?? "<unnamed>",
75
+ "TypeMetadata must have a non-empty string name",
76
+ );
77
+ }
78
+ if (!metadata.properties || typeof metadata.properties !== "object") {
79
+ throw new ValidatorGenerationError(
80
+ metadata.name,
81
+ "TypeMetadata must have a properties object",
82
+ );
83
+ }
84
+ }
85
+
86
+ function buildValidatorCode(
87
+ metadata: TypeMetadata,
88
+ registry?: Map<string, TypeMetadata>,
89
+ ): string {
90
+ const fnName = `validate${metadata.name}`;
91
+ const propEntries = Object.entries(metadata.properties);
92
+ const lines: string[] = [];
93
+
94
+ lines.push(`function ${fnName}(input) {`);
95
+ lines.push(` if (typeof input !== "object" || input === null) {`);
96
+ lines.push(
97
+ ` return { success: false, errors: [{ path: "$input", expected: "object", actual: input === null ? "null" : typeof input }] };`,
98
+ );
99
+ lines.push(` }`);
100
+
101
+ if (propEntries.length === 0) {
102
+ lines.push(` return { success: true, data: input };`);
103
+ lines.push(`}`);
104
+ return lines.join("\n");
105
+ }
106
+
107
+ lines.push(` var errors = [];`);
108
+
109
+ for (const [propName, propDef] of propEntries) {
110
+ const accessor = `input[${JSON.stringify(propName)}]`;
111
+ const typeCheck = generateTypeCheck(accessor, propDef.type, registry);
112
+
113
+ if (propDef.optional) {
114
+ lines.push(` if (${accessor} !== undefined) {`);
115
+ lines.push(` if (!(${typeCheck})) {`);
116
+ lines.push(
117
+ ` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: typeof ${accessor} });`,
118
+ );
119
+ lines.push(` }`);
120
+ lines.push(` }`);
121
+ } else {
122
+ lines.push(` if (${accessor} === undefined) {`);
123
+ lines.push(
124
+ ` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: "undefined" });`,
125
+ );
126
+ lines.push(` } else if (!(${typeCheck})) {`);
127
+ lines.push(
128
+ ` errors.push({ path: ${JSON.stringify(propName)}, expected: ${JSON.stringify(propDef.type)}, actual: typeof ${accessor} });`,
129
+ );
130
+ lines.push(` }`);
131
+ }
132
+ }
133
+
134
+ lines.push(
135
+ ` return errors.length === 0 ? { success: true, data: input } : { success: false, errors: errors };`,
136
+ );
137
+ lines.push(`}`);
138
+ return lines.join("\n");
139
+ }
140
+
141
+ function generateTypeCheck(
142
+ expr: string,
143
+ typeStr: string,
144
+ registry?: Map<string, TypeMetadata>,
145
+ ): string {
146
+ typeStr = typeStr.trim();
147
+
148
+ // Union types: "string | number"
149
+ const unionParts = splitUnionType(typeStr);
150
+ if (unionParts.length > 1) {
151
+ const checks = unionParts.map((p) =>
152
+ generateTypeCheck(expr, p.trim(), registry),
153
+ );
154
+ return `(${checks.join(" || ")})`;
155
+ }
156
+
157
+ // Array shorthand: "string[]"
158
+ if (typeStr.endsWith("[]")) {
159
+ const elementType = typeStr.slice(0, -2).trim();
160
+ return `(Array.isArray(${expr}) && ${expr}.every(function(item) { return ${generateTypeCheck("item", elementType, registry)}; }))`;
161
+ }
162
+
163
+ // Array generic: "Array<string>"
164
+ const arrayGenericMatch = typeStr.match(/^Array<(.+)>$/);
165
+ if (arrayGenericMatch) {
166
+ const elementType = arrayGenericMatch[1].trim();
167
+ return `(Array.isArray(${expr}) && ${expr}.every(function(item) { return ${generateTypeCheck("item", elementType, registry)}; }))`;
168
+ }
169
+
170
+ // Record<K, V>
171
+ const recordMatch = typeStr.match(/^Record<(.+),\s*(.+)>$/);
172
+ if (recordMatch) {
173
+ const valueType = recordMatch[2].trim();
174
+ return `(typeof ${expr} === "object" && ${expr} !== null && !Array.isArray(${expr}) && Object.values(${expr}).every(function(v) { return ${generateTypeCheck("v", valueType, registry)}; }))`;
175
+ }
176
+
177
+ // Template literal types: `prefix_${string}`
178
+ if (typeStr.startsWith("`") && typeStr.endsWith("`")) {
179
+ const pattern = buildTemplateLiteralRegex(typeStr.slice(1, -1));
180
+ return `(typeof ${expr} === "string" && /^${pattern}$/.test(${expr}))`;
181
+ }
182
+
183
+ // Primitive types
184
+ switch (typeStr) {
185
+ case "string":
186
+ return `typeof ${expr} === "string"`;
187
+ case "number":
188
+ return `(typeof ${expr} === "number" && !isNaN(${expr}))`;
189
+ case "boolean":
190
+ return `typeof ${expr} === "boolean"`;
191
+ case "null":
192
+ return `${expr} === null`;
193
+ case "undefined":
194
+ case "void":
195
+ return `${expr} === undefined`;
196
+ case "any":
197
+ case "unknown":
198
+ return `true`;
199
+ case "never":
200
+ return `false`;
201
+ case "bigint":
202
+ return `typeof ${expr} === "bigint"`;
203
+ case "symbol":
204
+ return `typeof ${expr} === "symbol"`;
205
+ case "object":
206
+ return `(typeof ${expr} === "object" && ${expr} !== null)`;
207
+ case "Date":
208
+ return `(${expr} instanceof Date)`;
209
+ default:
210
+ // Reference type or unknown — validate as non-null object
211
+ return `(typeof ${expr} === "object" && ${expr} !== null)`;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Split a type string on top-level `|` characters,
217
+ * respecting generics `<>`, parens `()`, braces `{}`, and backtick literals.
218
+ */
219
+ function splitUnionType(typeStr: string): string[] {
220
+ const parts: string[] = [];
221
+ let current = "";
222
+ let depth = 0;
223
+ let inBacktick = false;
224
+
225
+ for (let i = 0; i < typeStr.length; i++) {
226
+ const ch = typeStr[i];
227
+ if (ch === "`") {
228
+ inBacktick = !inBacktick;
229
+ }
230
+ if (!inBacktick) {
231
+ if (ch === "<" || ch === "(" || ch === "{") depth++;
232
+ if (ch === ">" || ch === ")" || ch === "}") depth--;
233
+ if (ch === "|" && depth === 0) {
234
+ parts.push(current.trim());
235
+ current = "";
236
+ continue;
237
+ }
238
+ }
239
+ current += ch;
240
+ }
241
+ if (current.trim()) {
242
+ parts.push(current.trim());
243
+ }
244
+ return parts;
245
+ }
246
+
247
+ /**
248
+ * Convert a template literal body (without surrounding backticks)
249
+ * into a JavaScript RegExp pattern string.
250
+ * Handles `${string}`, `${number}`, and literal characters.
251
+ */
252
+ function buildTemplateLiteralRegex(inner: string): string {
253
+ let pattern = "";
254
+ let i = 0;
255
+ while (i < inner.length) {
256
+ if (inner[i] === "$" && inner[i + 1] === "{") {
257
+ const end = inner.indexOf("}", i + 2);
258
+ if (end !== -1) {
259
+ const placeholder = inner.slice(i + 2, end);
260
+ switch (placeholder) {
261
+ case "string":
262
+ pattern += ".*";
263
+ break;
264
+ case "number":
265
+ pattern += "[0-9]+(?:\\.[0-9]+)?";
266
+ break;
267
+ default:
268
+ pattern += ".*";
269
+ break;
270
+ }
271
+ i = end + 1;
272
+ continue;
273
+ }
274
+ }
275
+ // Escape regex-special characters
276
+ if (/[.*+?^${}()|[\]\\]/.test(inner[i])) {
277
+ pattern += "\\" + inner[i];
278
+ } else {
279
+ pattern += inner[i];
280
+ }
281
+ i++;
282
+ }
283
+ return pattern;
284
+ }