@elliots/typical 0.2.1 → 0.2.4
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/LICENSE +21 -0
- package/README.md +118 -10
- package/dist/src/config.d.ts +0 -3
- package/dist/src/config.js +4 -20
- package/dist/src/config.js.map +1 -1
- package/dist/src/transformer.js +2 -2
- package/dist/src/transformer.js.map +1 -1
- package/package.json +5 -5
- package/src/config.ts +4 -27
- package/src/transformer.ts +2 -2
- package/dist/src/cli.typical.ts +0 -136
- package/dist/src/config.typical.ts +0 -287
- package/dist/src/file-filter.d.ts +0 -13
- package/dist/src/file-filter.js +0 -42
- package/dist/src/file-filter.js.map +0 -1
- package/dist/src/program-manager.d.ts +0 -27
- package/dist/src/program-manager.js +0 -121
- package/dist/src/program-manager.js.map +0 -1
- package/dist/src/regex-hoister.d.ts +0 -11
- package/dist/src/regex-hoister.js +0 -150
- package/dist/src/regex-hoister.js.map +0 -1
- package/dist/src/setup.d.ts +0 -2
- package/dist/src/setup.js +0 -20
- package/dist/src/setup.js.map +0 -1
- package/dist/src/source-map.d.ts +0 -78
- package/dist/src/source-map.js +0 -133
- package/dist/src/source-map.js.map +0 -1
- package/dist/src/source-map.typical.ts +0 -216
- package/dist/src/transformer.typical.ts +0 -2552
- package/dist/src/tsc-plugin.d.ts +0 -10
- package/dist/src/tsc-plugin.js +0 -13
- package/dist/src/tsc-plugin.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elliot Shepherd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -346,15 +346,59 @@ Typical uses a Go-based compiler that leverages the TypeScript type checker to a
|
|
|
346
346
|
|
|
347
347
|
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
348
|
|
|
349
|
-
## Compiler
|
|
349
|
+
## Compiler Optimisations
|
|
350
350
|
|
|
351
|
-
The generated validation code is
|
|
351
|
+
The generated validation code is optimised for runtime performance:
|
|
352
352
|
|
|
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)
|
|
353
|
+
- **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.
|
|
354
|
+
- **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
|
|
355
|
+
- **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
356
|
- **Union early bail-out** - Union type checks use if-else chains so the first matching type succeeds immediately
|
|
357
|
+
- **Skip comments** - Add `// @typical-ignore` before a function to skip all validation for it
|
|
358
|
+
|
|
359
|
+
## VSCode Extension
|
|
360
|
+
|
|
361
|
+
A VSCode extension is available that shows runtime validation indicators directly in your editor. It's not yet published to the marketplace, but you can build and install it locally.
|
|
362
|
+
|
|
363
|
+
### Features
|
|
364
|
+
|
|
365
|
+
- **Subtle underlines** on validated parameters, return values, type casts, and JSON operations
|
|
366
|
+
- Green dotted underline = validated at runtime
|
|
367
|
+
- Grey dotted underline = skipped (e.g., generic types)
|
|
368
|
+
- **Hover tooltips** explaining what's being validated and why
|
|
369
|
+
- **Optional inlay hints** showing validation status inline
|
|
370
|
+
- **Preview command** to see the compiled output with validation code
|
|
371
|
+
|
|
372
|
+
### Building and Installing
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Navigate to the extension directory
|
|
376
|
+
cd packages/vscode-extension
|
|
377
|
+
|
|
378
|
+
# Install dependencies
|
|
379
|
+
pnpm install
|
|
380
|
+
|
|
381
|
+
# Build and package the extension
|
|
382
|
+
pnpm run build
|
|
383
|
+
pnpm run package
|
|
384
|
+
|
|
385
|
+
# Install the .vsix file
|
|
386
|
+
code --install-extension typical-vscode-0.0.1.vsix
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Or use the convenience script:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
cd packages/vscode-extension
|
|
393
|
+
pnpm run dev-install
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Requirements
|
|
397
|
+
|
|
398
|
+
- Your project must have `@elliots/typical` or `@elliots/typical-compiler` as a dependency
|
|
399
|
+
- The extension uses the compiler binary from your project's `node_modules`
|
|
400
|
+
|
|
401
|
+
---
|
|
358
402
|
|
|
359
403
|
## Debugging
|
|
360
404
|
|
|
@@ -366,7 +410,71 @@ DEBUG=1 npm run build
|
|
|
366
410
|
|
|
367
411
|
## Limitations
|
|
368
412
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
413
|
+
### Types that cannot be validated at runtime
|
|
414
|
+
|
|
415
|
+
These TypeScript features have no runtime representation and are skipped:
|
|
416
|
+
|
|
417
|
+
| Feature | Why | Example |
|
|
418
|
+
| ----------------------- | -------------------------------- | -------------------------------------- |
|
|
419
|
+
| Generic type parameters | No runtime type info for `T` | `function process<T>(x: T): T` |
|
|
420
|
+
| Conditional types | Compile-time only | `T extends string ? A : B` |
|
|
421
|
+
| `infer` keyword | Compile-time type inference | `T extends Array<infer U> ? U : never` |
|
|
422
|
+
| `keyof` operator | Compile-time key extraction | `keyof User` |
|
|
423
|
+
| Indexed access types | Compile-time type lookup | `User['name']` |
|
|
424
|
+
| Unique symbols | Symbol identity not checkable | `declare const id: unique symbol` |
|
|
425
|
+
| Index signature values | Would require iterating all keys | `{ [key: string]: number }` |
|
|
426
|
+
|
|
427
|
+
### Other limitations
|
|
428
|
+
|
|
429
|
+
- **Type-only imports** - `import type { MyClass }` can't use instanceof (class doesn't exist at runtime)
|
|
430
|
+
- **Function signatures** - Only validates `typeof === 'function'`, not parameter/return types
|
|
431
|
+
- **Function overloads** - Validates the implementation signature, not individual overload signatures
|
|
432
|
+
- **Complex library types** - DOM types, React types, etc. may exceed complexity limits (configurable via `maxGeneratedFunctions`)
|
|
433
|
+
|
|
434
|
+
### What IS validated
|
|
435
|
+
|
|
436
|
+
Despite these limitations, Typical validates most practical TypeScript patterns:
|
|
437
|
+
|
|
438
|
+
- All primitive types (string, number, boolean, bigint, symbol, null, undefined)
|
|
439
|
+
- Object properties and nested objects
|
|
440
|
+
- Arrays and tuples (including variadic tuples)
|
|
441
|
+
- Union and intersection types
|
|
442
|
+
- Literal types and template literal types
|
|
443
|
+
- Enums (string and numeric)
|
|
444
|
+
- Utility types (Partial, Required, Pick, Omit, Record, Extract, Exclude)
|
|
445
|
+
- Mapped and conditional types (when resolved to concrete types)
|
|
446
|
+
- Branded/opaque types (validates the underlying primitive)
|
|
447
|
+
- Class instances (via instanceof)
|
|
448
|
+
- Built-in types (Date, Map, Set, URL, Error, etc.)
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Benchmarks
|
|
453
|
+
|
|
454
|
+
Runtime validation performance comparing Typical vs Zod vs no validation:
|
|
455
|
+
|
|
456
|
+
| Scenario | Nothing | Typical | Zod | vs Nothing | vs Zod |
|
|
457
|
+
| ---------------------------------- | --------: | --------: | --------: | ---------: | -------: |
|
|
458
|
+
| string | 23.91M/s | 24.86M/s | 24.80M/s | 🟡 1.0x | 🟡 1.0x |
|
|
459
|
+
| number | 24.33M/s | 25.44M/s | 24.44M/s | 🟡 1.0x | 🟡 1.0x |
|
|
460
|
+
| boolean | 24.49M/s | 24.49M/s | 24.19M/s | 🟡 1.0x | 🟡 1.0x |
|
|
461
|
+
| object w/ template literals | 24.53M/s | 21.39M/s | 7.71M/s | 🟡 0.9x | 🟢 2.8x |
|
|
462
|
+
| nested w/ template literals | 24.69M/s | 8.05M/s | 2.31M/s | 🔴 0.3x | 🟢 3.5x |
|
|
463
|
+
| array w/ templates (10) | 29.89M/s | 7.10M/s | 1.54M/s | 🔴 0.2x | 🟢 4.6x |
|
|
464
|
+
| array w/ templates (100) | 30.18M/s | 795.31K/s | 150.09K/s | 🔴 0.0x | 🟢 5.3x |
|
|
465
|
+
| union types | 29.77M/s | 30.69M/s | 10.76M/s | 🟡 1.0x | 🟢 2.9x |
|
|
466
|
+
| template literals | 30.09M/s | 17.23M/s | 1.71M/s | 🔴 0.6x | 🟢 10.1x |
|
|
467
|
+
| complex config | 30.56M/s | 29.14M/s | 3.51M/s | 🟡 1.0x | 🟢 8.3x |
|
|
468
|
+
| JSON.parse (small) | 4.61M/s | 4.37M/s | 3.85M/s | 🟡 0.9x | 🟢 1.1x |
|
|
469
|
+
| JSON.parse (small+filtered extras) | 4.65M/s | 4.32M/s | 3.79M/s | 🟡 0.9x | 🟢 1.1x |
|
|
470
|
+
| JSON.parse (medium) | 2.85M/s | 2.26M/s | 928.42K/s | 🔴 0.8x | 🟢 2.4x |
|
|
471
|
+
| JSON.parse (large) | 209.41K/s | 186.91K/s | 99.28K/s | 🟡 0.9x | 🟢 1.9x |
|
|
472
|
+
| JSON.parse (1000 large) | 211/s | 212/s | 104/s | 🟡 1.0x | 🟢 2.0x |
|
|
473
|
+
| JSON.stringify (small) | 9.99M/s | 9.30M/s | 6.70M/s | 🟡 0.9x | 🟢 1.4x |
|
|
474
|
+
| JSON.stringify (small+extras) | 2.85M/s | 9.20M/s | 6.98M/s | 🟢 3.2x | 🟢 1.3x |
|
|
475
|
+
| JSON.stringify (medium) | 5.09M/s | 3.82M/s | 1.16M/s | 🔴 0.8x | 🟢 3.3x |
|
|
476
|
+
| JSON.stringify (large) | 392.53K/s | 330.45K/s | 132.50K/s | 🔴 0.8x | 🟢 2.5x |
|
|
477
|
+
| JSON.stringify (1000 large) | 362/s | 339/s | 128/s | 🟡 0.9x | 🟢 2.7x |
|
|
478
|
+
|
|
479
|
+
- **vs Nothing**: Speed relative to no validation or filtering (1.0x = same speed)
|
|
480
|
+
- **vs Zod**: Speed relative to Zod (1.0x = same speed)
|
package/dist/src/config.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface TypicalSourceMapConfig {
|
|
|
21
21
|
export interface TypicalConfig {
|
|
22
22
|
include?: string[];
|
|
23
23
|
exclude?: string[];
|
|
24
|
-
reusableValidators?: boolean;
|
|
25
24
|
validateCasts?: boolean;
|
|
26
25
|
hoistRegex?: boolean;
|
|
27
26
|
debug?: TypicalDebugConfig;
|
|
@@ -67,8 +66,6 @@ export declare const defaultConfig: TypicalConfig;
|
|
|
67
66
|
export declare function loadConfig(configPath?: string): TypicalConfig;
|
|
68
67
|
/**
|
|
69
68
|
* Validate and adjust config for consistency.
|
|
70
|
-
* Currently handles:
|
|
71
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
72
69
|
*
|
|
73
70
|
* @param config The config to validate
|
|
74
71
|
* @returns Validated/adjusted config
|
package/dist/src/config.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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)
|
|
5
4
|
validateCasts: false,
|
|
6
5
|
validateFunctions: true,
|
|
7
6
|
transformJSONParse: true,
|
|
@@ -11,7 +10,7 @@ export const defaultConfig = {
|
|
|
11
10
|
writeIntermediateFiles: false,
|
|
12
11
|
},
|
|
13
12
|
sourceMap: {
|
|
14
|
-
enabled: true,
|
|
13
|
+
enabled: true,
|
|
15
14
|
includeContent: true,
|
|
16
15
|
inline: false,
|
|
17
16
|
},
|
|
@@ -36,30 +35,15 @@ export function loadConfig(configPath) {
|
|
|
36
35
|
}
|
|
37
36
|
return defaultConfig;
|
|
38
37
|
}
|
|
39
|
-
let warnedAboutSourceMaps = false;
|
|
40
38
|
/**
|
|
41
39
|
* Validate and adjust config for consistency.
|
|
42
|
-
* Currently handles:
|
|
43
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
44
40
|
*
|
|
45
41
|
* @param config The config to validate
|
|
46
42
|
* @returns Validated/adjusted config
|
|
47
43
|
*/
|
|
48
44
|
export function validateConfig(config) {
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
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;
|
|
45
|
+
// Reusable validators now throw at the call site, so they work correctly
|
|
46
|
+
// with source maps. No need for special handling.
|
|
47
|
+
return config;
|
|
64
48
|
}
|
|
65
49
|
//# 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":"AAmEA,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,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
|
@@ -23,7 +23,7 @@ export class TypicalTransformer {
|
|
|
23
23
|
* Ensure the Go compiler is started and project is loaded.
|
|
24
24
|
* Uses lazy initialization - only starts on first transform.
|
|
25
25
|
*/
|
|
26
|
-
async ensureInitialized() {
|
|
26
|
+
async ensureInitialized(x) {
|
|
27
27
|
if (!this.initPromise) {
|
|
28
28
|
this.initPromise = (async () => {
|
|
29
29
|
await this.compiler.start();
|
|
@@ -45,7 +45,7 @@ export class TypicalTransformer {
|
|
|
45
45
|
}
|
|
46
46
|
await this.ensureInitialized();
|
|
47
47
|
const resolvedPath = resolve(fileName);
|
|
48
|
-
// Pass
|
|
48
|
+
// Pass config options to the Go compiler
|
|
49
49
|
const result = await this.compiler.transformFile(this.projectHandle, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions);
|
|
50
50
|
return {
|
|
51
51
|
code: result.code,
|
|
@@ -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;
|
|
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,CAAC,CAAU;QACxC,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,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliots/typical",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "22",
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
"lint:fix": "oxlint --type-aware --fix",
|
|
62
62
|
"format": "oxfmt",
|
|
63
63
|
"format:check": "oxfmt --check",
|
|
64
|
-
"build": "
|
|
65
|
-
"build:all": "pnpm run build:go:all && pnpm run build:ts",
|
|
64
|
+
"build": "pnpm run build:go:all && pnpm run build:ts",
|
|
66
65
|
"build:go": "cd packages/compiler && npm run build:go",
|
|
67
66
|
"build:go:all": "./scripts/build-binaries.sh",
|
|
68
|
-
"build:ts": "pnpm --filter './packages/*' run build &&
|
|
67
|
+
"build:ts": "pnpm --filter './packages/*' run build && tsc",
|
|
68
|
+
"build:website": "cd docs/website && pnpm install && pnpm run build",
|
|
69
69
|
"dev": "tsc --watch",
|
|
70
70
|
"test": "rm -f test/test.temp.ts && tsc && node --import ./dist/src/esm-loader-register.js --test dist/test/*.test.js",
|
|
71
71
|
"test:coverage": "rm -f test/test.temp.ts && tsc && node --import ./dist/src/esm-loader-register.js --test --experimental-test-coverage dist/test/*.test.js",
|
package/src/config.ts
CHANGED
|
@@ -23,7 +23,6 @@ export interface TypicalSourceMapConfig {
|
|
|
23
23
|
export interface TypicalConfig {
|
|
24
24
|
include?: string[]
|
|
25
25
|
exclude?: string[]
|
|
26
|
-
reusableValidators?: boolean
|
|
27
26
|
validateCasts?: boolean
|
|
28
27
|
hoistRegex?: boolean
|
|
29
28
|
debug?: TypicalDebugConfig
|
|
@@ -69,7 +68,6 @@ export interface TypicalConfig {
|
|
|
69
68
|
export const defaultConfig: TypicalConfig = {
|
|
70
69
|
include: ['**/*.ts', '**/*.tsx'],
|
|
71
70
|
exclude: ['node_modules/**', '**/*.d.ts', 'dist/**', 'build/**'],
|
|
72
|
-
reusableValidators: false, // Off by default for accurate source maps (set to true for production)
|
|
73
71
|
validateCasts: false,
|
|
74
72
|
validateFunctions: true,
|
|
75
73
|
transformJSONParse: true,
|
|
@@ -79,7 +77,7 @@ export const defaultConfig: TypicalConfig = {
|
|
|
79
77
|
writeIntermediateFiles: false,
|
|
80
78
|
},
|
|
81
79
|
sourceMap: {
|
|
82
|
-
enabled: true,
|
|
80
|
+
enabled: true,
|
|
83
81
|
includeContent: true,
|
|
84
82
|
inline: false,
|
|
85
83
|
},
|
|
@@ -109,35 +107,14 @@ export function loadConfig(configPath?: string): TypicalConfig {
|
|
|
109
107
|
return defaultConfig
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
let warnedAboutSourceMaps = false
|
|
113
|
-
|
|
114
110
|
/**
|
|
115
111
|
* Validate and adjust config for consistency.
|
|
116
|
-
* Currently handles:
|
|
117
|
-
* - Disabling reusableValidators when source maps are enabled (required for accurate mappings)
|
|
118
112
|
*
|
|
119
113
|
* @param config The config to validate
|
|
120
114
|
* @returns Validated/adjusted config
|
|
121
115
|
*/
|
|
122
116
|
export function validateConfig(config: TypicalConfig): TypicalConfig {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
117
|
+
// Reusable validators now throw at the call site, so they work correctly
|
|
118
|
+
// with source maps. No need for special handling.
|
|
119
|
+
return config
|
|
143
120
|
}
|
package/src/transformer.ts
CHANGED
|
@@ -33,7 +33,7 @@ export class TypicalTransformer {
|
|
|
33
33
|
* Ensure the Go compiler is started and project is loaded.
|
|
34
34
|
* Uses lazy initialization - only starts on first transform.
|
|
35
35
|
*/
|
|
36
|
-
private async ensureInitialized(): Promise<void> {
|
|
36
|
+
private async ensureInitialized(x?: string): Promise<void> {
|
|
37
37
|
if (!this.initPromise) {
|
|
38
38
|
this.initPromise = (async () => {
|
|
39
39
|
await this.compiler.start()
|
|
@@ -58,7 +58,7 @@ export class TypicalTransformer {
|
|
|
58
58
|
await this.ensureInitialized()
|
|
59
59
|
|
|
60
60
|
const resolvedPath = resolve(fileName)
|
|
61
|
-
// Pass
|
|
61
|
+
// Pass config options to the Go compiler
|
|
62
62
|
const result = await this.compiler.transformFile(this.projectHandle!, resolvedPath, this.config.ignoreTypes, this.config.maxGeneratedFunctions)
|
|
63
63
|
|
|
64
64
|
return {
|
package/dist/src/cli.typical.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import typia from "typia";
|
|
3
|
-
//@L:3
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
//@L:4
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
//@L:5
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
//@L:6
|
|
10
|
-
import { TypicalTransformer } from './transformer.js';
|
|
11
|
-
//@L:7
|
|
12
|
-
import * as ts from 'typescript';
|
|
13
|
-
//@L:8
|
|
14
|
-
import { loadConfig, validateConfig } from './config.js';
|
|
15
|
-
//@L:9
|
|
16
|
-
import { shouldIncludeFile } from './file-filter.js';
|
|
17
|
-
//@L:10
|
|
18
|
-
import { inlineSourceMapComment, externalSourceMapComment } from './source-map.js';
|
|
19
|
-
//@L:12
|
|
20
|
-
const program = new Command();
|
|
21
|
-
//@L:14
|
|
22
|
-
program
|
|
23
|
-
.name('typical')
|
|
24
|
-
.description('Runtime safe TypeScript transformer using typia')
|
|
25
|
-
.version('0.1.0');
|
|
26
|
-
//@L:19
|
|
27
|
-
program
|
|
28
|
-
.command('transform')
|
|
29
|
-
.description('Transform a TypeScript file with runtime validation')
|
|
30
|
-
.argument('<file>', 'TypeScript file to transform')
|
|
31
|
-
.option('-o, --output <file>', 'Output file')
|
|
32
|
-
.option('-c, --config <file>', 'Config file path', 'typical.json')
|
|
33
|
-
.option('-m, --mode <mode>', 'Transformation mode: basic, typia, js', 'basic')
|
|
34
|
-
.option('--source-map', 'Generate external source map file')
|
|
35
|
-
.option('--inline-source-map', 'Include inline source map in output')
|
|
36
|
-
.option('--no-source-map', 'Disable source map generation')
|
|
37
|
-
.action(async (file: string, options: {
|
|
38
|
-
output?: string;
|
|
39
|
-
config?: string;
|
|
40
|
-
mode?: 'basic' | 'typia' | 'js';
|
|
41
|
-
sourceMap?: boolean;
|
|
42
|
-
inlineSourceMap?: boolean;
|
|
43
|
-
}) => {
|
|
44
|
-
try {
|
|
45
|
-
const config = validateConfig(loadConfig(options.config));
|
|
46
|
-
const transformer = new TypicalTransformer(config);
|
|
47
|
-
if (!fs.existsSync(file)) {
|
|
48
|
-
console.error(`File not found: ${file}`);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
// Determine source map behavior
|
|
52
|
-
const generateSourceMap = options.inlineSourceMap || options.sourceMap !== false;
|
|
53
|
-
console.log(`Transforming ${file}...`);
|
|
54
|
-
const result = transformer.transform(path.resolve(file), options.mode ?? 'basic', {
|
|
55
|
-
sourceMap: generateSourceMap,
|
|
56
|
-
});
|
|
57
|
-
// Determine output file path
|
|
58
|
-
const outputFile = options.output
|
|
59
|
-
? path.resolve(options.output)
|
|
60
|
-
: options.mode === 'js'
|
|
61
|
-
? file.replace(/\.tsx?$/, '.js')
|
|
62
|
-
: file + '.transformed.ts';
|
|
63
|
-
let outputCode = result.code;
|
|
64
|
-
// Handle source maps
|
|
65
|
-
if (result.map) {
|
|
66
|
-
if (options.inlineSourceMap) {
|
|
67
|
-
// Inline source map as data URL
|
|
68
|
-
outputCode += '\n' + inlineSourceMapComment(result.map);
|
|
69
|
-
}
|
|
70
|
-
else if (options.sourceMap !== false) {
|
|
71
|
-
// Write external source map file
|
|
72
|
-
const mapFile = outputFile + '.map';
|
|
73
|
-
fs.writeFileSync(mapFile, typia.json.stringify(result.map, null, 2));
|
|
74
|
-
outputCode += '\n' + externalSourceMapComment(path.basename(mapFile));
|
|
75
|
-
console.log(`Source map written to ${mapFile}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
fs.writeFileSync(outputFile, outputCode);
|
|
79
|
-
console.log(`Transformed code written to ${outputFile}`);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
console.error('Transformation failed:', error);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
// program
|
|
87
|
-
// .command('build')
|
|
88
|
-
// .description('Transform all TypeScript files in the project')
|
|
89
|
-
// .option('-c, --config <file>', 'Config file path')
|
|
90
|
-
// .option('--dry-run', 'Show what would be transformed without making changes')
|
|
91
|
-
// .action(async (options: { config?: string, dryRun?: boolean }) => {
|
|
92
|
-
// try {
|
|
93
|
-
// const transformer = new TypicalTransformer();
|
|
94
|
-
// const { glob } = await import('glob');
|
|
95
|
-
// const config = loadConfig(options.config);
|
|
96
|
-
// if (!config.include || config.include.length === 0) {
|
|
97
|
-
// console.error('No include patterns specified in config');
|
|
98
|
-
// process.exit(1);
|
|
99
|
-
// }
|
|
100
|
-
// const files: string[] = [];
|
|
101
|
-
// for (const pattern of config.include) {
|
|
102
|
-
// const matched = await glob(pattern, {
|
|
103
|
-
// ignore: config.exclude,
|
|
104
|
-
// absolute: true
|
|
105
|
-
// });
|
|
106
|
-
// files.push(...matched);
|
|
107
|
-
// }
|
|
108
|
-
// console.log(`Found ${files.length} files to transform`);
|
|
109
|
-
// if (options.dryRun) {
|
|
110
|
-
// files.forEach(file => console.log(`Would transform: ${file}`));
|
|
111
|
-
// return;
|
|
112
|
-
// }
|
|
113
|
-
// let transformed = 0;
|
|
114
|
-
// for (const file of files) {
|
|
115
|
-
// // Double-check with our shared filtering logic
|
|
116
|
-
// if (!shouldIncludeFile(file, config)) {
|
|
117
|
-
// console.log(`Skipping ${file} (excluded by filters)`);
|
|
118
|
-
// continue;
|
|
119
|
-
// }
|
|
120
|
-
// try {
|
|
121
|
-
// console.log(`Transforming ${file}...`);
|
|
122
|
-
// const transformedCode = transformer.transformFile(file, ts);
|
|
123
|
-
// fs.writeFileSync(file, transformedCode);
|
|
124
|
-
// transformed++;
|
|
125
|
-
// } catch (error) {
|
|
126
|
-
// console.error(`Failed to transform ${file}:`, error);
|
|
127
|
-
// }
|
|
128
|
-
// }
|
|
129
|
-
// console.log(`Successfully transformed ${transformed}/${files.length} files`);
|
|
130
|
-
// } catch (error) {
|
|
131
|
-
// console.error('Build failed:', error);
|
|
132
|
-
// process.exit(1);
|
|
133
|
-
// }
|
|
134
|
-
// });
|
|
135
|
-
//@L:145
|
|
136
|
-
program.parse();
|