env-runner 0.1.10 → 0.1.11

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
@@ -67,6 +67,9 @@ envServer.onReload(() => {
67
67
  // Optional — the server auto-starts on first fetch()
68
68
  await envServer.start();
69
69
 
70
+ // Restart with a fresh runner created from the server options
71
+ await envServer.reload();
72
+
70
73
  // Use with any HTTP server
71
74
  const server = serve({
72
75
  fetch: (request) => envServer.fetch(request),
@@ -141,7 +144,8 @@ await using runner = new NodeProcessEnvRunner({
141
144
  });
142
145
 
143
146
  // Proxy HTTP requests (retries with exponential backoff)
144
- const response = await runner.fetch("http://localhost/api");
147
+ // Relative URLs are resolved against a placeholder origin
148
+ const response = await runner.fetch("/api");
145
149
 
146
150
  // Proxy WebSocket upgrades
147
151
  runner.upgrade?.({ node: { req, socket, head } });
@@ -159,6 +163,10 @@ const result = await runner.rpc<string>("transformHTML", "<html>...</html>");
159
163
  // Hot-reload entry module without restarting the worker
160
164
  await runner.reloadModule();
161
165
 
166
+ // Invalidate a virtual module (re-runs a factory source), then reload
167
+ await runner.invalidateModule("#config.json");
168
+ await runner.reloadModule();
169
+
162
170
  // Graceful shutdown happens automatically at the end of the scope
163
171
  // (`await using`) — or call `await runner.close()` explicitly
164
172
  ```
@@ -238,6 +246,15 @@ await using runner = new NodeWorkerEnvRunner({
238
246
 
239
247
  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
248
 
249
+ To refresh a single virtual module without restarting the worker, call `invalidateModule(specifier)`: a factory-valued source is re-run on the host and the module is invalidated in the worker so its **next import evaluates fresh**. Virtual modules that import the invalidated one (directly or transitively) are invalidated along with it, so the fresh module is picked up even through intermediate virtual importers. Already-imported modules keep their instances, so pair it with `reloadModule()` to re-import the entry graph:
250
+
251
+ ```ts
252
+ await runner.invalidateModule("#config"); // re-runs the factory, busts the module
253
+ await runner.reloadModule(); // re-imports the entry, picking up the fresh module
254
+ ```
255
+
256
+ When fetching through `RunnerManager` or `EnvServer`, the reload is automatic: `invalidateModule()` marks the manager dirty and the next `fetch()` reloads the entry once before serving (concurrent fetches share the reload), so no explicit `reloadModule()` call is needed.
257
+
241
258
  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
259
 
243
260
  ```ts
@@ -21,3 +21,22 @@ Repository: https://github.com/nodejs/cjs-module-lexer
21
21
  > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
22
22
  >
23
23
  > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ ---------------------------------------
26
+
27
+ ## es-module-lexer
28
+
29
+ License: MIT
30
+ By: Guy Bedford
31
+ Repository: https://github.com/guybedford/es-module-lexer
32
+
33
+ > MIT License
34
+ > -----------
35
+ >
36
+ > Copyright (C) 2018-2022 Guy Bedford
37
+ >
38
+ > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
39
+ >
40
+ > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
41
+ >
42
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -37,9 +37,12 @@ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
37
37
  protected _name: string;
38
38
  protected _workerEntry: string;
39
39
  protected _data?: EnvRunnerData;
40
+ protected _virtualSources?: VirtualModules;
40
41
  protected _hooks: Partial<WorkerHooks>;
41
42
  protected _address?: WorkerAddress;
42
43
  protected _messageListeners: Set<(data: unknown) => void>;
44
+ protected _pendingRequests: Set<(cause?: unknown) => void>;
45
+ protected _virtualResolved?: Promise<void>;
43
46
  constructor(opts: {
44
47
  name: string;
45
48
  workerEntry: string;
@@ -63,9 +66,34 @@ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
63
66
  timeout?: number;
64
67
  }): Promise<T>;
65
68
  reloadModule(timeout?: number): Promise<void>;
69
+ /**
70
+ * Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
71
+ * A factory-valued `data.virtual` source is re-run on the host and the fresh
72
+ * source is shipped to the worker along with the invalidation. Rejects when
73
+ * the specifier is not a registered virtual module.
74
+ */
75
+ invalidateModule(specifier: string, timeout?: number): Promise<void>;
66
76
  close(cause?: unknown): Promise<void>;
67
77
  [Symbol.asyncDispose](): Promise<void>;
78
+ /**
79
+ * Resolve a relative fetch input (e.g. `"/path"`) against a placeholder
80
+ * `http://localhost` origin so it parses as a full URL. The origin is a
81
+ * placeholder — requests are dispatched to the worker address regardless.
82
+ */
83
+ protected _resolveFetchInput(input: string | URL | Request): string | URL | Request;
68
84
  protected _handleMessage(message: any): void;
85
+ /**
86
+ * Send a message and await a matching response message. Shared by `rpc()`,
87
+ * `reloadModule()`, and `invalidateModule()`. Rejects on timeout, on a
88
+ * response carrying an `error`, and promptly when the runner closes mid-wait
89
+ * (instead of letting callers wait out the timeout on a dead worker).
90
+ */
91
+ protected _request<T = unknown>(message: unknown, opts: {
92
+ match: (msg: any) => boolean;
93
+ timeout: number;
94
+ timeoutError: string;
95
+ send?: (message: unknown) => void;
96
+ }): Promise<T>;
69
97
  /**
70
98
  * Resolve any factory-valued `data.virtual` sources to strings before the
71
99
  * worker is spawned. Returns a pending promise only when there is async work
@@ -74,6 +102,12 @@ declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
74
102
  * functions can't cross the worker boundary and the load hook can't await.
75
103
  */
76
104
  protected _resolveVirtualData(): Promise<void> | undefined;
105
+ /**
106
+ * Re-run a factory-valued virtual source on the host and sync the resolved
107
+ * `data.virtual` map. Returns the fresh source, or `undefined` when the
108
+ * source is a plain string or unknown (nothing to re-evaluate).
109
+ */
110
+ protected _refreshVirtualSource(specifier: string): Promise<string | undefined>;
77
111
  /**
78
112
  * Run a subclass spawn callback after `data.virtual` is resolved.
79
113
  * Synchronous when no factory-valued source is present; otherwise defers
@@ -6,15 +6,19 @@ var BaseEnvRunner = class {
6
6
  _name;
7
7
  _workerEntry;
8
8
  _data;
9
+ _virtualSources;
9
10
  _hooks;
10
11
  _address;
11
12
  _messageListeners;
13
+ _pendingRequests;
14
+ _virtualResolved;
12
15
  constructor(opts) {
13
16
  this._name = opts.name;
14
17
  this._workerEntry = opts.workerEntry;
15
18
  this._data = opts.data;
16
19
  this._hooks = opts.hooks || {};
17
20
  this._messageListeners = /* @__PURE__ */ new Set();
21
+ this._pendingRequests = /* @__PURE__ */ new Set();
18
22
  }
19
23
  get ready() {
20
24
  return Boolean(!this.closed && this._address && this._hasRuntime());
@@ -22,7 +26,7 @@ var BaseEnvRunner = class {
22
26
  async fetch(input, init) {
23
27
  for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
24
28
  if (!this._address) return new Response(`${this._runtimeType()} env runner is unavailable`, { status: 503 });
25
- return proxyFetch(this._address, input, init);
29
+ return proxyFetch(this._address, this._resolveFetchInput(input), init);
26
30
  }
27
31
  async upgrade(context) {
28
32
  if (!this.ready || !this._address) return;
@@ -58,55 +62,40 @@ var BaseEnvRunner = class {
58
62
  }
59
63
  rpc(name, data, opts) {
60
64
  const id = Math.random().toString(36).slice(2);
61
- const timeout = opts?.timeout ?? 3e3;
62
- return new Promise((resolve, reject) => {
63
- const timer = setTimeout(() => {
64
- cleanup();
65
- reject(/* @__PURE__ */ new Error(`RPC "${name}" timed out`));
66
- }, timeout);
67
- const listener = (msg) => {
68
- if (msg?.__rpc_id === id) {
69
- cleanup();
70
- if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
71
- else resolve(msg.data);
72
- }
73
- };
74
- const cleanup = () => {
75
- clearTimeout(timer);
76
- this.offMessage(listener);
77
- };
78
- this.onMessage(listener);
79
- this.sendMessage({
80
- __rpc: name,
81
- __rpc_id: id,
82
- data
83
- });
84
- });
65
+ return this._request({
66
+ __rpc: name,
67
+ __rpc_id: id,
68
+ data
69
+ }, {
70
+ match: (msg) => msg?.__rpc_id === id,
71
+ timeout: opts?.timeout ?? 3e3,
72
+ timeoutError: `RPC "${name}" timed out`
73
+ }).then((msg) => msg.data);
85
74
  }
86
75
  async reloadModule(timeout = 5e3) {
87
- return new Promise((resolve, reject) => {
88
- const timer = setTimeout(() => {
89
- cleanup();
90
- reject(/* @__PURE__ */ new Error("Module reload timed out"));
91
- }, timeout);
92
- const listener = (msg) => {
93
- if (msg?.event === "module-reloaded") {
94
- cleanup();
95
- if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
96
- else resolve();
97
- }
98
- };
99
- const cleanup = () => {
100
- clearTimeout(timer);
101
- this.offMessage(listener);
102
- };
103
- this.onMessage(listener);
104
- this.sendMessage({ event: "reload-module" });
76
+ await this._request({ event: "reload-module" }, {
77
+ match: (msg) => msg?.event === "module-reloaded",
78
+ timeout,
79
+ timeoutError: "Module reload timed out"
80
+ });
81
+ }
82
+ async invalidateModule(specifier, timeout = 5e3) {
83
+ const source = await this._refreshVirtualSource(specifier);
84
+ await this._request({
85
+ event: "invalidate-module",
86
+ specifier,
87
+ source
88
+ }, {
89
+ match: (msg) => msg?.event === "module-invalidated" && msg.specifier === specifier,
90
+ timeout,
91
+ timeoutError: `Module invalidation timed out for "${specifier}"`
105
92
  });
106
93
  }
107
94
  async close(cause) {
108
95
  if (this.closed) return;
109
96
  this.closed = true;
97
+ for (const rejectPending of this._pendingRequests) rejectPending(cause);
98
+ this._pendingRequests.clear();
110
99
  this._hooks.onClose?.(this, cause);
111
100
  this._hooks = {};
112
101
  const onError = (error) => console.error(error);
@@ -120,6 +109,10 @@ var BaseEnvRunner = class {
120
109
  const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
121
110
  return `${this.constructor.name}#${this._name}(${status})`;
122
111
  }
112
+ _resolveFetchInput(input) {
113
+ if (typeof input === "string" && !URL.canParse(input)) return new URL(input, "http://localhost");
114
+ return input;
115
+ }
123
116
  _handleMessage(message) {
124
117
  if (message?.address) {
125
118
  this._address = message.address;
@@ -128,15 +121,59 @@ var BaseEnvRunner = class {
128
121
  if (message?.event === "init-error" && !this.ready && !this.closed) this.close(new Error(String(message.error || "Worker initialization failed")));
129
122
  for (const listener of this._messageListeners) listener(message);
130
123
  }
124
+ _request(message, opts) {
125
+ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner is closed"));
126
+ return new Promise((resolve, reject) => {
127
+ const timer = setTimeout(() => {
128
+ cleanup();
129
+ reject(new Error(opts.timeoutError));
130
+ }, opts.timeout);
131
+ const listener = (msg) => {
132
+ if (opts.match(msg)) {
133
+ cleanup();
134
+ if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
135
+ else resolve(msg);
136
+ }
137
+ };
138
+ const onClose = (cause) => {
139
+ cleanup();
140
+ reject(new Error("Runner closed before responding", cause ? { cause } : void 0));
141
+ };
142
+ const cleanup = () => {
143
+ clearTimeout(timer);
144
+ this.offMessage(listener);
145
+ this._pendingRequests.delete(onClose);
146
+ };
147
+ this.onMessage(listener);
148
+ this._pendingRequests.add(onClose);
149
+ try {
150
+ (opts.send ?? ((m) => this.sendMessage(m)))(message);
151
+ } catch (error) {
152
+ cleanup();
153
+ reject(error);
154
+ }
155
+ });
156
+ }
131
157
  _resolveVirtualData() {
132
158
  const virtual = this._data?.virtual;
159
+ this._virtualSources = virtual;
133
160
  if (!virtual || !Object.values(virtual).some((v) => typeof v === "function")) return;
134
- return resolveVirtualModules(virtual).then((resolved) => {
161
+ this._virtualResolved = resolveVirtualModules(virtual).then((resolved) => {
135
162
  this._data = {
136
163
  ...this._data,
137
164
  virtual: resolved
138
165
  };
139
166
  });
167
+ return this._virtualResolved;
168
+ }
169
+ async _refreshVirtualSource(specifier) {
170
+ await this._virtualResolved?.catch(() => {});
171
+ const original = this._virtualSources?.[specifier];
172
+ if (typeof original !== "function") return;
173
+ const source = await original();
174
+ const resolved = this._data?.virtual;
175
+ if (resolved) resolved[specifier] = source;
176
+ return source;
140
177
  }
141
178
  _initWithVirtualData(init) {
142
179
  const pending = this._resolveVirtualData();
@@ -0,0 +1,83 @@
1
+ var ImportType;
2
+ (function(A) {
3
+ A[A.Static = 1] = "Static", A[A.Dynamic = 2] = "Dynamic", A[A.ImportMeta = 3] = "ImportMeta", A[A.StaticSourcePhase = 4] = "StaticSourcePhase", A[A.DynamicSourcePhase = 5] = "DynamicSourcePhase", A[A.StaticDeferPhase = 6] = "StaticDeferPhase", A[A.DynamicDeferPhase = 7] = "DynamicDeferPhase";
4
+ })(ImportType || (ImportType = {}));
5
+ const A = 1 === new Uint8Array(new Uint16Array([1]).buffer)[0];
6
+ function parse(E, g = "@") {
7
+ if (!C) return init.then((() => parse(E)));
8
+ const I = E.length + 1, o = (C.__heap_base.value || C.__heap_base) + 4 * I - C.memory.buffer.byteLength;
9
+ o > 0 && C.memory.grow(Math.ceil(o / 65536));
10
+ const D = C.sa(I - 1);
11
+ if ((A ? B : Q)(E, new Uint16Array(C.memory.buffer, D, I)), !C.parse()) throw Object.assign(/* @__PURE__ */ new Error(`Parse error ${g}:${E.slice(0, C.e()).split("\n").length}:${C.e() - E.lastIndexOf("\n", C.e() - 1)}`), { idx: C.e() });
12
+ const K = [], k = [];
13
+ for (; C.ri();) {
14
+ const A = C.is(), Q = C.ie(), B = C.it(), g = C.ai(), I = C.id(), o = C.ss(), D = C.se();
15
+ let k;
16
+ C.ip() && (k = w(E.slice(-1 === I ? A - 1 : A, -1 === I ? Q + 1 : Q)));
17
+ const i = [];
18
+ for (C.rsa(); C.ra();) {
19
+ const A = C.aks(), Q = C.ake(), B = C.avs(), g = C.ave();
20
+ i.push([N(E.slice(A, Q)), N(E.slice(B, g))]);
21
+ }
22
+ K.push({
23
+ n: k,
24
+ t: B,
25
+ s: A,
26
+ e: Q,
27
+ ss: o,
28
+ se: D,
29
+ d: I,
30
+ a: g,
31
+ at: i.length > 0 ? i : null
32
+ });
33
+ }
34
+ for (; C.re();) {
35
+ const A = C.es(), Q = C.ee(), B = C.els(), g = C.ele(), I = N(E.slice(A, Q)), o = B < 0 ? void 0 : N(E.slice(B, g));
36
+ k.push({
37
+ s: A,
38
+ e: Q,
39
+ ls: B,
40
+ le: g,
41
+ n: I,
42
+ ln: o
43
+ });
44
+ }
45
+ function w(A) {
46
+ try {
47
+ return (0, eval)(A);
48
+ } catch (A) {}
49
+ }
50
+ function N(A) {
51
+ if (!A) return A;
52
+ const Q = A[0];
53
+ return ("\"" === Q || "'" === Q) && w(A) || A;
54
+ }
55
+ return [
56
+ K,
57
+ k,
58
+ !!C.f(),
59
+ !!C.ms()
60
+ ];
61
+ }
62
+ function Q(A, Q) {
63
+ const B = A.length;
64
+ let C = 0;
65
+ for (; C < B;) {
66
+ const B = A.charCodeAt(C);
67
+ Q[C++] = (255 & B) << 8 | B >>> 8;
68
+ }
69
+ }
70
+ function B(A, Q) {
71
+ const B = A.length;
72
+ let C = 0;
73
+ for (; C < B;) Q[C] = A.charCodeAt(C++);
74
+ }
75
+ let C;
76
+ const E = () => {
77
+ return A = "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", "undefined" != typeof Buffer ? Buffer.from(A, "base64") : Uint8Array.from(atob(A), ((A) => A.charCodeAt(0)));
78
+ var A;
79
+ };
80
+ const init = WebAssembly.compile(E()).then(WebAssembly.instantiate).then((({ exports: A }) => {
81
+ C = A;
82
+ }));
83
+ export { init, parse };
@@ -70,6 +70,18 @@ declare class MiniflareEnvRunner extends BaseEnvRunner {
70
70
  * and responds with `module-reloaded` when done.
71
71
  */
72
72
  reloadModule(timeout?: number): Promise<void>;
73
+ /**
74
+ * Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
75
+ *
76
+ * Host-side only (no worker round-trip): the module fallback service serves
77
+ * virtual sources from a live map, so re-running a factory source and
78
+ * bumping the per-specifier versions — the module plus its transitive
79
+ * virtual importers — is enough. Import specifiers in re-served module code
80
+ * are rewritten to the versioned form, giving workerd fresh module
81
+ * identities (it caches by name). A `persistent` instance is evicted from
82
+ * the cache, since its served sources no longer match the cache key.
83
+ */
84
+ invalidateModule(specifier: string, _timeout?: number): Promise<void>;
73
85
  protected _hasRuntime(): boolean;
74
86
  protected _runtimeType(): string;
75
87
  protected _closeRuntime(): Promise<void>;
@@ -1,6 +1,7 @@
1
- import { virtualModuleFormat } from "./virtual-loader.mjs";
1
+ import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
2
2
  import { BaseEnvRunner } from "./base-runner.mjs";
3
3
  import { init, parse } from "./libs/cjs-module-lexer.mjs";
4
+ import { init as init$1, parse as parse$1 } from "./libs/es-module-lexer.mjs";
4
5
  import { isVirtualSpecifier } from "./worker-utils.mjs";
5
6
  import { createRequire } from "node:module";
6
7
  import { proxyUpgrade } from "httpxy";
@@ -184,6 +185,9 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
184
185
  #miniflareOptions;
185
186
  #transformRequest;
186
187
  #reloadCounter = 0;
188
+ #virtual;
189
+ #virtualVersions = /* @__PURE__ */ new Map();
190
+ #cacheEntry;
187
191
  #ws;
188
192
  #persistent;
189
193
  #cacheKey;
@@ -215,7 +219,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
215
219
  this.#ws.close();
216
220
  this.#ws = void 0;
217
221
  }
218
- if (this.#cacheKey) _miniflareCache.delete(this.#cacheKey);
222
+ if (this.#cacheKey && _miniflareCache.get(this.#cacheKey)?.mf === this.#miniflare) _miniflareCache.delete(this.#cacheKey);
219
223
  await this.#miniflare.dispose();
220
224
  this.#miniflare = void 0;
221
225
  }
@@ -224,7 +228,8 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
224
228
  async fetch(input, init) {
225
229
  for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
226
230
  if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
227
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
231
+ const resolved = this._resolveFetchInput(input);
232
+ const url = typeof resolved === "string" ? resolved : resolved instanceof URL ? resolved.href : resolved.url;
228
233
  const res = await this.#miniflare.dispatchFetch(url, init);
229
234
  if (res instanceof Response) return res;
230
235
  return new Response(res.body, {
@@ -251,30 +256,27 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
251
256
  if (!this.#ws) throw new Error("Miniflare env runner should be initialized before reloading.");
252
257
  if (!this._data?.entry) return;
253
258
  this.#reloadCounter++;
254
- const version = this.#reloadCounter;
255
- return new Promise((resolve, reject) => {
256
- const timer = setTimeout(() => {
257
- cleanup();
258
- reject(/* @__PURE__ */ new Error("Module reload timed out"));
259
- }, timeout);
260
- const listener = (msg) => {
261
- if (msg?.event === "module-reloaded") {
262
- cleanup();
263
- if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
264
- else resolve();
265
- }
266
- };
267
- const cleanup = () => {
268
- clearTimeout(timer);
269
- this.offMessage(listener);
270
- };
271
- this.onMessage(listener);
272
- this.#ws.send(JSON.stringify({
273
- type: "reload",
274
- version
275
- }));
259
+ await this._request({
260
+ type: "reload",
261
+ version: this.#reloadCounter
262
+ }, {
263
+ match: (msg) => msg?.event === "module-reloaded",
264
+ timeout,
265
+ timeoutError: "Module reload timed out",
266
+ send: (message) => this.#ws.send(JSON.stringify(message))
276
267
  });
277
268
  }
269
+ async invalidateModule(specifier, _timeout) {
270
+ const virtual = this.#virtual;
271
+ if (!virtual || !Object.hasOwn(virtual, specifier)) {
272
+ const hasVirtual = Object.keys(this._data?.virtual ?? {}).length > 0;
273
+ throw !virtual && hasVirtual && !this.closed ? /* @__PURE__ */ new Error("Miniflare env runner should be initialized before invalidating modules.") : /* @__PURE__ */ new Error(`Cannot invalidate "${specifier}" (not a registered virtual module)`);
274
+ }
275
+ const source = await this._refreshVirtualSource(specifier);
276
+ if (source !== void 0) virtual[specifier] = await this.#prepareVirtualSource(specifier, source);
277
+ for (const key of expandVirtualInvalidation(virtual, specifier)) this.#virtualVersions.set(key, (this.#virtualVersions.get(key) ?? 0) + 1);
278
+ if (this.#cacheKey && _miniflareCache.get(this.#cacheKey) === this.#cacheEntry) _miniflareCache.delete(this.#cacheKey);
279
+ }
278
280
  _hasRuntime() {
279
281
  return Boolean(this.#miniflare);
280
282
  }
@@ -288,14 +290,12 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
288
290
  this.#ws.close();
289
291
  this.#ws = void 0;
290
292
  }
291
- if (this.#persistent && this.#cacheKey) {
292
- const cached = _miniflareCache.get(this.#cacheKey);
293
- if (cached) {
294
- cached.refCount--;
295
- if (cached.refCount <= 0) {
296
- _miniflareCache.delete(this.#cacheKey);
297
- await this.#miniflare.dispose();
298
- }
293
+ const entry = this.#cacheEntry;
294
+ if (entry) {
295
+ entry.refCount--;
296
+ if (entry.refCount <= 0) {
297
+ if (this.#cacheKey && _miniflareCache.get(this.#cacheKey) === entry) _miniflareCache.delete(this.#cacheKey);
298
+ await this.#miniflare.dispose();
299
299
  }
300
300
  } else await this.#miniflare.dispose();
301
301
  this.#miniflare = void 0;
@@ -322,21 +322,21 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
322
322
  const virtual = this._data?.virtual;
323
323
  if (!virtual || Object.keys(virtual).length === 0) return;
324
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;
325
+ for (const [specifier, source] of Object.entries(virtual)) out[specifier] = await this.#prepareVirtualSource(specifier, source);
334
326
  return out;
335
327
  }
328
+ async #prepareVirtualSource(specifier, source) {
329
+ if (virtualModuleFormat(specifier) !== "module-typescript") return source;
330
+ return stripVirtualTypeScript(specifier, source, await _getStripTypeScriptTypes(), {
331
+ requirement: "on the host (workerd does not parse TypeScript)",
332
+ remedy: "upgrade Node.js"
333
+ });
334
+ }
336
335
  async #initAsync() {
337
336
  const { Miniflare } = await import("miniflare");
338
337
  const entryPath = this._data?.entry;
339
338
  const virtual = await this.#prepareVirtualModules();
339
+ this.#virtual = virtual;
340
340
  const userFlags = this.#miniflareOptions.compatibilityFlags || [];
341
341
  const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
342
342
  const options = {
@@ -395,13 +395,15 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
395
395
  if (!options.unsafeModuleFallbackService) {
396
396
  const _require = createRequire(entryBase);
397
397
  const _virtual = virtual;
398
+ const _virtualVersions = this.#virtualVersions;
398
399
  const _transformRequest = this.#transformRequest;
399
400
  const _exportConditions = this.#exportConditions;
401
+ const _applyVirtualVersions = (code) => applyVirtualVersions(code, _virtualVersions);
400
402
  options.unsafeUseModuleFallbackService = true;
401
403
  const modulePathMap = /* @__PURE__ */ new Map();
402
- const _cjsLexerReady = ensureCjsLexer();
404
+ const _lexersReady = Promise.all([ensureCjsLexer(), init$1]);
403
405
  options.unsafeModuleFallbackService = async (request) => {
404
- await _cjsLexerReady;
406
+ await _lexersReady;
405
407
  const url = new URL(request.url);
406
408
  const specifier = url.searchParams.get("specifier");
407
409
  const rawSpecifier = url.searchParams.get("rawSpecifier");
@@ -424,7 +426,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
424
426
  json: source
425
427
  }) : Response.json({
426
428
  name,
427
- esModule: source
429
+ esModule: _applyVirtualVersions(source)
428
430
  });
429
431
  }
430
432
  }
@@ -480,7 +482,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
480
482
  modulePathMap.set(name, resolvedPath);
481
483
  return Response.json({
482
484
  name,
483
- esModule: result.code
485
+ esModule: _applyVirtualVersions(result.code)
484
486
  });
485
487
  }
486
488
  } catch {}
@@ -489,7 +491,7 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
489
491
  modulePathMap.set(name, resolvedPath);
490
492
  if (resolvedPath.endsWith(".mjs") || !resolvedPath.endsWith(".cjs") && /\b(import\s|import\(|export\s|export\{|import\.meta\b)/.test(contents)) return Response.json({
491
493
  name,
492
- esModule: contents
494
+ esModule: _applyVirtualVersions(contents)
493
495
  });
494
496
  const cjsSuffix = "?__cjs";
495
497
  if (specifier.endsWith(cjsSuffix)) return Response.json({
@@ -517,15 +519,23 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
517
519
  if (cached) {
518
520
  this.#miniflare = cached.mf;
519
521
  cached.refCount++;
522
+ this.#cacheEntry = cached;
523
+ this.#virtual = cached.virtual;
524
+ this.#virtualVersions = cached.versions;
520
525
  }
521
526
  }
522
527
  if (!this.#miniflare) {
523
528
  this.#miniflare = new Miniflare(options);
524
529
  await this.#miniflare.ready;
525
- if (this.#persistent && this.#cacheKey) _miniflareCache.set(this.#cacheKey, {
526
- mf: this.#miniflare,
527
- refCount: 1
528
- });
530
+ if (this.#persistent && this.#cacheKey) {
531
+ this.#cacheEntry = {
532
+ mf: this.#miniflare,
533
+ refCount: 1,
534
+ virtual,
535
+ versions: this.#virtualVersions
536
+ };
537
+ _miniflareCache.set(this.#cacheKey, this.#cacheEntry);
538
+ }
529
539
  }
530
540
  const initRes = await this.#miniflare.dispatchFetch("http://localhost/__env_runner_ipc", { headers: { upgrade: "websocket" } });
531
541
  const ws = initRes.webSocket;
@@ -571,6 +581,35 @@ function computeCacheKey(entryPath, opts) {
571
581
  for (const [k, v] of Object.entries(opts)) if (typeof v !== "function") serializableOpts[k] = v;
572
582
  return `${resolve(entryPath)}::${JSON.stringify(serializableOpts)}`;
573
583
  }
584
+ function applyVirtualVersions(code, versions) {
585
+ if (versions.size === 0) return code;
586
+ let imports;
587
+ try {
588
+ [imports] = parse$1(code);
589
+ } catch {
590
+ return code;
591
+ }
592
+ let out = "";
593
+ let last = 0;
594
+ for (const imp of imports) {
595
+ let specifier = imp.n;
596
+ if (specifier === void 0 && imp.d > -1) {
597
+ const expr = code.slice(imp.s, imp.e);
598
+ if (expr.length > 1 && expr[0] === "`" && expr.endsWith("`") && !expr.includes("${")) specifier = expr.slice(1, -1);
599
+ }
600
+ const version = specifier === void 0 ? void 0 : versions.get(specifier);
601
+ if (!version) continue;
602
+ const versioned = `${specifier}?v=${version}`;
603
+ out += code.slice(last, imp.s) + (imp.d > -1 ? JSON.stringify(versioned) : versioned);
604
+ last = imp.e;
605
+ }
606
+ return out + code.slice(last);
607
+ }
608
+ let _stripTypesPromise;
609
+ function _getStripTypeScriptTypes() {
610
+ _stripTypesPromise ??= import("node:module").then((m) => m.stripTypeScriptTypes);
611
+ return _stripTypesPromise;
612
+ }
574
613
  let _cjsLexerReady;
575
614
  function ensureCjsLexer() {
576
615
  if (!_cjsLexerReady) _cjsLexerReady = init();
@@ -56,6 +56,7 @@ var VercelEnvRunner = class extends NodeWorkerEnvRunner {
56
56
  warnIfVercelOidcTokenInvalid();
57
57
  }
58
58
  async fetch(input, init) {
59
+ input = this._resolveFetchInput(input);
59
60
  const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
60
61
  const requestId = generateVercelId();
61
62
  if (this._address && this._address.port != null && !headers.has("x-vercel-deployment-url")) {
@@ -10,6 +10,7 @@ var NetlifyEnvRunner = class extends NodeWorkerEnvRunner {
10
10
  });
11
11
  }
12
12
  async fetch(input, init) {
13
+ input = this._resolveFetchInput(input);
13
14
  const headers = new Headers(input instanceof Request ? input.headers : init?.headers);
14
15
  const clientIp = headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || "127.0.0.1";
15
16
  if (!headers.has("x-nf-client-connection-ip")) headers.set("x-nf-client-connection-ip", clientIp);
@@ -5,6 +5,8 @@ var RunnerManager = class {
5
5
  _messageListeners = /* @__PURE__ */ new Set();
6
6
  _closed = false;
7
7
  _reloading = false;
8
+ _moduleInvalidated = false;
9
+ _pendingModuleReload;
8
10
  _closeListeners = /* @__PURE__ */ new Set();
9
11
  _readyListeners = /* @__PURE__ */ new Set();
10
12
  constructor(runner) {
@@ -18,18 +20,35 @@ var RunnerManager = class {
18
20
  }
19
21
  async reload(runner) {
20
22
  this._reloading = true;
21
- const prev = this._runner;
22
- this._detach();
23
- this._attach(runner);
24
- this._reloading = false;
25
- if (prev) await prev.close();
23
+ try {
24
+ runner ??= await this._createRunner();
25
+ const prev = this._runner;
26
+ this._detach();
27
+ this._attach(runner);
28
+ this._moduleInvalidated = false;
29
+ if (prev) await prev.close();
30
+ } finally {
31
+ this._reloading = false;
32
+ }
33
+ }
34
+ _createRunner() {
35
+ throw new Error("reload() requires a runner argument (this manager has no runner factory)");
26
36
  }
27
37
  fetch = (input, init) => this._fetch(input, init);
28
38
  async _fetch(input, init) {
29
39
  const runner = await this._waitForRunner();
30
40
  if (!runner) return new Response("Runner is unavailable", { status: 503 });
41
+ if (this._moduleInvalidated) await this._flushInvalidation(runner);
31
42
  return runner.fetch(input, init);
32
43
  }
44
+ _flushInvalidation(runner) {
45
+ this._pendingModuleReload ??= Promise.resolve(runner.reloadModule?.()).then(() => {
46
+ this._moduleInvalidated = false;
47
+ }).finally(() => {
48
+ this._pendingModuleReload = void 0;
49
+ });
50
+ return this._pendingModuleReload;
51
+ }
33
52
  upgrade = (context) => {
34
53
  this._runner?.upgrade?.(context);
35
54
  };
@@ -94,7 +113,13 @@ var RunnerManager = class {
94
113
  }
95
114
  async reloadModule(timeout) {
96
115
  if (!this._runner?.reloadModule) throw new Error("Active runner does not support reloadModule()");
97
- return this._runner.reloadModule(timeout);
116
+ await this._runner.reloadModule(timeout);
117
+ this._moduleInvalidated = false;
118
+ }
119
+ async invalidateModule(specifier, timeout) {
120
+ if (!this._runner?.invalidateModule) throw new Error("Active runner does not support invalidateModule()");
121
+ await this._runner.invalidateModule(specifier, timeout);
122
+ this._moduleInvalidated = true;
98
123
  }
99
124
  async close() {
100
125
  this._closed = true;
@@ -202,6 +227,10 @@ var EnvServer = class extends RunnerManager {
202
227
  });
203
228
  return this._startPromise;
204
229
  }
230
+ async reload(runner) {
231
+ this.runner = runner ?? await this._createRunner();
232
+ await super.reload(this.runner);
233
+ }
205
234
  async close() {
206
235
  this._stopWatching();
207
236
  await super.close();
@@ -211,8 +240,7 @@ var EnvServer = class extends RunnerManager {
211
240
  return super._fetch(input, init);
212
241
  }
213
242
  async _start() {
214
- this.runner = await this._createRunner();
215
- await this.reload(this.runner);
243
+ await this.reload();
216
244
  if (this._opts.watch) this._startWatching();
217
245
  return this;
218
246
  }
@@ -245,8 +273,7 @@ var EnvServer = class extends RunnerManager {
245
273
  clearTimeout(this._reloadTimeout);
246
274
  this._reloadTimeout = setTimeout(async () => {
247
275
  try {
248
- this.runner = await this._createRunner();
249
- await this.reload(this.runner);
276
+ await this.reload();
250
277
  for (const fn of this._reloadListeners) fn();
251
278
  } catch (error) {
252
279
  console.error("Failed to reload runner:", error);
@@ -1,6 +1,11 @@
1
1
  import { IncomingMessage } from "node:http";
2
2
  import { Socket } from "node:net";
3
- /** Handler for proxying HTTP requests to the worker. */
3
+ /**
4
+ * Handler for proxying HTTP requests to the worker.
5
+ *
6
+ * A relative string input (e.g. `"/path"`) is resolved against a placeholder
7
+ * `http://localhost` origin before dispatching.
8
+ */
4
9
  type FetchHandler = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
5
10
  /** Callback for receiving messages from the worker. */
6
11
  type RunnerMessageListener = (data: unknown) => void;
@@ -67,6 +72,11 @@ interface EnvRunner extends RunnerRPCHooks, AsyncDisposable {
67
72
  rpc<T = unknown>(name: string, data?: unknown, opts?: RPCOptions): Promise<T>;
68
73
  /** Re-import the entry module without restarting the worker/process. */
69
74
  reloadModule?(timeout?: number): Promise<void>;
75
+ /**
76
+ * Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
77
+ * A factory-valued source is re-run for fresh contents.
78
+ */
79
+ invalidateModule?(specifier: string, timeout?: number): Promise<void>;
70
80
  /** Gracefully shut down the worker. */
71
81
  close(): Promise<void>;
72
82
  /** Alias for `close()`, enabling `await using` (explicit resource management). */
@@ -3,12 +3,16 @@ async function resolveVirtualModules(virtual) {
3
3
  return Object.fromEntries(entries);
4
4
  }
5
5
  const VIRTUAL_SCHEME = "virtual:";
6
- function createVirtualHooks(virtual) {
6
+ function createVirtualHooks(virtual, versions) {
7
7
  const resolve = (specifier, context, nextResolve) => {
8
- if (Object.hasOwn(virtual, _stripQuery(specifier))) return {
9
- url: VIRTUAL_SCHEME + encodeURIComponent(specifier),
10
- shortCircuit: true
11
- };
8
+ const key = _stripQuery(specifier);
9
+ if (Object.hasOwn(virtual, key)) {
10
+ const version = versions?.get(key);
11
+ return {
12
+ url: VIRTUAL_SCHEME + encodeURIComponent(specifier) + (version ? `?v=${version}` : ""),
13
+ shortCircuit: true
14
+ };
15
+ }
12
16
  return nextResolve(specifier, context);
13
17
  };
14
18
  const load = (url, context, nextLoad) => {
@@ -32,8 +36,28 @@ function virtualModuleFormat(specifier) {
32
36
  if (specifier.endsWith(".ts") || specifier.endsWith(".mts")) return "module-typescript";
33
37
  return "module";
34
38
  }
39
+ function stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, hints) {
40
+ if (typeof stripTypeScriptTypes !== "function") throw new TypeError(`[env-runner] virtual TypeScript module "${specifier}" requires \`module.stripTypeScriptTypes\` ${hints.requirement}; ${hints.remedy} or provide a pre-transpiled JavaScript source instead.`);
41
+ return stripTypeScriptTypes(source);
42
+ }
43
+ function expandVirtualInvalidation(virtual, specifier) {
44
+ const invalidated = [specifier];
45
+ const seen = new Set(invalidated);
46
+ for (const target of invalidated) {
47
+ const refs = [
48
+ `"${target}"`,
49
+ `'${target}'`,
50
+ "`" + target + "`"
51
+ ];
52
+ for (const [key, source] of Object.entries(virtual)) if (!seen.has(key) && refs.some((ref) => source.includes(ref))) {
53
+ seen.add(key);
54
+ invalidated.push(key);
55
+ }
56
+ }
57
+ return invalidated;
58
+ }
35
59
  function _stripQuery(specifier) {
36
60
  const qIndex = specifier.indexOf("?");
37
61
  return qIndex === -1 ? specifier : specifier.slice(0, qIndex);
38
62
  }
39
- export { createVirtualHooks, resolveVirtualModules, virtualModuleFormat };
63
+ export { createVirtualHooks, expandVirtualInvalidation, resolveVirtualModules, stripVirtualTypeScript, virtualModuleFormat };
@@ -1,4 +1,4 @@
1
- import { createVirtualHooks, virtualModuleFormat } from "./virtual-loader.mjs";
1
+ import { createVirtualHooks, expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { isAbsolute } from "node:path";
@@ -6,9 +6,25 @@ async function registerVirtualModules(virtual) {
6
6
  if (!virtual || Object.keys(virtual).length === 0) return _noop;
7
7
  const { registerHooks, stripTypeScriptTypes } = await import("node:module");
8
8
  if (typeof registerHooks === "function") {
9
- if ("Deno" in globalThis) virtual = _transformForDeno(virtual, stripTypeScriptTypes);
10
- const hooks = registerHooks(createVirtualHooks(virtual));
11
- return _once(() => hooks.deregister());
9
+ let transformSource;
10
+ if ("Deno" in globalThis) {
11
+ transformSource = (specifier, source) => _transformSourceForDeno(specifier, source, stripTypeScriptTypes);
12
+ const transformed = {};
13
+ for (const [specifier, source] of Object.entries(virtual)) transformed[specifier] = transformSource(specifier, source);
14
+ virtual = transformed;
15
+ }
16
+ const registration = {
17
+ virtual,
18
+ versions: /* @__PURE__ */ new Map(),
19
+ transformSource
20
+ };
21
+ const hooks = registerHooks(createVirtualHooks(virtual, registration.versions));
22
+ _hooksRegistrations.unshift(registration);
23
+ return _once(() => {
24
+ const index = _hooksRegistrations.indexOf(registration);
25
+ if (index !== -1) _hooksRegistrations.splice(index, 1);
26
+ hooks.deregister();
27
+ });
12
28
  }
13
29
  if (typeof globalThis.Bun?.plugin === "function") {
14
30
  _bunVirtual = virtual;
@@ -25,6 +41,30 @@ function refreshVirtualModule(specifier) {
25
41
  _registerBunModules([specifier]);
26
42
  return true;
27
43
  }
44
+ function invalidateVirtualModule(specifier, source) {
45
+ for (const registration of _hooksRegistrations) {
46
+ if (!Object.hasOwn(registration.virtual, specifier)) continue;
47
+ const { virtual, versions, transformSource } = registration;
48
+ if (source !== void 0) virtual[specifier] = transformSource ? transformSource(specifier, source) : source;
49
+ for (const key of expandVirtualInvalidation(virtual, specifier)) versions.set(key, (versions.get(key) ?? 0) + 1);
50
+ return true;
51
+ }
52
+ if (_bunVirtual && Object.hasOwn(_bunVirtual, specifier)) {
53
+ if (source !== void 0) _bunVirtual[specifier] = source;
54
+ _registerBunModules(expandVirtualInvalidation(_bunVirtual, specifier));
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+ function handleInvalidateModule(message, sendMessage) {
60
+ const ok = invalidateVirtualModule(message.specifier, message.source);
61
+ sendMessage({
62
+ event: "module-invalidated",
63
+ specifier: message.specifier,
64
+ error: ok ? void 0 : `Cannot invalidate "${message.specifier}" (not a registered virtual module)`
65
+ });
66
+ }
67
+ const _hooksRegistrations = [];
28
68
  let _bunVirtual;
29
69
  function _registerBunModules(specifiers) {
30
70
  globalThis.Bun.plugin({
@@ -46,17 +86,14 @@ function _registerBunModules(specifiers) {
46
86
  }
47
87
  });
48
88
  }
49
- function _transformForDeno(virtual, stripTypeScriptTypes) {
50
- const out = {};
51
- for (const [specifier, source] of Object.entries(virtual)) {
52
- const format = virtualModuleFormat(specifier);
53
- if (format === "module-typescript") {
54
- if (typeof stripTypeScriptTypes !== "function") throw new TypeError(`[env-runner] virtual TypeScript module "${specifier}" requires \`module.stripTypeScriptTypes\` (custom load hooks bypass Deno's native type stripping); upgrade Deno or provide a pre-transpiled JavaScript source instead.`);
55
- out[specifier] = stripTypeScriptTypes(source);
56
- } else if (format === "json") out[specifier] = `export default JSON.parse(${JSON.stringify(source)});`;
57
- else out[specifier] = source;
58
- }
59
- return out;
89
+ function _transformSourceForDeno(specifier, source, stripTypeScriptTypes) {
90
+ const format = virtualModuleFormat(specifier);
91
+ if (format === "module-typescript") return stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, {
92
+ requirement: "(custom load hooks bypass Deno's native type stripping)",
93
+ remedy: "upgrade Deno"
94
+ });
95
+ if (format === "json") return `export default JSON.parse(${JSON.stringify(source)});`;
96
+ return source;
60
97
  }
61
98
  const _noop = () => {};
62
99
  function _once(fn) {
@@ -111,4 +148,4 @@ async function _importFresh(entryPath, virtual) {
111
148
  if (typeof entry.fetch !== "function") throw new Error(`[env-runner] Entry module "${entryPath}" must export a \`fetch\` handler (export default { fetch(req) { ... } }).`);
112
149
  return entry;
113
150
  }
114
- export { isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry };
151
+ export { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry };
package/dist/index.d.mts CHANGED
@@ -16,15 +16,30 @@ declare class RunnerManager implements EnvRunner, AsyncDisposable {
16
16
  private _messageListeners;
17
17
  private _closed;
18
18
  private _reloading;
19
+ private _moduleInvalidated;
20
+ private _pendingModuleReload;
19
21
  private _closeListeners;
20
22
  private _readyListeners;
21
23
  constructor(runner?: EnvRunner);
22
24
  get ready(): boolean;
23
25
  get closed(): boolean;
24
- /** Replace the active runner with a new one. Closes the previous runner. */
25
- reload(runner: EnvRunner): Promise<void>;
26
+ /**
27
+ * Replace the active runner with a new one. Closes the previous runner.
28
+ *
29
+ * When called without a runner, a fresh one is created via `_createRunner()`
30
+ * (only available on subclasses with a runner factory, e.g. `EnvServer`).
31
+ */
32
+ reload(runner?: EnvRunner): Promise<void>;
33
+ /** Create a fresh runner for argument-less `reload()`. Overridden by subclasses with a runner factory. */
34
+ protected _createRunner(): EnvRunner | Promise<EnvRunner>;
26
35
  fetch: FetchHandler;
27
36
  protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
37
+ /**
38
+ * Lazily satisfy a pending `invalidateModule()` with a single entry reload
39
+ * before serving — concurrent fetches share the same reload. A failed reload
40
+ * keeps the invalidation pending, so the next fetch retries.
41
+ */
42
+ private _flushInvalidation;
28
43
  upgrade: UpgradeHandler;
29
44
  sendMessage(message: unknown): void;
30
45
  onMessage(listener: RunnerMessageListener): void;
@@ -34,6 +49,12 @@ declare class RunnerManager implements EnvRunner, AsyncDisposable {
34
49
  timeout?: number;
35
50
  }): Promise<T>;
36
51
  reloadModule(timeout?: number): Promise<void>;
52
+ /**
53
+ * Invalidate a virtual module on the active runner and mark the manager
54
+ * dirty: the next `fetch()` reloads the entry automatically, so callers
55
+ * don't need to pair the call with an explicit `reloadModule()`.
56
+ */
57
+ invalidateModule(specifier: string, timeout?: number): Promise<void>;
37
58
  close(): Promise<void>;
38
59
  [Symbol.asyncDispose](): Promise<void>;
39
60
  onClose(listener: (runner: EnvRunner, cause?: unknown) => void): void;
@@ -95,11 +116,16 @@ declare class EnvServer extends RunnerManager {
95
116
  * server. A failed start resets so a later call can retry.
96
117
  */
97
118
  start(): Promise<this>;
119
+ /**
120
+ * Replace the active runner. When called without an argument, a fresh
121
+ * runner is created from the server options.
122
+ */
123
+ reload(runner?: EnvRunner): Promise<void>;
98
124
  close(): Promise<void>;
99
125
  /** Auto-start on first fetch so an explicit `start()` call is optional. */
100
126
  protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
101
127
  private _start;
102
- private _createRunner;
128
+ protected _createRunner(): Promise<EnvRunner>;
103
129
  private _startWatching;
104
130
  private _stopWatching;
105
131
  private _scheduleReload;
@@ -1,4 +1,4 @@
1
- import { isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
1
+ import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
2
2
  import { serve } from "srvx";
3
3
  import { plugin } from "crossws/server";
4
4
  process.on("disconnect", () => process.exit(0));
@@ -58,6 +58,10 @@ process.on("message", async (message) => {
58
58
  }
59
59
  return;
60
60
  }
61
+ if (message?.event === "invalidate-module") {
62
+ handleInvalidateModule(message, sendMessage);
63
+ return;
64
+ }
61
65
  if (message?.type === "ping") {
62
66
  process.send({
63
67
  type: "pong",
@@ -1,4 +1,4 @@
1
- import { isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
1
+ import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
2
2
  import { serve } from "srvx";
3
3
  import { plugin } from "crossws/server";
4
4
  const data = JSON.parse(process.env.ENV_RUNNER_DATA || "{}");
@@ -79,6 +79,10 @@ async function handleMessage(message) {
79
79
  }
80
80
  return;
81
81
  }
82
+ if (message?.event === "invalidate-module") {
83
+ handleInvalidateModule(message, sendMessage);
84
+ return;
85
+ }
82
86
  if (message?.type === "ping") {
83
87
  sendMessage({
84
88
  type: "pong",
@@ -1,4 +1,4 @@
1
- import { isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
1
+ import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
2
2
  import { serve } from "srvx";
3
3
  import { plugin } from "crossws/server/node";
4
4
  process.on("disconnect", () => process.exit(0));
@@ -58,6 +58,10 @@ process.on("message", async (message) => {
58
58
  }
59
59
  return;
60
60
  }
61
+ if (message?.event === "invalidate-module") {
62
+ handleInvalidateModule(message, sendMessage);
63
+ return;
64
+ }
61
65
  if (message?.type === "ping") {
62
66
  process.send({
63
67
  type: "pong",
@@ -1,4 +1,4 @@
1
- import { isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
1
+ import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry } from "../../_chunks/worker-utils.mjs";
2
2
  import { parentPort, workerData } from "node:worker_threads";
3
3
  import { serve } from "srvx";
4
4
  import { plugin } from "crossws/server/node";
@@ -58,6 +58,10 @@ parentPort?.on("message", async (message) => {
58
58
  }
59
59
  return;
60
60
  }
61
+ if (message?.event === "invalidate-module") {
62
+ handleInvalidateModule(message, sendMessage);
63
+ return;
64
+ }
61
65
  if (message?.type === "ping") {
62
66
  parentPort?.postMessage({
63
67
  type: "pong",
@@ -16,6 +16,7 @@ declare class SelfEnvRunner extends BaseEnvRunner {
16
16
  };
17
17
  }): Promise<void>;
18
18
  sendMessage(message: unknown): void;
19
+ invalidateModule(specifier: string): Promise<void>;
19
20
  reloadModule(): Promise<void>;
20
21
  protected _hasRuntime(): boolean;
21
22
  protected _runtimeType(): string;
@@ -13,7 +13,7 @@ var SelfEnvRunner = class extends BaseEnvRunner {
13
13
  }
14
14
  async fetch(input, init) {
15
15
  if (!this.#entry || this.closed) return new Response("self env runner is unavailable", { status: 503 });
16
- const request = input instanceof Request ? input : new Request(input, init);
16
+ const request = input instanceof Request ? input : new Request(this._resolveFetchInput(input), init);
17
17
  return this.#entry.fetch(request);
18
18
  }
19
19
  async upgrade(context) {
@@ -35,6 +35,9 @@ var SelfEnvRunner = class extends BaseEnvRunner {
35
35
  }
36
36
  this.#entry?.ipc?.onMessage?.(message);
37
37
  }
38
+ async invalidateModule(specifier) {
39
+ throw new Error(`Cannot invalidate "${specifier}": the self runner does not support virtual modules`);
40
+ }
38
41
  async reloadModule() {
39
42
  const entryPath = this._data?.entry;
40
43
  if (!entryPath || !this.#entry) throw new Error("Cannot reload: no entry loaded");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-runner",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Generic environment runner for JavaScript runtimes.",
5
5
  "license": "MIT",
6
6
  "repository": "unjs/env-runner",
@@ -58,6 +58,7 @@
58
58
  "changelogen": "^0.6.2",
59
59
  "cjs-module-lexer": "^2.2.0",
60
60
  "env-runner-fixture": "link:",
61
+ "es-module-lexer": "^2.1.0",
61
62
  "miniflare": "^4.20260603.0",
62
63
  "obuild": "^0.4.36",
63
64
  "oxfmt": "^0.51.0",