@elliots/typical 0.2.0 → 0.2.3
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 +45 -6
- package/dist/src/config.d.ts +15 -39
- package/dist/src/config.js +5 -123
- package/dist/src/config.js.map +1 -1
- package/dist/src/transformer.js +2 -1
- package/dist/src/transformer.js.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +20 -158
- package/src/transformer.ts +2 -1
package/README.md
CHANGED
|
@@ -294,6 +294,7 @@ Create a `typical.json` file in your project root (optional):
|
|
|
294
294
|
| `validateCasts` | `false` | Validate type assertions (`as Type`) |
|
|
295
295
|
| `transformJSONParse` | `true` | Transform `JSON.parse` to validate and filter to typed properties |
|
|
296
296
|
| `transformJSONStringify` | `true` | Transform `JSON.stringify` to only include typed properties |
|
|
297
|
+
| `reusableValidators` | `true` | Hoist validators to module scope for reduced code size |
|
|
297
298
|
|
|
298
299
|
---
|
|
299
300
|
|
|
@@ -346,15 +347,15 @@ Typical uses a Go-based compiler that leverages the TypeScript type checker to a
|
|
|
346
347
|
|
|
347
348
|
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.
|
|
348
349
|
|
|
349
|
-
## Compiler
|
|
350
|
+
## Compiler Optimisations
|
|
350
351
|
|
|
351
|
-
The generated validation code is
|
|
352
|
+
The generated validation code is optimised for runtime performance:
|
|
352
353
|
|
|
353
|
-
- **
|
|
354
|
-
- **
|
|
355
|
-
- **
|
|
356
|
-
- **Type-aware dirty tracking** - Primitives stay validated after being passed to functions (they're copied), but objects are re-validated (they could be mutated)
|
|
354
|
+
- **Reusable validators** - When the same type is validated multiple times, Typical hoists the validation logic to a reusable function at module scope. Nested types that appear in multiple places (e.g., `Address` used in both `User` and `Company`) are also extracted and reused.
|
|
355
|
+
- **Smart redundancy elimination** - Skips validation when returning values that are already known to be valid: validated parameters, properties of validated objects, variables assigned from casts or `JSON.parse`, and aliased variables
|
|
356
|
+
- **Type-aware dirty tracking** - Tracks when validated values might become invalid. Primitives stay valid after being passed to functions (they're copied), but objects are re-validated if passed to unknown functions. Pure functions (listed in the config) like `console.log` don't invalidate objects.
|
|
357
357
|
- **Union early bail-out** - Union type checks use if-else chains so the first matching type succeeds immediately
|
|
358
|
+
- **Skip comments** - Add `// @typical-ignore` before a function to skip all validation for it
|
|
358
359
|
|
|
359
360
|
## Debugging
|
|
360
361
|
|
|
@@ -370,3 +371,41 @@ DEBUG=1 npm run build
|
|
|
370
371
|
- Type-only imports of classes aren't checked (can't do instanceof on type-only imports)
|
|
371
372
|
- Validation of functions is just not done. Need to think about that one.
|
|
372
373
|
- Some complex types may not be fully supported yet. If you find any that fail, please open an issue!
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Benchmarks
|
|
378
|
+
|
|
379
|
+
Runtime validation performance comparing Typical vs Zod vs no validation:
|
|
380
|
+
|
|
381
|
+
| Scenario | Nothing | Typical | Zod | vs Nothing | vs Zod |
|
|
382
|
+
| ---------------------------------- | --------: | --------: | --------: | -----------------------------: | ------------------------------: |
|
|
383
|
+
| string | 23.91M/s | 24.86M/s | 24.80M/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{olive}1.0x}}$ |
|
|
384
|
+
| number | 24.33M/s | 25.44M/s | 24.44M/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{olive}1.0x}}$ |
|
|
385
|
+
| boolean | 24.49M/s | 24.49M/s | 24.19M/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{olive}1.0x}}$ |
|
|
386
|
+
| object w/ template literals | 24.53M/s | 21.39M/s | 7.71M/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}2.8x}}$ |
|
|
387
|
+
| nested w/ template literals | 24.69M/s | 8.05M/s | 2.31M/s | ${\textsf{\color{red}0.3x}}$ | ${\textsf{\color{green}3.5x}}$ |
|
|
388
|
+
| array w/ templates (10) | 29.89M/s | 7.10M/s | 1.54M/s | ${\textsf{\color{red}0.2x}}$ | ${\textsf{\color{green}4.6x}}$ |
|
|
389
|
+
| array w/ templates (100) | 30.18M/s | 795.31K/s | 150.09K/s | ${\textsf{\color{red}0.0x}}$ | ${\textsf{\color{green}5.3x}}$ |
|
|
390
|
+
| union types | 29.77M/s | 30.69M/s | 10.76M/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{green}2.9x}}$ |
|
|
391
|
+
| template literals | 30.09M/s | 17.23M/s | 1.71M/s | ${\textsf{\color{red}0.6x}}$ | ${\textsf{\color{green}10.1x}}$ |
|
|
392
|
+
| complex config | 30.56M/s | 29.14M/s | 3.51M/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{green}8.3x}}$ |
|
|
393
|
+
| JSON.parse (small) | 4.61M/s | 4.37M/s | 3.85M/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}1.1x}}$ |
|
|
394
|
+
| JSON.parse (small+filtered extras) | 4.65M/s | 4.32M/s | 3.79M/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}1.1x}}$ |
|
|
395
|
+
| JSON.parse (medium) | 2.85M/s | 2.26M/s | 928.42K/s | ${\textsf{\color{red}0.8x}}$ | ${\textsf{\color{green}2.4x}}$ |
|
|
396
|
+
| JSON.parse (large) | 209.41K/s | 186.91K/s | 99.28K/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}1.9x}}$ |
|
|
397
|
+
| JSON.parse (1000 large) | 211/s | 212/s | 104/s | ${\textsf{\color{olive}1.0x}}$ | ${\textsf{\color{green}2.0x}}$ |
|
|
398
|
+
| JSON.stringify (small) | 9.99M/s | 9.30M/s | 6.70M/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}1.4x}}$ |
|
|
399
|
+
| JSON.stringify (small+extras) | 2.85M/s | 9.20M/s | 6.98M/s | ${\textsf{\color{green}3.2x}}$ | ${\textsf{\color{green}1.3x}}$ |
|
|
400
|
+
| JSON.stringify (medium) | 5.09M/s | 3.82M/s | 1.16M/s | ${\textsf{\color{red}0.8x}}$ | ${\textsf{\color{green}3.3x}}$ |
|
|
401
|
+
| JSON.stringify (large) | 392.53K/s | 330.45K/s | 132.50K/s | ${\textsf{\color{red}0.8x}}$ | ${\textsf{\color{green}2.5x}}$ |
|
|
402
|
+
| JSON.stringify (1000 large) | 362/s | 339/s | 128/s | ${\textsf{\color{olive}0.9x}}$ | ${\textsf{\color{green}2.7x}}$ |
|
|
403
|
+
|
|
404
|
+
- **vs Nothing**: Speed relative to no validation or filtering (1.0x = same speed)
|
|
405
|
+
- **vs Zod**: Speed relative to Zod (1.0x = same speed)
|
|
406
|
+
|
|
407
|
+
Key findings:
|
|
408
|
+
|
|
409
|
+
- Primitives and simple types have near-zero overhead
|
|
410
|
+
- Typical is **2-10x faster than Zod** for complex types
|
|
411
|
+
- JSON operations have minimal overhead while providing full type safety
|
package/dist/src/config.d.ts
CHANGED
|
@@ -21,7 +21,13 @@ export interface TypicalSourceMapConfig {
|
|
|
21
21
|
export interface TypicalConfig {
|
|
22
22
|
include?: string[];
|
|
23
23
|
exclude?: string[];
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Controls whether validators are hoisted to module scope for reuse.
|
|
26
|
+
* - 'auto' (default): Hoist only validators used more than once
|
|
27
|
+
* - 'never': Never hoist, always generate inline validators
|
|
28
|
+
* - 'always': Always hoist validators, even if only used once
|
|
29
|
+
*/
|
|
30
|
+
reusableValidators?: 'auto' | 'never' | 'always';
|
|
25
31
|
validateCasts?: boolean;
|
|
26
32
|
hoistRegex?: boolean;
|
|
27
33
|
debug?: TypicalDebugConfig;
|
|
@@ -31,12 +37,6 @@ export interface TypicalConfig {
|
|
|
31
37
|
* Example: ["React.*", "Express.Request", "*.Event"]
|
|
32
38
|
*/
|
|
33
39
|
ignoreTypes?: string[];
|
|
34
|
-
/**
|
|
35
|
-
* Skip validation for DOM types (Document, Element, Node, etc.) and their subclasses.
|
|
36
|
-
* These types have complex Window intersections that typia cannot process.
|
|
37
|
-
* Default: true
|
|
38
|
-
*/
|
|
39
|
-
ignoreDOMTypes?: boolean;
|
|
40
40
|
/**
|
|
41
41
|
* Validate function parameters and return types at runtime.
|
|
42
42
|
* When enabled, typed function parameters get runtime validation calls injected.
|
|
@@ -60,43 +60,19 @@ export interface TypicalConfig {
|
|
|
60
60
|
* Controls whether and how source maps are generated for transformed code.
|
|
61
61
|
*/
|
|
62
62
|
sourceMap?: TypicalSourceMapConfig;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/** Compiled patterns from DOM_TYPES_TO_IGNORE (when ignoreDOMTypes is true) */
|
|
72
|
-
domPatterns: RegExp[];
|
|
73
|
-
/** All patterns combined for quick checking */
|
|
74
|
-
allPatterns: RegExp[];
|
|
63
|
+
/**
|
|
64
|
+
* Maximum number of helper functions (_io0, _io1, etc.) that can be generated
|
|
65
|
+
* for a single type before erroring. Complex DOM types or library types can
|
|
66
|
+
* generate hundreds of functions which indicates a type that should be excluded.
|
|
67
|
+
* Set to 0 to disable the limit.
|
|
68
|
+
* Default: 50
|
|
69
|
+
*/
|
|
70
|
+
maxGeneratedFunctions?: number;
|
|
75
71
|
}
|
|
76
72
|
export declare const defaultConfig: TypicalConfig;
|
|
77
|
-
/**
|
|
78
|
-
* DOM types that typia cannot process due to Window global intersections.
|
|
79
|
-
* These are the base DOM types - classes extending them are checked separately.
|
|
80
|
-
*/
|
|
81
|
-
export declare const DOM_TYPES_TO_IGNORE: string[];
|
|
82
|
-
/**
|
|
83
|
-
* Convert a glob pattern to a RegExp for type matching.
|
|
84
|
-
* Supports wildcards: "React.*" -> /^React\..*$/
|
|
85
|
-
*/
|
|
86
|
-
export declare function compileIgnorePattern(pattern: string): RegExp | null;
|
|
87
|
-
/**
|
|
88
|
-
* Pre-compile all ignore patterns for efficient matching.
|
|
89
|
-
*/
|
|
90
|
-
export declare function compileIgnorePatterns(config: TypicalConfig): CompiledIgnorePatterns;
|
|
91
|
-
/**
|
|
92
|
-
* Get compiled ignore patterns, using cache if config hasn't changed.
|
|
93
|
-
*/
|
|
94
|
-
export declare function getCompiledIgnorePatterns(config: TypicalConfig): CompiledIgnorePatterns;
|
|
95
73
|
export declare function loadConfig(configPath?: string): TypicalConfig;
|
|
96
74
|
/**
|
|
97
75
|
* Validate and adjust config for consistency.
|
|
98
|
-
* Currently handles:
|
|
99
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
100
76
|
*
|
|
101
77
|
* @param config The config to validate
|
|
102
78
|
* @returns Validated/adjusted config
|
package/dist/src/config.js
CHANGED
|
@@ -1,126 +1,23 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
2
|
include: ['**/*.ts', '**/*.tsx'],
|
|
3
3
|
exclude: ['node_modules/**', '**/*.d.ts', 'dist/**', 'build/**'],
|
|
4
|
-
reusableValidators:
|
|
4
|
+
reusableValidators: 'auto', // Hoists validators to module scope for reduced code size
|
|
5
5
|
validateCasts: false,
|
|
6
6
|
validateFunctions: true,
|
|
7
7
|
transformJSONParse: true,
|
|
8
8
|
transformJSONStringify: true,
|
|
9
9
|
hoistRegex: true,
|
|
10
|
-
ignoreDOMTypes: true,
|
|
11
10
|
debug: {
|
|
12
11
|
writeIntermediateFiles: false,
|
|
13
12
|
},
|
|
14
13
|
sourceMap: {
|
|
15
|
-
enabled: true,
|
|
14
|
+
enabled: true,
|
|
16
15
|
includeContent: true,
|
|
17
16
|
inline: false,
|
|
18
17
|
},
|
|
19
18
|
};
|
|
20
|
-
// FIXME: find a better way to work out which types to ignore
|
|
21
|
-
/**
|
|
22
|
-
* DOM types that typia cannot process due to Window global intersections.
|
|
23
|
-
* These are the base DOM types - classes extending them are checked separately.
|
|
24
|
-
*/
|
|
25
|
-
export const DOM_TYPES_TO_IGNORE = [
|
|
26
|
-
// Core DOM types
|
|
27
|
-
'Document',
|
|
28
|
-
'DocumentFragment',
|
|
29
|
-
'Element',
|
|
30
|
-
'Node',
|
|
31
|
-
'ShadowRoot',
|
|
32
|
-
'Window',
|
|
33
|
-
'EventTarget',
|
|
34
|
-
// HTML Elements
|
|
35
|
-
'HTML*Element',
|
|
36
|
-
'HTMLElement',
|
|
37
|
-
'HTMLCollection',
|
|
38
|
-
// SVG Elements
|
|
39
|
-
'SVG*Element',
|
|
40
|
-
'SVGElement',
|
|
41
|
-
// Events
|
|
42
|
-
'*Event',
|
|
43
|
-
// Other common DOM types
|
|
44
|
-
'NodeList',
|
|
45
|
-
'DOMTokenList',
|
|
46
|
-
'NamedNodeMap',
|
|
47
|
-
'CSSStyleDeclaration',
|
|
48
|
-
'Selection',
|
|
49
|
-
'Range',
|
|
50
|
-
'Text',
|
|
51
|
-
'Comment',
|
|
52
|
-
'CDATASection',
|
|
53
|
-
'ProcessingInstruction',
|
|
54
|
-
'DocumentType',
|
|
55
|
-
'Attr',
|
|
56
|
-
'Table',
|
|
57
|
-
'TableRow',
|
|
58
|
-
'TableCell',
|
|
59
|
-
'StyleSheet',
|
|
60
|
-
];
|
|
61
19
|
import fs from 'fs';
|
|
62
20
|
import path from 'path';
|
|
63
|
-
/**
|
|
64
|
-
* Convert a glob pattern to a RegExp for type matching.
|
|
65
|
-
* Supports wildcards: "React.*" -> /^React\..*$/
|
|
66
|
-
*/
|
|
67
|
-
export function compileIgnorePattern(pattern) {
|
|
68
|
-
try {
|
|
69
|
-
const regexStr = '^' +
|
|
70
|
-
pattern
|
|
71
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except *
|
|
72
|
-
.replace(/\*/g, '.*') +
|
|
73
|
-
'$';
|
|
74
|
-
return new RegExp(regexStr);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.warn(`TYPICAL: Invalid ignoreTypes pattern "${pattern}": ${error.message}`);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Pre-compile all ignore patterns for efficient matching.
|
|
83
|
-
*/
|
|
84
|
-
export function compileIgnorePatterns(config) {
|
|
85
|
-
const userPatterns = [];
|
|
86
|
-
const domPatterns = [];
|
|
87
|
-
// Compile user patterns
|
|
88
|
-
for (const pattern of config.ignoreTypes ?? []) {
|
|
89
|
-
const compiled = compileIgnorePattern(pattern);
|
|
90
|
-
if (compiled) {
|
|
91
|
-
userPatterns.push(compiled);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// Compile DOM patterns if enabled (default: true)
|
|
95
|
-
if (config.ignoreDOMTypes !== false) {
|
|
96
|
-
for (const pattern of DOM_TYPES_TO_IGNORE) {
|
|
97
|
-
const compiled = compileIgnorePattern(pattern);
|
|
98
|
-
if (compiled) {
|
|
99
|
-
domPatterns.push(compiled);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
userPatterns,
|
|
105
|
-
domPatterns,
|
|
106
|
-
allPatterns: [...userPatterns, ...domPatterns],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
// Cache for compiled patterns, keyed by config identity
|
|
110
|
-
let cachedPatterns = null;
|
|
111
|
-
let cachedConfig = null;
|
|
112
|
-
/**
|
|
113
|
-
* Get compiled ignore patterns, using cache if config hasn't changed.
|
|
114
|
-
*/
|
|
115
|
-
export function getCompiledIgnorePatterns(config) {
|
|
116
|
-
// Simple identity check - if same config object, use cache
|
|
117
|
-
if (cachedConfig === config && cachedPatterns) {
|
|
118
|
-
return cachedPatterns;
|
|
119
|
-
}
|
|
120
|
-
cachedConfig = config;
|
|
121
|
-
cachedPatterns = compileIgnorePatterns(config);
|
|
122
|
-
return cachedPatterns;
|
|
123
|
-
}
|
|
124
21
|
export function loadConfig(configPath) {
|
|
125
22
|
const configFile = configPath || path.join(process.cwd(), 'typical.json');
|
|
126
23
|
if (fs.existsSync(configFile)) {
|
|
@@ -139,30 +36,15 @@ export function loadConfig(configPath) {
|
|
|
139
36
|
}
|
|
140
37
|
return defaultConfig;
|
|
141
38
|
}
|
|
142
|
-
let warnedAboutSourceMaps = false;
|
|
143
39
|
/**
|
|
144
40
|
* Validate and adjust config for consistency.
|
|
145
|
-
* Currently handles:
|
|
146
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
147
41
|
*
|
|
148
42
|
* @param config The config to validate
|
|
149
43
|
* @returns Validated/adjusted config
|
|
150
44
|
*/
|
|
151
45
|
export function validateConfig(config) {
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
// With reusable validators, the expanded typia code would all map to the validator
|
|
156
|
-
// declaration rather than the individual usage sites.
|
|
157
|
-
const sourceMapEnabled = config.sourceMap?.enabled !== false;
|
|
158
|
-
const reusableValidatorsEnabled = config.reusableValidators === true;
|
|
159
|
-
if (sourceMapEnabled && reusableValidatorsEnabled) {
|
|
160
|
-
if (!warnedAboutSourceMaps) {
|
|
161
|
-
warnedAboutSourceMaps = true;
|
|
162
|
-
console.warn('TYPICAL: Both sourceMap and reusableValidators are enabled. ' + 'Disabling reusableValidators for accurate source mapping. ' + 'For production builds, set sourceMap.enabled: false to use reusableValidators.');
|
|
163
|
-
}
|
|
164
|
-
result = { ...result, reusableValidators: false };
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
46
|
+
// Reusable validators now throw at the call site, so they work correctly
|
|
47
|
+
// with source maps. No need for special handling.
|
|
48
|
+
return config;
|
|
167
49
|
}
|
|
168
50
|
//# sourceMappingURL=config.js.map
|
package/dist/src/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AA0EA,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,OAAO,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAChC,OAAO,EAAE,CAAC,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC;IAChE,kBAAkB,EAAE,MAAM,EAAE,0DAA0D;IACtF,aAAa,EAAE,KAAK;IACpB,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,IAAI;IACxB,sBAAsB,EAAE,IAAI;IAC5B,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE;QACL,sBAAsB,EAAE,KAAK;KAC9B;IACD,SAAS,EAAE;QACT,OAAO,EAAE,IAAI;QACb,cAAc,EAAE,IAAI;QACpB,MAAM,EAAE,KAAK;KACd;CACF,CAAA;AAED,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,MAAM,UAAU,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAA;IAEzE,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;YACzD,MAAM,UAAU,GAA2B,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;YAEpE,OAAO;gBACL,GAAG,aAAa;gBAChB,GAAG,UAAU;aACd,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,+BAA+B,UAAU,GAAG,EAAE,KAAK,CAAC,CAAA;YACjE,OAAO,aAAa,CAAA;QACtB,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAA;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,yEAAyE;IACzE,kDAAkD;IAClD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/dist/src/transformer.js
CHANGED
|
@@ -45,7 +45,8 @@ export class TypicalTransformer {
|
|
|
45
45
|
}
|
|
46
46
|
await this.ensureInitialized();
|
|
47
47
|
const resolvedPath = resolve(fileName);
|
|
48
|
-
|
|
48
|
+
// Pass config options to the Go compiler
|
|
49
|
+
const result = await this.compiler.transformFile(this.projectHandle, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions, this.config.reusableValidators);
|
|
49
50
|
return {
|
|
50
51
|
code: result.code,
|
|
51
52
|
map: result.sourceMap ?? null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformer.js","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAyC,MAAM,2BAA2B,CAAA;AAElG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAOxC,MAAM,OAAO,kBAAkB;IACtB,MAAM,CAAe;IACpB,QAAQ,CAAiB;IACzB,aAAa,GAAyB,IAAI,CAAA;IAC1C,WAAW,GAAyB,IAAI,CAAA;IACxC,UAAU,CAAQ;IAE1B,YAAY,MAAsB,EAAE,aAAqB,eAAe;QACtE,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,UAAU,EAAE,CAAA;QACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;gBAC3B,IAAI,CAAC,aAAa,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YACvE,CAAC,CAAC,EAAE,CAAA;QACN,CAAC;QACD,MAAM,IAAI,CAAC,WAAW,CAAA;IACxB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,QAAgB,EAAE,OAAoB,IAAI;QACxD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;QACpF,CAAC;QAED,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAE9B,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,aAAc,EAAE,YAAY,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"transformer.js","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAyC,MAAM,2BAA2B,CAAA;AAElG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAOxC,MAAM,OAAO,kBAAkB;IACtB,MAAM,CAAe;IACpB,QAAQ,CAAiB;IACzB,aAAa,GAAyB,IAAI,CAAA;IAC1C,WAAW,GAAyB,IAAI,CAAA;IACxC,UAAU,CAAQ;IAE1B,YAAY,MAAsB,EAAE,aAAqB,eAAe;QACtE,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,UAAU,EAAE,CAAA;QACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;gBAC3B,IAAI,CAAC,aAAa,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YACvE,CAAC,CAAC,EAAE,CAAA;QACN,CAAC;QACD,MAAM,IAAI,CAAC,WAAW,CAAA;IACxB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,SAAS,CAAC,QAAgB,EAAE,OAAoB,IAAI;QACxD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;QACpF,CAAC;QAED,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAE9B,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;QACtC,yCAAyC;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,aAAc,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;QAE/K,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;SAC9B,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;IAC7B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliots/typical",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Runtime safe TypeScript transformer using typia",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"runtime",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"commander": "14.0.2",
|
|
43
|
-
"@elliots/typical-compiler": "0.2.
|
|
43
|
+
"@elliots/typical-compiler": "0.2.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "22",
|
package/src/config.ts
CHANGED
|
@@ -23,7 +23,13 @@ export interface TypicalSourceMapConfig {
|
|
|
23
23
|
export interface TypicalConfig {
|
|
24
24
|
include?: string[]
|
|
25
25
|
exclude?: string[]
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Controls whether validators are hoisted to module scope for reuse.
|
|
28
|
+
* - 'auto' (default): Hoist only validators used more than once
|
|
29
|
+
* - 'never': Never hoist, always generate inline validators
|
|
30
|
+
* - 'always': Always hoist validators, even if only used once
|
|
31
|
+
*/
|
|
32
|
+
reusableValidators?: 'auto' | 'never' | 'always'
|
|
27
33
|
validateCasts?: boolean
|
|
28
34
|
hoistRegex?: boolean
|
|
29
35
|
debug?: TypicalDebugConfig
|
|
@@ -33,12 +39,6 @@ export interface TypicalConfig {
|
|
|
33
39
|
* Example: ["React.*", "Express.Request", "*.Event"]
|
|
34
40
|
*/
|
|
35
41
|
ignoreTypes?: string[]
|
|
36
|
-
/**
|
|
37
|
-
* Skip validation for DOM types (Document, Element, Node, etc.) and their subclasses.
|
|
38
|
-
* These types have complex Window intersections that typia cannot process.
|
|
39
|
-
* Default: true
|
|
40
|
-
*/
|
|
41
|
-
ignoreDOMTypes?: boolean
|
|
42
42
|
/**
|
|
43
43
|
* Validate function parameters and return types at runtime.
|
|
44
44
|
* When enabled, typed function parameters get runtime validation calls injected.
|
|
@@ -62,155 +62,38 @@ export interface TypicalConfig {
|
|
|
62
62
|
* Controls whether and how source maps are generated for transformed code.
|
|
63
63
|
*/
|
|
64
64
|
sourceMap?: TypicalSourceMapConfig
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
userPatterns: RegExp[]
|
|
74
|
-
/** Compiled patterns from DOM_TYPES_TO_IGNORE (when ignoreDOMTypes is true) */
|
|
75
|
-
domPatterns: RegExp[]
|
|
76
|
-
/** All patterns combined for quick checking */
|
|
77
|
-
allPatterns: RegExp[]
|
|
65
|
+
/**
|
|
66
|
+
* Maximum number of helper functions (_io0, _io1, etc.) that can be generated
|
|
67
|
+
* for a single type before erroring. Complex DOM types or library types can
|
|
68
|
+
* generate hundreds of functions which indicates a type that should be excluded.
|
|
69
|
+
* Set to 0 to disable the limit.
|
|
70
|
+
* Default: 50
|
|
71
|
+
*/
|
|
72
|
+
maxGeneratedFunctions?: number
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
export const defaultConfig: TypicalConfig = {
|
|
81
76
|
include: ['**/*.ts', '**/*.tsx'],
|
|
82
77
|
exclude: ['node_modules/**', '**/*.d.ts', 'dist/**', 'build/**'],
|
|
83
|
-
reusableValidators:
|
|
78
|
+
reusableValidators: 'auto', // Hoists validators to module scope for reduced code size
|
|
84
79
|
validateCasts: false,
|
|
85
80
|
validateFunctions: true,
|
|
86
81
|
transformJSONParse: true,
|
|
87
82
|
transformJSONStringify: true,
|
|
88
83
|
hoistRegex: true,
|
|
89
|
-
ignoreDOMTypes: true,
|
|
90
84
|
debug: {
|
|
91
85
|
writeIntermediateFiles: false,
|
|
92
86
|
},
|
|
93
87
|
sourceMap: {
|
|
94
|
-
enabled: true,
|
|
88
|
+
enabled: true,
|
|
95
89
|
includeContent: true,
|
|
96
90
|
inline: false,
|
|
97
91
|
},
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
// FIXME: find a better way to work out which types to ignore
|
|
101
|
-
/**
|
|
102
|
-
* DOM types that typia cannot process due to Window global intersections.
|
|
103
|
-
* These are the base DOM types - classes extending them are checked separately.
|
|
104
|
-
*/
|
|
105
|
-
export const DOM_TYPES_TO_IGNORE = [
|
|
106
|
-
// Core DOM types
|
|
107
|
-
'Document',
|
|
108
|
-
'DocumentFragment',
|
|
109
|
-
'Element',
|
|
110
|
-
'Node',
|
|
111
|
-
'ShadowRoot',
|
|
112
|
-
'Window',
|
|
113
|
-
'EventTarget',
|
|
114
|
-
// HTML Elements
|
|
115
|
-
'HTML*Element',
|
|
116
|
-
'HTMLElement',
|
|
117
|
-
'HTMLCollection',
|
|
118
|
-
// SVG Elements
|
|
119
|
-
'SVG*Element',
|
|
120
|
-
'SVGElement',
|
|
121
|
-
// Events
|
|
122
|
-
'*Event',
|
|
123
|
-
// Other common DOM types
|
|
124
|
-
'NodeList',
|
|
125
|
-
'DOMTokenList',
|
|
126
|
-
'NamedNodeMap',
|
|
127
|
-
'CSSStyleDeclaration',
|
|
128
|
-
'Selection',
|
|
129
|
-
'Range',
|
|
130
|
-
'Text',
|
|
131
|
-
'Comment',
|
|
132
|
-
'CDATASection',
|
|
133
|
-
'ProcessingInstruction',
|
|
134
|
-
'DocumentType',
|
|
135
|
-
'Attr',
|
|
136
|
-
'Table',
|
|
137
|
-
'TableRow',
|
|
138
|
-
'TableCell',
|
|
139
|
-
'StyleSheet',
|
|
140
|
-
]
|
|
141
|
-
|
|
142
94
|
import fs from 'fs'
|
|
143
95
|
import path from 'path'
|
|
144
96
|
|
|
145
|
-
/**
|
|
146
|
-
* Convert a glob pattern to a RegExp for type matching.
|
|
147
|
-
* Supports wildcards: "React.*" -> /^React\..*$/
|
|
148
|
-
*/
|
|
149
|
-
export function compileIgnorePattern(pattern: string): RegExp | null {
|
|
150
|
-
try {
|
|
151
|
-
const regexStr =
|
|
152
|
-
'^' +
|
|
153
|
-
pattern
|
|
154
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except *
|
|
155
|
-
.replace(/\*/g, '.*') +
|
|
156
|
-
'$'
|
|
157
|
-
return new RegExp(regexStr)
|
|
158
|
-
} catch (error) {
|
|
159
|
-
console.warn(`TYPICAL: Invalid ignoreTypes pattern "${pattern}": ${(error as Error).message}`)
|
|
160
|
-
return null
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Pre-compile all ignore patterns for efficient matching.
|
|
166
|
-
*/
|
|
167
|
-
export function compileIgnorePatterns(config: TypicalConfig): CompiledIgnorePatterns {
|
|
168
|
-
const userPatterns: RegExp[] = []
|
|
169
|
-
const domPatterns: RegExp[] = []
|
|
170
|
-
|
|
171
|
-
// Compile user patterns
|
|
172
|
-
for (const pattern of config.ignoreTypes ?? []) {
|
|
173
|
-
const compiled = compileIgnorePattern(pattern)
|
|
174
|
-
if (compiled) {
|
|
175
|
-
userPatterns.push(compiled)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Compile DOM patterns if enabled (default: true)
|
|
180
|
-
if (config.ignoreDOMTypes !== false) {
|
|
181
|
-
for (const pattern of DOM_TYPES_TO_IGNORE) {
|
|
182
|
-
const compiled = compileIgnorePattern(pattern)
|
|
183
|
-
if (compiled) {
|
|
184
|
-
domPatterns.push(compiled)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
userPatterns,
|
|
191
|
-
domPatterns,
|
|
192
|
-
allPatterns: [...userPatterns, ...domPatterns],
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Cache for compiled patterns, keyed by config identity
|
|
197
|
-
let cachedPatterns: CompiledIgnorePatterns | null = null
|
|
198
|
-
let cachedConfig: TypicalConfig | null = null
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Get compiled ignore patterns, using cache if config hasn't changed.
|
|
202
|
-
*/
|
|
203
|
-
export function getCompiledIgnorePatterns(config: TypicalConfig): CompiledIgnorePatterns {
|
|
204
|
-
// Simple identity check - if same config object, use cache
|
|
205
|
-
if (cachedConfig === config && cachedPatterns) {
|
|
206
|
-
return cachedPatterns
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
cachedConfig = config
|
|
210
|
-
cachedPatterns = compileIgnorePatterns(config)
|
|
211
|
-
return cachedPatterns
|
|
212
|
-
}
|
|
213
|
-
|
|
214
97
|
export function loadConfig(configPath?: string): TypicalConfig {
|
|
215
98
|
const configFile = configPath || path.join(process.cwd(), 'typical.json')
|
|
216
99
|
|
|
@@ -232,35 +115,14 @@ export function loadConfig(configPath?: string): TypicalConfig {
|
|
|
232
115
|
return defaultConfig
|
|
233
116
|
}
|
|
234
117
|
|
|
235
|
-
let warnedAboutSourceMaps = false
|
|
236
|
-
|
|
237
118
|
/**
|
|
238
119
|
* Validate and adjust config for consistency.
|
|
239
|
-
* Currently handles:
|
|
240
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
241
120
|
*
|
|
242
121
|
* @param config The config to validate
|
|
243
122
|
* @returns Validated/adjusted config
|
|
244
123
|
*/
|
|
245
124
|
export function validateConfig(config: TypicalConfig): TypicalConfig {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// call needs its own source map marker pointing to the correct type annotation.
|
|
250
|
-
// With reusable validators, the expanded typia code would all map to the validator
|
|
251
|
-
// declaration rather than the individual usage sites.
|
|
252
|
-
const sourceMapEnabled = config.sourceMap?.enabled !== false
|
|
253
|
-
const reusableValidatorsEnabled = config.reusableValidators === true
|
|
254
|
-
|
|
255
|
-
if (sourceMapEnabled && reusableValidatorsEnabled) {
|
|
256
|
-
if (!warnedAboutSourceMaps) {
|
|
257
|
-
warnedAboutSourceMaps = true
|
|
258
|
-
console.warn(
|
|
259
|
-
'TYPICAL: Both sourceMap and reusableValidators are enabled. ' + 'Disabling reusableValidators for accurate source mapping. ' + 'For production builds, set sourceMap.enabled: false to use reusableValidators.',
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
|
-
result = { ...result, reusableValidators: false }
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return result
|
|
125
|
+
// Reusable validators now throw at the call site, so they work correctly
|
|
126
|
+
// with source maps. No need for special handling.
|
|
127
|
+
return config
|
|
266
128
|
}
|
package/src/transformer.ts
CHANGED
|
@@ -58,7 +58,8 @@ export class TypicalTransformer {
|
|
|
58
58
|
await this.ensureInitialized()
|
|
59
59
|
|
|
60
60
|
const resolvedPath = resolve(fileName)
|
|
61
|
-
|
|
61
|
+
// Pass config options to the Go compiler
|
|
62
|
+
const result = await this.compiler.transformFile(this.projectHandle!, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions, this.config.reusableValidators)
|
|
62
63
|
|
|
63
64
|
return {
|
|
64
65
|
code: result.code,
|