runtypex 0.1.12 β†’ 0.1.13

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 CHANGED
@@ -1,24 +1,43 @@
1
1
  # πŸ›‘οΈ runtypex
2
2
 
3
- Runtime type guards compiled from your TypeScript types.
4
- No schemas. No decorators. Just types β†’ blazing-fast runtime checks.
3
+ TypeScript νƒ€μž…μœΌλ‘œλΆ€ν„° **λŸ°νƒ€μž„ νƒ€μž… κ°€λ“œ(runtime type guard)** λ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€.
4
+ μŠ€ν‚€λ§ˆλ„, λ°μ½”λ ˆμ΄ν„°λ„ ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.
5
+ **λΉŒλ“œ μ‹œ νƒ€μž… 정보λ₯Ό 뢄석해 μ΅œμ ν™”λœ 검증 ν•¨μˆ˜λ₯Ό μžλ™ μƒμ„±ν•˜λ©°**,
6
+ νƒ€μž…λ§ŒμœΌλ‘œ β†’ **λŸ°νƒ€μž„ 검증**을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.
5
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
+ ```
6
20
 
7
- ## Use
8
21
  ```ts
9
22
  import { makeValidate, makeAssert } from "runtypex";
10
23
 
11
- interface User { id: number; name: string; active: boolean; }
24
+ interface User {
25
+ id: number;
26
+ name: string;
27
+ active: boolean;
28
+ }
12
29
 
13
30
  const isUser = makeValidate<User>();
14
31
  const assertUser: ReturnType<typeof makeAssert<User>> = makeAssert<User>();
15
32
 
16
- isUser({ id: 1, name: "Lux", active: true }); // true
17
- assertUser({ id: "bad" }); // throws
18
- toUser({ nope: true }); // β†’ fallback
33
+ isUser({ id: 1, name: "Lux", active: true }); // βœ… true
34
+ assertUser({ id: "bad" }); // ❌ throws
19
35
  ```
20
36
 
21
- ## Vite
37
+ <br/><br/>
38
+
39
+ ### Vite μ˜ˆμ‹œ (Vite Example)
40
+
22
41
  ```ts
23
42
  // vite.config.ts
24
43
  import { defineConfig } from "vite";
@@ -29,21 +48,24 @@ export default defineConfig({
29
48
  });
30
49
  ```
31
50
 
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";
51
+ <br/>
37
52
 
53
+ ν”„λ‘œλ•μ…˜ λΉŒλ“œ μ‹œ λŸ°νƒ€μž„ 검증 μ½”λ“œλ₯Ό μ œκ±°ν•˜λ €λ©΄ μ˜΅μ…˜ `{ removeInProd: true }`λ₯Ό μ „λ‹¬ν•˜μ„Έμš”.
54
+ To remove validation code in production builds, pass `{ removeInProd: true }`.
55
+
56
+ ```ts
38
57
  export default defineConfig({
39
58
  plugins: [runtypex({ removeInProd: true })],
40
59
  });
41
60
  ```
42
61
 
43
- ## Webpack (ts-loader)
62
+ <br/>
63
+
64
+ ### Webpack (ts-loader) μ˜ˆμ‹œ (Webpack Example)
65
+
44
66
  ```js
45
67
  // webpack.config.js
46
- const { tsTransformer } = require("runtypex/dist/ts-transformer.js");
68
+ const { tsTransformer } = require("runtypex");
47
69
 
48
70
  module.exports = {
49
71
  module: {
@@ -62,8 +84,195 @@ module.exports = {
62
84
  }
63
85
  ```
64
86
 
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`
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.
@@ -0,0 +1,2 @@
1
+ import ts from "typescript";
2
+ export declare function resolveTypeByName(program: ts.Program, sf: ts.SourceFile, checker: ts.TypeChecker, name: string): ts.Type | null;
@@ -0,0 +1,57 @@
1
+ import ts from "typescript";
2
+ export function resolveTypeByName(program, sf, checker, name) {
3
+ // -1️⃣ Primitive type fallback
4
+ const primitiveNames = ["string", "number", "boolean", "bigint", "symbol", "null", "undefined"];
5
+ if (primitiveNames.includes(name)) {
6
+ const map = {
7
+ string: checker.getStringType(),
8
+ number: checker.getNumberType(),
9
+ boolean: checker.getBooleanType(),
10
+ bigint: checker.getBigIntType(),
11
+ symbol: checker.getESSymbolType(),
12
+ null: checker.getNullType(),
13
+ undefined: checker.getUndefinedType(),
14
+ };
15
+ return map[name];
16
+ }
17
+ // 2️⃣ Scan source files
18
+ for (const file of program.getSourceFiles()) {
19
+ const decl = _findLocalDeclaration(file, name);
20
+ if (!decl)
21
+ continue;
22
+ // βœ… type, interface, enum, class
23
+ if (ts.isInterfaceDeclaration(decl) ||
24
+ ts.isClassDeclaration(decl) ||
25
+ ts.isEnumDeclaration(decl)) {
26
+ if (decl.name) {
27
+ const symbol = checker.getSymbolAtLocation(decl.name);
28
+ if (symbol)
29
+ return checker.getDeclaredTypeOfSymbol(symbol);
30
+ }
31
+ }
32
+ if (ts.isTypeAliasDeclaration(decl)) {
33
+ return checker.getTypeFromTypeNode(decl.type);
34
+ }
35
+ }
36
+ // 3️⃣ Scope-based fallback
37
+ const symbol = checker
38
+ .getSymbolsInScope(sf, ts.SymbolFlags.Type | ts.SymbolFlags.Alias | ts.SymbolFlags.Interface)
39
+ .find((s) => s.name === name);
40
+ return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
41
+ }
42
+ function _findLocalDeclaration(sf, name) {
43
+ let found;
44
+ (function walk(node) {
45
+ if ((ts.isInterfaceDeclaration(node) ||
46
+ ts.isTypeAliasDeclaration(node) ||
47
+ ts.isEnumDeclaration(node) ||
48
+ ts.isClassDeclaration(node)) &&
49
+ node.name?.text === name) {
50
+ found = node;
51
+ return;
52
+ }
53
+ if (!found)
54
+ node.forEachChild(walk);
55
+ })(sf);
56
+ return found;
57
+ }
@@ -1,5 +1,6 @@
1
1
  import ts from "typescript";
2
2
  import { emitGuardFromType } from "../core/index.js";
3
+ import { resolveTypeByName } from "./helper.js";
3
4
  /**
4
5
  * 🧩 tsTransformer
5
6
  * TypeScript custom transformer (BEFORE) factory.
@@ -33,7 +34,7 @@ export default function tsTransformer(options) {
33
34
  if (targetFunctions.includes(name) && node.typeArguments?.length) {
34
35
  const typeNode = node.typeArguments[0];
35
36
  const typeName = typeNode.getText();
36
- const type = _resolveTypeByName(program, node.getSourceFile(), checker, typeName);
37
+ const type = resolveTypeByName(program, node.getSourceFile(), checker, typeName);
37
38
  if (!type)
38
39
  return node;
39
40
  const isRemovedInProd = removeInProd && prod;
@@ -50,57 +51,6 @@ export default function tsTransformer(options) {
50
51
  return (sf) => ts.visitNode(sf, visit);
51
52
  };
52
53
  }
53
- function _resolveTypeByName(program, sf, checker, name) {
54
- // 1️⃣ <string|number|boolean|null|undefined>
55
- const primitiveNames = ["string", "number", "boolean", "bigint", "symbol", "null", "undefined"];
56
- if (primitiveNames.includes(name)) {
57
- const map = {
58
- string: checker.getStringType(),
59
- number: checker.getNumberType(),
60
- boolean: checker.getBooleanType(),
61
- bigint: checker.getBigIntType(),
62
- symbol: checker.getESSymbolType(),
63
- null: checker.getNullType(),
64
- undefined: checker.getUndefinedType(),
65
- };
66
- return map[name];
67
- }
68
- // 2️⃣ <Type|Interface|Enum> declared in the same file or other files
69
- for (const file of program.getSourceFiles()) {
70
- const decl = _findLocalDeclaration(file, name);
71
- if (!decl)
72
- continue;
73
- if (ts.isInterfaceDeclaration(decl) || ts.isClassDeclaration(decl) || ts.isEnumDeclaration(decl)) {
74
- const symbol = decl.name ? checker.getSymbolAtLocation(decl.name) : null;
75
- if (symbol)
76
- return checker.getDeclaredTypeOfSymbol(symbol);
77
- }
78
- if (ts.isTypeAliasDeclaration(decl)) {
79
- return checker.getTypeFromTypeNode(decl.type);
80
- }
81
- }
82
- // 3️⃣ <Type|Interface|Enum> imported from other modules
83
- const symbol = checker
84
- .getSymbolsInScope(sf, ts.SymbolFlags.Type | ts.SymbolFlags.Alias | ts.SymbolFlags.Interface)
85
- .find((s) => s.name === name);
86
- return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
87
- }
88
- function _findLocalDeclaration(sf, name) {
89
- let found;
90
- (function walk(node) {
91
- if ((ts.isInterfaceDeclaration(node) ||
92
- ts.isTypeAliasDeclaration(node) ||
93
- ts.isEnumDeclaration(node) ||
94
- ts.isClassDeclaration(node)) &&
95
- node.name?.text === name) {
96
- found = node;
97
- return;
98
- }
99
- if (!found)
100
- node.forEachChild(walk);
101
- })(sf);
102
- return found;
103
- }
104
54
  function _emitMakeValidate(checker, type, isRemovedInProd) {
105
55
  const guard = isRemovedInProd ? "((_)=>true)" : emitGuardFromType(checker, type);
106
56
  return ts.factory.createIdentifier(guard);
@@ -1,6 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import path from "node:path";
3
3
  import { emitGuardFromType } from "../core/index.js";
4
+ import { resolveTypeByName } from "./helper.js";
4
5
  /**
5
6
  * 🧩 vitePluginRuntypex
6
7
  * A Vite plugin that performs build-time type β†’ runtime validation transformation.
@@ -74,7 +75,7 @@ function _findNearestTsconfig(start) {
74
75
  function _emitMakeValidate({ program, checker, sf, typeName, prod, removeInProd, }) {
75
76
  if (removeInProd && prod)
76
77
  return `((_)=>true)`;
77
- const type = _resolveTypeByName(program, sf, checker, typeName.trim());
78
+ const type = resolveTypeByName(program, sf, checker, typeName.trim());
78
79
  if (!type)
79
80
  return null;
80
81
  return emitGuardFromType(checker, type);
@@ -82,71 +83,9 @@ function _emitMakeValidate({ program, checker, sf, typeName, prod, removeInProd,
82
83
  function _emitMakeAssert({ program, checker, sf, typeName, prod, removeInProd, }) {
83
84
  if (removeInProd && prod)
84
85
  return `((_)=>{})`;
85
- const type = _resolveTypeByName(program, sf, checker, typeName.trim());
86
+ const type = resolveTypeByName(program, sf, checker, typeName.trim());
86
87
  if (!type)
87
88
  return null;
88
89
  const guard = emitGuardFromType(checker, type);
89
90
  return `(function(){const G=${guard};return(i)=>{if(!G(i))throw new TypeError("[runtypex] Validation failed.");};})()`;
90
91
  }
91
- // ──────────────────────────────────────────────
92
- // β‘’ Type Resolution (support primitive/interface/type/enum)
93
- // ──────────────────────────────────────────────
94
- function _resolveTypeByName(program, sf, checker, name) {
95
- // -1️⃣ Primitive type fallback
96
- const primitiveNames = ["string", "number", "boolean", "bigint", "symbol", "null", "undefined"];
97
- if (primitiveNames.includes(name)) {
98
- const map = {
99
- string: checker.getStringType(),
100
- number: checker.getNumberType(),
101
- boolean: checker.getBooleanType(),
102
- bigint: checker.getBigIntType(),
103
- symbol: checker.getESSymbolType(),
104
- null: checker.getNullType(),
105
- undefined: checker.getUndefinedType(),
106
- };
107
- return map[name];
108
- }
109
- // 2️⃣ Scan source files
110
- for (const file of program.getSourceFiles()) {
111
- const decl = _findLocalDeclaration(file, name);
112
- if (!decl)
113
- continue;
114
- // βœ… type, interface, enum, class
115
- if (ts.isInterfaceDeclaration(decl) ||
116
- ts.isClassDeclaration(decl) ||
117
- ts.isEnumDeclaration(decl)) {
118
- if (decl.name) {
119
- const symbol = checker.getSymbolAtLocation(decl.name);
120
- if (symbol)
121
- return checker.getDeclaredTypeOfSymbol(symbol);
122
- }
123
- }
124
- if (ts.isTypeAliasDeclaration(decl)) {
125
- return checker.getTypeFromTypeNode(decl.type);
126
- }
127
- }
128
- // 3️⃣ Scope-based fallback
129
- const symbol = checker
130
- .getSymbolsInScope(sf, ts.SymbolFlags.Type | ts.SymbolFlags.Alias | ts.SymbolFlags.Interface)
131
- .find((s) => s.name === name);
132
- return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
133
- }
134
- // ──────────────────────────────────────────────
135
- // β‘£ AST Utility
136
- // ──────────────────────────────────────────────
137
- function _findLocalDeclaration(sf, name) {
138
- let found;
139
- (function walk(node) {
140
- if ((ts.isInterfaceDeclaration(node) ||
141
- ts.isTypeAliasDeclaration(node) ||
142
- ts.isEnumDeclaration(node) ||
143
- ts.isClassDeclaration(node)) &&
144
- node.name?.text === name) {
145
- found = node;
146
- return;
147
- }
148
- if (!found)
149
- node.forEachChild(walk);
150
- })(sf);
151
- return found;
152
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtypex",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Runtime type guards compiled from TypeScript types.",
5
5
  "license": "MIT",
6
6
  "author": "KumJungMin",