oxidejs 0.1.6 → 0.1.8
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 +36 -31
- package/dist/index.d.mts +10 -4
- package/dist/index.mjs +2 -2
- package/dist/rsbuild.d.mts +1 -1
- package/dist/rsbuild.mjs +1 -1
- package/dist/{src-CU7sBRGf.mjs → src-Cg_j9ZsH.mjs} +77 -31
- package/dist/{types-CFWyYfds.d.mts → types-C3ualQ39.d.mts} +14 -2
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ vite build
|
|
|
34
34
|
node dist/server.js
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Default preset is `"fetch"`. No `index.html` → only `dist/server.js`. With `index.html` → client to `dist/client/`, then `/
|
|
37
|
+
Default preset is `"fetch"`. No `index.html` → only `dist/server.js`. With `index.html` → client to `dist/client/`, then `/__oxide/action` (if you have a `*.server.{ts,tsx,js,jsx}` file) → `src/server.ts` (`undefined` continues) → static file → `index.html` for navigations. `public/` is copied next to the client. Hashed assets get `Cache-Control: immutable`. No `wrangler.jsonc`.
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
40
|
oxide({
|
|
@@ -47,18 +47,21 @@ oxide({
|
|
|
47
47
|
|
|
48
48
|
## Server actions
|
|
49
49
|
|
|
50
|
-
Install `tacho` if you use actions. Files named `*.server.ts
|
|
50
|
+
Install `tacho` if you use actions. Files named `*.server.ts`, `*.server.tsx`, `*.server.js`, or `*.server.jsx` are server-only. A client import is replaced with a tacho stub that POSTs `/__oxide/action`. The original module never enters the client graph. **Only exports wrapped in `action()` become remote actions** — any other export stays server-local and is not callable over the wire. Server and Vite SSR (`import.meta.env.SSR === true`) keep the real functions. Methods are `<file>.<fn>` (`test.ping`). Call `useRequest()` inside an action for the inbound `Request`. `useCtx()` is tacho `ctx` (`{ req }` plus anything middleware or `createContext` added). On `preset: "celld"`, `useEnv()` and `useFetchCtx()` are the Worker `env` and `ctx` from `fetch(request, env, ctx)` — same values as `useCtx().env` / `useCtx().fetchCtx`. Return `undefined` from `src/server.ts` to fall through to static files. No server action files → the bundle does not import tacho.
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
53
|
// src/test.server.ts
|
|
54
|
-
import { useRequest } from "oxidejs";
|
|
54
|
+
import { action, useRequest } from "oxidejs";
|
|
55
55
|
|
|
56
|
-
export
|
|
56
|
+
export const who = action(async () => {
|
|
57
57
|
return useRequest().headers.get("x-user");
|
|
58
|
-
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const ping = action(async () => "pong");
|
|
59
61
|
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
+
// a non-action export is never exposed over the wire
|
|
63
|
+
async function internalHelper() {
|
|
64
|
+
/* server-only */
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
// src/client.ts
|
|
@@ -73,15 +76,16 @@ export default {
|
|
|
73
76
|
};
|
|
74
77
|
```
|
|
75
78
|
|
|
76
|
-
`async function*`
|
|
79
|
+
`action()` is identity — it only marks the export. Wrap `async function*` in it to stream over tacho SSE. `oxidejs/tsconfig` makes `await ticks()` typecheck. Pass `{ signal }` last on any action to abort the fetch. Types come from the real `*.server.ts`, so declare the last argument there:
|
|
77
80
|
|
|
78
81
|
```ts
|
|
79
82
|
// src/test.server.ts
|
|
83
|
+
import { action } from "oxidejs";
|
|
80
84
|
import type { ActionOptions } from "oxidejs";
|
|
81
85
|
|
|
82
|
-
export async function*
|
|
86
|
+
export const ticks = action(async function* (n: number, _opts?: ActionOptions) {
|
|
83
87
|
for (let i = 0; i < n; i++) yield i;
|
|
84
|
-
}
|
|
88
|
+
});
|
|
85
89
|
|
|
86
90
|
// src/client.ts
|
|
87
91
|
import { ticks } from "./test.server";
|
|
@@ -91,7 +95,7 @@ const stream = await ticks(10, { signal: ac.signal });
|
|
|
91
95
|
ac.abort();
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
`vite dev` and `rsbuild dev` serve
|
|
98
|
+
`vite dev` and `rsbuild dev` serve the endpoint via middleware. `actions: "http"` (default) serves `/__oxide/action`; `actions: "ws"` uses a WebSocket instead (needs `crossws`; not with `preset: "celld"`). `actions.sameOrigin` defaults to `true` for both transports; set it to `false` only when you intentionally accept cross-origin requests. Set `actions.path` to move the endpoint. `actionHeaders` are static headers on the shared HTTP client.
|
|
95
99
|
|
|
96
100
|
## Rsbuild
|
|
97
101
|
|
|
@@ -105,26 +109,26 @@ export default defineConfig({
|
|
|
105
109
|
});
|
|
106
110
|
```
|
|
107
111
|
|
|
108
|
-
Same factory as Vite: client stubs, `/
|
|
112
|
+
Same factory as Vite: client stubs, `/__oxide/action`, and `dist/server.js`.
|
|
109
113
|
|
|
110
114
|
## Options
|
|
111
115
|
|
|
112
|
-
| Option | Default | Notes
|
|
113
|
-
| ------------------------------ | ------------------------ |
|
|
114
|
-
| `preset` | `"fetch"` | `"fetch"` or `"celld"`
|
|
115
|
-
| `workerEntry` | `src/server.ts` | Relative to project root
|
|
116
|
-
| `outDir` | `dist` | Output root
|
|
117
|
-
| `clientDir` | `client` | Must stay inside `outDir`
|
|
118
|
-
| `wrangler.name` | required if `emitConfig` |
|
|
119
|
-
| `wrangler.compatibility_date` | required if `emitConfig` |
|
|
120
|
-
| `wrangler.compatibility_flags` | — | optional
|
|
121
|
-
| `wrangler.durable_objects` | — | optional
|
|
122
|
-
| `wrangler.migrations` | — | optional
|
|
123
|
-
| `wrangler.services` | — | optional
|
|
124
|
-
| `wrangler.vars` | — | optional
|
|
125
|
-
| `emitConfig` | `true` on `celld` | Set `false` to skip `wrangler.jsonc`
|
|
126
|
-
| `actions` | `"http"` | `"ws"` needs `crossws`;
|
|
127
|
-
| `actionHeaders` | — | Static headers on the HTTP client
|
|
116
|
+
| Option | Default | Notes |
|
|
117
|
+
| ------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `preset` | `"fetch"` | `"fetch"` or `"celld"` |
|
|
119
|
+
| `workerEntry` | `src/server.ts` | Relative to project root |
|
|
120
|
+
| `outDir` | `dist` | Output root |
|
|
121
|
+
| `clientDir` | `client` | Must stay inside `outDir` |
|
|
122
|
+
| `wrangler.name` | required if `emitConfig` | |
|
|
123
|
+
| `wrangler.compatibility_date` | required if `emitConfig` | |
|
|
124
|
+
| `wrangler.compatibility_flags` | — | optional |
|
|
125
|
+
| `wrangler.durable_objects` | — | optional |
|
|
126
|
+
| `wrangler.migrations` | — | optional |
|
|
127
|
+
| `wrangler.services` | — | optional |
|
|
128
|
+
| `wrangler.vars` | — | optional |
|
|
129
|
+
| `emitConfig` | `true` on `celld` | Set `false` to skip `wrangler.jsonc` |
|
|
130
|
+
| `actions` | `"http"` | `"ws"` needs `crossws`; object form: `{ transport, path, sameOrigin }` (`sameOrigin: true`) |
|
|
131
|
+
| `actionHeaders` | — | Static headers on the HTTP client |
|
|
128
132
|
|
|
129
133
|
`main` is always `./server.js`. `assets` is added only when `index.html` exists. Unknown wrangler keys fail at build time.
|
|
130
134
|
|
|
@@ -152,10 +156,11 @@ The generated server serves static files from `dist/client/` (or the `public/` d
|
|
|
152
156
|
|
|
153
157
|
The generated `__asset` function uses `path.join` — not `path.resolve` — so a leading `/` in the relative path stays inside the asset root.
|
|
154
158
|
|
|
155
|
-
### Server actions (`*.server.ts`)
|
|
159
|
+
### Server actions (`*.server.{ts,tsx,js,jsx}`)
|
|
156
160
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
161
|
+
- Server action code is **never bundled into the client**. Client imports are replaced with tacho stubs that POST the action endpoint (default `/__oxide/action`). The original source stays server-only.
|
|
162
|
+
- Only `action()`-wrapped exports are exposed as RPC; other exports stay server-local.
|
|
163
|
+
- The endpoint is POST-only. Non-POST requests return `405`.
|
|
159
164
|
- Method dispatch uses `Object.hasOwn`, blocking `__proto__` / `constructor` walks.
|
|
160
165
|
- Unknown or missing content-types → `415`.
|
|
161
166
|
- Body size capped at 1 MB by default (enforced on the actual body, not just `Content-Length`).
|
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-C3ualQ39.mjs";
|
|
2
2
|
import { UnpluginFactory } from "unplugin";
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
type ExecutionContext = {
|
|
@@ -12,9 +12,9 @@ type ActionContext = {
|
|
|
12
12
|
fetchCtx?: ExecutionContext;
|
|
13
13
|
[key: string]: unknown;
|
|
14
14
|
};
|
|
15
|
-
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/
|
|
15
|
+
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
16
16
|
declare function useCtx<C extends ActionContext = ActionContext>(): C;
|
|
17
|
-
/** Current action `Request`. Throws outside `*.server.ts` running over `/
|
|
17
|
+
/** Current action `Request`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
18
18
|
declare function useRequest(): Request;
|
|
19
19
|
/** Worker `env` from `fetch(request, env, ctx)`. `undefined` on the Node fetch preset. */
|
|
20
20
|
declare function useEnv<E = unknown>(): E | undefined;
|
|
@@ -24,10 +24,16 @@ declare function useFetchCtx(): ExecutionContext | undefined;
|
|
|
24
24
|
type ActionOptions = {
|
|
25
25
|
signal?: AbortSignal;
|
|
26
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* Marks a `*.server.ts` export as a remote RPC action. Identity: returns `fn` unchanged.
|
|
29
|
+
* Only exports wrapped in `action()` become callable over the wire; other exports stay
|
|
30
|
+
* server-local. Wrap async functions and async generators.
|
|
31
|
+
*/
|
|
32
|
+
declare function action<T>(fn: T): T;
|
|
27
33
|
//#endregion
|
|
28
34
|
//#region src/index.d.ts
|
|
29
35
|
declare const unpluginFactory: UnpluginFactory<OxidejsOptions | undefined>;
|
|
30
36
|
declare const oxidejs: import("unplugin").UnpluginInstance<OxidejsOptions | undefined, boolean>;
|
|
31
37
|
declare const vite: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
|
|
32
38
|
//#endregion
|
|
33
|
-
export { type ActionContext, type ActionOptions, type ExecutionContext, type OxidejsActionHeaders, type OxidejsActionTransport, type OxidejsOptions, type OxidejsPreset, type OxidejsWranglerOptions, type ResolvedOptions, oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
|
|
39
|
+
export { type ActionContext, type ActionOptions, type ExecutionContext, type OxidejsActionHeaders, type OxidejsActionTransport, type OxidejsOptions, type OxidejsPreset, type OxidejsWranglerOptions, type ResolvedOptions, action, oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
|
|
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-Cg_j9ZsH.mjs";
|
|
2
|
+
export { action, oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
|
package/dist/rsbuild.d.mts
CHANGED
package/dist/rsbuild.mjs
CHANGED
|
@@ -10,25 +10,31 @@ const VIRTUAL_WORKER_ID = "virtual:oxide/worker";
|
|
|
10
10
|
const RESOLVED_VIRTUAL_WORKER_ID = `\0${VIRTUAL_WORKER_ID}`;
|
|
11
11
|
const VIRTUAL_CLIENT_ID = "virtual:oxide/client";
|
|
12
12
|
const RESOLVED_VIRTUAL_CLIENT_ID = `\0${VIRTUAL_CLIENT_ID}`;
|
|
13
|
-
const ACTION_PATH = "/
|
|
13
|
+
const ACTION_PATH = "/__oxide/action";
|
|
14
14
|
const IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
15
15
|
"node_modules",
|
|
16
16
|
"dist",
|
|
17
17
|
".git",
|
|
18
18
|
".wrangler"
|
|
19
19
|
]);
|
|
20
|
-
const
|
|
20
|
+
/** Only `export const name = action(...)` become remote RPC actions. Everything else stays server-local. */
|
|
21
|
+
const EXPORT_RE = /^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?action\s*\(/gm;
|
|
21
22
|
function isServerFileId(id) {
|
|
22
23
|
const file = id.split("?")[0]?.replace(/\\/g, "/") ?? "";
|
|
23
|
-
return
|
|
24
|
+
return [
|
|
25
|
+
".ts",
|
|
26
|
+
".tsx",
|
|
27
|
+
".js",
|
|
28
|
+
".jsx"
|
|
29
|
+
].some((ext) => file.endsWith(`.server${ext}`));
|
|
24
30
|
}
|
|
25
31
|
function moduleKey(absFile) {
|
|
26
|
-
return path.basename(absFile).replace(/\.server\.(
|
|
32
|
+
return path.basename(absFile).replace(/\.server\.(?:[jt]sx?)$/i, "");
|
|
27
33
|
}
|
|
28
34
|
function parseExportedNames(source) {
|
|
29
35
|
const names = /* @__PURE__ */ new Set();
|
|
30
36
|
for (const match of source.matchAll(EXPORT_RE)) {
|
|
31
|
-
const name = match[1]
|
|
37
|
+
const name = match[1];
|
|
32
38
|
if (name) names.add(name);
|
|
33
39
|
}
|
|
34
40
|
return [...names];
|
|
@@ -70,13 +76,13 @@ function scanServerFiles(root) {
|
|
|
70
76
|
}
|
|
71
77
|
return modules;
|
|
72
78
|
}
|
|
73
|
-
function generateClientModule(transport = "http", headers) {
|
|
79
|
+
function generateClientModule(transport = "http", headers, path = ACTION_PATH) {
|
|
74
80
|
if (transport === "ws") return `import { createClient } from "tacho/client/ws";
|
|
75
81
|
const __proto = typeof location === "undefined" ? "ws:" : location.protocol === "https:" ? "wss:" : "ws:";
|
|
76
82
|
const __host = typeof location === "undefined" ? "localhost" : location.host;
|
|
77
|
-
export const client = createClient({ url: __proto + "//" + __host + ${JSON.stringify(
|
|
83
|
+
export const client = createClient({ url: __proto + "//" + __host + ${JSON.stringify(path)} });
|
|
78
84
|
`;
|
|
79
|
-
const opts = { url:
|
|
85
|
+
const opts = { url: path };
|
|
80
86
|
if (headers) opts.headers = headers;
|
|
81
87
|
return `import { createClient } from "tacho/client/http";
|
|
82
88
|
export const client = createClient(${JSON.stringify(opts)});
|
|
@@ -97,7 +103,8 @@ function generateActionsModule(modules, opts) {
|
|
|
97
103
|
const lines = [
|
|
98
104
|
`import { AsyncLocalStorage } from "node:async_hooks";`,
|
|
99
105
|
`import { tacho } from "tacho";`,
|
|
100
|
-
`const
|
|
106
|
+
`const __alsKey = Symbol.for("oxidejs.requestContext");`,
|
|
107
|
+
`const __als = globalThis[__alsKey] ??= new AsyncLocalStorage();`
|
|
101
108
|
];
|
|
102
109
|
const aliases = modules.map((mod, i) => {
|
|
103
110
|
const alias = `__m${i}`;
|
|
@@ -163,6 +170,8 @@ function pipeResponse(req, res, response) {
|
|
|
163
170
|
function generateWorkerWrapper(userWorkerAbs, opts = {}) {
|
|
164
171
|
const preset = opts.preset ?? "fetch";
|
|
165
172
|
const clientDir = opts.clientDir ?? "client";
|
|
173
|
+
const actionPath = opts.actionPath ?? "/__oxide/action";
|
|
174
|
+
const sameOrigin = opts.actionSameOrigin ?? false;
|
|
166
175
|
const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
|
|
167
176
|
const hasActions = opts.hasActions !== false;
|
|
168
177
|
const ws = hasActions && opts.actions === "ws";
|
|
@@ -246,7 +255,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
246
255
|
import("crossws/adapters/node").then(({ default: crossws }) => {
|
|
247
256
|
const ws = crossws({ hooks: __ws });
|
|
248
257
|
server.on("upgrade", (req, socket, head) => {
|
|
249
|
-
if (req.url?.split("?")[0] === ${JSON.stringify(
|
|
258
|
+
if (req.url?.split("?")[0] === ${JSON.stringify(actionPath)}) ws.handleUpgrade(req, socket, head);
|
|
250
259
|
});
|
|
251
260
|
});` : ""}
|
|
252
261
|
server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
|
|
@@ -254,13 +263,13 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
254
263
|
` : "";
|
|
255
264
|
const actionImports = hasActions ? ws ? `import { handle as handleWs } from "tacho/transport/ws";
|
|
256
265
|
import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
|
|
257
|
-
const __ws = handleWs(actions, { path: ${JSON.stringify(
|
|
266
|
+
const __ws = handleWs(actions, { path: ${JSON.stringify(actionPath)}${sameOrigin ? `, sameOrigin: true` : ``} });
|
|
258
267
|
` : `import { handle } from "tacho/transport/fetch";
|
|
259
268
|
import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
|
|
260
269
|
const __fetch = Symbol.for("oxidejs.fetch");
|
|
261
|
-
const __rpc = handle(actions, { path: ${JSON.stringify(
|
|
270
|
+
const __rpc = handle(actions, { path: ${JSON.stringify(actionPath)}${sameOrigin ? `, sameOrigin: true` : ``}, createContext: (req) => req[__fetch] ?? {} });
|
|
262
271
|
` : "";
|
|
263
|
-
const actionGate = hasActions && !ws ? `if (new URL(request.url).pathname === ${JSON.stringify(
|
|
272
|
+
const actionGate = hasActions && !ws ? `if (new URL(request.url).pathname === ${JSON.stringify(actionPath)}) {
|
|
264
273
|
request[__fetch] = { env, fetchCtx: ctx };
|
|
265
274
|
return __rpc(request);
|
|
266
275
|
}
|
|
@@ -403,11 +412,30 @@ function hasHtmlEntry(root, config) {
|
|
|
403
412
|
return file.endsWith(".html") && fs.existsSync(file);
|
|
404
413
|
});
|
|
405
414
|
}
|
|
415
|
+
function resolveActions(raw) {
|
|
416
|
+
if (raw === void 0 || typeof raw === "string") {
|
|
417
|
+
const transport = raw ?? "http";
|
|
418
|
+
if (transport !== "http" && transport !== "ws") throw new Error(`oxidejs: unknown actions transport "${String(transport)}"`);
|
|
419
|
+
return {
|
|
420
|
+
transport,
|
|
421
|
+
path: ACTION_PATH,
|
|
422
|
+
sameOrigin: true
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const transport = raw.transport ?? "http";
|
|
426
|
+
if (transport !== "http" && transport !== "ws") throw new Error(`oxidejs: unknown actions transport "${String(transport)}"`);
|
|
427
|
+
const path = raw.path ?? "/__oxide/action";
|
|
428
|
+
if (!path.startsWith("/") || path.includes("?")) throw new Error(`oxidejs: actions.path must start with "/" and contain no query string (got "${path}")`);
|
|
429
|
+
return {
|
|
430
|
+
transport,
|
|
431
|
+
path,
|
|
432
|
+
sameOrigin: raw.sameOrigin ?? true
|
|
433
|
+
};
|
|
434
|
+
}
|
|
406
435
|
function resolveOptions(raw, root, config) {
|
|
407
436
|
const preset = raw?.preset ?? "fetch";
|
|
408
437
|
if (preset !== "fetch" && preset !== "celld") throw new Error(`oxidejs: unknown preset "${String(preset)}"`);
|
|
409
|
-
const actions = raw?.actions
|
|
410
|
-
if (actions !== "http" && actions !== "ws") throw new Error(`oxidejs: unknown actions transport "${String(actions)}"`);
|
|
438
|
+
const { transport: actions, path: actionPath, sameOrigin: actionSameOrigin } = resolveActions(raw?.actions);
|
|
411
439
|
if (actions === "ws" && preset === "celld") throw new Error("oxidejs: actions: \"ws\" is not supported with preset: \"celld\"");
|
|
412
440
|
const workerEntry = raw?.workerEntry ?? "src/server.ts";
|
|
413
441
|
const outDirInput = raw?.outDir ?? "dist";
|
|
@@ -432,6 +460,8 @@ function resolveOptions(raw, root, config) {
|
|
|
432
460
|
hasClient,
|
|
433
461
|
hasPublic,
|
|
434
462
|
actions,
|
|
463
|
+
actionPath,
|
|
464
|
+
actionSameOrigin,
|
|
435
465
|
actionHeaders: raw?.actionHeaders
|
|
436
466
|
};
|
|
437
467
|
}
|
|
@@ -576,7 +606,7 @@ function applyRsbuildEnvironments(config, opts) {
|
|
|
576
606
|
}
|
|
577
607
|
//#endregion
|
|
578
608
|
//#region src/context.ts
|
|
579
|
-
const ALS_KEY = "
|
|
609
|
+
const ALS_KEY = Symbol.for("oxidejs.requestContext");
|
|
580
610
|
function als() {
|
|
581
611
|
const g = globalThis;
|
|
582
612
|
return g[ALS_KEY] ??= new AsyncLocalStorage();
|
|
@@ -586,11 +616,11 @@ function store() {
|
|
|
586
616
|
if (!current) throw new Error("oxidejs: useRequest() called outside an action");
|
|
587
617
|
return current;
|
|
588
618
|
}
|
|
589
|
-
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/
|
|
619
|
+
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
590
620
|
function useCtx() {
|
|
591
621
|
return store();
|
|
592
622
|
}
|
|
593
|
-
/** Current action `Request`. Throws outside `*.server.ts` running over `/
|
|
623
|
+
/** Current action `Request`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
594
624
|
function useRequest() {
|
|
595
625
|
return store().req;
|
|
596
626
|
}
|
|
@@ -602,11 +632,19 @@ function useEnv() {
|
|
|
602
632
|
function useFetchCtx() {
|
|
603
633
|
return store().fetchCtx;
|
|
604
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* Marks a `*.server.ts` export as a remote RPC action. Identity: returns `fn` unchanged.
|
|
637
|
+
* Only exports wrapped in `action()` become callable over the wire; other exports stay
|
|
638
|
+
* server-local. Wrap async functions and async generators.
|
|
639
|
+
*/
|
|
640
|
+
function action(fn) {
|
|
641
|
+
return fn;
|
|
642
|
+
}
|
|
605
643
|
//#endregion
|
|
606
644
|
//#region src/index.ts
|
|
607
|
-
function actionMiddleware(loadRouter) {
|
|
645
|
+
function actionMiddleware(loadRouter, path, sameOrigin) {
|
|
608
646
|
return (req, res, next) => {
|
|
609
|
-
if ((req.url ?? "").split("?")[0] !==
|
|
647
|
+
if ((req.url ?? "").split("?")[0] !== path) {
|
|
610
648
|
next();
|
|
611
649
|
return;
|
|
612
650
|
}
|
|
@@ -615,11 +653,14 @@ function actionMiddleware(loadRouter) {
|
|
|
615
653
|
/* @vite-ignore */
|
|
616
654
|
"tacho/transport/fetch"
|
|
617
655
|
);
|
|
618
|
-
await sendWebResponseFrom(req, res, await handle(await loadRouter(), {
|
|
656
|
+
await sendWebResponseFrom(req, res, await handle(await loadRouter(), {
|
|
657
|
+
path,
|
|
658
|
+
...sameOrigin ? { sameOrigin: true } : {}
|
|
659
|
+
})(await nodeToWebRequest(req)));
|
|
619
660
|
})().catch(next);
|
|
620
661
|
};
|
|
621
662
|
}
|
|
622
|
-
function attachActionUpgrade(httpServer, loadRouter) {
|
|
663
|
+
function attachActionUpgrade(httpServer, loadRouter, path, sameOrigin) {
|
|
623
664
|
if (!httpServer) return;
|
|
624
665
|
Promise.all([import(
|
|
625
666
|
/* @vite-ignore */
|
|
@@ -629,8 +670,11 @@ function attachActionUpgrade(httpServer, loadRouter) {
|
|
|
629
670
|
"crossws/adapters/node"
|
|
630
671
|
)]).then(([{ handle }, { default: crossws }]) => {
|
|
631
672
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
632
|
-
if ((req.url ?? "").split("?")[0] !==
|
|
633
|
-
loadRouter().then((router) => crossws({ hooks: handle(router, {
|
|
673
|
+
if ((req.url ?? "").split("?")[0] !== path) return;
|
|
674
|
+
loadRouter().then((router) => crossws({ hooks: handle(router, {
|
|
675
|
+
path,
|
|
676
|
+
sameOrigin
|
|
677
|
+
}) }).handleUpgrade(req, socket, head)).catch(() => {
|
|
634
678
|
socket.destroy();
|
|
635
679
|
});
|
|
636
680
|
});
|
|
@@ -670,7 +714,7 @@ const unpluginFactory = (options) => {
|
|
|
670
714
|
return null;
|
|
671
715
|
},
|
|
672
716
|
load(id, extra) {
|
|
673
|
-
if (id === RESOLVED_VIRTUAL_CLIENT_ID) return generateClientModule(resolved?.actions ?? options?.actions ?? "http", resolved?.actionHeaders ?? options?.actionHeaders);
|
|
717
|
+
if (id === RESOLVED_VIRTUAL_CLIENT_ID) return generateClientModule(resolved?.actions ?? (typeof options?.actions === "string" || options?.actions === void 0 ? options?.actions : options.actions.transport) ?? "http", resolved?.actionHeaders ?? options?.actionHeaders, resolved?.actionPath);
|
|
674
718
|
if (id === RESOLVED_VIRTUAL_ACTIONS_ID || id === RESOLVED_VIRTUAL_WORKER_ID) {
|
|
675
719
|
if (pluginShouldStub(this, extra)) throw new Error(`oxidejs: ${id === RESOLVED_VIRTUAL_ACTIONS_ID ? VIRTUAL_ACTIONS_ID : VIRTUAL_WORKER_ID} is server-only`);
|
|
676
720
|
}
|
|
@@ -690,7 +734,9 @@ const unpluginFactory = (options) => {
|
|
|
690
734
|
hasClient: resolved.hasClient,
|
|
691
735
|
hasPublic: resolved.hasPublic,
|
|
692
736
|
hasActions: modules.length > 0,
|
|
693
|
-
actions: resolved.actions
|
|
737
|
+
actions: resolved.actions,
|
|
738
|
+
actionPath: resolved.actionPath,
|
|
739
|
+
actionSameOrigin: resolved.actionSameOrigin
|
|
694
740
|
});
|
|
695
741
|
}
|
|
696
742
|
if (isServerFileId(id) && pluginShouldStub(this, extra)) {
|
|
@@ -724,8 +770,8 @@ const unpluginFactory = (options) => {
|
|
|
724
770
|
const loadRouter = async () => {
|
|
725
771
|
return (await server.ssrLoadModule(VIRTUAL_ACTIONS_ID)).default;
|
|
726
772
|
};
|
|
727
|
-
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
|
|
728
|
-
else server.middlewares.use(actionMiddleware(loadRouter));
|
|
773
|
+
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter, resolved.actionPath, resolved.actionSameOrigin);
|
|
774
|
+
else server.middlewares.use(actionMiddleware(loadRouter, resolved.actionPath, resolved.actionSameOrigin));
|
|
729
775
|
},
|
|
730
776
|
configurePreviewServer(server) {
|
|
731
777
|
if (resolved?.preset !== "fetch") return;
|
|
@@ -741,8 +787,8 @@ const unpluginFactory = (options) => {
|
|
|
741
787
|
const loadRouter = async () => {
|
|
742
788
|
return (await loadActions(resolved?.root ?? process.cwd())).default;
|
|
743
789
|
};
|
|
744
|
-
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
|
|
745
|
-
else server.middlewares.use(actionMiddleware(loadRouter));
|
|
790
|
+
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter, resolved.actionPath, resolved.actionSameOrigin);
|
|
791
|
+
else server.middlewares.use(actionMiddleware(loadRouter, resolved.actionPath, resolved.actionSameOrigin));
|
|
746
792
|
});
|
|
747
793
|
api.onBeforeStartPreviewServer?.(({ server }) => {
|
|
748
794
|
if (resolved?.preset !== "fetch") return;
|
|
@@ -759,4 +805,4 @@ const unpluginFactory = (options) => {
|
|
|
759
805
|
const oxidejs = /* @__PURE__ */ createUnplugin(unpluginFactory);
|
|
760
806
|
const vite = /* @__PURE__ */ (() => oxidejs.vite)();
|
|
761
807
|
//#endregion
|
|
762
|
-
export {
|
|
808
|
+
export { useCtx as a, useRequest as c, action as i, unpluginFactory as n, useEnv as o, vite as r, useFetchCtx as s, oxidejs as t };
|
|
@@ -10,6 +10,14 @@ interface OxidejsWranglerOptions {
|
|
|
10
10
|
vars?: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
12
|
type OxidejsActionTransport = "http" | "ws";
|
|
13
|
+
/** `actions` config: transport string, or an object with `transport`, `path`, `sameOrigin`. */
|
|
14
|
+
type OxidejsActions = OxidejsActionTransport | {
|
|
15
|
+
transport?: OxidejsActionTransport;
|
|
16
|
+
/** Endpoint path for actions. Default: `/__oxide/action`. */
|
|
17
|
+
path?: string;
|
|
18
|
+
/** Reject cross-origin action requests (CSRF defense). Default: true. */
|
|
19
|
+
sameOrigin?: boolean;
|
|
20
|
+
};
|
|
13
21
|
/** Static headers inlined into the shared action client. Functions cannot ship to the browser. */
|
|
14
22
|
type OxidejsActionHeaders = Record<string, string> | [string, string][];
|
|
15
23
|
interface OxidejsOptions {
|
|
@@ -25,8 +33,8 @@ interface OxidejsOptions {
|
|
|
25
33
|
wrangler?: OxidejsWranglerOptions;
|
|
26
34
|
/** Skip config emission. Defaults to false for celld, true for fetch. */
|
|
27
35
|
emitConfig?: boolean;
|
|
28
|
-
/** Transport for `*.server.ts` stubs. Default: "http"
|
|
29
|
-
actions?:
|
|
36
|
+
/** Transport and path for `*.server.ts` stubs. Default: `"http"` at `/__oxide/action`. */
|
|
37
|
+
actions?: OxidejsActions;
|
|
30
38
|
/** Extra headers on the shared HTTP action client. Ignored when `actions` is "ws". */
|
|
31
39
|
actionHeaders?: OxidejsActionHeaders;
|
|
32
40
|
}
|
|
@@ -46,6 +54,10 @@ interface ResolvedOptions {
|
|
|
46
54
|
/** True when `<root>/public` exists. Copied next to client assets on fetch. */
|
|
47
55
|
hasPublic: boolean;
|
|
48
56
|
actions: OxidejsActionTransport;
|
|
57
|
+
/** Endpoint path for actions. Default: `/__oxide/action`. */
|
|
58
|
+
actionPath: string;
|
|
59
|
+
/** Reject cross-origin action requests (CSRF defense). Default: true. */
|
|
60
|
+
actionSameOrigin: boolean;
|
|
49
61
|
actionHeaders: OxidejsActionHeaders | undefined;
|
|
50
62
|
}
|
|
51
63
|
//#endregion
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as OxidejsOptions } from "./types-
|
|
1
|
+
import { r as OxidejsOptions } from "./types-C3ualQ39.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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxidejs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Vite/Rsbuild plugin. One build → dist/server.js + optional client. Server actions via *.server.ts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"unplugin": "^3.3.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"tacho": "^0.4.
|
|
62
|
+
"tacho": "^0.4.3"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@rsbuild/core": "*",
|