oxidejs 0.1.4 → 0.1.7

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 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 `/_action` (if you have `*.server.ts`) → `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`.
37
+ Default preset is `"fetch"`. No `index.html` → only `dist/server.js`. With `index.html` → client to `dist/client/`, then `/_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,7 +47,7 @@ oxide({
47
47
 
48
48
  ## Server actions
49
49
 
50
- Install `tacho` if you use actions. Files named `*.server.ts` / `*.server.js` are server-only. A client import is replaced with a tacho stub that POSTs `/_action`. The original module never enters the client graph. 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.ts` → the bundle does not import tacho.
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 `/_action`. The original module never enters the client graph. 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
@@ -133,3 +133,34 @@ Same factory as Vite: client stubs, `/_action`, and `dist/server.js`.
133
133
  - No `wrangler dev` / workerd emulation
134
134
  - No automatic `celld deploy`
135
135
  - No Node-builtin polyfills — Vite `ssr.noExternal: true` is a hard-fail for stray Node imports
136
+
137
+ ## Security
138
+
139
+ ### Asset serving (`preset: "fetch"`)
140
+
141
+ The generated server serves static files from `dist/client/` (or the `public/` directory merged into it). These guards are active:
142
+
143
+ | Attack vector | Guard |
144
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
145
+ | **Traversal** (`%2e%2e/`, `..%2f`) | `__rel()` rejects paths containing `..` segments. |
146
+ | **Double-slash** (`///etc/passwd`) | `__rel()` rejects results that still start with `/` after `slice(1)`. |
147
+ | **Null byte** (`%00`, `\0`) | `__rel()` rejects paths containing null bytes before and after `decodeURIComponent`. |
148
+ | **Absolute path** (`/etc/passwd`) | `__rel()` returns `null` for paths not starting with `/`. |
149
+ | **SPA fallback** | Unknown paths → `index.html`, never a directory listing. |
150
+ | **`clientDir` escape** | `resolveOptions` throws at build time if `clientDir` resolves outside `outDir`. |
151
+ | **Hashed assets** | Files matching `[-.][0-9a-f]{8,}.ext` get `Cache-Control: public, max-age=31536000, immutable`. Other files are not cached by default. |
152
+
153
+ The generated `__asset` function uses `path.join` — not `path.resolve` — so a leading `/` in the relative path stays inside the asset root.
154
+
155
+ ### Server actions (`*.server.{ts,tsx,js,jsx}`)
156
+
157
+ - Server action code is **never bundled into the client**. Client imports are replaced with tacho stubs that POST `/_action`. The original source stays server-only.
158
+ - `/_action` is POST-only. Non-POST requests return `405`.
159
+ - Method dispatch uses `Object.hasOwn`, blocking `__proto__` / `constructor` walks.
160
+ - Unknown or missing content-types → `415`.
161
+ - Body size capped at 1 MB by default (enforced on the actual body, not just `Content-Length`).
162
+ - Batch requests capped at 20 items (both HTTP and WebSocket transports).
163
+
164
+ ### Host header
165
+
166
+ The generated dev server constructs `request.url` from `req.headers.host`. This is standard HTTP/1.1 behavior (same as Express, Hono, Koa, Node http). If your `src/server.ts` reads `request.url` to construct redirects, validate the host yourself — the framework cannot distinguish a legitimate host header from a malicious one. In production, your reverse proxy handles this.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as useEnv, i as useCtx, n as unpluginFactory, o as useFetchCtx, r as vite, s as useRequest, t as oxidejs } from "./src-CU7sBRGf.mjs";
1
+ import { a as useEnv, i as useCtx, n as unpluginFactory, o as useFetchCtx, r as vite, s as useRequest, t as oxidejs } from "./src-BWalutn_.mjs";
2
2
  export { oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
package/dist/rsbuild.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as oxidejs } from "./src-CU7sBRGf.mjs";
1
+ import { t as oxidejs } from "./src-BWalutn_.mjs";
2
2
  //#region src/rsbuild.ts
3
3
  var rsbuild_default = oxidejs.rsbuild;
4
4
  //#endregion
@@ -20,10 +20,15 @@ const IGNORE_DIRS = /* @__PURE__ */ new Set([
20
20
  const EXPORT_RE = /^\s*export\s+(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)|^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=/gm;
21
21
  function isServerFileId(id) {
22
22
  const file = id.split("?")[0]?.replace(/\\/g, "/") ?? "";
23
- return file.endsWith(".server.ts") || file.endsWith(".server.js");
23
+ return [
24
+ ".ts",
25
+ ".tsx",
26
+ ".js",
27
+ ".jsx"
28
+ ].some((ext) => file.endsWith(`.server${ext}`));
24
29
  }
25
30
  function moduleKey(absFile) {
26
- return path.basename(absFile).replace(/\.server\.(ts|js)$/i, "");
31
+ return path.basename(absFile).replace(/\.server\.(?:[jt]sx?)$/i, "");
27
32
  }
28
33
  function parseExportedNames(source) {
29
34
  const names = /* @__PURE__ */ new Set();
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as oxidejs } from "./src-CU7sBRGf.mjs";
1
+ import { t as oxidejs } from "./src-BWalutn_.mjs";
2
2
  //#region src/vite.ts
3
3
  var vite_default = oxidejs.vite;
4
4
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxidejs",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
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.1"
62
+ "tacho": "^0.4.2"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@rsbuild/core": "*",