env-runner 0.2.3 → 0.3.0

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.
@@ -168,9 +168,16 @@ var RunnerManager = class {
168
168
  await this._runner.reloadModule(timeout);
169
169
  this._moduleInvalidated = false;
170
170
  }
171
+ async updateVirtualModules(changes, timeout) {
172
+ const runner = await this._waitForRunner();
173
+ if (!runner?.updateVirtualModules) throw new Error("Active runner does not support updateVirtualModules()");
174
+ await runner.updateVirtualModules(changes, timeout);
175
+ this._moduleInvalidated = true;
176
+ }
171
177
  async invalidateModule(specifier, timeout) {
172
- if (!this._runner?.invalidateModule) throw new Error("Active runner does not support invalidateModule()");
173
- await this._runner.invalidateModule(specifier, timeout);
178
+ const runner = await this._waitForRunner();
179
+ if (!runner?.invalidateModule) throw new Error("Active runner does not support invalidateModule()");
180
+ await runner.invalidateModule(specifier, timeout);
174
181
  this._moduleInvalidated = true;
175
182
  }
176
183
  async close() {
@@ -208,11 +215,11 @@ var RunnerManager = class {
208
215
  runner.onMessage(this._internalListener);
209
216
  for (const listener of this._messageListeners) runner.onMessage(listener);
210
217
  const originalClose = runner.close.bind(runner);
211
- runner.close = async () => {
212
- await originalClose();
218
+ runner.close = async (cause) => {
219
+ await originalClose(cause);
213
220
  if (this._runner === runner) {
214
221
  this._runner = void 0;
215
- for (const fn of this._closeListeners) fn(this);
222
+ for (const fn of this._closeListeners) fn(this, cause);
216
223
  }
217
224
  };
218
225
  if (runner.ready) this._flushQueue();
@@ -263,6 +270,7 @@ var EnvServer = class extends RunnerManager {
263
270
  _reloadTimeout;
264
271
  _reloadListeners = /* @__PURE__ */ new Set();
265
272
  _startPromise;
273
+ _virtual;
266
274
  runner = null;
267
275
  onReload(listener) {
268
276
  this._reloadListeners.add(listener);
@@ -285,6 +293,12 @@ var EnvServer = class extends RunnerManager {
285
293
  this.runner = runner ?? await this._createRunner();
286
294
  await super.reload(this.runner);
287
295
  }
296
+ async updateVirtualModules(changes, timeout) {
297
+ const virtual = this._virtual ??= { ...this._opts.data?.virtual };
298
+ for (const [key, source] of Object.entries(changes)) if (source === null) delete virtual[key];
299
+ else if (source !== void 0) virtual[key] = source;
300
+ if (await this._waitForRunner()) await super.updateVirtualModules(changes, timeout);
301
+ }
288
302
  async close() {
289
303
  this._stopWatching();
290
304
  await super.close();
@@ -305,6 +319,7 @@ var EnvServer = class extends RunnerManager {
305
319
  hooks: this._opts.hooks,
306
320
  data: {
307
321
  ...this._opts.data,
322
+ ...this._virtual && { virtual: { ...this._virtual } },
308
323
  entry: this._opts.entry
309
324
  },
310
325
  execArgv: this._opts.execArgv
@@ -1,5 +1,35 @@
1
1
  import { IncomingMessage } from "node:http";
2
2
  import { Socket } from "node:net";
3
+ /**
4
+ * How a virtual module is served. Code formats are Node's load formats, plus
5
+ * `jsx`/`tsx` (Bun only). Raw formats follow import attribute types: `text`
6
+ * default-exports a string, `bytes` a `Uint8Array`, and `wasm` a compiled
7
+ * `WebAssembly.Module` (like workerd's Wasm modules).
8
+ */
9
+ export type VirtualModuleFormat = "module" | "commonjs" | "module-typescript" | "commonjs-typescript" | "jsx" | "tsx" | "json" | "text" | "bytes" | "wasm";
10
+ /** A source with an explicit format. */
11
+ export interface VirtualModule {
12
+ /** Code or text; a `Uint8Array` for the `bytes` and `wasm` formats. */
13
+ source: string | Uint8Array;
14
+ /**
15
+ * Default: by the key's extension (`.cjs`, `.json`, `.ts`, `.wasm`, ...),
16
+ * else `module` for a string and `bytes` for a `Uint8Array`.
17
+ */
18
+ format?: VirtualModuleFormat;
19
+ }
20
+ /** A source whose format follows its key (string or bytes), or a source with its format. */
21
+ export type VirtualModuleContent = string | Uint8Array | VirtualModule;
22
+ /** Factories run on the host: before the worker spawns, and on updates. */
23
+ export type VirtualModuleSource = VirtualModuleContent | (() => VirtualModuleContent | Promise<VirtualModuleContent>);
24
+ /** Virtual modules as a `specifier => source` map. */
25
+ export type VirtualModules = Record<string, VirtualModuleSource>;
26
+ /** Changes to apply to a running map: a source sets (adds or replaces) a key, `null` removes it. */
27
+ export type VirtualModuleUpdates = Record<string, VirtualModuleSource | null>;
28
+ /**
29
+ * A validated module, as workers receive it: a string follows its key's
30
+ * extension, any other source carries its format.
31
+ */
32
+ export type ResolvedVirtualModule = string | Required<VirtualModule>;
3
33
  /** Proxy a request to the worker; relative inputs resolve against `http://localhost`. */
4
34
  export type FetchHandler = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
5
35
  /** Callback for receiving messages from the worker. */
@@ -65,6 +95,12 @@ export interface EnvRunner extends RunnerRPCHooks, AsyncDisposable {
65
95
  rpc<T = unknown>(name: string, data?: unknown, opts?: RPCOptions): Promise<T>;
66
96
  /** Re-import the entry module without restarting the worker/process. */
67
97
  reloadModule?(timeout?: number): Promise<void>;
98
+ /**
99
+ * Set (add or replace) and remove (`null`) virtual modules in one round trip.
100
+ * Changed and removed modules, and their importers, evaluate fresh on the
101
+ * next `reloadModule()`; a removed key falls through to normal resolution.
102
+ */
103
+ updateVirtualModules?(changes: VirtualModuleUpdates, timeout?: number): Promise<void>;
68
104
  /**
69
105
  * Invalidate a virtual module so the next `reloadModule()` re-evaluates it.
70
106
  * A factory-valued source is re-run for fresh contents.
@@ -1,71 +1,345 @@
1
- import { pathToFileURL } from "node:url";
1
+ import { Buffer } from "node:buffer";
2
+ import { isAbsolute } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ const VIRTUAL_MODULE_FORMATS = /* @__PURE__ */ new Set([
5
+ "module",
6
+ "commonjs",
7
+ "module-typescript",
8
+ "commonjs-typescript",
9
+ "jsx",
10
+ "tsx",
11
+ "json",
12
+ "text",
13
+ "bytes",
14
+ "wasm"
15
+ ]);
16
+ const EXTENSION_FORMATS = {
17
+ ".cjs": "commonjs",
18
+ ".json": "json",
19
+ ".ts": "module-typescript",
20
+ ".mts": "module-typescript",
21
+ ".cts": "commonjs-typescript",
22
+ ".jsx": "jsx",
23
+ ".tsx": "tsx",
24
+ ".wasm": "wasm"
25
+ };
26
+ function virtualModuleFormat(key, module) {
27
+ return typeof module === "object" ? module.format : _extensionFormat(key) ?? "module";
28
+ }
29
+ function virtualModuleCodeSource(key, module) {
30
+ if (module === void 0) return;
31
+ const format = virtualModuleFormat(key, module);
32
+ if (format === "json" || format === "text" || _isBinaryFormat(format)) return;
33
+ return typeof module === "string" ? module : module.source;
34
+ }
35
+ function normalizeVirtualModule(key, content) {
36
+ const module = typeof content === "string" || content instanceof Uint8Array ? { source: content } : content;
37
+ const source = module?.source;
38
+ if (typeof source !== "string" && !(source instanceof Uint8Array)) throw new TypeError(`[env-runner] virtual module "${key}" must be a string, a Uint8Array or { source, format }.`);
39
+ const binary = typeof source !== "string";
40
+ const format = module.format ?? _extensionFormat(key) ?? (binary ? "bytes" : "module");
41
+ if (!VIRTUAL_MODULE_FORMATS.has(format)) throw new TypeError(`[env-runner] virtual module "${key}" has an unknown format "${format}" (expected one of: ${[...VIRTUAL_MODULE_FORMATS].join(", ")}).`);
42
+ if (binary !== _isBinaryFormat(format)) throw new TypeError(`[env-runner] virtual module "${key}" has format "${format}", which needs ${binary ? "a string" : "a Uint8Array"} source.`);
43
+ if (format === "wasm" && !WebAssembly.validate(source)) throw new TypeError(`[env-runner] virtual module "${key}" is not a valid WebAssembly binary.`);
44
+ if (typeof content === "string") return content;
45
+ return {
46
+ source: binary ? new Uint8Array(source) : source,
47
+ format
48
+ };
49
+ }
50
+ function normalizeVirtualModules(virtual) {
51
+ const out = {};
52
+ for (const [key, content] of Object.entries(virtual)) out[key] = normalizeVirtualModule(key, content);
53
+ return out;
54
+ }
2
55
  async function resolveVirtualModules(virtual) {
3
- const entries = await Promise.all(Object.entries(virtual).map(async ([key, value]) => [key, typeof value === "function" ? await value() : value]));
56
+ const entries = await Promise.all(Object.entries(virtual).map(async ([key, value]) => {
57
+ return [key, normalizeVirtualModule(key, typeof value === "function" ? await value() : value)];
58
+ }));
4
59
  return Object.fromEntries(entries);
5
60
  }
61
+ function encodeVirtualModules(virtual) {
62
+ const out = {};
63
+ for (const [key, module] of Object.entries(virtual)) out[key] = module !== null && typeof module === "object" && typeof module.source !== "string" ? {
64
+ source: _toBase64(module.source),
65
+ format: module.format,
66
+ encoding: "base64"
67
+ } : module;
68
+ return out;
69
+ }
70
+ function decodeVirtualModules(virtual) {
71
+ const out = {};
72
+ for (const [key, module] of Object.entries(virtual)) out[key] = module !== null && typeof module === "object" && "encoding" in module ? {
73
+ source: new Uint8Array(Buffer.from(module.source, "base64")),
74
+ format: module.format
75
+ } : module;
76
+ return out;
77
+ }
78
+ function virtualModuleCode(format, source) {
79
+ if (format === "text") return `export default ${JSON.stringify(source)};\n`;
80
+ const bytes = [
81
+ `const base64 = ${JSON.stringify(_toBase64(source))};`,
82
+ `let bytes;`,
83
+ `if (typeof Uint8Array.fromBase64 === "function") {`,
84
+ ` bytes = Uint8Array.fromBase64(base64);`,
85
+ `} else {`,
86
+ ` const binary = atob(base64);`,
87
+ ` bytes = new Uint8Array(binary.length);`,
88
+ ` for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);`,
89
+ `}`
90
+ ];
91
+ const value = format === "wasm" ? "new WebAssembly.Module(bytes)" : "bytes";
92
+ return `${bytes.join("\n")}\nexport default ${value};\n`;
93
+ }
94
+ function unsupportedVirtualJSXError(key, format, runtime) {
95
+ return /* @__PURE__ */ new TypeError(`[env-runner] virtual module "${key}" has format "${format}", which ${runtime} can't load from memory; pre-transpile it to JavaScript (and pass { source, format: "module" } to keep the key).`);
96
+ }
6
97
  const VIRTUAL_SCHEME = "virtual:";
7
- function createVirtualHooks(virtual, versions, parentURL = _defaultParentURL(), forcePlainModule = false) {
98
+ function createVirtualHooks(virtual, opts = {}) {
99
+ const { versions, importers, forcePlainModule } = opts;
100
+ const baseURL = opts.baseURL ?? _defaultBaseURL();
101
+ const keyURLs = /* @__PURE__ */ new Map();
102
+ const urlKeys = /* @__PURE__ */ new Map();
103
+ for (const key of Object.keys(virtual)) {
104
+ const url = _virtualKeyURL(key);
105
+ if (url) {
106
+ keyURLs.set(key, url);
107
+ urlKeys.set(url, key);
108
+ }
109
+ }
110
+ const keyOf = (url) => {
111
+ if (url.startsWith(VIRTUAL_SCHEME)) {
112
+ const key = _virtualURLKey(url);
113
+ return key !== void 0 && Object.hasOwn(virtual, key) ? key : void 0;
114
+ }
115
+ return urlKeys.size > 0 && url.startsWith("file:") ? urlKeys.get(_bareURL(url)) : void 0;
116
+ };
117
+ const moduleFiles = importers && /* @__PURE__ */ new Set();
118
+ const track = (node, context) => {
119
+ const { parentURL } = context;
120
+ if (!importers || !parentURL) return;
121
+ let importer = keyOf(parentURL);
122
+ if (importer === void 0 && !context.conditions?.includes("require")) {
123
+ const file = _bareURL(parentURL);
124
+ importer = moduleFiles.has(file) ? file : void 0;
125
+ }
126
+ if (importer !== void 0 && importer !== node) {
127
+ let set = importers.get(node);
128
+ if (!set) importers.set(node, set = /* @__PURE__ */ new Set());
129
+ set.add(importer);
130
+ }
131
+ };
132
+ const serve = (key, url, context) => {
133
+ track(key, context);
134
+ if (opts.opaquePathURL && keyURLs.has(key) && opts.opaquePathURL(keyURLs.get(key))) url = _virtualURL(key) + url.slice(_bareURL(url).length);
135
+ const version = versions?.get(key);
136
+ return {
137
+ url: version ? _appendQuery(url, `v=${version}`) : url,
138
+ shortCircuit: true
139
+ };
140
+ };
8
141
  const resolve = (specifier, context, nextResolve) => {
9
142
  const key = _stripQuery(specifier);
10
143
  if (Object.hasOwn(virtual, key)) {
11
- const version = versions?.get(key);
12
- return {
13
- url: VIRTUAL_SCHEME + encodeURIComponent(specifier) + (version ? `?v=${version}` : ""),
14
- shortCircuit: true
15
- };
144
+ const keyURL = keyURLs.get(key);
145
+ const url = keyURL ? keyURL + specifier.slice(key.length) : _virtualURL(specifier);
146
+ return serve(key, url, context);
147
+ }
148
+ let parentURL = context.parentURL;
149
+ if (parentURL?.startsWith(VIRTUAL_SCHEME)) {
150
+ const parentKey = keyOf(parentURL);
151
+ parentURL = parentKey !== void 0 && keyURLs.get(parentKey) || baseURL;
16
152
  }
17
- if (context.parentURL?.startsWith(VIRTUAL_SCHEME)) return nextResolve(specifier, {
153
+ if (urlKeys.size > 0) {
154
+ const url = _resolvePathSpecifier(specifier, parentURL);
155
+ const pathKey = url && urlKeys.get(_bareURL(url));
156
+ if (pathKey) return serve(pathKey, url, context);
157
+ }
158
+ const result = nextResolve(specifier, parentURL === context.parentURL ? context : {
18
159
  ...context,
19
160
  parentURL
20
161
  });
21
- return nextResolve(specifier, context);
162
+ const resolvedKey = urlKeys.size > 0 ? urlKeys.get(_bareURL(result.url)) : void 0;
163
+ if (resolvedKey) return serve(resolvedKey, result.url, context);
164
+ if (result.url.startsWith("file:")) {
165
+ const file = _bareURL(result.url);
166
+ track(file, context);
167
+ const version = versions?.get(file);
168
+ if (version) return {
169
+ ...result,
170
+ url: _appendQuery(result.url, `v=${version}`)
171
+ };
172
+ }
173
+ return result;
22
174
  };
23
175
  const load = (url, context, nextLoad) => {
24
- if (url.startsWith(VIRTUAL_SCHEME)) {
25
- const key = _stripQuery(decodeURIComponent(url.slice(8)));
26
- if (Object.hasOwn(virtual, key)) return {
27
- format: forcePlainModule ? "module" : virtualModuleFormat(key),
28
- source: virtual[key],
176
+ const key = keyOf(url);
177
+ if (key !== void 0) {
178
+ const module = virtual[key];
179
+ if (forcePlainModule) return {
180
+ format: "module",
181
+ source: module,
182
+ shortCircuit: true
183
+ };
184
+ return {
185
+ ..._nodeLoadResult(key, module),
29
186
  shortCircuit: true
30
187
  };
31
188
  }
32
- return nextLoad(url, context);
189
+ const result = nextLoad(url, context);
190
+ if (moduleFiles && url.startsWith("file:") && _isModuleFormat(result.format)) moduleFiles.add(_bareURL(url));
191
+ return result;
192
+ };
193
+ const updateKeys = (keys) => {
194
+ for (const key of keys) {
195
+ const url = keyURLs.get(key) ?? _virtualKeyURL(key);
196
+ if (!url) continue;
197
+ if (Object.hasOwn(virtual, key)) {
198
+ keyURLs.set(key, url);
199
+ urlKeys.set(url, key);
200
+ continue;
201
+ }
202
+ keyURLs.delete(key);
203
+ if (urlKeys.get(url) === key) {
204
+ urlKeys.delete(url);
205
+ for (const [other, otherURL] of keyURLs) if (otherURL === url) urlKeys.set(url, other);
206
+ }
207
+ }
33
208
  };
34
209
  return {
35
210
  resolve,
36
- load
211
+ load,
212
+ updateKeys
37
213
  };
38
214
  }
39
- function virtualModuleFormat(specifier) {
40
- if (specifier.endsWith(".json")) return "json";
41
- if (specifier.endsWith(".ts") || specifier.endsWith(".mts")) return "module-typescript";
42
- return "module";
215
+ function _nodeLoadResult(key, module) {
216
+ const format = virtualModuleFormat(key, module);
217
+ const source = typeof module === "string" ? module : module.source;
218
+ if (format === "text" || format === "bytes" || format === "wasm") return {
219
+ format: "module",
220
+ source: virtualModuleCode(format, source)
221
+ };
222
+ if (format === "jsx" || format === "tsx") throw unsupportedVirtualJSXError(key, format, "Node.js");
223
+ return {
224
+ format,
225
+ source
226
+ };
43
227
  }
44
228
  function stripVirtualTypeScript(specifier, source, stripTypeScriptTypes, hints) {
45
229
  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.`);
46
- return stripTypeScriptTypes(source);
230
+ try {
231
+ return stripTypeScriptTypes(source);
232
+ } catch (error) {
233
+ throw new SyntaxError(`[env-runner] failed to strip types from virtual module "${specifier}": ${error?.message || error}`, { cause: error });
234
+ }
47
235
  }
48
- function expandVirtualInvalidation(virtual, specifier) {
49
- const invalidated = [specifier];
236
+ function expandVirtualInvalidation(virtual, specifier, importers) {
237
+ const invalidated = [...new Set(typeof specifier === "string" ? [specifier] : specifier)];
50
238
  const seen = new Set(invalidated);
239
+ const add = (key) => {
240
+ if (!seen.has(key)) {
241
+ seen.add(key);
242
+ invalidated.push(key);
243
+ }
244
+ };
51
245
  for (const target of invalidated) {
246
+ for (const key of importers?.get(target) ?? []) add(key);
247
+ if (!Object.hasOwn(virtual, target)) continue;
52
248
  const refs = [
53
249
  `"${target}"`,
54
250
  `'${target}'`,
55
251
  "`" + target + "`"
56
252
  ];
57
- for (const [key, source] of Object.entries(virtual)) if (!seen.has(key) && refs.some((ref) => source.includes(ref))) {
58
- seen.add(key);
59
- invalidated.push(key);
253
+ for (const [key, module] of Object.entries(virtual)) {
254
+ const code = virtualModuleCodeSource(key, module);
255
+ if (code !== void 0 && refs.some((ref) => code.includes(ref))) add(key);
60
256
  }
61
257
  }
62
258
  return invalidated;
63
259
  }
260
+ function findVirtualPathKey(virtual, specifier) {
261
+ const url = _virtualKeyURL(_stripQuery(specifier));
262
+ if (!url) return;
263
+ let match;
264
+ for (const key of Object.keys(virtual)) if (_virtualKeyURL(key) === url) match = key;
265
+ return match;
266
+ }
267
+ const _warnedPathCollisions = /* @__PURE__ */ new Set();
268
+ function warnVirtualPathCollisions(keys) {
269
+ try {
270
+ const urlKeys = /* @__PURE__ */ new Map();
271
+ for (const key of keys) {
272
+ const url = _virtualKeyURL(key);
273
+ if (!url) continue;
274
+ const other = urlKeys.get(url);
275
+ if (other !== void 0 && !_warnedPathCollisions.has(`${other}\0${key}`)) {
276
+ _warnedPathCollisions.add(`${other}\0${key}`);
277
+ console.warn(`[env-runner] virtual modules "${other}" and "${key}" name the same file; keep only one of them (which one is served depends on the runtime).`);
278
+ }
279
+ urlKeys.set(url, key);
280
+ }
281
+ } catch {}
282
+ }
283
+ const virtualKeyURL = _virtualKeyURL;
284
+ function virtualModuleCacheId(key) {
285
+ const url = _virtualKeyURL(key);
286
+ return url ? fileURLToPath(url) : _virtualURL(key);
287
+ }
288
+ function _virtualKeyURL(key) {
289
+ if (key.startsWith("file:")) try {
290
+ return _bareURL(new URL(key).href);
291
+ } catch {
292
+ return;
293
+ }
294
+ return isAbsolute(key) ? pathToFileURL(key).href : void 0;
295
+ }
296
+ function _virtualURL(specifier) {
297
+ return VIRTUAL_SCHEME + specifier.replace(/[\0-\x20"%<>`\x7F-\u{10FFFF}]/gu, encodeURIComponent);
298
+ }
299
+ function _virtualURLKey(url) {
300
+ try {
301
+ return decodeURIComponent(_stripQuery(url.slice(8)));
302
+ } catch {
303
+ return;
304
+ }
305
+ }
306
+ function _extensionFormat(key) {
307
+ const extension = /\.[^./\\]+$/.exec(key)?.[0];
308
+ return extension === void 0 ? void 0 : EXTENSION_FORMATS[extension];
309
+ }
310
+ function _isBinaryFormat(format) {
311
+ return format === "bytes" || format === "wasm";
312
+ }
313
+ function _toBase64(bytes) {
314
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
315
+ }
316
+ function _isModuleFormat(format) {
317
+ return format == null || format === "module" || format === "module-typescript";
318
+ }
64
319
  function _stripQuery(specifier) {
65
320
  const qIndex = specifier.indexOf("?");
66
321
  return qIndex === -1 ? specifier : specifier.slice(0, qIndex);
67
322
  }
68
- function _defaultParentURL() {
323
+ function _bareURL(url) {
324
+ const index = url.search(/[?#]/);
325
+ return index === -1 ? url : url.slice(0, index);
326
+ }
327
+ function _appendQuery(url, param) {
328
+ const hashIndex = url.startsWith(VIRTUAL_SCHEME) ? -1 : url.indexOf("#");
329
+ const base = hashIndex === -1 ? url : url.slice(0, hashIndex);
330
+ const hash = hashIndex === -1 ? "" : url.slice(hashIndex);
331
+ return base + (base.includes("?") ? "&" : "?") + param + hash;
332
+ }
333
+ function _resolvePathSpecifier(specifier, parentURL) {
334
+ try {
335
+ if (/^\.{0,2}\//.test(specifier) || specifier.startsWith("file:")) return new URL(specifier, parentURL).href;
336
+ if (isAbsolute(specifier)) {
337
+ const path = _stripQuery(specifier);
338
+ return pathToFileURL(path).href + specifier.slice(path.length);
339
+ }
340
+ } catch {}
341
+ }
342
+ function _defaultBaseURL() {
69
343
  return pathToFileURL(process.cwd() + "/").href;
70
344
  }
71
- export { createVirtualHooks, expandVirtualInvalidation, resolveVirtualModules, stripVirtualTypeScript, virtualModuleFormat };
345
+ export { createVirtualHooks, decodeVirtualModules, encodeVirtualModules, expandVirtualInvalidation, findVirtualPathKey, normalizeVirtualModules, resolveVirtualModules, stripVirtualTypeScript, unsupportedVirtualJSXError, virtualKeyURL, virtualModuleCacheId, virtualModuleCode, virtualModuleCodeSource, virtualModuleFormat, warnVirtualPathCollisions };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, WorkerAddress, WorkerHooks } from "./_chunks/types.mjs";
2
- import { BaseEnvRunner, EnvRunnerData, VirtualModuleSource, VirtualModules } from "./_chunks/common-base-runner.mjs";
1
+ import { EnvRunner, FetchHandler, NodeUpgradeContext, RPCOptions, RunnerMessageListener, RunnerRPCHooks, UpgradeContext, UpgradeHandler, VirtualModule, VirtualModuleContent, VirtualModuleFormat, VirtualModuleSource, VirtualModuleUpdates, VirtualModules, WorkerAddress, WorkerHooks } from "./_chunks/types.mjs";
2
+ import { BaseEnvRunner, EnvRunnerData } from "./_chunks/common-base-runner.mjs";
3
3
  import { RuntimeDep, resolveRuntimeDep } from "./_chunks/common-runtime-deps.mjs";
4
4
  import { DenoProcessEnvRunner } from "./_chunks/deno-process-runner.mjs";
5
5
  import { MiniflareEnvRunner, MiniflareEnvRunnerOptions, MiniflareExportInfo, TransformResult } from "./_chunks/miniflare-runner.mjs";
@@ -45,6 +45,11 @@ export declare class RunnerManager implements EnvRunner, AsyncDisposable {
45
45
  timeout?: number;
46
46
  }): Promise<T>;
47
47
  reloadModule(timeout?: number): Promise<void>;
48
+ /**
49
+ * Set or remove (`null`) virtual modules of the active runner (waiting for it
50
+ * during a reload); the next `fetch()` reloads the entry automatically.
51
+ */
52
+ updateVirtualModules(changes: VirtualModuleUpdates, timeout?: number): Promise<void>;
48
53
  /** Invalidate a virtual module; the next `fetch()` reloads the entry automatically. */
49
54
  invalidateModule(specifier: string, timeout?: number): Promise<void>;
50
55
  close(): Promise<void>;
@@ -56,7 +61,8 @@ export declare class RunnerManager implements EnvRunner, AsyncDisposable {
56
61
  private _internalListener;
57
62
  private _attach;
58
63
  private _detach;
59
- private _waitForRunner;
64
+ /** The active runner, waiting for one during a reload. */
65
+ protected _waitForRunner(timeout?: number): Promise<EnvRunner | undefined>;
60
66
  private _flushQueue;
61
67
  }
62
68
  type RunnerName = "node-worker" | "node-process" | "bun-process" | "deno-process" | "self" | "miniflare" | "vercel" | "netlify";
@@ -96,6 +102,7 @@ export declare class EnvServer extends RunnerManager {
96
102
  private _reloadTimeout;
97
103
  private _reloadListeners;
98
104
  private _startPromise;
105
+ private _virtual;
99
106
  runner: Awaited<ReturnType<typeof loadRunner>> | null;
100
107
  /** Register a listener called when the runner is reloaded due to a file change. */
101
108
  onReload(listener: () => void): void;
@@ -109,6 +116,12 @@ export declare class EnvServer extends RunnerManager {
109
116
  start(): Promise<this>;
110
117
  /** Replace the active runner; without an argument, create one from the server options. */
111
118
  reload(runner?: EnvRunner): Promise<void>;
119
+ /**
120
+ * Also kept in the server's own map, so runners it creates later (`reload()`,
121
+ * watch mode) start with the changes. Without an active runner (before the
122
+ * first start), the changes are only recorded.
123
+ */
124
+ updateVirtualModules(changes: VirtualModuleUpdates, timeout?: number): Promise<void>;
112
125
  close(): Promise<void>;
113
126
  /** Auto-start on first fetch so an explicit `start()` call is optional. */
114
127
  protected _fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
@@ -149,4 +162,4 @@ export declare const RESERVED_RUNTIME_OPTIONS: {
149
162
  };
150
163
  /** Callers add `fetch` (bound lazily so reloads swap it) and the crossws plugin. */
151
164
  export declare function toServerOptions(entry: AppEntry): Omit<ServerOptions, "fetch">;
152
- export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunner, type EnvRunnerData, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type MiniflareExportInfo, NetlifyEnvRunner, type NodeUpgradeContext, type RPCOptions, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type RuntimeDep, type TransformResult, type UpgradeContext, type UpgradeHandler, VercelEnvRunner, type VirtualModuleSource, type VirtualModules, type WorkerAddress, type WorkerHooks, resolveRuntimeDep };
165
+ export { type AppEntry, type AppEntryIPC, type AppEntryIPCContext, BaseEnvRunner, DenoProcessEnvRunner, type EnvRunnerData as DenoProcessEnvRunnerData, type EnvRunner, type EnvRunnerData, type EnvServerOptions, type FetchHandler, type LoadRunnerOptions, MiniflareEnvRunner, type MiniflareEnvRunnerOptions, type MiniflareExportInfo, NetlifyEnvRunner, type NodeUpgradeContext, type RPCOptions, type RunnerMessageListener, type RunnerName, type RunnerRPCHooks, type RuntimeDep, type TransformResult, type UpgradeContext, type UpgradeHandler, VercelEnvRunner, type VirtualModule, type VirtualModuleContent, type VirtualModuleFormat, type VirtualModuleSource, type VirtualModuleUpdates, type VirtualModules, type WorkerAddress, type WorkerHooks, resolveRuntimeDep };
@@ -1,8 +1,7 @@
1
1
  import { BaseEnvRunner } from "../../_chunks/common-base-runner.mjs";
2
- import { hostEnv } from "../../_chunks/common-host-env.mjs";
2
+ import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { existsSync } from "node:fs";
5
- import { join } from "node:path";
6
5
  import { execSync, spawn } from "node:child_process";
7
6
  import { homedir } from "node:os";
8
7
  let _defaultEntry;
@@ -57,10 +56,7 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
57
56
  this.close(`process entry not found in "${this._workerEntry}".`);
58
57
  return;
59
58
  }
60
- const env = hostEnv({
61
- ENV_RUNNER_NAME: this._name,
62
- ENV_RUNNER_DATA: JSON.stringify(this._data || {})
63
- });
59
+ const env = this._processEnv();
64
60
  if (_isBun) this.#initBunProcess(execArgv, env);
65
61
  else this.#initNodeProcess(execArgv, env);
66
62
  }
@@ -78,7 +74,7 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
78
74
  "pipe"
79
75
  ],
80
76
  ipc: (message) => {
81
- this._handleMessage(message);
77
+ this._handleProcessMessage(message);
82
78
  }
83
79
  });
84
80
  const child = {
@@ -128,7 +124,7 @@ var BunProcessEnvRunner = class extends BaseEnvRunner {
128
124
  }
129
125
  });
130
126
  child.on("message", (message) => {
131
- this._handleMessage(message);
127
+ this._handleProcessMessage(message);
132
128
  });
133
129
  child.stdout?.pipe(process.stdout);
134
130
  child.stderr?.pipe(process.stderr);
@@ -1,16 +1,16 @@
1
- import { handleInvalidateModule, isVirtualSpecifier, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry, toServerOptions } from "../../_chunks/common-worker-utils.mjs";
1
+ import { formatInitError, handleUpdateVirtualModules, isVirtualEntry, parseServerAddress, registerVirtualModules, reloadEntryModule, resolveEntry, toServerOptions } from "../../_chunks/common-worker-utils.mjs";
2
+ import { receiveProcessData } from "../../_chunks/common-process-data.mjs";
2
3
  import { serve } from "srvx";
3
4
  import { plugin } from "crossws/server";
4
5
  process.on("disconnect", () => process.exit(0));
5
- const data = JSON.parse(process.env.ENV_RUNNER_DATA || "{}");
6
+ const data = await receiveProcessData();
6
7
  const sendMessage = (message) => process.send(message);
7
- const virtualEntry = isVirtualSpecifier(data.entry, data.virtual);
8
8
  let unregisterVirtualModules;
9
9
  let entry;
10
10
  let server;
11
11
  try {
12
12
  unregisterVirtualModules = await registerVirtualModules(data.virtual);
13
- entry = await resolveEntry(data.entry, virtualEntry);
13
+ entry = await resolveEntry(data.entry, isVirtualEntry(data.entry));
14
14
  server = serve({
15
15
  ...toServerOptions(entry),
16
16
  fetch: (request) => entry.fetch(request),
@@ -18,7 +18,7 @@ try {
18
18
  });
19
19
  await server.ready();
20
20
  } catch (error) {
21
- const message = error?.message || String(error);
21
+ const message = formatInitError(error);
22
22
  sendMessage({
23
23
  event: "init-error",
24
24
  error: message
@@ -45,7 +45,7 @@ process.on("message", async (message) => {
45
45
  }
46
46
  if (message?.event === "reload-module") {
47
47
  try {
48
- entry = await reloadEntryModule(data.entry, entry, sendMessage, virtualEntry);
48
+ entry = await reloadEntryModule(data.entry, entry, sendMessage, isVirtualEntry(data.entry));
49
49
  process.send({ event: "module-reloaded" });
50
50
  } catch (error) {
51
51
  process.send({
@@ -55,8 +55,8 @@ process.on("message", async (message) => {
55
55
  }
56
56
  return;
57
57
  }
58
- if (message?.event === "invalidate-module") {
59
- handleInvalidateModule(message, sendMessage);
58
+ if (message?.event === "update-virtual-modules") {
59
+ handleUpdateVirtualModules(message, sendMessage);
60
60
  return;
61
61
  }
62
62
  if (message?.type === "ping") {