oxidejs 0.0.1 → 0.1.1

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 ADDED
@@ -0,0 +1,135 @@
1
+ # oxidejs
2
+
3
+ One build command → a deployable tree:
4
+
5
+ ```
6
+ dist/
7
+ ├── client/ # only if index.html exists
8
+ └── server.js # ESM server bundle
9
+ ```
10
+
11
+ `preset: "celld"` also writes `dist/wrangler.jsonc` with `main: "./server.js"`.
12
+
13
+ v1 targets **Vite** and **Rsbuild** via unplugin. Other bundlers are out of scope for now.
14
+
15
+ ## Vite
16
+
17
+ ```ts
18
+ // vite.config.ts
19
+ import { defineConfig } from "vite";
20
+ import oxide from "oxidejs/vite";
21
+
22
+ export default defineConfig({
23
+ plugins: [oxide()],
24
+ });
25
+ ```
26
+
27
+ ```json
28
+ // tsconfig.json
29
+ { "extends": "oxidejs/tsconfig" }
30
+ ```
31
+
32
+ ```bash
33
+ vite build
34
+ node dist/server.js
35
+ ```
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`.
38
+
39
+ ```ts
40
+ oxide({
41
+ preset: "celld",
42
+ wrangler: { name: "my-app", compatibility_date: "2026-01-01" },
43
+ });
44
+ ```
45
+
46
+ `"celld"` writes `dist/wrangler.jsonc` for celld, a self-hosted alternative to Cloudflare Workers, and skips asset serving (`ASSETS` does that).
47
+
48
+ ## Server actions
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.
51
+
52
+ ```ts
53
+ // src/test.server.ts
54
+ import { useRequest } from "oxidejs";
55
+
56
+ export async function who() {
57
+ return useRequest().headers.get("x-user");
58
+ }
59
+
60
+ export async function ping() {
61
+ return "pong";
62
+ }
63
+
64
+ // src/client.ts
65
+ import { ping } from "./test.server";
66
+ console.log(await ping()); // "pong"
67
+
68
+ // src/server.ts
69
+ export default {
70
+ fetch(request: Request) {
71
+ if (new URL(request.url).pathname === "/api/ok") return new Response("ok");
72
+ },
73
+ };
74
+ ```
75
+
76
+ `async function*` exports 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
+
78
+ ```ts
79
+ // src/test.server.ts
80
+ import type { ActionOptions } from "oxidejs";
81
+
82
+ export async function* ticks(n: number, _opts?: ActionOptions) {
83
+ for (let i = 0; i < n; i++) yield i;
84
+ }
85
+
86
+ // src/client.ts
87
+ import { ticks } from "./test.server";
88
+
89
+ const ac = new AbortController();
90
+ const stream = await ticks(10, { signal: ac.signal });
91
+ ac.abort();
92
+ ```
93
+
94
+ `vite dev` and `rsbuild dev` serve `/_action` via middleware. `oxide({ actions: "ws" })` uses a WebSocket instead (needs `crossws`; not with `preset: "celld"`). `actionHeaders` are static headers on the shared HTTP client.
95
+
96
+ ## Rsbuild
97
+
98
+ ```ts
99
+ // rsbuild.config.ts
100
+ import { defineConfig } from "@rsbuild/core";
101
+ import oxide from "oxidejs/rsbuild";
102
+
103
+ export default defineConfig({
104
+ plugins: [oxide()],
105
+ });
106
+ ```
107
+
108
+ Same factory as Vite: client stubs, `/_action`, and `dist/server.js`.
109
+
110
+ ## Options
111
+
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`; not with celld |
127
+ | `actionHeaders` | — | Static headers on the HTTP client |
128
+
129
+ `main` is always `./server.js`. `assets` is added only when `index.html` exists. Unknown wrangler keys fail at build time.
130
+
131
+ ## Non-goals
132
+
133
+ - No `wrangler dev` / workerd emulation
134
+ - No automatic `celld deploy`
135
+ - No Node-builtin polyfills — Vite `ssr.noExternal: true` is a hard-fail for stray Node imports
package/client.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ interface AsyncGenerator<T = unknown, TReturn = any, TNext = any> extends AsyncIteratorObject<
2
+ T,
3
+ TReturn,
4
+ TNext
5
+ > {
6
+ then<TResult1 = AsyncIterable<T>, TResult2 = never>(
7
+ onfulfilled?: ((value: AsyncIterable<T>) => TResult1 | PromiseLike<TResult1>) | null,
8
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
9
+ ): Promise<TResult1 | TResult2>;
10
+ }
@@ -0,0 +1,33 @@
1
+ import { a as OxidejsWranglerOptions, i as OxidejsPreset, n as OxidejsActionTransport, o as ResolvedOptions, r as OxidejsOptions, t as OxidejsActionHeaders } from "./types-CFWyYfds.mjs";
2
+ import { UnpluginFactory } from "unplugin";
3
+ //#region src/context.d.ts
4
+ type ExecutionContext = {
5
+ waitUntil?(promise: Promise<unknown>): void;
6
+ passThroughOnException?(): void;
7
+ };
8
+ /** Tacho procedure `ctx`. Starts as `{ req }` plus Worker extras. Middleware can add fields. */
9
+ type ActionContext = {
10
+ req: Request;
11
+ env?: unknown;
12
+ fetchCtx?: ExecutionContext;
13
+ [key: string]: unknown;
14
+ };
15
+ /** Current tacho `ctx`. Throws outside `*.server.ts` running over `/_action`. */
16
+ declare function useCtx<C extends ActionContext = ActionContext>(): C;
17
+ /** Current action `Request`. Throws outside `*.server.ts` running over `/_action`. */
18
+ declare function useRequest(): Request;
19
+ /** Worker `env` from `fetch(request, env, ctx)`. `undefined` on the Node fetch preset. */
20
+ declare function useEnv<E = unknown>(): E | undefined;
21
+ /** Worker `ctx` from `fetch(request, env, ctx)` (`waitUntil`). Not tacho `ctx`. `undefined` on Node. */
22
+ declare function useFetchCtx(): ExecutionContext | undefined;
23
+ /** Optional last argument on a `*.server.ts` export so the client can pass `{ signal }`. */
24
+ type ActionOptions = {
25
+ signal?: AbortSignal;
26
+ };
27
+ //#endregion
28
+ //#region src/index.d.ts
29
+ declare const unpluginFactory: UnpluginFactory<OxidejsOptions | undefined>;
30
+ declare const oxidejs: import("unplugin").UnpluginInstance<OxidejsOptions | undefined, boolean>;
31
+ declare const vite: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
32
+ //#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 };
package/dist/index.mjs ADDED
@@ -0,0 +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-B03PPyx9.mjs";
2
+ export { oxidejs as default, oxidejs, unpluginFactory, useCtx, useEnv, useFetchCtx, useRequest, vite };
@@ -0,0 +1,5 @@
1
+ import { r as OxidejsOptions } from "./types-CFWyYfds.mjs";
2
+ //#region src/rsbuild.d.ts
3
+ declare const _default: (options?: OxidejsOptions | undefined) => any;
4
+ //#endregion
5
+ export { type OxidejsOptions, _default as default };
@@ -0,0 +1,5 @@
1
+ import { t as oxidejs } from "./src-B03PPyx9.mjs";
2
+ //#region src/rsbuild.ts
3
+ var rsbuild_default = oxidejs.rsbuild;
4
+ //#endregion
5
+ export { rsbuild_default as default };
@@ -0,0 +1,760 @@
1
+ import { createUnplugin } from "unplugin";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import fs from "node:fs";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+ //#region src/actions.ts
7
+ const VIRTUAL_ACTIONS_ID = "virtual:oxide/actions";
8
+ const RESOLVED_VIRTUAL_ACTIONS_ID = `\0${VIRTUAL_ACTIONS_ID}`;
9
+ const VIRTUAL_WORKER_ID = "virtual:oxide/worker";
10
+ const RESOLVED_VIRTUAL_WORKER_ID = `\0${VIRTUAL_WORKER_ID}`;
11
+ const VIRTUAL_CLIENT_ID = "virtual:oxide/client";
12
+ const RESOLVED_VIRTUAL_CLIENT_ID = `\0${VIRTUAL_CLIENT_ID}`;
13
+ const ACTION_PATH = "/_action";
14
+ const IGNORE_DIRS = /* @__PURE__ */ new Set([
15
+ "node_modules",
16
+ "dist",
17
+ ".git",
18
+ ".wrangler"
19
+ ]);
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
+ function isServerFileId(id) {
22
+ const file = id.split("?")[0]?.replace(/\\/g, "/") ?? "";
23
+ return file.endsWith(".server.ts") || file.endsWith(".server.js");
24
+ }
25
+ function moduleKey(absFile) {
26
+ return path.basename(absFile).replace(/\.server\.(ts|js)$/i, "");
27
+ }
28
+ function parseExportedNames(source) {
29
+ const names = /* @__PURE__ */ new Set();
30
+ for (const match of source.matchAll(EXPORT_RE)) {
31
+ const name = match[1] ?? match[2];
32
+ if (name) names.add(name);
33
+ }
34
+ return [...names];
35
+ }
36
+ function scanServerFiles(root) {
37
+ const files = [];
38
+ const walk = (dir) => {
39
+ let entries;
40
+ try {
41
+ entries = fs.readdirSync(dir, { withFileTypes: true });
42
+ } catch {
43
+ return;
44
+ }
45
+ for (const entry of entries) {
46
+ if (entry.name.startsWith(".")) continue;
47
+ const abs = path.join(dir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ if (IGNORE_DIRS.has(entry.name)) continue;
50
+ walk(abs);
51
+ continue;
52
+ }
53
+ if (entry.isFile() && isServerFileId(entry.name)) files.push(abs);
54
+ }
55
+ };
56
+ walk(root);
57
+ const byKey = /* @__PURE__ */ new Map();
58
+ const modules = [];
59
+ for (const abs of files.sort()) {
60
+ const key = moduleKey(abs);
61
+ if (!key) throw new Error(`oxidejs: invalid server module name: ${abs}`);
62
+ const existing = byKey.get(key);
63
+ if (existing) throw new Error(`oxidejs: duplicate server module key "${key}": ${existing} and ${abs}`);
64
+ byKey.set(key, abs);
65
+ modules.push({
66
+ abs,
67
+ key,
68
+ exports: parseExportedNames(fs.readFileSync(abs, "utf8"))
69
+ });
70
+ }
71
+ return modules;
72
+ }
73
+ function generateClientModule(transport = "http", headers) {
74
+ if (transport === "ws") return `import { createClient } from "tacho/client/ws";
75
+ const __proto = typeof location === "undefined" ? "ws:" : location.protocol === "https:" ? "wss:" : "ws:";
76
+ const __host = typeof location === "undefined" ? "localhost" : location.host;
77
+ export const client = createClient({ url: __proto + "//" + __host + ${JSON.stringify(ACTION_PATH)} });
78
+ `;
79
+ const opts = { url: ACTION_PATH };
80
+ if (headers) opts.headers = headers;
81
+ return `import { createClient } from "tacho/client/http";
82
+ export const client = createClient(${JSON.stringify(opts)});
83
+ `;
84
+ }
85
+ function generateClientStub(mod) {
86
+ const lines = [`import { client } from ${JSON.stringify(VIRTUAL_CLIENT_ID)};`];
87
+ for (const name of mod.exports) lines.push(`export const ${name} = (...args) => {
88
+ const opts = args.at(-1);
89
+ // ponytail: peel last { signal } only. A lone payload { signal: AbortSignal } is treated as CallOptions.
90
+ return opts && typeof opts === "object" && opts.signal instanceof AbortSignal && Object.keys(opts).length === 1
91
+ ? client[${JSON.stringify(mod.key)}][${JSON.stringify(name)}](args.slice(0, -1), opts)
92
+ : client[${JSON.stringify(mod.key)}][${JSON.stringify(name)}](args);
93
+ };`);
94
+ return `${lines.join("\n")}\n`;
95
+ }
96
+ function generateActionsModule(modules, opts) {
97
+ const lines = [
98
+ `import { AsyncLocalStorage } from "node:async_hooks";`,
99
+ `import { tacho } from "tacho";`,
100
+ `const __als = globalThis.__oxidejsRequest ??= new AsyncLocalStorage();`
101
+ ];
102
+ const aliases = modules.map((mod, i) => {
103
+ const alias = `__m${i}`;
104
+ const spec = opts?.bust === true ? `${mod.abs}?t=${fs.statSync(mod.abs).mtimeMs}` : mod.abs;
105
+ lines.push(`import * as ${alias} from ${JSON.stringify(spec)};`);
106
+ return {
107
+ alias,
108
+ mod
109
+ };
110
+ });
111
+ lines.push(`const rpc = tacho();`);
112
+ lines.push(`const actions = rpc({`);
113
+ for (const { alias, mod } of aliases) {
114
+ lines.push(` ${JSON.stringify(mod.key)}: {`);
115
+ for (const name of mod.exports) lines.push(` ${JSON.stringify(name)}: rpc.run(({ input, ctx }) => __als.run(ctx, () => {
116
+ const out = ${alias}[${JSON.stringify(name)}].apply(null, Array.isArray(input) ? input : []);
117
+ if (!out || typeof out !== "object" || typeof out.next !== "function") return out;
118
+ return {
119
+ next: (v) => __als.run(ctx, () => out.next(v)),
120
+ return: (v) => __als.run(ctx, () => out.return(v)),
121
+ throw: (e) => __als.run(ctx, () => out.throw(e)),
122
+ [Symbol.asyncIterator]() { return this; },
123
+ };
124
+ })),`);
125
+ lines.push(` },`);
126
+ }
127
+ lines.push(`});`);
128
+ lines.push(`export default actions;`);
129
+ lines.push(`export { actions };`);
130
+ return `${lines.join("\n")}\n`;
131
+ }
132
+ function pipeResponse(req, res, response) {
133
+ return new Promise((resolve, reject) => {
134
+ res.statusCode = response.status;
135
+ response.headers.forEach((value, key) => {
136
+ res.setHeader(key, value);
137
+ });
138
+ if (!response.body) {
139
+ res.end();
140
+ resolve();
141
+ return;
142
+ }
143
+ const reader = response.body.getReader();
144
+ const abort = () => {
145
+ reader.cancel();
146
+ };
147
+ req.once("aborted", abort);
148
+ const pull = () => {
149
+ reader.read().then(({ done, value }) => {
150
+ if (done) {
151
+ req.off("aborted", abort);
152
+ res.end();
153
+ resolve();
154
+ return;
155
+ }
156
+ if (value) res.write(value);
157
+ pull();
158
+ }, reject);
159
+ };
160
+ pull();
161
+ });
162
+ }
163
+ function generateWorkerWrapper(userWorkerAbs, opts = {}) {
164
+ const preset = opts.preset ?? "fetch";
165
+ const clientDir = opts.clientDir ?? "client";
166
+ const serveAssets = preset === "fetch" && (opts.hasClient === true || opts.hasPublic === true);
167
+ const hasActions = opts.hasActions !== false;
168
+ const ws = hasActions && opts.actions === "ws";
169
+ const assetBlock = serveAssets ? `import { readFile } from "node:fs/promises";
170
+ import { extname, join } from "node:path";
171
+ const __assets = join(import.meta.dirname, ${JSON.stringify(clientDir)});
172
+ 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" };
173
+ function __nav(request) {
174
+ const dest = request.headers.get("sec-fetch-dest");
175
+ if (dest) return dest === "document";
176
+ return (request.headers.get("accept") ?? "").includes("text/html");
177
+ }
178
+ function __cache(file) {
179
+ if (file === "index.html") return "no-cache";
180
+ return /[-.][0-9a-f]{8,}.[a-z0-9]+$/i.test(file) ? "public, max-age=31536000, immutable" : undefined;
181
+ }
182
+ function __rel(pathname, spa) {
183
+ if (pathname.includes("\0")) return;
184
+ let file;
185
+ try { file = decodeURIComponent(pathname); } catch { return; }
186
+ if (file.includes("\0")) return;
187
+ if (file === "/" || spa) file = "/index.html";
188
+ if (!file.startsWith("/") || file.split("/").includes("..")) return;
189
+ return file.slice(1);
190
+ }
191
+ async function __asset(request, spa) {
192
+ const file = __rel(new URL(request.url).pathname, spa);
193
+ if (!file) return;
194
+ try {
195
+ const body = await readFile(join(__assets, file));
196
+ const headers = { "content-type": __types[extname(file)] ?? "application/octet-stream" };
197
+ const cache = __cache(file);
198
+ if (cache) headers["cache-control"] = cache;
199
+ return new Response(body, { headers });
200
+ } catch {
201
+ return;
202
+ }
203
+ }
204
+ ` : "";
205
+ const afterAction = serveAssets ? `if (typeof user.fetch === "function") {
206
+ const hit = await user.fetch(request, env, ctx);
207
+ if (hit) return hit;
208
+ }
209
+ return (await __asset(request)) ?? (__nav(request) ? await __asset(request, true) : undefined) ?? new Response("Not Found", { status: 404 });` : `return typeof user.fetch === "function"
210
+ ? user.fetch(request, env, ctx)
211
+ : new Response("Not Found", { status: 404 });`;
212
+ const listen = preset === "fetch" ? `
213
+ import { createServer } from "node:http";
214
+ import { pathToFileURL } from "node:url";
215
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
216
+ const port = Number(process.env.PORT) || 3000;
217
+ const server = createServer(async (req, res) => {
218
+ const url = \`http://\${req.headers.host ?? "localhost"}\${req.url ?? "/"}\`;
219
+ const headers = new Headers();
220
+ for (const [key, value] of Object.entries(req.headers)) {
221
+ if (value === undefined) continue;
222
+ if (Array.isArray(value)) for (const item of value) headers.append(key, item);
223
+ else headers.set(key, value);
224
+ }
225
+ const ac = new AbortController();
226
+ req.once("aborted", () => ac.abort());
227
+ const method = req.method ?? "GET";
228
+ const chunks = [];
229
+ if (method !== "GET" && method !== "HEAD") for await (const chunk of req) chunks.push(chunk);
230
+ const init = { method, headers, signal: ac.signal };
231
+ if (chunks.length) init.body = Buffer.concat(chunks);
232
+ const response = await app.fetch(new Request(url, init));
233
+ res.statusCode = response.status;
234
+ response.headers.forEach((value, key) => res.setHeader(key, value));
235
+ if (!response.body) { res.end(); return; }
236
+ const reader = response.body.getReader();
237
+ for (;;) {
238
+ const { done, value } = await reader.read();
239
+ if (done) break;
240
+ if (value) res.write(value);
241
+ }
242
+ res.end();
243
+ });${ws ? `
244
+ import("crossws/adapters/node").then(({ default: crossws }) => {
245
+ const ws = crossws({ hooks: __ws });
246
+ server.on("upgrade", (req, socket, head) => {
247
+ if (req.url?.split("?")[0] === ${JSON.stringify(ACTION_PATH)}) ws.handleUpgrade(req, socket, head);
248
+ });
249
+ });` : ""}
250
+ server.listen(port, () => console.log(\`oxidejs listening on \${port}\`));
251
+ }
252
+ ` : "";
253
+ const actionImports = hasActions ? ws ? `import { handle as handleWs } from "tacho/transport/ws";
254
+ import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
255
+ const __ws = handleWs(actions, { path: ${JSON.stringify(ACTION_PATH)} });
256
+ ` : `import { handle } from "tacho/transport/fetch";
257
+ import actions from ${JSON.stringify(VIRTUAL_ACTIONS_ID)};
258
+ const __fetch = Symbol.for("oxidejs.fetch");
259
+ const __rpc = handle(actions, { path: ${JSON.stringify(ACTION_PATH)}, createContext: (req) => req[__fetch] ?? {} });
260
+ ` : "";
261
+ const actionGate = hasActions && !ws ? `if (new URL(request.url).pathname === ${JSON.stringify(ACTION_PATH)}) {
262
+ request[__fetch] = { env, fetchCtx: ctx };
263
+ return __rpc(request);
264
+ }
265
+ ` : "";
266
+ return `export * from ${JSON.stringify(userWorkerAbs)};
267
+ import user from ${JSON.stringify(userWorkerAbs)};
268
+ ${actionImports}${assetBlock}const app = {
269
+ ...user,
270
+ async fetch(request, env, ctx) {
271
+ ${actionGate}${afterAction}
272
+ },
273
+ };
274
+ export default app;
275
+ ${listen}`;
276
+ }
277
+ const SERVER_TARGETS = /* @__PURE__ */ new Set([
278
+ "node",
279
+ "async-node",
280
+ "webworker",
281
+ "web-worker"
282
+ ]);
283
+ const CLIENT_TARGETS = /* @__PURE__ */ new Set(["web", "browserslist"]);
284
+ const SERVER_NAMES = /* @__PURE__ */ new Set([
285
+ "server",
286
+ "ssr",
287
+ "worker",
288
+ "node"
289
+ ]);
290
+ const CLIENT_NAMES = /* @__PURE__ */ new Set(["client", "web"]);
291
+ /** Stub unless the graph is a known server. Unknown graphs stub so *.server.ts never ships. */
292
+ function shouldStubServerModule(environment, extra) {
293
+ if (extra?.ssr) return false;
294
+ const consumer = environment?.config?.consumer ?? environment?.consumer;
295
+ if (consumer === "server") return false;
296
+ if (consumer === "client") return true;
297
+ const name = environment?.name;
298
+ if (name && CLIENT_NAMES.has(name)) return true;
299
+ if (name && SERVER_NAMES.has(name)) return false;
300
+ const targets = extra?.target == null ? [] : [extra.target].flat();
301
+ if (targets.some((target) => SERVER_TARGETS.has(target))) return false;
302
+ if (targets.some((target) => CLIENT_TARGETS.has(target))) return true;
303
+ return true;
304
+ }
305
+ function pluginShouldStub(pluginThis, options) {
306
+ const ctx = pluginThis;
307
+ const compiler = ctx.getNativeBuildContext?.()?.compiler;
308
+ const env = {};
309
+ const name = ctx.environment?.name ?? compiler?.name ?? compiler?.options?.name;
310
+ if (name) env.name = name;
311
+ if (ctx.environment?.consumer) env.consumer = ctx.environment.consumer;
312
+ if (ctx.environment?.config) env.config = ctx.environment.config;
313
+ const extra = {};
314
+ if (options?.ssr) extra.ssr = true;
315
+ if (compiler?.options?.target) extra.target = compiler.options.target;
316
+ return shouldStubServerModule(env, extra);
317
+ }
318
+ function loadClientStub(id) {
319
+ const file = id.split("?")[0] ?? id;
320
+ return generateClientStub({
321
+ key: moduleKey(file),
322
+ exports: parseExportedNames(fs.readFileSync(file, "utf8"))
323
+ });
324
+ }
325
+ async function nodeToWebRequest(req) {
326
+ const url = `http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`;
327
+ const headers = new Headers();
328
+ for (const [key, value] of Object.entries(req.headers)) {
329
+ if (value === void 0) continue;
330
+ if (Array.isArray(value)) for (const item of value) headers.append(key, item);
331
+ else headers.set(key, value);
332
+ }
333
+ const ac = new AbortController();
334
+ req.once("aborted", () => ac.abort());
335
+ const method = req.method ?? "GET";
336
+ const init = {
337
+ method,
338
+ headers,
339
+ signal: ac.signal
340
+ };
341
+ if (method === "GET" || method === "HEAD") return new Request(url, init);
342
+ const chunks = [];
343
+ for await (const chunk of req) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
344
+ const body = Buffer.concat(chunks);
345
+ if (body.length > 0) init.body = body;
346
+ return new Request(url, init);
347
+ }
348
+ async function sendWebResponseFrom(req, res, response) {
349
+ return pipeResponse(req, res, response);
350
+ }
351
+ //#endregion
352
+ //#region src/core.ts
353
+ const CELLD_ALLOWED_KEYS = [
354
+ "name",
355
+ "main",
356
+ "compatibility_date",
357
+ "compatibility_flags",
358
+ "durable_objects",
359
+ "migrations",
360
+ "assets",
361
+ "services",
362
+ "vars"
363
+ ];
364
+ const USER_FORBIDDEN_KEYS = ["main", "assets"];
365
+ function createEmitState() {
366
+ return { emitted: false };
367
+ }
368
+ function validateWranglerOptions(wrangler) {
369
+ const allowed = CELLD_ALLOWED_KEYS;
370
+ const invalid = Object.keys(wrangler).filter((key) => !allowed.includes(key));
371
+ if (invalid.length) throw new Error(`oxidejs: these wrangler keys are not supported by celld deploy: ${invalid.join(", ")}`);
372
+ const forbidden = USER_FORBIDDEN_KEYS.filter((key) => key in wrangler);
373
+ if (forbidden.length) throw new Error(`oxidejs: wrangler keys ${forbidden.join(", ")} are computed by the plugin and cannot be user-supplied`);
374
+ }
375
+ function assertContained(outDirAbs, childAbs, label) {
376
+ const outDir = path.resolve(outDirAbs);
377
+ const child = path.resolve(childAbs);
378
+ const relative = path.relative(outDir, child);
379
+ if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`oxidejs: ${label} must resolve inside outDir (got ${relative || "."})`);
380
+ }
381
+ function requireWranglerFields(wrangler) {
382
+ if (!wrangler?.name || !wrangler.compatibility_date) throw new Error("oxidejs: wrangler.name and wrangler.compatibility_date are required when emitConfig is true");
383
+ return wrangler;
384
+ }
385
+ function flattenInput(input) {
386
+ if (!input) return [];
387
+ if (typeof input === "string") return [input];
388
+ if (Array.isArray(input)) return input;
389
+ return Object.values(input);
390
+ }
391
+ function envInput(env) {
392
+ if (!env || typeof env !== "object") return;
393
+ const rec = env;
394
+ return rec.build?.rolldownOptions?.input || rec.build?.rollupOptions?.input || rec.build?.input || rec.input;
395
+ }
396
+ /** Vite client input: rolldown/rollup `input`, else `path.resolve(root, "index.html")`. */
397
+ function hasHtmlEntry(root, config) {
398
+ const cfg = config;
399
+ return flattenInput(envInput(cfg?.environments?.["client"]) || envInput(cfg?.environments?.["web"]) || cfg?.build?.rolldownOptions?.input || cfg?.build?.rollupOptions?.input || path.resolve(root, "index.html")).some((entry) => {
400
+ const file = path.resolve(root, entry);
401
+ return file.endsWith(".html") && fs.existsSync(file);
402
+ });
403
+ }
404
+ function resolveOptions(raw, root, config) {
405
+ const preset = raw?.preset ?? "fetch";
406
+ if (preset !== "fetch" && preset !== "celld") throw new Error(`oxidejs: unknown preset "${String(preset)}"`);
407
+ const actions = raw?.actions ?? "http";
408
+ if (actions !== "http" && actions !== "ws") throw new Error(`oxidejs: unknown actions transport "${String(actions)}"`);
409
+ if (actions === "ws" && preset === "celld") throw new Error("oxidejs: actions: \"ws\" is not supported with preset: \"celld\"");
410
+ const workerEntry = raw?.workerEntry ?? "src/server.ts";
411
+ const outDirInput = raw?.outDir ?? "dist";
412
+ const clientDir = raw?.clientDir ?? "client";
413
+ const emitConfig = raw?.emitConfig ?? preset === "celld";
414
+ const rootAbs = path.resolve(root);
415
+ const outDir = path.resolve(rootAbs, outDirInput);
416
+ const workerEntryAbs = path.resolve(rootAbs, workerEntry);
417
+ const hasClient = hasHtmlEntry(rootAbs, config);
418
+ const hasPublic = fs.existsSync(path.join(rootAbs, "public"));
419
+ if (hasClient || hasPublic) assertContained(outDir, path.resolve(outDir, clientDir), "clientDir");
420
+ if (raw?.wrangler) validateWranglerOptions(raw.wrangler);
421
+ return {
422
+ root: rootAbs,
423
+ preset,
424
+ workerEntry,
425
+ workerEntryAbs,
426
+ outDir,
427
+ clientDir,
428
+ wrangler: emitConfig ? requireWranglerFields(raw?.wrangler) : raw?.wrangler,
429
+ emitConfig,
430
+ hasClient,
431
+ hasPublic,
432
+ actions,
433
+ actionHeaders: raw?.actionHeaders
434
+ };
435
+ }
436
+ function copyPublicDir(opts) {
437
+ if (opts.preset !== "fetch") return;
438
+ const src = path.join(opts.root, "public");
439
+ if (!fs.existsSync(src)) return;
440
+ fs.cpSync(src, path.join(opts.outDir, opts.clientDir), {
441
+ recursive: true,
442
+ force: true
443
+ });
444
+ }
445
+ function tryEmitWranglerConfig(opts, state) {
446
+ if (state.emitted || opts.emitConfig === false) return;
447
+ const wrangler = requireWranglerFields(opts.wrangler);
448
+ const serverFile = path.join(opts.outDir, "server.js");
449
+ const clientDirPath = path.join(opts.outDir, opts.clientDir);
450
+ if (!fs.existsSync(serverFile)) return;
451
+ if (opts.hasClient && !fs.existsSync(clientDirPath)) return;
452
+ assertContained(opts.outDir, serverFile, "main");
453
+ if (opts.hasClient) assertContained(opts.outDir, clientDirPath, "assets.directory");
454
+ const config = {
455
+ name: wrangler.name,
456
+ main: "./server.js",
457
+ compatibility_date: wrangler.compatibility_date,
458
+ ...wrangler.compatibility_flags ? { compatibility_flags: wrangler.compatibility_flags } : {},
459
+ ...wrangler.durable_objects ? { durable_objects: wrangler.durable_objects } : {},
460
+ ...wrangler.migrations ? { migrations: wrangler.migrations } : {},
461
+ ...wrangler.services ? { services: wrangler.services } : {},
462
+ ...wrangler.vars ? { vars: wrangler.vars } : {},
463
+ ...opts.hasClient ? { assets: {
464
+ directory: `./${opts.clientDir}`,
465
+ binding: "ASSETS"
466
+ } } : {}
467
+ };
468
+ fs.writeFileSync(path.join(opts.outDir, "wrangler.jsonc"), `${JSON.stringify(config, null, 2)}\n`);
469
+ state.emitted = true;
470
+ }
471
+ //#endregion
472
+ //#region src/worker-build.ts
473
+ function applyViteEnvironments(config, opts) {
474
+ config.builder ??= {};
475
+ config.environments ??= {};
476
+ const celld = opts.preset === "celld";
477
+ config.environments["ssr"] = {
478
+ consumer: "server",
479
+ build: {
480
+ outDir: opts.outDir,
481
+ emptyOutDir: true,
482
+ ssr: true,
483
+ rolldownOptions: {
484
+ input: VIRTUAL_WORKER_ID,
485
+ external: celld ? [/^cloudflare:/] : [],
486
+ output: {
487
+ format: "es",
488
+ entryFileNames: "server.js"
489
+ }
490
+ },
491
+ rollupOptions: {
492
+ input: VIRTUAL_WORKER_ID,
493
+ external: celld ? [/^cloudflare:/] : [],
494
+ output: {
495
+ format: "es",
496
+ entryFileNames: "server.js"
497
+ }
498
+ }
499
+ },
500
+ resolve: celld ? {
501
+ conditions: ["worker"],
502
+ noExternal: true
503
+ } : { noExternal: true },
504
+ ssr: celld ? {
505
+ target: "webworker",
506
+ noExternal: true,
507
+ external: [/^cloudflare:/]
508
+ } : { noExternal: true }
509
+ };
510
+ config.build ??= {};
511
+ if (opts.hasClient) {
512
+ const clientOutDir = path.join(opts.outDir, opts.clientDir);
513
+ const existingClient = config.environments["client"];
514
+ config.environments["client"] = {
515
+ ...existingClient,
516
+ consumer: "client",
517
+ build: {
518
+ ...existingClient?.build,
519
+ outDir: clientOutDir,
520
+ emptyOutDir: true,
521
+ manifest: true
522
+ }
523
+ };
524
+ config.environments["ssr"].build.emptyOutDir = false;
525
+ config.build.outDir ??= clientOutDir;
526
+ config.build.manifest ??= true;
527
+ } else {
528
+ delete config.environments["client"];
529
+ config.appType = "custom";
530
+ config.build.outDir ??= opts.outDir;
531
+ config.build.emptyOutDir ??= true;
532
+ config.builder.buildApp ??= async (builder) => {
533
+ const server = builder.environments["ssr"];
534
+ if (server) await builder.build(server);
535
+ };
536
+ }
537
+ return config;
538
+ }
539
+ function applyRsbuildEnvironments(config, opts) {
540
+ config.environments ??= {};
541
+ if (opts.hasClient) {
542
+ const clientOutDir = path.join(opts.outDir, opts.clientDir);
543
+ const existingClient = config.environments["web"] ?? config.environments["client"];
544
+ config.environments["web"] = {
545
+ ...existingClient,
546
+ output: {
547
+ ...existingClient?.output,
548
+ target: "web",
549
+ distPath: {
550
+ ...existingClient?.output?.distPath,
551
+ root: clientOutDir
552
+ },
553
+ manifest: true
554
+ }
555
+ };
556
+ } else {
557
+ delete config.environments["web"];
558
+ delete config.environments["client"];
559
+ }
560
+ const server = {
561
+ source: { entry: { server: {
562
+ import: VIRTUAL_WORKER_ID,
563
+ html: false
564
+ } } },
565
+ output: {
566
+ target: opts.preset === "celld" ? "web-worker" : "node",
567
+ filename: { js: "server.js" },
568
+ distPath: { root: opts.outDir }
569
+ }
570
+ };
571
+ if (opts.preset === "celld") server.resolve = { conditionNames: ["worker", "..."] };
572
+ config.environments["server"] = server;
573
+ return config;
574
+ }
575
+ //#endregion
576
+ //#region src/context.ts
577
+ const ALS_KEY = "__oxidejsRequest";
578
+ function als() {
579
+ const g = globalThis;
580
+ return g[ALS_KEY] ??= new AsyncLocalStorage();
581
+ }
582
+ function store() {
583
+ const current = als().getStore();
584
+ if (!current) throw new Error("oxidejs: useRequest() called outside an action");
585
+ return current;
586
+ }
587
+ /** Current tacho `ctx`. Throws outside `*.server.ts` running over `/_action`. */
588
+ function useCtx() {
589
+ return store();
590
+ }
591
+ /** Current action `Request`. Throws outside `*.server.ts` running over `/_action`. */
592
+ function useRequest() {
593
+ return store().req;
594
+ }
595
+ /** Worker `env` from `fetch(request, env, ctx)`. `undefined` on the Node fetch preset. */
596
+ function useEnv() {
597
+ return store().env;
598
+ }
599
+ /** Worker `ctx` from `fetch(request, env, ctx)` (`waitUntil`). Not tacho `ctx`. `undefined` on Node. */
600
+ function useFetchCtx() {
601
+ return store().fetchCtx;
602
+ }
603
+ //#endregion
604
+ //#region src/index.ts
605
+ function actionMiddleware(loadRouter) {
606
+ return (req, res, next) => {
607
+ if ((req.url ?? "").split("?")[0] !== "/_action") {
608
+ next();
609
+ return;
610
+ }
611
+ (async () => {
612
+ const { handle } = await import(
613
+ /* @vite-ignore */
614
+ "tacho/transport/fetch"
615
+ );
616
+ await sendWebResponseFrom(req, res, await handle(await loadRouter(), { path: ACTION_PATH })(await nodeToWebRequest(req)));
617
+ })().catch(next);
618
+ };
619
+ }
620
+ function attachActionUpgrade(httpServer, loadRouter) {
621
+ if (!httpServer) return;
622
+ Promise.all([import(
623
+ /* @vite-ignore */
624
+ "tacho/transport/ws"
625
+ ), import(
626
+ /* @vite-ignore */
627
+ "crossws/adapters/node"
628
+ )]).then(([{ handle }, { default: crossws }]) => {
629
+ httpServer.on("upgrade", (req, socket, head) => {
630
+ if ((req.url ?? "").split("?")[0] !== "/_action") return;
631
+ loadRouter().then((router) => crossws({ hooks: handle(router, { path: ACTION_PATH }) }).handleUpgrade(req, socket, head)).catch(() => {
632
+ socket.destroy();
633
+ });
634
+ });
635
+ });
636
+ }
637
+ function previewMiddleware(file) {
638
+ return (req, res, next) => {
639
+ (async () => {
640
+ await sendWebResponseFrom(req, res, await (await import(
641
+ /* @vite-ignore */
642
+ pathToFileURL(file).href
643
+ )).default.fetch(await nodeToWebRequest(req)));
644
+ })().catch(next);
645
+ };
646
+ }
647
+ function loadActions(root) {
648
+ const code = generateActionsModule(scanServerFiles(root), { bust: true });
649
+ return import(
650
+ /* @vite-ignore */
651
+ `data:text/javascript,${encodeURIComponent(code)}`
652
+ );
653
+ }
654
+ const unpluginFactory = (options) => {
655
+ let resolved;
656
+ const emitState = createEmitState();
657
+ return {
658
+ name: "oxidejs",
659
+ enforce: "pre",
660
+ buildStart() {
661
+ resolved ??= resolveOptions(options, process.cwd());
662
+ emitState.emitted = false;
663
+ },
664
+ resolveId(id) {
665
+ if (id === "virtual:oxide/actions") return RESOLVED_VIRTUAL_ACTIONS_ID;
666
+ if (id === "virtual:oxide/worker") return RESOLVED_VIRTUAL_WORKER_ID;
667
+ if (id === "virtual:oxide/client") return RESOLVED_VIRTUAL_CLIENT_ID;
668
+ return null;
669
+ },
670
+ load(id, extra) {
671
+ if (id === RESOLVED_VIRTUAL_CLIENT_ID) return generateClientModule(resolved?.actions ?? options?.actions ?? "http", resolved?.actionHeaders ?? options?.actionHeaders);
672
+ if (id === RESOLVED_VIRTUAL_ACTIONS_ID || id === RESOLVED_VIRTUAL_WORKER_ID) {
673
+ if (pluginShouldStub(this, extra)) throw new Error(`oxidejs: ${id === RESOLVED_VIRTUAL_ACTIONS_ID ? VIRTUAL_ACTIONS_ID : VIRTUAL_WORKER_ID} is server-only`);
674
+ }
675
+ if (id === RESOLVED_VIRTUAL_ACTIONS_ID) {
676
+ const modules = scanServerFiles(resolved?.root ?? process.cwd());
677
+ for (const mod of modules) this.addWatchFile(mod.abs);
678
+ return generateActionsModule(modules);
679
+ }
680
+ if (id === RESOLVED_VIRTUAL_WORKER_ID) {
681
+ if (!resolved) return;
682
+ this.addWatchFile(resolved.workerEntryAbs);
683
+ const modules = scanServerFiles(resolved.root);
684
+ for (const mod of modules) this.addWatchFile(mod.abs);
685
+ return generateWorkerWrapper(resolved.workerEntryAbs, {
686
+ preset: resolved.preset,
687
+ clientDir: resolved.clientDir,
688
+ hasClient: resolved.hasClient,
689
+ hasPublic: resolved.hasPublic,
690
+ hasActions: modules.length > 0,
691
+ actions: resolved.actions
692
+ });
693
+ }
694
+ if (isServerFileId(id) && pluginShouldStub(this, extra)) {
695
+ const file = id.split("?")[0] ?? id;
696
+ this.addWatchFile(file);
697
+ return loadClientStub(id);
698
+ }
699
+ },
700
+ transform(code, id, extra) {
701
+ if (!isServerFileId(id) || !pluginShouldStub(this, extra)) return;
702
+ return generateClientStub({
703
+ key: moduleKey(id.split("?")[0] ?? id),
704
+ exports: parseExportedNames(code)
705
+ });
706
+ },
707
+ vite: {
708
+ config(config) {
709
+ resolved = resolveOptions(options, typeof config.root === "string" ? config.root : process.cwd(), config);
710
+ applyViteEnvironments(config, resolved);
711
+ },
712
+ configureServer(server) {
713
+ const invalidateActions = () => {
714
+ for (const env of Object.values(server.environments)) {
715
+ const mod = env.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ACTIONS_ID);
716
+ if (mod) env.moduleGraph.invalidateModule(mod);
717
+ }
718
+ };
719
+ server.watcher.on("all", (_event, file) => {
720
+ if (isServerFileId(file)) invalidateActions();
721
+ });
722
+ const loadRouter = async () => {
723
+ return (await server.ssrLoadModule(VIRTUAL_ACTIONS_ID)).default;
724
+ };
725
+ if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
726
+ else server.middlewares.use(actionMiddleware(loadRouter));
727
+ },
728
+ configurePreviewServer(server) {
729
+ if (resolved?.preset !== "fetch") return;
730
+ server.middlewares.use(previewMiddleware(path.join(resolved.outDir, "server.js")));
731
+ }
732
+ },
733
+ rsbuild: { setup(api) {
734
+ api.modifyRsbuildConfig((config) => {
735
+ resolved = resolveOptions(options, typeof config.root === "string" ? config.root : process.cwd(), config);
736
+ applyRsbuildEnvironments(config, resolved);
737
+ });
738
+ api.onBeforeStartDevServer(({ server }) => {
739
+ const loadRouter = async () => {
740
+ return (await loadActions(resolved?.root ?? process.cwd())).default;
741
+ };
742
+ if (resolved?.actions === "ws") attachActionUpgrade(server.httpServer, loadRouter);
743
+ else server.middlewares.use(actionMiddleware(loadRouter));
744
+ });
745
+ api.onBeforeStartPreviewServer?.(({ server }) => {
746
+ if (resolved?.preset !== "fetch") return;
747
+ server.middlewares.use(previewMiddleware(path.join(resolved.outDir, "server.js")));
748
+ });
749
+ } },
750
+ writeBundle() {
751
+ if (!resolved) return;
752
+ copyPublicDir(resolved);
753
+ tryEmitWranglerConfig(resolved, emitState);
754
+ }
755
+ };
756
+ };
757
+ const oxidejs = /* @__PURE__ */ createUnplugin(unpluginFactory);
758
+ const vite = /* @__PURE__ */ (() => oxidejs.vite)();
759
+ //#endregion
760
+ export { useEnv as a, useCtx as i, unpluginFactory as n, useFetchCtx as o, vite as r, useRequest as s, oxidejs as t };
@@ -0,0 +1,52 @@
1
+ //#region src/types.d.ts
2
+ type OxidejsPreset = "fetch" | "celld";
3
+ interface OxidejsWranglerOptions {
4
+ name: string;
5
+ compatibility_date: string;
6
+ compatibility_flags?: string[];
7
+ durable_objects?: Record<string, unknown>;
8
+ migrations?: unknown[];
9
+ services?: unknown[];
10
+ vars?: Record<string, unknown>;
11
+ }
12
+ type OxidejsActionTransport = "http" | "ws";
13
+ /** Static headers inlined into the shared action client. Functions cannot ship to the browser. */
14
+ type OxidejsActionHeaders = Record<string, string> | [string, string][];
15
+ interface OxidejsOptions {
16
+ /** "fetch" (default) skips wrangler.jsonc and serves client assets. "celld" emits wrangler.jsonc. */
17
+ preset?: OxidejsPreset;
18
+ /** Path to server entry, relative to project root. Default: "src/server.ts" */
19
+ workerEntry?: string;
20
+ /** Output root. Default: "dist" */
21
+ outDir?: string;
22
+ /** Client subdirectory under outDir. Default: "client" */
23
+ clientDir?: string;
24
+ /** Wrangler config fields to merge into the generated wrangler.jsonc. */
25
+ wrangler?: OxidejsWranglerOptions;
26
+ /** Skip config emission. Defaults to false for celld, true for fetch. */
27
+ emitConfig?: boolean;
28
+ /** Transport for `*.server.ts` stubs. Default: "http". */
29
+ actions?: OxidejsActionTransport;
30
+ /** Extra headers on the shared HTTP action client. Ignored when `actions` is "ws". */
31
+ actionHeaders?: OxidejsActionHeaders;
32
+ }
33
+ interface ResolvedOptions {
34
+ root: string;
35
+ preset: OxidejsPreset;
36
+ workerEntry: string;
37
+ workerEntryAbs: string;
38
+ /** Absolute output root. */
39
+ outDir: string;
40
+ /** Relative segment only. */
41
+ clientDir: string;
42
+ wrangler: OxidejsWranglerOptions | undefined;
43
+ emitConfig: boolean;
44
+ /** False when there is no index.html — server-only, no client env or assets. */
45
+ hasClient: boolean;
46
+ /** True when `<root>/public` exists. Copied next to client assets on fetch. */
47
+ hasPublic: boolean;
48
+ actions: OxidejsActionTransport;
49
+ actionHeaders: OxidejsActionHeaders | undefined;
50
+ }
51
+ //#endregion
52
+ export { OxidejsWranglerOptions as a, OxidejsPreset as i, OxidejsActionTransport as n, ResolvedOptions as o, OxidejsOptions as r, OxidejsActionHeaders as t };
@@ -0,0 +1,5 @@
1
+ import { r as OxidejsOptions } from "./types-CFWyYfds.mjs";
2
+ //#region src/vite.d.ts
3
+ declare const _default: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
4
+ //#endregion
5
+ export { type OxidejsOptions, _default as default };
package/dist/vite.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { t as oxidejs } from "./src-B03PPyx9.mjs";
2
+ //#region src/vite.ts
3
+ var vite_default = oxidejs.vite;
4
+ //#endregion
5
+ export { vite_default as default };
package/package.json CHANGED
@@ -1,12 +1,87 @@
1
1
  {
2
2
  "name": "oxidejs",
3
- "version": "0.0.1",
4
- "module": "index.ts",
3
+ "version": "0.1.1",
4
+ "description": "Vite/Rsbuild plugin. One build → dist/server.js + optional client. Server actions via *.server.ts.",
5
+ "keywords": [
6
+ "cloudflare",
7
+ "rsbuild",
8
+ "server-actions",
9
+ "typescript",
10
+ "unplugin",
11
+ "vite"
12
+ ],
13
+ "homepage": "https://github.com/ryuzcorp/oxide/tree/main/packages/oxidejs#readme",
14
+ "bugs": "https://github.com/ryuzcorp/oxide/issues",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ryuzcorp/oxide.git",
19
+ "directory": "packages/oxidejs"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "virtual.d.ts",
24
+ "client.d.ts",
25
+ "tsconfig.app.json"
26
+ ],
5
27
  "type": "module",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.mts",
32
+ "import": "./dist/index.mjs"
33
+ },
34
+ "./vite": {
35
+ "types": "./dist/vite.d.mts",
36
+ "import": "./dist/vite.mjs"
37
+ },
38
+ "./rsbuild": {
39
+ "types": "./dist/rsbuild.d.mts",
40
+ "import": "./dist/rsbuild.mjs"
41
+ },
42
+ "./virtual": {
43
+ "types": "./virtual.d.ts"
44
+ },
45
+ "./client": {
46
+ "types": "./client.d.ts"
47
+ },
48
+ "./tsconfig": "./tsconfig.app.json"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown",
52
+ "test": "bun test",
53
+ "typecheck": "tsc --noEmit",
54
+ "lint": "oxlint",
55
+ "fmt": "oxfmt --write .",
56
+ "prepublishOnly": "bun run build"
57
+ },
58
+ "dependencies": {
59
+ "unplugin": "^3.3.0"
60
+ },
6
61
  "devDependencies": {
7
- "@types/bun": "latest"
62
+ "tacho": "^0.4.1"
8
63
  },
9
64
  "peerDependencies": {
10
- "typescript": "^5"
65
+ "@rsbuild/core": "*",
66
+ "crossws": "*",
67
+ "tacho": "*",
68
+ "vite": "*"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@rsbuild/core": {
72
+ "optional": true
73
+ },
74
+ "crossws": {
75
+ "optional": true
76
+ },
77
+ "vite": {
78
+ "optional": true
79
+ },
80
+ "tacho": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "engines": {
85
+ "node": ">=20"
11
86
  }
12
87
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "moduleDetection": "force",
8
+ "isolatedModules": true,
9
+ "verbatimModuleSyntax": true,
10
+ "allowJs": true,
11
+ "allowImportingTsExtensions": true,
12
+ "strict": true,
13
+ "noEmit": true,
14
+ "skipLibCheck": true,
15
+ "resolveJsonModule": true,
16
+ "noImplicitOverride": true,
17
+ "jsx": "preserve",
18
+ "types": ["vite/client", "oxidejs/client", "oxidejs/virtual"]
19
+ }
20
+ }
package/virtual.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ declare module "virtual:oxide/actions" {
2
+ // Generated tacho router. Typed loosely so apps can pass it to handle().
3
+ const actions: Record<string, never>;
4
+ export default actions;
5
+ export { actions };
6
+ }
7
+
8
+ declare module "virtual:oxide/client" {
9
+ export const client: Record<string, Record<string, (...args: unknown[]) => Promise<unknown>>>;
10
+ }
package/bun.lock DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "oxide-placeholder",
7
- "devDependencies": {
8
- "@types/bun": "latest",
9
- },
10
- "peerDependencies": {
11
- "typescript": "^5",
12
- },
13
- },
14
- },
15
- "packages": {
16
- "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
17
-
18
- "@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
19
-
20
- "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
21
-
22
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
23
-
24
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
25
- }
26
- }
package/index.ts DELETED
@@ -1 +0,0 @@
1
- console.log("Hello via Bun!");
package/readme.md DELETED
@@ -1 +0,0 @@
1
- Placeholder for https://github.com/oxidejs/oxide release
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
18
- "strict": true,
19
- "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
- "noImplicitOverride": true,
23
-
24
- // Some stricter flags (disabled by default)
25
- "noUnusedLocals": false,
26
- "noUnusedParameters": false,
27
- "noPropertyAccessFromIndexSignature": false
28
- }
29
- }