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
package/README.md
CHANGED
|
@@ -1,278 +1,112 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
isUser
|
|
34
|
-
assertUser
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
##
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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,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,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;
|