oxidejs 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,382 @@
1
+ import { _ as matchesActionPath, t as ACTION_PATH } from "./actions-DE6p5Cyp.mjs";
2
+ import { t as runWithRequest } from "./context-zrTZyYpF.mjs";
3
+ import { Layer } from "effect";
4
+ import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
5
+ import { HttpRouter } from "effect/unstable/http";
6
+ //#region src/rpc/same-origin.ts
7
+ function isSameOrigin(request) {
8
+ const site = request.headers.get("sec-fetch-site");
9
+ const origin = request.headers.get("origin");
10
+ if (!origin && !site) return false;
11
+ if (site && site !== "same-origin" && site !== "none") return false;
12
+ if (!origin) return site === "same-origin" || site === "none";
13
+ try {
14
+ return new URL(origin).host === (request.headers.get("host") ?? new URL(request.url).host);
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ //#endregion
20
+ //#region src/rpc/scrub.ts
21
+ /** Strip Effect RPC `Defect` / `Cause` payloads down to plain JSON-RPC errors. */
22
+ const INTERNAL = {
23
+ code: -32603,
24
+ message: "Internal error"
25
+ };
26
+ const NDJSON_CONTENT = "application/json-rpc";
27
+ function isRecord(value) {
28
+ return value !== null && typeof value === "object";
29
+ }
30
+ function classifyCause(error) {
31
+ const blob = `${String(error["message"] ?? "")}${JSON.stringify(error["data"] ?? "")}`;
32
+ if (/Unknown request tag/i.test(blob)) return {
33
+ code: -32601,
34
+ message: "Method not found"
35
+ };
36
+ if (/Missing key/i.test(blob) || /Expected/i.test(blob) && /\["args"\]|\[\\"args\\"\]/.test(blob)) return {
37
+ code: -32602,
38
+ message: "Invalid params"
39
+ };
40
+ return { ...INTERNAL };
41
+ }
42
+ function scrubError(error) {
43
+ if (!isRecord(error)) return { ...INTERNAL };
44
+ if (error["_tag"] === "Defect") return { ...INTERNAL };
45
+ if (error["_tag"] === "Cause") return classifyCause(error);
46
+ if (typeof error["code"] === "number" && typeof error["message"] === "string") return {
47
+ code: error["code"],
48
+ message: error["message"]
49
+ };
50
+ return { ...INTERNAL };
51
+ }
52
+ function createIdRepairState(requestIds = []) {
53
+ return { remaining: new Set(requestIds) };
54
+ }
55
+ /**
56
+ * Scrub one JSON-RPC response object.
57
+ * Effect encodes Defects with `id: -32603`; reclaim the originating request id from `state.remaining`.
58
+ */
59
+ function scrubRpcMessage(msg, requestIds = [], state = createIdRepairState(requestIds)) {
60
+ if (!isRecord(msg) || !("error" in msg) || msg["error"] == null) {
61
+ if (isRecord(msg) && msg["chunk"] !== true && msg["id"] !== -32603 && "id" in msg) state.remaining.delete(msg["id"]);
62
+ return msg;
63
+ }
64
+ const error = scrubError(msg["error"]);
65
+ let id = msg["id"];
66
+ if (id === -32603) {
67
+ const next = state.remaining.values().next();
68
+ if (!next.done) {
69
+ id = next.value;
70
+ state.remaining.delete(next.value);
71
+ } else id = null;
72
+ } else if (id !== void 0 && id !== null) state.remaining.delete(id);
73
+ if (id === void 0) id = null;
74
+ return {
75
+ jsonrpc: "2.0",
76
+ id,
77
+ error
78
+ };
79
+ }
80
+ /**
81
+ * Rewrite a JSON / NDJSON body so clients never see Effect `_tag` / `data` trees.
82
+ * Accepts a single object, a JSON array, or newline-delimited frames.
83
+ */
84
+ function scrubRpcJson(body, requestIds = []) {
85
+ const trimmed = body.replace(/^\uFEFF/, "");
86
+ if (!trimmed) return body;
87
+ const state = createIdRepairState(requestIds);
88
+ if (trimmed.includes("\n")) {
89
+ const lines = trimmed.split("\n");
90
+ const out = [];
91
+ for (const line of lines) {
92
+ if (line === "") continue;
93
+ out.push(scrubRpcLine(line, state));
94
+ }
95
+ return trimmed.endsWith("\n") ? `${out.join("\n")}\n` : out.join("\n");
96
+ }
97
+ try {
98
+ const parsed = JSON.parse(trimmed);
99
+ if (Array.isArray(parsed)) return JSON.stringify(parsed.map((msg) => scrubRpcMessage(msg, requestIds, state)));
100
+ return JSON.stringify(scrubRpcMessage(parsed, requestIds, state));
101
+ } catch {
102
+ return body;
103
+ }
104
+ }
105
+ function scrubRpcLine(line, state) {
106
+ try {
107
+ return JSON.stringify(scrubRpcMessage(JSON.parse(line), [], state));
108
+ } catch {
109
+ return line;
110
+ }
111
+ }
112
+ /** Collect JSON-RPC request ids from a unary object or batch array body. */
113
+ function extractJsonRpcRequestIds(body) {
114
+ try {
115
+ let text = typeof body === "string" ? body : new TextDecoder().decode(body instanceof Uint8Array ? body : new Uint8Array(body));
116
+ text = text.replace(/^\uFEFF/, "").trimEnd();
117
+ if (text.includes("\n")) {
118
+ const ids = [];
119
+ for (const line of text.split("\n")) {
120
+ if (!line) continue;
121
+ const parsed = JSON.parse(line);
122
+ if (isRecord(parsed) && "id" in parsed) ids.push(parsed["id"]);
123
+ }
124
+ return ids;
125
+ }
126
+ const parsed = JSON.parse(text);
127
+ if (Array.isArray(parsed)) return parsed.filter(isRecord).filter((item) => "id" in item).map((item) => item["id"]);
128
+ if (isRecord(parsed) && "id" in parsed) return [parsed["id"]];
129
+ } catch {}
130
+ return [];
131
+ }
132
+ /** Ensure a body is a valid NDJSON frame (Effect's ndJsonRpc decode requires a trailing newline). */
133
+ function ensureNdjsonBody(buf) {
134
+ const bytes = new Uint8Array(buf);
135
+ if (bytes.length > 0 && bytes[bytes.length - 1] === 10) return bytes;
136
+ const out = new Uint8Array(bytes.length + 1);
137
+ out.set(bytes);
138
+ out[bytes.length] = 10;
139
+ return out;
140
+ }
141
+ /**
142
+ * TransformStream that scrubs Effect defect payloads one NDJSON line at a time,
143
+ * so long-running stream actions stay incremental.
144
+ */
145
+ function scrubNdjsonTransform(requestIds = []) {
146
+ const decoder = new TextDecoder();
147
+ const encoder = new TextEncoder();
148
+ const state = createIdRepairState(requestIds);
149
+ let pending = "";
150
+ return new TransformStream({
151
+ transform(chunk, controller) {
152
+ pending += decoder.decode(chunk, { stream: true });
153
+ let nl = pending.indexOf("\n");
154
+ while (nl !== -1) {
155
+ const line = pending.slice(0, nl);
156
+ pending = pending.slice(nl + 1);
157
+ if (line.length > 0) controller.enqueue(encoder.encode(`${scrubRpcLine(line, state)}\n`));
158
+ nl = pending.indexOf("\n");
159
+ }
160
+ },
161
+ flush(controller) {
162
+ pending += decoder.decode();
163
+ if (pending.length > 0) {
164
+ controller.enqueue(encoder.encode(`${scrubRpcLine(pending, state)}\n`));
165
+ pending = "";
166
+ }
167
+ }
168
+ });
169
+ }
170
+ //#endregion
171
+ //#region src/rpc/server.ts
172
+ const JSON_RPC_FORBIDDEN = {
173
+ jsonrpc: "2.0",
174
+ error: {
175
+ code: -32600,
176
+ message: "Forbidden"
177
+ },
178
+ id: null
179
+ };
180
+ const bundles = /* @__PURE__ */ new Map();
181
+ const groupIds = /* @__PURE__ */ new WeakMap();
182
+ let nextGroupId = 0;
183
+ const serialization = RpcSerialization.layerNdJsonRpc();
184
+ function bundleKey(group, path, transport) {
185
+ let id = groupIds.get(group);
186
+ if (id === void 0) {
187
+ id = nextGroupId++;
188
+ groupIds.set(group, id);
189
+ }
190
+ return `${id}:${path}:${transport}`;
191
+ }
192
+ function forbidden() {
193
+ return new Response(JSON.stringify(JSON_RPC_FORBIDDEN), {
194
+ status: 403,
195
+ headers: { "content-type": "application/json" }
196
+ });
197
+ }
198
+ function methodNotAllowed() {
199
+ return new Response("Method Not Allowed", {
200
+ status: 405,
201
+ headers: { Allow: "POST" }
202
+ });
203
+ }
204
+ function buildBundle(group, handlers, path, transport) {
205
+ const app = RpcServer.layerHttp({
206
+ group,
207
+ path,
208
+ protocol: transport === "ws" ? "websocket" : "http"
209
+ }).pipe(Layer.provide(handlers), Layer.provide(serialization));
210
+ return HttpRouter.toWebHandler(app, { disableLogger: true });
211
+ }
212
+ function bundleFor(group, handlers, path, transport) {
213
+ const key = bundleKey(group, path, transport);
214
+ const cached = bundles.get(key);
215
+ if (cached) return cached;
216
+ const built = buildBundle(group, handlers, path, transport);
217
+ bundles.set(key, built);
218
+ return built;
219
+ }
220
+ function scrubJsonResponse(response, requestIds) {
221
+ const contentType = response.headers.get("content-type") ?? "";
222
+ if (!contentType.includes("json")) return response;
223
+ if (response.body && (contentType.includes("application/json-rpc") || contentType.includes("ndjson"))) {
224
+ const headers = new Headers(response.headers);
225
+ headers.delete("content-length");
226
+ return new Response(response.body.pipeThrough(scrubNdjsonTransform(requestIds)), {
227
+ status: response.status,
228
+ statusText: response.statusText,
229
+ headers
230
+ });
231
+ }
232
+ return response;
233
+ }
234
+ async function scrubBufferedJson(response, requestIds) {
235
+ const text = await response.text();
236
+ const headers = new Headers(response.headers);
237
+ headers.delete("content-length");
238
+ return new Response(scrubRpcJson(text, requestIds), {
239
+ status: response.status,
240
+ statusText: response.statusText,
241
+ headers
242
+ });
243
+ }
244
+ function createActionHandler(group, handlers, options = {}) {
245
+ const path = options.path ?? "/__oxide/action";
246
+ const transport = options.transport ?? "http";
247
+ const sameOrigin = options.sameOrigin ?? true;
248
+ return async (request) => {
249
+ if (!matchesActionPath(new URL(request.url).pathname, path)) return new Response("Not Found", { status: 404 });
250
+ if (transport === "http" && request.method !== "POST") return methodNotAllowed();
251
+ if (sameOrigin && !isSameOrigin(request)) return forbidden();
252
+ const rawBody = ensureNdjsonBody(await request.arrayBuffer());
253
+ const requestIds = extractJsonRpcRequestIds(rawBody);
254
+ const headers = new Headers(request.headers);
255
+ headers.set("content-type", NDJSON_CONTENT);
256
+ const forwarded = new Request(request.url, {
257
+ method: request.method,
258
+ headers,
259
+ body: rawBody,
260
+ signal: request.signal
261
+ });
262
+ const extra = await options.createContext?.(forwarded) ?? {};
263
+ const { handler } = bundleFor(group, handlers, path, transport);
264
+ const response = await runWithRequest(forwarded, () => handler(forwarded), extra);
265
+ const contentType = response.headers.get("content-type") ?? "";
266
+ if (contentType.includes("application/json-rpc") || contentType.includes("ndjson")) return scrubJsonResponse(response, requestIds);
267
+ if (contentType.includes("json")) return scrubBufferedJson(response, requestIds);
268
+ return response;
269
+ };
270
+ }
271
+ function disposeActionHandler(group, path = ACTION_PATH, transport = "http") {
272
+ const key = bundleKey(group, path, transport);
273
+ const bundle = bundles.get(key);
274
+ bundles.delete(key);
275
+ return bundle?.dispose() ?? Promise.resolve();
276
+ }
277
+ //#endregion
278
+ //#region src/rpc/ws.ts
279
+ function parseMessage(message, maxBytes) {
280
+ try {
281
+ const raw = message.text();
282
+ if (new TextEncoder().encode(raw).byteLength > maxBytes) return {
283
+ ok: false,
284
+ tooLarge: true
285
+ };
286
+ return {
287
+ ok: true,
288
+ value: raw
289
+ };
290
+ } catch {
291
+ return { ok: false };
292
+ }
293
+ }
294
+ /** Forward each complete NDJSON line as its own WS message (keeps streams incremental). */
295
+ async function sendNdjsonFrames(peer, response, signal) {
296
+ if (signal.aborted) {
297
+ await response.body?.cancel();
298
+ return;
299
+ }
300
+ if (!response.body) {
301
+ const text = await response.text();
302
+ if (text && !signal.aborted) peer.send(text);
303
+ return;
304
+ }
305
+ const reader = response.body.getReader();
306
+ const decoder = new TextDecoder();
307
+ let pending = "";
308
+ const onAbort = () => {
309
+ reader.cancel();
310
+ };
311
+ signal.addEventListener("abort", onAbort, { once: true });
312
+ try {
313
+ while (!signal.aborted) {
314
+ const { done, value } = await reader.read();
315
+ if (done) break;
316
+ pending += decoder.decode(value, { stream: true });
317
+ let nl = pending.indexOf("\n");
318
+ while (nl !== -1) {
319
+ const line = pending.slice(0, nl);
320
+ pending = pending.slice(nl + 1);
321
+ if (line.length > 0 && !signal.aborted) peer.send(`${line}\n`);
322
+ nl = pending.indexOf("\n");
323
+ }
324
+ }
325
+ if (!signal.aborted) {
326
+ pending += decoder.decode();
327
+ if (pending.length > 0) peer.send(pending.endsWith("\n") ? pending : `${pending}\n`);
328
+ }
329
+ } finally {
330
+ signal.removeEventListener("abort", onAbort);
331
+ }
332
+ }
333
+ function createWsHooks(group, handlers, options = {}) {
334
+ const path = options.path ?? "/__oxide/action";
335
+ const maxBytes = options.maxMessageSize ?? 1048576;
336
+ const sameOrigin = options.sameOrigin ?? true;
337
+ const baseOptions = {
338
+ path,
339
+ transport: "http",
340
+ sameOrigin
341
+ };
342
+ return {
343
+ upgrade(req) {
344
+ if (!matchesActionPath(new URL(req.url).pathname, path)) return new Response("Not Found", { status: 404 });
345
+ if (sameOrigin && !isSameOrigin(req)) return new Response("Forbidden", { status: 403 });
346
+ },
347
+ async message(peer, message) {
348
+ const parsed = parseMessage(message, maxBytes);
349
+ if (!parsed.ok) {
350
+ peer.send(JSON.stringify({
351
+ jsonrpc: "2.0",
352
+ error: {
353
+ code: -32600,
354
+ message: parsed.tooLarge ? "Payload too large" : "Parse error"
355
+ },
356
+ id: null
357
+ }));
358
+ return;
359
+ }
360
+ const abort = new AbortController();
361
+ peer.onClose?.(() => abort.abort());
362
+ const host = peer.request?.headers.get("host") ?? "localhost";
363
+ const headers = new Headers(peer.request?.headers);
364
+ headers.set("content-type", NDJSON_CONTENT);
365
+ const peerCtx = await options.createContext?.(peer) ?? peer.context;
366
+ await sendNdjsonFrames(peer, await createActionHandler(group, handlers, {
367
+ ...baseOptions,
368
+ createContext: (req) => ({
369
+ ...peerCtx,
370
+ req
371
+ })
372
+ })(new Request(`http://${host}${path}`, {
373
+ method: "POST",
374
+ headers,
375
+ body: parsed.value,
376
+ signal: abort.signal
377
+ })), abort.signal);
378
+ }
379
+ };
380
+ }
381
+ //#endregion
382
+ export { extractJsonRpcRequestIds as a, scrubRpcMessage as c, ensureNdjsonBody as i, createActionHandler as n, scrubNdjsonTransform as o, disposeActionHandler as r, scrubRpcJson as s, createWsHooks as t };
package/dist/rpc.d.mts ADDED
@@ -0,0 +1,75 @@
1
+ import { t as ActionContext } from "./context-C1UFQ0Zc.mjs";
2
+ import { RpcClientOptions, createClient } from "./rpc/client.mjs";
3
+ import { Layer, Stream } from "effect";
4
+ import { Rpc, RpcGroup } from "effect/unstable/rpc";
5
+ //#region src/rpc/server.d.ts
6
+ type ActionHandlerOptions = {
7
+ path?: string;
8
+ sameOrigin?: boolean;
9
+ transport?: "http" | "ws";
10
+ createContext?: (req: Request) => ActionContext | Promise<ActionContext>;
11
+ };
12
+ type ActionGroup = RpcGroup.RpcGroup<Rpc.Any>;
13
+ declare function createActionHandler(group: ActionGroup, handlers: Layer.Layer<unknown, unknown, unknown>, options?: ActionHandlerOptions): (request: Request) => Promise<Response>;
14
+ declare function disposeActionHandler(group: ActionGroup, path?: string, transport?: "http" | "ws"): Promise<void>;
15
+ //#endregion
16
+ //#region src/rpc/ws.d.ts
17
+ type WsPeer = {
18
+ request?: Request;
19
+ context: Record<string, unknown>;
20
+ send: (data: unknown) => unknown;
21
+ /** Optional: register a listener when the peer disconnects. */
22
+ onClose?: (fn: () => void) => void;
23
+ };
24
+ type WsMessage = {
25
+ text: () => string;
26
+ };
27
+ type WsHooksOptions = {
28
+ path?: string;
29
+ sameOrigin?: boolean;
30
+ maxMessageSize?: number;
31
+ createContext?: (peer: WsPeer) => ActionContext | Promise<ActionContext>;
32
+ };
33
+ declare function createWsHooks(group: RpcGroup.RpcGroup<Rpc.Any>, handlers: Layer.Layer<unknown, unknown, unknown>, options?: WsHooksOptions): {
34
+ upgrade(req: Request): Response | undefined;
35
+ message(peer: WsPeer, message: WsMessage): Promise<void>;
36
+ };
37
+ //#endregion
38
+ //#region src/rpc/stream.d.ts
39
+ declare function asyncGenToStream<T>(gen: AsyncGenerator<T, unknown, unknown>): Stream.Stream<T, Error, never>;
40
+ /**
41
+ * Re-enter `run` for every generator pull so AsyncLocalStorage request context
42
+ * stays available across yields (Effect may drain the stream after the outer ALS scope ends).
43
+ */
44
+ declare function bindAsyncGenContext<T>(gen: AsyncGenerator<T, unknown, unknown>, run: <R>(fn: () => R) => R): AsyncGenerator<T, unknown, unknown>;
45
+ /** Create a generator inside `run`, then keep every subsequent pull inside `run`. */
46
+ declare function asyncGenToStreamInContext<T>(create: () => AsyncGenerator<T, unknown, unknown>, run: <R>(fn: () => R) => R): Stream.Stream<T, Error, never>;
47
+ declare function streamToAsyncGen<T>(stream: Stream.Stream<T>): AsyncIterable<T>;
48
+ //#endregion
49
+ //#region src/rpc/scrub.d.ts
50
+ /** Mutable repair state so each Defect can claim a distinct originating request id. */
51
+ type IdRepairState = {
52
+ /** Request ids not yet claimed by a terminal (non-chunk) response. */
53
+ remaining: Set<unknown>;
54
+ };
55
+ /**
56
+ * Scrub one JSON-RPC response object.
57
+ * Effect encodes Defects with `id: -32603`; reclaim the originating request id from `state.remaining`.
58
+ */
59
+ declare function scrubRpcMessage(msg: unknown, requestIds?: readonly unknown[], state?: IdRepairState): unknown;
60
+ /**
61
+ * Rewrite a JSON / NDJSON body so clients never see Effect `_tag` / `data` trees.
62
+ * Accepts a single object, a JSON array, or newline-delimited frames.
63
+ */
64
+ declare function scrubRpcJson(body: string, requestIds?: readonly unknown[]): string;
65
+ /** Collect JSON-RPC request ids from a unary object or batch array body. */
66
+ declare function extractJsonRpcRequestIds(body: ArrayBuffer | Uint8Array | string): unknown[];
67
+ /** Ensure a body is a valid NDJSON frame (Effect's ndJsonRpc decode requires a trailing newline). */
68
+ declare function ensureNdjsonBody(buf: ArrayBuffer): Uint8Array<ArrayBuffer>;
69
+ /**
70
+ * TransformStream that scrubs Effect defect payloads one NDJSON line at a time,
71
+ * so long-running stream actions stay incremental.
72
+ */
73
+ declare function scrubNdjsonTransform(requestIds?: readonly unknown[]): TransformStream<Uint8Array, Uint8Array>;
74
+ //#endregion
75
+ export { type ActionHandlerOptions, type RpcClientOptions, type WsHooksOptions, asyncGenToStream, asyncGenToStreamInContext, bindAsyncGenContext, createActionHandler, createClient, createWsHooks, disposeActionHandler, ensureNdjsonBody, extractJsonRpcRequestIds, scrubNdjsonTransform, scrubRpcJson, scrubRpcMessage, streamToAsyncGen };
package/dist/rpc.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { a as streamToAsyncGen, i as bindAsyncGenContext, n as asyncGenToStream, r as asyncGenToStreamInContext, t as createClient } from "./client-Bc4g9AEw.mjs";
2
+ import { a as extractJsonRpcRequestIds, c as scrubRpcMessage, i as ensureNdjsonBody, n as createActionHandler, o as scrubNdjsonTransform, r as disposeActionHandler, s as scrubRpcJson, t as createWsHooks } from "./rpc-DYFEdah8.mjs";
3
+ export { asyncGenToStream, asyncGenToStreamInContext, bindAsyncGenContext, createActionHandler, createClient, createWsHooks, disposeActionHandler, ensureNdjsonBody, extractJsonRpcRequestIds, scrubNdjsonTransform, scrubRpcJson, scrubRpcMessage, streamToAsyncGen };
package/dist/rsbuild.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as oxidejs } from "./plugin-nO0yjtk0.mjs";
1
+ import { t as oxidejs } from "./plugin-IQa_lKkT.mjs";
2
2
  //#region src/rsbuild.ts
3
3
  var rsbuild_default = oxidejs.rsbuild;
4
4
  //#endregion
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as oxidejs } from "./plugin-nO0yjtk0.mjs";
1
+ import { t as oxidejs } from "./plugin-IQa_lKkT.mjs";
2
2
  //#region src/vite.ts
3
3
  var vite_default = oxidejs.vite;
4
4
  //#endregion
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,6 @@
1
+ import { ensureWorkerDom } from "../worker-dom.mjs";
2
+ //#region src/worker-dom/install.ts
3
+ /** Side-effect import: install Worker DOM before any user worker module evaluates. */
4
+ ensureWorkerDom();
5
+ //#endregion
6
+ export {};
@@ -0,0 +1,5 @@
1
+ //#region src/worker-dom.d.ts
2
+ /** Install a minimal DOM on `globalThis` for Ilha `renderToString` in Workers. */
3
+ declare function ensureWorkerDom(): void;
4
+ //#endregion
5
+ export { ensureWorkerDom };
@@ -0,0 +1,16 @@
1
+ import * as linkedom from "linkedom";
2
+ //#region src/worker-dom.ts
3
+ /** Install a minimal DOM on `globalThis` for Ilha `renderToString` in Workers. */
4
+ function ensureWorkerDom() {
5
+ if (typeof globalThis.document !== "undefined") return;
6
+ const window = linkedom.parseHTML("<!DOCTYPE html><html><body></body></html>");
7
+ const g = globalThis;
8
+ g.document = window.document;
9
+ g.window = window;
10
+ for (const [key, value] of Object.entries(linkedom)) {
11
+ if (key === "parseHTML" || key === "parseJSON" || key === "toJSON" || key === "Document") continue;
12
+ if (typeof value === "function" && /^[A-Z]/.test(key)) g[key] = value;
13
+ }
14
+ }
15
+ //#endregion
16
+ export { ensureWorkerDom };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxidejs",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Vite/Rsbuild plugin. One build → dist/server.js + optional client. Server actions via *.server.ts.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -45,6 +45,22 @@
45
45
  "./client": {
46
46
  "types": "./client.d.ts"
47
47
  },
48
+ "./rpc": {
49
+ "types": "./dist/rpc.d.mts",
50
+ "import": "./dist/rpc.mjs"
51
+ },
52
+ "./rpc/client": {
53
+ "types": "./dist/rpc/client.d.mts",
54
+ "import": "./dist/rpc/client.mjs"
55
+ },
56
+ "./worker-dom": {
57
+ "types": "./dist/worker-dom.d.mts",
58
+ "import": "./dist/worker-dom.mjs"
59
+ },
60
+ "./worker-dom/install": {
61
+ "types": "./dist/worker-dom/install.d.mts",
62
+ "import": "./dist/worker-dom/install.mjs"
63
+ },
48
64
  "./tsconfig": "./tsconfig.app.json"
49
65
  },
50
66
  "scripts": {
@@ -56,15 +72,13 @@
56
72
  "prepublishOnly": "bun run build"
57
73
  },
58
74
  "dependencies": {
75
+ "effect": "~4.0.0-rc.112",
76
+ "linkedom": "^0.18.13",
59
77
  "unplugin": "^3.3.0"
60
78
  },
61
- "devDependencies": {
62
- "tacho": "^0.6.1"
63
- },
64
79
  "peerDependencies": {
65
80
  "@rsbuild/core": "*",
66
81
  "crossws": "*",
67
- "tacho": "*",
68
82
  "vite": "*"
69
83
  },
70
84
  "peerDependenciesMeta": {
@@ -76,9 +90,6 @@
76
90
  },
77
91
  "vite": {
78
92
  "optional": true
79
- },
80
- "tacho": {
81
- "optional": true
82
93
  }
83
94
  },
84
95
  "engines": {
package/virtual.d.ts CHANGED
@@ -1,8 +1,11 @@
1
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 };
2
+ import type { Rpc, RpcGroup } from "effect/unstable/rpc";
3
+ import type { Layer } from "effect";
4
+
5
+ const actionsGroup: RpcGroup.RpcGroup<Rpc.Any>;
6
+ export const actionsHandlers: Layer.Layer<unknown, unknown, unknown>;
7
+ export default actionsGroup;
8
+ export { actionsGroup as actions };
6
9
  }
7
10
 
8
11
  declare module "virtual:oxide/client" {