@elliots/typical 0.2.4 → 0.3.0
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 +19 -0
- package/bin/typical +0 -0
- package/dist/src/cli.js +15 -15
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +6 -6
- package/dist/src/config.js.map +1 -1
- package/dist/src/esm-loader-register.js +2 -2
- package/dist/src/esm-loader-register.js.map +1 -1
- package/dist/src/esm-loader.js +11 -11
- package/dist/src/esm-loader.js.map +1 -1
- package/dist/src/index.d.ts +5 -5
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/timing.d.ts +18 -11
- package/dist/src/timing.js +39 -51
- package/dist/src/timing.js.map +1 -1
- package/dist/src/transformer.d.ts +3 -3
- package/dist/src/transformer.js +6 -6
- package/dist/src/transformer.js.map +1 -1
- package/package.json +11 -3
- package/src/cli.ts +31 -31
- package/src/config.ts +28 -28
- package/src/esm-loader-register.ts +2 -2
- package/src/esm-loader.ts +26 -26
- package/src/index.ts +5 -5
- package/src/patch-fs.cjs +14 -14
- package/src/timing.ts +43 -57
- package/src/transformer.ts +34 -29
package/README.md
CHANGED
|
@@ -352,6 +352,10 @@ The generated validation code is optimised for runtime performance:
|
|
|
352
352
|
|
|
353
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
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
|
+
- **Cross-file call graph analysis** - Analyses the entire project to eliminate redundant validation across files:
|
|
356
|
+
- **Trusted return values** - If a function validates its return type, callers don't re-validate the result
|
|
357
|
+
- **Internal function parameters** - Non-exported functions only called with pre-validated arguments skip parameter validation
|
|
358
|
+
- **Chained function calls** - When `step2(step1(user))` is called, validation flows through the chain
|
|
355
359
|
- **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.
|
|
356
360
|
- **Union early bail-out** - Union type checks use if-else chains so the first matching type succeeds immediately
|
|
357
361
|
- **Skip comments** - Add `// @typical-ignore` before a function to skip all validation for it
|
|
@@ -478,3 +482,18 @@ Runtime validation performance comparing Typical vs Zod vs no validation:
|
|
|
478
482
|
|
|
479
483
|
- **vs Nothing**: Speed relative to no validation or filtering (1.0x = same speed)
|
|
480
484
|
- **vs Zod**: Speed relative to Zod (1.0x = same speed)
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Changelog
|
|
489
|
+
|
|
490
|
+
<!-- CHANGELOG_START -->
|
|
491
|
+
|
|
492
|
+
### v0.3.0 (2026-01-14)
|
|
493
|
+
|
|
494
|
+
- Build call graph across whole project to avoid validating already-validated data
|
|
495
|
+
- Source map vis in the playround
|
|
496
|
+
- Add 'never' support, the property must not exist
|
|
497
|
+
- Fix: Node 24
|
|
498
|
+
|
|
499
|
+
<!-- CHANGELOG_END -->
|
package/bin/typical
CHANGED
|
Binary file
|
package/dist/src/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from
|
|
3
|
-
import * as fs from
|
|
4
|
-
import * as path from
|
|
5
|
-
import { TypicalTransformer } from
|
|
6
|
-
import { loadConfig, validateConfig } from
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { TypicalTransformer } from "./transformer.js";
|
|
6
|
+
import { loadConfig, validateConfig } from "./config.js";
|
|
7
7
|
const program = new Command();
|
|
8
|
-
program.name(
|
|
8
|
+
program.name("typical").description("Runtime safe TypeScript transformer").version("0.1.0");
|
|
9
9
|
program
|
|
10
|
-
.command(
|
|
11
|
-
.description(
|
|
12
|
-
.argument(
|
|
13
|
-
.option(
|
|
14
|
-
.option(
|
|
15
|
-
.option(
|
|
10
|
+
.command("transform")
|
|
11
|
+
.description("Transform a TypeScript file with runtime validation")
|
|
12
|
+
.argument("<file>", "TypeScript file to transform")
|
|
13
|
+
.option("-o, --output <file>", "Output file")
|
|
14
|
+
.option("-c, --config <file>", "Config file path", "typical.json")
|
|
15
|
+
.option("-p, --project <file>", "TypeScript config file path", "tsconfig.json")
|
|
16
16
|
.action(async (file, options) => {
|
|
17
17
|
let transformer = null;
|
|
18
18
|
try {
|
|
@@ -23,14 +23,14 @@ program
|
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
console.log(`Transforming ${file}...`);
|
|
26
|
-
const result = await transformer.transform(path.resolve(file),
|
|
26
|
+
const result = await transformer.transform(path.resolve(file), "ts");
|
|
27
27
|
// Determine output file path
|
|
28
|
-
const outputFile = options.output ? path.resolve(options.output) : file +
|
|
28
|
+
const outputFile = options.output ? path.resolve(options.output) : file + ".transformed.ts";
|
|
29
29
|
fs.writeFileSync(outputFile, result.code);
|
|
30
30
|
console.log(`Transformed code written to ${outputFile}`);
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
|
-
console.error(
|
|
33
|
+
console.error("Transformation failed:", error);
|
|
34
34
|
process.exit(1);
|
|
35
35
|
}
|
|
36
36
|
finally {
|
package/dist/src/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAE5F,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qDAAqD,CAAC;KAClE,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,CAAC;KAClD,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,cAAc,CAAC;KACjE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,EAAE,eAAe,CAAC;KAC9E,MAAM,CACL,KAAK,EACH,IAAY,EACZ,OAIC,EACD,EAAE;IACF,IAAI,WAAW,GAA8B,IAAI,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,WAAW,GAAG,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAE9D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QAErE,6BAA6B;QAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAE5F,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,+BAA+B,UAAU,EAAE,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;AACH,CAAC,CACF,CAAC;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/src/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
|
-
include: [
|
|
3
|
-
exclude: [
|
|
2
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
3
|
+
exclude: ["node_modules/**", "**/*.d.ts", "dist/**", "build/**"],
|
|
4
4
|
validateCasts: false,
|
|
5
5
|
validateFunctions: true,
|
|
6
6
|
transformJSONParse: true,
|
|
@@ -15,13 +15,13 @@ export const defaultConfig = {
|
|
|
15
15
|
inline: false,
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
|
-
import fs from
|
|
19
|
-
import path from
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
20
|
export function loadConfig(configPath) {
|
|
21
|
-
const configFile = configPath || path.join(process.cwd(),
|
|
21
|
+
const configFile = configPath || path.join(process.cwd(), "typical.json");
|
|
22
22
|
if (fs.existsSync(configFile)) {
|
|
23
23
|
try {
|
|
24
|
-
const configContent = fs.readFileSync(configFile,
|
|
24
|
+
const configContent = fs.readFileSync(configFile, "utf8");
|
|
25
25
|
const userConfig = JSON.parse(configContent);
|
|
26
26
|
return {
|
|
27
27
|
...defaultConfig,
|
package/dist/src/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAAC;AAEF,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,MAAM,UAAU,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;IAE1E,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC1D,MAAM,UAAU,GAA2B,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YAErE,OAAO;gBACL,GAAG,aAAa;gBAChB,GAAG,UAAU;aACd,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,+BAA+B,UAAU,GAAG,EAAE,KAAK,CAAC,CAAC;YAClE,OAAO,aAAa,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAqB;IAClD,yEAAyE;IACzE,kDAAkD;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { register } from
|
|
2
|
-
register(
|
|
1
|
+
import { register } from "node:module";
|
|
2
|
+
register("./esm-loader.js", { parentURL: import.meta.url });
|
|
3
3
|
//# sourceMappingURL=esm-loader-register.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"esm-loader-register.js","sourceRoot":"","sources":["../../src/esm-loader-register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"esm-loader-register.js","sourceRoot":"","sources":["../../src/esm-loader-register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,QAAQ,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC"}
|
package/dist/src/esm-loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fileURLToPath, pathToFileURL } from
|
|
2
|
-
import { existsSync } from
|
|
3
|
-
import { TypicalTransformer } from
|
|
4
|
-
import { loadConfig, validateConfig } from
|
|
1
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { TypicalTransformer } from "./transformer.js";
|
|
4
|
+
import { loadConfig, validateConfig } from "./config.js";
|
|
5
5
|
const config = validateConfig(loadConfig());
|
|
6
6
|
// Shared transformer - stays alive for the lifetime of the process
|
|
7
7
|
let transformer = null;
|
|
@@ -16,12 +16,12 @@ async function getTransformer() {
|
|
|
16
16
|
*/
|
|
17
17
|
export async function resolve(specifier, context, nextResolve) {
|
|
18
18
|
// Only handle relative imports ending in .js
|
|
19
|
-
if (specifier.startsWith(
|
|
19
|
+
if (specifier.startsWith(".") && specifier.endsWith(".js")) {
|
|
20
20
|
const { parentURL } = context;
|
|
21
21
|
if (parentURL) {
|
|
22
22
|
const parentPath = fileURLToPath(parentURL);
|
|
23
|
-
const dir = parentPath.substring(0, parentPath.lastIndexOf(
|
|
24
|
-
const tsPath = dir +
|
|
23
|
+
const dir = parentPath.substring(0, parentPath.lastIndexOf("/"));
|
|
24
|
+
const tsPath = dir + "/" + specifier.slice(0, -3) + ".ts";
|
|
25
25
|
if (existsSync(tsPath)) {
|
|
26
26
|
return {
|
|
27
27
|
url: pathToFileURL(tsPath).href,
|
|
@@ -37,17 +37,17 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
37
37
|
* Note: Source maps not yet supported in v2
|
|
38
38
|
*/
|
|
39
39
|
export async function load(url, context, nextLoad) {
|
|
40
|
-
if (!url.endsWith(
|
|
40
|
+
if (!url.endsWith(".ts")) {
|
|
41
41
|
return nextLoad(url, context);
|
|
42
42
|
}
|
|
43
43
|
const filePath = fileURLToPath(url);
|
|
44
44
|
try {
|
|
45
45
|
const t = await getTransformer();
|
|
46
|
-
const result = await t.transform(filePath,
|
|
46
|
+
const result = await t.transform(filePath, "ts");
|
|
47
47
|
// For now, output is TypeScript - need to transpile to JS
|
|
48
48
|
// TODO: Add JS transpilation in Go or here
|
|
49
49
|
// For now, use TypeScript's transpileModule as fallback
|
|
50
|
-
const ts = await import(
|
|
50
|
+
const ts = await import("typescript");
|
|
51
51
|
const transpiled = ts.default.transpileModule(result.code, {
|
|
52
52
|
compilerOptions: {
|
|
53
53
|
module: ts.default.ModuleKind.ESNext,
|
|
@@ -57,7 +57,7 @@ export async function load(url, context, nextLoad) {
|
|
|
57
57
|
fileName: filePath,
|
|
58
58
|
});
|
|
59
59
|
return {
|
|
60
|
-
format:
|
|
60
|
+
format: "module",
|
|
61
61
|
source: transpiled.outputText,
|
|
62
62
|
shortCircuit: true,
|
|
63
63
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"esm-loader.js","sourceRoot":"","sources":["../../src/esm-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"esm-loader.js","sourceRoot":"","sources":["../../src/esm-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,EAAE,CAAC,CAAC;AAE5C,mEAAmE;AACnE,IAAI,WAAW,GAA8B,IAAI,CAAC;AAElD,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,SAAiB,EAAE,OAAY,EAAE,WAAgB;IAC7E,6CAA6C;IAC7C,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAC9B,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YACjE,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YAE1D,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvB,OAAO;oBACL,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI;oBAC/B,YAAY,EAAE,IAAI;iBACnB,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,OAAY,EAAE,QAAa;IACjE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,cAAc,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAEjD,0DAA0D;QAC1D,2CAA2C;QAC3C,wDAAwD;QACxD,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE;YACzD,eAAe,EAAE;gBACf,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM;gBACpC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM;gBACtC,eAAe,EAAE,IAAI;aACtB;YACD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QAEH,OAAO;YACL,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,UAAU,CAAC,UAAU;YAC7B,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,sBAAsB,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;QACxD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { TypicalTransformer } from
|
|
2
|
-
export type { TransformResult } from
|
|
3
|
-
export { loadConfig, validateConfig, defaultConfig } from
|
|
4
|
-
export type { TypicalConfig, TypicalSourceMapConfig } from
|
|
5
|
-
export { BuildTimer, buildTimer } from
|
|
1
|
+
export { TypicalTransformer } from "./transformer.js";
|
|
2
|
+
export type { TransformResult } from "./transformer.js";
|
|
3
|
+
export { loadConfig, validateConfig, defaultConfig } from "./config.js";
|
|
4
|
+
export type { TypicalConfig, TypicalSourceMapConfig } from "./config.js";
|
|
5
|
+
export { BuildTimer, buildTimer } from "./timing.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { TypicalTransformer } from
|
|
2
|
-
export { loadConfig, validateConfig, defaultConfig } from
|
|
3
|
-
export { BuildTimer, buildTimer } from
|
|
1
|
+
export { TypicalTransformer } from "./transformer.js";
|
|
2
|
+
export { loadConfig, validateConfig, defaultConfig } from "./config.js";
|
|
3
|
+
export { BuildTimer, buildTimer } from "./timing.js";
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/src/timing.d.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simple build timing utility for debugging performance.
|
|
3
3
|
*/
|
|
4
4
|
export declare class BuildTimer {
|
|
5
5
|
private timings;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Reset all timing data.
|
|
8
|
+
*/
|
|
9
9
|
reset(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Start timing a named section.
|
|
12
|
+
*/
|
|
13
|
+
start(name: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* End timing a named section.
|
|
16
|
+
*/
|
|
17
|
+
end(name: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Print a timing report to console.
|
|
20
|
+
*/
|
|
10
21
|
report(prefix?: string): void;
|
|
11
|
-
getTimings(): Map<string, {
|
|
12
|
-
count: number;
|
|
13
|
-
total: number;
|
|
14
|
-
avg: number;
|
|
15
|
-
min: number;
|
|
16
|
-
max: number;
|
|
17
|
-
}>;
|
|
18
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Shared build timer instance.
|
|
25
|
+
*/
|
|
19
26
|
export declare const buildTimer: BuildTimer;
|
package/dist/src/timing.js
CHANGED
|
@@ -1,65 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simple build timing utility for debugging performance.
|
|
3
3
|
*/
|
|
4
4
|
export class BuildTimer {
|
|
5
5
|
timings = new Map();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
end(stage) {
|
|
11
|
-
const start = this.starts.get(stage);
|
|
12
|
-
if (start !== undefined) {
|
|
13
|
-
const duration = performance.now() - start;
|
|
14
|
-
const existing = this.timings.get(stage) ?? [];
|
|
15
|
-
existing.push(duration);
|
|
16
|
-
this.timings.set(stage, existing);
|
|
17
|
-
this.starts.delete(stage);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
6
|
+
/**
|
|
7
|
+
* Reset all timing data.
|
|
8
|
+
*/
|
|
20
9
|
reset() {
|
|
21
10
|
this.timings.clear();
|
|
22
|
-
this.starts.clear();
|
|
23
11
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const max = Math.max(...times);
|
|
35
|
-
const count = times.length;
|
|
36
|
-
totalTime += total;
|
|
37
|
-
console.log(`${stage}:`);
|
|
38
|
-
console.log(` Count: ${count}`);
|
|
39
|
-
console.log(` Total: ${total.toFixed(2)}ms`);
|
|
40
|
-
console.log(` Avg: ${avg.toFixed(2)}ms`);
|
|
41
|
-
console.log(` Min: ${min.toFixed(2)}ms`);
|
|
42
|
-
console.log(` Max: ${max.toFixed(2)}ms`);
|
|
12
|
+
/**
|
|
13
|
+
* Start timing a named section.
|
|
14
|
+
*/
|
|
15
|
+
start(name) {
|
|
16
|
+
const existing = this.timings.get(name);
|
|
17
|
+
if (existing) {
|
|
18
|
+
existing.start = performance.now();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.timings.set(name, { start: performance.now(), total: 0, count: 0 });
|
|
43
22
|
}
|
|
44
|
-
console.log('─'.repeat(60));
|
|
45
|
-
console.log(`Total transform time: ${totalTime.toFixed(2)}ms`);
|
|
46
|
-
console.log('');
|
|
47
23
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
24
|
+
/**
|
|
25
|
+
* End timing a named section.
|
|
26
|
+
*/
|
|
27
|
+
end(name) {
|
|
28
|
+
const timing = this.timings.get(name);
|
|
29
|
+
if (timing && timing.start > 0) {
|
|
30
|
+
timing.total += performance.now() - timing.start;
|
|
31
|
+
timing.count++;
|
|
32
|
+
timing.start = 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Print a timing report to console.
|
|
37
|
+
*/
|
|
38
|
+
report(prefix = "[typical]") {
|
|
39
|
+
if (this.timings.size === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(`${prefix} Timing report:`);
|
|
43
|
+
for (const [name, timing] of this.timings) {
|
|
44
|
+
const avg = timing.count > 0 ? timing.total / timing.count : 0;
|
|
45
|
+
console.log(` ${name}: ${timing.total.toFixed(2)}ms total, ${timing.count} calls, ${avg.toFixed(2)}ms avg`);
|
|
59
46
|
}
|
|
60
|
-
return result;
|
|
61
47
|
}
|
|
62
48
|
}
|
|
63
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Shared build timer instance.
|
|
51
|
+
*/
|
|
64
52
|
export const buildTimer = new BuildTimer();
|
|
65
53
|
//# sourceMappingURL=timing.js.map
|
package/dist/src/timing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/timing.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/timing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,OAAO,UAAU;IACb,OAAO,GAAiE,IAAI,GAAG,EAAE,CAAC;IAE1F;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAY;QAChB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,IAAY;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;YACjD,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,SAAiB,WAAW;QACjC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,iBAAiB,CAAC,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/D,OAAO,CAAC,GAAG,CACT,KAAK,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,KAAK,WAAW,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAChG,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC"}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and validation code generation. This class just manages the lifecycle
|
|
6
6
|
* and communication with the Go process.
|
|
7
7
|
*/
|
|
8
|
-
import { type RawSourceMap } from
|
|
9
|
-
import type { TypicalConfig } from
|
|
8
|
+
import { type RawSourceMap } from "@elliots/typical-compiler";
|
|
9
|
+
import type { TypicalConfig } from "./config.js";
|
|
10
10
|
export interface TransformResult {
|
|
11
11
|
code: string;
|
|
12
12
|
map: RawSourceMap | null;
|
|
@@ -30,7 +30,7 @@ export declare class TypicalTransformer {
|
|
|
30
30
|
* @param mode - Output mode: 'ts' returns TypeScript, 'js' would transpile (not yet supported)
|
|
31
31
|
* @returns Transformed code with validation
|
|
32
32
|
*/
|
|
33
|
-
transform(fileName: string, mode?:
|
|
33
|
+
transform(fileName: string, mode?: "ts" | "js"): Promise<TransformResult>;
|
|
34
34
|
/**
|
|
35
35
|
* Close the Go compiler process and release resources.
|
|
36
36
|
* This immediately kills the process without waiting for pending operations.
|
package/dist/src/transformer.js
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* and validation code generation. This class just manages the lifecycle
|
|
6
6
|
* and communication with the Go process.
|
|
7
7
|
*/
|
|
8
|
-
import { resolve } from
|
|
9
|
-
import { TypicalCompiler } from
|
|
10
|
-
import { loadConfig } from
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import { TypicalCompiler } from "@elliots/typical-compiler";
|
|
10
|
+
import { loadConfig } from "./config.js";
|
|
11
11
|
export class TypicalTransformer {
|
|
12
12
|
config;
|
|
13
13
|
compiler;
|
|
14
14
|
projectHandle = null;
|
|
15
15
|
initPromise = null;
|
|
16
16
|
configFile;
|
|
17
|
-
constructor(config, configFile =
|
|
17
|
+
constructor(config, configFile = "tsconfig.json") {
|
|
18
18
|
this.config = config ?? loadConfig();
|
|
19
19
|
this.configFile = configFile;
|
|
20
20
|
this.compiler = new TypicalCompiler({ cwd: process.cwd() });
|
|
@@ -39,8 +39,8 @@ export class TypicalTransformer {
|
|
|
39
39
|
* @param mode - Output mode: 'ts' returns TypeScript, 'js' would transpile (not yet supported)
|
|
40
40
|
* @returns Transformed code with validation
|
|
41
41
|
*/
|
|
42
|
-
async transform(fileName, mode =
|
|
43
|
-
if (mode ===
|
|
42
|
+
async transform(fileName, mode = "ts") {
|
|
43
|
+
if (mode === "js") {
|
|
44
44
|
throw new Error('Mode "js" not yet supported - use "ts" and transpile separately');
|
|
45
45
|
}
|
|
46
46
|
await this.ensureInitialized();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformer.js","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"transformer.js","sourceRoot":"","sources":["../../src/transformer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAyC,MAAM,2BAA2B,CAAC;AAEnG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC,MAAM,OAAO,kBAAkB;IACtB,MAAM,CAAgB;IACrB,QAAQ,CAAkB;IAC1B,aAAa,GAAyB,IAAI,CAAC;IAC3C,WAAW,GAAyB,IAAI,CAAC;IACzC,UAAU,CAAS;IAE3B,YAAY,MAAsB,EAAE,aAAqB,eAAe;QACtE,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC9D,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,CAAC;gBAC5B,IAAI,CAAC,aAAa,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxE,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,MAAM,IAAI,CAAC,WAAW,CAAC;IACzB,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,CAAC;QACrF,CAAC;QAED,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE/B,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,yCAAyC;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAC9C,IAAI,CAAC,aAAc,EACnB,YAAY,EACZ,IAAI,CAAC,MAAM,CAAC,WAAW,EACvB,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAClC,CAAC;QAEF,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;SAC9B,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliots/typical",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Runtime safe TypeScript transformer using typia",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"runtime",
|
|
@@ -40,19 +40,27 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"commander": "14.0.2",
|
|
43
|
-
"@elliots/typical-compiler": "0.
|
|
43
|
+
"@elliots/typical-compiler": "0.3.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "22",
|
|
47
47
|
"c8": "10.1.3",
|
|
48
|
+
"nano-staged": "^0.9.0",
|
|
48
49
|
"oxfmt": "^0.21.0",
|
|
49
50
|
"oxlint": "1.36.0",
|
|
50
51
|
"oxlint-tsgolint": "0.10.1",
|
|
52
|
+
"simple-git-hooks": "^2.13.1",
|
|
51
53
|
"typescript": "5.9.3"
|
|
52
54
|
},
|
|
53
55
|
"peerDependencies": {
|
|
54
56
|
"typescript": "^5.0.0"
|
|
55
57
|
},
|
|
58
|
+
"simple-git-hooks": {
|
|
59
|
+
"pre-commit": "pnpm nano-staged"
|
|
60
|
+
},
|
|
61
|
+
"nano-staged": {
|
|
62
|
+
"*.{js,ts,tsx,jsx,json,md,html,css}": "oxfmt"
|
|
63
|
+
},
|
|
56
64
|
"engines": {
|
|
57
65
|
"pnpm": ">=10"
|
|
58
66
|
},
|
|
@@ -65,7 +73,7 @@
|
|
|
65
73
|
"build:go": "cd packages/compiler && npm run build:go",
|
|
66
74
|
"build:go:all": "./scripts/build-binaries.sh",
|
|
67
75
|
"build:ts": "pnpm --filter './packages/*' run build && tsc",
|
|
68
|
-
"build:website": "
|
|
76
|
+
"build:website": "pnpm --filter typical-website run build",
|
|
69
77
|
"dev": "tsc --watch",
|
|
70
78
|
"test": "rm -f test/test.temp.ts && tsc && node --import ./dist/src/esm-loader-register.js --test dist/test/*.test.js",
|
|
71
79
|
"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/cli.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { Command } from
|
|
4
|
-
import * as fs from
|
|
5
|
-
import * as path from
|
|
6
|
-
import { TypicalTransformer } from
|
|
7
|
-
import { loadConfig, validateConfig } from
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { TypicalTransformer } from "./transformer.js";
|
|
7
|
+
import { loadConfig, validateConfig } from "./config.js";
|
|
8
8
|
|
|
9
|
-
const program = new Command()
|
|
9
|
+
const program = new Command();
|
|
10
10
|
|
|
11
|
-
program.name(
|
|
11
|
+
program.name("typical").description("Runtime safe TypeScript transformer").version("0.1.0");
|
|
12
12
|
|
|
13
13
|
program
|
|
14
|
-
.command(
|
|
15
|
-
.description(
|
|
16
|
-
.argument(
|
|
17
|
-
.option(
|
|
18
|
-
.option(
|
|
19
|
-
.option(
|
|
14
|
+
.command("transform")
|
|
15
|
+
.description("Transform a TypeScript file with runtime validation")
|
|
16
|
+
.argument("<file>", "TypeScript file to transform")
|
|
17
|
+
.option("-o, --output <file>", "Output file")
|
|
18
|
+
.option("-c, --config <file>", "Config file path", "typical.json")
|
|
19
|
+
.option("-p, --project <file>", "TypeScript config file path", "tsconfig.json")
|
|
20
20
|
.action(
|
|
21
21
|
async (
|
|
22
22
|
file: string,
|
|
23
23
|
options: {
|
|
24
|
-
output?: string
|
|
25
|
-
config?: string
|
|
26
|
-
project?: string
|
|
24
|
+
output?: string;
|
|
25
|
+
config?: string;
|
|
26
|
+
project?: string;
|
|
27
27
|
},
|
|
28
28
|
) => {
|
|
29
|
-
let transformer: TypicalTransformer | null = null
|
|
29
|
+
let transformer: TypicalTransformer | null = null;
|
|
30
30
|
try {
|
|
31
|
-
const config = validateConfig(loadConfig(options.config))
|
|
32
|
-
transformer = new TypicalTransformer(config, options.project)
|
|
31
|
+
const config = validateConfig(loadConfig(options.config));
|
|
32
|
+
transformer = new TypicalTransformer(config, options.project);
|
|
33
33
|
|
|
34
34
|
if (!fs.existsSync(file)) {
|
|
35
|
-
console.error(`File not found: ${file}`)
|
|
36
|
-
process.exit(1)
|
|
35
|
+
console.error(`File not found: ${file}`);
|
|
36
|
+
process.exit(1);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
console.log(`Transforming ${file}...`)
|
|
40
|
-
const result = await transformer.transform(path.resolve(file),
|
|
39
|
+
console.log(`Transforming ${file}...`);
|
|
40
|
+
const result = await transformer.transform(path.resolve(file), "ts");
|
|
41
41
|
|
|
42
42
|
// Determine output file path
|
|
43
|
-
const outputFile = options.output ? path.resolve(options.output) : file +
|
|
43
|
+
const outputFile = options.output ? path.resolve(options.output) : file + ".transformed.ts";
|
|
44
44
|
|
|
45
|
-
fs.writeFileSync(outputFile, result.code)
|
|
46
|
-
console.log(`Transformed code written to ${outputFile}`)
|
|
45
|
+
fs.writeFileSync(outputFile, result.code);
|
|
46
|
+
console.log(`Transformed code written to ${outputFile}`);
|
|
47
47
|
} catch (error) {
|
|
48
|
-
console.error(
|
|
49
|
-
process.exit(1)
|
|
48
|
+
console.error("Transformation failed:", error);
|
|
49
|
+
process.exit(1);
|
|
50
50
|
} finally {
|
|
51
51
|
if (transformer) {
|
|
52
|
-
await transformer.close()
|
|
52
|
+
await transformer.close();
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
|
-
)
|
|
56
|
+
);
|
|
57
57
|
|
|
58
|
-
program.parse()
|
|
58
|
+
program.parse();
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface TypicalDebugConfig {
|
|
2
|
-
writeIntermediateFiles?: boolean
|
|
2
|
+
writeIntermediateFiles?: boolean;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,52 +9,52 @@ export interface TypicalSourceMapConfig {
|
|
|
9
9
|
/**
|
|
10
10
|
* Generate source maps. Default: true
|
|
11
11
|
*/
|
|
12
|
-
enabled?: boolean
|
|
12
|
+
enabled?: boolean;
|
|
13
13
|
/**
|
|
14
14
|
* Include original source content in the map. Default: true
|
|
15
15
|
*/
|
|
16
|
-
includeContent?: boolean
|
|
16
|
+
includeContent?: boolean;
|
|
17
17
|
/**
|
|
18
18
|
* Use inline source maps (data URL) instead of external files. Default: false
|
|
19
19
|
*/
|
|
20
|
-
inline?: boolean
|
|
20
|
+
inline?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface TypicalConfig {
|
|
24
|
-
include?: string[]
|
|
25
|
-
exclude?: string[]
|
|
26
|
-
validateCasts?: boolean
|
|
27
|
-
hoistRegex?: boolean
|
|
28
|
-
debug?: TypicalDebugConfig
|
|
24
|
+
include?: string[];
|
|
25
|
+
exclude?: string[];
|
|
26
|
+
validateCasts?: boolean;
|
|
27
|
+
hoistRegex?: boolean;
|
|
28
|
+
debug?: TypicalDebugConfig;
|
|
29
29
|
/**
|
|
30
30
|
* Type patterns to skip validation for (supports wildcards).
|
|
31
31
|
* Use this for types that typia cannot process (e.g., React event types).
|
|
32
32
|
* Example: ["React.*", "Express.Request", "*.Event"]
|
|
33
33
|
*/
|
|
34
|
-
ignoreTypes?: string[]
|
|
34
|
+
ignoreTypes?: string[];
|
|
35
35
|
/**
|
|
36
36
|
* Validate function parameters and return types at runtime.
|
|
37
37
|
* When enabled, typed function parameters get runtime validation calls injected.
|
|
38
38
|
* Default: true
|
|
39
39
|
*/
|
|
40
|
-
validateFunctions?: boolean
|
|
40
|
+
validateFunctions?: boolean;
|
|
41
41
|
/**
|
|
42
42
|
* Transform JSON.parse<T>() calls to validate and filter the parsed result
|
|
43
43
|
* to only include properties defined in type T.
|
|
44
44
|
* Default: true
|
|
45
45
|
*/
|
|
46
|
-
transformJSONParse?: boolean
|
|
46
|
+
transformJSONParse?: boolean;
|
|
47
47
|
/**
|
|
48
48
|
* Transform JSON.stringify<T>() calls to only stringify properties defined
|
|
49
49
|
* in type T, preventing accidental data leaks.
|
|
50
50
|
* Default: true
|
|
51
51
|
*/
|
|
52
|
-
transformJSONStringify?: boolean
|
|
52
|
+
transformJSONStringify?: boolean;
|
|
53
53
|
/**
|
|
54
54
|
* Source map generation settings.
|
|
55
55
|
* Controls whether and how source maps are generated for transformed code.
|
|
56
56
|
*/
|
|
57
|
-
sourceMap?: TypicalSourceMapConfig
|
|
57
|
+
sourceMap?: TypicalSourceMapConfig;
|
|
58
58
|
/**
|
|
59
59
|
* Maximum number of helper functions (_io0, _io1, etc.) that can be generated
|
|
60
60
|
* for a single type before erroring. Complex DOM types or library types can
|
|
@@ -62,12 +62,12 @@ export interface TypicalConfig {
|
|
|
62
62
|
* Set to 0 to disable the limit.
|
|
63
63
|
* Default: 50
|
|
64
64
|
*/
|
|
65
|
-
maxGeneratedFunctions?: number
|
|
65
|
+
maxGeneratedFunctions?: number;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export const defaultConfig: TypicalConfig = {
|
|
69
|
-
include: [
|
|
70
|
-
exclude: [
|
|
69
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
70
|
+
exclude: ["node_modules/**", "**/*.d.ts", "dist/**", "build/**"],
|
|
71
71
|
validateCasts: false,
|
|
72
72
|
validateFunctions: true,
|
|
73
73
|
transformJSONParse: true,
|
|
@@ -81,30 +81,30 @@ export const defaultConfig: TypicalConfig = {
|
|
|
81
81
|
includeContent: true,
|
|
82
82
|
inline: false,
|
|
83
83
|
},
|
|
84
|
-
}
|
|
84
|
+
};
|
|
85
85
|
|
|
86
|
-
import fs from
|
|
87
|
-
import path from
|
|
86
|
+
import fs from "fs";
|
|
87
|
+
import path from "path";
|
|
88
88
|
|
|
89
89
|
export function loadConfig(configPath?: string): TypicalConfig {
|
|
90
|
-
const configFile = configPath || path.join(process.cwd(),
|
|
90
|
+
const configFile = configPath || path.join(process.cwd(), "typical.json");
|
|
91
91
|
|
|
92
92
|
if (fs.existsSync(configFile)) {
|
|
93
93
|
try {
|
|
94
|
-
const configContent = fs.readFileSync(configFile,
|
|
95
|
-
const userConfig: Partial<TypicalConfig> = JSON.parse(configContent)
|
|
94
|
+
const configContent = fs.readFileSync(configFile, "utf8");
|
|
95
|
+
const userConfig: Partial<TypicalConfig> = JSON.parse(configContent);
|
|
96
96
|
|
|
97
97
|
return {
|
|
98
98
|
...defaultConfig,
|
|
99
99
|
...userConfig,
|
|
100
|
-
}
|
|
100
|
+
};
|
|
101
101
|
} catch (error) {
|
|
102
|
-
console.warn(`Failed to parse config file ${configFile}:`, error)
|
|
103
|
-
return defaultConfig
|
|
102
|
+
console.warn(`Failed to parse config file ${configFile}:`, error);
|
|
103
|
+
return defaultConfig;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
return defaultConfig
|
|
107
|
+
return defaultConfig;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
@@ -116,5 +116,5 @@ export function loadConfig(configPath?: string): TypicalConfig {
|
|
|
116
116
|
export function validateConfig(config: TypicalConfig): TypicalConfig {
|
|
117
117
|
// Reusable validators now throw at the call site, so they work correctly
|
|
118
118
|
// with source maps. No need for special handling.
|
|
119
|
-
return config
|
|
119
|
+
return config;
|
|
120
120
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { register } from
|
|
2
|
-
register(
|
|
1
|
+
import { register } from "node:module";
|
|
2
|
+
register("./esm-loader.js", { parentURL: import.meta.url });
|
package/src/esm-loader.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { fileURLToPath, pathToFileURL } from
|
|
2
|
-
import { existsSync } from
|
|
3
|
-
import { TypicalTransformer } from
|
|
4
|
-
import { loadConfig, validateConfig } from
|
|
1
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { TypicalTransformer } from "./transformer.js";
|
|
4
|
+
import { loadConfig, validateConfig } from "./config.js";
|
|
5
5
|
|
|
6
|
-
const config = validateConfig(loadConfig())
|
|
6
|
+
const config = validateConfig(loadConfig());
|
|
7
7
|
|
|
8
8
|
// Shared transformer - stays alive for the lifetime of the process
|
|
9
|
-
let transformer: TypicalTransformer | null = null
|
|
9
|
+
let transformer: TypicalTransformer | null = null;
|
|
10
10
|
|
|
11
11
|
async function getTransformer(): Promise<TypicalTransformer> {
|
|
12
12
|
if (!transformer) {
|
|
13
|
-
transformer = new TypicalTransformer(config)
|
|
13
|
+
transformer = new TypicalTransformer(config);
|
|
14
14
|
}
|
|
15
|
-
return transformer
|
|
15
|
+
return transformer;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -20,23 +20,23 @@ async function getTransformer(): Promise<TypicalTransformer> {
|
|
|
20
20
|
*/
|
|
21
21
|
export async function resolve(specifier: string, context: any, nextResolve: any) {
|
|
22
22
|
// Only handle relative imports ending in .js
|
|
23
|
-
if (specifier.startsWith(
|
|
24
|
-
const { parentURL } = context
|
|
23
|
+
if (specifier.startsWith(".") && specifier.endsWith(".js")) {
|
|
24
|
+
const { parentURL } = context;
|
|
25
25
|
if (parentURL) {
|
|
26
|
-
const parentPath = fileURLToPath(parentURL)
|
|
27
|
-
const dir = parentPath.substring(0, parentPath.lastIndexOf(
|
|
28
|
-
const tsPath = dir +
|
|
26
|
+
const parentPath = fileURLToPath(parentURL);
|
|
27
|
+
const dir = parentPath.substring(0, parentPath.lastIndexOf("/"));
|
|
28
|
+
const tsPath = dir + "/" + specifier.slice(0, -3) + ".ts";
|
|
29
29
|
|
|
30
30
|
if (existsSync(tsPath)) {
|
|
31
31
|
return {
|
|
32
32
|
url: pathToFileURL(tsPath).href,
|
|
33
33
|
shortCircuit: true,
|
|
34
|
-
}
|
|
34
|
+
};
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return nextResolve(specifier, context)
|
|
39
|
+
return nextResolve(specifier, context);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -44,19 +44,19 @@ export async function resolve(specifier: string, context: any, nextResolve: any)
|
|
|
44
44
|
* Note: Source maps not yet supported in v2
|
|
45
45
|
*/
|
|
46
46
|
export async function load(url: string, context: any, nextLoad: any) {
|
|
47
|
-
if (!url.endsWith(
|
|
48
|
-
return nextLoad(url, context)
|
|
47
|
+
if (!url.endsWith(".ts")) {
|
|
48
|
+
return nextLoad(url, context);
|
|
49
49
|
}
|
|
50
|
-
const filePath = fileURLToPath(url)
|
|
50
|
+
const filePath = fileURLToPath(url);
|
|
51
51
|
|
|
52
52
|
try {
|
|
53
|
-
const t = await getTransformer()
|
|
54
|
-
const result = await t.transform(filePath,
|
|
53
|
+
const t = await getTransformer();
|
|
54
|
+
const result = await t.transform(filePath, "ts");
|
|
55
55
|
|
|
56
56
|
// For now, output is TypeScript - need to transpile to JS
|
|
57
57
|
// TODO: Add JS transpilation in Go or here
|
|
58
58
|
// For now, use TypeScript's transpileModule as fallback
|
|
59
|
-
const ts = await import(
|
|
59
|
+
const ts = await import("typescript");
|
|
60
60
|
const transpiled = ts.default.transpileModule(result.code, {
|
|
61
61
|
compilerOptions: {
|
|
62
62
|
module: ts.default.ModuleKind.ESNext,
|
|
@@ -64,15 +64,15 @@ export async function load(url: string, context: any, nextLoad: any) {
|
|
|
64
64
|
esModuleInterop: true,
|
|
65
65
|
},
|
|
66
66
|
fileName: filePath,
|
|
67
|
-
})
|
|
67
|
+
});
|
|
68
68
|
|
|
69
69
|
return {
|
|
70
|
-
format:
|
|
70
|
+
format: "module",
|
|
71
71
|
source: transpiled.outputText,
|
|
72
72
|
shortCircuit: true,
|
|
73
|
-
}
|
|
73
|
+
};
|
|
74
74
|
} catch (error) {
|
|
75
|
-
console.error(`Error transforming ${filePath}:`, error)
|
|
76
|
-
throw error
|
|
75
|
+
console.error(`Error transforming ${filePath}:`, error);
|
|
76
|
+
throw error;
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { TypicalTransformer } from
|
|
2
|
-
export type { TransformResult } from
|
|
3
|
-
export { loadConfig, validateConfig, defaultConfig } from
|
|
4
|
-
export type { TypicalConfig, TypicalSourceMapConfig } from
|
|
5
|
-
export { BuildTimer, buildTimer } from
|
|
1
|
+
export { TypicalTransformer } from "./transformer.js";
|
|
2
|
+
export type { TransformResult } from "./transformer.js";
|
|
3
|
+
export { loadConfig, validateConfig, defaultConfig } from "./config.js";
|
|
4
|
+
export type { TypicalConfig, TypicalSourceMapConfig } from "./config.js";
|
|
5
|
+
export { BuildTimer, buildTimer } from "./timing.js";
|
package/src/patch-fs.cjs
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
// logs all readFile and readFileSync calls (for debugging)
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const fsp = require(
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const fsp = require("fs/promises");
|
|
5
5
|
|
|
6
6
|
// monkeypatch promises/readFile
|
|
7
|
-
const origFspReadFile = fsp.readFile
|
|
7
|
+
const origFspReadFile = fsp.readFile;
|
|
8
8
|
fsp.readFile = async function (path, ...args) {
|
|
9
|
-
console.log(
|
|
10
|
-
return origFspReadFile.call(this, path, ...args)
|
|
11
|
-
}
|
|
9
|
+
console.log("fsp.readFile", path);
|
|
10
|
+
return origFspReadFile.call(this, path, ...args);
|
|
11
|
+
};
|
|
12
12
|
|
|
13
13
|
// monkeypatch readFile
|
|
14
|
-
const origFsReadFile = fs.readFile
|
|
14
|
+
const origFsReadFile = fs.readFile;
|
|
15
15
|
fs.readFile = async function (path, ...args) {
|
|
16
|
-
console.log(
|
|
17
|
-
return origFsReadFile.call(this, path, ...args)
|
|
18
|
-
}
|
|
16
|
+
console.log("fs.readFile", path);
|
|
17
|
+
return origFsReadFile.call(this, path, ...args);
|
|
18
|
+
};
|
|
19
19
|
|
|
20
20
|
// monkeypatch readFileSync
|
|
21
|
-
const origFsReadFileSync = fs.readFileSync
|
|
21
|
+
const origFsReadFileSync = fs.readFileSync;
|
|
22
22
|
fs.readFileSync = function (path, ...args) {
|
|
23
|
-
console.log(
|
|
24
|
-
return origFsReadFileSync.call(this, path, ...args)
|
|
25
|
-
}
|
|
23
|
+
console.log("fs.readFileSync", path);
|
|
24
|
+
return origFsReadFileSync.call(this, path, ...args);
|
|
25
|
+
};
|
package/src/timing.ts
CHANGED
|
@@ -1,74 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simple build timing utility for debugging performance.
|
|
3
3
|
*/
|
|
4
|
+
|
|
4
5
|
export class BuildTimer {
|
|
5
|
-
private timings
|
|
6
|
-
private starts = new Map<string, number>()
|
|
6
|
+
private timings: Map<string, { start: number; total: number; count: number }> = new Map();
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Reset all timing data.
|
|
10
|
+
*/
|
|
11
|
+
reset(): void {
|
|
12
|
+
this.timings.clear();
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Start timing a named section.
|
|
17
|
+
*/
|
|
18
|
+
start(name: string): void {
|
|
19
|
+
const existing = this.timings.get(name);
|
|
20
|
+
if (existing) {
|
|
21
|
+
existing.start = performance.now();
|
|
22
|
+
} else {
|
|
23
|
+
this.timings.set(name, { start: performance.now(), total: 0, count: 0 });
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* End timing a named section.
|
|
29
|
+
*/
|
|
30
|
+
end(name: string): void {
|
|
31
|
+
const timing = this.timings.get(name);
|
|
32
|
+
if (timing && timing.start > 0) {
|
|
33
|
+
timing.total += performance.now() - timing.start;
|
|
34
|
+
timing.count++;
|
|
35
|
+
timing.start = 0;
|
|
36
|
+
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let totalTime = 0
|
|
36
|
-
for (const [stage, times] of sortedStages) {
|
|
37
|
-
const total = times.reduce((a, b) => a + b, 0)
|
|
38
|
-
const avg = total / times.length
|
|
39
|
-
const min = Math.min(...times)
|
|
40
|
-
const max = Math.max(...times)
|
|
41
|
-
const count = times.length
|
|
42
|
-
totalTime += total
|
|
43
|
-
|
|
44
|
-
console.log(`${stage}:`)
|
|
45
|
-
console.log(` Count: ${count}`)
|
|
46
|
-
console.log(` Total: ${total.toFixed(2)}ms`)
|
|
47
|
-
console.log(` Avg: ${avg.toFixed(2)}ms`)
|
|
48
|
-
console.log(` Min: ${min.toFixed(2)}ms`)
|
|
49
|
-
console.log(` Max: ${max.toFixed(2)}ms`)
|
|
39
|
+
/**
|
|
40
|
+
* Print a timing report to console.
|
|
41
|
+
*/
|
|
42
|
+
report(prefix: string = "[typical]"): void {
|
|
43
|
+
if (this.timings.size === 0) {
|
|
44
|
+
return;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
console.log(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const result = new Map()
|
|
59
|
-
for (const [stage, times] of this.timings) {
|
|
60
|
-
const total = times.reduce((a, b) => a + b, 0)
|
|
61
|
-
result.set(stage, {
|
|
62
|
-
count: times.length,
|
|
63
|
-
total,
|
|
64
|
-
avg: total / times.length,
|
|
65
|
-
min: Math.min(...times),
|
|
66
|
-
max: Math.max(...times),
|
|
67
|
-
})
|
|
47
|
+
console.log(`${prefix} Timing report:`);
|
|
48
|
+
for (const [name, timing] of this.timings) {
|
|
49
|
+
const avg = timing.count > 0 ? timing.total / timing.count : 0;
|
|
50
|
+
console.log(
|
|
51
|
+
` ${name}: ${timing.total.toFixed(2)}ms total, ${timing.count} calls, ${avg.toFixed(2)}ms avg`,
|
|
52
|
+
);
|
|
68
53
|
}
|
|
69
|
-
return result
|
|
70
54
|
}
|
|
71
55
|
}
|
|
72
56
|
|
|
73
|
-
|
|
74
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Shared build timer instance.
|
|
59
|
+
*/
|
|
60
|
+
export const buildTimer = new BuildTimer();
|
package/src/transformer.ts
CHANGED
|
@@ -6,27 +6,27 @@
|
|
|
6
6
|
* and communication with the Go process.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { resolve } from
|
|
10
|
-
import { TypicalCompiler, type ProjectHandle, type RawSourceMap } from
|
|
11
|
-
import type { TypicalConfig } from
|
|
12
|
-
import { loadConfig } from
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import { TypicalCompiler, type ProjectHandle, type RawSourceMap } from "@elliots/typical-compiler";
|
|
11
|
+
import type { TypicalConfig } from "./config.js";
|
|
12
|
+
import { loadConfig } from "./config.js";
|
|
13
13
|
|
|
14
14
|
export interface TransformResult {
|
|
15
|
-
code: string
|
|
16
|
-
map: RawSourceMap | null
|
|
15
|
+
code: string;
|
|
16
|
+
map: RawSourceMap | null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export class TypicalTransformer {
|
|
20
|
-
public config: TypicalConfig
|
|
21
|
-
private compiler: TypicalCompiler
|
|
22
|
-
private projectHandle: ProjectHandle | null = null
|
|
23
|
-
private initPromise: Promise<void> | null = null
|
|
24
|
-
private configFile: string
|
|
20
|
+
public config: TypicalConfig;
|
|
21
|
+
private compiler: TypicalCompiler;
|
|
22
|
+
private projectHandle: ProjectHandle | null = null;
|
|
23
|
+
private initPromise: Promise<void> | null = null;
|
|
24
|
+
private configFile: string;
|
|
25
25
|
|
|
26
|
-
constructor(config?: TypicalConfig, configFile: string =
|
|
27
|
-
this.config = config ?? loadConfig()
|
|
28
|
-
this.configFile = configFile
|
|
29
|
-
this.compiler = new TypicalCompiler({ cwd: process.cwd() })
|
|
26
|
+
constructor(config?: TypicalConfig, configFile: string = "tsconfig.json") {
|
|
27
|
+
this.config = config ?? loadConfig();
|
|
28
|
+
this.configFile = configFile;
|
|
29
|
+
this.compiler = new TypicalCompiler({ cwd: process.cwd() });
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -36,11 +36,11 @@ export class TypicalTransformer {
|
|
|
36
36
|
private async ensureInitialized(x?: string): Promise<void> {
|
|
37
37
|
if (!this.initPromise) {
|
|
38
38
|
this.initPromise = (async () => {
|
|
39
|
-
await this.compiler.start()
|
|
40
|
-
this.projectHandle = await this.compiler.loadProject(this.configFile)
|
|
41
|
-
})()
|
|
39
|
+
await this.compiler.start();
|
|
40
|
+
this.projectHandle = await this.compiler.loadProject(this.configFile);
|
|
41
|
+
})();
|
|
42
42
|
}
|
|
43
|
-
await this.initPromise
|
|
43
|
+
await this.initPromise;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -50,21 +50,26 @@ export class TypicalTransformer {
|
|
|
50
50
|
* @param mode - Output mode: 'ts' returns TypeScript, 'js' would transpile (not yet supported)
|
|
51
51
|
* @returns Transformed code with validation
|
|
52
52
|
*/
|
|
53
|
-
async transform(fileName: string, mode:
|
|
54
|
-
if (mode ===
|
|
55
|
-
throw new Error('Mode "js" not yet supported - use "ts" and transpile separately')
|
|
53
|
+
async transform(fileName: string, mode: "ts" | "js" = "ts"): Promise<TransformResult> {
|
|
54
|
+
if (mode === "js") {
|
|
55
|
+
throw new Error('Mode "js" not yet supported - use "ts" and transpile separately');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
await this.ensureInitialized()
|
|
58
|
+
await this.ensureInitialized();
|
|
59
59
|
|
|
60
|
-
const resolvedPath = resolve(fileName)
|
|
60
|
+
const resolvedPath = resolve(fileName);
|
|
61
61
|
// Pass config options to the Go compiler
|
|
62
|
-
const result = await this.compiler.transformFile(
|
|
62
|
+
const result = await this.compiler.transformFile(
|
|
63
|
+
this.projectHandle!,
|
|
64
|
+
resolvedPath,
|
|
65
|
+
this.config.ignoreTypes,
|
|
66
|
+
this.config.maxGeneratedFunctions,
|
|
67
|
+
);
|
|
63
68
|
|
|
64
69
|
return {
|
|
65
70
|
code: result.code,
|
|
66
71
|
map: result.sourceMap ?? null,
|
|
67
|
-
}
|
|
72
|
+
};
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -72,8 +77,8 @@ export class TypicalTransformer {
|
|
|
72
77
|
* This immediately kills the process without waiting for pending operations.
|
|
73
78
|
*/
|
|
74
79
|
async close(): Promise<void> {
|
|
75
|
-
this.projectHandle = null
|
|
76
|
-
this.initPromise = null
|
|
77
|
-
await this.compiler.close()
|
|
80
|
+
this.projectHandle = null;
|
|
81
|
+
this.initPromise = null;
|
|
82
|
+
await this.compiler.close();
|
|
78
83
|
}
|
|
79
84
|
}
|