oxidejs 0.3.1 → 0.3.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.
@@ -1,48 +1,10 @@
1
- import { t as runWithRequest } from "./context-zrTZyYpF.mjs";
1
+ import { c as withRequestEntry, r as runWithRequest } from "./context-DUN_VWJF.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { Layer } from "effect";
5
- import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
6
5
  import { HttpRouter } from "effect/unstable/http";
7
- //#region \0rolldown/runtime.js
8
- var __defProp = Object.defineProperty;
9
- var __exportAll = (all, no_symbols) => {
10
- let target = {};
11
- for (var name in all) __defProp(target, name, {
12
- get: all[name],
13
- enumerable: true
14
- });
15
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
- return target;
17
- };
18
- //#endregion
6
+ import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
19
7
  //#region src/actions.ts
20
- var actions_exports = /* @__PURE__ */ __exportAll({
21
- ACTION_PATH: () => ACTION_PATH,
22
- RESOLVED_VIRTUAL_ACTIONS_ID: () => RESOLVED_VIRTUAL_ACTIONS_ID,
23
- RESOLVED_VIRTUAL_CLIENT_ID: () => RESOLVED_VIRTUAL_CLIENT_ID,
24
- RESOLVED_VIRTUAL_WORKER_ID: () => RESOLVED_VIRTUAL_WORKER_ID,
25
- RequestBodyTooLargeError: () => RequestBodyTooLargeError,
26
- VIRTUAL_ACTIONS_ID: () => VIRTUAL_ACTIONS_ID,
27
- VIRTUAL_CLIENT_ID: () => VIRTUAL_CLIENT_ID,
28
- VIRTUAL_WORKER_ID: () => VIRTUAL_WORKER_ID,
29
- generateActionsClientModule: () => generateActionsClientModule,
30
- generateActionsModule: () => generateActionsModule,
31
- generateClientModule: () => generateClientModule,
32
- generateClientStub: () => generateClientStub,
33
- generateWorkerWrapper: () => generateWorkerWrapper,
34
- isServerFileId: () => isServerFileId,
35
- loadClientStub: () => loadClientStub,
36
- matchesActionPath: () => matchesActionPath,
37
- moduleKey: () => moduleKey,
38
- nodeToWebRequest: () => nodeToWebRequest,
39
- parseExportedNames: () => parseExportedNames,
40
- parseStreamExports: () => parseStreamExports,
41
- pluginShouldStub: () => pluginShouldStub,
42
- scanServerFiles: () => scanServerFiles,
43
- sendWebResponseFrom: () => sendWebResponseFrom,
44
- shouldStubServerModule: () => shouldStubServerModule
45
- });
46
8
  const VIRTUAL_ACTIONS_ID = "virtual:oxide/actions";
47
9
  const RESOLVED_VIRTUAL_ACTIONS_ID = `\0${VIRTUAL_ACTIONS_ID}`;
48
10
  const VIRTUAL_WORKER_ID = "virtual:oxide/worker";
@@ -51,9 +13,9 @@ const VIRTUAL_CLIENT_ID = "virtual:oxide/client";
51
13
  const RESOLVED_VIRTUAL_CLIENT_ID = `\0${VIRTUAL_CLIENT_ID}`;
52
14
  const ACTION_PATH = "/__oxide/action";
53
15
  /** Match the action endpoint with or without a trailing slash (Effect RPC posts to `path/`). */
54
- function matchesActionPath(pathname, path = ACTION_PATH) {
55
- return pathname === path || pathname === `${path}/`;
56
- }
16
+ const matchesActionPath = function matchesActionPath(pathname, actionPath = ACTION_PATH) {
17
+ return pathname === actionPath || pathname === `${actionPath}/`;
18
+ };
57
19
  const IGNORE_DIRS = /* @__PURE__ */ new Set([
58
20
  "node_modules",
59
21
  "dist",
@@ -61,37 +23,37 @@ const IGNORE_DIRS = /* @__PURE__ */ new Set([
61
23
  ".wrangler"
62
24
  ]);
63
25
  /** Only `export const name = action(...)` become remote RPC actions. Everything else stays server-local. */
64
- const EXPORT_RE = /^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?action\s*\(/gm;
65
- const STREAM_EXPORT_RE = /^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*action\s*\(\s*async\s+function\s*\*/gm;
66
- function isServerFileId(id) {
67
- const file = id.split("?")[0]?.replace(/\\/g, "/") ?? "";
26
+ const EXPORT_RE = /^\s*export\s+const\s+(?<exportName>[A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?action\s*\(/gmu;
27
+ const STREAM_EXPORT_RE = /^\s*export\s+const\s+(?<exportName>[A-Za-z_$][\w$]*)\s*=\s*action\s*\(\s*async\s+function\s*\*/gmu;
28
+ const isServerFileId = function isServerFileId(id) {
29
+ const file = id.split("?")[0]?.replaceAll("\\", "/") ?? "";
68
30
  return [
69
31
  ".ts",
70
32
  ".tsx",
71
33
  ".js",
72
34
  ".jsx"
73
35
  ].some((ext) => file.endsWith(`.server${ext}`));
74
- }
75
- function moduleKey(absFile) {
76
- return path.basename(absFile).replace(/\.server\.(?:[jt]sx?)$/i, "");
77
- }
78
- function parseExportedNames(source) {
36
+ };
37
+ const moduleKey = function moduleKey(absFile) {
38
+ return path.basename(absFile).replace(/\.server\.(?:[jt]sx?)$/iu, "");
39
+ };
40
+ const parseExportedNames = function parseExportedNames(source) {
79
41
  const names = /* @__PURE__ */ new Set();
80
42
  for (const match of source.matchAll(EXPORT_RE)) {
81
- const name = match[1];
43
+ const [, name] = match;
82
44
  if (name) names.add(name);
83
45
  }
84
46
  return [...names];
85
- }
86
- function parseStreamExports(source) {
47
+ };
48
+ const parseStreamExports = function parseStreamExports(source) {
87
49
  const names = /* @__PURE__ */ new Set();
88
50
  for (const match of source.matchAll(STREAM_EXPORT_RE)) {
89
- const name = match[1];
51
+ const [, name] = match;
90
52
  if (name) names.add(name);
91
53
  }
92
54
  return [...names];
93
- }
94
- function scanServerFiles(root) {
55
+ };
56
+ const scanServerFiles = function scanServerFiles(root) {
95
57
  const files = [];
96
58
  const walk = (dir) => {
97
59
  let entries;
@@ -114,39 +76,40 @@ function scanServerFiles(root) {
114
76
  walk(root);
115
77
  const byKey = /* @__PURE__ */ new Map();
116
78
  const modules = [];
117
- for (const abs of files.sort()) {
79
+ const sortedFiles = files.toSorted();
80
+ for (const abs of sortedFiles) {
118
81
  const key = moduleKey(abs);
119
82
  if (!key) throw new Error(`oxidejs: invalid server module name: ${abs}`);
120
83
  const existing = byKey.get(key);
121
84
  if (existing) throw new Error(`oxidejs: duplicate server module key "${key}": ${existing} and ${abs}`);
122
85
  byKey.set(key, abs);
123
- const source = fs.readFileSync(abs, "utf8");
86
+ const source = fs.readFileSync(abs, "utf-8");
124
87
  modules.push({
125
88
  abs,
126
- key,
127
89
  exports: parseExportedNames(source),
90
+ key,
128
91
  streams: parseStreamExports(source)
129
92
  });
130
93
  }
131
94
  return modules;
132
- }
133
- function generateClientModule(transport = "http", headers, path = ACTION_PATH) {
134
- const opts = { url: path };
95
+ };
96
+ const generateClientModule = function generateClientModule(transport = "http", headers, actionPath = ACTION_PATH) {
97
+ const opts = { url: actionPath };
135
98
  if (transport === "ws") opts.transport = "ws";
136
99
  if (headers) opts.headers = headers;
137
100
  if (transport === "ws") return `import { createClient } from "oxidejs/rpc/client";
138
101
  import { actionsGroup } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
139
102
  const __proto = typeof location === "undefined" ? "ws:" : location.protocol === "https:" ? "wss:" : "ws:";
140
103
  const __host = typeof location === "undefined" ? "localhost" : location.host;
141
- export const client = createClient(actionsGroup, { ...${JSON.stringify(opts)}, url: __proto + "//" + __host + ${JSON.stringify(path)} });
104
+ export const client = createClient(actionsGroup, { ...${JSON.stringify(opts)}, url: __proto + "//" + __host + ${JSON.stringify(actionPath)} });
142
105
  `;
143
106
  return `import { createClient } from "oxidejs/rpc/client";
144
107
  import { actionsGroup } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
145
108
  export const client = createClient(actionsGroup, ${JSON.stringify(opts)});
146
109
  `;
147
- }
148
- function generateClientStub(mod) {
149
- const streams = new Set(mod.streams ?? []);
110
+ };
111
+ const generateClientStub = function generateClientStub(mod) {
112
+ const streams = new Set(mod.streams);
150
113
  const lines = [
151
114
  `// oxidejs:client-stub`,
152
115
  `import { wrapClientRpc, wrapClientStreamRpc } from "oxidejs";`,
@@ -165,40 +128,30 @@ function generateClientStub(mod) {
165
128
  else lines.push(`export const ${name} = wrapClientRpc(${peel});`);
166
129
  }
167
130
  return `${lines.join("\n")}\n`;
168
- }
169
- function generateActionsClientModule(modules) {
131
+ };
132
+ const generateActionsClientModule = function generateActionsClientModule(modules) {
170
133
  const lines = [`import { Schema } from "effect";`, `import { Rpc, RpcGroup } from "effect/unstable/rpc";`];
171
134
  const rpcNames = [];
172
- modules.forEach((mod, i) => {
173
- for (const name of mod.exports) {
174
- const rpc = `__rpc_${i}_${name}`;
175
- rpcNames.push(rpc);
176
- const tag = `${mod.key}.${name}`;
177
- const stream = mod.streams?.includes(name) ?? false;
178
- lines.push(`const ${rpc} = Rpc.make(${JSON.stringify(tag)}, { payload: Schema.Struct({ args: Schema.Array(Schema.Unknown) }), success: Schema.Unknown${stream ? ", stream: true" : ""} });`);
179
- }
180
- });
181
- lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`);
182
- lines.push(`export default actionsGroup;`);
183
- lines.push(`export { actionsGroup as actions };`);
135
+ for (const [i, mod] of modules.entries()) for (const name of mod.exports) {
136
+ const rpc = `__rpc_${i}_${name}`;
137
+ rpcNames.push(rpc);
138
+ const tag = `${mod.key}.${name}`;
139
+ const stream = mod.streams?.includes(name) ?? false;
140
+ lines.push(`const ${rpc} = Rpc.make(${JSON.stringify(tag)}, { payload: Schema.Struct({ args: Schema.Array(Schema.Unknown) }), success: Schema.Unknown${stream ? ", stream: true" : ""} });`);
141
+ }
142
+ lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`, `export default actionsGroup;`, `export { actionsGroup as actions };`);
184
143
  return `${lines.join("\n")}\n`;
185
- }
186
- function generateActionsModule(modules, opts) {
144
+ };
145
+ const generateActionsModule = function generateActionsModule(modules, opts) {
187
146
  const lines = [
188
147
  `import { Effect } from "effect";`,
189
148
  `import { Schema } from "effect";`,
190
149
  `import { Rpc, RpcGroup } from "effect/unstable/rpc";`,
191
- `import { AsyncLocalStorage } from "node:async_hooks";`,
150
+ `import { getRequestStore, withRequestStore } from "oxidejs";`,
192
151
  `import { asyncGenToStreamInContext } from "oxidejs/rpc";`,
193
- `const __alsKey = Symbol.for("oxidejs.requestContext");`,
194
- `const __als = globalThis[__alsKey] ??= new AsyncLocalStorage();`,
195
- `const __store = () => {`,
196
- ` const ctx = __als.getStore();`,
197
- ` if (!ctx) throw new Error("oxidejs: request context is unavailable");`,
198
- ` return ctx;`,
199
- `};`,
200
- `const __run = (fn) =>`,
201
- ` Effect.promise(() => __als.run(__store(), fn)).pipe(`,
152
+ `const __run = (fn) => {`,
153
+ ` const __s = getRequestStore();`,
154
+ ` return Effect.promise(() => withRequestStore(__s, fn)).pipe(`,
202
155
  ` Effect.map((value) => {`,
203
156
  ` if (value instanceof Response) {`,
204
157
  ` console.error("oxidejs: action() returned a Response; actions must return serializable data. Return a Response from src/server.ts for raw HTTP responses.");`,
@@ -207,7 +160,8 @@ function generateActionsModule(modules, opts) {
207
160
  ` return value === undefined ? null : value;`,
208
161
  ` }),`,
209
162
  ` );`,
210
- `const __withStore = (store, fn) => __als.run(store, fn);`
163
+ `};`,
164
+ `const __withStore = (store, fn) => withRequestStore(store, fn);`
211
165
  ];
212
166
  const rpcNames = [];
213
167
  const aliases = modules.map((mod, i) => {
@@ -226,60 +180,47 @@ function generateActionsModule(modules, opts) {
226
180
  mod
227
181
  };
228
182
  });
229
- lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`);
230
- lines.push(`export const actionsHandlers = actionsGroup.toLayer({`);
183
+ lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`, `export const actionsHandlers = actionsGroup.toLayer({`);
231
184
  for (const { alias, mod } of aliases) for (const name of mod.exports) {
232
185
  const tag = `${mod.key}.${name}`;
233
186
  const stream = mod.streams?.includes(name) ?? false;
234
- lines.push(stream ? ` ${JSON.stringify(tag)}: ({ args }) => { const __s = __store(); return asyncGenToStreamInContext(() => ${alias}[${JSON.stringify(name)}].apply(null, args), (fn) => __withStore(__s, fn)); },` : ` ${JSON.stringify(tag)}: ({ args }) => __run(() => ${alias}[${JSON.stringify(name)}].apply(null, args)),`);
187
+ lines.push(stream ? ` ${JSON.stringify(tag)}: ({ args }) => { const __s = getRequestStore(); return asyncGenToStreamInContext(() => ${alias}[${JSON.stringify(name)}].apply(null, args), (fn) => __withStore(__s, fn)); },` : ` ${JSON.stringify(tag)}: ({ args }) => __run(() => ${alias}[${JSON.stringify(name)}].apply(null, args)),`);
235
188
  }
236
- lines.push(`});`);
237
- lines.push(`export default actionsGroup;`);
238
- lines.push(`export { actionsGroup as actions };`);
189
+ lines.push(`});`, `export default actionsGroup;`, `export { actionsGroup as actions };`);
239
190
  return `${lines.join("\n")}\n`;
240
- }
241
- function pipeResponse(req, res, response) {
242
- return new Promise((resolve, reject) => {
243
- res.statusCode = response.status;
244
- response.headers.forEach((value, key) => {
245
- res.setHeader(key, value);
246
- });
247
- if (!response.body) {
248
- res.end();
249
- resolve();
250
- return;
191
+ };
192
+ const pipeResponse = async function pipeResponse(req, res, response) {
193
+ res.statusCode = response.status;
194
+ for (const [key, value] of response.headers) res.setHeader(key, value);
195
+ if (!response.body) {
196
+ res.end();
197
+ return;
198
+ }
199
+ const reader = response.body.getReader();
200
+ const abort = () => {
201
+ reader.cancel();
202
+ };
203
+ req.once("aborted", abort);
204
+ try {
205
+ for (;;) {
206
+ const { done, value } = await reader.read();
207
+ if (done) {
208
+ res.end();
209
+ return;
210
+ }
211
+ if (value) res.write(value);
251
212
  }
252
- const reader = response.body.getReader();
253
- const abort = () => {
254
- reader.cancel();
255
- };
256
- req.once("aborted", abort);
257
- const pull = () => {
258
- reader.read().then(({ done, value }) => {
259
- if (done) {
260
- req.off("aborted", abort);
261
- res.end();
262
- resolve();
263
- return;
264
- }
265
- if (value) res.write(value);
266
- pull();
267
- }, reject);
268
- };
269
- pull();
270
- });
271
- }
272
- function generateWorkerWrapper(userWorkerAbs, opts = {}) {
273
- const preset = opts.preset ?? "fetch";
274
- const clientDir = opts.clientDir ?? "client";
275
- const actionPath = opts.actionPath ?? "/__oxide/action";
276
- const sameOrigin = opts.actionSameOrigin ?? false;
277
- const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
278
- const hasActions = opts.hasActions !== false;
279
- const ws = hasActions && opts.actions === "ws";
280
- const bodyLimit = opts.bodyLimit ?? 1048576;
281
- const nfBlock = `const __nf = ${`() => new Response(${JSON.stringify(opts.notFound ?? "<h1>404 Not Found</h1>")}, { status: 404, headers: { "content-type": "text/html; charset=utf-8" } })`};\n`;
282
- const assetBlock = serveAssets ? `import { readFile } from "node:fs/promises";
213
+ } finally {
214
+ req.off("aborted", abort);
215
+ }
216
+ };
217
+ const isMiddlewareSpecifier = function isMiddlewareSpecifier(entry) {
218
+ return typeof entry === "string";
219
+ };
220
+ const EMPTY_MIDDLEWARE_IMPORTS = [];
221
+ const buildAssetBlock = function buildAssetBlock(serveAssets, clientDir) {
222
+ if (!serveAssets) return "";
223
+ return `import { readFile } from "node:fs/promises";
283
224
  import { extname, join } from "node:path";
284
225
  const __assets = join(import.meta.dirname, ${JSON.stringify(clientDir)});
285
226
  const __types = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".ico": "image/x-icon", ".woff2": "font/woff2", ".webp": "image/webp" };
@@ -321,23 +262,29 @@ async function __asset(request, spa) {
321
262
  return;
322
263
  }
323
264
  }
324
- ` : "";
325
- const envJson = JSON.stringify(opts.env ?? {});
265
+ `;
266
+ };
267
+ const buildAfterAction = function buildAfterAction(serveAssets, preset, envJson) {
326
268
  const celldAfterAction = `{
327
- const hit = typeof user.fetch === "function" ? await user.fetch(request, env ?? ${envJson}, ctx) : undefined;
269
+ const hit = __userFetch ? await __userFetch(request, env ?? ${envJson}, ctx) : undefined;
328
270
  if (hit) return hit;
329
271
  const assets = env?.ASSETS;
330
272
  if (assets && typeof assets.fetch === "function") return assets.fetch(request);
331
273
  return __nf();
332
274
  }`;
333
- const afterAction = serveAssets ? `if (typeof user.fetch === "function") {
334
- const hit = await user.fetch(request, env ?? ${envJson}, ctx);
275
+ if (serveAssets) return `if (__userFetch) {
276
+ const hit = await __userFetch(request, env ?? ${envJson}, ctx);
335
277
  if (hit) return hit;
336
278
  }
337
- return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ?? __nf();` : preset === "celld" ? celldAfterAction : `return typeof user.fetch === "function"
338
- ? user.fetch(request, env, ctx)
279
+ return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ?? __nf();`;
280
+ if (preset === "celld") return celldAfterAction;
281
+ return `return __userFetch
282
+ ? __userFetch(request, env, ctx)
339
283
  : __nf();`;
340
- const listen = preset === "fetch" ? `
284
+ };
285
+ const buildListenBlock = function buildListenBlock(preset, bodyLimit, ws) {
286
+ if (preset !== "fetch") return "";
287
+ return `
341
288
  import { createServer } from "node:http";
342
289
  import { pathToFileURL } from "node:url";
343
290
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
@@ -383,42 +330,92 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
383
330
  });
384
331
  });` : ""}
385
332
  server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
386
- }
387
333
  for (const signal of ["SIGTERM", "SIGINT"]) {
388
334
  process.on(signal, () => {
389
335
  server.close(() => process.exit(0));
390
336
  setTimeout(() => process.exit(0), 5000).unref();
391
337
  });
392
338
  }
393
- ` : "";
394
- const actionImports = hasActions ? ws ? `import { createWsHooks } from "oxidejs/rpc";
339
+ }
340
+ `;
341
+ };
342
+ const buildActionImports = function buildActionImports(hasActions, ws, actionPath, sameOrigin) {
343
+ if (!hasActions) return "";
344
+ if (ws) return `import { createWsHooks } from "oxidejs/rpc";
395
345
  import { actionsGroup, actionsHandlers } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
396
346
  const __ws = createWsHooks(actionsGroup, actionsHandlers, { path: ${JSON.stringify(actionPath)}, sameOrigin: ${sameOrigin} });
397
- ` : `import { createActionHandler } from "oxidejs/rpc";
347
+ `;
348
+ return `import { createActionHandler } from "oxidejs/rpc";
398
349
  import { actionsGroup, actionsHandlers } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
399
350
  const __fetch = Symbol.for("oxidejs.fetch");
400
351
  const __rpc = createActionHandler(actionsGroup, actionsHandlers, { path: ${JSON.stringify(actionPath)}, sameOrigin: ${sameOrigin}, createContext: (req) => req[__fetch] ?? {} });
401
- ` : "";
402
- const actionMatchFn = `const __actionMatch = (p) => p === ${JSON.stringify(actionPath)} || p === ${JSON.stringify(`${actionPath}/`)};`;
403
- const actionGate = hasActions && !ws ? `if ((__actionMatch)(new URL(request.url).pathname)) {
352
+ `;
353
+ };
354
+ const normalizeMiddlewareEntries = function normalizeMiddlewareEntries(middleware) {
355
+ const ILHA_SSR_IMPLICIT = ["ilha:pages/server", "ilha:loaders"];
356
+ return middleware.map((m) => {
357
+ if (isMiddlewareSpecifier(m)) return {
358
+ imports: m === "@ilha/router/ssr" ? ILHA_SSR_IMPLICIT : EMPTY_MIDDLEWARE_IMPORTS,
359
+ module: m
360
+ };
361
+ return m;
362
+ });
363
+ };
364
+ const buildMiddlewareImports = function buildMiddlewareImports(mwEntries) {
365
+ return `${mwEntries.map((m, i) => {
366
+ return `${(m.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n")}\nimport __mw${i} from ${JSON.stringify(m.module)};`;
367
+ }).join("\n")}\n`;
368
+ };
369
+ const buildActionGate = function buildActionGate(hasActions, ws) {
370
+ if (!(hasActions && !ws)) return "";
371
+ return `if ((__actionMatch)(new URL(request.url).pathname)) {
404
372
  return __rpc(request);
405
373
  }
406
- ` : "";
407
- const ILHA_SSR_IMPLICIT = ["ilha:pages/server", "ilha:loaders"];
408
- const middlewareImports = (opts.middleware ?? []).map((m) => typeof m === "string" ? {
409
- module: m,
410
- imports: m === "@ilha/router/ssr" ? ILHA_SSR_IMPLICIT : []
411
- } : m).map((m, i) => (m.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n") + `\nimport __mw${i} from ${JSON.stringify(m.module)};`).join("\n") + "\n";
412
- const middlewareList = (opts.middleware ?? []).map((_, i) => `__mw${i}`).join(", ");
413
- const middlewareGate = opts.middleware?.length ? `for (const __mw of [${middlewareList}]) {
374
+ `;
375
+ };
376
+ const buildMiddlewareGate = function buildMiddlewareGate(middleware) {
377
+ if (!middleware?.length) return "";
378
+ return `for (const __mw of [${middleware.map((_, i) => `__mw${i}`).join(", ")}]) {
414
379
  const hit = await __mw(request, { env, ctx });
415
380
  if (hit) return hit;
416
381
  }
417
- ` : "";
418
- return `${(opts.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n")}${preset === "celld" ? `import "oxidejs/worker-dom/install";\n` : ""}export * from ${JSON.stringify(userWorkerAbs)};
419
- import user from ${JSON.stringify(userWorkerAbs)};
382
+ `;
383
+ };
384
+ const buildCelldDomBlock = function buildCelldDomBlock(preset) {
385
+ if (preset !== "celld") return "";
386
+ return `import "oxidejs/worker-dom/install";\n`;
387
+ };
388
+ const generateWorkerWrapper = function generateWorkerWrapper(userWorkerAbs, opts = {}) {
389
+ const preset = opts.preset ?? "fetch";
390
+ const clientDir = opts.clientDir ?? "client";
391
+ const actionPath = opts.actionPath ?? "/__oxide/action";
392
+ const sameOrigin = opts.actionSameOrigin ?? false;
393
+ const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
394
+ const hasActions = opts.hasActions !== false;
395
+ const ws = hasActions && opts.actions === "ws";
396
+ const bodyLimit = opts.bodyLimit ?? 1048576;
397
+ const nfBlock = `const __nf = ${`() => new Response(${JSON.stringify(opts.notFound ?? "<h1>404 Not Found</h1>")}, { status: 404, headers: { "content-type": "text/html; charset=utf-8" } })`};\n`;
398
+ const assetBlock = buildAssetBlock(serveAssets, clientDir);
399
+ const envJson = JSON.stringify(opts.env ?? {});
400
+ const afterAction = buildAfterAction(serveAssets, preset, envJson);
401
+ const listen = buildListenBlock(preset, bodyLimit, ws);
402
+ const actionImports = buildActionImports(hasActions, ws, actionPath, sameOrigin);
403
+ const actionMatchFn = `const __actionMatch = (p) => p === ${JSON.stringify(actionPath)} || p === ${JSON.stringify(`${actionPath}/`)};`;
404
+ const actionGate = buildActionGate(hasActions, ws);
405
+ const mwEntries = normalizeMiddlewareEntries(opts.middleware ?? []);
406
+ const middlewareImports = buildMiddlewareImports(mwEntries);
407
+ const middlewareGate = buildMiddlewareGate(opts.middleware);
408
+ return `${(opts.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n")}${buildCelldDomBlock(preset)}export * from ${JSON.stringify(userWorkerAbs)};
409
+ import * as __userMod from ${JSON.stringify(userWorkerAbs)};
410
+ const user = __userMod.default;
411
+ const __userFetch =
412
+ user != null && typeof user.fetch === "function"
413
+ ? user.fetch.bind(user)
414
+ : typeof __userMod.fetch === "function"
415
+ ? __userMod.fetch
416
+ : undefined;
420
417
  ${middlewareImports}${actionImports}${hasActions ? `${actionMatchFn}\n` : ""}${assetBlock}${nfBlock}const app = {
421
- ...user,
418
+ ...(user ?? {}),
422
419
  async fetch(request, env, ctx) {
423
420
  request[__fetch] = { env, fetchCtx: ctx };
424
421
  ${middlewareGate}${actionGate}${afterAction}
@@ -426,7 +423,7 @@ ${middlewareImports}${actionImports}${hasActions ? `${actionMatchFn}\n` : ""}${a
426
423
  };
427
424
  export default app;
428
425
  ${listen}`;
429
- }
426
+ };
430
427
  const SERVER_TARGETS = /* @__PURE__ */ new Set([
431
428
  "node",
432
429
  "async-node",
@@ -442,7 +439,7 @@ const SERVER_NAMES = /* @__PURE__ */ new Set([
442
439
  ]);
443
440
  const CLIENT_NAMES = /* @__PURE__ */ new Set(["client", "web"]);
444
441
  /** Stub unless the graph is a known server. Unknown graphs stub so *.server.ts never ships. */
445
- function shouldStubServerModule(environment, extra) {
442
+ const shouldStubServerModule = function shouldStubServerModule(environment, extra) {
446
443
  if (extra?.ssr) return false;
447
444
  const consumer = environment?.config?.consumer ?? environment?.consumer;
448
445
  if (consumer === "server") return false;
@@ -450,12 +447,12 @@ function shouldStubServerModule(environment, extra) {
450
447
  const name = environment?.name;
451
448
  if (name && CLIENT_NAMES.has(name)) return true;
452
449
  if (name && SERVER_NAMES.has(name)) return false;
453
- const targets = extra?.target == null ? [] : [extra.target].flat();
450
+ const targets = extra?.target === void 0 || extra.target === null ? [] : [extra.target].flat();
454
451
  if (targets.some((target) => SERVER_TARGETS.has(target))) return false;
455
452
  if (targets.some((target) => CLIENT_TARGETS.has(target))) return true;
456
453
  return true;
457
- }
458
- function pluginShouldStub(pluginThis, options) {
454
+ };
455
+ const pluginShouldStub = function pluginShouldStub(pluginThis, options) {
459
456
  const ctx = pluginThis;
460
457
  const compiler = ctx.getNativeBuildContext?.()?.compiler;
461
458
  const env = {};
@@ -467,18 +464,23 @@ function pluginShouldStub(pluginThis, options) {
467
464
  if (options?.ssr) extra.ssr = true;
468
465
  if (compiler?.options?.target) extra.target = compiler.options.target;
469
466
  return shouldStubServerModule(env, extra);
470
- }
471
- function loadClientStub(id) {
467
+ };
468
+ const loadClientStub = function loadClientStub(id) {
472
469
  const file = id.split("?")[0] ?? id;
473
- const source = fs.readFileSync(file, "utf8");
470
+ const source = fs.readFileSync(file, "utf-8");
474
471
  return generateClientStub({
475
- key: moduleKey(file),
476
472
  exports: parseExportedNames(source),
473
+ key: moduleKey(file),
477
474
  streams: parseStreamExports(source)
478
475
  });
479
- }
480
- var RequestBodyTooLargeError = class extends Error {};
481
- async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
476
+ };
477
+ var RequestBodyTooLargeError = class extends Error {
478
+ constructor(message) {
479
+ super(message);
480
+ this.name = "RequestBodyTooLargeError";
481
+ }
482
+ };
483
+ const nodeToWebRequest = async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
482
484
  const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`;
483
485
  const headers = new Headers();
484
486
  for (const [key, value] of Object.entries(req.headers)) {
@@ -490,15 +492,15 @@ async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
490
492
  req.once("aborted", () => ac.abort());
491
493
  const method = req.method ?? "GET";
492
494
  const init = {
493
- method,
494
495
  headers,
496
+ method,
495
497
  signal: ac.signal
496
498
  };
497
499
  if (method === "GET" || method === "HEAD") return new Request(url, init);
498
500
  const chunks = [];
499
501
  let size = 0;
500
502
  for await (const chunk of req) {
501
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
503
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
502
504
  size += buffer.length;
503
505
  if (size > maxBytes) throw new RequestBodyTooLargeError();
504
506
  chunks.push(buffer);
@@ -506,13 +508,13 @@ async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
506
508
  const body = Buffer.concat(chunks);
507
509
  if (body.length > 0) init.body = body;
508
510
  return new Request(url, init);
509
- }
510
- async function sendWebResponseFrom(req, res, response) {
511
+ };
512
+ const sendWebResponseFrom = function sendWebResponseFrom(req, res, response) {
511
513
  return pipeResponse(req, res, response);
512
- }
514
+ };
513
515
  //#endregion
514
516
  //#region src/rpc/same-origin.ts
515
- function isSameOrigin(request) {
517
+ const isSameOrigin = function isSameOrigin(request) {
516
518
  const site = request.headers.get("sec-fetch-site");
517
519
  const origin = request.headers.get("origin");
518
520
  if (!origin && !site) return false;
@@ -523,74 +525,90 @@ function isSameOrigin(request) {
523
525
  } catch {
524
526
  return false;
525
527
  }
526
- }
528
+ };
527
529
  //#endregion
528
530
  //#region src/rpc/scrub.ts
529
- /** Strip Effect RPC `Defect` / `Cause` payloads down to plain JSON-RPC errors. */
530
531
  const INTERNAL = {
531
532
  code: -32603,
532
533
  message: "Internal error"
533
534
  };
534
535
  const NDJSON_CONTENT = "application/json-rpc";
535
- function isRecord(value) {
536
- return value !== null && typeof value === "object";
537
- }
538
- function classifyCause(error) {
536
+ const isJsonObject = function isJsonObject(value) {
537
+ return value !== null && typeof value === "object" && !Array.isArray(value);
538
+ };
539
+ const classifyCause = function classifyCause(error) {
539
540
  const blob = `${String(error["message"] ?? "")}${JSON.stringify(error["data"] ?? "")}`;
540
- if (/Unknown request tag/i.test(blob)) return {
541
+ if (/Unknown request tag/iu.test(blob)) return {
541
542
  code: -32601,
542
543
  message: "Method not found"
543
544
  };
544
- if (/Missing key/i.test(blob) || /Expected/i.test(blob) && /\["args"\]|\[\\"args\\"\]/.test(blob)) return {
545
+ if (/Missing key/iu.test(blob) || /Expected/iu.test(blob) && /\["args"\]|\[\\"args\\"\]/u.test(blob)) return {
545
546
  code: -32602,
546
547
  message: "Invalid params"
547
548
  };
548
549
  return { ...INTERNAL };
549
- }
550
- function scrubError(error) {
551
- if (!isRecord(error)) return { ...INTERNAL };
550
+ };
551
+ const isPlainJsonRpcError = function isPlainJsonRpcError(error) {
552
+ return typeof error["code"] === "number" && typeof error["message"] === "string";
553
+ };
554
+ const scrubError = function scrubError(error) {
555
+ if (!isJsonObject(error)) return { ...INTERNAL };
552
556
  if (error["_tag"] === "Defect") return { ...INTERNAL };
553
557
  if (error["_tag"] === "Cause") return classifyCause(error);
554
- if (typeof error["code"] === "number" && typeof error["message"] === "string") return {
558
+ if (isPlainJsonRpcError(error)) return {
555
559
  code: error["code"],
556
560
  message: error["message"]
557
561
  };
558
562
  return { ...INTERNAL };
559
- }
560
- function createIdRepairState(requestIds = []) {
563
+ };
564
+ const createIdRepairState = function createIdRepairState(requestIds = []) {
561
565
  return { remaining: new Set(requestIds) };
562
- }
566
+ };
567
+ const parseOxidejsJson = function parseOxidejsJson(raw) {
568
+ return JSON.parse(raw);
569
+ };
563
570
  /**
564
571
  * Scrub one JSON-RPC response object.
565
572
  * Effect encodes Defects with `id: -32603`; reclaim the originating request id from `state.remaining`.
566
573
  */
567
- function scrubRpcMessage(msg, requestIds = [], state = createIdRepairState(requestIds)) {
568
- if (!isRecord(msg) || !("error" in msg) || msg["error"] == null) {
569
- if (isRecord(msg) && msg["chunk"] !== true && msg["id"] !== -32603 && "id" in msg) state.remaining.delete(msg["id"]);
574
+ const scrubRpcMessage = function scrubRpcMessage(msg, requestIds = [], state = createIdRepairState(requestIds)) {
575
+ if (!isJsonObject(msg) || !("error" in msg) || msg["error"] === null || msg["error"] === void 0) {
576
+ if (isJsonObject(msg) && msg["chunk"] !== true && msg["id"] !== -32603 && "id" in msg) state.remaining.delete(msg["id"]);
570
577
  return msg;
571
578
  }
572
- const error = scrubError(msg["error"]);
573
- let id = msg["id"];
579
+ const scrubbedError = scrubError(msg["error"]);
580
+ let { id } = msg;
574
581
  if (id === -32603) {
575
582
  const next = state.remaining.values().next();
576
- if (!next.done) {
577
- id = next.value;
583
+ if (next.done) id = null;
584
+ else {
585
+ ({value: id} = next);
578
586
  state.remaining.delete(next.value);
579
- } else id = null;
587
+ }
580
588
  } else if (id !== void 0 && id !== null) state.remaining.delete(id);
581
589
  if (id === void 0) id = null;
582
590
  return {
583
- jsonrpc: "2.0",
591
+ error: {
592
+ code: scrubbedError.code,
593
+ message: scrubbedError.message
594
+ },
584
595
  id,
585
- error
596
+ jsonrpc: "2.0"
586
597
  };
587
- }
598
+ };
599
+ const scrubRpcLine = function scrubRpcLine(line, state) {
600
+ try {
601
+ return JSON.stringify(scrubRpcMessage(parseOxidejsJson(line), [], state));
602
+ } catch {
603
+ return line;
604
+ }
605
+ };
588
606
  /**
589
607
  * Rewrite a JSON / NDJSON body so clients never see Effect `_tag` / `data` trees.
590
608
  * Accepts a single object, a JSON array, or newline-delimited frames.
591
609
  */
592
- function scrubRpcJson(body, requestIds = []) {
593
- const trimmed = body.replace(/^\uFEFF/, "");
610
+ const scrubRpcJson = function scrubRpcJson(body, requestIds = []) {
611
+ const trimmed = body.replace(/^\uFEFF/u, "");
594
612
  if (!trimmed) return body;
595
613
  const state = createIdRepairState(requestIds);
596
614
  if (trimmed.includes("\n")) {
@@ -603,59 +621,71 @@ function scrubRpcJson(body, requestIds = []) {
603
621
  return trimmed.endsWith("\n") ? `${out.join("\n")}\n` : out.join("\n");
604
622
  }
605
623
  try {
606
- const parsed = JSON.parse(trimmed);
624
+ const parsed = parseOxidejsJson(trimmed);
607
625
  if (Array.isArray(parsed)) return JSON.stringify(parsed.map((msg) => scrubRpcMessage(msg, requestIds, state)));
608
626
  return JSON.stringify(scrubRpcMessage(parsed, requestIds, state));
609
627
  } catch {
610
628
  return body;
611
629
  }
612
- }
613
- function scrubRpcLine(line, state) {
614
- try {
615
- return JSON.stringify(scrubRpcMessage(JSON.parse(line), [], state));
616
- } catch {
617
- return line;
618
- }
619
- }
630
+ };
631
+ const requestBodyText = function requestBodyText(body) {
632
+ if (body instanceof Uint8Array) return new TextDecoder().decode(body);
633
+ if (body instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(body));
634
+ return body;
635
+ };
636
+ const isJsonRpcId = function isJsonRpcId(value) {
637
+ return value === null || typeof value === "string" || typeof value === "number";
638
+ };
620
639
  /** Collect JSON-RPC request ids from a unary object or batch array body. */
621
- function extractJsonRpcRequestIds(body) {
640
+ const extractJsonRpcRequestIds = function extractJsonRpcRequestIds(body) {
622
641
  try {
623
- let text = typeof body === "string" ? body : new TextDecoder().decode(body instanceof Uint8Array ? body : new Uint8Array(body));
624
- text = text.replace(/^\uFEFF/, "").trimEnd();
642
+ let text = requestBodyText(body);
643
+ text = text.replace(/^\uFEFF/u, "").trimEnd();
625
644
  if (text.includes("\n")) {
626
645
  const ids = [];
627
646
  for (const line of text.split("\n")) {
628
647
  if (!line) continue;
629
- const parsed = JSON.parse(line);
630
- if (isRecord(parsed) && "id" in parsed) ids.push(parsed["id"]);
648
+ const parsed = parseOxidejsJson(line);
649
+ if (isJsonObject(parsed) && "id" in parsed && isJsonRpcId(parsed["id"])) ids.push(parsed["id"]);
631
650
  }
632
651
  return ids;
633
652
  }
634
- const parsed = JSON.parse(text);
635
- if (Array.isArray(parsed)) return parsed.filter(isRecord).filter((item) => "id" in item).map((item) => item["id"]);
636
- if (isRecord(parsed) && "id" in parsed) return [parsed["id"]];
653
+ const parsed = parseOxidejsJson(text);
654
+ if (Array.isArray(parsed)) {
655
+ const ids = [];
656
+ for (const item of parsed) if (isJsonObject(item) && "id" in item && isJsonRpcId(item["id"])) ids.push(item["id"]);
657
+ return ids;
658
+ }
659
+ if (isJsonObject(parsed) && "id" in parsed && isJsonRpcId(parsed["id"])) return [parsed["id"]];
637
660
  } catch {}
638
661
  return [];
639
- }
662
+ };
640
663
  /** Ensure a body is a valid NDJSON frame (Effect's ndJsonRpc decode requires a trailing newline). */
641
- function ensureNdjsonBody(buf) {
664
+ const ensureNdjsonBody = function ensureNdjsonBody(buf) {
642
665
  const bytes = new Uint8Array(buf);
643
- if (bytes.length > 0 && bytes[bytes.length - 1] === 10) return bytes;
666
+ if (bytes.length > 0 && bytes.at(-1) === 10) return bytes;
644
667
  const out = new Uint8Array(bytes.length + 1);
645
668
  out.set(bytes);
646
669
  out[bytes.length] = 10;
647
670
  return out;
648
- }
671
+ };
649
672
  /**
650
673
  * TransformStream that scrubs Effect defect payloads one NDJSON line at a time,
651
674
  * so long-running stream actions stay incremental.
652
675
  */
653
- function scrubNdjsonTransform(requestIds = []) {
676
+ const scrubNdjsonTransform = function scrubNdjsonTransform(requestIds = []) {
654
677
  const decoder = new TextDecoder();
655
678
  const encoder = new TextEncoder();
656
679
  const state = createIdRepairState(requestIds);
657
680
  let pending = "";
658
681
  return new TransformStream({
682
+ flush(controller) {
683
+ pending += decoder.decode();
684
+ if (pending.length > 0) {
685
+ controller.enqueue(encoder.encode(`${scrubRpcLine(pending, state)}\n`));
686
+ pending = "";
687
+ }
688
+ },
659
689
  transform(chunk, controller) {
660
690
  pending += decoder.decode(chunk, { stream: true });
661
691
  let nl = pending.indexOf("\n");
@@ -665,126 +695,122 @@ function scrubNdjsonTransform(requestIds = []) {
665
695
  if (line.length > 0) controller.enqueue(encoder.encode(`${scrubRpcLine(line, state)}\n`));
666
696
  nl = pending.indexOf("\n");
667
697
  }
668
- },
669
- flush(controller) {
670
- pending += decoder.decode();
671
- if (pending.length > 0) {
672
- controller.enqueue(encoder.encode(`${scrubRpcLine(pending, state)}\n`));
673
- pending = "";
674
- }
675
698
  }
676
699
  });
677
- }
700
+ };
678
701
  //#endregion
679
702
  //#region src/rpc/server.ts
680
703
  const JSON_RPC_FORBIDDEN = {
681
- jsonrpc: "2.0",
682
704
  error: {
683
705
  code: -32600,
684
706
  message: "Forbidden"
685
707
  },
686
- id: null
708
+ id: null,
709
+ jsonrpc: "2.0"
687
710
  };
688
711
  const bundles = /* @__PURE__ */ new Map();
689
712
  const groupIds = /* @__PURE__ */ new WeakMap();
690
713
  let nextGroupId = 0;
691
714
  const serialization = RpcSerialization.layerNdJsonRpc();
692
- function bundleKey(group, path, transport) {
715
+ const bundleKey = function bundleKey(group, path, transport) {
693
716
  let id = groupIds.get(group);
694
717
  if (id === void 0) {
695
- id = nextGroupId++;
718
+ id = nextGroupId;
719
+ nextGroupId += 1;
696
720
  groupIds.set(group, id);
697
721
  }
698
722
  return `${id}:${path}:${transport}`;
699
- }
700
- function forbidden() {
701
- return new Response(JSON.stringify(JSON_RPC_FORBIDDEN), {
702
- status: 403,
703
- headers: { "content-type": "application/json" }
723
+ };
724
+ const forbidden = function forbidden() {
725
+ return Response.json(JSON_RPC_FORBIDDEN, {
726
+ headers: { "content-type": "application/json" },
727
+ status: 403
704
728
  });
705
- }
706
- function methodNotAllowed() {
729
+ };
730
+ const methodNotAllowed = function methodNotAllowed() {
707
731
  return new Response("Method Not Allowed", {
708
- status: 405,
709
- headers: { Allow: "POST" }
732
+ headers: { Allow: "POST" },
733
+ status: 405
710
734
  });
711
- }
712
- function buildBundle(group, handlers, path, transport) {
735
+ };
736
+ const buildBundle = function buildBundle(group, handlers, path, transport) {
713
737
  const app = RpcServer.layerHttp({
714
738
  group,
715
739
  path,
716
740
  protocol: transport === "ws" ? "websocket" : "http"
717
741
  }).pipe(Layer.provide(handlers), Layer.provide(serialization));
718
742
  return HttpRouter.toWebHandler(app, { disableLogger: true });
719
- }
720
- function bundleFor(group, handlers, path, transport) {
743
+ };
744
+ const bundleFor = function bundleFor(group, handlers, path, transport) {
721
745
  const key = bundleKey(group, path, transport);
722
746
  const cached = bundles.get(key);
723
747
  if (cached) return cached;
724
748
  const built = buildBundle(group, handlers, path, transport);
725
749
  bundles.set(key, built);
726
750
  return built;
727
- }
728
- function scrubJsonResponse(response, requestIds) {
751
+ };
752
+ const scrubJsonResponse = function scrubJsonResponse(response, requestIds) {
729
753
  const contentType = response.headers.get("content-type") ?? "";
730
754
  if (!contentType.includes("json")) return response;
731
755
  if (response.body && (contentType.includes("application/json-rpc") || contentType.includes("ndjson"))) {
732
756
  const headers = new Headers(response.headers);
733
757
  headers.delete("content-length");
734
758
  return new Response(response.body.pipeThrough(scrubNdjsonTransform(requestIds)), {
759
+ headers,
735
760
  status: response.status,
736
- statusText: response.statusText,
737
- headers
761
+ statusText: response.statusText
738
762
  });
739
763
  }
740
764
  return response;
741
- }
742
- async function scrubBufferedJson(response, requestIds) {
765
+ };
766
+ const scrubBufferedJson = async function scrubBufferedJson(response, requestIds) {
743
767
  const text = await response.text();
744
768
  const headers = new Headers(response.headers);
745
769
  headers.delete("content-length");
746
770
  return new Response(scrubRpcJson(text, requestIds), {
771
+ headers,
747
772
  status: response.status,
748
- statusText: response.statusText,
749
- headers
773
+ statusText: response.statusText
750
774
  });
751
- }
752
- function createActionHandler(group, handlers, options = {}) {
775
+ };
776
+ const createActionHandler = function createActionHandler(group, handlers, options = {}) {
753
777
  const path = options.path ?? "/__oxide/action";
754
778
  const transport = options.transport ?? "http";
755
779
  const sameOrigin = options.sameOrigin ?? true;
756
- return async (request) => {
780
+ return async function handleActionRequest(request) {
757
781
  if (!matchesActionPath(new URL(request.url).pathname, path)) return new Response("Not Found", { status: 404 });
758
782
  if (transport === "http" && request.method !== "POST") return methodNotAllowed();
759
783
  if (sameOrigin && !isSameOrigin(request)) return forbidden();
760
- const rawBody = ensureNdjsonBody(await request.arrayBuffer());
761
- const requestIds = extractJsonRpcRequestIds(rawBody);
762
- const headers = new Headers(request.headers);
763
- headers.set("content-type", NDJSON_CONTENT);
764
- const forwarded = new Request(request.url, {
765
- method: request.method,
766
- headers,
767
- body: rawBody,
768
- signal: request.signal
784
+ return await withRequestEntry(async () => {
785
+ const rawBody = ensureNdjsonBody(await request.arrayBuffer());
786
+ const requestIds = extractJsonRpcRequestIds(rawBody);
787
+ const headers = new Headers(request.headers);
788
+ headers.set("content-type", NDJSON_CONTENT);
789
+ const forwarded = new Request(request.url, {
790
+ body: rawBody,
791
+ headers,
792
+ method: request.method,
793
+ signal: request.signal
794
+ });
795
+ const extra = await options.createContext?.(forwarded) ?? {};
796
+ const { handler } = bundleFor(group, handlers, path, transport);
797
+ const response = await runWithRequest(forwarded, () => handler(forwarded), extra);
798
+ const contentType = response.headers.get("content-type") ?? "";
799
+ if (contentType.includes("application/json-rpc") || contentType.includes("ndjson")) return scrubJsonResponse(response, requestIds);
800
+ if (contentType.includes("json")) return scrubBufferedJson(response, requestIds);
801
+ return response;
769
802
  });
770
- const extra = await options.createContext?.(forwarded) ?? {};
771
- const { handler } = bundleFor(group, handlers, path, transport);
772
- const response = await runWithRequest(forwarded, () => handler(forwarded), extra);
773
- const contentType = response.headers.get("content-type") ?? "";
774
- if (contentType.includes("application/json-rpc") || contentType.includes("ndjson")) return scrubJsonResponse(response, requestIds);
775
- if (contentType.includes("json")) return scrubBufferedJson(response, requestIds);
776
- return response;
777
803
  };
778
- }
779
- function disposeActionHandler(group, path = ACTION_PATH, transport = "http") {
804
+ };
805
+ const disposeActionHandler = function disposeActionHandler(group, path = ACTION_PATH, transport = "http") {
780
806
  const key = bundleKey(group, path, transport);
781
807
  const bundle = bundles.get(key);
782
808
  bundles.delete(key);
783
809
  return bundle?.dispose() ?? Promise.resolve();
784
- }
810
+ };
785
811
  //#endregion
786
812
  //#region src/rpc/ws.ts
787
- function parseMessage(message, maxBytes) {
813
+ const parseMessage = function parseMessage(message, maxBytes) {
788
814
  try {
789
815
  const raw = message.text();
790
816
  if (new TextEncoder().encode(raw).byteLength > maxBytes) return {
@@ -798,23 +824,26 @@ function parseMessage(message, maxBytes) {
798
824
  } catch {
799
825
  return { ok: false };
800
826
  }
801
- }
827
+ };
828
+ const isJsonRpcPing = function isJsonRpcPing(value) {
829
+ return value !== null && !Array.isArray(value) && typeof value === "object" && value["method"] === "@effect/rpc/Ping";
830
+ };
802
831
  /**
803
832
  * Effect's socket client sends `@effect/rpc/Ping` keepalives (no id) and hangs
804
833
  * up unless the server answers `@effect/rpc/Pong`. Handle control messages here
805
834
  * so they never reach the action handler.
806
835
  */
807
- function controlReply(raw) {
836
+ const controlReply = function controlReply(raw) {
808
837
  try {
809
838
  const parsed = JSON.parse(raw);
810
- if (parsed && typeof parsed === "object" && parsed.method === "@effect/rpc/Ping") return JSON.stringify({
839
+ if (isJsonRpcPing(parsed)) return JSON.stringify({
811
840
  jsonrpc: "2.0",
812
841
  method: "@effect/rpc/Pong"
813
842
  });
814
843
  } catch {}
815
- }
844
+ };
816
845
  /** Forward each complete NDJSON line as its own WS message (keeps streams incremental). */
817
- async function sendNdjsonFrames(peer, response, signal) {
846
+ const sendNdjsonFrames = async function sendNdjsonFrames(peer, response, signal) {
818
847
  if (signal.aborted) {
819
848
  await response.body?.cancel();
820
849
  return;
@@ -827,14 +856,15 @@ async function sendNdjsonFrames(peer, response, signal) {
827
856
  const reader = response.body.getReader();
828
857
  const decoder = new TextDecoder();
829
858
  let pending = "";
830
- const onAbort = () => {
859
+ const onAbort = function onAbort() {
831
860
  reader.cancel();
832
861
  };
833
862
  signal.addEventListener("abort", onAbort, { once: true });
834
- try {
835
- while (!signal.aborted) {
863
+ const pump = async function pump() {
864
+ for (;;) {
865
+ if (signal.aborted) return;
836
866
  const { done, value } = await reader.read();
837
- if (done) break;
867
+ if (done) return;
838
868
  pending += decoder.decode(value, { stream: true });
839
869
  let nl = pending.indexOf("\n");
840
870
  while (nl !== -1) {
@@ -844,6 +874,9 @@ async function sendNdjsonFrames(peer, response, signal) {
844
874
  nl = pending.indexOf("\n");
845
875
  }
846
876
  }
877
+ };
878
+ try {
879
+ await pump();
847
880
  if (!signal.aborted) {
848
881
  pending += decoder.decode();
849
882
  if (pending.length > 0) peer.send(pending.endsWith("\n") ? pending : `${pending}\n`);
@@ -851,37 +884,27 @@ async function sendNdjsonFrames(peer, response, signal) {
851
884
  } finally {
852
885
  signal.removeEventListener("abort", onAbort);
853
886
  }
854
- }
855
- function createWsHooks(group, handlers, options = {}) {
887
+ };
888
+ const createWsHooks = function createWsHooks(group, handlers, options = {}) {
856
889
  const path = options.path ?? "/__oxide/action";
857
890
  const maxBytes = options.maxMessageSize ?? 1048576;
858
891
  const sameOrigin = options.sameOrigin ?? true;
859
892
  const baseOptions = {
860
893
  path,
861
- transport: "http",
862
- sameOrigin
894
+ sameOrigin,
895
+ transport: "http"
863
896
  };
864
897
  return {
865
- upgrade(req) {
866
- let pathname;
867
- try {
868
- pathname = new URL(req.url).pathname;
869
- } catch {
870
- return new Response("Bad Request", { status: 400 });
871
- }
872
- if (!matchesActionPath(pathname, path)) return new Response("Not Found", { status: 404 });
873
- if (sameOrigin && !isSameOrigin(req)) return new Response("Forbidden", { status: 403 });
874
- },
875
898
  async message(peer, message) {
876
899
  const parsed = parseMessage(message, maxBytes);
877
900
  if (!parsed.ok) {
878
901
  peer.send(JSON.stringify({
879
- jsonrpc: "2.0",
880
902
  error: {
881
903
  code: -32600,
882
904
  message: parsed.tooLarge ? "Payload too large" : "Parse error"
883
905
  },
884
- id: null
906
+ id: null,
907
+ jsonrpc: "2.0"
885
908
  }));
886
909
  return;
887
910
  }
@@ -896,20 +919,31 @@ function createWsHooks(group, handlers, options = {}) {
896
919
  const headers = new Headers(peer.request?.headers);
897
920
  headers.set("content-type", NDJSON_CONTENT);
898
921
  const peerCtx = await options.createContext?.(peer) ?? peer.context;
899
- await sendNdjsonFrames(peer, await createActionHandler(group, handlers, {
922
+ const response = await createActionHandler(group, handlers, {
900
923
  ...baseOptions,
901
924
  createContext: (req) => ({
902
925
  ...peerCtx,
903
926
  req
904
927
  })
905
928
  })(new Request(`http://${host}${path}`, {
906
- method: "POST",
907
- headers,
908
929
  body: parsed.value,
930
+ headers,
931
+ method: "POST",
909
932
  signal: abort.signal
910
- })), abort.signal);
933
+ }));
934
+ await sendNdjsonFrames(peer, response, abort.signal);
935
+ },
936
+ upgrade(req) {
937
+ let pathname;
938
+ try {
939
+ ({pathname} = new URL(req.url));
940
+ } catch {
941
+ return new Response("Bad Request", { status: 400 });
942
+ }
943
+ if (!matchesActionPath(pathname, path)) return new Response("Not Found", { status: 404 });
944
+ if (sameOrigin && !isSameOrigin(req)) return new Response("Forbidden", { status: 403 });
911
945
  }
912
946
  };
913
- }
947
+ };
914
948
  //#endregion
915
- export { pluginShouldStub as A, isServerFileId as C, nodeToWebRequest as D, moduleKey as E, sendWebResponseFrom as M, parseExportedNames as O, generateWorkerWrapper as S, matchesActionPath as T, actions_exports as _, extractJsonRpcRequestIds as a, generateClientModule as b, scrubRpcMessage as c, RESOLVED_VIRTUAL_CLIENT_ID as d, RESOLVED_VIRTUAL_WORKER_ID as f, VIRTUAL_WORKER_ID as g, VIRTUAL_CLIENT_ID as h, ensureNdjsonBody as i, scanServerFiles as j, parseStreamExports as k, ACTION_PATH as l, VIRTUAL_ACTIONS_ID as m, createActionHandler as n, scrubNdjsonTransform as o, RequestBodyTooLargeError as p, disposeActionHandler as r, scrubRpcJson as s, createWsHooks as t, RESOLVED_VIRTUAL_ACTIONS_ID as u, generateActionsClientModule as v, loadClientStub as w, generateClientStub as x, generateActionsModule as y };
949
+ export { scanServerFiles as A, loadClientStub as C, parseExportedNames as D, nodeToWebRequest as E, parseStreamExports as O, isServerFileId as S, moduleKey as T, generateActionsClientModule as _, extractJsonRpcRequestIds as a, generateClientStub as b, scrubRpcMessage as c, RESOLVED_VIRTUAL_CLIENT_ID as d, RESOLVED_VIRTUAL_WORKER_ID as f, VIRTUAL_WORKER_ID as g, VIRTUAL_CLIENT_ID as h, ensureNdjsonBody as i, sendWebResponseFrom as j, pluginShouldStub as k, ACTION_PATH as l, VIRTUAL_ACTIONS_ID as m, createActionHandler as n, scrubNdjsonTransform as o, RequestBodyTooLargeError as p, disposeActionHandler as r, scrubRpcJson as s, createWsHooks as t, RESOLVED_VIRTUAL_ACTIONS_ID as u, generateActionsModule as v, matchesActionPath as w, generateWorkerWrapper as x, generateClientModule as y };