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.
Files changed (72) hide show
  1. package/README.md +112 -278
  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 +33 -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 +103 -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 +72 -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 +33 -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 +97 -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 +72 -0
  61. package/dist/esm/runtime/mapper.js +71 -0
  62. package/dist/esm/transformer/index.d.ts +3 -0
  63. package/dist/esm/transformer/index.js +3 -0
  64. package/dist/esm/transformer/ts-transformer.d.ts +8 -4
  65. package/dist/esm/transformer/ts-transformer.js +51 -10
  66. package/dist/esm/transformer/vite-plugin.js +7 -32
  67. package/docs/build-integrations.md +89 -0
  68. package/docs/jsdoc-generation.md +105 -0
  69. package/docs/mapper.md +104 -0
  70. package/docs/mapping-policy.md +78 -0
  71. package/docs/runtime-validation.md +84 -0
  72. package/package.json +76 -36
package/README.md CHANGED
@@ -1,278 +1,112 @@
1
- # πŸ›‘οΈ runtypex
2
-
3
- TypeScript νƒ€μž…μœΌλ‘œλΆ€ν„° **λŸ°νƒ€μž„ νƒ€μž… κ°€λ“œ(runtime type guard)** λ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€.
4
- μŠ€ν‚€λ§ˆλ„, λ°μ½”λ ˆμ΄ν„°λ„ ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.
5
- **λΉŒλ“œ μ‹œ νƒ€μž… 정보λ₯Ό 뢄석해 μ΅œμ ν™”λœ 검증 ν•¨μˆ˜λ₯Ό μžλ™ μƒμ„±ν•˜λ©°**,
6
- νƒ€μž…λ§ŒμœΌλ‘œ β†’ **λŸ°νƒ€μž„ 검증**을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.
7
-
8
- > Generate **runtime type guards** automatically from your TypeScript types.
9
- > No schemas. No decorators.
10
- > **Analyzes types at build time to generate optimized validation functions**,
11
- > enabling **blazing-fast runtime validation** powered purely by TypeScript.
12
-
13
-
14
- <br/><br/>
15
-
16
- ## βš™οΈ μ‚¬μš©λ²• (Usage)
17
- ```bash
18
- npm i runtypex
19
- ```
20
-
21
- ```ts
22
- import { makeValidate, makeAssert } from "runtypex";
23
-
24
- interface User {
25
- id: number;
26
- name: string;
27
- active: boolean;
28
- }
29
-
30
- const isUser = makeValidate<User>();
31
- const assertUser: ReturnType<typeof makeAssert<User>> = makeAssert<User>();
32
-
33
- isUser({ id: 1, name: "Lux", active: true }); // βœ… true
34
- assertUser({ id: "bad" }); // ❌ throws
35
- ```
36
-
37
- <br/><br/>
38
-
39
- ### Vite μ˜ˆμ‹œ (Vite Example)
40
-
41
- ```ts
42
- // vite.config.ts
43
- import { defineConfig } from "vite";
44
- import { vitePlugin as runtypex } from "runtypex";
45
-
46
- export default defineConfig({
47
- plugins: [runtypex()],
48
- });
49
- ```
50
-
51
- <br/>
52
-
53
- ν”„λ‘œλ•μ…˜ λΉŒλ“œ μ‹œ λŸ°νƒ€μž„ 검증 μ½”λ“œλ₯Ό μ œκ±°ν•˜λ €λ©΄ μ˜΅μ…˜ `{ removeInProd: true }`λ₯Ό μ „λ‹¬ν•˜μ„Έμš”.
54
- To remove validation code in production builds, pass `{ removeInProd: true }`.
55
-
56
- ```ts
57
- export default defineConfig({
58
- plugins: [runtypex({ removeInProd: true })],
59
- });
60
- ```
61
-
62
- <br/>
63
-
64
- ### Webpack (ts-loader) μ˜ˆμ‹œ (Webpack Example)
65
-
66
- ```js
67
- // webpack.config.js
68
- const { tsTransformer } = require("runtypex");
69
-
70
- module.exports = {
71
- module: {
72
- rules: [
73
- {
74
- test: /\.tsx?$/,
75
- loader: "ts-loader",
76
- options: {
77
- getCustomTransformers: (program) => ({
78
- before: [ tsTransformer({ program }) ]
79
- })
80
- }
81
- }
82
- ]
83
- }
84
- }
85
- ```
86
-
87
- <br/><br/>
88
-
89
- ## ⚑ runtypex의 νŠΉμ§• (Features)
90
-
91
- | ν•­λͺ© | μ„€λͺ… | Description |
92
- |------|------|-------------|
93
- | ⚑ **빠름 (Fast)** | AST둜 컴파일된 검증 μ½”λ“œ, λŸ°νƒ€μž„ μŠ€ν‚€λ§ˆ νŒŒμ‹± μ—†μŒ | Compiled guards, no runtime schema parsing |
94
- | 🧩 **λ‹¨μˆœν•¨ (Simple)** | νƒ€μž…λ§Œ μ •μ˜, μŠ€ν‚€λ§ˆ 쀑볡 μ„ μ–Έ λΆˆν•„μš” | Define once, no schema duplication |
95
- | 🧱 **μœ μ—°ν•¨ (Flexible)** | Vite, Webpack λͺ¨λ‘ 지원 | Works with both Vite and Webpack |
96
- | πŸ› οΈ **API** | `makeValidate`, `makeAssert` 제곡 | Clean runtime API |
97
-
98
- <br/><br/>
99
-
100
- ## πŸ§ͺ 데λͺ¨ (Demo)
101
-
102
- πŸ”— [runtypex-demo (GitHub)](https://github.com/KumJungMin/runtypex-demo)
103
- TypeScript νƒ€μž…μ΄ **λΉŒλ“œ μ‹œ μžλ™μœΌλ‘œ λŸ°νƒ€μž„ 검증 μ½”λ“œλ‘œ λ³€ν™˜λ˜λŠ” κ³Όμ •**을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
104
- See how TypeScript types are transformed into real runtime guards at build time.
105
-
106
- <br/><br/>
107
-
108
- ## 🧭 λ§Œλ“€κ²Œ 된 이유 (Background)
109
-
110
- TypeScript의 νƒ€μž… μ‹œμŠ€ν…œ 덕뢄에 μ½”λ“œ μƒμ—μ„œλŠ” μ•ˆμ „ν–ˆμ§€λ§Œ,
111
- 결정적인 ν•œκ³„λ₯Ό λ§ˆμ£Όν–ˆμŠ΅λ‹ˆλ‹€.
112
-
113
- > TypeScript의 νƒ€μž…μ€ **λŸ°νƒ€μž„μ— μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€.**
114
- > TypeScript types **do not exist at runtime.**
115
-
116
- <br/>
117
-
118
-
119
- 즉, λΉŒλ“œ νƒ€μž„μ—λŠ” μ•ˆμ „ν•˜μ§€λ§Œ μ‹€μ œ μ‹€ν–‰ ν™˜κ²½(JS)μ—μ„œλŠ”
120
- λͺ¨λ“  νƒ€μž… 정보가 μ‚¬λΌμ§‘λ‹ˆλ‹€.
121
- κ²°κ΅­ **APIλ‚˜ μ™ΈλΆ€ λͺ¨λ“ˆμ—μ„œ 잘λͺ»λœ νƒ€μž…μ˜ 데이터가 듀어와도 검증할 방법이 μ—†μ—ˆμŠ΅λ‹ˆλ‹€.**
122
-
123
- > In short, TypeScript ensures type safety at build time,
124
- > but once the code is compiled, all type information disappears.
125
- > This means that even if invalid data comes from an API or an external source,
126
- > there’s no way to detect it at runtime.
127
-
128
- <br/>
129
-
130
- ### πŸ’‘ 기쑴의 μ‹œλ„ (Existing Approach): Zod λ“± μŠ€ν‚€λ§ˆ 기반 검증 <br/>(Previous Approach: Schema-Based Validators (e.g., Zod, Yup))
131
-
132
- Zod, Yup 같은 **μŠ€ν‚€λ§ˆ 기반 검증기**λ₯Ό λ¨Όμ € μ‹œλ„ν–ˆμŠ΅λ‹ˆλ‹€.
133
- ν•˜μ§€λ§Œ λ‹€μŒ μ„Έ κ°€μ§€ λ¬Έμ œμ— λΆ€λ”ͺν˜”μŠ΅λ‹ˆλ‹€:
134
-
135
- > We first tried schema-based validators like **Zod** and **Yup**,
136
- > but encountered three main problems:
137
-
138
- | 문제 | μ„€λͺ… | Problem | Description |
139
- |------|------|----------|-------------|
140
- | ⚑ μ„±λŠ₯ | 맀번 μŠ€ν‚€λ§ˆλ₯Ό ν•΄μ„ν•˜λ©° 검증 β†’ 반볡 λΉ„μš© λ°œμƒ | Performance | Every validation re-parses schema, causing overhead |
141
- | ⚠️ μ•ˆμ •μ„± | νƒ€μž… μ •μ˜μ™€ μŠ€ν‚€λ§ˆ 뢈일치 κ°€λŠ₯ | Safety | Schema can desync from TypeScript type definitions |
142
- | πŸ§‘β€πŸ’» DX | νƒ€μž…μ΄ 쀑볡 선언됨 (`interface` + `z.object`) | DX | Requires writing both `interface` and `z.object` |
143
-
144
- ---
145
-
146
- ### 🧠 μƒˆλ‘œμš΄ μ ‘κ·Ό (New Approach): AST둜 검증 μ½”λ“œ 생성<br/>(A New Approach: Compile-Time Guard Generation via AST)
147
-
148
- runtypexλŠ” **TypeScript AST(Abstract Syntax Tree)** λ₯Ό 뢄석해
149
- **λΉŒλ“œ μ‹œμ μ— μžλ™μœΌλ‘œ 검증 ν•¨μˆ˜λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.**
150
-
151
- > runtypex analyzes the **TypeScript AST (Abstract Syntax Tree)**
152
- > and automatically generates runtime validation functions **at build time**.
153
-
154
- 이 방식은 λ‹€μŒκ³Ό 같은 μž₯점을 κ°€μ§‘λ‹ˆλ‹€ πŸ‘‡
155
- This approach provides several key advantages πŸ‘‡
156
-
157
- - νƒ€μž… μ •μ˜ ν•œ 번으둜 λŸ°νƒ€μž„ 검증 μžλ™ν™”
158
- β†’ **Single source of truth** for type + validation
159
- - νƒ€μž… 뢈일치 문제 제거
160
- β†’ Eliminates schema desync between TS and runtime
161
- - λŸ°νƒ€μž„ μ˜€λ²„ν—€λ“œ μ΅œμ†Œν™”
162
- β†’ No dynamic schema parsing during execution
163
-
164
- <br/><br/>
165
-
166
- ## πŸ“˜ κ°œλ… μš”μ•½ (Concept Overview)
167
-
168
- | ν•­λͺ© | λ‚΄μš© | Concept | Description |
169
- |------|------|----------|-------------|
170
- | **문제** | TypeScript νƒ€μž…μ€ λŸ°νƒ€μž„μ—μ„œ 사라진닀 | Problem | TypeScript types vanish at runtime |
171
- | **κ²°κ³Ό** | μ™ΈλΆ€ 데이터 뢈일치 μ‹œ μ—λŸ¬ λ°œμƒ μ•ˆ 함 | Result | Type mismatches aren’t caught at runtime |
172
- | **ν•΄κ²°** | AST둜 νƒ€μž… 뢄석 β†’ 검증 μ½”λ“œ μžλ™ 생성 | Solution | Parse TS AST β†’ Generate guard functions |
173
-
174
- > Simply put: runtypex bridges the gap between TypeScript’s compile-time safety
175
- > and JavaScript’s runtime uncertainty β€” automatically.
176
-
177
- <br/>
178
-
179
- ### 🧩 μ˜ˆμ‹œ (Example)
180
-
181
- ```ts
182
- // Before: manual type guard
183
- function isUser(value: unknown): value is User {
184
- return (
185
- typeof value === "object" &&
186
- value !== null &&
187
- typeof (value as any).id === "number" &&
188
- typeof (value as any).name === "string"
189
- );
190
- }
191
-
192
- // After: runtypex auto-generated
193
- const isUser = makeValidate<User>();
194
- ```
195
-
196
- <br/>
197
-
198
- λΉŒλ“œ μ‹œ `makeValidate<User>()`λŠ”
199
- ASTλ₯Ό 기반으둜 μ•„λž˜μ™€ 같은 ν•¨μˆ˜λ‘œ **μžλ™ λ³€ν™˜λ©λ‹ˆλ‹€.**
200
-
201
- > At build time, `makeValidate<User>()`
202
- > is replaced with the following optimized validation code:
203
-
204
- ```js
205
- const isUser = (v) =>
206
- typeof v === "object" &&
207
- v !== null &&
208
- typeof v.id === "number" &&
209
- typeof v.name === "string";
210
- ```
211
-
212
- > βœ… Zero runtime parsing
213
- > βœ… Fully type-synced
214
- > βœ… Generated during build β€” not executed dynamically
215
-
216
- <br/>
217
-
218
- ### πŸ“š 더 읽어보기 (Further Reading)
219
-
220
- πŸ“– [TS Γ— 클린 μ•„ν‚€ν…μ²˜ 2편 β€” νƒ€μž…μŠ€ν¬λ¦½νŠΈ ν•œκ³„μ™€ Mapper, AST둜 νƒ€μž… κ²€μ¦ν•˜κΈ°](https://mong-blog.tistory.com/entry/TS-Γ—-클린-μ•„ν‚€ν…μ²˜-2편-β€”-νƒ€μž…μŠ€ν¬λ¦½νŠΈ-ν•œκ³„μ™€-Mapper-AST둜-νƒ€μž…-κ²€μ¦ν•˜κΈ°)
221
- > AST 기반 νƒ€μž… 검증 μžλ™ν™”μ˜ 원리와 λΉŒλ“œ νƒ€μž„ μ΅œμ ν™” 과정을 μžμ„Ένžˆ λ‹€λ£Ήλ‹ˆλ‹€.
222
- > A deep dive into how AST-based type parsing enables build-time validation generation and optimization.
223
-
224
- <br/>
225
-
226
- ### βœ… μš”μ•½ (Summary)
227
-
228
- | 핡심 포인트 | English Summary |
229
- |--------------|-----------------|
230
- | TypeScript νƒ€μž…μ€ λŸ°νƒ€μž„μ— μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€ | TypeScript types vanish at runtime |
231
- | μ™ΈλΆ€ APIλ‘œλΆ€ν„°μ˜ λ°μ΄ν„°λŠ” μ‹ λ’°ν•  수 μ—†λ‹€ | External data can’t be trusted blindly |
232
- | AST둜 νƒ€μž…μ„ 뢄석해 검증 ν•¨μˆ˜λ₯Ό μƒμ„±ν•œλ‹€ | Parse TS AST β†’ generate guards at build time |
233
- | λŸ°νƒ€μž„ μ˜€λ²„ν—€λ“œ 없이 νƒ€μž… μ•ˆμ „μ„±μ„ ν™•λ³΄ν•œλ‹€ | Provides runtime safety with zero runtime cost |
234
- | DX, μ•ˆμ •μ„±, μ„±λŠ₯ λͺ¨λ‘ κ°•ν™”λœλ‹€ | Improves DX, safety, and performance |
235
-
236
-
237
- <br/><br/>
238
-
239
- ## πŸ†• v0.2.0 μ—…λ°μ΄νŠΈ μ˜ˆμ • (Upcoming in v0.2.0)
240
-
241
- ### πŸš€ μƒˆλ‘œμš΄ κΈ°λŠ₯ (New Features)
242
-
243
- | ν•­λͺ© | μ„€λͺ… | Description |
244
- |------|------|-------------|
245
- | βš™οΈ **mode μ˜΅μ…˜ μΆ”κ°€** | λŸ°νƒ€μž„ λ™μž‘ λͺ¨λ“œ μ„€μ • (`"development"`, `"production"`, `"test"`) | Define runtime behavior mode (`"development"`, `"production"`, `"test"`) |
246
- | 🧹 **strip μ˜΅μ…˜ μΆ”κ°€** | λΉŒλ“œ μ‹œ λͺ¨λ“  `makeAssert`, `makeValidate` ν˜ΈμΆœμ„ μ œκ±°ν•˜μ—¬ 파일 크기 μ΅œμ†Œν™” | Removes all runtime validators from build output for minimal bundle size |
247
- | πŸͺΆ **logLevel μ˜΅μ…˜ μΆ”κ°€** | ν”ŒλŸ¬κ·ΈμΈ λ‘œκΉ… 레벨 μ„€μ • (`"silent"`, `"info"`, `"debug"`) | Controls plugin log verbosity (`"silent"`, `"info"`, `"debug"`) |
248
- | 🏷️ **`/* @runtypex:keep */` 주석 지원** | νŠΉμ • 검증 ν•¨μˆ˜λŠ” μ œκ±°ν•˜μ§€ μ•Šκ³  μœ μ§€ (`strip` ν™œμ„±ν™” μ‹œ μ˜ˆμ™Έ 처리용) | Preserve marked validators even when `strip` is enabled |
249
-
250
- <br/>
251
-
252
- ### βš™οΈ μ˜ˆμ‹œ (Example)
253
-
254
- ```ts
255
- // vite.config.ts
256
- import { defineConfig } from "vite";
257
- import { vitePlugin as runtypex } from "runtypex";
258
-
259
- export default defineConfig({
260
- plugins: [
261
- runtypex({
262
- mode: "development", // λŸ°νƒ€μž„ 검증 μœ μ§€
263
- strip: false, // 검증 μ½”λ“œ 제거 λΉ„ν™œμ„±ν™”
264
- logLevel: "info", // 일반 둜그 좜λ ₯
265
- }),
266
- ],
267
- });
268
- ```
269
-
270
-
271
- ```ts
272
- /* @runtypex:keep */
273
- const assertUser = makeAssert<User>();
274
- ```
275
-
276
- > `/* @runtypex:keep */` 주석을 뢙이면 `strip` μ˜΅μ…˜μ΄ ν™œμ„±ν™”λ˜μ–΄λ„
277
- > ν•΄λ‹Ή 검증 ν•¨μˆ˜λŠ” λΉŒλ“œ κ²°κ³Όλ¬Όμ—μ„œ μ œκ±°λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
278
- > Add `/* @runtypex:keep */` above a validator to keep it even when `strip` is enabled.
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
+ /** User id */
83
+ id: string;
84
+ displayName: string;
85
+ isActive: boolean;
86
+ }
87
+
88
+ const userMap = defineMap<UserDto, User>()({
89
+ id: source("user_id", {
90
+ db: "users.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,33 @@
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
+ /** @deprecated Prefer domain property JSDoc for domain field descriptions. */
13
+ description?: string;
14
+ dtoDescription?: string;
15
+ };
16
+ export declare function emitMapperFromSpec(params: {
17
+ checker: ts.TypeChecker;
18
+ dtoType: ts.Type;
19
+ domainType: ts.Type;
20
+ specNode: ts.Expression;
21
+ sourceFile: ts.SourceFile;
22
+ options?: MapperEmitOptions;
23
+ }): string | null;
24
+ export declare function readMapRules(checker: ts.TypeChecker, specNode: ts.Expression): Map<string, MapRuleInfo>;
25
+ export type MapPolicyViolation = {
26
+ from: string;
27
+ expectedKey: string;
28
+ actualKey: string;
29
+ };
30
+ export declare function findMapPolicyViolations(checker: ts.TypeChecker, specNode: ts.Expression, policyNode: ts.Expression | undefined): MapPolicyViolation[];
31
+ export declare function handleMapPolicyViolations(violations: MapPolicyViolation[], mode: "warn" | "error"): void;
32
+ /** Finds the mapping object behind inline, defineMap-wrapped, or identifier specs. */
33
+ export declare function resolveMapSpecObject(checker: ts.TypeChecker, node: ts.Expression): ts.ObjectLiteralExpression | null;