@elliots/typical 0.2.1 → 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;
@@ -67,8 +73,6 @@ export declare const defaultConfig: TypicalConfig;
67
73
  export declare function loadConfig(configPath?: string): TypicalConfig;
68
74
  /**
69
75
  * Validate and adjust config for consistency.
70
- * Currently handles:
71
- * - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
72
76
  *
73
77
  * @param config The config to validate
74
78
  * @returns Validated/adjusted config
@@ -1,7 +1,7 @@
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,
@@ -11,7 +11,7 @@ export const defaultConfig = {
11
11
  writeIntermediateFiles: false,
12
12
  },
13
13
  sourceMap: {
14
- enabled: true, // On by default for debugging (set to false for production)
14
+ enabled: true,
15
15
  includeContent: true,
16
16
  inline: false,
17
17
  },
@@ -36,30 +36,15 @@ export function loadConfig(configPath) {
36
36
  }
37
37
  return defaultConfig;
38
38
  }
39
- let warnedAboutSourceMaps = false;
40
39
  /**
41
40
  * Validate and adjust config for consistency.
42
- * Currently handles:
43
- * - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
44
41
  *
45
42
  * @param config The config to validate
46
43
  * @returns Validated/adjusted config
47
44
  */
48
45
  export function validateConfig(config) {
49
- let result = config;
50
- // Source maps require inline validators (not reusable) because each validation
51
- // call needs its own source map marker pointing to the correct type annotation.
52
- // With reusable validators, the expanded typia code would all map to the validator
53
- // declaration rather than the individual usage sites.
54
- const sourceMapEnabled = config.sourceMap?.enabled !== false;
55
- const reusableValidatorsEnabled = config.reusableValidators === true;
56
- if (sourceMapEnabled && reusableValidatorsEnabled) {
57
- if (!warnedAboutSourceMaps) {
58
- warnedAboutSourceMaps = true;
59
- 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.');
60
- }
61
- result = { ...result, reusableValidators: false };
62
- }
63
- 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;
64
49
  }
65
50
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAoEA,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,KAAK,EAAE,uEAAuE;IAClG,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,EAAE,4DAA4D;QAC3E,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,IAAI,qBAAqB,GAAG,KAAK,CAAA;AAEjC;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,IAAI,MAAM,GAAG,MAAM,CAAA;IAEnB,+EAA+E;IAC/E,gFAAgF;IAChF,mFAAmF;IACnF,sDAAsD;IACtD,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,EAAE,OAAO,KAAK,KAAK,CAAA;IAC5D,MAAM,yBAAyB,GAAG,MAAM,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAEpE,IAAI,gBAAgB,IAAI,yBAAyB,EAAE,CAAC;QAClD,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC3B,qBAAqB,GAAG,IAAI,CAAA;YAC5B,OAAO,CAAC,IAAI,CACV,8DAA8D,GAAG,4DAA4D,GAAG,gFAAgF,CACjN,CAAA;QACH,CAAC;QACD,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAA;IACnD,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
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,8 +45,8 @@ export class TypicalTransformer {
45
45
  }
46
46
  await this.ensureInitialized();
47
47
  const resolvedPath = resolve(fileName);
48
- // Pass ignoreTypes and maxGeneratedFunctions from config to the Go compiler
49
- const result = await this.compiler.transformFile(this.projectHandle, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions);
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);
50
50
  return {
51
51
  code: result.code,
52
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,4EAA4E;QAC5E,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,CAAC,CAAA;QAE/I,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.1",
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.1"
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
@@ -69,7 +75,7 @@ export interface TypicalConfig {
69
75
  export const defaultConfig: TypicalConfig = {
70
76
  include: ['**/*.ts', '**/*.tsx'],
71
77
  exclude: ['node_modules/**', '**/*.d.ts', 'dist/**', 'build/**'],
72
- 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
73
79
  validateCasts: false,
74
80
  validateFunctions: true,
75
81
  transformJSONParse: true,
@@ -79,7 +85,7 @@ export const defaultConfig: TypicalConfig = {
79
85
  writeIntermediateFiles: false,
80
86
  },
81
87
  sourceMap: {
82
- enabled: true, // On by default for debugging (set to false for production)
88
+ enabled: true,
83
89
  includeContent: true,
84
90
  inline: false,
85
91
  },
@@ -109,35 +115,14 @@ export function loadConfig(configPath?: string): TypicalConfig {
109
115
  return defaultConfig
110
116
  }
111
117
 
112
- let warnedAboutSourceMaps = false
113
-
114
118
  /**
115
119
  * Validate and adjust config for consistency.
116
- * Currently handles:
117
- * - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
118
120
  *
119
121
  * @param config The config to validate
120
122
  * @returns Validated/adjusted config
121
123
  */
122
124
  export function validateConfig(config: TypicalConfig): TypicalConfig {
123
- let result = config
124
-
125
- // Source maps require inline validators (not reusable) because each validation
126
- // call needs its own source map marker pointing to the correct type annotation.
127
- // With reusable validators, the expanded typia code would all map to the validator
128
- // declaration rather than the individual usage sites.
129
- const sourceMapEnabled = config.sourceMap?.enabled !== false
130
- const reusableValidatorsEnabled = config.reusableValidators === true
131
-
132
- if (sourceMapEnabled && reusableValidatorsEnabled) {
133
- if (!warnedAboutSourceMaps) {
134
- warnedAboutSourceMaps = true
135
- console.warn(
136
- 'TYPICAL: Both sourceMap and reusableValidators are enabled. ' + 'Disabling reusableValidators for accurate source mapping. ' + 'For production builds, set sourceMap.enabled: false to use reusableValidators.',
137
- )
138
- }
139
- result = { ...result, reusableValidators: false }
140
- }
141
-
142
- 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
143
128
  }
@@ -58,8 +58,8 @@ export class TypicalTransformer {
58
58
  await this.ensureInitialized()
59
59
 
60
60
  const resolvedPath = resolve(fileName)
61
- // Pass ignoreTypes and maxGeneratedFunctions from config to the Go compiler
62
- const result = await this.compiler.transformFile(this.projectHandle!, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions)
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)
63
63
 
64
64
  return {
65
65
  code: result.code,