@elliots/typical 0.1.10 → 0.2.0-beta.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 +187 -208
- package/dist/src/cli.js +12 -85
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cli.typical.ts +136 -0
- package/dist/src/config.js +38 -38
- package/dist/src/config.js.map +1 -1
- package/dist/src/config.typical.ts +287 -0
- package/dist/src/esm-loader-register.js.map +1 -1
- package/dist/src/esm-loader.d.ts +1 -1
- package/dist/src/esm-loader.js +30 -17
- package/dist/src/esm-loader.js.map +1 -1
- package/dist/src/file-filter.d.ts +1 -1
- package/dist/src/file-filter.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/program-manager.d.ts +27 -0
- package/dist/src/program-manager.js +121 -0
- package/dist/src/program-manager.js.map +1 -0
- package/dist/src/regex-hoister.d.ts +1 -1
- package/dist/src/regex-hoister.js +13 -19
- package/dist/src/regex-hoister.js.map +1 -1
- package/dist/src/setup.d.ts +1 -1
- package/dist/src/setup.js +3 -3
- package/dist/src/setup.js.map +1 -1
- package/dist/src/source-map.d.ts +1 -1
- package/dist/src/source-map.js +1 -1
- package/dist/src/source-map.js.map +1 -1
- package/dist/src/source-map.typical.ts +216 -0
- package/dist/src/timing.d.ts +19 -0
- package/dist/src/timing.js +65 -0
- package/dist/src/timing.js.map +1 -0
- package/dist/src/transformer.d.ts +28 -193
- package/dist/src/transformer.js +41 -1917
- package/dist/src/transformer.js.map +1 -1
- package/dist/src/transformer.typical.ts +2552 -0
- package/dist/src/tsc-plugin.d.ts +8 -1
- package/dist/src/tsc-plugin.js +11 -7
- package/dist/src/tsc-plugin.js.map +1 -1
- package/package.json +51 -47
- package/src/cli.ts +41 -128
- package/src/config.ts +92 -91
- package/src/esm-loader-register.ts +2 -2
- package/src/esm-loader.ts +44 -29
- package/src/index.ts +5 -10
- package/src/patch-fs.cjs +14 -14
- package/src/timing.ts +74 -0
- package/src/transformer.ts +47 -2592
- package/bin/ttsc +0 -12
- package/src/file-filter.ts +0 -49
- package/src/patch-tsconfig.cjs +0 -52
- package/src/regex-hoister.ts +0 -203
- package/src/setup.ts +0 -39
- package/src/source-map.ts +0 -202
- package/src/tsc-plugin.ts +0 -12
package/README.md
CHANGED
|
@@ -1,113 +1,129 @@
|
|
|
1
1
|
# Typical
|
|
2
2
|
|
|
3
|
-
Typical
|
|
3
|
+
Typical makes TypeScript type-safe at runtime _with no changes to your code_.
|
|
4
4
|
|
|
5
|
-
It
|
|
5
|
+
It transforms your code to inject runtime validation based on your existing type annotations. With source maps, so errors point to the right lines in your original code.
|
|
6
6
|
|
|
7
7
|
## Why?
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Less need for zod, yup, ajv, or other runtime validation libraries - your types are already validated automatically. If you can express it in TypeScript, Typical can validate it at runtime.
|
|
10
|
+
- Protects against data leaks via `JSON.stringify` by ensuring only properties defined in your types are included
|
|
11
|
+
- Catches type mismatches at runtime that TypeScript can't catch at compile time (API responses, JSON parsing, un-typed/badly-typed libraries, vibe-coding coworkers etc.)
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Features
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- Validation of function parameters and return types
|
|
16
|
+
- Safe `JSON.parse` with type validation
|
|
17
|
+
- Safe `JSON.stringify` that only includes defined properties
|
|
18
|
+
- Validation of type casts (`as Type`)
|
|
19
|
+
- Configurable include/exclude patterns
|
|
14
20
|
|
|
15
|
-
##
|
|
21
|
+
## Example
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
- ✅ Automatic validation of return types
|
|
19
|
-
- ✅ Replace `JSON.stringify` with a custom stringifier (very fast!)
|
|
20
|
-
- ✅ Replace `JSON.parse` with a custom parser and validator (very fast!)
|
|
21
|
-
- ✅ Configurable include/exclude patterns
|
|
22
|
-
- ✅ Optionally reuse validation logic for identical types to optimize performance (enabled by default)
|
|
23
|
-
- ✅ TSC plugin
|
|
24
|
-
- ✅ ESM loader for runtime transformation with `node --import @elliots/typical/esm` (or `node --loader @elliots/typical/esm-loader` for older Node versions)
|
|
25
|
-
- ✅ tsx wrapper (ttsx) for easy use like `npx ttsx script.ts`
|
|
26
|
-
- ✅ Unplugin for Vite, Webpack, Rollup, esbuild, and more
|
|
23
|
+
This code runs without errors in normal TypeScript, but Typical catches the invalid data:
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
```ts
|
|
26
|
+
interface User {
|
|
27
|
+
name: string;
|
|
28
|
+
email: `${string}@${string}`;
|
|
29
|
+
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// This will throw - email doesn't match the template literal type
|
|
32
|
+
const user = JSON.parse('{"name":"Alice","email":"not-an-email"}') as User;
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Usage Options
|
|
38
|
+
|
|
39
|
+
Choose the integration that fits your workflow:
|
|
40
|
+
|
|
41
|
+
| Method | Best For | Package |
|
|
42
|
+
| --------------------------------------------------------- | ------------------------------- | ----------------------------- |
|
|
43
|
+
| [ESM Loader](#nodejs-esm-loader) | Node.js scripts, development | `@elliots/typical` |
|
|
44
|
+
| [ttsx](#ttsx-tsx-wrapper) | Quick scripts with tsx | `@elliots/typical` + `tsx` |
|
|
45
|
+
| [Bun Plugin](#bun) | Bun projects | `@elliots/bun-plugin-typical` |
|
|
46
|
+
| [Vite/Webpack/etc](#bundlers-vite-webpack-rollup-esbuild) | Frontend apps, bundled projects | `@elliots/unplugin-typical` |
|
|
47
|
+
| [tsc Plugin](#typescript-compiler-tsc) | Pure TypeScript compilation | `@elliots/typical-tsc-plugin` |
|
|
48
|
+
|
|
49
|
+
---
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
## Node.js (ESM Loader)
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
The simplest way to run TypeScript with Typical validation.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm add @elliots/typical
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node --import @elliots/typical/esm src/index.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Add to `package.json` scripts:
|
|
39
64
|
|
|
40
65
|
```json
|
|
41
66
|
{
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"reusableValidators": false,
|
|
45
|
-
"validateFunctions": true,
|
|
46
|
-
"validateCasts": false,
|
|
47
|
-
"hoistRegex": true,
|
|
48
|
-
"ignoreDOMTypes": true,
|
|
49
|
-
"ignoreTypes": [],
|
|
50
|
-
"sourceMap": {
|
|
51
|
-
"enabled": true,
|
|
52
|
-
"includeContent": true,
|
|
53
|
-
"inline": false
|
|
67
|
+
"scripts": {
|
|
68
|
+
"start": "node --import @elliots/typical/esm src/index.ts"
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
71
|
```
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
---
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|--------|---------|-------------|
|
|
62
|
-
| `include` | `["**/*.ts", "**/*.tsx"]` | Glob patterns for files to transform |
|
|
63
|
-
| `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Glob patterns for files to skip |
|
|
64
|
-
| `reusableValidators` | `false` | Create shared validators for identical types (smaller output). Incompatible with source maps. |
|
|
65
|
-
| `validateFunctions` | `true` | Validate function parameters and return types at runtime |
|
|
66
|
-
| `validateCasts` | `false` | Validate type assertions (`as Type`) at runtime |
|
|
67
|
-
| `hoistRegex` | `true` | Hoist regex patterns to top-level constants (improves performance) |
|
|
68
|
-
| `ignoreDOMTypes` | `true` | Skip validation for DOM types (Document, Element, etc.) |
|
|
69
|
-
| `ignoreTypes` | `[]` | Type patterns to skip validation for (supports wildcards, e.g., `["React.*"]`) |
|
|
70
|
-
| `sourceMap.enabled` | `true` | Generate source maps for transformed code |
|
|
71
|
-
| `sourceMap.includeContent` | `true` | Include original source content in source maps |
|
|
72
|
-
| `sourceMap.inline` | `false` | Use inline source maps (data URL) instead of external files |
|
|
73
|
-
| `debug.writeIntermediateFiles` | `false` | Write `.typical.ts` files showing code before typia transform |
|
|
75
|
+
## ttsx (tsx wrapper)
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
A convenience wrapper that combines [tsx](https://github.com/privatenumber/tsx) with Typical.
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
```bash
|
|
80
|
+
npm add @elliots/typical tsx
|
|
81
|
+
```
|
|
78
82
|
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
"reusableValidators": true,
|
|
82
|
-
"sourceMap": {
|
|
83
|
-
"enabled": false
|
|
84
|
-
}
|
|
85
|
-
}
|
|
83
|
+
```bash
|
|
84
|
+
npx ttsx script.ts
|
|
86
85
|
```
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
Or install globally:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm add -g @elliots/typical tsx
|
|
91
|
+
ttsx script.ts
|
|
92
|
+
```
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
> **Note:** `tsx` must be installed separately. The `ttsx` command is a thin wrapper that runs `tsx` with the Typical ESM loader.
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
---
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
## Bun
|
|
95
99
|
|
|
96
100
|
```bash
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
bun add @elliots/bun-plugin-typical
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Create `bunfig.toml`:
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
preload = ["./preload.ts"]
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
Create `preload.ts`:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { typicalPlugin } from "@elliots/bun-plugin-typical";
|
|
114
|
+
|
|
115
|
+
Bun.plugin(typicalPlugin());
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then run:
|
|
102
119
|
|
|
103
120
|
```bash
|
|
104
|
-
|
|
105
|
-
ttsx your-script.ts
|
|
121
|
+
bun run src/index.ts
|
|
106
122
|
```
|
|
107
123
|
|
|
108
|
-
|
|
124
|
+
---
|
|
109
125
|
|
|
110
|
-
|
|
126
|
+
## Bundlers (Vite, Webpack, Rollup, esbuild)
|
|
111
127
|
|
|
112
128
|
```bash
|
|
113
129
|
npm add @elliots/unplugin-typical
|
|
@@ -115,224 +131,187 @@ npm add @elliots/unplugin-typical
|
|
|
115
131
|
|
|
116
132
|
### Vite
|
|
117
133
|
|
|
118
|
-
```
|
|
134
|
+
```ts
|
|
119
135
|
// vite.config.ts
|
|
120
|
-
import Typical from
|
|
136
|
+
import Typical from "@elliots/unplugin-typical/vite";
|
|
121
137
|
|
|
122
138
|
export default defineConfig({
|
|
123
|
-
plugins: [
|
|
124
|
-
|
|
125
|
-
],
|
|
126
|
-
})
|
|
139
|
+
plugins: [Typical()],
|
|
140
|
+
});
|
|
127
141
|
```
|
|
128
142
|
|
|
129
143
|
### Webpack
|
|
130
144
|
|
|
131
|
-
```
|
|
145
|
+
```js
|
|
132
146
|
// webpack.config.js
|
|
133
|
-
const Typical = require(
|
|
147
|
+
const Typical = require("@elliots/unplugin-typical/webpack").default;
|
|
134
148
|
|
|
135
149
|
module.exports = {
|
|
136
|
-
plugins: [
|
|
137
|
-
|
|
138
|
-
],
|
|
139
|
-
}
|
|
150
|
+
plugins: [Typical()],
|
|
151
|
+
};
|
|
140
152
|
```
|
|
141
153
|
|
|
142
154
|
### Rollup
|
|
143
155
|
|
|
144
|
-
```
|
|
156
|
+
```js
|
|
145
157
|
// rollup.config.js
|
|
146
|
-
import Typical from
|
|
158
|
+
import Typical from "@elliots/unplugin-typical/rollup";
|
|
147
159
|
|
|
148
160
|
export default {
|
|
149
|
-
plugins: [
|
|
150
|
-
|
|
151
|
-
],
|
|
152
|
-
}
|
|
161
|
+
plugins: [Typical()],
|
|
162
|
+
};
|
|
153
163
|
```
|
|
154
164
|
|
|
155
165
|
### esbuild
|
|
156
166
|
|
|
157
|
-
```
|
|
158
|
-
import { build } from
|
|
159
|
-
import Typical from
|
|
167
|
+
```ts
|
|
168
|
+
import { build } from "esbuild";
|
|
169
|
+
import Typical from "@elliots/unplugin-typical/esbuild";
|
|
160
170
|
|
|
161
171
|
build({
|
|
162
172
|
plugins: [Typical()],
|
|
163
|
-
})
|
|
173
|
+
});
|
|
164
174
|
```
|
|
165
175
|
|
|
166
|
-
###
|
|
176
|
+
### Rolldown
|
|
167
177
|
|
|
168
|
-
|
|
178
|
+
```ts
|
|
179
|
+
// rolldown.config.ts
|
|
180
|
+
import Typical from "@elliots/unplugin-typical/rolldown";
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
Typical(
|
|
172
|
-
|
|
173
|
-
validateCasts: false,
|
|
174
|
-
// ... other options
|
|
175
|
-
})
|
|
182
|
+
export default {
|
|
183
|
+
plugins: [Typical()],
|
|
184
|
+
};
|
|
176
185
|
```
|
|
177
186
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
## Example
|
|
181
|
-
|
|
182
|
-
This code will run without errors when compiled normally, but will throw an error when using Typical.
|
|
183
|
-
|
|
187
|
+
### Farm
|
|
184
188
|
|
|
185
189
|
```ts
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
email: `${string}@${string}`;
|
|
189
|
-
}
|
|
190
|
-
const u = JSON.parse('{"name":"Alice","email":"oops-not-an-email"}') as User;
|
|
191
|
-
```
|
|
190
|
+
// farm.config.ts
|
|
191
|
+
import Typical from "@elliots/unplugin-typical/farm";
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
But basically you shouldn't need to care about how it works internally, it makes typescript strongly typed*. You can still use `any` and `unknown` if you want to opt out of type safety.
|
|
198
|
-
|
|
199
|
-
* sort of. probably. something like it anyway.
|
|
193
|
+
export default {
|
|
194
|
+
plugins: [Typical()],
|
|
195
|
+
};
|
|
196
|
+
```
|
|
200
197
|
|
|
201
|
-
|
|
198
|
+
### Rspack
|
|
202
199
|
|
|
203
|
-
|
|
200
|
+
```ts
|
|
201
|
+
// rspack.config.ts
|
|
202
|
+
import Typical from "@elliots/unplugin-typical/rspack";
|
|
204
203
|
|
|
205
|
-
|
|
204
|
+
export default {
|
|
205
|
+
plugins: [Typical()],
|
|
206
|
+
};
|
|
207
|
+
```
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
interface User { name: string; }
|
|
209
|
+
---
|
|
209
210
|
|
|
210
|
-
|
|
211
|
-
function validate(user: User): User {
|
|
212
|
-
return user; // Already validated on entry, skip return validation
|
|
213
|
-
}
|
|
211
|
+
## TypeScript Compiler (tsc)
|
|
214
212
|
|
|
215
|
-
|
|
216
|
-
function getAddress(user: User): Address {
|
|
217
|
-
return user.address; // user was validated, address is safe
|
|
218
|
-
}
|
|
213
|
+
For projects that compile with `tsc` directly using [ts-patch](https://github.com/nonara/ts-patch).
|
|
219
214
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const user: User = fetchData(); // const is validated here
|
|
223
|
-
return user; // skip redundant validation
|
|
224
|
-
}
|
|
215
|
+
```bash
|
|
216
|
+
npm add @elliots/typical-tsc-plugin ts-patch
|
|
225
217
|
```
|
|
226
218
|
|
|
227
|
-
###
|
|
219
|
+
### Option 1: ttsc (auto-injects plugin)
|
|
228
220
|
|
|
229
|
-
|
|
230
|
-
// After mutation
|
|
231
|
-
function updateUser(user: User): User {
|
|
232
|
-
user.name = "modified"; // Mutation taints the value
|
|
233
|
-
return user; // Must re-validate
|
|
234
|
-
}
|
|
221
|
+
The `ttsc` command automatically injects the plugin - no config needed:
|
|
235
222
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return user; // Must re-validate
|
|
240
|
-
}
|
|
223
|
+
```bash
|
|
224
|
+
npx ttsc
|
|
225
|
+
```
|
|
241
226
|
|
|
242
|
-
|
|
243
|
-
async function asyncProcess(user: User): Promise<User> {
|
|
244
|
-
await delay(); // Async boundary taints values
|
|
245
|
-
return user; // Must re-validate
|
|
246
|
-
}
|
|
227
|
+
Add to `package.json`:
|
|
247
228
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"scripts": {
|
|
232
|
+
"build": "ttsc"
|
|
233
|
+
}
|
|
251
234
|
}
|
|
252
235
|
```
|
|
253
236
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
### Intermediate Files
|
|
237
|
+
### Option 2: Manual tsconfig.json
|
|
257
238
|
|
|
258
|
-
|
|
239
|
+
Add to your `tsconfig.json`:
|
|
259
240
|
|
|
260
241
|
```json
|
|
261
242
|
{
|
|
262
|
-
"
|
|
263
|
-
"
|
|
243
|
+
"compilerOptions": {
|
|
244
|
+
"plugins": [
|
|
245
|
+
{
|
|
246
|
+
"transform": "@elliots/typical-tsc-plugin",
|
|
247
|
+
"transformProgram": true
|
|
248
|
+
}
|
|
249
|
+
]
|
|
264
250
|
}
|
|
265
251
|
}
|
|
266
252
|
```
|
|
267
253
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
### Verbose Logging
|
|
271
|
-
|
|
272
|
-
Set `DEBUG=1` environment variable for detailed logging:
|
|
254
|
+
Then run ts-patch's tsc:
|
|
273
255
|
|
|
274
256
|
```bash
|
|
275
|
-
|
|
257
|
+
npx ts-patch install
|
|
258
|
+
npx tsc
|
|
276
259
|
```
|
|
277
260
|
|
|
278
|
-
|
|
261
|
+
Or add a prepare script:
|
|
279
262
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"scripts": {
|
|
266
|
+
"prepare": "ts-patch install -s",
|
|
267
|
+
"build": "tsc"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
283
271
|
|
|
284
|
-
|
|
285
|
-
- Solution: Enable `ignoreDOMTypes: true` (default) or add specific types to `ignoreTypes`
|
|
272
|
+
---
|
|
286
273
|
|
|
287
|
-
|
|
288
|
-
- Solution: Add `"React.*"` to `ignoreTypes`
|
|
274
|
+
## Configuration
|
|
289
275
|
|
|
290
|
-
|
|
291
|
-
- Solution: Add the specific type patterns to `ignoreTypes`
|
|
276
|
+
Create a `typical.json` file in your project root (optional):
|
|
292
277
|
|
|
293
278
|
```json
|
|
294
279
|
{
|
|
295
|
-
"
|
|
280
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
281
|
+
"exclude": ["node_modules/**", "**/*.d.ts"],
|
|
282
|
+
"validateFunctions": true,
|
|
283
|
+
"validateCasts": false
|
|
296
284
|
}
|
|
297
285
|
```
|
|
298
286
|
|
|
299
|
-
###
|
|
287
|
+
### Options
|
|
300
288
|
|
|
301
|
-
|
|
289
|
+
| Option | Default | Description |
|
|
290
|
+
| ------------------- | --------------------------------------------------------- | --------------------------------------------- |
|
|
291
|
+
| `include` | `["**/*.ts", "**/*.tsx"]` | Files to transform |
|
|
292
|
+
| `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Files to skip |
|
|
293
|
+
| `validateFunctions` | `true` | Validate function parameters and return types |
|
|
294
|
+
| `validateCasts` | `false` | Validate type assertions (`as Type`) |
|
|
302
295
|
|
|
303
|
-
|
|
296
|
+
---
|
|
304
297
|
|
|
305
|
-
|
|
298
|
+
## How It Works
|
|
306
299
|
|
|
307
|
-
|
|
308
|
-
function identity<T>(value: T): T {
|
|
309
|
-
return value; // T is not validated - no runtime type info
|
|
310
|
-
}
|
|
311
|
-
```
|
|
300
|
+
Typical uses a Go-based compiler that leverages the TypeScript type checker to analyze your code. It generates runtime validators that check values against their declared types.
|
|
312
301
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
function process<T>(value: T, user: User): User {
|
|
317
|
-
return user; // User IS validated
|
|
318
|
-
}
|
|
319
|
-
```
|
|
302
|
+
Types that can't be validated at runtime (like generic type parameters `T`) are skipped. You can still use `any` and `unknown` to opt out of validation.
|
|
320
303
|
|
|
321
|
-
|
|
304
|
+
## Debugging
|
|
322
305
|
|
|
323
|
-
|
|
306
|
+
Set `DEBUG=1` for verbose logging:
|
|
324
307
|
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
constructor(options: Options) {
|
|
328
|
-
// Manual validation if needed
|
|
329
|
-
if (!options.timeout) throw new Error("timeout required");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
308
|
+
```bash
|
|
309
|
+
DEBUG=1 npm run build
|
|
332
310
|
```
|
|
333
311
|
|
|
334
|
-
##
|
|
335
|
-
The actual validation work is done by [typia](https://github.com/samchon/typia). This package just generates the necessary code to call typia's functions based on your TypeScript types.
|
|
336
|
-
|
|
337
|
-
> NOTE: The whole package was all mostly LLM. Feel free to improve it without care for the author's feelings.
|
|
312
|
+
## Limitations
|
|
338
313
|
|
|
314
|
+
- Generic type parameters (`T`) cannot be validated - no runtime type information
|
|
315
|
+
- Type-only imports of classes aren't checked (can't do instanceof on type-only imports)
|
|
316
|
+
- Validation of functions is just not done. Need to think about that one.
|
|
317
|
+
- Some complex types may not be fully supported yet. If you find any that fail, please open an issue!
|
package/dist/src/cli.js
CHANGED
|
@@ -4,113 +4,40 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { TypicalTransformer } from './transformer.js';
|
|
6
6
|
import { loadConfig, validateConfig } from './config.js';
|
|
7
|
-
import { inlineSourceMapComment, externalSourceMapComment } from './source-map.js';
|
|
8
7
|
const program = new Command();
|
|
9
|
-
program
|
|
10
|
-
.name('typical')
|
|
11
|
-
.description('Runtime safe TypeScript transformer using typia')
|
|
12
|
-
.version('0.1.0');
|
|
8
|
+
program.name('typical').description('Runtime safe TypeScript transformer').version('0.1.0');
|
|
13
9
|
program
|
|
14
10
|
.command('transform')
|
|
15
11
|
.description('Transform a TypeScript file with runtime validation')
|
|
16
12
|
.argument('<file>', 'TypeScript file to transform')
|
|
17
13
|
.option('-o, --output <file>', 'Output file')
|
|
18
14
|
.option('-c, --config <file>', 'Config file path', 'typical.json')
|
|
19
|
-
.option('-
|
|
20
|
-
.option('--source-map', 'Generate external source map file')
|
|
21
|
-
.option('--inline-source-map', 'Include inline source map in output')
|
|
22
|
-
.option('--no-source-map', 'Disable source map generation')
|
|
15
|
+
.option('-p, --project <file>', 'TypeScript config file path', 'tsconfig.json')
|
|
23
16
|
.action(async (file, options) => {
|
|
17
|
+
let transformer = null;
|
|
24
18
|
try {
|
|
25
19
|
const config = validateConfig(loadConfig(options.config));
|
|
26
|
-
|
|
20
|
+
transformer = new TypicalTransformer(config, options.project);
|
|
27
21
|
if (!fs.existsSync(file)) {
|
|
28
22
|
console.error(`File not found: ${file}`);
|
|
29
23
|
process.exit(1);
|
|
30
24
|
}
|
|
31
|
-
// Determine source map behavior
|
|
32
|
-
const generateSourceMap = options.inlineSourceMap || options.sourceMap !== false;
|
|
33
25
|
console.log(`Transforming ${file}...`);
|
|
34
|
-
const result = transformer.transform(path.resolve(file),
|
|
35
|
-
sourceMap: generateSourceMap,
|
|
36
|
-
});
|
|
26
|
+
const result = await transformer.transform(path.resolve(file), 'ts');
|
|
37
27
|
// Determine output file path
|
|
38
|
-
const outputFile = options.output
|
|
39
|
-
|
|
40
|
-
: options.mode === 'js'
|
|
41
|
-
? file.replace(/\.tsx?$/, '.js')
|
|
42
|
-
: file + '.transformed.ts';
|
|
43
|
-
let outputCode = result.code;
|
|
44
|
-
// Handle source maps
|
|
45
|
-
if (result.map) {
|
|
46
|
-
if (options.inlineSourceMap) {
|
|
47
|
-
// Inline source map as data URL
|
|
48
|
-
outputCode += '\n' + inlineSourceMapComment(result.map);
|
|
49
|
-
}
|
|
50
|
-
else if (options.sourceMap !== false) {
|
|
51
|
-
// Write external source map file
|
|
52
|
-
const mapFile = outputFile + '.map';
|
|
53
|
-
fs.writeFileSync(mapFile, JSON.stringify(result.map, null, 2));
|
|
54
|
-
outputCode += '\n' + externalSourceMapComment(path.basename(mapFile));
|
|
55
|
-
console.log(`Source map written to ${mapFile}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
fs.writeFileSync(outputFile, outputCode);
|
|
28
|
+
const outputFile = options.output ? path.resolve(options.output) : file + '.transformed.ts';
|
|
29
|
+
fs.writeFileSync(outputFile, result.code);
|
|
59
30
|
console.log(`Transformed code written to ${outputFile}`);
|
|
60
31
|
}
|
|
61
32
|
catch (error) {
|
|
62
33
|
console.error('Transformation failed:', error);
|
|
63
34
|
process.exit(1);
|
|
64
35
|
}
|
|
36
|
+
finally {
|
|
37
|
+
if (transformer) {
|
|
38
|
+
await transformer.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
65
41
|
});
|
|
66
|
-
// program
|
|
67
|
-
// .command('build')
|
|
68
|
-
// .description('Transform all TypeScript files in the project')
|
|
69
|
-
// .option('-c, --config <file>', 'Config file path')
|
|
70
|
-
// .option('--dry-run', 'Show what would be transformed without making changes')
|
|
71
|
-
// .action(async (options: { config?: string, dryRun?: boolean }) => {
|
|
72
|
-
// try {
|
|
73
|
-
// const transformer = new TypicalTransformer();
|
|
74
|
-
// const { glob } = await import('glob');
|
|
75
|
-
// const config = loadConfig(options.config);
|
|
76
|
-
// if (!config.include || config.include.length === 0) {
|
|
77
|
-
// console.error('No include patterns specified in config');
|
|
78
|
-
// process.exit(1);
|
|
79
|
-
// }
|
|
80
|
-
// const files: string[] = [];
|
|
81
|
-
// for (const pattern of config.include) {
|
|
82
|
-
// const matched = await glob(pattern, {
|
|
83
|
-
// ignore: config.exclude,
|
|
84
|
-
// absolute: true
|
|
85
|
-
// });
|
|
86
|
-
// files.push(...matched);
|
|
87
|
-
// }
|
|
88
|
-
// console.log(`Found ${files.length} files to transform`);
|
|
89
|
-
// if (options.dryRun) {
|
|
90
|
-
// files.forEach(file => console.log(`Would transform: ${file}`));
|
|
91
|
-
// return;
|
|
92
|
-
// }
|
|
93
|
-
// let transformed = 0;
|
|
94
|
-
// for (const file of files) {
|
|
95
|
-
// // Double-check with our shared filtering logic
|
|
96
|
-
// if (!shouldIncludeFile(file, config)) {
|
|
97
|
-
// console.log(`Skipping ${file} (excluded by filters)`);
|
|
98
|
-
// continue;
|
|
99
|
-
// }
|
|
100
|
-
// try {
|
|
101
|
-
// console.log(`Transforming ${file}...`);
|
|
102
|
-
// const transformedCode = transformer.transformFile(file, ts);
|
|
103
|
-
// fs.writeFileSync(file, transformedCode);
|
|
104
|
-
// transformed++;
|
|
105
|
-
// } catch (error) {
|
|
106
|
-
// console.error(`Failed to transform ${file}:`, error);
|
|
107
|
-
// }
|
|
108
|
-
// }
|
|
109
|
-
// console.log(`Successfully transformed ${transformed}/${files.length} files`);
|
|
110
|
-
// } catch (error) {
|
|
111
|
-
// console.error('Build failed:', error);
|
|
112
|
-
// process.exit(1);
|
|
113
|
-
// }
|
|
114
|
-
// });
|
|
115
42
|
program.parse();
|
|
116
43
|
//# sourceMappingURL=cli.js.map
|
package/dist/src/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3F,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qDAAqD,CAAC;KAClE,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,CAAC;KAClD,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,cAAc,CAAC;KACjE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,EAAE,eAAe,CAAC;KAC9E,MAAM,CACL,KAAK,EACH,IAAY,EACZ,OAIC,EACD,EAAE;IACF,IAAI,WAAW,GAA8B,IAAI,CAAA;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QACzD,WAAW,GAAG,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;QAE7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAA;YACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAA;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QAEpE,6BAA6B;QAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAE3F,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,CAAC,GAAG,CAAC,+BAA+B,UAAU,EAAE,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAA;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,WAAW,CAAC,KAAK,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;AACH,CAAC,CACF,CAAA;AAEH,OAAO,CAAC,KAAK,EAAE,CAAA"}
|