env-runner 0.2.2 → 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.
- package/README.md +203 -26
- package/dist/_chunks/common-base-runner.d.mts +58 -51
- package/dist/_chunks/common-base-runner.mjs +127 -33
- package/dist/_chunks/common-process-data.mjs +13 -0
- package/dist/_chunks/common-runtime-deps.d.mts +7 -30
- package/dist/_chunks/common-worker-utils.mjs +464 -73
- package/dist/_chunks/deno-process-runner.mjs +11 -37
- package/dist/_chunks/lexer.mjs +2 -0
- package/dist/_chunks/libs/cjs-module-lexer.mjs +6 -1
- package/dist/_chunks/miniflare-runner.d.mts +56 -107
- package/dist/_chunks/miniflare-runner.mjs +834 -154
- package/dist/_chunks/netlify-runner.d.mts +3 -21
- package/dist/_chunks/node-worker-runner.mjs +1 -2
- package/dist/_chunks/server.mjs +20 -5
- package/dist/_chunks/types.d.mts +36 -9
- package/dist/_chunks/vercel-runner.mjs +1 -0
- package/dist/_chunks/virtual-loader.mjs +303 -29
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +31 -69
- package/dist/runners/bun-process/runner.mjs +4 -8
- package/dist/runners/bun-process/worker.mjs +8 -8
- package/dist/runners/deno-process/worker.mjs +17 -39
- package/dist/runners/node-process/runner.mjs +2 -6
- package/dist/runners/node-process/worker.mjs +8 -8
- package/dist/runners/node-worker/worker.mjs +6 -7
- package/dist/runners/self/runner.d.mts +2 -1
- package/dist/runners/self/runner.mjs +8 -0
- package/dist/runners/vercel/queue-dev.d.mts +9 -45
- package/dist/vite.d.mts +2 -12
- package/package.json +13 -13
- package/dist/_chunks/common-host-env.mjs +0 -11
package/README.md
CHANGED
|
@@ -205,7 +205,8 @@ const response = await runner.fetch("/api");
|
|
|
205
205
|
// prefer `manager.wsSrvxPlugin()` for cross-runtime proxying)
|
|
206
206
|
runner.upgrade?.({ node: { req, socket, head } });
|
|
207
207
|
|
|
208
|
-
// Wait for runner to be ready
|
|
208
|
+
// Wait for runner to be ready (rejects as soon as it closes, with the close
|
|
209
|
+
// reason as `error.cause`)
|
|
209
210
|
await runner.waitForReady();
|
|
210
211
|
|
|
211
212
|
// Bidirectional messaging
|
|
@@ -215,7 +216,12 @@ runner.onMessage((msg) => console.log(msg));
|
|
|
215
216
|
// Request-response RPC
|
|
216
217
|
const result = await runner.rpc<string>("transformHTML", "<html>...</html>");
|
|
217
218
|
|
|
218
|
-
// Hot-reload entry module without restarting the worker
|
|
219
|
+
// Hot-reload entry module without restarting the worker (the entry is
|
|
220
|
+
// re-read under its own URL; modules it imports stay cached)
|
|
221
|
+
await runner.reloadModule();
|
|
222
|
+
|
|
223
|
+
// Add, replace or remove (`null`) virtual modules in one round trip, then reload
|
|
224
|
+
await runner.updateVirtualModules({ "#routes": `export default []`, "#old": null });
|
|
219
225
|
await runner.reloadModule();
|
|
220
226
|
|
|
221
227
|
// Invalidate a virtual module (re-runs a factory source), then reload
|
|
@@ -231,7 +237,7 @@ await runner.reloadModule();
|
|
|
231
237
|
| Runner | Isolation | IPC mechanism |
|
|
232
238
|
| ---------------------- | ------------------------------- | ---------------------------------- |
|
|
233
239
|
| `NodeWorkerEnvRunner` | Worker thread | `workerData` / `parentPort` |
|
|
234
|
-
| `NodeProcessEnvRunner` | Child process (`fork`) | `
|
|
240
|
+
| `NodeProcessEnvRunner` | Child process (`fork`) | `process.send` IPC channel |
|
|
235
241
|
| `BunProcessEnvRunner` | Bun or Node.js process | `Bun.spawn` IPC or `fork()` |
|
|
236
242
|
| `DenoProcessEnvRunner` | Deno process | `deno run` with IPC channel |
|
|
237
243
|
| `SelfEnvRunner` | In-process | In-memory channel |
|
|
@@ -241,7 +247,7 @@ await runner.reloadModule();
|
|
|
241
247
|
|
|
242
248
|
#### Virtual Modules
|
|
243
249
|
|
|
244
|
-
The Node.js runners (`NodeWorkerEnvRunner`, `NodeProcessEnvRunner`, and the runners built on top of them), `BunProcessEnvRunner`, `DenoProcessEnvRunner` (Deno >= 2.
|
|
250
|
+
The Node.js runners (`NodeWorkerEnvRunner`, `NodeProcessEnvRunner`, and the runners built on top of them), `BunProcessEnvRunner`, `DenoProcessEnvRunner` (Deno >= 2.8), and `MiniflareEnvRunner` can serve **virtual modules** from an in-memory `specifier => source` map passed via `data.virtual` (`SelfEnvRunner` cannot, and closes with an error when it is set). The entry (and its dependencies) can then `import` them as if they were real files:
|
|
245
251
|
|
|
246
252
|
```ts
|
|
247
253
|
import { NodeWorkerEnvRunner } from "env-runner/runners/node-worker";
|
|
@@ -268,7 +274,7 @@ export default {
|
|
|
268
274
|
};
|
|
269
275
|
```
|
|
270
276
|
|
|
271
|
-
The **entry itself can be virtual** — set `data.entry` to one of the `data.virtual` keys to run an entry whose source lives in memory (it may import other virtual modules too):
|
|
277
|
+
The **entry itself can be virtual** — set `data.entry` to one of the `data.virtual` keys (exactly as written in the map) to run an entry whose source lives in memory (it may import other virtual modules too):
|
|
272
278
|
|
|
273
279
|
```ts
|
|
274
280
|
await using runner = new NodeWorkerEnvRunner({
|
|
@@ -284,7 +290,45 @@ await using runner = new NodeWorkerEnvRunner({
|
|
|
284
290
|
});
|
|
285
291
|
```
|
|
286
292
|
|
|
287
|
-
|
|
293
|
+
Keys can also be **file paths**: absolute paths (including Windows `C:\...`) or `file://` URLs. Such a module behaves like a file at that path, whether or not its directory exists on disk:
|
|
294
|
+
|
|
295
|
+
- it is matched by resolved URL, so any relative or absolute import that resolves to it is served from the map, whether the importer is a virtual module or a real file. A key equal to a real file's path therefore **overrides that file** for every importer.
|
|
296
|
+
- it runs under its real `file:` URL, so `import.meta.url`, `import.meta.dirname` and `import.meta.filename` point at the key.
|
|
297
|
+
- its own imports (relative files, bare packages) resolve from the key's directory.
|
|
298
|
+
- as `data.entry`, it may be written either way (`/app/x.mjs` for a `file:///app/x.mjs` key, or the reverse). It is still run from the map on load and across `reloadModule()`, even when a real file exists there.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { join, resolve } from "node:path";
|
|
302
|
+
|
|
303
|
+
const dir = resolve("src/generated"); // doesn't need to exist
|
|
304
|
+
|
|
305
|
+
await using runner = new NodeWorkerEnvRunner({
|
|
306
|
+
name: "my-app",
|
|
307
|
+
data: {
|
|
308
|
+
entry: join(dir, "entry.mjs"),
|
|
309
|
+
virtual: {
|
|
310
|
+
[join(dir, "entry.mjs")]: `import { body } from "./body.mjs";
|
|
311
|
+
export default { fetch: () => new Response(body) };`,
|
|
312
|
+
[join(dir, "body.mjs")]: `export const body = "Hello from " + import.meta.filename;`,
|
|
313
|
+
// Overrides the real `src/config.mjs` for all of its importers
|
|
314
|
+
[resolve("src/config.mjs")]: `export default { mode: "virtual" };`,
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Other keys (`#name`, bare names) only match an import specifier equal to the key, and relative imports inside them resolve from the working directory. Having no file, they get a runtime-specific id, shown by `import.meta.url` and stack traces: `virtual:#config` on Node.js and Deno, `file:///%23config` on Bun (`file:///env-runner-virtual:%23util.mjs` for keys with an extension), while on miniflare `import.meta.url` is undefined and stack traces show the key itself. A `?query` appended to an import is ignored for matching (`#config.json?raw` matches `#config.json`), but gives a separate module instance. Path keys are fully supported on the Node.js, Bun and Deno runners, with these exceptions:
|
|
321
|
+
|
|
322
|
+
- On Bun (`BunProcessEnvRunner`, and the Node.js runners when the host runtime is Bun), runtime plugins only see a specifier whose last `.` is followed by a letter, typically a file extension. A key without an extension (`#config`, `/app/entry`) therefore only matches an import spelled exactly like the key: no appended `?query`, and no relative or `file://` import of an extensionless path key.
|
|
323
|
+
- `MiniflareEnvRunner` supports relative imports and overrides, and treats `file://` keys and imports like their path, but `import.meta.url` is undefined.
|
|
324
|
+
|
|
325
|
+
On both, a virtual `data.entry` must be written exactly like its key.
|
|
326
|
+
|
|
327
|
+
Two path keys naming the same file (`/app/x.mjs` and `file:///app/x.mjs`) can't both be served, so the runner logs a warning naming both. Keep a single key per file.
|
|
328
|
+
|
|
329
|
+
Keys match **every importer** in the worker, dependencies included, like an import map entry. A bare key such as `react` replaces that package everywhere (useful to alias or stub it), and a `#name` key also replaces a dependency's own `#name` [subpath import](https://nodejs.org/api/packages.html#subpath-imports). There is no warning for this, since overriding a package is often the point, so give your own modules distinctive names (`#app/config` rather than `#utils`).
|
|
330
|
+
|
|
331
|
+
Each source may also be a **factory** returning a source (or a promise of one) — useful for lazily computed or asynchronously loaded sources:
|
|
288
332
|
|
|
289
333
|
```ts
|
|
290
334
|
await using runner = new NodeWorkerEnvRunner({
|
|
@@ -299,9 +343,9 @@ await using runner = new NodeWorkerEnvRunner({
|
|
|
299
343
|
});
|
|
300
344
|
```
|
|
301
345
|
|
|
302
|
-
Factories are invoked once on the host (before the worker is spawned), so the worker always receives
|
|
346
|
+
Factories are invoked once on the host (before the worker is spawned), so the worker always receives resolved sources — 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 sources for modules that don't need computation. Maps without factories skip this step entirely.
|
|
303
347
|
|
|
304
|
-
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:
|
|
348
|
+
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. On Node.js, Bun and Deno this follows the imports that were actually resolved, and on miniflare the import specifiers of the virtual sources, including relative ones between path keys. On Node.js and Deno it also covers **real files** on the way from the entry, such as a `./lib.mjs` importing `#config`: they are re-evaluated on the next reload too, while files that don't depend on the module stay cached. CommonJS files are never re-evaluated. On Bun and miniflare, a real file other than the entry keeps the old module. Already-imported modules keep their instances, so pair it with `reloadModule()` to re-import the entry graph:
|
|
305
349
|
|
|
306
350
|
```ts
|
|
307
351
|
await runner.invalidateModule("#config"); // re-runs the factory, busts the module
|
|
@@ -310,9 +354,29 @@ await runner.reloadModule(); // re-imports the entry, picking up the fresh modul
|
|
|
310
354
|
|
|
311
355
|
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.
|
|
312
356
|
|
|
313
|
-
|
|
357
|
+
To change the map itself while the runner is running, call `updateVirtualModules(changes)`. A source (string or factory) **adds or replaces** a key, and `null` **removes** it. All changes of one call are applied together in a single round trip to the worker:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
await runner.updateVirtualModules({
|
|
361
|
+
"#routes": `export default ["/", "/about"]`, // add or replace
|
|
362
|
+
[resolve("src/generated/api.mjs")]: () => generateApi(), // factories run on the host
|
|
363
|
+
"#legacy": null, // remove
|
|
364
|
+
});
|
|
365
|
+
await runner.reloadModule(); // or let RunnerManager/EnvServer reload on the next fetch
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Changed and removed keys are invalidated like `invalidateModule()` does, together with the modules importing them, so the next `reloadModule()` sees the new map:
|
|
369
|
+
|
|
370
|
+
- An **added** key resolves from then on, path keys included. An importer that failed to import it, or that loaded the real file it now overrides, picks it up once it is re-evaluated: the reloaded entry, virtual importers, and on Node.js and Deno also real files between them. A runner started without `data.virtual` registers its virtual modules on the first update, and real files it loaded before aren't tracked as importers.
|
|
371
|
+
- A **removed** key falls through to normal resolution: the real file it overrode, or a "not found" error. On Bun, a removed key without a file extension (see the Bun notes above) fails to load instead of falling through, and on miniflare an unresolvable bare specifier gets an empty module, as usual there.
|
|
372
|
+
|
|
373
|
+
Calls are applied one at a time in call order (a later call never loses to a slower factory of an earlier one), and `reloadModule()` waits for pending ones. A call made before the runner is ready waits for it. `invalidateModule(specifier)` is the same as updating the key with its current source. The runner keeps its own copy of the map, never changing your `data.virtual`. `RunnerManager.updateVirtualModules()` marks the manager dirty like `invalidateModule()`, and `EnvServer` also keeps the changes for the runners it creates later (`reload()`, watch mode). Changes made before the server started are only recorded, and the first runner starts with them.
|
|
374
|
+
|
|
375
|
+
Each module has a **format**. By default it follows the key's extension, like Node.js does for files: `.cjs` is CommonJS, `.ts`/`.mts` TypeScript, `.cts` CommonJS TypeScript, `.json` JSON, `.jsx`/`.tsx` JSX and `.wasm` WebAssembly. Any other string is an ES module, and any other `Uint8Array` raw bytes. To set the format explicitly, for example on a key without an extension, pass `{ source, format }`:
|
|
314
376
|
|
|
315
377
|
```ts
|
|
378
|
+
import { readFileSync } from "node:fs";
|
|
379
|
+
|
|
316
380
|
await using runner = new NodeWorkerEnvRunner({
|
|
317
381
|
name: "my-app",
|
|
318
382
|
data: {
|
|
@@ -320,31 +384,60 @@ await using runner = new NodeWorkerEnvRunner({
|
|
|
320
384
|
virtual: {
|
|
321
385
|
"#entry.ts": `
|
|
322
386
|
import { getGreeting } from "#util.ts";
|
|
323
|
-
import config from "#config
|
|
324
|
-
|
|
387
|
+
import config from "#config";
|
|
388
|
+
import legacy from "#legacy.cjs";
|
|
389
|
+
import logo from "#logo";
|
|
390
|
+
import add from "#add.wasm";
|
|
391
|
+
const { exports } = await WebAssembly.instantiate(add);
|
|
392
|
+
const handler: () => Response = () =>
|
|
393
|
+
new Response(\`\${getGreeting(config.name)} \${legacy.answer} \${logo.length} \${exports.add(1, 2)}\`);
|
|
325
394
|
export default { fetch: handler };
|
|
326
395
|
`,
|
|
327
396
|
"#util.ts": `export function getGreeting(name: string): string {
|
|
328
397
|
return \`Hello, \${name}!\`;
|
|
329
398
|
}`,
|
|
330
|
-
"#config
|
|
399
|
+
"#config": { source: JSON.stringify({ name: "virtual" }), format: "json" },
|
|
400
|
+
"#legacy.cjs": `module.exports = { answer: 42 };`,
|
|
401
|
+
"#logo": readFileSync("logo.png"), // a Uint8Array: `bytes`
|
|
402
|
+
"#add.wasm": readFileSync("add.wasm"),
|
|
331
403
|
},
|
|
332
404
|
},
|
|
333
405
|
});
|
|
334
406
|
```
|
|
335
407
|
|
|
336
|
-
|
|
408
|
+
| Format | Default for | Source | Importing it gives |
|
|
409
|
+
| --------------------- | -------------------------- | ------------ | ------------------------------------------------- |
|
|
410
|
+
| `module` | other string sources | string | the ES module |
|
|
411
|
+
| `commonjs` | `.cjs` | string | `module.exports` as default export, named exports |
|
|
412
|
+
| `module-typescript` | `.ts`, `.mts` | string | the ES module, types stripped |
|
|
413
|
+
| `commonjs-typescript` | `.cts` | string | like `commonjs`, types stripped |
|
|
414
|
+
| `json` | `.json` | string | the parsed value as default export |
|
|
415
|
+
| `jsx`, `tsx` | `.jsx`, `.tsx` | string | the ES module (Bun only) |
|
|
416
|
+
| `text` | — | string | the string as default export |
|
|
417
|
+
| `bytes` | other `Uint8Array` sources | `Uint8Array` | a `Uint8Array` as default export |
|
|
418
|
+
| `wasm` | `.wasm` | `Uint8Array` | a compiled `WebAssembly.Module` as default export |
|
|
419
|
+
|
|
420
|
+
The code formats are named like Node's [load formats](https://nodejs.org/api/module.html#loadurl-context-nextload), and `text` and `bytes` like the `with { type }` import attributes proposed for them (import them without attributes, though). An unknown format, or a source that doesn't fit its format (a `Uint8Array` for `json`, a string for `wasm`, bytes that aren't valid WebAssembly), closes the runner at startup with an error naming the key, or rejects `updateVirtualModules()` before anything changes.
|
|
421
|
+
|
|
422
|
+
- **TypeScript** is type-stripped by Node's native [type stripping](https://nodejs.org/api/typescript.html#type-stripping) (Node.js >= 22.18 / 23.6 — erasable syntax only) and by Bun's `ts` loader. On Deno, custom load hooks bypass its native type stripping, so sources are pre-stripped with [`module.stripTypeScriptTypes`](https://docs.deno.com/api/node/module/~/Module.stripTypeScriptTypes) (Deno >= 2.8.2); on older Deno without it, virtual TypeScript sources **throw at registration** — pass pre-transpiled JavaScript instead. On miniflare, sources are likewise pre-stripped with `module.stripTypeScriptTypes` on the host (workerd does not parse TypeScript).
|
|
337
423
|
- **JSON** sources expose the parsed value as the default export on all runtimes. The `with { type: "json" }` import attribute is optional on Node.js and Bun; on Deno and miniflare it must be **omitted** (static imports carrying an import attribute bypass `registerHooks` resolution on Deno, and workerd rejects import attributes outright).
|
|
424
|
+
- **CommonJS** is loaded natively by Node.js and workerd (miniflare). Bun and Deno only parse in-memory sources as ES modules, so there the source runs inside an ES module wrapper, as strict-mode code. Named exports are detected like Node.js does ([cjs-module-lexer](https://github.com/nodejs/cjs-module-lexer)), but re-exports (`module.exports = require("./other.cjs")`) only add named exports on Node.js. `require()` resolves packages, real files and other virtual modules, with these exceptions:
|
|
425
|
+
- On Deno, `require()` only reaches real files and packages, not virtual modules (Deno reads required files from disk), and requiring an ES module crashes Deno 2.9 while module hooks are registered (a Deno bug).
|
|
426
|
+
- On miniflare, a virtual module that is `require()`d keeps its first instance when it changes (`invalidateModule()`, updates): only `import` specifiers are rewritten. Import it instead, or restart the runner.
|
|
427
|
+
- On Node.js, after a module re-exported by CommonJS (`module.exports = require("./dep.cjs")`) changes, Node reads it as an empty, circular module (this happens with real files too after deleting them from `require.cache`). Assign it first (`const dep = require("./dep.cjs"); module.exports = dep;`).
|
|
428
|
+
- **Text and bytes** can't be imported from memory natively on every runtime, so they are served as ES modules where needed. `bytes` gives each module instance its own `Uint8Array`. Bytes survive every transport, including the process runners' JSON IPC (as base64) and `updateVirtualModules()`.
|
|
429
|
+
- **WebAssembly** default-exports a compiled [`WebAssembly.Module`](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Module) on every runtime, so instantiate it yourself with `WebAssembly.instantiate(module, imports)`. This follows workerd, which compiles `.wasm` modules ahead of time and disallows compiling Wasm at runtime. Node.js and Deno's own `.wasm` imports instantiate the module instead ([Wasm ESM integration](https://github.com/WebAssembly/esm-integration)), and Bun's give a file path, so those aren't used.
|
|
430
|
+
- **JSX** is only supported on Bun, by its `jsx`/`tsx` loaders (configure the JSX runtime with `tsconfig.json` or pragma comments like `/** @jsxImportSource preact */`). Node.js, Deno and miniflare can't load JSX from memory and fail with an error naming the key: pre-transpile it to JavaScript, and pass `{ source, format: "module" }` to keep a `.jsx`/`.tsx` key.
|
|
338
431
|
|
|
339
|
-
Virtual modules are registered inside the worker, before the entry is imported. On Node.js (>= 22.15 / 23.5) and Deno (>= 2.
|
|
432
|
+
Virtual modules are registered inside the worker, before the entry is imported. On Node.js (>= 22.15 / 23.5) and Deno (>= 2.8) this uses [ESM customization hooks](https://nodejs.org/api/module.html#moduleregisterhooksoptions) (`module.registerHooks`); on Bun (which does not implement `registerHooks`) it uses a [`Bun.plugin()`](https://bun.com/docs/runtime/plugins) runtime plugin instead, also for the Node.js runners when the host runtime is Bun. Each source is served in its format, and virtual specifiers (including a virtual entry) resolve across `reloadModule()`. On runtimes supporting neither mechanism, a warning is logged and registration is skipped. When the worker shuts down gracefully the registration is unregistered again (the `registerHooks` registration is deregistered; on Bun, which has no plugin-removal API, the registration is detached so fresh loads and reloads stop resolving, and an overridden real file loads from disk again).
|
|
340
433
|
|
|
341
|
-
On `MiniflareEnvRunner` there is no in-worker registration: the runner's module fallback service serves virtual specifiers to workerd directly (taking precedence over disk files and the `transformRequest` pipeline, so a virtual key overrides a real file with the same path).
|
|
434
|
+
On `MiniflareEnvRunner` there is no in-worker registration: the runner's module fallback service serves virtual specifiers to workerd directly (taking precedence over disk files and the `transformRequest` pipeline, so a virtual key overrides a real file with the same path). Named `exports` (Durable Objects / WorkerEntrypoints) also work with virtual entries. One limitation on miniflare v4: a **real** entry with auto-detected named exports can't import virtual modules, because miniflare's module locator reads its imports from disk at startup. Use a virtual entry, a separate `exports` module or miniflare v5 instead.
|
|
342
435
|
|
|
343
436
|
#### Miniflare Runner
|
|
344
437
|
|
|
345
438
|
Run your app in the Cloudflare Workers runtime using [miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare).
|
|
346
439
|
|
|
347
|
-
`env-runner` declares no peer dependencies — install `miniflare` yourself and pass it to the runner (see [Runtime dependencies](#runtime-dependencies)):
|
|
440
|
+
`env-runner` declares no peer dependencies — install `miniflare` (v4 or v5) yourself and pass it to the runner (see [Runtime dependencies](#runtime-dependencies)):
|
|
348
441
|
|
|
349
442
|
```bash
|
|
350
443
|
npm install miniflare
|
|
@@ -373,7 +466,9 @@ Passing `miniflare` explicitly is preferred — the version you install is then
|
|
|
373
466
|
|
|
374
467
|
The entry uses the same `AppEntry` format as the other runners. Requests are handled like srvx's Cloudflare adapter (`srvx/cloudflare`): the entry's `plugins`, `middleware` and `error` handler are applied, and the request carries `request.runtime` (`{ name: "cloudflare", cloudflare: { env, context } }`), `request.ip` (from `cf-connecting-ip`) and `request.waitUntil()`. For Workers-style entries, `fetch` still receives `(request, env, ctx)`. env-runner's internal bindings are never exposed on `env`. Listener-level srvx options (`maxRequestBodySize`, `trustProxy`, `node`/`bun`/`deno`, ...) do not apply to miniflare.
|
|
375
468
|
|
|
376
|
-
When you don't set a
|
|
469
|
+
When you don't set a compatibility date, it defaults to the date supported by the installed `workerd` binary rather than today's date — the binary always lags the calendar slightly, and pinning a future date makes `workerd` refuse to start. Set the runner's `compatibilityDate` option to pin one, or to `"latest"` to use the installed `workerd`'s supported date explicitly (no need to import `miniflare` for `supportedCompatibilityDate`). Precedence: `miniflareOptions.compatibilityDate` > `compatibilityDate` > the wrangler config's `compatibility_date` > the supported date. Whatever the source, a date newer than the installed `workerd` supports falls back to the supported date with a warning (like `wrangler dev`).
|
|
470
|
+
|
|
471
|
+
The runner enables the `nodejs_compat` compatibility flag by default. Set `no_nodejs_compat` (in the wrangler config's `compatibility_flags` or in `miniflareOptions.compatibilityFlags`) to opt out; the generated wrapper then avoids Node.js built-ins. If the two sources disagree, `miniflareOptions` wins.
|
|
377
472
|
|
|
378
473
|
#### Wrangler Config
|
|
379
474
|
|
|
@@ -386,12 +481,15 @@ await using runner = new MiniflareEnvRunner({
|
|
|
386
481
|
miniflare,
|
|
387
482
|
name: "my-worker",
|
|
388
483
|
data: { entry: "./worker.ts" },
|
|
389
|
-
wrangler: true, // auto-discover wrangler.{json,jsonc,toml}
|
|
484
|
+
wrangler: true, // auto-discover wrangler.{json,jsonc,toml} (see below)
|
|
390
485
|
// wrangler: "./config/wrangler.toml", // or an explicit path
|
|
391
486
|
// wranglerEnv: "production", // select a `[env.production]` block
|
|
487
|
+
// compatibilityDate: "latest", // override the config's compatibility_date
|
|
392
488
|
});
|
|
393
489
|
```
|
|
394
490
|
|
|
491
|
+
Auto-discovery searches parent directories: when the entry file is inside the current working directory, it walks up from the entry's directory to the filesystem root (so a config at a monorepo root is found for an entry in `apps/web/src/`); when the entry lives elsewhere (e.g. a framework entry hoisted under `node_modules/.pnpm`), only the entry's own directory is checked before walking up from the cwd, so the cwd's config is never shadowed by one above the entry. The nearest directory wins; within one directory `wrangler.json` is preferred over `wrangler.jsonc`, then `wrangler.toml` (unlike `wrangler`, which looks for each filename all the way up before trying the next). A config found in a parent directory is logged once.
|
|
492
|
+
|
|
395
493
|
`wranglerEnv` selects a named Wrangler environment (`--env`). When omitted, it defaults to the `CLOUDFLARE_ENV` environment variable, so `CLOUDFLARE_ENV=production` selects the `production` env without passing the option.
|
|
396
494
|
|
|
397
495
|
You can also pass an **inline** config object (raw `wrangler.json` shape) instead of (or in addition to) a file — handy for programmatic setups:
|
|
@@ -410,9 +508,27 @@ await using runner = new MiniflareEnvRunner({
|
|
|
410
508
|
});
|
|
411
509
|
```
|
|
412
510
|
|
|
413
|
-
When an inline config is passed, a `wrangler.{json,jsonc,toml}` file is still auto-discovered (
|
|
511
|
+
When an inline config is passed, a `wrangler.{json,jsonc,toml}` file is still auto-discovered (as above) and loaded, and the inline config is **merged on top of it** — inline values win per key, binding records (e.g. `vars`) merge, and `compatibilityFlags` are unioned. This lets you keep a committed `wrangler` file and override a few fields programmatically. If the inline config doesn't define the selected `wranglerEnv`, its top level is used as-is (the file's env still applies), and a config that fails to load only warns without discarding the other one.
|
|
512
|
+
|
|
513
|
+
Set `wranglerConfigPath` to load a specific config file instead of auto-discovering one — with `wrangler: true` or an inline config (which still merges on top), and without changing the working directory:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
await using runner = new MiniflareEnvRunner({
|
|
517
|
+
miniflare,
|
|
518
|
+
name: "my-worker",
|
|
519
|
+
data: { entry: "./.nitro/dev/index.mjs" },
|
|
520
|
+
wrangler: { vars: { GREETING: "hello" } },
|
|
521
|
+
wranglerConfigPath: "./wrangler.jsonc", // relative to cwd
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
A missing `wranglerConfigPath` file warns (an inline config is still applied). When `wrangler` is itself a string path, that path wins and `wranglerConfigPath` is ignored.
|
|
526
|
+
|
|
527
|
+
The runner hosts a single fetch-only worker, so config entries it can't run are **dropped**: `assets`, `services`, `queues.consumers`, `workflows`, `tail_consumers`/`streaming_tail_consumers`, and Durable Object bindings to another script (`script_name`). Durable Object bindings to local classes (exported by your entry or an [exports module](#exports-module)) are kept — including bindings whose `script_name` is the worker's own `name` (the inline config's `name` when set, else the file's; with `wranglerEnv` suffixed `-<env>` unless the env section sets a `name`, e.g. `my-worker-staging`), which are local in `wrangler dev` too — and merged with [auto-detected exports](#auto-detected-exports). Pass any of the dropped options via `miniflareOptions` to opt back in.
|
|
414
528
|
|
|
415
|
-
|
|
529
|
+
Whenever `wrangler` is enabled (`true`, a path, or an inline config), local state (KV, D1, R2, Durable Objects, ...) persists under `<dir>/.wrangler/state/v3` — the same place `wrangler dev` uses, so both share data. `<dir>` is the directory of the loaded config file, else of the requested config path (`wrangler` string or `wranglerConfigPath`, even if the file is missing), else the current working directory (e.g. inline-only configs, or `wrangler: true` with no file found). Set `miniflareOptions.defaultPersistRoot` (or any `*Persist` option, e.g. `kvPersist: false`; on miniflare v5, `resourcePersistencePath`) to opt out.
|
|
530
|
+
|
|
531
|
+
Pass the [`wrangler`](https://www.npmjs.com/package/wrangler) package as `wranglerModule` — the imported module or a specifier — for full fidelity: TOML, config validation, and every binding type.
|
|
416
532
|
|
|
417
533
|
```ts
|
|
418
534
|
import * as miniflare from "miniflare";
|
|
@@ -427,7 +543,26 @@ await using runner = new MiniflareEnvRunner({
|
|
|
427
543
|
});
|
|
428
544
|
```
|
|
429
545
|
|
|
430
|
-
|
|
546
|
+
With the `wrangler` package, wrangler's own config warnings (e.g. unexpected/misspelled keys, or a `wranglerEnv` the config doesn't define) are printed for a config file — once per file version and env, so hot reloads don't repeat them. Inline configs are validated without printing wrangler's warnings (load errors still warn). As in `wrangler dev`, unexpected keys also trigger wrangler's npm update check (cached for a day), which may print a "newer version of Wrangler available" hint.
|
|
547
|
+
|
|
548
|
+
Set `wranglerEnvFiles` to load local dev vars/secrets from custom `.env` files, like `getPlatformProxy({ envFiles })`:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
await using runner = new MiniflareEnvRunner({
|
|
552
|
+
miniflare,
|
|
553
|
+
wranglerModule: wrangler,
|
|
554
|
+
name: "my-worker",
|
|
555
|
+
data: { entry: "./worker.ts" },
|
|
556
|
+
wrangler: true,
|
|
557
|
+
wranglerEnvFiles: [".env", ".env.development"], // relative to the config file's dir
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Paths resolve against the loaded config file's directory (else the current working directory) and later files override earlier ones. When set (non-empty), `.dev.vars` is not read; when unset, wrangler's defaults apply (`.dev.vars[.<env>]`, else `.env*`); an empty array reads `.dev.vars` but no `.env*` files. Both the `wrangler` package and the built-in minimal reader honor it.
|
|
562
|
+
|
|
563
|
+
Without `wranglerModule`, `wrangler` is imported optionally; if that fails too, a built-in minimal reader handles JSON/JSONC files and inline objects (TOML files are skipped with a warning). It follows wrangler's semantics for `env` selection (bindings and `vars` are not inherited into a named env), local ids (`preview_id` / `preview_bucket_name` / `preview_database_id` first, so state is shared with `wrangler dev`), SQLite-backed Durable Objects (`migrations[].new_sqlite_classes`) and dev vars (`.dev.vars[.<env>]`, `.env*`, `secrets.required`), but only maps common bindings (`vars`, KV, R2, D1, Durable Objects, queue producers); other bindings (e.g. `hyperdrive`, `ai`, `ratelimits`) are ignored with a warning. Pass `wranglerModule: false` to always use the minimal reader. Values you pass in `miniflareOptions` always take precedence over config-derived ones — binding records (e.g. `bindings`) merge per key, and `compatibilityFlags` are merged.
|
|
564
|
+
|
|
565
|
+
Config options a single dev worker can't run — `services`, `assets`, `queues.consumers`, `workflows`, `tail_consumers`/`streaming_tail_consumers`, and `durable_objects` bindings with a `script_name` naming another worker — are ignored with one warning listing them (e.g. `services (MY_SERVICE)`); pass the equivalent Miniflare options via `miniflareOptions` to opt in.
|
|
431
566
|
|
|
432
567
|
#### Module Transform Pipeline
|
|
433
568
|
|
|
@@ -449,7 +584,7 @@ When `transformRequest` is provided:
|
|
|
449
584
|
|
|
450
585
|
- The `unsafeModuleFallbackService` calls it with the resolved file path before falling back to raw disk reads
|
|
451
586
|
- Module rules for `.ts`, `.tsx`, `.jsx`, and `.mts` are added automatically
|
|
452
|
-
-
|
|
587
|
+
- The wrapper never statically re-exports the entry (`export *`), to avoid miniflare's ModuleLocator pre-walking its import tree
|
|
453
588
|
|
|
454
589
|
The callback should return `{ code: string }` for transformed modules, or `null`/`undefined` to fall back to the default raw file read.
|
|
455
590
|
|
|
@@ -465,9 +600,9 @@ export class Counter {
|
|
|
465
600
|
|
|
466
601
|
export default {
|
|
467
602
|
async fetch(request, env) {
|
|
468
|
-
// env.
|
|
469
|
-
const id = env.
|
|
470
|
-
const stub = env.
|
|
603
|
+
// env.COUNTER is auto-wired — no manual config needed
|
|
604
|
+
const id = env.COUNTER.idFromName("test");
|
|
605
|
+
const stub = env.COUNTER.get(id);
|
|
471
606
|
return stub.fetch(request);
|
|
472
607
|
},
|
|
473
608
|
};
|
|
@@ -485,7 +620,30 @@ await using runner = new MiniflareEnvRunner({
|
|
|
485
620
|
});
|
|
486
621
|
```
|
|
487
622
|
|
|
488
|
-
Set `exports: false` to disable auto-detection entirely.
|
|
623
|
+
Auto-wired bindings are merged with Durable Object bindings from `miniflareOptions` and a wrangler config: exports whose class is already bound (or whose binding name is taken) are skipped. Set `exports: false` to disable auto-detection entirely.
|
|
624
|
+
|
|
625
|
+
#### Exports Module
|
|
626
|
+
|
|
627
|
+
To load named exports from a separate module, set `exports` to its absolute path or a `data.virtual` key (a relative path resolves from the entry's directory, not the working directory). The wrapper re-exports it with `export *`, so re-exports and exported aliases work.
|
|
628
|
+
|
|
629
|
+
In this mode nothing is auto-detected or auto-wired: configure the bindings with `wrangler` or `miniflareOptions`. The entry's own `export class` declarations are **not** re-exported either, so re-export them from the exports module if they are bound.
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
const runner = new MiniflareEnvRunner({
|
|
633
|
+
name: "app",
|
|
634
|
+
miniflare,
|
|
635
|
+
data: {
|
|
636
|
+
entry: "/path/to/server.mjs",
|
|
637
|
+
virtual: {
|
|
638
|
+
"#server-exports": 'export { Counter } from "/path/to/counter.mjs";',
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
exports: "#server-exports",
|
|
642
|
+
miniflareOptions: { durableObjects: { COUNTER: "Counter" } },
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
In both modes, named exports are registered when the worker starts. Recreate the runner when their implementation or export list changes; `reloadModule()` only reloads the request entry.
|
|
489
647
|
|
|
490
648
|
#### Error Capture
|
|
491
649
|
|
|
@@ -527,6 +685,8 @@ const runner2 = new MiniflareEnvRunner({
|
|
|
527
685
|
// Fully destroy: runner.dispose() or MiniflareEnvRunner.disposeAll()
|
|
528
686
|
```
|
|
529
687
|
|
|
688
|
+
An instance is only reused by runners with the same virtual module sources. Once `invalidateModule()` or `updateVirtualModules()` changes them, the instance leaves the cache (runners attached to it keep using it), and later runners start a fresh one.
|
|
689
|
+
|
|
530
690
|
#### Vercel Runner
|
|
531
691
|
|
|
532
692
|
Simulates a Vercel deployment environment with automatic header injection (`x-vercel-deployment-url`, `x-vercel-forwarded-for`, forwarding headers) and global context.
|
|
@@ -734,6 +894,23 @@ await using runner = new NodeProcessEnvRunner({
|
|
|
734
894
|
});
|
|
735
895
|
```
|
|
736
896
|
|
|
897
|
+
Process runners (`NodeProcessEnvRunner`, `BunProcessEnvRunner`, `DenoProcessEnvRunner`) deliver `data` over IPC, so its size (e.g. large virtual modules) is not bound by environment variable limits. A custom process worker requests it once its message listener is attached:
|
|
898
|
+
|
|
899
|
+
```ts
|
|
900
|
+
// custom-worker.ts
|
|
901
|
+
const data = await new Promise((resolve) => {
|
|
902
|
+
const onMessage = (message) => {
|
|
903
|
+
if (message?.event === "init-data") {
|
|
904
|
+
process.off("message", onMessage);
|
|
905
|
+
resolve(JSON.parse(message.data)); // `data` is sent as a JSON string
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
process.on("message", onMessage);
|
|
909
|
+
process.send({ event: "request-init-data" });
|
|
910
|
+
});
|
|
911
|
+
// ... start a server, then report it with `process.send({ address: { host, port } })`
|
|
912
|
+
```
|
|
913
|
+
|
|
737
914
|
## Development
|
|
738
915
|
|
|
739
916
|
<details>
|
|
@@ -1,38 +1,21 @@
|
|
|
1
|
-
import { EnvRunner, RunnerMessageListener, WorkerAddress, WorkerHooks } from "./types.mjs";
|
|
1
|
+
import { EnvRunner, ResolvedVirtualModule, RunnerMessageListener, VirtualModuleUpdates, VirtualModules, WorkerAddress, WorkerHooks } from "./types.mjs";
|
|
2
2
|
import { IncomingMessage } from "node:http";
|
|
3
3
|
import { Socket } from "node:net";
|
|
4
|
-
/**
|
|
5
|
-
* Source for a virtual module: either a literal ES module string or a factory
|
|
6
|
-
* that returns one (sync or async).
|
|
7
|
-
*
|
|
8
|
-
* Factories are evaluated **once on the host side** before the worker is spawned
|
|
9
|
-
* (functions can't cross the `workerData`/`JSON` boundary, and Node's synchronous
|
|
10
|
-
* load hook can't await), so the worker always receives plain strings. See
|
|
11
|
-
* {@link resolveVirtualModules}.
|
|
12
|
-
*/
|
|
13
|
-
export type VirtualModuleSource = string | (() => string | Promise<string>);
|
|
14
|
-
/** Virtual modules as a `specifier => source` map. */
|
|
15
|
-
export type VirtualModules = Record<string, VirtualModuleSource>;
|
|
16
4
|
export interface EnvRunnerData {
|
|
17
5
|
name?: string;
|
|
18
6
|
/**
|
|
19
|
-
* Virtual modules
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Each source may be a string or a factory `() => string | Promise<string>`.
|
|
26
|
-
* Factories are evaluated once on the host before the worker is spawned (so the
|
|
27
|
-
* worker always receives plain strings).
|
|
28
|
-
*
|
|
29
|
-
* Supported by the `node-worker`, `node-process`, `bun-process`,
|
|
30
|
-
* `deno-process`, `vercel`, `netlify`, and `miniflare` runners.
|
|
7
|
+
* Virtual modules importable from the entry, e.g.
|
|
8
|
+
* `{ "#virtual-import": "export const foo = 1" }`. A source is a string, a
|
|
9
|
+
* `Uint8Array` or `{ source, format }` (format by extension by default), or
|
|
10
|
+
* a factory returning one, which runs on the host before spawn. Change them
|
|
11
|
+
* at runtime with `updateVirtualModules()`. Not supported by the `self`
|
|
12
|
+
* runner (it closes with an error).
|
|
31
13
|
*/
|
|
32
14
|
virtual?: VirtualModules;
|
|
33
15
|
[key: string]: unknown;
|
|
34
16
|
}
|
|
35
17
|
export declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposable {
|
|
18
|
+
#private;
|
|
36
19
|
closed: boolean;
|
|
37
20
|
protected _name: string;
|
|
38
21
|
protected _workerEntry: string;
|
|
@@ -42,7 +25,10 @@ export declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposabl
|
|
|
42
25
|
protected _address?: WorkerAddress;
|
|
43
26
|
protected _messageListeners: Set<(data: unknown) => void>;
|
|
44
27
|
protected _pendingRequests: Set<(cause?: unknown) => void>;
|
|
28
|
+
protected _closeCause?: unknown;
|
|
45
29
|
protected _virtualResolved?: Promise<void>;
|
|
30
|
+
protected _virtualUpdates: Promise<void>;
|
|
31
|
+
protected _processData?: string;
|
|
46
32
|
constructor(opts: {
|
|
47
33
|
name: string;
|
|
48
34
|
workerEntry: string;
|
|
@@ -62,32 +48,49 @@ export declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposabl
|
|
|
62
48
|
abstract sendMessage(message: unknown): void;
|
|
63
49
|
onMessage(listener: RunnerMessageListener): void;
|
|
64
50
|
offMessage(listener: RunnerMessageListener): void;
|
|
51
|
+
/** Rejects on timeout, and as soon as the runner closes (with the close cause). */
|
|
65
52
|
waitForReady(timeout?: number): Promise<void>;
|
|
66
53
|
rpc<T = unknown>(name: string, data?: unknown, opts?: {
|
|
67
54
|
timeout?: number;
|
|
68
55
|
}): Promise<T>;
|
|
56
|
+
/** Re-import the entry, after any pending `updateVirtualModules()` call. */
|
|
69
57
|
reloadModule(timeout?: number): Promise<void>;
|
|
70
58
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
59
|
+
* Set (add or replace) and remove (`null`) virtual modules in one round trip.
|
|
60
|
+
* Factory sources run on the host. Changed and removed keys, and the modules
|
|
61
|
+
* importing them, evaluate fresh on the next `reloadModule()`; a removed key
|
|
62
|
+
* falls through to normal resolution. Calls apply in order, waiting for the
|
|
63
|
+
* runner to become ready. The runner keeps its own copy of the map in sync,
|
|
64
|
+
* never changing the caller's `data.virtual`.
|
|
65
|
+
*/
|
|
66
|
+
updateVirtualModules(changes: VirtualModuleUpdates, timeout?: number): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidate a virtual module so the next `reloadModule()` re-evaluates it:
|
|
69
|
+
* `updateVirtualModules()` with its current source, so a factory re-runs.
|
|
70
|
+
* Rejects for unknown specifiers.
|
|
75
71
|
*/
|
|
76
72
|
invalidateModule(specifier: string, timeout?: number): Promise<void>;
|
|
77
73
|
close(cause?: unknown): Promise<void>;
|
|
78
74
|
[Symbol.asyncDispose](): Promise<void>;
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* placeholder — requests are dispatched to the worker address regardless.
|
|
83
|
-
*/
|
|
75
|
+
/** Briefly back off (~3s total) while the worker is still starting. */
|
|
76
|
+
protected _waitForAddress(): Promise<void>;
|
|
77
|
+
/** Placeholder origin for relative inputs; requests go to the worker address regardless. */
|
|
84
78
|
protected _resolveFetchInput(input: string | URL | Request): string | URL | Request;
|
|
85
79
|
protected _handleMessage(message: any): void;
|
|
86
80
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
|
|
81
|
+
* Env for a process worker. Also snapshots the runner data, which goes over
|
|
82
|
+
* IPC on request (see `common/process-data.ts`), virtual module bytes as
|
|
83
|
+
* base64; throws if not JSON-serializable.
|
|
84
|
+
*/
|
|
85
|
+
protected _processEnv(): NodeJS.ProcessEnv;
|
|
86
|
+
/**
|
|
87
|
+
* Process worker messages: answer the `request-init-data` handshake with the
|
|
88
|
+
* runner data (internal, not forwarded to listeners), handle everything else.
|
|
89
|
+
*/
|
|
90
|
+
protected _handleProcessMessage(message: any): void;
|
|
91
|
+
/**
|
|
92
|
+
* Send a message and await the matching response. Rejects on timeout, on an
|
|
93
|
+
* `error` response, and as soon as the runner closes.
|
|
91
94
|
*/
|
|
92
95
|
protected _request<T = unknown>(message: unknown, opts: {
|
|
93
96
|
match: (msg: any) => boolean;
|
|
@@ -96,24 +99,28 @@ export declare abstract class BaseEnvRunner implements EnvRunner, AsyncDisposabl
|
|
|
96
99
|
send?: (message: unknown) => void;
|
|
97
100
|
}): Promise<T>;
|
|
98
101
|
/**
|
|
99
|
-
* Resolve
|
|
100
|
-
* worker
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* functions can't cross the worker boundary and the load hook can't await.
|
|
102
|
+
* Resolve factory `data.virtual` sources before spawn (functions can't cross
|
|
103
|
+
* the worker boundary; the load hook can't await), and validate every
|
|
104
|
+
* module. `undefined` when there is no factory and nothing is invalid, so
|
|
105
|
+
* subclasses can spawn synchronously.
|
|
104
106
|
*/
|
|
105
107
|
protected _resolveVirtualData(): Promise<void> | undefined;
|
|
106
108
|
/**
|
|
107
|
-
*
|
|
108
|
-
* `
|
|
109
|
-
*
|
|
109
|
+
* Queue a virtual module update. Updates apply one at a time in call order
|
|
110
|
+
* (`changes` is read when its turn comes), each after the initial factory
|
|
111
|
+
* resolution: until it settles, `_data.virtual` aliases the factory map.
|
|
112
|
+
*/
|
|
113
|
+
protected _enqueueVirtualUpdate(changes: () => VirtualModuleUpdates, timeout: number): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Apply resolved changes (`null` removes) to the running worker in one round
|
|
116
|
+
* trip, bytes as base64 (the message may be JSON). Overridden by runners
|
|
117
|
+
* serving virtual modules from the host.
|
|
110
118
|
*/
|
|
111
|
-
protected
|
|
119
|
+
protected _applyVirtualUpdates(changes: Record<string, ResolvedVirtualModule | null>, timeout: number): Promise<void>;
|
|
112
120
|
/**
|
|
113
|
-
* Run
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* runner with the error as cause instead of leaving an unhandled rejection.
|
|
121
|
+
* Run `init` once `data.virtual` is resolved (synchronously without factories).
|
|
122
|
+
* A failing factory (or deferred `init`, e.g. a spawn error) closes the runner
|
|
123
|
+
* with the error as cause.
|
|
117
124
|
*/
|
|
118
125
|
protected _initWithVirtualData(init: () => void): void;
|
|
119
126
|
protected _closeSocket(): Promise<void>;
|