env-runner 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -365,11 +365,17 @@ await using runner = new MiniflareEnvRunner({
365
365
  });
366
366
 
367
367
  const response = await runner.fetch("http://localhost/api");
368
+ // Request inputs also preserve methods, headers, streaming bodies and cancellation.
369
+ // An optional RequestInit overrides the corresponding Request properties.
368
370
  ```
369
371
 
370
372
  Passing `miniflare` explicitly is preferred — the version you install is then the version that runs. A specifier works too (`miniflare: "miniflare"`). If you omit it, the runner imports `miniflare` itself and only fails (with an actionable error) when the package isn't installed either. 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.
371
373
 
372
- When you don't set a `compatibilityDate` (via `miniflareOptions` or a wrangler config), it defaults to the date supported by the installed `workerd` binary rather than today's date — the binary always lags the calendar slightly, and pinning a future date makes `workerd` refuse to start.
374
+ The entry uses the same `AppEntry` format as the other runners. Requests are handled like srvx's Cloudflare adapter (`srvx/cloudflare`): the entry's `plugins`, `middleware` and `error` handler are applied, and the request carries `request.runtime` (`{ name: "cloudflare", cloudflare: { env, context } }`), `request.ip` (from `cf-connecting-ip`) and `request.waitUntil()`. For Workers-style entries, `fetch` still receives `(request, env, ctx)`. env-runner's internal bindings are never exposed on `env`. Listener-level srvx options (`maxRequestBodySize`, `trustProxy`, `node`/`bun`/`deno`, ...) do not apply to miniflare.
375
+
376
+ When you don't set a compatibility date, it defaults to the date supported by the installed `workerd` binary rather than today's date — the binary always lags the calendar slightly, and pinning a future date makes `workerd` refuse to start. Set the runner's `compatibilityDate` option to pin one, or to `"latest"` to use the installed `workerd`'s supported date explicitly (no need to import `miniflare` for `supportedCompatibilityDate`). Precedence: `miniflareOptions.compatibilityDate` > `compatibilityDate` > the wrangler config's `compatibility_date` > the supported date. Whatever the source, a date newer than the installed `workerd` supports falls back to the supported date with a warning (like `wrangler dev`).
377
+
378
+ The runner enables the `nodejs_compat` compatibility flag by default. Set `no_nodejs_compat` (in the wrangler config's `compatibility_flags` or in `miniflareOptions.compatibilityFlags`) to opt out; the generated wrapper then avoids Node.js built-ins. If the two sources disagree, `miniflareOptions` wins.
373
379
 
374
380
  #### Wrangler Config
375
381
 
@@ -382,12 +388,15 @@ await using runner = new MiniflareEnvRunner({
382
388
  miniflare,
383
389
  name: "my-worker",
384
390
  data: { entry: "./worker.ts" },
385
- wrangler: true, // auto-discover wrangler.{json,jsonc,toml} next to the entry, then cwd
391
+ wrangler: true, // auto-discover wrangler.{json,jsonc,toml} (see below)
386
392
  // wrangler: "./config/wrangler.toml", // or an explicit path
387
393
  // wranglerEnv: "production", // select a `[env.production]` block
394
+ // compatibilityDate: "latest", // override the config's compatibility_date
388
395
  });
389
396
  ```
390
397
 
398
+ Auto-discovery searches parent directories: when the entry file is inside the current working directory, it walks up from the entry's directory to the filesystem root (so a config at a monorepo root is found for an entry in `apps/web/src/`); when the entry lives elsewhere (e.g. a framework entry hoisted under `node_modules/.pnpm`), only the entry's own directory is checked before walking up from the cwd, so the cwd's config is never shadowed by one above the entry. The nearest directory wins; within one directory `wrangler.json` is preferred over `wrangler.jsonc`, then `wrangler.toml` (unlike `wrangler`, which looks for each filename all the way up before trying the next). A config found in a parent directory is logged once.
399
+
391
400
  `wranglerEnv` selects a named Wrangler environment (`--env`). When omitted, it defaults to the `CLOUDFLARE_ENV` environment variable, so `CLOUDFLARE_ENV=production` selects the `production` env without passing the option.
392
401
 
393
402
  You can also pass an **inline** config object (raw `wrangler.json` shape) instead of (or in addition to) a file — handy for programmatic setups:
@@ -406,9 +415,27 @@ await using runner = new MiniflareEnvRunner({
406
415
  });
407
416
  ```
408
417
 
409
- When an inline config is passed, a `wrangler.{json,jsonc,toml}` file is still auto-discovered (next to the entry, then cwd) and loaded, and the inline config is **merged on top of it** — inline values win per key, binding records (e.g. `vars`) merge, and `compatibilityFlags` are unioned. This lets you keep a committed `wrangler` file and override a few fields programmatically.
418
+ When an inline config is passed, a `wrangler.{json,jsonc,toml}` file is still auto-discovered (as above) and loaded, and the inline config is **merged on top of it** — inline values win per key, binding records (e.g. `vars`) merge, and `compatibilityFlags` are unioned. This lets you keep a committed `wrangler` file and override a few fields programmatically. If the inline config doesn't define the selected `wranglerEnv`, its top level is used as-is (the file's env still applies), and a config that fails to load only warns without discarding the other one.
410
419
 
411
- Pass the [`wrangler`](https://www.npmjs.com/package/wrangler) package as `wranglerModule` — the imported module or a specifier — for full fidelity: TOML, `env` inheritance, `.dev.vars`, and every binding type.
420
+ Set `wranglerConfigPath` to load a specific config file instead of auto-discovering one — with `wrangler: true` or an inline config (which still merges on top), and without changing the working directory:
421
+
422
+ ```ts
423
+ await using runner = new MiniflareEnvRunner({
424
+ miniflare,
425
+ name: "my-worker",
426
+ data: { entry: "./.nitro/dev/index.mjs" },
427
+ wrangler: { vars: { GREETING: "hello" } },
428
+ wranglerConfigPath: "./wrangler.jsonc", // relative to cwd
429
+ });
430
+ ```
431
+
432
+ A missing `wranglerConfigPath` file warns (an inline config is still applied). When `wrangler` is itself a string path, that path wins and `wranglerConfigPath` is ignored.
433
+
434
+ The runner hosts a single fetch-only worker, so config entries it can't run are **dropped**: `assets`, `services`, `queues.consumers`, `workflows`, `tail_consumers`/`streaming_tail_consumers`, and Durable Object bindings to another script (`script_name`). Durable Object bindings to classes exported by your entry are kept — including bindings whose `script_name` is the worker's own `name` (the inline config's `name` when set, else the file's; with `wranglerEnv` suffixed `-<env>` unless the env section sets a `name`, e.g. `my-worker-staging`), which are local in `wrangler dev` too — and merged with [auto-detected exports](#auto-detected-exports). Pass any of the dropped options via `miniflareOptions` to opt back in.
435
+
436
+ Whenever `wrangler` is enabled (`true`, a path, or an inline config), local state (KV, D1, R2, Durable Objects, ...) persists under `<dir>/.wrangler/state/v3` — the same place `wrangler dev` uses, so both share data. `<dir>` is the directory of the loaded config file, else of the requested config path (`wrangler` string or `wranglerConfigPath`, even if the file is missing), else the current working directory (e.g. inline-only configs, or `wrangler: true` with no file found). Set `miniflareOptions.defaultPersistRoot` (or any `*Persist` option, e.g. `kvPersist: false`) to opt out.
437
+
438
+ Pass the [`wrangler`](https://www.npmjs.com/package/wrangler) package as `wranglerModule` — the imported module or a specifier — for full fidelity: TOML, config validation, and every binding type.
412
439
 
413
440
  ```ts
414
441
  import * as miniflare from "miniflare";
@@ -423,7 +450,26 @@ await using runner = new MiniflareEnvRunner({
423
450
  });
424
451
  ```
425
452
 
426
- Without `wranglerModule`, `wrangler` is imported optionally; if that fails too, a built-in minimal reader handles plain JSON files and inline objects (common fields only) and JSONC/TOML files are skipped with a warning (they need `wrangler` to parse). Pass `wranglerModule: false` to always use the minimal reader. Values you pass in `miniflareOptions` always take precedence over config-derived ones — binding records (e.g. `bindings`) merge per key, and `compatibilityFlags` are merged.
453
+ With the `wrangler` package, wrangler's own config warnings (e.g. unexpected/misspelled keys, or a `wranglerEnv` the config doesn't define) are printed for a config file — once per file version and env, so hot reloads don't repeat them. Inline configs are validated without printing wrangler's warnings (load errors still warn). As in `wrangler dev`, unexpected keys also trigger wrangler's npm update check (cached for a day), which may print a "newer version of Wrangler available" hint.
454
+
455
+ Set `wranglerEnvFiles` to load local dev vars/secrets from custom `.env` files, like `getPlatformProxy({ envFiles })`:
456
+
457
+ ```ts
458
+ await using runner = new MiniflareEnvRunner({
459
+ miniflare,
460
+ wranglerModule: wrangler,
461
+ name: "my-worker",
462
+ data: { entry: "./worker.ts" },
463
+ wrangler: true,
464
+ wranglerEnvFiles: [".env", ".env.development"], // relative to the config file's dir
465
+ });
466
+ ```
467
+
468
+ Paths resolve against the loaded config file's directory (else the current working directory) and later files override earlier ones. When set (non-empty), `.dev.vars` is not read; when unset, wrangler's defaults apply (`.dev.vars[.<env>]`, else `.env*`); an empty array reads `.dev.vars` but no `.env*` files. Both the `wrangler` package and the built-in minimal reader honor it.
469
+
470
+ Without `wranglerModule`, `wrangler` is imported optionally; if that fails too, a built-in minimal reader handles JSON/JSONC files and inline objects (TOML files are skipped with a warning). It follows wrangler's semantics for `env` selection (bindings and `vars` are not inherited into a named env), local ids (`preview_id` / `preview_bucket_name` / `preview_database_id` first, so state is shared with `wrangler dev`), SQLite-backed Durable Objects (`migrations[].new_sqlite_classes`) and dev vars (`.dev.vars[.<env>]`, `.env*`, `secrets.required`), but only maps common bindings (`vars`, KV, R2, D1, Durable Objects, queue producers); other bindings (e.g. `hyperdrive`, `ai`, `ratelimits`) are ignored with a warning. Pass `wranglerModule: false` to always use the minimal reader. Values you pass in `miniflareOptions` always take precedence over config-derived ones — binding records (e.g. `bindings`) merge per key, and `compatibilityFlags` are merged.
471
+
472
+ Config options a single dev worker can't run — `services`, `assets`, `queues.consumers`, `workflows`, `tail_consumers`/`streaming_tail_consumers`, and `durable_objects` bindings with a `script_name` naming another worker — are ignored with one warning listing them (e.g. `services (MY_SERVICE)`); pass the equivalent Miniflare options via `miniflareOptions` to opt in.
427
473
 
428
474
  #### Module Transform Pipeline
429
475
 
@@ -461,9 +507,9 @@ export class Counter {
461
507
 
462
508
  export default {
463
509
  async fetch(request, env) {
464
- // env.Counter is auto-wired — no manual config needed
465
- const id = env.Counter.idFromName("test");
466
- const stub = env.Counter.get(id);
510
+ // env.COUNTER is auto-wired — no manual config needed
511
+ const id = env.COUNTER.idFromName("test");
512
+ const stub = env.COUNTER.get(id);
467
513
  return stub.fetch(request);
468
514
  },
469
515
  };
@@ -481,7 +527,7 @@ await using runner = new MiniflareEnvRunner({
481
527
  });
482
528
  ```
483
529
 
484
- Set `exports: false` to disable auto-detection entirely.
530
+ Auto-wired bindings are merged with Durable Object bindings from `miniflareOptions` and a wrangler config: exports whose class is already bound (or whose binding name is taken) are skipped. Set `exports: false` to disable auto-detection entirely.
485
531
 
486
532
  #### Error Capture
487
533
 
@@ -687,6 +733,14 @@ export default {
687
733
  },
688
734
  middleware: [], // Optional srvx middleware
689
735
  plugins: [], // Optional srvx plugins
736
+ // Any other srvx ServerOptions are forwarded to serve() as-is, e.g.:
737
+ error(error) {
738
+ return new Response(error.message, { status: 500 });
739
+ },
740
+ maxRequestBodySize: 1024 * 1024,
741
+ trustProxy: true,
742
+ node: { keepAliveTimeout: 5000 },
743
+ bun: { idleTimeout: 30 },
690
744
  ipc: {
691
745
  onOpen({ sendMessage }) {
692
746
  // IPC channel is ready — send messages back to the runner
@@ -710,6 +764,8 @@ The built-in worker automatically:
710
764
  3. Reports the address back to the runner via IPC
711
765
  4. Handles graceful shutdown
712
766
 
767
+ Every other [srvx `ServerOptions`](https://srvx.h3.dev/guide/options) key exported by the entry (`error`, `maxRequestBodySize`, `trustProxy`, `reusePort`, the runtime-specific `node` / `bun` / `deno` objects, ...) is forwarded to `serve()` unchanged, so one `server.ts` can carry the same options in dev and production. The listener options are owned by the worker, which sits behind the runner's proxy, and are ignored if set: `port`, `hostname`, `protocol`, `tls`, `silent`, `manual` and `gracefulShutdown` — as well as their equivalents nested in `node` (`port`, `host`, `path`, `cert`, `key`, `passphrase`, and `http2`, which srvx only supports with TLS), `bun` (`port`, `hostname`, `unix`, `tls`) and `deno` (`port`, `hostname`, `path`, `cert`, `key`). Custom workers can reuse the same logic via the exported `toServerOptions(entry)` helper.
768
+
713
769
  For advanced use cases, you can provide a custom worker entry:
714
770
 
715
771
  ```ts
@@ -1,38 +1,21 @@
1
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>);
4
+ /** Factories run once on the host, before the worker spawns. */
5
+ export type VirtualModuleSource = string | (() => string | Promise<string>);
14
6
  /** Virtual modules as a `specifier => source` map. */
15
- type VirtualModules = Record<string, VirtualModuleSource>;
16
- interface EnvRunnerData {
7
+ export type VirtualModules = Record<string, VirtualModuleSource>;
8
+ export interface EnvRunnerData {
17
9
  name?: string;
18
10
  /**
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.
11
+ * Virtual modules importable from the entry, e.g.
12
+ * `{ "#virtual-import": "export const foo = 1" }`. Factory sources run once
13
+ * on the host before spawn. Not supported by the `self` runner.
31
14
  */
32
15
  virtual?: VirtualModules;
33
16
  [key: string]: unknown;
34
17
  }
35
- declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
18
+ export declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
36
19
  closed: boolean;
37
20
  protected _name: string;
38
21
  protected _workerEntry: string;
@@ -69,25 +52,19 @@ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
69
52
  reloadModule(timeout?: number): Promise<void>;
70
53
  /**
71
54
  * Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
72
- * A factory-valued `data.virtual` source is re-run on the host and the fresh
73
- * source is shipped to the worker along with the invalidation. Rejects when
74
- * the specifier is not a registered virtual module.
55
+ * Factory sources are re-run on the host. Rejects for unknown specifiers.
75
56
  */
76
57
  invalidateModule(specifier: string, timeout?: number): Promise<void>;
77
58
  close(cause?: unknown): Promise<void>;
78
59
  [Symbol.asyncDispose](): Promise<void>;
79
- /**
80
- * Resolve a relative fetch input (e.g. `"/path"`) against a placeholder
81
- * `http://localhost` origin so it parses as a full URL. The origin is a
82
- * placeholder — requests are dispatched to the worker address regardless.
83
- */
60
+ /** Briefly back off (~3s total) while the worker is still starting. */
61
+ protected _waitForAddress(): Promise<void>;
62
+ /** Placeholder origin for relative inputs; requests go to the worker address regardless. */
84
63
  protected _resolveFetchInput(input: string | URL | Request): string | URL | Request;
85
64
  protected _handleMessage(message: any): void;
86
65
  /**
87
- * Send a message and await a matching response message. Shared by `rpc()`,
88
- * `reloadModule()`, and `invalidateModule()`. Rejects on timeout, on a
89
- * response carrying an `error`, and promptly when the runner closes mid-wait
90
- * (instead of letting callers wait out the timeout on a dead worker).
66
+ * Send a message and await the matching response. Rejects on timeout, on an
67
+ * `error` response, and as soon as the runner closes.
91
68
  */
92
69
  protected _request<T = unknown>(message: unknown, opts: {
93
70
  match: (msg: any) => boolean;
@@ -96,29 +73,20 @@ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
96
73
  send?: (message: unknown) => void;
97
74
  }): Promise<T>;
98
75
  /**
99
- * Resolve any factory-valued `data.virtual` sources to strings before the
100
- * worker is spawned. Returns a pending promise only when there is async work
101
- * to do (a factory is present); otherwise returns `undefined` so subclasses can
102
- * keep their synchronous spawn path. Factories must be resolved here because
103
- * functions can't cross the worker boundary and the load hook can't await.
76
+ * Resolve factory `data.virtual` sources before spawn (functions can't cross
77
+ * the worker boundary; the load hook can't await). `undefined` when there is
78
+ * no factory, so subclasses can spawn synchronously.
104
79
  */
105
80
  protected _resolveVirtualData(): Promise<void> | undefined;
106
- /**
107
- * Re-run a factory-valued virtual source on the host and sync the resolved
108
- * `data.virtual` map. Returns the fresh source, or `undefined` when the
109
- * source is a plain string or unknown (nothing to re-evaluate).
110
- */
81
+ /** Re-run a factory virtual source and sync `data.virtual`; `undefined` if not a factory. */
111
82
  protected _refreshVirtualSource(specifier: string): Promise<string | undefined>;
112
83
  /**
113
- * Run a subclass spawn callback after `data.virtual` is resolved.
114
- * Synchronous when no factory-valued source is present; otherwise defers
115
- * `init` until factories resolve. A throwing/rejecting factory closes the
116
- * runner with the error as cause instead of leaving an unhandled rejection.
84
+ * Run `init` once `data.virtual` is resolved (synchronously without factories).
85
+ * A failing factory closes the runner with the error as cause.
117
86
  */
118
87
  protected _initWithVirtualData(init: () => void): void;
119
88
  protected _closeSocket(): Promise<void>;
120
89
  protected abstract _hasRuntime(): boolean;
121
90
  protected abstract _closeRuntime(): Promise<void>;
122
91
  protected abstract _runtimeType(): string;
123
- }
124
- export { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules };
92
+ }
@@ -27,7 +27,7 @@ var BaseEnvRunner = class {
27
27
  return this._address;
28
28
  }
29
29
  async fetch(input, init) {
30
- for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
30
+ await this._waitForAddress();
31
31
  if (!this._address) return new Response(`${this._runtimeType()} env runner is unavailable`, { status: 503 });
32
32
  return proxyFetch(this._address, this._resolveFetchInput(input), init);
33
33
  }
@@ -118,6 +118,9 @@ var BaseEnvRunner = class {
118
118
  const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
119
119
  return `${this.constructor.name}#${this._name}(${status})`;
120
120
  }
121
+ async _waitForAddress() {
122
+ for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
123
+ }
121
124
  _resolveFetchInput(input) {
122
125
  if (typeof input === "string" && !URL.canParse(input)) return new URL(input, "http://localhost");
123
126
  return input;
@@ -1,20 +1,9 @@
1
1
  /**
2
- * A runtime dependency the application owns, not `env-runner`.
3
- *
4
- * Every runner option that names an external package accepts the same three
5
- * shapes, so the choice is about ergonomics rather than per-runner API:
6
- *
7
- * - the **imported module** (`import * as miniflare from "miniflare"`) — the
8
- * version installed by the app is the version that runs
9
- * - a **module specifier** (`"miniflare"`, `import.meta.resolve("miniflare")`,
10
- * or a `URL`) — resolved from {@link ResolveRuntimeDepOptions.from} (cwd by
11
- * default), so a bare specifier resolves against the app rather than against
12
- * `env-runner`'s own `node_modules`
13
- * - `false` — opt out of the package entirely
14
- *
15
- * Omitting the option falls back to an optional `import()` of the package.
2
+ * An app-owned package: the imported module, a specifier (resolved from the
3
+ * app, not `env-runner`), or `false` to opt out. Omitted falls back to an
4
+ * optional `import()`.
16
5
  */
17
- type RuntimeDep<T> = T | string | URL | false;
6
+ export type RuntimeDep<T> = T | string | URL | false;
18
7
  interface ResolveRuntimeDepOptions<T> {
19
8
  /** Package name, used for the fallback import and in messages. */
20
9
  name: string;
@@ -22,30 +11,17 @@ interface ResolveRuntimeDepOptions<T> {
22
11
  option: string;
23
12
  /** Value as passed by the caller. */
24
13
  value?: RuntimeDep<T>;
25
- /**
26
- * Named export the resolved module must expose. A module that lacks it is
27
- * treated as the wrong package (throws for an explicit value).
28
- */
14
+ /** Named export identifying the package (a mismatch throws for an explicit value). */
29
15
  expect?: string;
30
16
  /** Directory bare specifiers resolve from. @default process.cwd() */
31
17
  from?: string;
32
- /**
33
- * Throw when the package cannot be resolved at all. Otherwise an
34
- * unavailable optional package resolves to `undefined` and the caller
35
- * degrades (minimal reader, shim, no-op).
36
- */
18
+ /** Throw when unresolvable instead of resolving `undefined` (callers degrade). */
37
19
  required?: boolean;
38
20
  /** Extra sentence appended to the "not installed" error when `required`. */
39
21
  hint?: string;
40
22
  }
41
23
  /**
42
- * Resolve a {@link RuntimeDep} to an imported module.
43
- *
44
- * Resolution order: `false` → `undefined`; an imported module → validated and
45
- * returned as-is; a specifier → imported (errors propagate, since an explicit
46
- * specifier that cannot load is a mistake worth surfacing); omitted → optional
47
- * `import(name)`, which yields `undefined` when the package isn't installed
48
- * unless {@link ResolveRuntimeDepOptions.required} is set.
24
+ * Resolve a {@link RuntimeDep} to a module. An explicit specifier that fails to
25
+ * import throws; an omitted one resolves `undefined` unless `required`.
49
26
  */
50
- declare function resolveRuntimeDep<T>(opts: ResolveRuntimeDepOptions<T>): Promise<T | undefined>;
51
- export { RuntimeDep, resolveRuntimeDep };
27
+ export declare function resolveRuntimeDep<T>(opts: ResolveRuntimeDepOptions<T>): Promise<T | undefined>;
@@ -106,6 +106,55 @@ function _once(fn) {
106
106
  }
107
107
  };
108
108
  }
109
+ const RESERVED_SERVER_OPTIONS = [
110
+ "port",
111
+ "hostname",
112
+ "protocol",
113
+ "tls",
114
+ "silent",
115
+ "manual",
116
+ "gracefulShutdown"
117
+ ];
118
+ const RESERVED_RUNTIME_OPTIONS = {
119
+ node: [
120
+ "port",
121
+ "host",
122
+ "path",
123
+ "http2",
124
+ "cert",
125
+ "key",
126
+ "passphrase"
127
+ ],
128
+ bun: [
129
+ "port",
130
+ "hostname",
131
+ "unix",
132
+ "tls"
133
+ ],
134
+ deno: [
135
+ "port",
136
+ "hostname",
137
+ "path",
138
+ "cert",
139
+ "key"
140
+ ]
141
+ };
142
+ function toServerOptions(entry) {
143
+ const { fetch: _fetch, upgrade: _upgrade, websocket: _websocket, ipc: _ipc, ...options } = entry;
144
+ for (const key of RESERVED_SERVER_OPTIONS) delete options[key];
145
+ for (const runtime of Object.keys(RESERVED_RUNTIME_OPTIONS)) if (options[runtime]) {
146
+ const runtimeOptions = { ...options[runtime] };
147
+ for (const key of RESERVED_RUNTIME_OPTIONS[runtime]) delete runtimeOptions[key];
148
+ options[runtime] = runtimeOptions;
149
+ }
150
+ return {
151
+ ...options,
152
+ port: 0,
153
+ hostname: "127.0.0.1",
154
+ silent: true,
155
+ gracefulShutdown: false
156
+ };
157
+ }
109
158
  function isVirtualSpecifier(specifier, virtual) {
110
159
  return Boolean(specifier && virtual && Object.hasOwn(virtual, specifier));
111
160
  }
@@ -149,4 +198,4 @@ async function _importFresh(entryPath, virtual) {
149
198
  if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
150
199
  return entry;
151
200
  }
152
- export { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry };
201
+ export { RESERVED_RUNTIME_OPTIONS, RESERVED_SERVER_OPTIONS, handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry, toServerOptions };
@@ -1,6 +1,6 @@
1
1
  import { WorkerHooks } from "./types.mjs";
2
2
  import { BaseEnvRunner, EnvRunnerData } from "./common-base-runner.mjs";
3
- declare class DenoProcessEnvRunner extends BaseEnvRunner {
3
+ export declare class DenoProcessEnvRunner extends BaseEnvRunner {
4
4
  #private;
5
5
  constructor(opts: {
6
6
  name: string;
@@ -13,5 +13,4 @@ declare class DenoProcessEnvRunner extends BaseEnvRunner {
13
13
  protected _hasRuntime(): boolean;
14
14
  protected _runtimeType(): string;
15
15
  protected _closeRuntime(): Promise<void>;
16
- }
17
- export { DenoProcessEnvRunner };
16
+ }
@@ -26,7 +26,7 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
26
26
  }
27
27
  async _closeRuntime() {
28
28
  if (!this.#process) return;
29
- this.#process.removeAllListeners?.();
29
+ this.#process.removeAllListeners();
30
30
  try {
31
31
  this.#process.kill();
32
32
  } catch {}
@@ -37,10 +37,6 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
37
37
  this.close(`process entry not found in "${this._workerEntry}".`);
38
38
  return;
39
39
  }
40
- const env = hostEnv({
41
- ENV_RUNNER_NAME: this._name,
42
- ENV_RUNNER_DATA: JSON.stringify(this._data || {})
43
- });
44
40
  const child = spawn("deno", [
45
41
  "run",
46
42
  "-A",
@@ -49,28 +45,20 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
49
45
  ...execArgv || [],
50
46
  this._workerEntry
51
47
  ], {
52
- env,
48
+ env: hostEnv({
49
+ ENV_RUNNER_NAME: this._name,
50
+ ENV_RUNNER_DATA: JSON.stringify(this._data || {})
51
+ }),
53
52
  stdio: [
54
53
  "pipe",
55
54
  "pipe",
56
- "pipe"
57
- ]
58
- });
59
- const exited = new Promise((resolve) => {
60
- child.once("exit", (code) => resolve(code ?? 1));
55
+ "pipe",
56
+ "ipc"
57
+ ],
58
+ serialization: "json"
61
59
  });
62
- const handle = {
63
- pid: child.pid,
64
- kill: () => child.kill(),
65
- send: (message) => {
66
- child.stdin.write(JSON.stringify(message) + "\n");
67
- },
68
- exited,
69
- _exitCode: void 0,
70
- removeAllListeners: () => child.removeAllListeners()
71
- };
72
60
  child.once("exit", (code) => {
73
- handle._exitCode = code;
61
+ child._exitCode = code;
74
62
  this.close(`process exited with code ${code}`);
75
63
  });
76
64
  child.on("error", (error) => {
@@ -79,22 +67,12 @@ var DenoProcessEnvRunner = class extends BaseEnvRunner {
79
67
  this.close(error);
80
68
  }
81
69
  });
82
- let buffer = "";
83
- child.stdout.on("data", (chunk) => {
84
- buffer += chunk.toString();
85
- let newlineIdx;
86
- while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
87
- const line = buffer.slice(0, newlineIdx);
88
- buffer = buffer.slice(newlineIdx + 1);
89
- if (line.startsWith("{")) try {
90
- this._handleMessage(JSON.parse(line));
91
- continue;
92
- } catch {}
93
- process.stdout.write(line + "\n");
94
- }
70
+ child.on("message", (message) => {
71
+ this._handleMessage(message);
95
72
  });
73
+ child.stdout?.pipe(process.stdout);
96
74
  child.stderr?.pipe(process.stderr);
97
- this.#process = handle;
75
+ this.#process = child;
98
76
  }
99
77
  };
100
78
  export { DenoProcessEnvRunner };