env-runner 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -8
- package/dist/_chunks/base-runner.d.mts +7 -62
- package/dist/_chunks/base-runner.mjs +65 -0
- package/dist/_chunks/libs/std-env.mjs +1 -1
- package/dist/_chunks/runner.d.mts +2 -1
- package/dist/_chunks/runner2.d.mts +60 -2
- package/dist/_chunks/runner2.mjs +474 -83
- package/dist/_chunks/server.mjs +48 -0
- package/dist/_chunks/types.d.mts +76 -0
- package/dist/_chunks/worker-utils.mjs +11 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +12 -3
- package/dist/runners/bun-process/runner.d.mts +2 -1
- package/dist/runners/bun-process/worker.mjs +28 -6
- package/dist/runners/deno-process/worker.mjs +28 -6
- package/dist/runners/miniflare/runner.d.mts +2 -2
- package/dist/runners/node-process/runner.d.mts +2 -1
- package/dist/runners/node-process/worker.mjs +28 -6
- package/dist/runners/node-worker/runner.d.mts +2 -1
- package/dist/runners/node-worker/worker.mjs +28 -6
- package/dist/runners/self/runner.d.mts +12 -1
- package/dist/runners/self/runner.mjs +13 -1
- package/dist/vite.d.mts +32 -0
- package/dist/vite.mjs +58 -0
- package/package.json +4 -2
package/dist/_chunks/runner2.mjs
CHANGED
|
@@ -1,25 +1,256 @@
|
|
|
1
1
|
import { t as BaseEnvRunner } from "./base-runner.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { proxyUpgrade } from "httpxy";
|
|
2
4
|
import { readFileSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, resolve } from "node:path";
|
|
7
|
+
//#region src/runners/miniflare/wrapper.ts
|
|
8
|
+
const IPC_PATH$1 = "/__env_runner_ipc";
|
|
9
|
+
/** Service binding name used for cross-request IPC (worker → runner). */
|
|
10
|
+
const IPC_BINDING = "__ENV_RUNNER_IPC";
|
|
11
|
+
/**
|
|
12
|
+
* Generates a wrapper module that imports the user entry and adds IPC glue.
|
|
13
|
+
*
|
|
14
|
+
* The user module is expected to export `fetch` and optionally `ipc`.
|
|
15
|
+
* The wrapper uses a persistent WebSocket pair for bidirectional IPC:
|
|
16
|
+
* - Init: `fetch` with `upgrade: websocket` creates a WebSocketPair
|
|
17
|
+
* - Messages: JSON over the WebSocket (no per-message `dispatchFetch`)
|
|
18
|
+
* - Reload: `{ type: "reload" }` triggers cache-busted re-import
|
|
19
|
+
* - Shutdown: `{ type: "shutdown" }` calls `ipc.onClose()`
|
|
20
|
+
*
|
|
21
|
+
* For outgoing messages during fetch request handling, uses a service binding
|
|
22
|
+
* (`__ENV_RUNNER_IPC`) to avoid workerd's cross-request I/O restriction on
|
|
23
|
+
* the WebSocket object.
|
|
24
|
+
*
|
|
25
|
+
* Passed as an in-memory `script` to Miniflare (no temp files needed).
|
|
26
|
+
*/
|
|
27
|
+
function generateWrapper(entryPath, opts) {
|
|
28
|
+
const staticReExport = opts?.dynamicOnly ? "" : `export * from ${JSON.stringify(entryPath)};`;
|
|
29
|
+
const explicitExports = opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
|
|
30
|
+
const fetchBody = opts?.captureErrors ?? true ? `try {
|
|
31
|
+
return await entryFetch(request, env, ctx);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
34
|
+
const body = JSON.stringify({
|
|
35
|
+
error: error.message,
|
|
36
|
+
stack: error.stack,
|
|
37
|
+
name: error.constructor?.name || "Error",
|
|
38
|
+
});
|
|
39
|
+
return new Response(body, {
|
|
40
|
+
status: 500,
|
|
41
|
+
headers: { "Content-Type": "application/json", "X-Env-Runner-Error": "1" },
|
|
42
|
+
});
|
|
43
|
+
}` : `return entryFetch(request, env, ctx);`;
|
|
44
|
+
return `import __process from "node:process";
|
|
45
|
+
if (!globalThis.process) { globalThis.process = __process; }
|
|
46
|
+
${staticReExport}
|
|
47
|
+
${explicitExports}
|
|
48
|
+
|
|
49
|
+
const __IPC_PATH = "${IPC_PATH$1}";
|
|
50
|
+
const __IPC_BINDING = "${IPC_BINDING}";
|
|
51
|
+
const __entryPath = ${JSON.stringify(entryPath)};
|
|
52
|
+
let __userEntry;
|
|
53
|
+
let __ipcInitialized = false;
|
|
54
|
+
let __serverWs;
|
|
55
|
+
let __currentEnv;
|
|
56
|
+
|
|
57
|
+
async function __loadEntry(env, path) {
|
|
58
|
+
globalThis.__ENV_RUNNER_UNSAFE_EVAL__ = env.__ENV_RUNNER_UNSAFE_EVAL__;
|
|
59
|
+
const importFn = env.__ENV_RUNNER_UNSAFE_EVAL__.newAsyncFunction(
|
|
60
|
+
"return await import(path)",
|
|
61
|
+
"loadEntry",
|
|
62
|
+
"path"
|
|
63
|
+
);
|
|
64
|
+
const mod = await importFn(path);
|
|
65
|
+
return mod.default || mod;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function __sendMessage(message) {
|
|
69
|
+
const payload = JSON.stringify(message);
|
|
70
|
+
const env = __currentEnv;
|
|
71
|
+
if (env && env[__IPC_BINDING]) {
|
|
72
|
+
env[__IPC_BINDING].fetch("http://localhost/__ipc", {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: payload,
|
|
75
|
+
}).catch(() => {});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (__serverWs) {
|
|
79
|
+
__serverWs.send(payload);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function __handleWsMessage(env, data) {
|
|
84
|
+
let msg;
|
|
85
|
+
try { msg = JSON.parse(data); } catch { return; }
|
|
86
|
+
|
|
87
|
+
if (msg.type === "message") {
|
|
88
|
+
if (__userEntry?.ipc?.onMessage) {
|
|
89
|
+
__userEntry.ipc.onMessage(msg.data);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (msg.type === "reload" && env.__ENV_RUNNER_UNSAFE_EVAL__) {
|
|
95
|
+
const version = msg.version || 0;
|
|
96
|
+
try {
|
|
97
|
+
const newEntry = await __loadEntry(env, __entryPath + "?t=" + version);
|
|
98
|
+
if (__userEntry?.ipc?.onClose) {
|
|
99
|
+
await __userEntry.ipc.onClose();
|
|
100
|
+
}
|
|
101
|
+
__userEntry = newEntry;
|
|
102
|
+
__crosswsAdapter = undefined;
|
|
103
|
+
__ipcInitialized = false;
|
|
104
|
+
if (__userEntry.ipc?.onOpen) {
|
|
105
|
+
__ipcInitialized = true;
|
|
106
|
+
await __userEntry.ipc.onOpen({ sendMessage: __sendMessage });
|
|
107
|
+
}
|
|
108
|
+
__sendMessage({ event: "module-reloaded" });
|
|
109
|
+
} catch (e) {
|
|
110
|
+
__sendMessage({ event: "module-reloaded", error: String(e) });
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (msg.type === "shutdown") {
|
|
116
|
+
if (__userEntry?.ipc?.onClose) {
|
|
117
|
+
await __userEntry.ipc.onClose();
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let __crosswsAdapter;
|
|
124
|
+
|
|
125
|
+
async function __initCrossws(env, hooks) {
|
|
126
|
+
if (__crosswsAdapter) return __crosswsAdapter;
|
|
127
|
+
const importFn = env.__ENV_RUNNER_UNSAFE_EVAL__.newAsyncFunction(
|
|
128
|
+
"return await import('crossws/adapters/cloudflare')",
|
|
129
|
+
"loadCrossws"
|
|
130
|
+
);
|
|
131
|
+
const { default: cloudflareAdapter } = await importFn();
|
|
132
|
+
__crosswsAdapter = cloudflareAdapter({ hooks });
|
|
133
|
+
return __crosswsAdapter;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default {
|
|
137
|
+
async fetch(request, env, ctx) {
|
|
138
|
+
const url = new URL(request.url);
|
|
139
|
+
|
|
140
|
+
// WebSocket IPC handshake
|
|
141
|
+
if (url.pathname === __IPC_PATH && request.headers.get("upgrade") === "websocket") {
|
|
142
|
+
try {
|
|
143
|
+
if (!__userEntry) {
|
|
144
|
+
__userEntry = await __loadEntry(env, __entryPath);
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return new Response("Failed to load entry: " + String(e), { status: 500 });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const pair = new WebSocketPair();
|
|
151
|
+
const client = pair[0];
|
|
152
|
+
const server = pair[1];
|
|
153
|
+
server.accept();
|
|
154
|
+
__serverWs = server;
|
|
155
|
+
|
|
156
|
+
server.addEventListener("message", (event) => {
|
|
157
|
+
__handleWsMessage(env, event.data);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Initialize IPC hooks
|
|
161
|
+
if (!__ipcInitialized && __userEntry.ipc) {
|
|
162
|
+
__ipcInitialized = true;
|
|
163
|
+
if (__userEntry.ipc.onOpen) {
|
|
164
|
+
await __userEntry.ipc.onOpen({ sendMessage: __sendMessage });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!__userEntry) {
|
|
172
|
+
return new Response("Worker not initialized", { status: 503 });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Handle WebSocket upgrade via crossws cloudflare adapter
|
|
176
|
+
if (__userEntry.websocket && request.headers.get("upgrade") === "websocket") {
|
|
177
|
+
const adapter = await __initCrossws(env, __userEntry.websocket);
|
|
178
|
+
return adapter.handleUpgrade(request, env, ctx);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const entryFetch = __userEntry.fetch;
|
|
182
|
+
if (!entryFetch) {
|
|
183
|
+
return new Response("No fetch handler exported", { status: 500 });
|
|
184
|
+
}
|
|
185
|
+
__currentEnv = env;
|
|
186
|
+
try {
|
|
187
|
+
${fetchBody}
|
|
188
|
+
} finally {
|
|
189
|
+
__currentEnv = undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
3
196
|
//#region src/runners/miniflare/runner.ts
|
|
4
|
-
const
|
|
197
|
+
const IPC_PATH = "/__env_runner_ipc";
|
|
198
|
+
const _miniflareCache = /* @__PURE__ */ new Map();
|
|
5
199
|
var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
6
200
|
#miniflare;
|
|
7
201
|
#miniflareOptions;
|
|
202
|
+
#transformRequest;
|
|
203
|
+
#reloadCounter = 0;
|
|
204
|
+
#ws;
|
|
205
|
+
#persistent;
|
|
206
|
+
#cacheKey;
|
|
207
|
+
#exports;
|
|
208
|
+
#captureErrors;
|
|
8
209
|
constructor(opts) {
|
|
9
210
|
super({
|
|
10
211
|
...opts,
|
|
11
212
|
workerEntry: ""
|
|
12
213
|
});
|
|
13
214
|
this.#miniflareOptions = opts.miniflareOptions || {};
|
|
215
|
+
this.#transformRequest = opts.transformRequest;
|
|
216
|
+
this.#persistent = opts.persistent ?? false;
|
|
217
|
+
this.#exports = opts.exports ?? {};
|
|
218
|
+
this.#captureErrors = opts.captureErrors ?? true;
|
|
14
219
|
this.#init();
|
|
15
220
|
}
|
|
221
|
+
/** Dispose all persistent Miniflare instances from the cache. */
|
|
222
|
+
static async disposeAll() {
|
|
223
|
+
const entries = [..._miniflareCache.values()];
|
|
224
|
+
_miniflareCache.clear();
|
|
225
|
+
for (const entry of entries) await entry.mf.dispose().catch(() => {});
|
|
226
|
+
}
|
|
227
|
+
/** Fully dispose the Miniflare instance (even if persistent). */
|
|
228
|
+
async dispose() {
|
|
229
|
+
if (this.#miniflare) {
|
|
230
|
+
if (this.#ws) {
|
|
231
|
+
this.#ws.send(JSON.stringify({ type: "shutdown" }));
|
|
232
|
+
this.#ws.close();
|
|
233
|
+
this.#ws = void 0;
|
|
234
|
+
}
|
|
235
|
+
if (this.#cacheKey) _miniflareCache.delete(this.#cacheKey);
|
|
236
|
+
await this.#miniflare.dispose();
|
|
237
|
+
this.#miniflare = void 0;
|
|
238
|
+
}
|
|
239
|
+
if (!this.closed) await this.close();
|
|
240
|
+
}
|
|
16
241
|
async fetch(input, init) {
|
|
17
242
|
if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
|
|
18
243
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
19
|
-
|
|
244
|
+
const res = await this.#miniflare.dispatchFetch(url, init);
|
|
245
|
+
if (res instanceof Response) return res;
|
|
246
|
+
return new Response(res.body, {
|
|
247
|
+
status: res.status,
|
|
248
|
+
statusText: res.statusText,
|
|
249
|
+
headers: res.headers
|
|
250
|
+
});
|
|
20
251
|
}
|
|
21
252
|
sendMessage(message) {
|
|
22
|
-
if (!this.#
|
|
253
|
+
if (!this.#ws) throw new Error("Miniflare env runner should be initialized before sending messages.");
|
|
23
254
|
if (message?.type === "ping") {
|
|
24
255
|
queueMicrotask(() => this._handleMessage({
|
|
25
256
|
type: "pong",
|
|
@@ -27,11 +258,45 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
27
258
|
}));
|
|
28
259
|
return;
|
|
29
260
|
}
|
|
30
|
-
this.#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
261
|
+
this.#ws.send(JSON.stringify({
|
|
262
|
+
type: "message",
|
|
263
|
+
data: message
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Hot-reload the user entry module without recreating the Miniflare instance.
|
|
268
|
+
*
|
|
269
|
+
* Sends `reload-module` event over the WebSocket. The worker wrapper uses
|
|
270
|
+
* `unsafeEvalBinding` to re-import the entry with a cache-busting query string
|
|
271
|
+
* and responds with `module-reloaded` when done.
|
|
272
|
+
*/
|
|
273
|
+
async reloadModule(timeout = 5e3) {
|
|
274
|
+
if (!this.#ws) throw new Error("Miniflare env runner should be initialized before reloading.");
|
|
275
|
+
if (!this._data?.entry) return;
|
|
276
|
+
this.#reloadCounter++;
|
|
277
|
+
const version = this.#reloadCounter;
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
const timer = setTimeout(() => {
|
|
280
|
+
cleanup();
|
|
281
|
+
reject(/* @__PURE__ */ new Error("Module reload timed out"));
|
|
282
|
+
}, timeout);
|
|
283
|
+
const listener = (msg) => {
|
|
284
|
+
if (msg?.event === "module-reloaded") {
|
|
285
|
+
cleanup();
|
|
286
|
+
if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
|
|
287
|
+
else resolve();
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
const cleanup = () => {
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
this.offMessage(listener);
|
|
293
|
+
};
|
|
294
|
+
this.onMessage(listener);
|
|
295
|
+
this.#ws.send(JSON.stringify({
|
|
296
|
+
type: "reload",
|
|
297
|
+
version
|
|
298
|
+
}));
|
|
299
|
+
});
|
|
35
300
|
}
|
|
36
301
|
_hasRuntime() {
|
|
37
302
|
return Boolean(this.#miniflare);
|
|
@@ -41,11 +306,21 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
41
306
|
}
|
|
42
307
|
async _closeRuntime() {
|
|
43
308
|
if (!this.#miniflare) return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
309
|
+
if (this.#ws) {
|
|
310
|
+
this.#ws.send(JSON.stringify({ type: "shutdown" }));
|
|
311
|
+
this.#ws.close();
|
|
312
|
+
this.#ws = void 0;
|
|
313
|
+
}
|
|
314
|
+
if (this.#persistent && this.#cacheKey) {
|
|
315
|
+
const cached = _miniflareCache.get(this.#cacheKey);
|
|
316
|
+
if (cached) {
|
|
317
|
+
cached.refCount--;
|
|
318
|
+
if (cached.refCount <= 0) {
|
|
319
|
+
_miniflareCache.delete(this.#cacheKey);
|
|
320
|
+
await this.#miniflare.dispose();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} else await this.#miniflare.dispose();
|
|
49
324
|
this.#miniflare = void 0;
|
|
50
325
|
}
|
|
51
326
|
#init() {
|
|
@@ -54,32 +329,180 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
54
329
|
this.close(error);
|
|
55
330
|
});
|
|
56
331
|
}
|
|
332
|
+
async upgrade(context) {
|
|
333
|
+
if (!this.#miniflare || this.closed) {
|
|
334
|
+
context.node.socket.destroy();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const mfUrl = await this.#miniflare.unsafeGetDirectURL();
|
|
338
|
+
const address = new URL(mfUrl);
|
|
339
|
+
await proxyUpgrade({
|
|
340
|
+
host: address.hostname,
|
|
341
|
+
port: Number(address.port)
|
|
342
|
+
}, context.node.req, context.node.socket, context.node.head);
|
|
343
|
+
}
|
|
57
344
|
async #initAsync() {
|
|
58
345
|
const { Miniflare } = await import("miniflare");
|
|
59
346
|
const entryPath = this._data?.entry;
|
|
347
|
+
const userFlags = this.#miniflareOptions.compatibilityFlags || [];
|
|
348
|
+
const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
|
|
60
349
|
const options = {
|
|
61
350
|
compatibilityDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
62
351
|
modules: true,
|
|
63
|
-
...this.#miniflareOptions
|
|
352
|
+
...this.#miniflareOptions,
|
|
353
|
+
compatibilityFlags: [...new Set(["nodejs_compat", ...userFlags])],
|
|
354
|
+
unsafeDirectSockets: [{
|
|
355
|
+
host: "127.0.0.1",
|
|
356
|
+
port: 0
|
|
357
|
+
}, ...userDirectSockets]
|
|
64
358
|
};
|
|
65
|
-
options.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
359
|
+
if (entryPath && !options.script && !options.scriptPath) {
|
|
360
|
+
const resolvedEntry = resolve(entryPath);
|
|
361
|
+
const entryDir = dirname(resolvedEntry);
|
|
362
|
+
const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(resolvedEntry, typeof this.#exports === "object" ? this.#exports : {});
|
|
363
|
+
if (detectedExports.length > 0 && !options.durableObjects) {
|
|
364
|
+
const autoDOs = { ...this.#miniflareOptions.durableObjects || {} };
|
|
365
|
+
for (const name of detectedExports) {
|
|
366
|
+
const bindingName = toScreamingSnakeCase(name);
|
|
367
|
+
if (!autoDOs[bindingName]) autoDOs[bindingName] = name;
|
|
368
|
+
}
|
|
369
|
+
options.durableObjects = autoDOs;
|
|
71
370
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
371
|
+
options.script = generateWrapper(resolvedEntry, {
|
|
372
|
+
dynamicOnly: true,
|
|
373
|
+
captureErrors: this.#captureErrors,
|
|
374
|
+
exports: detectedExports
|
|
375
|
+
});
|
|
376
|
+
options.scriptPath = entryDir + "/__env_runner_wrapper.mjs";
|
|
377
|
+
if (!options.modulesRoot) options.modulesRoot = "/";
|
|
378
|
+
options.unsafeEvalBinding = "__ENV_RUNNER_UNSAFE_EVAL__";
|
|
379
|
+
options.serviceBindings = {
|
|
380
|
+
...options.serviceBindings || {},
|
|
381
|
+
[IPC_BINDING]: async (request) => {
|
|
382
|
+
try {
|
|
383
|
+
const message = await request.json();
|
|
384
|
+
this._handleMessage(message);
|
|
385
|
+
} catch {}
|
|
386
|
+
return new Response(null, { status: 204 });
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
if (this.#transformRequest && !options.modulesRules) options.modulesRules = [{
|
|
390
|
+
type: "ESModule",
|
|
391
|
+
include: [
|
|
392
|
+
"**/*.ts",
|
|
393
|
+
"**/*.tsx",
|
|
394
|
+
"**/*.jsx",
|
|
395
|
+
"**/*.mts"
|
|
396
|
+
]
|
|
397
|
+
}];
|
|
398
|
+
if (!options.unsafeModuleFallbackService) {
|
|
399
|
+
const _require = createRequire(resolvedEntry);
|
|
400
|
+
const _transformRequest = this.#transformRequest;
|
|
401
|
+
options.unsafeUseModuleFallbackService = true;
|
|
402
|
+
const modulePathMap = /* @__PURE__ */ new Map();
|
|
403
|
+
options.unsafeModuleFallbackService = async (request) => {
|
|
404
|
+
const url = new URL(request.url);
|
|
405
|
+
const specifier = url.searchParams.get("specifier");
|
|
406
|
+
const rawSpecifier = url.searchParams.get("rawSpecifier");
|
|
407
|
+
const referrer = url.searchParams.get("referrer") || "";
|
|
408
|
+
if (!specifier) return new Response(null, { status: 404 });
|
|
409
|
+
const cleanSpecifier = specifier.split("?")[0] || specifier;
|
|
410
|
+
const cleanRaw = rawSpecifier?.split("?")[0];
|
|
411
|
+
let resolvedPath;
|
|
412
|
+
const fileUrlRaw = cleanRaw || cleanSpecifier;
|
|
413
|
+
if (fileUrlRaw.startsWith("file://")) try {
|
|
414
|
+
resolvedPath = fileURLToPath(fileUrlRaw);
|
|
415
|
+
} catch {
|
|
416
|
+
return new Response(null, { status: 404 });
|
|
417
|
+
}
|
|
418
|
+
else if (cleanRaw && !cleanRaw.startsWith(".") && !cleanRaw.startsWith("/")) {
|
|
419
|
+
const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
|
|
420
|
+
const referrerReal = modulePathMap.get(referrerKey);
|
|
421
|
+
const contextRequire = referrerReal ? createRequire(referrerReal) : _require;
|
|
422
|
+
if (cleanRaw.startsWith("node:")) {
|
|
423
|
+
const nodeName = cleanRaw.slice(5);
|
|
424
|
+
try {
|
|
425
|
+
resolvedPath = contextRequire.resolve(`unenv/node/${nodeName}`);
|
|
426
|
+
} catch {
|
|
427
|
+
return new Response(null, { status: 404 });
|
|
428
|
+
}
|
|
429
|
+
} else try {
|
|
430
|
+
resolvedPath = contextRequire.resolve(cleanRaw);
|
|
431
|
+
} catch {
|
|
432
|
+
const name = cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier;
|
|
433
|
+
return Response.json({
|
|
434
|
+
name,
|
|
435
|
+
esModule: "export default undefined;"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
const referrerKey = referrer.startsWith("/") ? referrer.slice(1) : referrer;
|
|
440
|
+
const referrerDir = dirname(modulePathMap.get(referrerKey) || (referrer.startsWith("/") ? referrer : "/" + referrer));
|
|
441
|
+
const raw = cleanRaw || cleanSpecifier;
|
|
442
|
+
if (raw.startsWith(".")) resolvedPath = resolve(referrerDir, raw);
|
|
443
|
+
else if (cleanSpecifier.startsWith("/")) resolvedPath = cleanSpecifier;
|
|
444
|
+
else try {
|
|
445
|
+
resolvedPath = _require.resolve(raw);
|
|
446
|
+
} catch {
|
|
447
|
+
return new Response(null, { status: 404 });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const rawQuery = specifier.includes("?") ? specifier.slice(specifier.indexOf("?")) : "";
|
|
451
|
+
const name = (cleanSpecifier.startsWith("/") ? cleanSpecifier.slice(1) : cleanSpecifier) + rawQuery;
|
|
452
|
+
if (_transformRequest) try {
|
|
453
|
+
const result = await _transformRequest(resolvedPath);
|
|
454
|
+
if (result?.code) {
|
|
455
|
+
modulePathMap.set(name, resolvedPath);
|
|
456
|
+
return Response.json({
|
|
457
|
+
name,
|
|
458
|
+
esModule: result.code
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
} catch {}
|
|
462
|
+
try {
|
|
463
|
+
const contents = readFileSync(resolvedPath, "utf8");
|
|
464
|
+
modulePathMap.set(name, resolvedPath);
|
|
465
|
+
const isESM = resolvedPath.endsWith(".mjs") || !resolvedPath.endsWith(".cjs") && /\b(import\s|import\(|export\s|export\{|import\.meta\b)/.test(contents);
|
|
466
|
+
return Response.json({
|
|
467
|
+
name,
|
|
468
|
+
...isESM ? { esModule: contents } : { commonJsModule: contents }
|
|
469
|
+
});
|
|
470
|
+
} catch {
|
|
471
|
+
return new Response(null, { status: 404 });
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (this.#persistent && entryPath) {
|
|
477
|
+
this.#cacheKey = computeCacheKey(entryPath, this.#miniflareOptions);
|
|
478
|
+
const cached = _miniflareCache.get(this.#cacheKey);
|
|
479
|
+
if (cached) {
|
|
480
|
+
this.#miniflare = cached.mf;
|
|
481
|
+
cached.refCount++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!this.#miniflare) {
|
|
485
|
+
this.#miniflare = new Miniflare(options);
|
|
486
|
+
await this.#miniflare.ready;
|
|
487
|
+
if (this.#persistent && this.#cacheKey) _miniflareCache.set(this.#cacheKey, {
|
|
488
|
+
mf: this.#miniflare,
|
|
489
|
+
refCount: 1
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
const initRes = await this.#miniflare.dispatchFetch("http://localhost" + IPC_PATH, { headers: { upgrade: "websocket" } });
|
|
493
|
+
const ws = initRes.webSocket;
|
|
494
|
+
if (!ws) {
|
|
495
|
+
const body = await initRes.text().catch(() => "");
|
|
496
|
+
throw new Error(`Failed to establish WebSocket IPC channel (${initRes.status}: ${body})`);
|
|
76
497
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
498
|
+
ws.accept();
|
|
499
|
+
this.#ws = ws;
|
|
500
|
+
ws.addEventListener("message", (event) => {
|
|
501
|
+
try {
|
|
502
|
+
const parsed = JSON.parse(event.data);
|
|
503
|
+
this._handleMessage(parsed);
|
|
504
|
+
} catch {}
|
|
505
|
+
});
|
|
83
506
|
this._handleMessage({ address: {
|
|
84
507
|
host: "127.0.0.1",
|
|
85
508
|
port: 0
|
|
@@ -87,60 +510,28 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
87
510
|
}
|
|
88
511
|
};
|
|
89
512
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* The user module is expected to `export default { fetch, ipc? }`.
|
|
93
|
-
* The wrapper renames the user's default export, intercepts IPC requests,
|
|
94
|
-
* and bridges the `ipc` hooks via `env.__ENV_RUNNER_IPC` service binding.
|
|
513
|
+
* Detect `export class` declarations in the entry file.
|
|
514
|
+
* Merges with explicitly declared exports from options.
|
|
95
515
|
*/
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
};
|
|
117
|
-
if (__userEntry.ipc.onOpen) {
|
|
118
|
-
await __userEntry.ipc.onOpen({ sendMessage });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return new Response(null, { status: 204 });
|
|
122
|
-
}
|
|
123
|
-
if (ipcType === "message") {
|
|
124
|
-
const message = await request.json();
|
|
125
|
-
if (__userEntry.ipc?.onMessage) {
|
|
126
|
-
__userEntry.ipc.onMessage(message);
|
|
127
|
-
}
|
|
128
|
-
return new Response(null, { status: 204 });
|
|
129
|
-
}
|
|
130
|
-
if (ipcType === "shutdown") {
|
|
131
|
-
if (__userEntry.ipc?.onClose) {
|
|
132
|
-
await __userEntry.ipc.onClose();
|
|
133
|
-
}
|
|
134
|
-
return new Response(null, { status: 204 });
|
|
135
|
-
}
|
|
136
|
-
return new Response(null, { status: 400 });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Delegate to user's fetch handler
|
|
140
|
-
return __userEntry.fetch(request, env, ctx);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
`;
|
|
516
|
+
function detectExportedClasses(entryPath, explicit) {
|
|
517
|
+
const names = new Set(Object.keys(explicit));
|
|
518
|
+
try {
|
|
519
|
+
const source = readFileSync(entryPath, "utf8");
|
|
520
|
+
const re = /\bexport\s+class\s+(\w+)/g;
|
|
521
|
+
let match;
|
|
522
|
+
while (match = re.exec(source)) if (match[1]) names.add(match[1]);
|
|
523
|
+
} catch {}
|
|
524
|
+
return [...names];
|
|
525
|
+
}
|
|
526
|
+
/** Convert PascalCase/camelCase to SCREAMING_SNAKE_CASE (e.g. `Counter` → `COUNTER`, `MyDurableObject` → `MY_DURABLE_OBJECT`). */
|
|
527
|
+
function toScreamingSnakeCase(name) {
|
|
528
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
529
|
+
}
|
|
530
|
+
/** Compute a stable cache key for persistent Miniflare instances. */
|
|
531
|
+
function computeCacheKey(entryPath, opts) {
|
|
532
|
+
const serializableOpts = {};
|
|
533
|
+
for (const [k, v] of Object.entries(opts)) if (typeof v !== "function") serializableOpts[k] = v;
|
|
534
|
+
return `${resolve(entryPath)}::${JSON.stringify(serializableOpts)}`;
|
|
144
535
|
}
|
|
145
536
|
//#endregion
|
|
146
537
|
export { MiniflareEnvRunner as t };
|
package/dist/_chunks/server.mjs
CHANGED
|
@@ -51,6 +51,54 @@ var RunnerManager = class {
|
|
|
51
51
|
this._messageListeners.delete(listener);
|
|
52
52
|
this._runner?.offMessage(listener);
|
|
53
53
|
}
|
|
54
|
+
waitForReady(timeout = 5e3) {
|
|
55
|
+
if (this.ready) return Promise.resolve();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
this._messageListeners.delete(listener);
|
|
59
|
+
reject(/* @__PURE__ */ new Error("Runner did not become ready in time"));
|
|
60
|
+
}, timeout);
|
|
61
|
+
const listener = (message) => {
|
|
62
|
+
if (message?.address || this.ready) {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
this._messageListeners.delete(listener);
|
|
65
|
+
resolve();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
this._messageListeners.add(listener);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
rpc(name, data, opts) {
|
|
72
|
+
const id = Math.random().toString(36).slice(2);
|
|
73
|
+
const timeout = opts?.timeout ?? 3e3;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
cleanup();
|
|
77
|
+
reject(/* @__PURE__ */ new Error(`RPC "${name}" timed out`));
|
|
78
|
+
}, timeout);
|
|
79
|
+
const listener = (msg) => {
|
|
80
|
+
if (msg?.__rpc_id === id) {
|
|
81
|
+
cleanup();
|
|
82
|
+
if (msg.error) reject(typeof msg.error === "string" ? new Error(msg.error) : msg.error);
|
|
83
|
+
else resolve(msg.data);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const cleanup = () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
this.offMessage(listener);
|
|
89
|
+
};
|
|
90
|
+
this.onMessage(listener);
|
|
91
|
+
this.sendMessage({
|
|
92
|
+
__rpc: name,
|
|
93
|
+
__rpc_id: id,
|
|
94
|
+
data
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async reloadModule(timeout) {
|
|
99
|
+
if (!this._runner?.reloadModule) throw new Error("Active runner does not support reloadModule()");
|
|
100
|
+
return this._runner.reloadModule(timeout);
|
|
101
|
+
}
|
|
54
102
|
async close() {
|
|
55
103
|
this._closed = true;
|
|
56
104
|
this._messageQueue.length = 0;
|