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.
- package/README.md +42 -41
- package/client.d.ts +15 -9
- package/dist/client-DOfB6wxk.mjs +207 -0
- package/dist/context-CmChiQ2W.d.mts +98 -0
- package/dist/{context-DQDDwFYi.mjs → context-DUN_VWJF.mjs} +44 -36
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +72 -51
- package/dist/plugin-BIb3YnN7.mjs +674 -0
- package/dist/plugin.d.mts +1 -1
- package/dist/plugin.mjs +1 -1
- package/dist/rpc/client.d.mts +16 -7
- package/dist/rpc/client.mjs +1 -1
- package/dist/{rpc-tYqxQToi.mjs → rpc-gCOSJXql.mjs} +337 -300
- package/dist/rpc.d.mts +37 -31
- package/dist/rpc.mjs +2 -2
- package/dist/rsbuild.d.mts +1 -1
- package/dist/rsbuild.mjs +1 -1
- package/dist/{types-BM4NAnzy.d.mts → types-BaZeMYcP.d.mts} +22 -8
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +1 -1
- package/dist/worker-dom.d.mts +1 -1
- package/dist/worker-dom.mjs +15 -8
- package/package.json +2 -2
- package/virtual.d.ts +16 -3
- package/dist/client-BgBfkZDx.mjs +0 -166
- package/dist/context-Ct8u5XUC.d.mts +0 -73
- package/dist/plugin-CurpVnGn.mjs +0 -602
|
@@ -1,48 +1,10 @@
|
|
|
1
|
-
import { c as withRequestEntry, r as runWithRequest } from "./context-
|
|
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
|
-
|
|
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,
|
|
55
|
-
return pathname ===
|
|
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*\(/
|
|
65
|
-
const STREAM_EXPORT_RE = /^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*action\s*\(\s*async\s+function\s*\*/
|
|
66
|
-
function isServerFileId(id) {
|
|
67
|
-
const file = id.split("?")[0]?.
|
|
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?)$/
|
|
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
|
|
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
|
|
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
|
-
|
|
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, "
|
|
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,
|
|
134
|
-
const opts = { url:
|
|
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(
|
|
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.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
265
|
+
`;
|
|
266
|
+
};
|
|
267
|
+
const buildAfterAction = function buildAfterAction(serveAssets, preset, envJson) {
|
|
321
268
|
const celldAfterAction = `{
|
|
322
|
-
const hit =
|
|
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
|
-
|
|
329
|
-
const hit = await
|
|
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()
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
414
|
-
|
|
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
|
|
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, "
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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/
|
|
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/
|
|
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
|
|
546
|
-
|
|
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 (
|
|
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 (!
|
|
564
|
-
if (
|
|
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
|
|
568
|
-
let id = msg
|
|
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 (
|
|
572
|
-
|
|
583
|
+
if (next.done) id = null;
|
|
584
|
+
else {
|
|
585
|
+
({value: id} = next);
|
|
573
586
|
state.remaining.delete(next.value);
|
|
574
|
-
}
|
|
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
|
-
|
|
591
|
+
error: {
|
|
592
|
+
code: scrubbedError.code,
|
|
593
|
+
message: scrubbedError.message
|
|
594
|
+
},
|
|
579
595
|
id,
|
|
580
|
-
|
|
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 =
|
|
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
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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 =
|
|
619
|
-
text = text.replace(/^\uFEFF
|
|
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 =
|
|
625
|
-
if (
|
|
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 =
|
|
630
|
-
if (Array.isArray(parsed))
|
|
631
|
-
|
|
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
|
|
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
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
704
|
-
|
|
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
|
|
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
|
-
|
|
832
|
-
|
|
863
|
+
const pump = async function pump() {
|
|
864
|
+
for (;;) {
|
|
865
|
+
if (signal.aborted) return;
|
|
833
866
|
const { done, value } = await reader.read();
|
|
834
|
-
if (done)
|
|
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
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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
|
-
}))
|
|
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 {
|
|
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 };
|