oxidejs 0.3.2 → 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 { c as withRequestEntry, r as runWithRequest } from "./context-DQDDwFYi.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,25 +128,21 @@ 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";`,
@@ -221,60 +180,47 @@ function generateActionsModule(modules, opts) {
221
180
  mod
222
181
  };
223
182
  });
224
- lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`);
225
- lines.push(`export const actionsHandlers = actionsGroup.toLayer({`);
183
+ lines.push(`export const actionsGroup = RpcGroup.make(${rpcNames.join(", ")});`, `export const actionsHandlers = actionsGroup.toLayer({`);
226
184
  for (const { alias, mod } of aliases) for (const name of mod.exports) {
227
185
  const tag = `${mod.key}.${name}`;
228
186
  const stream = mod.streams?.includes(name) ?? false;
229
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)),`);
230
188
  }
231
- lines.push(`});`);
232
- lines.push(`export default actionsGroup;`);
233
- lines.push(`export { actionsGroup as actions };`);
189
+ lines.push(`});`, `export default actionsGroup;`, `export { actionsGroup as actions };`);
234
190
  return `${lines.join("\n")}\n`;
235
- }
236
- function pipeResponse(req, res, response) {
237
- return new Promise((resolve, reject) => {
238
- res.statusCode = response.status;
239
- response.headers.forEach((value, key) => {
240
- res.setHeader(key, value);
241
- });
242
- if (!response.body) {
243
- res.end();
244
- resolve();
245
- 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);
246
212
  }
247
- const reader = response.body.getReader();
248
- const abort = () => {
249
- reader.cancel();
250
- };
251
- req.once("aborted", abort);
252
- const pull = () => {
253
- reader.read().then(({ done, value }) => {
254
- if (done) {
255
- req.off("aborted", abort);
256
- res.end();
257
- resolve();
258
- return;
259
- }
260
- if (value) res.write(value);
261
- pull();
262
- }, reject);
263
- };
264
- pull();
265
- });
266
- }
267
- function generateWorkerWrapper(userWorkerAbs, opts = {}) {
268
- const preset = opts.preset ?? "fetch";
269
- const clientDir = opts.clientDir ?? "client";
270
- const actionPath = opts.actionPath ?? "/__oxide/action";
271
- const sameOrigin = opts.actionSameOrigin ?? false;
272
- const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
273
- const hasActions = opts.hasActions !== false;
274
- const ws = hasActions && opts.actions === "ws";
275
- const bodyLimit = opts.bodyLimit ?? 1048576;
276
- 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`;
277
- 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";
278
224
  import { extname, join } from "node:path";
279
225
  const __assets = join(import.meta.dirname, ${JSON.stringify(clientDir)});
280
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" };
@@ -316,23 +262,29 @@ async function __asset(request, spa) {
316
262
  return;
317
263
  }
318
264
  }
319
- ` : "";
320
- const envJson = JSON.stringify(opts.env ?? {});
265
+ `;
266
+ };
267
+ const buildAfterAction = function buildAfterAction(serveAssets, preset, envJson) {
321
268
  const celldAfterAction = `{
322
- 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;
323
270
  if (hit) return hit;
324
271
  const assets = env?.ASSETS;
325
272
  if (assets && typeof assets.fetch === "function") return assets.fetch(request);
326
273
  return __nf();
327
274
  }`;
328
- const afterAction = serveAssets ? `if (typeof user.fetch === "function") {
329
- const hit = await user.fetch(request, env ?? ${envJson}, ctx);
275
+ if (serveAssets) return `if (__userFetch) {
276
+ const hit = await __userFetch(request, env ?? ${envJson}, ctx);
330
277
  if (hit) return hit;
331
278
  }
332
- return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ?? __nf();` : preset === "celld" ? celldAfterAction : `return typeof user.fetch === "function"
333
- ? 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)
334
283
  : __nf();`;
335
- const listen = preset === "fetch" ? `
284
+ };
285
+ const buildListenBlock = function buildListenBlock(preset, bodyLimit, ws) {
286
+ if (preset !== "fetch") return "";
287
+ return `
336
288
  import { createServer } from "node:http";
337
289
  import { pathToFileURL } from "node:url";
338
290
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
@@ -378,42 +330,92 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
378
330
  });
379
331
  });` : ""}
380
332
  server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
381
- }
382
333
  for (const signal of ["SIGTERM", "SIGINT"]) {
383
334
  process.on(signal, () => {
384
335
  server.close(() => process.exit(0));
385
336
  setTimeout(() => process.exit(0), 5000).unref();
386
337
  });
387
338
  }
388
- ` : "";
389
- 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";
390
345
  import { actionsGroup, actionsHandlers } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
391
346
  const __ws = createWsHooks(actionsGroup, actionsHandlers, { path: ${JSON.stringify(actionPath)}, sameOrigin: ${sameOrigin} });
392
- ` : `import { createActionHandler } from "oxidejs/rpc";
347
+ `;
348
+ return `import { createActionHandler } from "oxidejs/rpc";
393
349
  import { actionsGroup, actionsHandlers } from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
394
350
  const __fetch = Symbol.for("oxidejs.fetch");
395
351
  const __rpc = createActionHandler(actionsGroup, actionsHandlers, { path: ${JSON.stringify(actionPath)}, sameOrigin: ${sameOrigin}, createContext: (req) => req[__fetch] ?? {} });
396
- ` : "";
397
- const actionMatchFn = `const __actionMatch = (p) => p === ${JSON.stringify(actionPath)} || p === ${JSON.stringify(`${actionPath}/`)};`;
398
- 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)) {
399
372
  return __rpc(request);
400
373
  }
401
- ` : "";
402
- const ILHA_SSR_IMPLICIT = ["ilha:pages/server", "ilha:loaders"];
403
- const middlewareImports = (opts.middleware ?? []).map((m) => typeof m === "string" ? {
404
- module: m,
405
- imports: m === "@ilha/router/ssr" ? ILHA_SSR_IMPLICIT : []
406
- } : 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";
407
- const middlewareList = (opts.middleware ?? []).map((_, i) => `__mw${i}`).join(", ");
408
- 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(", ")}]) {
409
379
  const hit = await __mw(request, { env, ctx });
410
380
  if (hit) return hit;
411
381
  }
412
- ` : "";
413
- return `${(opts.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n")}${preset === "celld" ? `import "oxidejs/worker-dom/install";\n` : ""}export * from ${JSON.stringify(userWorkerAbs)};
414
- 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;
415
417
  ${middlewareImports}${actionImports}${hasActions ? `${actionMatchFn}\n` : ""}${assetBlock}${nfBlock}const app = {
416
- ...user,
418
+ ...(user ?? {}),
417
419
  async fetch(request, env, ctx) {
418
420
  request[__fetch] = { env, fetchCtx: ctx };
419
421
  ${middlewareGate}${actionGate}${afterAction}
@@ -421,7 +423,7 @@ ${middlewareImports}${actionImports}${hasActions ? `${actionMatchFn}\n` : ""}${a
421
423
  };
422
424
  export default app;
423
425
  ${listen}`;
424
- }
426
+ };
425
427
  const SERVER_TARGETS = /* @__PURE__ */ new Set([
426
428
  "node",
427
429
  "async-node",
@@ -437,7 +439,7 @@ const SERVER_NAMES = /* @__PURE__ */ new Set([
437
439
  ]);
438
440
  const CLIENT_NAMES = /* @__PURE__ */ new Set(["client", "web"]);
439
441
  /** Stub unless the graph is a known server. Unknown graphs stub so *.server.ts never ships. */
440
- function shouldStubServerModule(environment, extra) {
442
+ const shouldStubServerModule = function shouldStubServerModule(environment, extra) {
441
443
  if (extra?.ssr) return false;
442
444
  const consumer = environment?.config?.consumer ?? environment?.consumer;
443
445
  if (consumer === "server") return false;
@@ -445,12 +447,12 @@ function shouldStubServerModule(environment, extra) {
445
447
  const name = environment?.name;
446
448
  if (name && CLIENT_NAMES.has(name)) return true;
447
449
  if (name && SERVER_NAMES.has(name)) return false;
448
- const targets = extra?.target == null ? [] : [extra.target].flat();
450
+ const targets = extra?.target === void 0 || extra.target === null ? [] : [extra.target].flat();
449
451
  if (targets.some((target) => SERVER_TARGETS.has(target))) return false;
450
452
  if (targets.some((target) => CLIENT_TARGETS.has(target))) return true;
451
453
  return true;
452
- }
453
- function pluginShouldStub(pluginThis, options) {
454
+ };
455
+ const pluginShouldStub = function pluginShouldStub(pluginThis, options) {
454
456
  const ctx = pluginThis;
455
457
  const compiler = ctx.getNativeBuildContext?.()?.compiler;
456
458
  const env = {};
@@ -462,18 +464,23 @@ function pluginShouldStub(pluginThis, options) {
462
464
  if (options?.ssr) extra.ssr = true;
463
465
  if (compiler?.options?.target) extra.target = compiler.options.target;
464
466
  return shouldStubServerModule(env, extra);
465
- }
466
- function loadClientStub(id) {
467
+ };
468
+ const loadClientStub = function loadClientStub(id) {
467
469
  const file = id.split("?")[0] ?? id;
468
- const source = fs.readFileSync(file, "utf8");
470
+ const source = fs.readFileSync(file, "utf-8");
469
471
  return generateClientStub({
470
- key: moduleKey(file),
471
472
  exports: parseExportedNames(source),
473
+ key: moduleKey(file),
472
474
  streams: parseStreamExports(source)
473
475
  });
474
- }
475
- var RequestBodyTooLargeError = class extends Error {};
476
- 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) {
477
484
  const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`;
478
485
  const headers = new Headers();
479
486
  for (const [key, value] of Object.entries(req.headers)) {
@@ -485,15 +492,15 @@ async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
485
492
  req.once("aborted", () => ac.abort());
486
493
  const method = req.method ?? "GET";
487
494
  const init = {
488
- method,
489
495
  headers,
496
+ method,
490
497
  signal: ac.signal
491
498
  };
492
499
  if (method === "GET" || method === "HEAD") return new Request(url, init);
493
500
  const chunks = [];
494
501
  let size = 0;
495
502
  for await (const chunk of req) {
496
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
503
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
497
504
  size += buffer.length;
498
505
  if (size > maxBytes) throw new RequestBodyTooLargeError();
499
506
  chunks.push(buffer);
@@ -501,13 +508,13 @@ async function nodeToWebRequest(req, maxBytes = Number.POSITIVE_INFINITY) {
501
508
  const body = Buffer.concat(chunks);
502
509
  if (body.length > 0) init.body = body;
503
510
  return new Request(url, init);
504
- }
505
- async function sendWebResponseFrom(req, res, response) {
511
+ };
512
+ const sendWebResponseFrom = function sendWebResponseFrom(req, res, response) {
506
513
  return pipeResponse(req, res, response);
507
- }
514
+ };
508
515
  //#endregion
509
516
  //#region src/rpc/same-origin.ts
510
- function isSameOrigin(request) {
517
+ const isSameOrigin = function isSameOrigin(request) {
511
518
  const site = request.headers.get("sec-fetch-site");
512
519
  const origin = request.headers.get("origin");
513
520
  if (!origin && !site) return false;
@@ -518,74 +525,90 @@ function isSameOrigin(request) {
518
525
  } catch {
519
526
  return false;
520
527
  }
521
- }
528
+ };
522
529
  //#endregion
523
530
  //#region src/rpc/scrub.ts
524
- /** Strip Effect RPC `Defect` / `Cause` payloads down to plain JSON-RPC errors. */
525
531
  const INTERNAL = {
526
532
  code: -32603,
527
533
  message: "Internal error"
528
534
  };
529
535
  const NDJSON_CONTENT = "application/json-rpc";
530
- function isRecord(value) {
531
- return value !== null && typeof value === "object";
532
- }
533
- 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) {
534
540
  const blob = `${String(error["message"] ?? "")}${JSON.stringify(error["data"] ?? "")}`;
535
- if (/Unknown request tag/i.test(blob)) return {
541
+ if (/Unknown request tag/iu.test(blob)) return {
536
542
  code: -32601,
537
543
  message: "Method not found"
538
544
  };
539
- 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 {
540
546
  code: -32602,
541
547
  message: "Invalid params"
542
548
  };
543
549
  return { ...INTERNAL };
544
- }
545
- function scrubError(error) {
546
- 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 };
547
556
  if (error["_tag"] === "Defect") return { ...INTERNAL };
548
557
  if (error["_tag"] === "Cause") return classifyCause(error);
549
- if (typeof error["code"] === "number" && typeof error["message"] === "string") return {
558
+ if (isPlainJsonRpcError(error)) return {
550
559
  code: error["code"],
551
560
  message: error["message"]
552
561
  };
553
562
  return { ...INTERNAL };
554
- }
555
- function createIdRepairState(requestIds = []) {
563
+ };
564
+ const createIdRepairState = function createIdRepairState(requestIds = []) {
556
565
  return { remaining: new Set(requestIds) };
557
- }
566
+ };
567
+ const parseOxidejsJson = function parseOxidejsJson(raw) {
568
+ return JSON.parse(raw);
569
+ };
558
570
  /**
559
571
  * Scrub one JSON-RPC response object.
560
572
  * Effect encodes Defects with `id: -32603`; reclaim the originating request id from `state.remaining`.
561
573
  */
562
- function scrubRpcMessage(msg, requestIds = [], state = createIdRepairState(requestIds)) {
563
- if (!isRecord(msg) || !("error" in msg) || msg["error"] == null) {
564
- 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"]);
565
577
  return msg;
566
578
  }
567
- const error = scrubError(msg["error"]);
568
- let id = msg["id"];
579
+ const scrubbedError = scrubError(msg["error"]);
580
+ let { id } = msg;
569
581
  if (id === -32603) {
570
582
  const next = state.remaining.values().next();
571
- if (!next.done) {
572
- id = next.value;
583
+ if (next.done) id = null;
584
+ else {
585
+ ({value: id} = next);
573
586
  state.remaining.delete(next.value);
574
- } else id = null;
587
+ }
575
588
  } else if (id !== void 0 && id !== null) state.remaining.delete(id);
576
589
  if (id === void 0) id = null;
577
590
  return {
578
- jsonrpc: "2.0",
591
+ error: {
592
+ code: scrubbedError.code,
593
+ message: scrubbedError.message
594
+ },
579
595
  id,
580
- error
596
+ jsonrpc: "2.0"
581
597
  };
582
- }
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
+ };
583
606
  /**
584
607
  * Rewrite a JSON / NDJSON body so clients never see Effect `_tag` / `data` trees.
585
608
  * Accepts a single object, a JSON array, or newline-delimited frames.
586
609
  */
587
- function scrubRpcJson(body, requestIds = []) {
588
- const trimmed = body.replace(/^\uFEFF/, "");
610
+ const scrubRpcJson = function scrubRpcJson(body, requestIds = []) {
611
+ const trimmed = body.replace(/^\uFEFF/u, "");
589
612
  if (!trimmed) return body;
590
613
  const state = createIdRepairState(requestIds);
591
614
  if (trimmed.includes("\n")) {
@@ -598,59 +621,71 @@ function scrubRpcJson(body, requestIds = []) {
598
621
  return trimmed.endsWith("\n") ? `${out.join("\n")}\n` : out.join("\n");
599
622
  }
600
623
  try {
601
- const parsed = JSON.parse(trimmed);
624
+ const parsed = parseOxidejsJson(trimmed);
602
625
  if (Array.isArray(parsed)) return JSON.stringify(parsed.map((msg) => scrubRpcMessage(msg, requestIds, state)));
603
626
  return JSON.stringify(scrubRpcMessage(parsed, requestIds, state));
604
627
  } catch {
605
628
  return body;
606
629
  }
607
- }
608
- function scrubRpcLine(line, state) {
609
- try {
610
- return JSON.stringify(scrubRpcMessage(JSON.parse(line), [], state));
611
- } catch {
612
- return line;
613
- }
614
- }
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
+ };
615
639
  /** Collect JSON-RPC request ids from a unary object or batch array body. */
616
- function extractJsonRpcRequestIds(body) {
640
+ const extractJsonRpcRequestIds = function extractJsonRpcRequestIds(body) {
617
641
  try {
618
- let text = typeof body === "string" ? body : new TextDecoder().decode(body instanceof Uint8Array ? body : new Uint8Array(body));
619
- text = text.replace(/^\uFEFF/, "").trimEnd();
642
+ let text = requestBodyText(body);
643
+ text = text.replace(/^\uFEFF/u, "").trimEnd();
620
644
  if (text.includes("\n")) {
621
645
  const ids = [];
622
646
  for (const line of text.split("\n")) {
623
647
  if (!line) continue;
624
- const parsed = JSON.parse(line);
625
- 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"]);
626
650
  }
627
651
  return ids;
628
652
  }
629
- const parsed = JSON.parse(text);
630
- if (Array.isArray(parsed)) return parsed.filter(isRecord).filter((item) => "id" in item).map((item) => item["id"]);
631
- 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"]];
632
660
  } catch {}
633
661
  return [];
634
- }
662
+ };
635
663
  /** Ensure a body is a valid NDJSON frame (Effect's ndJsonRpc decode requires a trailing newline). */
636
- function ensureNdjsonBody(buf) {
664
+ const ensureNdjsonBody = function ensureNdjsonBody(buf) {
637
665
  const bytes = new Uint8Array(buf);
638
- if (bytes.length > 0 && bytes[bytes.length - 1] === 10) return bytes;
666
+ if (bytes.length > 0 && bytes.at(-1) === 10) return bytes;
639
667
  const out = new Uint8Array(bytes.length + 1);
640
668
  out.set(bytes);
641
669
  out[bytes.length] = 10;
642
670
  return out;
643
- }
671
+ };
644
672
  /**
645
673
  * TransformStream that scrubs Effect defect payloads one NDJSON line at a time,
646
674
  * so long-running stream actions stay incremental.
647
675
  */
648
- function scrubNdjsonTransform(requestIds = []) {
676
+ const scrubNdjsonTransform = function scrubNdjsonTransform(requestIds = []) {
649
677
  const decoder = new TextDecoder();
650
678
  const encoder = new TextEncoder();
651
679
  const state = createIdRepairState(requestIds);
652
680
  let pending = "";
653
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
+ },
654
689
  transform(chunk, controller) {
655
690
  pending += decoder.decode(chunk, { stream: true });
656
691
  let nl = pending.indexOf("\n");
@@ -660,107 +695,101 @@ function scrubNdjsonTransform(requestIds = []) {
660
695
  if (line.length > 0) controller.enqueue(encoder.encode(`${scrubRpcLine(line, state)}\n`));
661
696
  nl = pending.indexOf("\n");
662
697
  }
663
- },
664
- flush(controller) {
665
- pending += decoder.decode();
666
- if (pending.length > 0) {
667
- controller.enqueue(encoder.encode(`${scrubRpcLine(pending, state)}\n`));
668
- pending = "";
669
- }
670
698
  }
671
699
  });
672
- }
700
+ };
673
701
  //#endregion
674
702
  //#region src/rpc/server.ts
675
703
  const JSON_RPC_FORBIDDEN = {
676
- jsonrpc: "2.0",
677
704
  error: {
678
705
  code: -32600,
679
706
  message: "Forbidden"
680
707
  },
681
- id: null
708
+ id: null,
709
+ jsonrpc: "2.0"
682
710
  };
683
711
  const bundles = /* @__PURE__ */ new Map();
684
712
  const groupIds = /* @__PURE__ */ new WeakMap();
685
713
  let nextGroupId = 0;
686
714
  const serialization = RpcSerialization.layerNdJsonRpc();
687
- function bundleKey(group, path, transport) {
715
+ const bundleKey = function bundleKey(group, path, transport) {
688
716
  let id = groupIds.get(group);
689
717
  if (id === void 0) {
690
- id = nextGroupId++;
718
+ id = nextGroupId;
719
+ nextGroupId += 1;
691
720
  groupIds.set(group, id);
692
721
  }
693
722
  return `${id}:${path}:${transport}`;
694
- }
695
- function forbidden() {
696
- return new Response(JSON.stringify(JSON_RPC_FORBIDDEN), {
697
- status: 403,
698
- 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
699
728
  });
700
- }
701
- function methodNotAllowed() {
729
+ };
730
+ const methodNotAllowed = function methodNotAllowed() {
702
731
  return new Response("Method Not Allowed", {
703
- status: 405,
704
- headers: { Allow: "POST" }
732
+ headers: { Allow: "POST" },
733
+ status: 405
705
734
  });
706
- }
707
- function buildBundle(group, handlers, path, transport) {
735
+ };
736
+ const buildBundle = function buildBundle(group, handlers, path, transport) {
708
737
  const app = RpcServer.layerHttp({
709
738
  group,
710
739
  path,
711
740
  protocol: transport === "ws" ? "websocket" : "http"
712
741
  }).pipe(Layer.provide(handlers), Layer.provide(serialization));
713
742
  return HttpRouter.toWebHandler(app, { disableLogger: true });
714
- }
715
- function bundleFor(group, handlers, path, transport) {
743
+ };
744
+ const bundleFor = function bundleFor(group, handlers, path, transport) {
716
745
  const key = bundleKey(group, path, transport);
717
746
  const cached = bundles.get(key);
718
747
  if (cached) return cached;
719
748
  const built = buildBundle(group, handlers, path, transport);
720
749
  bundles.set(key, built);
721
750
  return built;
722
- }
723
- function scrubJsonResponse(response, requestIds) {
751
+ };
752
+ const scrubJsonResponse = function scrubJsonResponse(response, requestIds) {
724
753
  const contentType = response.headers.get("content-type") ?? "";
725
754
  if (!contentType.includes("json")) return response;
726
755
  if (response.body && (contentType.includes("application/json-rpc") || contentType.includes("ndjson"))) {
727
756
  const headers = new Headers(response.headers);
728
757
  headers.delete("content-length");
729
758
  return new Response(response.body.pipeThrough(scrubNdjsonTransform(requestIds)), {
759
+ headers,
730
760
  status: response.status,
731
- statusText: response.statusText,
732
- headers
761
+ statusText: response.statusText
733
762
  });
734
763
  }
735
764
  return response;
736
- }
737
- async function scrubBufferedJson(response, requestIds) {
765
+ };
766
+ const scrubBufferedJson = async function scrubBufferedJson(response, requestIds) {
738
767
  const text = await response.text();
739
768
  const headers = new Headers(response.headers);
740
769
  headers.delete("content-length");
741
770
  return new Response(scrubRpcJson(text, requestIds), {
771
+ headers,
742
772
  status: response.status,
743
- statusText: response.statusText,
744
- headers
773
+ statusText: response.statusText
745
774
  });
746
- }
747
- function createActionHandler(group, handlers, options = {}) {
775
+ };
776
+ const createActionHandler = function createActionHandler(group, handlers, options = {}) {
748
777
  const path = options.path ?? "/__oxide/action";
749
778
  const transport = options.transport ?? "http";
750
779
  const sameOrigin = options.sameOrigin ?? true;
751
- return async (request) => {
780
+ return async function handleActionRequest(request) {
752
781
  if (!matchesActionPath(new URL(request.url).pathname, path)) return new Response("Not Found", { status: 404 });
753
782
  if (transport === "http" && request.method !== "POST") return methodNotAllowed();
754
783
  if (sameOrigin && !isSameOrigin(request)) return forbidden();
755
- return withRequestEntry(async () => {
784
+ return await withRequestEntry(async () => {
756
785
  const rawBody = ensureNdjsonBody(await request.arrayBuffer());
757
786
  const requestIds = extractJsonRpcRequestIds(rawBody);
758
787
  const headers = new Headers(request.headers);
759
788
  headers.set("content-type", NDJSON_CONTENT);
760
789
  const forwarded = new Request(request.url, {
761
- method: request.method,
762
- headers,
763
790
  body: rawBody,
791
+ headers,
792
+ method: request.method,
764
793
  signal: request.signal
765
794
  });
766
795
  const extra = await options.createContext?.(forwarded) ?? {};
@@ -772,16 +801,16 @@ function createActionHandler(group, handlers, options = {}) {
772
801
  return response;
773
802
  });
774
803
  };
775
- }
776
- function disposeActionHandler(group, path = ACTION_PATH, transport = "http") {
804
+ };
805
+ const disposeActionHandler = function disposeActionHandler(group, path = ACTION_PATH, transport = "http") {
777
806
  const key = bundleKey(group, path, transport);
778
807
  const bundle = bundles.get(key);
779
808
  bundles.delete(key);
780
809
  return bundle?.dispose() ?? Promise.resolve();
781
- }
810
+ };
782
811
  //#endregion
783
812
  //#region src/rpc/ws.ts
784
- function parseMessage(message, maxBytes) {
813
+ const parseMessage = function parseMessage(message, maxBytes) {
785
814
  try {
786
815
  const raw = message.text();
787
816
  if (new TextEncoder().encode(raw).byteLength > maxBytes) return {
@@ -795,23 +824,26 @@ function parseMessage(message, maxBytes) {
795
824
  } catch {
796
825
  return { ok: false };
797
826
  }
798
- }
827
+ };
828
+ const isJsonRpcPing = function isJsonRpcPing(value) {
829
+ return value !== null && !Array.isArray(value) && typeof value === "object" && value["method"] === "@effect/rpc/Ping";
830
+ };
799
831
  /**
800
832
  * Effect's socket client sends `@effect/rpc/Ping` keepalives (no id) and hangs
801
833
  * up unless the server answers `@effect/rpc/Pong`. Handle control messages here
802
834
  * so they never reach the action handler.
803
835
  */
804
- function controlReply(raw) {
836
+ const controlReply = function controlReply(raw) {
805
837
  try {
806
838
  const parsed = JSON.parse(raw);
807
- if (parsed && typeof parsed === "object" && parsed.method === "@effect/rpc/Ping") return JSON.stringify({
839
+ if (isJsonRpcPing(parsed)) return JSON.stringify({
808
840
  jsonrpc: "2.0",
809
841
  method: "@effect/rpc/Pong"
810
842
  });
811
843
  } catch {}
812
- }
844
+ };
813
845
  /** Forward each complete NDJSON line as its own WS message (keeps streams incremental). */
814
- async function sendNdjsonFrames(peer, response, signal) {
846
+ const sendNdjsonFrames = async function sendNdjsonFrames(peer, response, signal) {
815
847
  if (signal.aborted) {
816
848
  await response.body?.cancel();
817
849
  return;
@@ -824,14 +856,15 @@ async function sendNdjsonFrames(peer, response, signal) {
824
856
  const reader = response.body.getReader();
825
857
  const decoder = new TextDecoder();
826
858
  let pending = "";
827
- const onAbort = () => {
859
+ const onAbort = function onAbort() {
828
860
  reader.cancel();
829
861
  };
830
862
  signal.addEventListener("abort", onAbort, { once: true });
831
- try {
832
- while (!signal.aborted) {
863
+ const pump = async function pump() {
864
+ for (;;) {
865
+ if (signal.aborted) return;
833
866
  const { done, value } = await reader.read();
834
- if (done) break;
867
+ if (done) return;
835
868
  pending += decoder.decode(value, { stream: true });
836
869
  let nl = pending.indexOf("\n");
837
870
  while (nl !== -1) {
@@ -841,6 +874,9 @@ async function sendNdjsonFrames(peer, response, signal) {
841
874
  nl = pending.indexOf("\n");
842
875
  }
843
876
  }
877
+ };
878
+ try {
879
+ await pump();
844
880
  if (!signal.aborted) {
845
881
  pending += decoder.decode();
846
882
  if (pending.length > 0) peer.send(pending.endsWith("\n") ? pending : `${pending}\n`);
@@ -848,37 +884,27 @@ async function sendNdjsonFrames(peer, response, signal) {
848
884
  } finally {
849
885
  signal.removeEventListener("abort", onAbort);
850
886
  }
851
- }
852
- function createWsHooks(group, handlers, options = {}) {
887
+ };
888
+ const createWsHooks = function createWsHooks(group, handlers, options = {}) {
853
889
  const path = options.path ?? "/__oxide/action";
854
890
  const maxBytes = options.maxMessageSize ?? 1048576;
855
891
  const sameOrigin = options.sameOrigin ?? true;
856
892
  const baseOptions = {
857
893
  path,
858
- transport: "http",
859
- sameOrigin
894
+ sameOrigin,
895
+ transport: "http"
860
896
  };
861
897
  return {
862
- upgrade(req) {
863
- let pathname;
864
- try {
865
- pathname = new URL(req.url).pathname;
866
- } catch {
867
- return new Response("Bad Request", { status: 400 });
868
- }
869
- if (!matchesActionPath(pathname, path)) return new Response("Not Found", { status: 404 });
870
- if (sameOrigin && !isSameOrigin(req)) return new Response("Forbidden", { status: 403 });
871
- },
872
898
  async message(peer, message) {
873
899
  const parsed = parseMessage(message, maxBytes);
874
900
  if (!parsed.ok) {
875
901
  peer.send(JSON.stringify({
876
- jsonrpc: "2.0",
877
902
  error: {
878
903
  code: -32600,
879
904
  message: parsed.tooLarge ? "Payload too large" : "Parse error"
880
905
  },
881
- id: null
906
+ id: null,
907
+ jsonrpc: "2.0"
882
908
  }));
883
909
  return;
884
910
  }
@@ -893,20 +919,31 @@ function createWsHooks(group, handlers, options = {}) {
893
919
  const headers = new Headers(peer.request?.headers);
894
920
  headers.set("content-type", NDJSON_CONTENT);
895
921
  const peerCtx = await options.createContext?.(peer) ?? peer.context;
896
- await sendNdjsonFrames(peer, await createActionHandler(group, handlers, {
922
+ const response = await createActionHandler(group, handlers, {
897
923
  ...baseOptions,
898
924
  createContext: (req) => ({
899
925
  ...peerCtx,
900
926
  req
901
927
  })
902
928
  })(new Request(`http://${host}${path}`, {
903
- method: "POST",
904
- headers,
905
929
  body: parsed.value,
930
+ headers,
931
+ method: "POST",
906
932
  signal: abort.signal
907
- })), 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 });
908
945
  }
909
946
  };
910
- }
947
+ };
911
948
  //#endregion
912
- 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 };