weifuwu 0.23.2 → 0.23.3

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.
@@ -1,8 +1,27 @@
1
1
  import type { Context, Middleware } from './types.ts';
2
+ /** Options for {@link requestId}. */
2
3
  export interface RequestIdOptions {
4
+ /** Header name for request ID (default: `'X-Request-ID'`). */
3
5
  header?: string;
6
+ /** Custom ID generator (default: `crypto.randomUUID`). */
4
7
  generator?: () => string;
5
8
  }
9
+ /**
10
+ * Request ID middleware.
11
+ *
12
+ * Reads an incoming `X-Request-ID` header (or custom header name) from the
13
+ * request. If absent, generates a new UUID. Sets the response header and
14
+ * injects `ctx.requestId`.
15
+ *
16
+ * ```ts
17
+ * import { requestId } from 'weifuwu'
18
+ * app.use(requestId())
19
+ *
20
+ * app.get('/', (req, ctx) => {
21
+ * console.log('request ID:', ctx.requestId)
22
+ * })
23
+ * ```
24
+ */
6
25
  export declare function requestId(options?: RequestIdOptions): Middleware<Context, Context & {
7
26
  requestId: string;
8
27
  }>;
package/dist/seo.d.ts CHANGED
@@ -1,39 +1,104 @@
1
1
  import type { Middleware } from './types.ts';
2
2
  import { Router } from './router.ts';
3
+ /** A rule in `robots.txt`. */
3
4
  export interface RobotsRule {
5
+ /** User-agent this rule applies to (default: `'*'`). */
4
6
  userAgent?: string;
7
+ /** Path(s) to allow. */
5
8
  allow?: string | string[];
9
+ /** Path(s) to disallow. */
6
10
  disallow?: string | string[];
7
11
  }
12
+ /** A URL entry in `sitemap.xml`. */
8
13
  export interface SitemapUrl {
14
+ /** Absolute URL of the page. */
9
15
  loc: string;
16
+ /** Last modification date (ISO 8601). */
10
17
  lastmod?: string;
18
+ /** Expected change frequency. */
11
19
  changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
20
+ /** Priority (0.0 to 1.0). */
12
21
  priority?: number;
13
22
  }
23
+ /** Configuration for sitemap generation. */
14
24
  export interface SitemapConfig {
25
+ /** Static list of URLs to include. */
15
26
  urls?: SitemapUrl[];
27
+ /** Dynamic URL resolver (called on each sitemap request, or cached per `cacheTTL`). */
16
28
  resolve?: () => SitemapUrl[] | Promise<SitemapUrl[]>;
29
+ /** Cache TTL in ms (default: 3600000 = 1 hour). Set 0 to disable cache. */
17
30
  cacheTTL?: number;
18
31
  }
32
+ /** Configuration for per-path response headers. */
19
33
  export interface SeoHeadersConfig {
34
+ /** `X-Robots-Tag` header value. Use a function for path-dependent values. */
20
35
  'X-Robots-Tag'?: string | ((path: string) => string | undefined);
21
36
  }
37
+ /** Options for {@link seo}. */
22
38
  export interface SeoOptions {
39
+ /** Rules for `robots.txt`. */
23
40
  robots?: RobotsRule[];
41
+ /** Configuration for `sitemap.xml`. */
24
42
  sitemap?: SitemapConfig;
43
+ /** Per-path response headers. */
25
44
  headers?: SeoHeadersConfig;
26
45
  baseUrl?: string;
27
46
  }
47
+ /**
48
+ * SEO middleware — sets `X-Robots-Tag` headers per path.
49
+ * Used standalone or included automatically by {@link seo}.
50
+ *
51
+ * ```ts
52
+ * app.use(seoMiddleware({
53
+ * headers: { 'X-Robots-Tag': (path) => path.startsWith('/admin') ? 'noindex' : undefined },
54
+ * }))
55
+ * ```
56
+ */
28
57
  export declare function seoMiddleware(options?: SeoOptions): Middleware;
58
+ /**
59
+ * SEO module — serves `robots.txt` and `sitemap.xml`.
60
+ *
61
+ * ```ts
62
+ * import { seo } from 'weifuwu'
63
+ *
64
+ * app.use(seo({
65
+ * robots: [{ userAgent: '*', allow: '/', disallow: '/admin' }],
66
+ * sitemap: {
67
+ * resolve: async () => [
68
+ * { loc: 'https://example.com/', changefreq: 'daily', priority: 1.0 },
69
+ * ],
70
+ * },
71
+ * }))
72
+ * ```
73
+ */
29
74
  export declare function seo(options?: SeoOptions): Router;
75
+ /** Options for {@link seoTags}. */
30
76
  export interface SeoTagsConfig {
77
+ /** Page title (`<title>` + `og:title`). */
31
78
  title?: string;
79
+ /** Meta description. */
32
80
  description?: string;
81
+ /** Open Graph image URL. */
33
82
  ogImage?: string;
83
+ /** Override `og:title` (defaults to `title`). */
34
84
  ogTitle?: string;
85
+ /** Override `og:description` (defaults to `description`). */
35
86
  ogDescription?: string;
87
+ /** Twitter card type. */
36
88
  twitterCard?: 'summary' | 'summary_large_image';
89
+ /** Canonical URL (`<link rel="canonical">`). */
37
90
  canonical?: string;
38
91
  }
92
+ /**
93
+ * Generate `<meta>` and `<link>` tag HTML string for SEO.
94
+ *
95
+ * ```ts
96
+ * const tags = seoTags({
97
+ * title: 'My App',
98
+ * description: 'A description',
99
+ * ogImage: 'https://example.com/og.png',
100
+ * })
101
+ * // <meta name="description" content="A description" />...
102
+ * ```
103
+ */
39
104
  export declare function seoTags(config: SeoTagsConfig): string;
package/dist/sse.d.ts CHANGED
@@ -1,9 +1,46 @@
1
+ /**
2
+ * Format an SSE message string with a named event type.
3
+ *
4
+ * ```ts
5
+ * formatSSE('ping', { ts: Date.now() })
6
+ * // "event: ping\ndata: {"ts":...}\n\n"
7
+ * ```
8
+ */
1
9
  export declare function formatSSE(event: string, data: unknown): string;
10
+ /**
11
+ * Format an SSE message string with only a data line (no event type).
12
+ *
13
+ * ```ts
14
+ * formatSSEData({ message: 'hello' })
15
+ * // "data: {"message":"hello"}\n\n"
16
+ * ```
17
+ */
2
18
  export declare function formatSSEData(data: unknown): string;
19
+ /** An SSE event to be sent via {@link createSSEStream}. */
3
20
  export interface SSEEvent {
21
+ /** Event type (maps to `event:` field). */
4
22
  event: string;
23
+ /** Event payload (serialized as JSON in `data:` field). */
5
24
  data: unknown;
6
25
  }
26
+ /**
27
+ * Create a Server-Sent Events (SSE) `Response` from an async iterable.
28
+ *
29
+ * Each item in the iterable is serialized as an SSE message:
30
+ * - If the item has a `.type` property → `event: {type}` + `data: {item}`
31
+ * - Otherwise → `data: {item}`
32
+ *
33
+ * Errors are sent as `event: error` messages. `AbortError` is silently ignored.
34
+ *
35
+ * ```ts
36
+ * app.get('/events', () => {
37
+ * async function* generate() {
38
+ * yield { type: 'ping', data: { ts: Date.now() } }
39
+ * }
40
+ * return createSSEStream(generate())
41
+ * })
42
+ * ```
43
+ */
7
44
  export declare function createSSEStream(iterable: AsyncIterable<any>, opts?: {
8
45
  headers?: Record<string, string>;
9
46
  status?: number;
package/dist/static.d.ts CHANGED
@@ -1,7 +1,23 @@
1
1
  import type { Handler } from './types.ts';
2
+ /** Options for {@link serveStatic}. */
2
3
  export interface ServeStaticOptions {
4
+ /** Directory index filename (default: `'index.html'`). */
3
5
  index?: string;
6
+ /** `Cache-Control max-age` in seconds. */
4
7
  maxAge?: number;
8
+ /** Add `immutable` to `Cache-Control` (requires `maxAge`). */
5
9
  immutable?: boolean;
6
10
  }
11
+ /**
12
+ * Static file serving handler.
13
+ *
14
+ * Serves files from a root directory. Supports ETag/304, directory index,
15
+ * Content-Type detection by extension, and directory traversal protection.
16
+ *
17
+ * ```ts
18
+ * import { serveStatic, Router } from 'weifuwu'
19
+ * const app = new Router()
20
+ * app.get('/static/*', serveStatic('./public'))
21
+ * ```
22
+ */
7
23
  export declare function serveStatic(root: string, options?: ServeStaticOptions): Handler;
package/dist/stream.d.ts CHANGED
@@ -1,14 +1,29 @@
1
1
  import type { Context } from './types.ts';
2
+ /** Options for {@link streamResponse}. */
2
3
  export interface StreamOpts {
4
+ /** Request context (for injecting hydration data). */
3
5
  ctx: Context;
6
+ /** Base path for SSR asset URLs. */
4
7
  base: string;
8
+ /** Tailwind CSS context. */
5
9
  tailwind?: {
6
10
  css: string;
7
11
  url: string;
8
12
  };
13
+ /** Whether in development mode (injects livereload script). */
9
14
  isDev: boolean;
15
+ /** HTTP status code (default: 200). */
10
16
  status?: number;
17
+ /** Serialized loader data injected into the HTML shell. */
11
18
  loaderData?: Record<string, unknown>;
12
19
  }
13
20
  export declare function readStream(stream: ReadableStream): Promise<string>;
21
+ /**
22
+ * Create an HTML response from a React SSR stream.
23
+ *
24
+ * Injects the React stream into an HTML shell, adds hydration script,
25
+ * loader data script, tailwind CSS, and (in dev mode) livereload script.
26
+ *
27
+ * Used internally by {@link ssr}.
28
+ */
14
29
  export declare function streamResponse(reactStream: ReadableStream, opts: StreamOpts, hydrationScript?: string): Response;
package/dist/trace.d.ts CHANGED
@@ -1,15 +1,51 @@
1
1
  export interface TraceContext {
2
+ /** Unique identifier for the current request trace. */
2
3
  traceId: string;
4
+ /** Timestamp (ms since epoch) when the trace started. */
3
5
  startTime: number;
4
6
  }
5
- /** Returns the current request's trace ID, or undefined if outside a request. */
7
+ /**
8
+ * Get the current request's trace ID.
9
+ * Returns `undefined` when called outside a request context (e.g. at startup).
10
+ *
11
+ * ```ts
12
+ * const traceId = currentTraceId()
13
+ * log.info({ traceId }, 'request started')
14
+ * ```
15
+ */
6
16
  export declare function currentTraceId(): string | undefined;
7
- /** Returns the current full trace context, or undefined if outside a request. */
17
+ /**
18
+ * Get the full current trace context ({ traceId, startTime }).
19
+ * Returns `undefined` outside a request.
20
+ */
8
21
  export declare function currentTrace(): TraceContext | undefined;
9
22
  /**
10
- * Run a function inside a trace context. Used internally by serve().
11
- * If an incoming trace header is present, it's reused; otherwise a new one is generated.
23
+ * Run a function inside a trace context.
24
+ * Used internally by `serve()` for every incoming request.
25
+ * If `incomingTraceId` is provided (e.g. from an `X-Trace-Id` header) it is reused;
26
+ * otherwise a new UUID is generated.
27
+ *
28
+ * ```ts
29
+ * const result = runWithTrace(req.headers.get('x-trace-id'), () => {
30
+ * return handleRequest(req)
31
+ * })
32
+ * ```
33
+ *
34
+ * @param incomingTraceId - Optional trace ID from upstream. Pass `null` to auto-generate.
35
+ * @param fn - Function to execute within the trace scope.
36
+ * @returns The return value of `fn`.
12
37
  */
13
38
  export declare function runWithTrace<T>(incomingTraceId: string | null, fn: () => T): T;
14
- /** Elapsed time in ms since the trace started. */
39
+ /**
40
+ * Milliseconds elapsed since the current trace started.
41
+ * Returns `0` if called outside a request context.
42
+ *
43
+ * ```ts
44
+ * app.use(async (req, ctx, next) => {
45
+ * const res = await next(req, ctx)
46
+ * console.log('handled in', traceElapsed(), 'ms')
47
+ * return res
48
+ * })
49
+ * ```
50
+ */
15
51
  export declare function traceElapsed(): number;
package/dist/types.d.ts CHANGED
@@ -4,10 +4,19 @@ export interface Context {
4
4
  user?: unknown;
5
5
  parsed?: Record<string, unknown>;
6
6
  mountPath?: string;
7
- theme?: string;
7
+ theme?: {
8
+ value: string;
9
+ set?: (value: string, loc?: string) => Response;
10
+ };
11
+ flash?: {
12
+ value: unknown;
13
+ set: (data: unknown, location?: string) => Response;
14
+ };
8
15
  i18n?: {
9
16
  locale: string;
17
+ messages?: Record<string, unknown>;
10
18
  t: (key: string, params?: Record<string, string>, fallback?: string) => string;
19
+ set?: (value: string, loc?: string) => Response;
11
20
  };
12
21
  env?: Record<string, string>;
13
22
  layoutStack?: {
package/dist/upload.d.ts CHANGED
@@ -1,14 +1,44 @@
1
1
  import type { Middleware } from './types.ts';
2
+ /** A parsed file from a multipart upload. */
2
3
  export interface UploadedFile {
4
+ /** Original filename from the client. */
3
5
  name: string;
6
+ /** MIME type from the `Content-Type` part header. */
4
7
  type: string;
8
+ /** File size in bytes. */
5
9
  size: number;
10
+ /** Path where the file was saved (when `dir` option is set). */
6
11
  path?: string;
12
+ /** File content as Buffer (when `dir` option is not set). */
7
13
  buffer?: Buffer;
8
14
  }
15
+ /** Options for {@link upload}. */
9
16
  export interface UploadOptions {
17
+ /** Directory to save uploaded files. If not set, files stay in memory via `.buffer`. */
10
18
  dir?: string;
19
+ /** Maximum file size in bytes. Default: 10 MB. Set `0` to allow unlimited. */
11
20
  maxFileSize?: number;
21
+ /** Allowed MIME types (e.g. `['image/jpeg', 'image/png']`). Empty array allows all. */
12
22
  allowedTypes?: string[];
13
23
  }
24
+ /**
25
+ * Multipart file upload middleware.
26
+ *
27
+ * Parses `multipart/form-data` requests, extracting files and fields.
28
+ * Files can be saved to disk (`dir` option) or kept in memory as Buffers.
29
+ * Parsed fields are available in `ctx.parsed`.
30
+ *
31
+ * ```ts
32
+ * import { upload } from 'weifuwu'
33
+ *
34
+ * // Save to disk
35
+ * app.use(upload({ dir: './uploads', maxFileSize: 5_000_000 }))
36
+ *
37
+ * // In-memory
38
+ * app.post('/upload', async (req, ctx) => {
39
+ * const file = ctx.parsed?.file as UploadedFile
40
+ * console.log(file.name, file.type, file.buffer!.length)
41
+ * })
42
+ * ```
43
+ */
14
44
  export declare function upload(options?: UploadOptions): Middleware;
@@ -1,14 +1,42 @@
1
+ /** Options for {@link useAction}. */
1
2
  export interface UseActionOptions<T = any> {
3
+ /** HTTP method (default: `'POST'`). */
2
4
  method?: string;
5
+ /** Additional request headers. */
3
6
  headers?: Record<string, string>;
7
+ /** Called with the response data on success. */
4
8
  onSuccess?: (data: T) => void;
9
+ /** Called with the error on failure. */
5
10
  onError?: (err: Error) => void;
6
11
  }
12
+ /** Return value of {@link useAction}. */
7
13
  export interface UseActionReturn<T = any> {
14
+ /** Submit the action. Pass a body to send as JSON (or FormData). */
8
15
  submit: (body?: any) => Promise<T | undefined>;
16
+ /** Response data from the last successful submission. */
9
17
  data: T | null;
18
+ /** Error from the last failed submission. */
10
19
  error: Error | null;
20
+ /** Whether a submission is in progress. */
11
21
  pending: boolean;
22
+ /** Reset data and error to their initial states. */
12
23
  reset: () => void;
13
24
  }
25
+ /**
26
+ * Hook to submit form actions via `fetch`. Handles JSON serialization,
27
+ * CSRF token injection, and loading/error state.
28
+ *
29
+ * ```tsx
30
+ * import { useAction } from 'weifuwu/react'
31
+ *
32
+ * function SaveButton() {
33
+ * const { submit, pending, error } = useAction('/api/save')
34
+ * return (
35
+ * <button onClick={() => submit({ title: 'Hello' })} disabled={pending}>
36
+ * {pending ? 'Saving...' : 'Save'}
37
+ * </button>
38
+ * )
39
+ * }
40
+ * ```
41
+ */
14
42
  export declare function useAction<T = any>(url: string | URL, options?: UseActionOptions<T>): UseActionReturn<T>;
@@ -1,27 +1,49 @@
1
+ /** Streaming state for all agents in a channel. */
1
2
  export interface AgentStreamState {
2
- /** Accumulated streaming text per agent, keyed by agent_id */
3
+ /** Accumulated streaming text per agent, keyed by `agent_id`. */
3
4
  streams: Record<number, string>;
4
- /** Whether any agent is currently streaming (typing) */
5
+ /** Whether any agent is currently streaming (typing). */
5
6
  streaming: boolean;
6
- /** Set of agent IDs currently streaming */
7
+ /** Set of agent IDs currently streaming. */
7
8
  activeAgents: Set<number>;
8
9
  }
10
+ /** Options for {@link useAgentStream}. */
9
11
  export interface UseAgentStreamOptions {
10
- /** WebSocket path, e.g. '/ws' */
12
+ /** WebSocket path, e.g. `'/ws'`. */
11
13
  wsPath: string;
12
- /** Channel ID to listen for agent streams */
14
+ /** Channel ID to listen for agent streams. */
13
15
  channelId: number;
14
- /** Called when a stream finishes (agent done generating) */
16
+ /** Called when an agent finishes generating. */
15
17
  onStreamEnd?: (agentId: number, fullText: string) => void;
16
- /** Called on stream error */
18
+ /** Called on stream error. */
17
19
  onError?: (agentId: number, error: string) => void;
18
20
  }
21
+ /** Return value of {@link useAgentStream}. */
19
22
  export interface UseAgentStreamReturn {
20
- /** Accumulated streaming state */
23
+ /** Accumulated streaming state for all agents. */
21
24
  stream: AgentStreamState;
22
- /** Get accumulated text for a specific agent */
25
+ /** Get accumulated text for a specific agent by ID. */
23
26
  getAgentText: (agentId: number) => string;
24
- /** Whether a specific agent is currently streaming */
27
+ /** Whether a specific agent is currently streaming. */
25
28
  isAgentStreaming: (agentId: number) => boolean;
26
29
  }
30
+ /**
31
+ * React hook to consume agent AI streaming output via WebSocket.
32
+ *
33
+ * Connects to a WebSocket endpoint, listens for `agent_stream`,
34
+ * `agent_stream_end`, and `agent_error` messages, and accumulates
35
+ * the token stream per agent.
36
+ *
37
+ * ```tsx
38
+ * import { useAgentStream } from 'weifuwu/react'
39
+ *
40
+ * function Chat() {
41
+ * const { stream, getAgentText } = useAgentStream({
42
+ * wsPath: '/ws/chat',
43
+ * channelId: 1,
44
+ * })
45
+ * return <pre>{getAgentText(1)}</pre>
46
+ * }
47
+ * ```
48
+ */
27
49
  export declare function useAgentStream(opts: UseAgentStreamOptions): UseAgentStreamReturn;
@@ -1 +1,17 @@
1
+ /**
2
+ * React hook to read the server-flashed message on the client side.
3
+ *
4
+ * Reads `window.__WEIFUWU_CTX.flash.value` (set by the flash middleware
5
+ * during SSR). Returns `null` when no flash message is present.
6
+ *
7
+ * ```tsx
8
+ * import { useFlashMessage } from 'weifuwu/react'
9
+ *
10
+ * function FlashNotice() {
11
+ * const flash = useFlashMessage<{ type: string; text: string }>()
12
+ * if (!flash) return null
13
+ * return <div className={flash.type}>{flash.text}</div>
14
+ * }
15
+ * ```
16
+ */
1
17
  export declare function useFlashMessage<T = any>(): T | null;
@@ -1,17 +1,42 @@
1
+ /** Options for {@link useWebsocket}. */
1
2
  export type UseWebsocketOptions = {
3
+ /** Called when a message is received. */
2
4
  onMessage?: (data: string) => void;
5
+ /** Auto-reconnect config. Set to `false` to disable. Default: `{ maxRetries: 10, delay: 3000 }`. */
3
6
  reconnect?: boolean | {
4
7
  maxRetries?: number;
5
8
  delay?: number;
6
9
  };
10
+ /** WebSocket sub-protocols. */
7
11
  protocols?: string | string[];
12
+ /** Whether the WebSocket is enabled. Set to `false` to keep closed. Default: `true`. */
8
13
  enabled?: boolean;
9
14
  };
15
+ /** Return value of {@link useWebsocket}. */
10
16
  export type UseWebsocketReturn = {
17
+ /** Send data through the WebSocket. */
11
18
  send: (data: string | ArrayBuffer | Blob) => void;
19
+ /** Close the WebSocket manually. */
12
20
  close: () => void;
21
+ /** Current `WebSocket.readyState`. */
13
22
  readyState: number;
23
+ /** The last received message string. */
14
24
  lastMessage: string | null;
25
+ /** Manually trigger reconnection. */
15
26
  reconnect: () => void;
16
27
  };
28
+ /**
29
+ * React hook for WebSocket connections with auto-reconnect.
30
+ *
31
+ * ```tsx
32
+ * import { useWebsocket } from 'weifuwu/react'
33
+ *
34
+ * function Chat() {
35
+ * const { send, lastMessage, readyState } = useWebsocket('/ws/chat', {
36
+ * onMessage: (data) => console.log('received:', data),
37
+ * })
38
+ * return <button onClick={() => send('Hello')}>Send</button>
39
+ * }
40
+ * ```
41
+ */
17
42
  export declare function useWebsocket(url: string | URL | (() => string | URL | null), options?: UseWebsocketOptions): UseWebsocketReturn;
@@ -1,2 +1,17 @@
1
1
  import type { UserOptions, UserModule } from './types.ts';
2
+ /**
3
+ * User authentication module — local register/login, JWT verification, OAuth2 server, social login.
4
+ *
5
+ * ```ts
6
+ * import { user, postgres } from 'weifuwu'
7
+ *
8
+ * const pg = postgres({ connection: DATABASE_URL })
9
+ * const auth = user({ pg, jwtSecret: process.env.JWT_SECRET })
10
+ *
11
+ * await auth.migrate()
12
+ *
13
+ * app.use(auth.middleware()) // inject `ctx.user` on every request
14
+ * app.use('/', auth) // mount auth routes: /register, /login
15
+ * ```
16
+ */
2
17
  export declare function user(options: UserOptions): UserModule;
@@ -7,6 +7,16 @@ interface OAuth2Deps {
7
7
  jwtSecret: string;
8
8
  expiresIn: string | number;
9
9
  }
10
+ /**
11
+ * Create an OAuth2 authorization code server.
12
+ *
13
+ * Registers routes:
14
+ * - `GET /oauth/authorize` — authorization page (user consent)
15
+ * - `POST /oauth/consent` — user grants/denies access
16
+ * - `POST /oauth/token` — exchange code for access token
17
+ *
18
+ * Used internally by the user module when `oauth2: { server: true }` is set.
19
+ */
10
20
  export declare function createOAuth2Server(deps: OAuth2Deps): {
11
21
  authorizeHandler: (req: Request, _ctx: Context) => Promise<Response>;
12
22
  consentHandler: (req: Request) => Promise<Response>;