@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 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 Optimizations
350
+ ## Compiler Optimisations
350
351
 
351
- The generated validation code is optimized for runtime performance:
352
+ The generated validation code is optimised for runtime performance:
352
353
 
353
- - **Skip redundant validation** - When returning a validated parameter directly, the return validation is skipped (e.g., `return x` where `x: string` was already validated)
354
- - **Subtype-aware skipping** - If a validated type is assignable to the return type, validation is skipped (e.g., `string` parameter returned as `string | null`)
355
- - **Property chain tracking** - Accessing properties of validated objects skips re-validation (e.g., `return user.name` when `user: User` was validated)
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
@@ -21,7 +21,13 @@ export interface TypicalSourceMapConfig {
21
21
  export interface TypicalConfig {
22
22
  include?: string[];
23
23
  exclude?: string[];
24
- reusableValidators?: boolean;
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
- * Pre-compiled regex patterns for ignore type matching.
66
- * This is populated during config loading for performance.
67
- */
68
- export interface CompiledIgnorePatterns {
69
- /** Compiled patterns from user ignoreTypes config */
70
- userPatterns: RegExp[];
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
@@ -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: false, // Off by default for accurate source maps (set to true for production)
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, // On by default for debugging (set to false for production)
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
- let result = config;
153
- // Source maps require inline validators (not reusable) because each validation
154
- // call needs its own source map marker pointing to the correct type annotation.
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
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
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"}
@@ -45,7 +45,8 @@ export class TypicalTransformer {
45
45
  }
46
46
  await this.ensureInitialized();
47
47
  const resolvedPath = resolve(fileName);
48
- const result = await this.compiler.transformFile(this.projectHandle, resolvedPath);
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;QAEnF,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"}
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.0",
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.0"
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
- reusableValidators?: boolean
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
- * Pre-compiled regex patterns for ignore type matching.
69
- * This is populated during config loading for performance.
70
- */
71
- export interface CompiledIgnorePatterns {
72
- /** Compiled patterns from user ignoreTypes config */
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: false, // Off by default for accurate source maps (set to true for production)
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, // On by default for debugging (set to false for production)
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
- let result = config
247
-
248
- // Source maps require inline validators (not reusable) because each validation
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
  }
@@ -58,7 +58,8 @@ export class TypicalTransformer {
58
58
  await this.ensureInitialized()
59
59
 
60
60
  const resolvedPath = resolve(fileName)
61
- const result = await this.compiler.transformFile(this.projectHandle!, resolvedPath)
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,