env-runner 0.1.9 → 0.1.10
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 +112 -15
- package/dist/_chunks/base-runner.d.mts +46 -3
- package/dist/_chunks/base-runner.mjs +28 -1
- package/dist/_chunks/libs/cjs-module-lexer.mjs +1 -1
- package/dist/_chunks/runner.d.mts +3 -3
- package/dist/_chunks/runner.mjs +6 -3
- package/dist/_chunks/runner2.d.mts +3 -3
- package/dist/_chunks/runner2.mjs +66 -16
- package/dist/_chunks/runner3.d.mts +3 -3
- package/dist/_chunks/runner3.mjs +3 -3
- package/dist/_chunks/runner4.d.mts +4 -4
- package/dist/_chunks/runner4.mjs +2 -2
- package/dist/_chunks/runner5.d.mts +4 -4
- package/dist/_chunks/runner5.mjs +2 -2
- package/dist/_chunks/server.mjs +25 -9
- package/dist/_chunks/types.d.mts +4 -2
- package/dist/_chunks/virtual-loader.mjs +39 -0
- package/dist/_chunks/worker-utils.mjs +86 -9
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +23 -11
- package/dist/index.mjs +6 -6
- package/dist/runners/bun-process/runner.d.mts +2 -2
- package/dist/runners/bun-process/runner.mjs +12 -2
- package/dist/runners/bun-process/worker.mjs +19 -3
- package/dist/runners/deno-process/runner.d.mts +2 -2
- package/dist/runners/deno-process/runner.mjs +1 -1
- package/dist/runners/deno-process/worker.mjs +18 -3
- package/dist/runners/miniflare/runner.d.mts +2 -2
- package/dist/runners/miniflare/runner.mjs +1 -1
- package/dist/runners/netlify/runner.d.mts +2 -2
- package/dist/runners/netlify/runner.mjs +1 -1
- package/dist/runners/node-process/runner.d.mts +2 -2
- package/dist/runners/node-process/runner.mjs +4 -2
- package/dist/runners/node-process/worker.mjs +19 -3
- package/dist/runners/node-worker/runner.d.mts +2 -2
- package/dist/runners/node-worker/runner.mjs +1 -1
- package/dist/runners/node-worker/worker.mjs +18 -3
- package/dist/runners/self/runner.d.mts +2 -2
- package/dist/runners/self/runner.mjs +2 -2
- package/dist/runners/vercel/runner.d.mts +2 -2
- package/dist/runners/vercel/runner.mjs +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ import { serve } from "srvx";
|
|
|
50
50
|
import { EnvServer } from "env-runner";
|
|
51
51
|
|
|
52
52
|
const envServer = new EnvServer({
|
|
53
|
-
runner: "node-process",
|
|
53
|
+
runner: "node-process", // optional, defaults to "node-worker"
|
|
54
54
|
entry: "./app.ts",
|
|
55
55
|
watch: true,
|
|
56
56
|
watchPaths: ["./src"],
|
|
@@ -64,6 +64,7 @@ envServer.onReload(() => {
|
|
|
64
64
|
console.log("Reloaded!");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
// Optional — the server auto-starts on first fetch()
|
|
67
68
|
await envServer.start();
|
|
68
69
|
|
|
69
70
|
// Use with any HTTP server
|
|
@@ -79,7 +80,7 @@ Proxy manager for hot-reload with message queueing and listener forwarding:
|
|
|
79
80
|
```ts
|
|
80
81
|
import { RunnerManager, NodeProcessEnvRunner } from "env-runner";
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
await using manager = new RunnerManager();
|
|
83
84
|
|
|
84
85
|
manager.onReady((_runner, address) => {
|
|
85
86
|
console.log("Ready:", address);
|
|
@@ -106,9 +107,11 @@ await manager.reload(newRunner); // old runner is closed automatically
|
|
|
106
107
|
manager.sendMessage({ type: "config", value: 42 });
|
|
107
108
|
manager.onMessage((msg) => console.log("From worker:", msg));
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
// manager.close() is awaited automatically at the end of the scope (`await using`)
|
|
110
111
|
```
|
|
111
112
|
|
|
113
|
+
All runners, `RunnerManager`, and `EnvServer` implement `AsyncDisposable`, so they can be auto-closed with [explicit resource management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using) (`await using`) — or closed manually with `await runner.close()`.
|
|
114
|
+
|
|
112
115
|
### Runners
|
|
113
116
|
|
|
114
117
|
Use runners directly for lower-level control:
|
|
@@ -127,7 +130,7 @@ import { NetlifyEnvRunner } from "env-runner/runners/netlify";
|
|
|
127
130
|
All runners implement the [`EnvRunner`](./src/types.ts) interface:
|
|
128
131
|
|
|
129
132
|
```ts
|
|
130
|
-
|
|
133
|
+
await using runner = new NodeProcessEnvRunner({
|
|
131
134
|
name: "my-app",
|
|
132
135
|
data: { entry: "./app.ts" },
|
|
133
136
|
hooks: {
|
|
@@ -156,8 +159,8 @@ const result = await runner.rpc<string>("transformHTML", "<html>...</html>");
|
|
|
156
159
|
// Hot-reload entry module without restarting the worker
|
|
157
160
|
await runner.reloadModule();
|
|
158
161
|
|
|
159
|
-
// Graceful shutdown
|
|
160
|
-
await runner.close()
|
|
162
|
+
// Graceful shutdown happens automatically at the end of the scope
|
|
163
|
+
// (`await using`) — or call `await runner.close()` explicitly
|
|
161
164
|
```
|
|
162
165
|
|
|
163
166
|
**Available runners:**
|
|
@@ -173,6 +176,98 @@ await runner.close();
|
|
|
173
176
|
| `VercelEnvRunner` | Worker thread (Vercel context) | `workerData` / `parentPort` |
|
|
174
177
|
| `NetlifyEnvRunner` | Worker thread (Netlify context) | `workerData` / `parentPort` |
|
|
175
178
|
|
|
179
|
+
#### Virtual Modules
|
|
180
|
+
|
|
181
|
+
The Node.js runners (`NodeWorkerEnvRunner`, `NodeProcessEnvRunner`, and the runners built on top of them), `BunProcessEnvRunner`, `DenoProcessEnvRunner` (Deno >= 2.x), and `MiniflareEnvRunner` can serve **virtual modules** from an in-memory `specifier => source` map passed via `data.virtual`. The entry (and its dependencies) can then `import` them as if they were real files:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { NodeWorkerEnvRunner } from "env-runner/runners/node-worker";
|
|
185
|
+
|
|
186
|
+
await using runner = new NodeWorkerEnvRunner({
|
|
187
|
+
name: "my-app",
|
|
188
|
+
data: {
|
|
189
|
+
entry: "./app.ts",
|
|
190
|
+
virtual: {
|
|
191
|
+
"#config": `export const apiBase = "https://api.example.com";`,
|
|
192
|
+
"#banner": `export default "Hello from a virtual module!";`,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
// app.ts
|
|
200
|
+
import banner from "#banner";
|
|
201
|
+
import { apiBase } from "#config";
|
|
202
|
+
|
|
203
|
+
export default {
|
|
204
|
+
fetch: () => new Response(`${banner} (${apiBase})`),
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The **entry itself can be virtual** — set `data.entry` to one of the `data.virtual` keys to run an entry whose source lives in memory (it may import other virtual modules too):
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
await using runner = new NodeWorkerEnvRunner({
|
|
212
|
+
name: "my-app",
|
|
213
|
+
data: {
|
|
214
|
+
entry: "#entry",
|
|
215
|
+
virtual: {
|
|
216
|
+
"#entry": `import { body } from "#dep";
|
|
217
|
+
export default { fetch: () => new Response(body) };`,
|
|
218
|
+
"#dep": `export const body = "Hello from a virtual entry!";`,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Each source may also be a **factory** `() => string | Promise<string>` instead of a literal string — useful for lazily computed or asynchronously loaded sources:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
await using runner = new NodeWorkerEnvRunner({
|
|
228
|
+
name: "my-app",
|
|
229
|
+
data: {
|
|
230
|
+
entry: "./app.ts",
|
|
231
|
+
virtual: {
|
|
232
|
+
"#config": () => `export const apiBase = ${JSON.stringify(getApiBase())};`,
|
|
233
|
+
"#schema": async () => `export default ${await loadSchemaJson()};`,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Factories are invoked once on the host (before the worker is spawned), so the worker always receives plain strings — functions can't cross the `workerData`/`JSON` boundary, and Node's synchronous load hook can't await. For the same reason, **all** factories are resolved eagerly at startup (in parallel), not lazily on first import — so keep them cheap, or use plain strings for sources that don't need computation. Maps containing only strings skip this step entirely.
|
|
240
|
+
|
|
241
|
+
The module format is derived from the specifier extension: `.ts`/`.mts` sources are served as **TypeScript** and `.json` sources as **JSON modules**; everything else is plain JavaScript ESM:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
await using runner = new NodeWorkerEnvRunner({
|
|
245
|
+
name: "my-app",
|
|
246
|
+
data: {
|
|
247
|
+
entry: "#entry.ts",
|
|
248
|
+
virtual: {
|
|
249
|
+
"#entry.ts": `
|
|
250
|
+
import { getGreeting } from "#util.ts";
|
|
251
|
+
import config from "#config.json";
|
|
252
|
+
const handler: () => Response = () => new Response(getGreeting(config.name));
|
|
253
|
+
export default { fetch: handler };
|
|
254
|
+
`,
|
|
255
|
+
"#util.ts": `export function getGreeting(name: string): string {
|
|
256
|
+
return \`Hello, \${name}!\`;
|
|
257
|
+
}`,
|
|
258
|
+
"#config.json": JSON.stringify({ name: "virtual" }),
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- **TypeScript** is type-stripped by Node's native [type stripping](https://nodejs.org/api/typescript.html#type-stripping) (Node.js >= 22.18 / 23.6 — erasable syntax only) and by Bun's `ts` loader. On Deno, custom load hooks bypass its native type stripping, so sources are pre-stripped with [`module.stripTypeScriptTypes`](https://docs.deno.com/api/node/module/~/Module.stripTypeScriptTypes) (Deno >= 2.8.2); on older Deno without it, virtual `.ts`/`.mts` sources **throw at registration** — pass pre-transpiled JavaScript instead. On miniflare, sources are likewise pre-stripped with `module.stripTypeScriptTypes` on the host (workerd does not parse TypeScript).
|
|
265
|
+
- **JSON** sources expose the parsed value as the default export on all runtimes. The `with { type: "json" }` import attribute is optional on Node.js and Bun; on Deno and miniflare it must be **omitted** (static imports carrying an import attribute bypass `registerHooks` resolution on Deno, and workerd rejects import attributes outright).
|
|
266
|
+
|
|
267
|
+
Virtual modules are registered inside the worker, before the entry is imported. On Node.js (>= 22.15 / 23.5) and Deno (>= 2.x) this uses [ESM customization hooks](https://nodejs.org/api/module.html#moduleregisterhooksoptions) (`module.registerHooks`); on Bun (which does not implement `registerHooks`) it uses [`Bun.plugin()`](https://bun.com/docs/runtime/plugins) virtual modules instead. The source string is treated as an ES module, and virtual specifiers (including a virtual entry) resolve across `reloadModule()`. On runtimes supporting neither mechanism, a warning is logged and registration is skipped. When the worker shuts down gracefully the registration is unregistered again (the `registerHooks` registration is deregistered; on Bun, which has no plugin-removal API, the in-memory source map is detached so fresh loads and reloads stop resolving).
|
|
268
|
+
|
|
269
|
+
On `MiniflareEnvRunner` there is no in-worker registration: the runner's module fallback service serves virtual specifiers to workerd directly (taking precedence over disk files and the `transformRequest` pipeline, so a virtual key overrides a real file with the same path). One limitation: named `exports` (Durable Objects / WorkerEntrypoints) cannot be combined with a **virtual entry** — the wrapper would need a static re-export that miniflare cannot resolve at startup — and the runner fails fast with a clear error in that case.
|
|
270
|
+
|
|
176
271
|
#### Miniflare Runner
|
|
177
272
|
|
|
178
273
|
Run your app in the Cloudflare Workers runtime using [miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare):
|
|
@@ -184,7 +279,7 @@ npm install miniflare
|
|
|
184
279
|
```ts
|
|
185
280
|
import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
|
|
186
281
|
|
|
187
|
-
|
|
282
|
+
await using runner = new MiniflareEnvRunner({
|
|
188
283
|
name: "my-worker",
|
|
189
284
|
data: { entry: "./worker.ts" },
|
|
190
285
|
miniflareOptions: {
|
|
@@ -194,7 +289,6 @@ const runner = new MiniflareEnvRunner({
|
|
|
194
289
|
});
|
|
195
290
|
|
|
196
291
|
const response = await runner.fetch("http://localhost/api");
|
|
197
|
-
await runner.close();
|
|
198
292
|
```
|
|
199
293
|
|
|
200
294
|
The `miniflareOptions` object is passed directly to the [Miniflare constructor](https://developers.cloudflare.com/workers/testing/miniflare/) — you can configure bindings, KV, D1, Durable Objects, and any other Miniflare option.
|
|
@@ -206,7 +300,7 @@ Pass a `transformRequest` callback to route module resolution through Vite's (or
|
|
|
206
300
|
```ts
|
|
207
301
|
import { MiniflareEnvRunner } from "env-runner/runners/miniflare";
|
|
208
302
|
|
|
209
|
-
|
|
303
|
+
await using runner = new MiniflareEnvRunner({
|
|
210
304
|
name: "my-worker",
|
|
211
305
|
data: { entry: "./worker.ts" },
|
|
212
306
|
// Route module resolution through Vite's transform pipeline
|
|
@@ -245,7 +339,7 @@ export default {
|
|
|
245
339
|
To explicitly declare exports or override auto-detection:
|
|
246
340
|
|
|
247
341
|
```ts
|
|
248
|
-
|
|
342
|
+
await using runner = new MiniflareEnvRunner({
|
|
249
343
|
name: "my-worker",
|
|
250
344
|
data: { entry: "./worker.ts" },
|
|
251
345
|
// Explicit exports (merged with auto-detected ones)
|
|
@@ -300,7 +394,7 @@ Simulates a Vercel deployment environment with automatic header injection (`x-ve
|
|
|
300
394
|
```ts
|
|
301
395
|
import { VercelEnvRunner } from "env-runner/runners/vercel";
|
|
302
396
|
|
|
303
|
-
|
|
397
|
+
await using runner = new VercelEnvRunner({
|
|
304
398
|
name: "my-app",
|
|
305
399
|
data: { entry: "./app.ts" },
|
|
306
400
|
});
|
|
@@ -313,7 +407,7 @@ Simulates a Netlify deployment environment with automatic header injection (`x-n
|
|
|
313
407
|
```ts
|
|
314
408
|
import { NetlifyEnvRunner } from "env-runner/runners/netlify";
|
|
315
409
|
|
|
316
|
-
|
|
410
|
+
await using runner = new NetlifyEnvRunner({
|
|
317
411
|
name: "my-app",
|
|
318
412
|
data: { entry: "./app.ts" },
|
|
319
413
|
});
|
|
@@ -343,7 +437,10 @@ const env = new DevEnvironment("ssr", config, { hot: true, transport });
|
|
|
343
437
|
import { createViteTransport } from "env-runner/vite";
|
|
344
438
|
|
|
345
439
|
const transport = createViteTransport(sendMessage, onMessage, "ssr");
|
|
346
|
-
const runner = new ModuleRunner({
|
|
440
|
+
const runner = new ModuleRunner({
|
|
441
|
+
transport,
|
|
442
|
+
sourcemapInterceptor: "prepareStackTrace",
|
|
443
|
+
});
|
|
347
444
|
```
|
|
348
445
|
|
|
349
446
|
Messages are namespaced by environment name, so multiple Vite environments can share a single runner's IPC channel.
|
|
@@ -389,7 +486,7 @@ You can also use `loadRunner()` to dynamically load a runner by name:
|
|
|
389
486
|
```ts
|
|
390
487
|
import { loadRunner } from "env-runner";
|
|
391
488
|
|
|
392
|
-
|
|
489
|
+
await using runner = await loadRunner("node-worker", {
|
|
393
490
|
name: "my-app",
|
|
394
491
|
data: { entry: "./app.ts" },
|
|
395
492
|
});
|
|
@@ -448,7 +545,7 @@ The built-in worker automatically:
|
|
|
448
545
|
For advanced use cases, you can provide a custom worker entry:
|
|
449
546
|
|
|
450
547
|
```ts
|
|
451
|
-
|
|
548
|
+
await using runner = new NodeProcessEnvRunner({
|
|
452
549
|
name: "my-app",
|
|
453
550
|
workerEntry: "/path/to/custom-worker.ts",
|
|
454
551
|
data: { entry: "./app.ts" },
|
|
@@ -1,11 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EnvRunner, RunnerMessageListener, WorkerAddress, WorkerHooks } from "./types.mjs";
|
|
2
2
|
import { IncomingMessage } from "node:http";
|
|
3
3
|
import { Socket } from "node:net";
|
|
4
|
+
/**
|
|
5
|
+
* Source for a virtual module: either a literal ES module string or a factory
|
|
6
|
+
* that returns one (sync or async).
|
|
7
|
+
*
|
|
8
|
+
* Factories are evaluated **once on the host side** before the worker is spawned
|
|
9
|
+
* (functions can't cross the `workerData`/`JSON` boundary, and Node's synchronous
|
|
10
|
+
* load hook can't await), so the worker always receives plain strings. See
|
|
11
|
+
* {@link resolveVirtualModules}.
|
|
12
|
+
*/
|
|
13
|
+
type VirtualModuleSource = string | (() => string | Promise<string>);
|
|
14
|
+
/** Virtual modules as a `specifier => source` map. */
|
|
15
|
+
type VirtualModules = Record<string, VirtualModuleSource>;
|
|
4
16
|
interface EnvRunnerData {
|
|
5
17
|
name?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Virtual modules as a `specifier => source` map.
|
|
20
|
+
*
|
|
21
|
+
* Registered as Node.js ESM customization hooks in the worker so the entry
|
|
22
|
+
* (and its dependencies) can `import` them, e.g.
|
|
23
|
+
* `{ "#virtual-import": "export const foo = 1" }`.
|
|
24
|
+
*
|
|
25
|
+
* Each source may be a string or a factory `() => string | Promise<string>`.
|
|
26
|
+
* Factories are evaluated once on the host before the worker is spawned (so the
|
|
27
|
+
* worker always receives plain strings).
|
|
28
|
+
*
|
|
29
|
+
* Supported by the `node-worker`, `node-process`, `bun-process`,
|
|
30
|
+
* `deno-process`, `vercel`, `netlify`, and `miniflare` runners.
|
|
31
|
+
*/
|
|
32
|
+
virtual?: VirtualModules;
|
|
6
33
|
[key: string]: unknown;
|
|
7
34
|
}
|
|
8
|
-
declare abstract class BaseEnvRunner implements EnvRunner {
|
|
35
|
+
declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
|
|
9
36
|
closed: boolean;
|
|
10
37
|
protected _name: string;
|
|
11
38
|
protected _workerEntry: string;
|
|
@@ -37,10 +64,26 @@ declare abstract class BaseEnvRunner implements EnvRunner {
|
|
|
37
64
|
}): Promise<T>;
|
|
38
65
|
reloadModule(timeout?: number): Promise<void>;
|
|
39
66
|
close(cause?: unknown): Promise<void>;
|
|
67
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
40
68
|
protected _handleMessage(message: any): void;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve any factory-valued `data.virtual` sources to strings before the
|
|
71
|
+
* worker is spawned. Returns a pending promise only when there is async work
|
|
72
|
+
* to do (a factory is present); otherwise returns `undefined` so subclasses can
|
|
73
|
+
* keep their synchronous spawn path. Factories must be resolved here because
|
|
74
|
+
* functions can't cross the worker boundary and the load hook can't await.
|
|
75
|
+
*/
|
|
76
|
+
protected _resolveVirtualData(): Promise<void> | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Run a subclass spawn callback after `data.virtual` is resolved.
|
|
79
|
+
* Synchronous when no factory-valued source is present; otherwise defers
|
|
80
|
+
* `init` until factories resolve. A throwing/rejecting factory closes the
|
|
81
|
+
* runner with the error as cause instead of leaving an unhandled rejection.
|
|
82
|
+
*/
|
|
83
|
+
protected _initWithVirtualData(init: () => void): void;
|
|
41
84
|
protected _closeSocket(): Promise<void>;
|
|
42
85
|
protected abstract _hasRuntime(): boolean;
|
|
43
86
|
protected abstract _closeRuntime(): Promise<void>;
|
|
44
87
|
protected abstract _runtimeType(): string;
|
|
45
88
|
}
|
|
46
|
-
export { EnvRunnerData
|
|
89
|
+
export { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveVirtualModules } from "./virtual-loader.mjs";
|
|
1
2
|
import { rm } from "node:fs/promises";
|
|
2
3
|
import { proxyFetch, proxyUpgrade } from "httpxy";
|
|
3
4
|
var BaseEnvRunner = class {
|
|
@@ -35,6 +36,7 @@ var BaseEnvRunner = class {
|
|
|
35
36
|
}
|
|
36
37
|
waitForReady(timeout = 5e3) {
|
|
37
38
|
if (this.ready) return Promise.resolve();
|
|
39
|
+
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
|
|
38
40
|
return new Promise((resolve, reject) => {
|
|
39
41
|
const timer = setTimeout(() => {
|
|
40
42
|
this._messageListeners.delete(listener);
|
|
@@ -45,6 +47,10 @@ var BaseEnvRunner = class {
|
|
|
45
47
|
clearTimeout(timer);
|
|
46
48
|
this._messageListeners.delete(listener);
|
|
47
49
|
resolve();
|
|
50
|
+
} else if (this.closed) {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
this._messageListeners.delete(listener);
|
|
53
|
+
reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
|
|
48
54
|
}
|
|
49
55
|
};
|
|
50
56
|
this._messageListeners.add(listener);
|
|
@@ -107,6 +113,9 @@ var BaseEnvRunner = class {
|
|
|
107
113
|
await this._closeRuntime().catch(onError);
|
|
108
114
|
await this._closeSocket().catch(onError);
|
|
109
115
|
}
|
|
116
|
+
async [Symbol.asyncDispose]() {
|
|
117
|
+
await this.close();
|
|
118
|
+
}
|
|
110
119
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
111
120
|
const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
|
|
112
121
|
return `${this.constructor.name}#${this._name}(${status})`;
|
|
@@ -116,12 +125,30 @@ var BaseEnvRunner = class {
|
|
|
116
125
|
this._address = message.address;
|
|
117
126
|
this._hooks.onReady?.(this, this._address);
|
|
118
127
|
}
|
|
128
|
+
if (message?.event === "init-error" && !this.ready && !this.closed) this.close(new Error(String(message.error || "Worker initialization failed")));
|
|
119
129
|
for (const listener of this._messageListeners) listener(message);
|
|
120
130
|
}
|
|
131
|
+
_resolveVirtualData() {
|
|
132
|
+
const virtual = this._data?.virtual;
|
|
133
|
+
if (!virtual || !Object.values(virtual).some((v) => typeof v === "function")) return;
|
|
134
|
+
return resolveVirtualModules(virtual).then((resolved) => {
|
|
135
|
+
this._data = {
|
|
136
|
+
...this._data,
|
|
137
|
+
virtual: resolved
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
_initWithVirtualData(init) {
|
|
142
|
+
const pending = this._resolveVirtualData();
|
|
143
|
+
if (pending) pending.then(() => {
|
|
144
|
+
if (!this.closed) init();
|
|
145
|
+
}, (error) => this.close(error));
|
|
146
|
+
else init();
|
|
147
|
+
}
|
|
121
148
|
async _closeSocket() {
|
|
122
149
|
const socketPath = this._address?.socketPath;
|
|
123
150
|
if (socketPath && socketPath[0] !== "\0" && !socketPath.startsWith(String.raw`\\.\\pipe`)) await rm(socketPath).catch(() => {});
|
|
124
151
|
this._address = void 0;
|
|
125
152
|
}
|
|
126
153
|
};
|
|
127
|
-
export { BaseEnvRunner
|
|
154
|
+
export { BaseEnvRunner };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
|
|
3
3
|
declare class DenoProcessEnvRunner extends BaseEnvRunner {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(opts: {
|
|
@@ -14,4 +14,4 @@ declare class DenoProcessEnvRunner extends BaseEnvRunner {
|
|
|
14
14
|
protected _runtimeType(): string;
|
|
15
15
|
protected _closeRuntime(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
-
export { DenoProcessEnvRunner
|
|
17
|
+
export { DenoProcessEnvRunner };
|
package/dist/_chunks/runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseEnvRunner } from "./base-runner.mjs";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,7 +11,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
11
11
|
...opts,
|
|
12
12
|
workerEntry: opts.workerEntry || _defaultEntry
|
|
13
13
|
});
|
|
14
|
-
this.#initProcess(opts.execArgv);
|
|
14
|
+
this._initWithVirtualData(() => this.#initProcess(opts.execArgv));
|
|
15
15
|
}
|
|
16
16
|
sendMessage(message) {
|
|
17
17
|
if (!this.#process) throw new Error("Deno env process should be initialized before sending messages.");
|
|
@@ -88,10 +88,13 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
|
|
|
88
88
|
buffer = buffer.slice(newlineIdx + 1);
|
|
89
89
|
if (line.startsWith("{")) try {
|
|
90
90
|
this._handleMessage(JSON.parse(line));
|
|
91
|
+
continue;
|
|
91
92
|
} catch {}
|
|
93
|
+
process.stdout.write(line + "\n");
|
|
92
94
|
}
|
|
93
95
|
});
|
|
96
|
+
child.stderr?.pipe(process.stderr);
|
|
94
97
|
this.#process = handle;
|
|
95
98
|
}
|
|
96
99
|
};
|
|
97
|
-
export { DenoProcessEnvRunner
|
|
100
|
+
export { DenoProcessEnvRunner };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
|
|
3
3
|
import { IncomingMessage } from "node:http";
|
|
4
4
|
import { Socket } from "node:net";
|
|
5
5
|
/** Result from a module transform (compatible with Vite's `TransformResult`). */
|
|
@@ -81,4 +81,4 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {
|
|
|
81
81
|
};
|
|
82
82
|
}): Promise<void>;
|
|
83
83
|
}
|
|
84
|
-
export {
|
|
84
|
+
export { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult };
|
package/dist/_chunks/runner2.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { virtualModuleFormat } from "./virtual-loader.mjs";
|
|
2
|
+
import { BaseEnvRunner } from "./base-runner.mjs";
|
|
3
|
+
import { init, parse } from "./libs/cjs-module-lexer.mjs";
|
|
4
|
+
import { isVirtualSpecifier } from "./worker-utils.mjs";
|
|
3
5
|
import { createRequire } from "node:module";
|
|
4
6
|
import { proxyUpgrade } from "httpxy";
|
|
5
7
|
import { readFileSync } from "node:fs";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { basename, dirname, resolve } from "node:path";
|
|
9
|
+
import { basename, dirname, isAbsolute, resolve } from "node:path";
|
|
8
10
|
import { resolveModulePath } from "exsolve";
|
|
9
11
|
const IPC_PATH = "/__env_runner_ipc";
|
|
10
12
|
const IPC_BINDING = "__ENV_RUNNER_IPC";
|
|
@@ -199,7 +201,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
199
201
|
this.#exports = opts.exports ?? {};
|
|
200
202
|
this.#captureErrors = opts.captureErrors ?? true;
|
|
201
203
|
this.#exportConditions = opts.exportConditions ?? ["workerd", "worker"];
|
|
202
|
-
this.#init();
|
|
204
|
+
this._initWithVirtualData(() => this.#init());
|
|
203
205
|
}
|
|
204
206
|
static async disposeAll() {
|
|
205
207
|
const entries = [..._miniflareCache.values()];
|
|
@@ -220,6 +222,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
220
222
|
if (!this.closed) await this.close();
|
|
221
223
|
}
|
|
222
224
|
async fetch(input, init) {
|
|
225
|
+
for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
|
|
223
226
|
if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
|
|
224
227
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
225
228
|
const res = await this.#miniflare.dispatchFetch(url, init);
|
|
@@ -315,9 +318,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
315
318
|
port: Number(address.port)
|
|
316
319
|
}, context.node.req, context.node.socket, context.node.head);
|
|
317
320
|
}
|
|
321
|
+
async #prepareVirtualModules() {
|
|
322
|
+
const virtual = this._data?.virtual;
|
|
323
|
+
if (!virtual || Object.keys(virtual).length === 0) return;
|
|
324
|
+
const out = {};
|
|
325
|
+
let strip;
|
|
326
|
+
for (const [specifier, source] of Object.entries(virtual)) if (virtualModuleFormat(specifier) === "module-typescript") {
|
|
327
|
+
if (!strip) {
|
|
328
|
+
const { stripTypeScriptTypes } = await import("node:module");
|
|
329
|
+
if (typeof stripTypeScriptTypes !== "function") throw new TypeError(`[env-runner] virtual TypeScript module "${specifier}" requires \`module.stripTypeScriptTypes\` on the host (workerd does not parse TypeScript); upgrade Node.js or provide a pre-transpiled JavaScript source instead.`);
|
|
330
|
+
strip = stripTypeScriptTypes;
|
|
331
|
+
}
|
|
332
|
+
out[specifier] = strip(source);
|
|
333
|
+
} else out[specifier] = source;
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
318
336
|
async #initAsync() {
|
|
319
337
|
const { Miniflare } = await import("miniflare");
|
|
320
338
|
const entryPath = this._data?.entry;
|
|
339
|
+
const virtual = await this.#prepareVirtualModules();
|
|
321
340
|
const userFlags = this.#miniflareOptions.compatibilityFlags || [];
|
|
322
341
|
const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
|
|
323
342
|
const options = {
|
|
@@ -331,9 +350,13 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
331
350
|
}, ...userDirectSockets]
|
|
332
351
|
};
|
|
333
352
|
if (entryPath && !options.script && !options.scriptPath) {
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
const
|
|
353
|
+
const entryIsVirtual = isVirtualSpecifier(entryPath, virtual);
|
|
354
|
+
const resolvedEntry = entryIsVirtual ? entryPath : resolve(entryPath);
|
|
355
|
+
const entryBase = isAbsolute(resolvedEntry) ? resolvedEntry : resolve("__env_runner_virtual_entry__.mjs");
|
|
356
|
+
const entryDir = dirname(entryBase);
|
|
357
|
+
const entrySource = entryIsVirtual ? virtual[entryPath] : _tryReadFile(resolvedEntry);
|
|
358
|
+
const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
|
|
359
|
+
if (entryIsVirtual && detectedExports.length > 0) throw new Error(`[env-runner] named exports (${detectedExports.join(", ")}) are not supported with a virtual entry on the miniflare runner; pass \`exports: false\` or use a real entry file.`);
|
|
337
360
|
if (detectedExports.length > 0 && !options.durableObjects) {
|
|
338
361
|
const autoDOs = { ...this.#miniflareOptions.durableObjects || {} };
|
|
339
362
|
for (const name of detectedExports) {
|
|
@@ -370,7 +393,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
370
393
|
]
|
|
371
394
|
}];
|
|
372
395
|
if (!options.unsafeModuleFallbackService) {
|
|
373
|
-
const _require = createRequire(
|
|
396
|
+
const _require = createRequire(entryBase);
|
|
397
|
+
const _virtual = virtual;
|
|
374
398
|
const _transformRequest = this.#transformRequest;
|
|
375
399
|
const _exportConditions = this.#exportConditions;
|
|
376
400
|
options.unsafeUseModuleFallbackService = true;
|
|
@@ -385,6 +409,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
385
409
|
if (!specifier) return new Response(null, { status: 404 });
|
|
386
410
|
const cleanSpecifier = specifier.split("?")[0] || specifier;
|
|
387
411
|
const cleanRaw = rawSpecifier?.split("?")[0];
|
|
412
|
+
if (_virtual) {
|
|
413
|
+
const bareSpecifier = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
|
|
414
|
+
const virtualKey = [
|
|
415
|
+
cleanRaw,
|
|
416
|
+
cleanSpecifier,
|
|
417
|
+
bareSpecifier
|
|
418
|
+
].find((key) => key !== void 0 && Object.hasOwn(_virtual, key));
|
|
419
|
+
if (virtualKey !== void 0) {
|
|
420
|
+
const name = bareSpecifier + (specifier.includes("?") ? specifier.slice(specifier.indexOf("?")) : "");
|
|
421
|
+
const source = _virtual[virtualKey];
|
|
422
|
+
return virtualModuleFormat(virtualKey) === "json" ? Response.json({
|
|
423
|
+
name,
|
|
424
|
+
json: source
|
|
425
|
+
}) : Response.json({
|
|
426
|
+
name,
|
|
427
|
+
esModule: source
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
388
431
|
let resolvedPath;
|
|
389
432
|
const fileUrlRaw = cleanRaw || cleanSpecifier;
|
|
390
433
|
if (fileUrlRaw.startsWith("file://")) try {
|
|
@@ -406,7 +449,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
406
449
|
}
|
|
407
450
|
} else try {
|
|
408
451
|
resolvedPath = resolveModulePath(cleanRaw, {
|
|
409
|
-
from: referrerReal ||
|
|
452
|
+
from: referrerReal || entryBase,
|
|
410
453
|
conditions: _exportConditions,
|
|
411
454
|
try: true
|
|
412
455
|
}) || contextRequire.resolve(cleanRaw);
|
|
@@ -467,7 +510,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
467
510
|
if (this.#persistent && entryPath) {
|
|
468
511
|
this.#cacheKey = computeCacheKey(entryPath, {
|
|
469
512
|
...this.#miniflareOptions,
|
|
470
|
-
_exportConditions: this.#exportConditions
|
|
513
|
+
_exportConditions: this.#exportConditions,
|
|
514
|
+
_virtual: virtual
|
|
471
515
|
});
|
|
472
516
|
const cached = _miniflareCache.get(this.#cacheKey);
|
|
473
517
|
if (cached) {
|
|
@@ -503,16 +547,22 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
503
547
|
} });
|
|
504
548
|
}
|
|
505
549
|
};
|
|
506
|
-
function detectExportedClasses(
|
|
550
|
+
function detectExportedClasses(entrySource, explicit) {
|
|
507
551
|
const names = new Set(Object.keys(explicit));
|
|
508
|
-
|
|
509
|
-
const source = readFileSync(entryPath, "utf8");
|
|
552
|
+
if (entrySource) {
|
|
510
553
|
const re = /\bexport\s+class\s+(\w+)/g;
|
|
511
554
|
let match;
|
|
512
|
-
while (match = re.exec(
|
|
513
|
-
}
|
|
555
|
+
while (match = re.exec(entrySource)) if (match[1]) names.add(match[1]);
|
|
556
|
+
}
|
|
514
557
|
return [...names];
|
|
515
558
|
}
|
|
559
|
+
function _tryReadFile(path) {
|
|
560
|
+
try {
|
|
561
|
+
return readFileSync(path, "utf8");
|
|
562
|
+
} catch {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
516
566
|
function toScreamingSnakeCase(name) {
|
|
517
567
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
518
568
|
}
|
|
@@ -536,4 +586,4 @@ function createCjsEsmShim(cjsSpecifier, contents) {
|
|
|
536
586
|
for (const name of namedExports) shim += `export var ${name} = __cjs_mod__["${name}"];\n`;
|
|
537
587
|
return shim;
|
|
538
588
|
}
|
|
539
|
-
export { MiniflareEnvRunner
|
|
589
|
+
export { MiniflareEnvRunner };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { BaseEnvRunner, EnvRunnerData } from "./base-runner.mjs";
|
|
3
3
|
declare class NodeWorkerEnvRunner extends BaseEnvRunner {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(opts: {
|
|
@@ -13,4 +13,4 @@ declare class NodeWorkerEnvRunner extends BaseEnvRunner {
|
|
|
13
13
|
protected _runtimeType(): string;
|
|
14
14
|
protected _closeRuntime(): Promise<void>;
|
|
15
15
|
}
|
|
16
|
-
export { NodeWorkerEnvRunner
|
|
16
|
+
export { NodeWorkerEnvRunner };
|
package/dist/_chunks/runner3.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseEnvRunner } from "./base-runner.mjs";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
@@ -11,7 +11,7 @@ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
|
|
|
11
11
|
...opts,
|
|
12
12
|
workerEntry: opts.workerEntry || _defaultEntry
|
|
13
13
|
});
|
|
14
|
-
this.#initWorker();
|
|
14
|
+
this._initWithVirtualData(() => this.#initWorker());
|
|
15
15
|
}
|
|
16
16
|
sendMessage(message) {
|
|
17
17
|
if (!this.#worker) throw new Error("Worker thread should be initialized before sending messages.");
|
|
@@ -57,4 +57,4 @@ var NodeWorkerEnvRunner = class extends BaseEnvRunner {
|
|
|
57
57
|
this.#worker = worker;
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
export { NodeWorkerEnvRunner
|
|
60
|
+
export { NodeWorkerEnvRunner };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { WorkerHooks } from "./types.mjs";
|
|
2
|
+
import { EnvRunnerData } from "./base-runner.mjs";
|
|
3
|
+
import { NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
4
4
|
declare class VercelEnvRunner extends NodeWorkerEnvRunner {
|
|
5
5
|
constructor(opts: {
|
|
6
6
|
name: string;
|
|
@@ -11,4 +11,4 @@ declare class VercelEnvRunner extends NodeWorkerEnvRunner {
|
|
|
11
11
|
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
12
12
|
protected _runtimeType(): string;
|
|
13
13
|
}
|
|
14
|
-
export { VercelEnvRunner
|
|
14
|
+
export { VercelEnvRunner };
|
package/dist/_chunks/runner4.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodeWorkerEnvRunner } from "./runner3.mjs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
let _warned = false;
|
|
@@ -90,4 +90,4 @@ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
|
|
|
90
90
|
return "vercel";
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
|
-
export { VercelEnvRunner
|
|
93
|
+
export { VercelEnvRunner };
|