@elliots/unplugin-typical 0.1.7 → 0.1.8

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.
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/esbuild.d.ts
4
4
 
package/dist/esbuild.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/esbuild.ts
4
4
  /**
package/dist/farm.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/farm.d.ts
4
4
 
package/dist/farm.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/farm.ts
4
4
  /**
@@ -24,6 +24,12 @@ interface TypicalConfig {
24
24
  * Default: true
25
25
  */
26
26
  ignoreDOMTypes?: boolean;
27
+ /**
28
+ * Validate function parameters and return types at runtime.
29
+ * When enabled, typed function parameters get runtime validation calls injected.
30
+ * Default: true
31
+ */
32
+ validateFunctions?: boolean;
27
33
  }
28
34
  //#endregion
29
35
  //#region src/core/options.d.ts
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as Options, t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { n as Options, t as Typical } from "./index-cR_y1bPs.mjs";
2
2
  export { Options, Typical };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  export { Typical };
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/rolldown.d.ts
4
4
 
package/dist/rolldown.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/rolldown.ts
4
4
  /**
package/dist/rollup.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/rollup.d.ts
4
4
 
package/dist/rollup.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/rollup.ts
4
4
  /**
package/dist/rspack.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/rspack.d.ts
4
4
 
package/dist/rspack.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/rspack.ts
4
4
  /**
@@ -0,0 +1,250 @@
1
+ import { createUnplugin } from "unplugin";
2
+ import { TypicalTransformer, loadConfig } from "@elliots/typical";
3
+ import { dirname, extname, resolve } from "path";
4
+ import ts from "typescript";
5
+
6
+ //#region src/core/options.ts
7
+ function resolveOptions(options) {
8
+ return {
9
+ include: options.include || [/\.[cm]?[jt]sx?$/],
10
+ exclude: options.exclude || [/node_modules/],
11
+ enforce: "enforce" in options ? options.enforce : "pre",
12
+ typical: options.typical
13
+ };
14
+ }
15
+
16
+ //#endregion
17
+ //#region src/core/timing.ts
18
+ /**
19
+ * Performance instrumentation for tracking build times
20
+ */
21
+ var BuildTimer = class {
22
+ timings = /* @__PURE__ */ new Map();
23
+ starts = /* @__PURE__ */ new Map();
24
+ start(stage) {
25
+ this.starts.set(stage, performance.now());
26
+ }
27
+ end(stage) {
28
+ const start = this.starts.get(stage);
29
+ if (start !== void 0) {
30
+ const duration = performance.now() - start;
31
+ const existing = this.timings.get(stage) ?? [];
32
+ existing.push(duration);
33
+ this.timings.set(stage, existing);
34
+ this.starts.delete(stage);
35
+ }
36
+ }
37
+ reset() {
38
+ this.timings.clear();
39
+ this.starts.clear();
40
+ }
41
+ report() {
42
+ console.log("\n[unplugin-typical] Build Performance Report:");
43
+ console.log("─".repeat(60));
44
+ const sortedStages = Array.from(this.timings.entries()).sort(([, a], [, b]) => b.reduce((x, y) => x + y, 0) - a.reduce((x, y) => x + y, 0));
45
+ let totalTime = 0;
46
+ for (const [stage, times] of sortedStages) {
47
+ const total = times.reduce((a, b) => a + b, 0);
48
+ const avg = total / times.length;
49
+ const min = Math.min(...times);
50
+ const max = Math.max(...times);
51
+ const count = times.length;
52
+ totalTime += total;
53
+ console.log(`${stage}:`);
54
+ console.log(` Count: ${count}`);
55
+ console.log(` Total: ${total.toFixed(2)}ms`);
56
+ console.log(` Avg: ${avg.toFixed(2)}ms`);
57
+ console.log(` Min: ${min.toFixed(2)}ms`);
58
+ console.log(` Max: ${max.toFixed(2)}ms`);
59
+ }
60
+ console.log("─".repeat(60));
61
+ console.log(`Total transform time: ${totalTime.toFixed(2)}ms`);
62
+ console.log("");
63
+ }
64
+ getTimings() {
65
+ const result = /* @__PURE__ */ new Map();
66
+ for (const [stage, times] of this.timings) {
67
+ const total = times.reduce((a, b) => a + b, 0);
68
+ result.set(stage, {
69
+ count: times.length,
70
+ total,
71
+ avg: total / times.length,
72
+ min: Math.min(...times),
73
+ max: Math.max(...times)
74
+ });
75
+ }
76
+ return result;
77
+ }
78
+ };
79
+ const buildTimer = new BuildTimer();
80
+
81
+ //#endregion
82
+ //#region src/core/transform.ts
83
+ const TRANSFORM_EXTENSIONS = new Set([
84
+ ".ts",
85
+ ".tsx",
86
+ ".mts",
87
+ ".cts"
88
+ ]);
89
+ /**
90
+ * Transform a TypeScript file with Typical.
91
+ *
92
+ * Uses a shared ProgramManager for incremental compilation across files.
93
+ */
94
+ function transformTypia(id, source, config, programManager) {
95
+ buildTimer.start("total-transform");
96
+ const ext = extname(id).toLowerCase();
97
+ if (!TRANSFORM_EXTENSIONS.has(ext)) {
98
+ buildTimer.end("total-transform");
99
+ return;
100
+ }
101
+ const resolvedId = resolve(id);
102
+ buildTimer.start("get-program");
103
+ const program = programManager.getProgram(resolvedId, source);
104
+ buildTimer.end("get-program");
105
+ buildTimer.start("get-source-file");
106
+ const sourceFile = programManager.getSourceFile(resolvedId);
107
+ buildTimer.end("get-source-file");
108
+ if (!sourceFile) {
109
+ buildTimer.end("total-transform");
110
+ console.warn(`[unplugin-typical] Could not get source file for: ${id}`);
111
+ return;
112
+ }
113
+ buildTimer.start("create-transformer");
114
+ const transformer = new TypicalTransformer(config, program);
115
+ buildTimer.end("create-transformer");
116
+ buildTimer.start("transform");
117
+ const result = transformer.transform(sourceFile, "js");
118
+ buildTimer.end("transform");
119
+ buildTimer.end("total-transform");
120
+ if (process.env.DEBUG) console.log("[unplugin-typical] Transform output (first 1000 chars):", result.substring(0, 1e3));
121
+ return result;
122
+ }
123
+
124
+ //#endregion
125
+ //#region src/core/program-manager.ts
126
+ /**
127
+ * Manages a shared TypeScript program across file transformations.
128
+ * This avoids the expensive cost of creating a new program for each file.
129
+ */
130
+ var ProgramManager = class {
131
+ program;
132
+ compilerOptions;
133
+ sourceContents = /* @__PURE__ */ new Map();
134
+ sourceFileCache = /* @__PURE__ */ new Map();
135
+ host;
136
+ /**
137
+ * Get or create a program with the given source content for a file.
138
+ * Uses incremental compilation to reuse data from previous program.
139
+ */
140
+ getProgram(id, source) {
141
+ const resolvedId = resolve(id);
142
+ this.sourceContents.set(resolvedId, source);
143
+ this.sourceFileCache.delete(resolvedId);
144
+ if (!this.compilerOptions) {
145
+ buildTimer.start("load-compiler-options");
146
+ this.compilerOptions = this.loadCompilerOptions();
147
+ buildTimer.end("load-compiler-options");
148
+ }
149
+ if (!this.host) this.host = this.createHost();
150
+ const rootFiles = this.program?.getRootFileNames() ?? [];
151
+ const rootFileSet = new Set(rootFiles);
152
+ if (!rootFileSet.has(resolvedId)) rootFileSet.add(resolvedId);
153
+ buildTimer.start("create-program-incremental");
154
+ this.program = ts.createProgram(Array.from(rootFileSet), this.compilerOptions, this.host, this.program);
155
+ buildTimer.end("create-program-incremental");
156
+ return this.program;
157
+ }
158
+ /**
159
+ * Get the source file for a given ID from the current program.
160
+ */
161
+ getSourceFile(id) {
162
+ const resolvedId = resolve(id);
163
+ return this.program?.getSourceFile(resolvedId);
164
+ }
165
+ /**
166
+ * Reset the program manager state (e.g., at build start).
167
+ */
168
+ reset() {
169
+ this.program = void 0;
170
+ this.sourceContents.clear();
171
+ }
172
+ loadCompilerOptions() {
173
+ const configPath = ts.findConfigFile(process.cwd(), ts.sys.fileExists, "tsconfig.json");
174
+ if (!configPath) return {
175
+ target: ts.ScriptTarget.ES2020,
176
+ module: ts.ModuleKind.ESNext,
177
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
178
+ esModuleInterop: true,
179
+ strict: true
180
+ };
181
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
182
+ if (configFile.error) throw new Error(ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n"));
183
+ return ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath)).options;
184
+ }
185
+ createHost() {
186
+ const baseHost = ts.createCompilerHost(this.compilerOptions);
187
+ const originalGetSourceFile = baseHost.getSourceFile.bind(baseHost);
188
+ return {
189
+ ...baseHost,
190
+ getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
191
+ const resolvedFileName = resolve(fileName);
192
+ const virtualContent = this.sourceContents.get(resolvedFileName);
193
+ if (virtualContent !== void 0) {
194
+ const cached = this.sourceFileCache.get(resolvedFileName);
195
+ if (cached && cached.text === virtualContent) return cached;
196
+ const sourceFile = ts.createSourceFile(resolvedFileName, virtualContent, languageVersion, true);
197
+ this.sourceFileCache.set(resolvedFileName, sourceFile);
198
+ return sourceFile;
199
+ }
200
+ const cachedDisk = this.sourceFileCache.get(resolvedFileName);
201
+ if (cachedDisk) return cachedDisk;
202
+ const result = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
203
+ if (result) this.sourceFileCache.set(resolvedFileName, result);
204
+ return result;
205
+ },
206
+ fileExists: (fileName) => {
207
+ const resolvedFileName = resolve(fileName);
208
+ return this.sourceContents.has(resolvedFileName) || baseHost.fileExists(fileName);
209
+ },
210
+ readFile: (fileName) => {
211
+ const resolvedFileName = resolve(fileName);
212
+ return this.sourceContents.get(resolvedFileName) ?? baseHost.readFile(fileName);
213
+ }
214
+ };
215
+ }
216
+ };
217
+
218
+ //#endregion
219
+ //#region src/index.ts
220
+ const Typical = createUnplugin((rawOptions = {}) => {
221
+ const options = resolveOptions(rawOptions);
222
+ const typicalConfig = {
223
+ ...loadConfig(),
224
+ ...options.typical
225
+ };
226
+ const programManager = new ProgramManager();
227
+ return {
228
+ name: "unplugin-typical",
229
+ enforce: options.enforce,
230
+ buildStart() {
231
+ buildTimer.reset();
232
+ programManager.reset();
233
+ },
234
+ buildEnd() {
235
+ if (process.env.DEBUG) buildTimer.report();
236
+ },
237
+ transform: {
238
+ filter: { id: {
239
+ include: options.include,
240
+ exclude: options.exclude
241
+ } },
242
+ handler(code, id) {
243
+ return transformTypia(id, code, typicalConfig, programManager);
244
+ }
245
+ }
246
+ };
247
+ });
248
+
249
+ //#endregion
250
+ export { Typical as t };
package/dist/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/vite.d.ts
4
4
 
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/vite.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./index-DmCN96cN.mjs";
1
+ import { t as Typical } from "./index-cR_y1bPs.mjs";
2
2
 
3
3
  //#region src/webpack.d.ts
4
4
 
package/dist/webpack.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Typical } from "./src-DVJWj1px.mjs";
1
+ import { t as Typical } from "./src-DfYOX15z.mjs";
2
2
 
3
3
  //#region src/webpack.ts
4
4
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elliots/unplugin-typical",
3
3
  "type": "module",
4
- "version": "0.1.7",
4
+ "version": "0.1.8",
5
5
  "description": "Unplugin for typical - runtime safe TypeScript transformer",
6
6
  "author": "Elliot Shepherd <elliot@jarofworms.com>",
7
7
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@elliots/typical": "0.1.7",
60
+ "@elliots/typical": "0.1.8",
61
61
  "unplugin": "^2.3.11"
62
62
  },
63
63
  "devDependencies": {
@@ -1,112 +0,0 @@
1
- import { createUnplugin } from "unplugin";
2
- import { TypicalTransformer, loadConfig } from "@elliots/typical";
3
- import ts from "typescript";
4
- import { dirname, extname, resolve } from "path";
5
-
6
- //#region src/core/options.ts
7
- function resolveOptions(options) {
8
- return {
9
- include: options.include || [/\.[cm]?[jt]sx?$/],
10
- exclude: options.exclude || [/node_modules/],
11
- enforce: "enforce" in options ? options.enforce : "pre",
12
- typical: options.typical
13
- };
14
- }
15
-
16
- //#endregion
17
- //#region src/core/transform.ts
18
- let cachedCompilerOptions;
19
- const sourceFileCache = /* @__PURE__ */ new Map();
20
- const TRANSFORM_EXTENSIONS = new Set([
21
- ".ts",
22
- ".tsx",
23
- ".mts",
24
- ".cts"
25
- ]);
26
- /**
27
- * Transform a TypeScript file with Typical.
28
- *
29
- * Creates a program per file that includes the provided source content.
30
- * This ensures the type checker can resolve types for the incoming code.
31
- */
32
- function transformTypia(id, source, config) {
33
- const ext = extname(id).toLowerCase();
34
- if (!TRANSFORM_EXTENSIONS.has(ext)) return;
35
- const { program, sourceFile } = createProgramWithSource(resolve(id), source, getCompilerOptions());
36
- const result = new TypicalTransformer(config, program).transform(sourceFile, "js");
37
- if (process.env.DEBUG) console.log("[unplugin-typical] Transform output (first 1000 chars):", result.substring(0, 1e3));
38
- return result;
39
- }
40
- /**
41
- * Get TypeScript compiler options from tsconfig.json (cached)
42
- */
43
- function getCompilerOptions() {
44
- if (cachedCompilerOptions) return cachedCompilerOptions;
45
- const configPath = ts.findConfigFile(process.cwd(), ts.sys.fileExists, "tsconfig.json");
46
- if (!configPath) {
47
- cachedCompilerOptions = {
48
- target: ts.ScriptTarget.ES2020,
49
- module: ts.ModuleKind.ESNext,
50
- moduleResolution: ts.ModuleResolutionKind.Bundler,
51
- esModuleInterop: true,
52
- strict: true
53
- };
54
- return cachedCompilerOptions;
55
- }
56
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
57
- if (configFile.error) throw new Error(ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n"));
58
- cachedCompilerOptions = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath)).options;
59
- return cachedCompilerOptions;
60
- }
61
- /**
62
- * Create a TypeScript program with the provided source content.
63
- * Uses a custom compiler host that:
64
- * - Returns the provided source for the target file
65
- * - Caches other source files from disk for reuse
66
- */
67
- function createProgramWithSource(id, source, compilerOptions) {
68
- const sourceFile = ts.createSourceFile(id, source, compilerOptions.target ?? ts.ScriptTarget.ES2020, true);
69
- const host = ts.createCompilerHost(compilerOptions);
70
- const originalGetSourceFile = host.getSourceFile.bind(host);
71
- host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
72
- const resolvedFileName = resolve(fileName);
73
- if (resolvedFileName === id) return sourceFile;
74
- const cached = sourceFileCache.get(resolvedFileName);
75
- if (cached) return cached;
76
- const result = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
77
- if (result) sourceFileCache.set(resolvedFileName, result);
78
- return result;
79
- };
80
- const program = ts.createProgram([id], compilerOptions, host);
81
- if (process.env.DEBUG) console.log("[unplugin-typical] Program source files:", program.getSourceFiles().map((sf) => sf.fileName));
82
- return {
83
- program,
84
- sourceFile: program.getSourceFile(id)
85
- };
86
- }
87
-
88
- //#endregion
89
- //#region src/index.ts
90
- const Typical = createUnplugin((rawOptions = {}) => {
91
- const options = resolveOptions(rawOptions);
92
- const typicalConfig = {
93
- ...loadConfig(),
94
- ...options.typical
95
- };
96
- return {
97
- name: "unplugin-typical",
98
- enforce: options.enforce,
99
- transform: {
100
- filter: { id: {
101
- include: options.include,
102
- exclude: options.exclude
103
- } },
104
- handler(code, id) {
105
- return transformTypia(id, code, typicalConfig);
106
- }
107
- }
108
- };
109
- });
110
-
111
- //#endregion
112
- export { Typical as t };