oxidejs 0.1.7 → 0.1.9
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 +35 -30
- 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-BWalutn_.mjs → src-GLi2EcPa.mjs} +72 -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`, `*.server.tsx`, `*.server.js`, or `*.server.jsx` are server-only. A client import is replaced with a tacho stub that POSTs `/
|
|
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
|
|
|
@@ -154,8 +158,9 @@ The generated `__asset` function uses `path.join` — not `path.resolve` — so
|
|
|
154
158
|
|
|
155
159
|
### Server actions (`*.server.{ts,tsx,js,jsx}`)
|
|
156
160
|
|
|
157
|
-
- Server action code is **never bundled into the client**. Client imports are replaced with tacho stubs that POST `/
|
|
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-GLi2EcPa.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,14 +10,15 @@ 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
24
|
return [
|
|
@@ -33,7 +34,7 @@ function moduleKey(absFile) {
|
|
|
33
34
|
function parseExportedNames(source) {
|
|
34
35
|
const names = /* @__PURE__ */ new Set();
|
|
35
36
|
for (const match of source.matchAll(EXPORT_RE)) {
|
|
36
|
-
const name = match[1]
|
|
37
|
+
const name = match[1];
|
|
37
38
|
if (name) names.add(name);
|
|
38
39
|
}
|
|
39
40
|
return [...names];
|
|
@@ -75,20 +76,20 @@ function scanServerFiles(root) {
|
|
|
75
76
|
}
|
|
76
77
|
return modules;
|
|
77
78
|
}
|
|
78
|
-
function generateClientModule(transport = "http", headers) {
|
|
79
|
+
function generateClientModule(transport = "http", headers, path = ACTION_PATH) {
|
|
79
80
|
if (transport === "ws") return `import { createClient } from "tacho/client/ws";
|
|
80
81
|
const __proto = typeof location === "undefined" ? "ws:" : location.protocol === "https:" ? "wss:" : "ws:";
|
|
81
82
|
const __host = typeof location === "undefined" ? "localhost" : location.host;
|
|
82
|
-
export const client = createClient({ url: __proto + "//" + __host + ${JSON.stringify(
|
|
83
|
+
export const client = createClient({ url: __proto + "//" + __host + ${JSON.stringify(path)} });
|
|
83
84
|
`;
|
|
84
|
-
const opts = { url:
|
|
85
|
+
const opts = { url: path };
|
|
85
86
|
if (headers) opts.headers = headers;
|
|
86
87
|
return `import { createClient } from "tacho/client/http";
|
|
87
88
|
export const client = createClient(${JSON.stringify(opts)});
|
|
88
89
|
`;
|
|
89
90
|
}
|
|
90
91
|
function generateClientStub(mod) {
|
|
91
|
-
const lines = [`import { client } from ${JSON.stringify(VIRTUAL_CLIENT_ID)};`];
|
|
92
|
+
const lines = [`// oxidejs:client-stub`, `import { client } from ${JSON.stringify(VIRTUAL_CLIENT_ID)};`];
|
|
92
93
|
for (const name of mod.exports) lines.push(`export const ${name} = (...args) => {
|
|
93
94
|
const opts = args.at(-1);
|
|
94
95
|
// ponytail: peel last { signal } only. A lone payload { signal: AbortSignal } is treated as CallOptions.
|
|
@@ -102,7 +103,8 @@ function generateActionsModule(modules, opts) {
|
|
|
102
103
|
const lines = [
|
|
103
104
|
`import { AsyncLocalStorage } from "node:async_hooks";`,
|
|
104
105
|
`import { tacho } from "tacho";`,
|
|
105
|
-
`const
|
|
106
|
+
`const __alsKey = Symbol.for("oxidejs.requestContext");`,
|
|
107
|
+
`const __als = globalThis[__alsKey] ??= new AsyncLocalStorage();`
|
|
106
108
|
];
|
|
107
109
|
const aliases = modules.map((mod, i) => {
|
|
108
110
|
const alias = `__m${i}`;
|
|
@@ -168,6 +170,8 @@ function pipeResponse(req, res, response) {
|
|
|
168
170
|
function generateWorkerWrapper(userWorkerAbs, opts = {}) {
|
|
169
171
|
const preset = opts.preset ?? "fetch";
|
|
170
172
|
const clientDir = opts.clientDir ?? "client";
|
|
173
|
+
const actionPath = opts.actionPath ?? "/__oxide/action";
|
|
174
|
+
const sameOrigin = opts.actionSameOrigin ?? false;
|
|
171
175
|
const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
|
|
172
176
|
const hasActions = opts.hasActions !== false;
|
|
173
177
|
const ws = hasActions && opts.actions === "ws";
|
|
@@ -251,7 +255,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
251
255
|
import("crossws/adapters/node").then(({ default: crossws }) => {
|
|
252
256
|
const ws = crossws({ hooks: __ws });
|
|
253
257
|
server.on("upgrade", (req, socket, head) => {
|
|
254
|
-
if (req.url?.split("?")[0] === ${JSON.stringify(
|
|
258
|
+
if (req.url?.split("?")[0] === ${JSON.stringify(actionPath)}) ws.handleUpgrade(req, socket, head);
|
|
255
259
|
});
|
|
256
260
|
});` : ""}
|
|
257
261
|
server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
|
|
@@ -259,13 +263,13 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
259
263
|
` : "";
|
|
260
264
|
const actionImports = hasActions ? ws ? `import { handle as handleWs } from "tacho/transport/ws";
|
|
261
265
|
import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
|
|
262
|
-
const __ws = handleWs(actions, { path: ${JSON.stringify(
|
|
266
|
+
const __ws = handleWs(actions, { path: ${JSON.stringify(actionPath)}${sameOrigin ? `, sameOrigin: true` : ``} });
|
|
263
267
|
` : `import { handle } from "tacho/transport/fetch";
|
|
264
268
|
import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
|
|
265
269
|
const __fetch = Symbol.for("oxidejs.fetch");
|
|
266
|
-
const __rpc = handle(actions, { path: ${JSON.stringify(
|
|
270
|
+
const __rpc = handle(actions, { path: ${JSON.stringify(actionPath)}${sameOrigin ? `, sameOrigin: true` : ``}, createContext: (req) => req[__fetch] ?? {} });
|
|
267
271
|
` : "";
|
|
268
|
-
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)}) {
|
|
269
273
|
request[__fetch] = { env, fetchCtx: ctx };
|
|
270
274
|
return __rpc(request);
|
|
271
275
|
}
|
|
@@ -408,11 +412,30 @@ function hasHtmlEntry(root, config) {
|
|
|
408
412
|
return file.endsWith(".html") && fs.existsSync(file);
|
|
409
413
|
});
|
|
410
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
|
+
}
|
|
411
435
|
function resolveOptions(raw, root, config) {
|
|
412
436
|
const preset = raw?.preset ?? "fetch";
|
|
413
437
|
if (preset !== "fetch" && preset !== "celld") throw new Error(`oxidejs: unknown preset "${String(preset)}"`);
|
|
414
|
-
const actions = raw?.actions
|
|
415
|
-
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);
|
|
416
439
|
if (actions === "ws" && preset === "celld") throw new Error("oxidejs: actions: \"ws\" is not supported with preset: \"celld\"");
|
|
417
440
|
const workerEntry = raw?.workerEntry ?? "src/server.ts";
|
|
418
441
|
const outDirInput = raw?.outDir ?? "dist";
|
|
@@ -437,6 +460,8 @@ function resolveOptions(raw, root, config) {
|
|
|
437
460
|
hasClient,
|
|
438
461
|
hasPublic,
|
|
439
462
|
actions,
|
|
463
|
+
actionPath,
|
|
464
|
+
actionSameOrigin,
|
|
440
465
|
actionHeaders: raw?.actionHeaders
|
|
441
466
|
};
|
|
442
467
|
}
|
|
@@ -581,7 +606,7 @@ function applyRsbuildEnvironments(config, opts) {
|
|
|
581
606
|
}
|
|
582
607
|
//#endregion
|
|
583
608
|
//#region src/context.ts
|
|
584
|
-
const ALS_KEY = "
|
|
609
|
+
const ALS_KEY = Symbol.for("oxidejs.requestContext");
|
|
585
610
|
function als() {
|
|
586
611
|
const g = globalThis;
|
|
587
612
|
return g[ALS_KEY] ??= new AsyncLocalStorage();
|
|
@@ -591,11 +616,11 @@ function store() {
|
|
|
591
616
|
if (!current) throw new Error("oxidejs: useRequest() called outside an action");
|
|
592
617
|
return current;
|
|
593
618
|
}
|
|
594
|
-
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/
|
|
619
|
+
/** Current tacho `ctx`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
595
620
|
function useCtx() {
|
|
596
621
|
return store();
|
|
597
622
|
}
|
|
598
|
-
/** Current action `Request`. Throws outside `*.server.ts` running over `/
|
|
623
|
+
/** Current action `Request`. Throws outside `*.server.ts` running over `/__oxide/action`. */
|
|
599
624
|
function useRequest() {
|
|
600
625
|
return store().req;
|
|
601
626
|
}
|
|
@@ -607,11 +632,19 @@ function useEnv() {
|
|
|
607
632
|
function useFetchCtx() {
|
|
608
633
|
return store().fetchCtx;
|
|
609
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
|
+
}
|
|
610
643
|
//#endregion
|
|
611
644
|
//#region src/index.ts
|
|
612
|
-
function actionMiddleware(loadRouter) {
|
|
645
|
+
function actionMiddleware(loadRouter, path, sameOrigin) {
|
|
613
646
|
return (req, res, next) => {
|
|
614
|
-
if ((req.url ?? "").split("?")[0] !==
|
|
647
|
+
if ((req.url ?? "").split("?")[0] !== path) {
|
|
615
648
|
next();
|
|
616
649
|
return;
|
|
617
650
|
}
|
|
@@ -620,11 +653,14 @@ function actionMiddleware(loadRouter) {
|
|
|
620
653
|
/* @vite-ignore */
|
|
621
654
|
"tacho/transport/fetch"
|
|
622
655
|
);
|
|
623
|
-
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)));
|
|
624
660
|
})().catch(next);
|
|
625
661
|
};
|
|
626
662
|
}
|
|
627
|
-
function attachActionUpgrade(httpServer, loadRouter) {
|
|
663
|
+
function attachActionUpgrade(httpServer, loadRouter, path, sameOrigin) {
|
|
628
664
|
if (!httpServer) return;
|
|
629
665
|
Promise.all([import(
|
|
630
666
|
/* @vite-ignore */
|
|
@@ -634,8 +670,11 @@ function attachActionUpgrade(httpServer, loadRouter) {
|
|
|
634
670
|
"crossws/adapters/node"
|
|
635
671
|
)]).then(([{ handle }, { default: crossws }]) => {
|
|
636
672
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
637
|
-
if ((req.url ?? "").split("?")[0] !==
|
|
638
|
-
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(() => {
|
|
639
678
|
socket.destroy();
|
|
640
679
|
});
|
|
641
680
|
});
|
|
@@ -675,7 +714,7 @@ const unpluginFactory = (options) => {
|
|
|
675
714
|
return null;
|
|
676
715
|
},
|
|
677
716
|
load(id, extra) {
|
|
678
|
-
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);
|
|
679
718
|
if (id === RESOLVED_VIRTUAL_ACTIONS_ID || id === RESOLVED_VIRTUAL_WORKER_ID) {
|
|
680
719
|
if (pluginShouldStub(this, extra)) throw new Error(`oxidejs: ${id === RESOLVED_VIRTUAL_ACTIONS_ID ? VIRTUAL_ACTIONS_ID : VIRTUAL_WORKER_ID} is server-only`);
|
|
681
720
|
}
|
|
@@ -695,7 +734,9 @@ const unpluginFactory = (options) => {
|
|
|
695
734
|
hasClient: resolved.hasClient,
|
|
696
735
|
hasPublic: resolved.hasPublic,
|
|
697
736
|
hasActions: modules.length > 0,
|
|
698
|
-
actions: resolved.actions
|
|
737
|
+
actions: resolved.actions,
|
|
738
|
+
actionPath: resolved.actionPath,
|
|
739
|
+
actionSameOrigin: resolved.actionSameOrigin
|
|
699
740
|
});
|
|
700
741
|
}
|
|
701
742
|
if (isServerFileId(id) && pluginShouldStub(this, extra)) {
|
|
@@ -705,7 +746,7 @@ const unpluginFactory = (options) => {
|
|
|
705
746
|
}
|
|
706
747
|
},
|
|
707
748
|
transform(code, id, extra) {
|
|
708
|
-
if (!isServerFileId(id) || !pluginShouldStub(this, extra)) return;
|
|
749
|
+
if (!isServerFileId(id) || !pluginShouldStub(this, extra) || code.startsWith("// oxidejs:client-stub\n")) return;
|
|
709
750
|
return generateClientStub({
|
|
710
751
|
key: moduleKey(id.split("?")[0] ?? id),
|
|
711
752
|
exports: parseExportedNames(code)
|
|
@@ -729,8 +770,8 @@ const unpluginFactory = (options) => {
|
|
|
729
770
|
const loadRouter = async () => {
|
|
730
771
|
return (await server.ssrLoadModule(VIRTUAL_ACTIONS_ID)).default;
|
|
731
772
|
};
|
|
732
|
-
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
|
|
733
|
-
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));
|
|
734
775
|
},
|
|
735
776
|
configurePreviewServer(server) {
|
|
736
777
|
if (resolved?.preset !== "fetch") return;
|
|
@@ -746,8 +787,8 @@ const unpluginFactory = (options) => {
|
|
|
746
787
|
const loadRouter = async () => {
|
|
747
788
|
return (await loadActions(resolved?.root ?? process.cwd())).default;
|
|
748
789
|
};
|
|
749
|
-
if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
|
|
750
|
-
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));
|
|
751
792
|
});
|
|
752
793
|
api.onBeforeStartPreviewServer?.(({ server }) => {
|
|
753
794
|
if (resolved?.preset !== "fetch") return;
|
|
@@ -764,4 +805,4 @@ const unpluginFactory = (options) => {
|
|
|
764
805
|
const oxidejs = /* @__PURE__ */ createUnplugin(unpluginFactory);
|
|
765
806
|
const vite = /* @__PURE__ */ (() => oxidejs.vite)();
|
|
766
807
|
//#endregion
|
|
767
|
-
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.9",
|
|
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": "*",
|