weifuwu 0.16.4 → 0.16.5

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
@@ -451,7 +451,7 @@ The hydration bundle also injects `self.process = { env: {} }` as a safety net s
451
451
  #### useWebsocket — auto-reconnecting WebSocket
452
452
 
453
453
  ```tsx
454
- import { useWebsocket } from 'weifuwu'
454
+ import { useWebsocket } from 'weifuwu/react'
455
455
 
456
456
  function Chat() {
457
457
  const { send, lastMessage, readyState, close, reconnect } = useWebsocket('/ws/chat', {
@@ -472,7 +472,7 @@ function Chat() {
472
472
  #### useAction — async form submission
473
473
 
474
474
  ```tsx
475
- import { useAction } from 'weifuwu'
475
+ import { useAction } from 'weifuwu/react'
476
476
 
477
477
  function FeedbackForm() {
478
478
  const { submit, data, error, pending, reset } = useAction('/api/feedback', { method: 'POST' })
@@ -490,7 +490,7 @@ Auto-serializes JSON, auto-reads `_csrf` cookie and sends as `X-CSRF-Token`. Ret
490
490
  ### Client-side navigation
491
491
 
492
492
  ```tsx
493
- import { Link, useNavigate } from 'weifuwu'
493
+ import { Link, useNavigate } from 'weifuwu/react'
494
494
 
495
495
  function Nav() {
496
496
  const navigate = useNavigate()
package/cli.ts CHANGED
@@ -123,7 +123,7 @@ async function cmdInit(name: string) {
123
123
 
124
124
  await writeFile(join(targetDir, 'ui', 'pages', 'page.tsx'), [
125
125
  "import { useState } from 'react'",
126
- "import { useWebsocket } from 'weifuwu'",
126
+ "import { useWebsocket } from 'weifuwu/react'",
127
127
  '',
128
128
  'export default function Home() {',
129
129
  ' const [input, setInput] = useState("")',
package/dist/cli.js CHANGED
@@ -110,7 +110,7 @@ async function cmdInit(name) {
110
110
  ].join("\n"));
111
111
  await writeFile(join(targetDir, "ui", "pages", "page.tsx"), [
112
112
  "import { useState } from 'react'",
113
- "import { useWebsocket } from 'weifuwu'",
113
+ "import { useWebsocket } from 'weifuwu/react'",
114
114
  "",
115
115
  "export default function Home() {",
116
116
  ' const [input, setInput] = useState("")',
package/dist/index.d.ts CHANGED
@@ -60,11 +60,6 @@ export { seo, seoMiddleware, seoTags } from './seo.ts';
60
60
  export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig, } from './seo.ts';
61
61
  export { mailer } from './mailer.ts';
62
62
  export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
63
- export { useWebsocket } from './use-websocket.ts';
64
- export type { UseWebsocketOptions, UseWebsocketReturn } from './use-websocket.ts';
65
- export { useAction } from './use-action.ts';
66
- export type { UseActionOptions, UseActionReturn } from './use-action.ts';
67
- export { Link, useNavigate, navigate } from './client-router.ts';
68
63
  export { csrf } from './csrf.ts';
69
64
  export type { CsrfOptions } from './csrf.ts';
70
65
  export { logdb } from './logdb/index.ts';
package/dist/index.js CHANGED
@@ -995,6 +995,7 @@ ${src}`;
995
995
  alias: resolveAliases(),
996
996
  banner: { js: "self.process={env:{}};" },
997
997
  define: Object.keys(publicEnv).length > 0 ? publicEnv : void 0,
998
+ loader: { ".node": "empty" },
998
999
  write: false,
999
1000
  minify: true
1000
1001
  });
@@ -1773,7 +1774,7 @@ function upload(options) {
1773
1774
  // rate-limit.ts
1774
1775
  function rateLimit(options) {
1775
1776
  const max = options?.max ?? 100;
1776
- const window2 = options?.window ?? 6e4;
1777
+ const window = options?.window ?? 6e4;
1777
1778
  const getKey = options?.key ?? ((req) => {
1778
1779
  const forwarded = req.headers.get("x-forwarded-for");
1779
1780
  if (forwarded) return forwarded.split(",")[0].trim();
@@ -1790,19 +1791,19 @@ function rateLimit(options) {
1790
1791
  for (const [key, entry] of hits) {
1791
1792
  if (entry.reset < now) hits.delete(key);
1792
1793
  }
1793
- }, window2);
1794
+ }, window);
1794
1795
  if (interval.unref) interval.unref();
1795
1796
  const mw = async (req, ctx, next) => {
1796
1797
  const key = getKey(req);
1797
1798
  const now = Date.now();
1798
1799
  const entry = hits.get(key);
1799
1800
  if (!entry || entry.reset < now) {
1800
- hits.set(key, { count: 1, reset: now + window2 });
1801
+ hits.set(key, { count: 1, reset: now + window });
1801
1802
  const res2 = await next(req, ctx);
1802
1803
  const headers2 = new Headers(res2.headers);
1803
1804
  headers2.set("X-RateLimit-Limit", String(max));
1804
1805
  headers2.set("X-RateLimit-Remaining", String(max - 1));
1805
- headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window2) / 1e3)));
1806
+ headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window) / 1e3)));
1806
1807
  return new Response(res2.body, { status: res2.status, statusText: res2.statusText, headers: headers2 });
1807
1808
  }
1808
1809
  entry.count++;
@@ -6380,7 +6381,7 @@ async function buildRouter4(deps) {
6380
6381
  skills: allSkills,
6381
6382
  systemPrompt: session.system_prompt || systemPrompt
6382
6383
  });
6383
- const history2 = await getHistory(sql2, sessionId);
6384
+ const history = await getHistory(sql2, sessionId);
6384
6385
  await addTextMessage(sql2, sessionId, "user", content);
6385
6386
  const stream = executeGenerator({
6386
6387
  sessionId,
@@ -6388,7 +6389,7 @@ async function buildRouter4(deps) {
6388
6389
  model,
6389
6390
  tools,
6390
6391
  systemPrompt: sysPrompt,
6391
- messages: history2,
6392
+ messages: history,
6392
6393
  sql: sql2
6393
6394
  });
6394
6395
  return createSSEStream(stream);
@@ -6468,7 +6469,7 @@ function createWSHandler2(deps) {
6468
6469
  skills: allSkills,
6469
6470
  systemPrompt: session.system_prompt || systemPrompt
6470
6471
  });
6471
- const history2 = await getHistory(sql2, session_id);
6472
+ const history = await getHistory(sql2, session_id);
6472
6473
  await addTextMessage(sql2, session_id, "user", content);
6473
6474
  const stream = executeGenerator({
6474
6475
  sessionId: session_id,
@@ -6476,7 +6477,7 @@ function createWSHandler2(deps) {
6476
6477
  model,
6477
6478
  tools,
6478
6479
  systemPrompt: sysPrompt,
6479
- messages: history2,
6480
+ messages: history,
6480
6481
  sql: sql2,
6481
6482
  abortSignal: controller.signal
6482
6483
  });
@@ -6913,207 +6914,6 @@ function mailer(options) {
6913
6914
  return { send, close };
6914
6915
  }
6915
6916
 
6916
- // use-websocket.ts
6917
- import { useEffect, useRef, useCallback, useState } from "react";
6918
- var RECONNECT_DELAY = 3e3;
6919
- var MAX_RETRIES = 10;
6920
- function resolveUrl(url) {
6921
- return typeof url === "function" ? url() : url;
6922
- }
6923
- function useWebsocket(url, options) {
6924
- const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
6925
- const [lastMessage, setLastMessage] = useState(null);
6926
- const [readyState, setReadyState] = useState(WebSocket.CLOSED);
6927
- const wsRef = useRef(null);
6928
- const retryRef = useRef(0);
6929
- const timerRef = useRef(void 0);
6930
- const mountedRef = useRef(true);
6931
- const shouldReconnectRef = useRef(true);
6932
- const urlRef = useRef(url);
6933
- const optsRef = useRef({ onMessage, reconnectOpt, protocols });
6934
- urlRef.current = url;
6935
- optsRef.current = { onMessage, reconnectOpt, protocols };
6936
- const cleanup = useCallback(() => {
6937
- clearTimeout(timerRef.current);
6938
- wsRef.current?.close();
6939
- wsRef.current = null;
6940
- }, []);
6941
- const connect = useCallback(() => {
6942
- if (!mountedRef.current || !enabled) return;
6943
- const resolved = resolveUrl(urlRef.current);
6944
- if (!resolved) return;
6945
- wsRef.current?.close();
6946
- const ws = new WebSocket(resolved, optsRef.current.protocols);
6947
- wsRef.current = ws;
6948
- setReadyState(WebSocket.CONNECTING);
6949
- ws.addEventListener("open", () => {
6950
- if (!mountedRef.current) return;
6951
- retryRef.current = 0;
6952
- setReadyState(WebSocket.OPEN);
6953
- });
6954
- ws.addEventListener("message", (e) => {
6955
- if (!mountedRef.current) return;
6956
- const data = typeof e.data === "string" ? e.data : String(e.data);
6957
- setLastMessage(data);
6958
- optsRef.current.onMessage?.(data);
6959
- });
6960
- ws.addEventListener("close", () => {
6961
- if (!mountedRef.current) return;
6962
- setReadyState(WebSocket.CLOSED);
6963
- const ro = optsRef.current.reconnectOpt;
6964
- if (ro && shouldReconnectRef.current && mountedRef.current) {
6965
- const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
6966
- const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
6967
- if (retryRef.current < maxRetries) {
6968
- retryRef.current++;
6969
- timerRef.current = setTimeout(() => connect(), delay);
6970
- }
6971
- }
6972
- });
6973
- }, [enabled]);
6974
- useEffect(() => {
6975
- mountedRef.current = true;
6976
- shouldReconnectRef.current = true;
6977
- if (enabled) connect();
6978
- return () => {
6979
- mountedRef.current = false;
6980
- cleanup();
6981
- };
6982
- }, [enabled, connect, cleanup]);
6983
- const send = useCallback((data) => {
6984
- wsRef.current?.send(data);
6985
- }, []);
6986
- const close = useCallback(() => {
6987
- shouldReconnectRef.current = false;
6988
- cleanup();
6989
- setReadyState(WebSocket.CLOSED);
6990
- }, [cleanup]);
6991
- const reconnectFn = useCallback(() => {
6992
- retryRef.current = 0;
6993
- shouldReconnectRef.current = true;
6994
- cleanup();
6995
- connect();
6996
- }, [cleanup, connect]);
6997
- return { send, close, readyState, lastMessage, reconnect: reconnectFn };
6998
- }
6999
-
7000
- // use-action.ts
7001
- import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
7002
- function getCsrfToken() {
7003
- if (typeof document === "undefined") return void 0;
7004
- const match = document.cookie.match(/(?:^|;\s*)_csrf=([^;]+)/);
7005
- return match ? decodeURIComponent(match[1]) : void 0;
7006
- }
7007
- function useAction(url, options) {
7008
- const { method = "POST", headers, onSuccess, onError } = options ?? {};
7009
- const [data, setData] = useState2(null);
7010
- const [error, setError] = useState2(null);
7011
- const [pending, setPending] = useState2(false);
7012
- const mountedRef = useRef2(true);
7013
- const submit = useCallback2(async (body) => {
7014
- setPending(true);
7015
- setError(null);
7016
- try {
7017
- const csrfToken = getCsrfToken();
7018
- const hdrs = { ...headers };
7019
- if (csrfToken) hdrs["x-csrf-token"] = csrfToken;
7020
- if (body && typeof body === "object" && !(body instanceof FormData)) {
7021
- hdrs["content-type"] = "application/json";
7022
- }
7023
- const res = await fetch(url, {
7024
- method,
7025
- headers: hdrs,
7026
- body: body instanceof FormData ? body : body !== void 0 ? JSON.stringify(body) : void 0
7027
- });
7028
- if (!res.ok) {
7029
- const text2 = await res.text();
7030
- throw new Error(text2 || `HTTP ${res.status}`);
7031
- }
7032
- const result = res.status === 204 ? void 0 : await res.json();
7033
- if (mountedRef.current) {
7034
- setData(result);
7035
- onSuccess?.(result);
7036
- }
7037
- return result;
7038
- } catch (err) {
7039
- const e = err instanceof Error ? err : new Error(String(err));
7040
- if (mountedRef.current) {
7041
- setError(e);
7042
- onError?.(e);
7043
- }
7044
- return void 0;
7045
- } finally {
7046
- if (mountedRef.current) setPending(false);
7047
- }
7048
- }, [url, method, headers, onSuccess, onError]);
7049
- const reset = useCallback2(() => {
7050
- setData(null);
7051
- setError(null);
7052
- }, []);
7053
- return { submit, data, error, pending, reset };
7054
- }
7055
-
7056
- // client-router.ts
7057
- import { createElement as createElement2, useCallback as useCallback3 } from "react";
7058
- async function navigate(href) {
7059
- if (typeof document === "undefined") return;
7060
- const url = new URL(href, location.origin);
7061
- if (url.origin !== location.origin) {
7062
- location.href = href;
7063
- return;
7064
- }
7065
- const html = await fetch(url.pathname + url.search, {
7066
- headers: { accept: "text/html" }
7067
- }).then((r) => r.text());
7068
- const doc = new DOMParser().parseFromString(html, "text/html");
7069
- const rootEl = doc.getElementById("__weifuwu_root");
7070
- if (!rootEl) {
7071
- location.href = href;
7072
- return;
7073
- }
7074
- const newHtml = rootEl.innerHTML;
7075
- const propsMatch = html.match(/window\.__WEIFUWU_PROPS=(.+?)<\/script>/);
7076
- if (!propsMatch) {
7077
- location.href = href;
7078
- return;
7079
- }
7080
- const bundleMatch = html.match(/src="(\/__wfw\/client\/[^"]+\.js)"/);
7081
- const bundleUrl = bundleMatch ? bundleMatch[1] : null;
7082
- const currentRoot = document.getElementById("__weifuwu_root");
7083
- if (!currentRoot) {
7084
- location.href = href;
7085
- return;
7086
- }
7087
- ;
7088
- window.__WEIFUWU_ROOT?.unmount();
7089
- currentRoot.innerHTML = newHtml;
7090
- window.__WEIFUWU_PROPS = JSON.parse(propsMatch[1]);
7091
- history.pushState(null, "", url.pathname + url.search);
7092
- if (bundleUrl) {
7093
- const cacheBust = bundleUrl.includes("?") ? "&_t=" : "?_t=";
7094
- try {
7095
- await import(
7096
- /* @vite-ignore */
7097
- `${bundleUrl}${cacheBust}${Date.now()}`
7098
- );
7099
- } catch (e) {
7100
- console.error("[weifuwu/router] hydration failed:", e);
7101
- }
7102
- }
7103
- }
7104
- function useNavigate() {
7105
- return useCallback3((href) => navigate(href), []);
7106
- }
7107
- function Link({ href, children, onClick, ...props }) {
7108
- const handleClick = useCallback3((e) => {
7109
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
7110
- e.preventDefault();
7111
- navigate(href);
7112
- onClick?.(e);
7113
- }, [href, onClick]);
7114
- return createElement2("a", { href, onClick: handleClick, ...props }, children);
7115
- }
7116
-
7117
6917
  // csrf.ts
7118
6918
  function csrf(options) {
7119
6919
  const cookieName = options?.cookie ?? "_csrf";
@@ -8305,7 +8105,6 @@ function registerWorker(url) {
8305
8105
  };
8306
8106
  }
8307
8107
  export {
8308
- Link,
8309
8108
  Router,
8310
8109
  TsxContext,
8311
8110
  agent,
@@ -8339,7 +8138,6 @@ export {
8339
8138
  logger,
8340
8139
  mailer,
8341
8140
  messager,
8342
- navigate,
8343
8141
  openai,
8344
8142
  opencode,
8345
8143
  postgres,
@@ -8362,10 +8160,7 @@ export {
8362
8160
  tool2 as tool,
8363
8161
  tsx,
8364
8162
  upload,
8365
- useAction,
8366
- useNavigate,
8367
8163
  useTsx,
8368
- useWebsocket,
8369
8164
  user,
8370
8165
  validate
8371
8166
  };
@@ -0,0 +1,6 @@
1
+ export { useWebsocket } from './use-websocket.ts';
2
+ export type { UseWebsocketOptions, UseWebsocketReturn } from './use-websocket.ts';
3
+ export { useAction } from './use-action.ts';
4
+ export type { UseActionOptions, UseActionReturn } from './use-action.ts';
5
+ export { Link, useNavigate, navigate } from './client-router.ts';
6
+ export { TsxContext, useTsx } from './tsx-context.ts';
package/dist/react.js ADDED
@@ -0,0 +1,216 @@
1
+ // use-websocket.ts
2
+ import { useEffect, useRef, useCallback, useState } from "react";
3
+ var RECONNECT_DELAY = 3e3;
4
+ var MAX_RETRIES = 10;
5
+ function resolveUrl(url) {
6
+ return typeof url === "function" ? url() : url;
7
+ }
8
+ function useWebsocket(url, options) {
9
+ const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
10
+ const [lastMessage, setLastMessage] = useState(null);
11
+ const [readyState, setReadyState] = useState(WebSocket.CLOSED);
12
+ const wsRef = useRef(null);
13
+ const retryRef = useRef(0);
14
+ const timerRef = useRef(void 0);
15
+ const mountedRef = useRef(true);
16
+ const shouldReconnectRef = useRef(true);
17
+ const urlRef = useRef(url);
18
+ const optsRef = useRef({ onMessage, reconnectOpt, protocols });
19
+ urlRef.current = url;
20
+ optsRef.current = { onMessage, reconnectOpt, protocols };
21
+ const cleanup = useCallback(() => {
22
+ clearTimeout(timerRef.current);
23
+ wsRef.current?.close();
24
+ wsRef.current = null;
25
+ }, []);
26
+ const connect = useCallback(() => {
27
+ if (!mountedRef.current || !enabled) return;
28
+ const resolved = resolveUrl(urlRef.current);
29
+ if (!resolved) return;
30
+ wsRef.current?.close();
31
+ const ws = new WebSocket(resolved, optsRef.current.protocols);
32
+ wsRef.current = ws;
33
+ setReadyState(WebSocket.CONNECTING);
34
+ ws.addEventListener("open", () => {
35
+ if (!mountedRef.current) return;
36
+ retryRef.current = 0;
37
+ setReadyState(WebSocket.OPEN);
38
+ });
39
+ ws.addEventListener("message", (e) => {
40
+ if (!mountedRef.current) return;
41
+ const data = typeof e.data === "string" ? e.data : String(e.data);
42
+ setLastMessage(data);
43
+ optsRef.current.onMessage?.(data);
44
+ });
45
+ ws.addEventListener("close", () => {
46
+ if (!mountedRef.current) return;
47
+ setReadyState(WebSocket.CLOSED);
48
+ const ro = optsRef.current.reconnectOpt;
49
+ if (ro && shouldReconnectRef.current && mountedRef.current) {
50
+ const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
51
+ const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
52
+ if (retryRef.current < maxRetries) {
53
+ retryRef.current++;
54
+ timerRef.current = setTimeout(() => connect(), delay);
55
+ }
56
+ }
57
+ });
58
+ }, [enabled]);
59
+ useEffect(() => {
60
+ mountedRef.current = true;
61
+ shouldReconnectRef.current = true;
62
+ if (enabled) connect();
63
+ return () => {
64
+ mountedRef.current = false;
65
+ cleanup();
66
+ };
67
+ }, [enabled, connect, cleanup]);
68
+ const send = useCallback((data) => {
69
+ wsRef.current?.send(data);
70
+ }, []);
71
+ const close = useCallback(() => {
72
+ shouldReconnectRef.current = false;
73
+ cleanup();
74
+ setReadyState(WebSocket.CLOSED);
75
+ }, [cleanup]);
76
+ const reconnectFn = useCallback(() => {
77
+ retryRef.current = 0;
78
+ shouldReconnectRef.current = true;
79
+ cleanup();
80
+ connect();
81
+ }, [cleanup, connect]);
82
+ return { send, close, readyState, lastMessage, reconnect: reconnectFn };
83
+ }
84
+
85
+ // use-action.ts
86
+ import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
87
+ function getCsrfToken() {
88
+ if (typeof document === "undefined") return void 0;
89
+ const match = document.cookie.match(/(?:^|;\s*)_csrf=([^;]+)/);
90
+ return match ? decodeURIComponent(match[1]) : void 0;
91
+ }
92
+ function useAction(url, options) {
93
+ const { method = "POST", headers, onSuccess, onError } = options ?? {};
94
+ const [data, setData] = useState2(null);
95
+ const [error, setError] = useState2(null);
96
+ const [pending, setPending] = useState2(false);
97
+ const mountedRef = useRef2(true);
98
+ const submit = useCallback2(async (body) => {
99
+ setPending(true);
100
+ setError(null);
101
+ try {
102
+ const csrfToken = getCsrfToken();
103
+ const hdrs = { ...headers };
104
+ if (csrfToken) hdrs["x-csrf-token"] = csrfToken;
105
+ if (body && typeof body === "object" && !(body instanceof FormData)) {
106
+ hdrs["content-type"] = "application/json";
107
+ }
108
+ const res = await fetch(url, {
109
+ method,
110
+ headers: hdrs,
111
+ body: body instanceof FormData ? body : body !== void 0 ? JSON.stringify(body) : void 0
112
+ });
113
+ if (!res.ok) {
114
+ const text = await res.text();
115
+ throw new Error(text || `HTTP ${res.status}`);
116
+ }
117
+ const result = res.status === 204 ? void 0 : await res.json();
118
+ if (mountedRef.current) {
119
+ setData(result);
120
+ onSuccess?.(result);
121
+ }
122
+ return result;
123
+ } catch (err) {
124
+ const e = err instanceof Error ? err : new Error(String(err));
125
+ if (mountedRef.current) {
126
+ setError(e);
127
+ onError?.(e);
128
+ }
129
+ return void 0;
130
+ } finally {
131
+ if (mountedRef.current) setPending(false);
132
+ }
133
+ }, [url, method, headers, onSuccess, onError]);
134
+ const reset = useCallback2(() => {
135
+ setData(null);
136
+ setError(null);
137
+ }, []);
138
+ return { submit, data, error, pending, reset };
139
+ }
140
+
141
+ // client-router.ts
142
+ import { createElement, useCallback as useCallback3 } from "react";
143
+ async function navigate(href) {
144
+ if (typeof document === "undefined") return;
145
+ const url = new URL(href, location.origin);
146
+ if (url.origin !== location.origin) {
147
+ location.href = href;
148
+ return;
149
+ }
150
+ const html = await fetch(url.pathname + url.search, {
151
+ headers: { accept: "text/html" }
152
+ }).then((r) => r.text());
153
+ const doc = new DOMParser().parseFromString(html, "text/html");
154
+ const rootEl = doc.getElementById("__weifuwu_root");
155
+ if (!rootEl) {
156
+ location.href = href;
157
+ return;
158
+ }
159
+ const newHtml = rootEl.innerHTML;
160
+ const propsMatch = html.match(/window\.__WEIFUWU_PROPS=(.+?)<\/script>/);
161
+ if (!propsMatch) {
162
+ location.href = href;
163
+ return;
164
+ }
165
+ const bundleMatch = html.match(/src="(\/__wfw\/client\/[^"]+\.js)"/);
166
+ const bundleUrl = bundleMatch ? bundleMatch[1] : null;
167
+ const currentRoot = document.getElementById("__weifuwu_root");
168
+ if (!currentRoot) {
169
+ location.href = href;
170
+ return;
171
+ }
172
+ ;
173
+ window.__WEIFUWU_ROOT?.unmount();
174
+ currentRoot.innerHTML = newHtml;
175
+ window.__WEIFUWU_PROPS = JSON.parse(propsMatch[1]);
176
+ history.pushState(null, "", url.pathname + url.search);
177
+ if (bundleUrl) {
178
+ const cacheBust = bundleUrl.includes("?") ? "&_t=" : "?_t=";
179
+ try {
180
+ await import(
181
+ /* @vite-ignore */
182
+ `${bundleUrl}${cacheBust}${Date.now()}`
183
+ );
184
+ } catch (e) {
185
+ console.error("[weifuwu/router] hydration failed:", e);
186
+ }
187
+ }
188
+ }
189
+ function useNavigate() {
190
+ return useCallback3((href) => navigate(href), []);
191
+ }
192
+ function Link({ href, children, onClick, ...props }) {
193
+ const handleClick = useCallback3((e) => {
194
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
195
+ e.preventDefault();
196
+ navigate(href);
197
+ onClick?.(e);
198
+ }, [href, onClick]);
199
+ return createElement("a", { href, onClick: handleClick, ...props }, children);
200
+ }
201
+
202
+ // tsx-context.ts
203
+ import { createContext, useContext } from "react";
204
+ var TsxContext = createContext({ params: {}, query: {} });
205
+ function useTsx() {
206
+ return useContext(TsxContext);
207
+ }
208
+ export {
209
+ Link,
210
+ TsxContext,
211
+ navigate,
212
+ useAction,
213
+ useNavigate,
214
+ useTsx,
215
+ useWebsocket
216
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.16.4",
3
+ "version": "0.16.5",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": "./dist/index.js",
9
- "./tsx": "./dist/index.js"
9
+ "./react": "./dist/react.js"
10
10
  },
11
11
  "bin": {
12
12
  "weifuwu": "dist/cli.js"
@@ -18,7 +18,7 @@
18
18
  "LICENSE"
19
19
  ],
20
20
  "scripts": {
21
- "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external",
21
+ "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external && esbuild react.ts --bundle --format=esm --outfile=dist/react.js --external:react --external:react-dom",
22
22
  "prepublishOnly": "npm run build && tsc --emitDeclarationOnly --outdir dist",
23
23
  "test": "node --test 'test/**/*.test.ts'"
24
24
  },