oxidejs 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/rsbuild.d.mts +1 -1
- package/dist/rsbuild.mjs +2 -1
- package/dist/{src-CkhMD9M5.mjs → src-zvdCGkyp.mjs} +108 -8
- package/dist/{types-CMEB9B4S.d.mts → types-BM4NAnzy.d.mts} +24 -2
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,27 @@ Same factory as Vite: client stubs, `/__oxide/action`, and `dist/server.js`.
|
|
|
113
113
|
|
|
114
114
|
## Options
|
|
115
115
|
|
|
116
|
+
### `middleware` and `imports`
|
|
117
|
+
|
|
118
|
+
\`\`\`ts
|
|
119
|
+
oxide({
|
|
120
|
+
middleware: ["@ilha/router/ssr"], // string or { module, imports }
|
|
121
|
+
imports: ["./side-effects"], // side-effect modules loaded at startup
|
|
122
|
+
})
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
Middleware handlers run in production before the action gate; the same specifiers are loaded through the SSR graph in dev, so dev and prod behave identically. Middleware entries may carry their own \`imports\`.
|
|
126
|
+
|
|
127
|
+
### Other server options
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Description |
|
|
130
|
+
| ------------- | ------ | ------- | -------------------------------------------------------------------- |
|
|
131
|
+
| \`bodyLimit\` | number | 1048576 | Max request body size (Node preset); larger requests get 413 |
|
|
132
|
+
| \`notFound\` | string | — | Custom HTML 404 body when no route or asset matches |
|
|
133
|
+
| \`env\` | object | — | Passed as \`env\` to \`fetch(request, env, ctx)\` on the Node preset |
|
|
134
|
+
|
|
135
|
+
## Options
|
|
136
|
+
|
|
116
137
|
| Option | Default | Notes |
|
|
117
138
|
| ------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------- |
|
|
118
139
|
| `preset` | `"fetch"` | `"fetch"` or `"celld"` |
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as OxidejsWranglerOptions, i as OxidejsPreset, n as OxidejsActionTransport, o as ResolvedOptions, r as OxidejsOptions, t as OxidejsActionHeaders } from "./types-
|
|
1
|
+
import { a as OxidejsWranglerOptions, i as OxidejsPreset, n as OxidejsActionTransport, o as ResolvedOptions, r as OxidejsOptions, t as OxidejsActionHeaders } from "./types-BM4NAnzy.mjs";
|
|
2
2
|
import { UnpluginFactory } from "unplugin";
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
type ExecutionContext = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as useCtx, c as useRequest, i as action, n as unpluginFactory, o as useEnv, r as vite, s as useFetchCtx, t as oxidejs } from "./src-
|
|
1
|
+
import { a as useCtx, c as useRequest, i as action, n as unpluginFactory, o as useEnv, r as vite, s as useFetchCtx, t as oxidejs } from "./src-zvdCGkyp.mjs";
|
|
2
2
|
export { action, oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
|
package/dist/rsbuild.d.mts
CHANGED
package/dist/rsbuild.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { t as oxidejs } from "./src-
|
|
1
|
+
import { t as oxidejs } from "./src-zvdCGkyp.mjs";
|
|
2
2
|
//#region src/rsbuild.ts
|
|
3
|
+
/** @experimental Rsbuild integration is not yet implemented. */
|
|
3
4
|
var rsbuild_default = oxidejs.rsbuild;
|
|
4
5
|
//#endregion
|
|
5
6
|
export { rsbuild_default as default };
|
|
@@ -3,7 +3,41 @@ import path from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
//#region \0rolldown/runtime.js
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __exportAll = (all, no_symbols) => {
|
|
9
|
+
let target = {};
|
|
10
|
+
for (var name in all) __defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true
|
|
13
|
+
});
|
|
14
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
15
|
+
return target;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
6
18
|
//#region src/actions.ts
|
|
19
|
+
var actions_exports = /* @__PURE__ */ __exportAll({
|
|
20
|
+
ACTION_PATH: () => ACTION_PATH,
|
|
21
|
+
RESOLVED_VIRTUAL_ACTIONS_ID: () => RESOLVED_VIRTUAL_ACTIONS_ID,
|
|
22
|
+
RESOLVED_VIRTUAL_CLIENT_ID: () => RESOLVED_VIRTUAL_CLIENT_ID,
|
|
23
|
+
RESOLVED_VIRTUAL_WORKER_ID: () => RESOLVED_VIRTUAL_WORKER_ID,
|
|
24
|
+
VIRTUAL_ACTIONS_ID: () => VIRTUAL_ACTIONS_ID,
|
|
25
|
+
VIRTUAL_CLIENT_ID: () => VIRTUAL_CLIENT_ID,
|
|
26
|
+
VIRTUAL_WORKER_ID: () => VIRTUAL_WORKER_ID,
|
|
27
|
+
generateActionsModule: () => generateActionsModule,
|
|
28
|
+
generateClientModule: () => generateClientModule,
|
|
29
|
+
generateClientStub: () => generateClientStub,
|
|
30
|
+
generateWorkerWrapper: () => generateWorkerWrapper,
|
|
31
|
+
isServerFileId: () => isServerFileId,
|
|
32
|
+
loadClientStub: () => loadClientStub,
|
|
33
|
+
moduleKey: () => moduleKey,
|
|
34
|
+
nodeToWebRequest: () => nodeToWebRequest,
|
|
35
|
+
parseExportedNames: () => parseExportedNames,
|
|
36
|
+
pluginShouldStub: () => pluginShouldStub,
|
|
37
|
+
scanServerFiles: () => scanServerFiles,
|
|
38
|
+
sendWebResponseFrom: () => sendWebResponseFrom,
|
|
39
|
+
shouldStubServerModule: () => shouldStubServerModule
|
|
40
|
+
});
|
|
7
41
|
const VIRTUAL_ACTIONS_ID = "virtual:oxide/actions";
|
|
8
42
|
const RESOLVED_VIRTUAL_ACTIONS_ID = `\0${VIRTUAL_ACTIONS_ID}`;
|
|
9
43
|
const VIRTUAL_WORKER_ID = "virtual:oxide/worker";
|
|
@@ -175,6 +209,8 @@ function generateWorkerWrapper(userWorkerAbs, opts = {}) {
|
|
|
175
209
|
const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
|
|
176
210
|
const hasActions = opts.hasActions !== false;
|
|
177
211
|
const ws = hasActions && opts.actions === "ws";
|
|
212
|
+
const bodyLimit = opts.bodyLimit ?? 1048576;
|
|
213
|
+
const __nf = `() => new Response(${JSON.stringify(opts.notFound ?? "<h1>404 Not Found</h1>")}, { status: 404, headers: { "content-type": "text/html; charset=utf-8" } })`;
|
|
178
214
|
const assetBlock = serveAssets ? `import { readFile } from "node:fs/promises";
|
|
179
215
|
import { extname, join } from "node:path";
|
|
180
216
|
const __assets = join(import.meta.dirname, ${JSON.stringify(clientDir)});
|
|
@@ -207,24 +243,32 @@ async function __asset(request, spa) {
|
|
|
207
243
|
const headers = { "content-type": __types[extname(file)] ?? "application/octet-stream" };
|
|
208
244
|
const cache = __cache(file);
|
|
209
245
|
if (cache) headers["cache-control"] = cache;
|
|
246
|
+
const etag = '"' + body.length.toString(16) + "-" + file + '"';
|
|
247
|
+
headers["etag"] = etag;
|
|
248
|
+
if (request.headers.get("if-none-match") === etag) {
|
|
249
|
+
return new Response(null, { status: 304, headers });
|
|
250
|
+
}
|
|
210
251
|
return new Response(body, { headers });
|
|
211
252
|
} catch {
|
|
212
253
|
return;
|
|
213
254
|
}
|
|
214
255
|
}
|
|
256
|
+
const __nf = ${__nf};
|
|
215
257
|
` : "";
|
|
258
|
+
const envJson = JSON.stringify(opts.env ?? {});
|
|
216
259
|
const afterAction = serveAssets ? `if (typeof user.fetch === "function") {
|
|
217
|
-
const hit = await user.fetch(request, env, ctx);
|
|
260
|
+
const hit = await user.fetch(request, env ?? ${envJson}, ctx);
|
|
218
261
|
if (hit) return hit;
|
|
219
262
|
}
|
|
220
|
-
return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ??
|
|
263
|
+
return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ?? __nf();` : `return typeof user.fetch === "function"
|
|
221
264
|
? user.fetch(request, env, ctx)
|
|
222
|
-
:
|
|
265
|
+
: __nf();`;
|
|
223
266
|
const listen = preset === "fetch" ? `
|
|
224
267
|
import { createServer } from "node:http";
|
|
225
268
|
import { pathToFileURL } from "node:url";
|
|
226
269
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
227
270
|
const port = Number(process.env.PORT) || 3000;
|
|
271
|
+
const bodyLimit = ${bodyLimit};
|
|
228
272
|
const server = createServer(async (req, res) => {
|
|
229
273
|
const url = \`http://\${req.headers.host ?? "localhost"}\${req.url ?? "/"}\`;
|
|
230
274
|
const headers = new Headers();
|
|
@@ -237,7 +281,13 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
237
281
|
req.once("aborted", () => ac.abort());
|
|
238
282
|
const method = req.method ?? "GET";
|
|
239
283
|
const chunks = [];
|
|
240
|
-
|
|
284
|
+
let size = 0;
|
|
285
|
+
if (method !== "GET" && method !== "HEAD") for await (const chunk of req) {
|
|
286
|
+
size += chunk.length;
|
|
287
|
+
// Bound request buffering — unbounded bodies are a memory DoS vector.
|
|
288
|
+
if (size > ${bodyLimit}) { res.statusCode = 413; res.end(); return; }
|
|
289
|
+
chunks.push(chunk);
|
|
290
|
+
}
|
|
241
291
|
const init = { method, headers, signal: ac.signal };
|
|
242
292
|
if (chunks.length) init.body = Buffer.concat(chunks);
|
|
243
293
|
const response = await app.fetch(new Request(url, init));
|
|
@@ -260,6 +310,12 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
260
310
|
});` : ""}
|
|
261
311
|
server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
|
|
262
312
|
}
|
|
313
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
314
|
+
process.on(signal, () => {
|
|
315
|
+
server.close(() => process.exit(0));
|
|
316
|
+
setTimeout(() => process.exit(0), 5000).unref();
|
|
317
|
+
});
|
|
318
|
+
}
|
|
263
319
|
` : "";
|
|
264
320
|
const actionImports = hasActions ? ws ? `import { handle as handleWs } from "tacho/transport/ws";
|
|
265
321
|
import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
|
|
@@ -274,14 +330,18 @@ const __rpc = handle(actions, { path: ${JSON.stringify(actionPath)}${sameOrigin
|
|
|
274
330
|
return __rpc(request);
|
|
275
331
|
}
|
|
276
332
|
` : "";
|
|
277
|
-
const
|
|
333
|
+
const ILHA_SSR_IMPLICIT = ["ilha:pages/server", "ilha:loaders"];
|
|
334
|
+
const middlewareImports = (opts.middleware ?? []).map((m) => typeof m === "string" ? {
|
|
335
|
+
module: m,
|
|
336
|
+
imports: m === "@ilha/router/ssr" ? ILHA_SSR_IMPLICIT : []
|
|
337
|
+
} : 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";
|
|
278
338
|
const middlewareList = (opts.middleware ?? []).map((_, i) => `__mw${i}`).join(", ");
|
|
279
339
|
const middlewareGate = opts.middleware?.length ? `for (const __mw of [${middlewareList}]) {
|
|
280
340
|
const hit = await __mw(request, { env, ctx });
|
|
281
341
|
if (hit) return hit;
|
|
282
342
|
}
|
|
283
343
|
` : "";
|
|
284
|
-
return `export * from ${JSON.stringify(userWorkerAbs)};
|
|
344
|
+
return `${(opts.imports ?? []).map((spec) => `import ${JSON.stringify(spec)};`).join("\n")}export * from ${JSON.stringify(userWorkerAbs)};
|
|
285
345
|
import user from ${JSON.stringify(userWorkerAbs)};
|
|
286
346
|
${middlewareImports}${actionImports}${assetBlock}const app = {
|
|
287
347
|
...user,
|
|
@@ -470,7 +530,11 @@ function resolveOptions(raw, root, config) {
|
|
|
470
530
|
actionPath,
|
|
471
531
|
actionSameOrigin,
|
|
472
532
|
actionHeaders: raw?.actionHeaders,
|
|
473
|
-
middleware: raw?.middleware ?? []
|
|
533
|
+
middleware: raw?.middleware ?? [],
|
|
534
|
+
imports: raw?.imports ?? [],
|
|
535
|
+
bodyLimit: raw?.bodyLimit ?? 1048576,
|
|
536
|
+
notFound: raw?.notFound,
|
|
537
|
+
env: raw?.env
|
|
474
538
|
};
|
|
475
539
|
}
|
|
476
540
|
function copyPublicDir(opts) {
|
|
@@ -745,7 +809,11 @@ const unpluginFactory = (options) => {
|
|
|
745
809
|
actions: resolved.actions,
|
|
746
810
|
actionPath: resolved.actionPath,
|
|
747
811
|
actionSameOrigin: resolved.actionSameOrigin,
|
|
748
|
-
middleware: resolved.middleware
|
|
812
|
+
middleware: resolved.middleware,
|
|
813
|
+
imports: resolved.imports,
|
|
814
|
+
bodyLimit: resolved.bodyLimit,
|
|
815
|
+
notFound: resolved.notFound,
|
|
816
|
+
env: resolved.env
|
|
749
817
|
});
|
|
750
818
|
}
|
|
751
819
|
if (isServerFileId(id) && pluginShouldStub(this, extra)) {
|
|
@@ -779,6 +847,38 @@ const unpluginFactory = (options) => {
|
|
|
779
847
|
const loadRouter = async () => {
|
|
780
848
|
return (await server.ssrLoadModule(VIRTUAL_ACTIONS_ID)).default;
|
|
781
849
|
};
|
|
850
|
+
if ((resolved?.middleware?.length ?? 0) > 0 || (resolved?.imports?.length ?? 0) > 0) (async () => {
|
|
851
|
+
try {
|
|
852
|
+
for (const spec of resolved.imports ?? []) await server.ssrLoadModule(spec);
|
|
853
|
+
const handlers = [];
|
|
854
|
+
for (const entry of resolved.middleware ?? []) {
|
|
855
|
+
const spec2 = typeof entry === "string" ? entry : entry.module;
|
|
856
|
+
const mod = await server.ssrLoadModule(spec2);
|
|
857
|
+
if (typeof mod.default !== "function") continue;
|
|
858
|
+
const fn = mod.default;
|
|
859
|
+
handlers.push((request) => Promise.resolve(fn(request)));
|
|
860
|
+
}
|
|
861
|
+
if (handlers.length === 0) return;
|
|
862
|
+
const { nodeToWebRequest, sendWebResponseFrom } = await Promise.resolve().then(() => actions_exports);
|
|
863
|
+
server.middlewares.use((creq, cres, next) => {
|
|
864
|
+
(async () => {
|
|
865
|
+
try {
|
|
866
|
+
const request = await nodeToWebRequest(creq);
|
|
867
|
+
for (const handler of handlers) {
|
|
868
|
+
const hit = await handler(request);
|
|
869
|
+
if (hit) return sendWebResponseFrom(creq, cres, hit);
|
|
870
|
+
}
|
|
871
|
+
next();
|
|
872
|
+
} catch (error) {
|
|
873
|
+
cres.statusCode = 500;
|
|
874
|
+
cres.end(String(error));
|
|
875
|
+
}
|
|
876
|
+
})();
|
|
877
|
+
});
|
|
878
|
+
} catch (error) {
|
|
879
|
+
server.config.logger.error("oxidejs: failed to wire dev middleware: " + String(error));
|
|
880
|
+
}
|
|
881
|
+
})();
|
|
782
882
|
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter, resolved.actionPath, resolved.actionSameOrigin);
|
|
783
883
|
else server.middlewares.use(actionMiddleware(loadRouter, resolved.actionPath, resolved.actionSameOrigin));
|
|
784
884
|
},
|
|
@@ -40,7 +40,22 @@ interface OxidejsOptions {
|
|
|
40
40
|
/** Module specifiers whose default export is `(request, ctx) => Response | undefined | Promise<Response | undefined>`.
|
|
41
41
|
* Tried in order at the top of the production fetch handler; a Response short-circuits.
|
|
42
42
|
* Dev servers use connect middleware instead. */
|
|
43
|
-
middleware?: string
|
|
43
|
+
middleware?: (string | {
|
|
44
|
+
module: string;
|
|
45
|
+
imports?: string[];
|
|
46
|
+
})[];
|
|
47
|
+
/** Module specifiers imported for side effects at the top of the production
|
|
48
|
+
* server bundle (e.g. virtual modules that self-register handlers). */
|
|
49
|
+
imports?: string[];
|
|
50
|
+
/** Max request body size in bytes (Node preset). Larger requests get 413.
|
|
51
|
+
* Default: 1048576 (1 MiB). */
|
|
52
|
+
bodyLimit?: number;
|
|
53
|
+
/** Custom 404 body (HTML) served when no route, asset, or user fetch
|
|
54
|
+
* handled the request (fetch preset with client assets). */
|
|
55
|
+
notFound?: string;
|
|
56
|
+
/** Extra env passed as the second argument to fetch(request, env, ctx) on
|
|
57
|
+
* the Node fetch preset — read it with useEnv(). */
|
|
58
|
+
env?: Record<string, unknown>;
|
|
44
59
|
}
|
|
45
60
|
interface ResolvedOptions {
|
|
46
61
|
root: string;
|
|
@@ -63,7 +78,14 @@ interface ResolvedOptions {
|
|
|
63
78
|
/** Reject cross-origin action requests (CSRF defense). Default: true. */
|
|
64
79
|
actionSameOrigin: boolean;
|
|
65
80
|
actionHeaders: OxidejsActionHeaders | undefined;
|
|
66
|
-
middleware: string
|
|
81
|
+
middleware: (string | {
|
|
82
|
+
module: string;
|
|
83
|
+
imports?: string[];
|
|
84
|
+
})[];
|
|
85
|
+
imports: string[];
|
|
86
|
+
bodyLimit: number;
|
|
87
|
+
notFound: string | undefined;
|
|
88
|
+
env: Record<string, unknown> | undefined;
|
|
67
89
|
}
|
|
68
90
|
//#endregion
|
|
69
91
|
export { OxidejsWranglerOptions as a, OxidejsPreset as i, OxidejsActionTransport as n, ResolvedOptions as o, OxidejsOptions as r, OxidejsActionHeaders as t };
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as OxidejsOptions } from "./types-
|
|
1
|
+
import { r as OxidejsOptions } from "./types-BM4NAnzy.mjs";
|
|
2
2
|
//#region src/vite.d.ts
|
|
3
3
|
declare const _default: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
|
|
4
4
|
//#endregion
|
package/dist/vite.mjs
CHANGED