You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit dbc9cbb
Browse filesBrowse the repository at this point in the historyBrowse files
- **`src/common/base-runner.ts`** — `BaseEnvRunner` abstract class + `EnvRunnerData`: shared logic for all runners (fetch proxy with exponential backoff, upgrade, message dispatch, socket cleanup). Exposes a read-only `address` getter (the live `WorkerAddress`, used by the WebSocket bridge to target the worker). `upgrade()` is the Node raw-socket passthrough (httpxy `proxyUpgrade`); it now awaits `waitForReady()` (bounded) before proxying instead of silently dropping an upgrade that arrives while the worker is still (re)starting, and destroys the raw client socket on the give-up path (worker never became ready) so it isn't leaked. `_resolveVirtualData()` resolves factory-valued `data.virtual` sources to strings on the host before spawn (returns a promise only when a factory is present, else `undefined` for the synchronous spawn path); `_initWithVirtualData(init)` wraps it for subclass constructors — runs `init` synchronously when no factory is present, defers it otherwise, and routes a throwing/rejecting factory to `close(error)` (no unhandled rejection). `_handleMessage()` treats a worker `{ event: "init-error", error }` message (sent by built-in workers when virtual-module registration or the entry import throws) as fatal before ready: it closes the runner with the error as cause, so callers see the actionable message instead of a bare "process exited with code 1". `waitForReady()` also rejects promptly ("Runner closed before becoming ready") when the runner is/becomes closed before the address arrives. `rpc()`, `reloadModule()`, and `invalidateModule()` all go through the protected `_request(message, { match, timeout, timeoutError, send? })` helper (one send-and-await-ack implementation; `send` overrides the transport, used by the miniflare runner's WebSocket reload): it rejects on timeout, on an `error`-carrying response, and **promptly when the runner closes mid-wait** (pending rejectors in `_pendingRequests`, fired by `close()` with the close cause) instead of letting callers wait out the timeout on a dead worker. `invalidateModule(specifier, timeout?)` re-runs a factory-valued virtual source on the host (`_refreshVirtualSource()`, using the original sources kept in `_virtualSources` by `_resolveVirtualData()`), sends `{ event: "invalidate-module", specifier, source? }` to the worker, and awaits the `{ event: "module-invalidated", specifier, error? }` ack — rejects for non-virtual specifiers. `_refreshVirtualSource()` first awaits `_virtualResolved` (the pending factory resolution): before it settles, `_data.virtual` still aliases the original factory-valued map, and writing a resolved string into it would permanently replace the factory (and mutate the caller's options). Re-exports `VirtualModules`/`VirtualModuleSource`
8
8
- **`src/common/worker-utils.ts`** — Shared utilities for built-in workers: `AppEntry` interface (with optional `websocket`, `upgrade`, and `ipc` hooks), `AppEntryIPC`/`AppEntryIPCContext` types, `resolveEntry()` to dynamically import user entry (a real path **or** a virtual/bare specifier resolved by registered ESM hooks), `isVirtualSpecifier()` to check whether a specifier is a key of the `data.virtual` map, `parseServerAddress()` to extract host/port from srvx server, `reloadEntryModule()` for cache-busted re-import with IPC teardown/re-init. Workers pass `isVirtualSpecifier(data.entry, data.virtual)` as the `virtual` flag through `resolveEntry()`/`reloadEntryModule()`: a virtual entry skips all filesystem path handling (no `file://` conversion, no `existsSync`/`data:`-URL re-read), so a virtual key **overrides** a real file with the same path. Without the flag, `_importFresh()` re-reads real files via a `data:` URL and cache-busts bare specifiers with a `?__envRunnerReload=<n>` query (detected by `existsSync` on the path part). For virtual specifiers, `_importFresh()` first tries `refreshVirtualModule()` (Bun backend: re-register to bust the cache, then plain re-import) and falls back to the `?__envRunnerReload` query (registerHooks backend)
9
+
- **`src/common/runtime-deps.ts`** — Shared resolver for the runtime dependencies `env-runner` deliberately does not depend on. `RuntimeDep<T> = T | string | URL | false` is the single shape every such option accepts: the imported module, a specifier, or `false` to opt out. `resolveRuntimeDep({ name, option, value, expect?, from?, required?, hint? })` resolves it — `false` → `undefined`; a module → validated against `expect` (a named export that must be a function) and returned as-is; a specifier → `resolveSpecifier()` (URLs and URL-like strings pass through, bare names go through exsolve's `resolveModulePath(..., { from: cwd, try: true })` → `file://` href, unresolvable ones pass through verbatim) then `import()`, wrapping a failure in an actionable `TypeError` with the original as `cause`; omitted → the same optional import of `name`, yielding `undefined` when the package isn't installed unless `required` is set. `resolveRuntimeDepSpecifier(value, option, from?)` is the boundary-crossing variant for options whose package must be imported _inside_ a worker: it normalizes a specifier the same way but throws for a module instance. Used by `MiniflareEnvRunner`'s `miniflare` (`required`), `loadWranglerConfig()`'s `wranglerModule`, `registerVercelQueueConsumer()`'s `sdk`, and `NetlifyEnvRunner`'s `netlifyRuntime` (host: `resolveRuntimeDepSpecifier()`; worker: `resolveRuntimeDep()`). Exported publicly from `src/index.ts` as `resolveRuntimeDep`/`RuntimeDep`
9
10
- **`src/common/ws-proxy.ts`** — `createRunnerWSProxyPlugin(getRunner)`: builds a runtime-native WebSocket reverse-proxy srvx plugin for the public-facing server, picking the strategy by **host** runtime. On Node it attaches the `http.Server` `"upgrade"` event (after `server.ready()`) and forwards the raw socket through `runner.upgrade()` — transparent, single-framed, end-to-end subprotocol/extension negotiation. On Bun/Deno (which serve natively and expose no Node upgrade socket) it terminates the client with crossws (`crossws/server/{bun,deno}` + `createWebSocketProxy`) and bridges to the worker over crossws's own `WebSocket` client (`resolveWSProxyTarget()` builds a `ws://host:port` or `ws+unix://<socket>:<path>` target; crossws dials both uniformly on Node/Bun/Deno, Deno needing `--unstable-net` for Unix sockets), forwarding the subprotocol and awaiting runner readiness before resolving the target. `getRunner` is read lazily so the plugin survives hot-reloads. Internal — surfaced publicly only as `RunnerManager.wsSrvxPlugin()`
10
11
-**`src/virtual-loader.ts`** + **`src/common/virtual-modules.ts`** — Virtual-module ESM hooks/registration. See [`VIRTUAL-MODULES.md`](VIRTUAL-MODULES.md)
-**`src/loader.ts`** — `loadRunner(name, opts)`: dynamic loader that imports a runner by name (`node-worker` | `node-process` | `bun-process` | `deno-process` | `self` | `miniflare` | `vercel` | `netlify`) and returns an `EnvRunner` instance
13
+
-**`src/loader.ts`** — `loadRunner(name, opts)`: dynamic loader that imports a runner by name (`node-worker` | `node-process` | `bun-process` | `deno-process` | `self` | `miniflare` | `vercel` | `netlify`) and returns an `EnvRunner` instance. `LoadRunnerOptions`' index signature carries runner-specific options (e.g. the `miniflare` module) through untyped, which is why the `miniflare` entry casts to `RunnerConstructor`
13
14
-**`src/manager.ts`** — `RunnerManager`: proxy manager for hot-reload, message queueing, and listener forwarding across runner swaps. The `fetch` field delegates to a protected `_fetch()` method so subclasses can hook request handling (used by `EnvServer` for auto-start). `reload(runner?)`'s argument is optional: with no runner it calls the protected `_createRunner()` factory, which throws on the base class and is overridden by `EnvServer`. `invalidateModule()` sets a dirty flag (`_moduleInvalidated`) and the next `fetch()` lazily flushes it with one `reloadModule()` before serving (`_flushInvalidation()` — concurrent fetches share the reload promise; a failed reload keeps the flag set so the next fetch retries); an explicit `reloadModule()` or a runner swap via `reload()` clears the flag
14
-
-**`src/server.ts`** — `EnvServer` extends `RunnerManager`: high-level API combining runner loading, watch mode (`fs.watch` with 100ms debounce), and auto-reload on file changes. Supports `watch` and `watchPaths` options. The `runner` option is optional and defaults to `"node-worker"`. `start()` is idempotent (a shared `_startPromise`; a failed start resets it so a later call retries) and optional: the first `fetch()` auto-starts via the `_fetch()` override (skipped once closed — fetch after `close()` stays a 503, never respawns)
15
+
-**`src/server.ts`** — `EnvServer` extends `RunnerManager`: high-level API combining runner loading, watch mode (`fs.watch` with 100ms debounce), and auto-reload on file changes. Supports `watch` and `watchPaths` options. The `runner` option is optional and defaults to `"node-worker"`. `runnerOptions` (a plain record) is spread into the runner constructor ahead of the derived `name`/`hooks`/`data`/`execArgv`, carrying runner-specific dependencies such as `{ miniflare }` (the CLI passes none — the miniflare runner's own optional import covers `--runner miniflare`). `start()` is idempotent (a shared `_startPromise`; a failed start resets it so a later call retries) and optional: the first `fetch()` auto-starts via the `_fetch()` override (skipped once closed — fetch after `close()` stays a 503, never respawns)
0 commit comments