weifuwu 0.24.3 → 0.25.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.
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ export interface MCPClientOptions {
2
+ /** Command to spawn (e.g. 'npx', 'node', 'uvx'). */
3
+ command: string;
4
+ /** Arguments passed to the command. */
5
+ args?: string[];
6
+ /** Environment variables (merged with process.env). */
7
+ env?: Record<string, string>;
8
+ /** How long to wait for handshake/response (ms). Default: 15_000. */
9
+ timeout?: number;
10
+ /** Max tool response body size in bytes. Default: 10MB. */
11
+ maxResponseSize?: number;
12
+ }
13
+ /** A tool definition from an MCP server. */
14
+ export interface MCPToolDef {
15
+ name: string;
16
+ description?: string;
17
+ inputSchema?: {
18
+ type: 'object';
19
+ properties?: Record<string, unknown>;
20
+ required?: string[];
21
+ };
22
+ }
23
+ /** MCP client — spawns and manages a single MCP server subprocess. */
24
+ export interface MCPClient {
25
+ /** Fetch tool definitions from the server and return AI SDK Tool objects. */
26
+ getTools(): Promise<Record<string, unknown>>;
27
+ /** Refresh tool definitions (re-call tools/list). */
28
+ refresh(): Promise<void>;
29
+ /** Call a tool by name. Returns the raw result from the MCP server. */
30
+ callTool(name: string, args: unknown): Promise<unknown>;
31
+ /** Shutdown the MCP server and release resources. */
32
+ close(): Promise<void>;
33
+ }
34
+ export declare function mcpClient(options: MCPClientOptions): MCPClient;
@@ -0,0 +1,2 @@
1
+ import type { Notifier, NotifierOptions } from './types.ts';
2
+ export declare function notifier(opts: NotifierOptions): Notifier;
@@ -0,0 +1,2 @@
1
+ export { notifier } from './client.ts';
2
+ export type { NotifierOptions, Notifier, NotifierInjected, NotifyMessage, Notification, NotifyChannel, NotifyPreferences, } from './types.ts';
@@ -0,0 +1,105 @@
1
+ import type { Context, Middleware, Closeable } from '../types.ts';
2
+ import type { Mailer } from '../mailer.ts';
3
+ import type { Hub } from '../hub.ts';
4
+ import type { SqlClient } from '../vendor.ts';
5
+ declare module '../types.ts' {
6
+ interface Context {
7
+ notifier: Notifier;
8
+ }
9
+ }
10
+ /** Shape injected into ctx when notifier middleware is active. */
11
+ export interface NotifierInjected {
12
+ notifier: Notifier;
13
+ }
14
+ /** Configuration for the notifier module. */
15
+ export interface NotifierOptions {
16
+ /** SQL client (PostgreSQL) for persistent notifications. */
17
+ sql: SqlClient;
18
+ /** Optional mailer for email channel. */
19
+ mailer?: Mailer;
20
+ /** Optional hub for WebSocket push channel. */
21
+ hub?: Hub;
22
+ /** Default sender name for email (default: system name). */
23
+ fromName?: string;
24
+ /** Table name for notifications (default: '_notifications'). */
25
+ table?: string;
26
+ /** Max notification list page size (default: 50). */
27
+ pageSize?: number;
28
+ }
29
+ /** A notification message. */
30
+ export interface NotifyMessage {
31
+ /** Notification title. */
32
+ title: string;
33
+ /** Notification body text. */
34
+ body?: string;
35
+ /** Optional action URL. */
36
+ actionUrl?: string;
37
+ /** Optional action button text. */
38
+ actionText?: string;
39
+ /** Notification type for categorization (default: 'default'). */
40
+ type?: string;
41
+ /** Arbitrary metadata (JSON object). */
42
+ metadata?: Record<string, unknown>;
43
+ }
44
+ /** A notification record from the database. */
45
+ export interface Notification {
46
+ id: number;
47
+ user_id: number;
48
+ title: string;
49
+ body: string;
50
+ action_url: string | null;
51
+ action_text: string | null;
52
+ type: string;
53
+ metadata: Record<string, unknown>;
54
+ read_at: string | null;
55
+ created_at: string;
56
+ }
57
+ /** Supported notification channels. */
58
+ export type NotifyChannel = 'inbox' | 'email' | 'ws';
59
+ /** User's channel preferences for a notification type. */
60
+ export interface NotifyPreferences {
61
+ /** Which channels are enabled. Default: ['inbox']. */
62
+ channels: NotifyChannel[];
63
+ }
64
+ /** The notifier API injected into ctx. */
65
+ export interface Notifier extends Closeable {
66
+ /**
67
+ * Send a notification to a specific user.
68
+ * Routes through the user's channel preferences automatically.
69
+ */
70
+ send(to: {
71
+ userId: number;
72
+ email?: string;
73
+ }, message: NotifyMessage): Promise<void>;
74
+ /**
75
+ * Send a system-wide notification to all users.
76
+ * Useful for announcements.
77
+ */
78
+ broadcast(message: NotifyMessage): Promise<void>;
79
+ /** Get user's unread notification count. */
80
+ unreadCount(userId: number): Promise<number>;
81
+ /** Mark specific notifications as read. If no ids given, mark all as read. */
82
+ markRead(userId: number, notificationIds?: number[]): Promise<void>;
83
+ /** List notifications for a user, newest first. */
84
+ list(userId: number, opts?: {
85
+ limit?: number;
86
+ offset?: number;
87
+ unreadOnly?: boolean;
88
+ }): Promise<Notification[]>;
89
+ /** Get or set user's notification channel preferences. */
90
+ getPreferences(userId: number): Promise<NotifyPreferences>;
91
+ setPreferences(userId: number, prefs: NotifyPreferences): Promise<void>;
92
+ /** Count total notifications for a user. */
93
+ count(userId: number, unreadOnly?: boolean): Promise<number>;
94
+ /** Clean up old notifications (older than days). */
95
+ clean(days: number): Promise<number>;
96
+ /** Create the notifications and preferences tables. Safe to call multiple times. */
97
+ migrate(): Promise<void>;
98
+ /** Release resources. */
99
+ close(): Promise<void>;
100
+ }
101
+ /** Notifier middleware type. */
102
+ export interface NotifierMiddleware extends Middleware<Context, Context & NotifierInjected>, Notifier {
103
+ /** Alias for backward compatibility. */
104
+ middleware: () => Middleware<Context, Context & NotifierInjected>;
105
+ }
package/dist/router.d.ts CHANGED
@@ -10,6 +10,24 @@ export type WebSocketHandler = {
10
10
  error?: (ws: WebSocket, ctx: Context, error: Error) => void | Promise<void>;
11
11
  };
12
12
  type WsUpgradeHandler = (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
13
+ /**
14
+ * Middleware metadata for dependency checking.
15
+ * Middleware factories can attach this to their return value for runtime validation.
16
+ *
17
+ * ```ts
18
+ * function postgres(): PostgresClient {
19
+ * const mw = async (req, ctx, next) => { ... }
20
+ * mw.__meta = { injects: ['sql'], depends: [] }
21
+ * return Object.assign(mw, { sql, migrate, close })
22
+ * }
23
+ * ```
24
+ */
25
+ export interface MiddlewareMeta {
26
+ /** Fields this middleware injects into ctx. */
27
+ injects: string[];
28
+ /** Fields this middleware depends on (must be injected earlier). */
29
+ depends: string[];
30
+ }
13
31
  export declare class Router<T extends Context = Context> {
14
32
  private root;
15
33
  private wsRoot;
@@ -18,6 +36,8 @@ export declare class Router<T extends Context = Context> {
18
36
  private _hasWildcard;
19
37
  private _hub?;
20
38
  private _wss?;
39
+ /** Track which ctx fields have been injected so far (for dependency checking). */
40
+ private _ctxFields;
21
41
  private get wss();
22
42
  private get hub();
23
43
  /** Inject a custom hub (e.g. with Redis for cross-process broadcast). */
@@ -28,6 +48,16 @@ export declare class Router<T extends Context = Context> {
28
48
  use(mod: Router & {
29
49
  middleware: () => Middleware;
30
50
  }): Router<T>;
51
+ /**
52
+ * Check a middleware's dependency metadata and emit warnings if
53
+ * required fields haven't been injected yet.
54
+ * Attach __meta to a middleware function:
55
+ *
56
+ * ```ts
57
+ * mw.__meta = { injects: ['sql'], depends: ['session'] }
58
+ * ```
59
+ */
60
+ private _checkMiddlewareMeta;
31
61
  get(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<Context>]): Router<T>;
32
62
  post(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<Context>]): Router<T>;
33
63
  put(path: string, ...args: [...Middleware<T, T>[], Handler<T> | Router<Context>]): Router<T>;
@@ -1,5 +1,7 @@
1
1
  import type { Context, Handler } from './types.ts';
2
2
  import type { SqlClient } from './vendor.ts';
3
+ import { Router } from './router.ts';
4
+ import { WebSocket as WSWebSocket } from 'ws';
3
5
  export interface TestResponse {
4
6
  readonly status: number;
5
7
  readonly headers: Headers;
@@ -35,7 +37,15 @@ export declare class TestRequest {
35
37
  }
36
38
  export declare class TestApp {
37
39
  private router;
40
+ private wsServer;
41
+ private wsConnections;
38
42
  constructor();
43
+ /**
44
+ * Register a WebSocket handler.
45
+ */
46
+ ws(path: string, handler: import('./router.ts').WebSocketHandler): this;
47
+ /** Get the raw Router (for advanced use). */
48
+ get _router(): Router;
39
49
  /** Add global middleware */
40
50
  use(mw: any): this;
41
51
  /** Register a GET route — supports route-level middleware via spread args. */
@@ -60,6 +70,74 @@ export declare class TestApp {
60
70
  deleteReq(path: string): TestRequest;
61
71
  /** Get the underlying handler (for advanced usage) */
62
72
  handler(): Handler;
73
+ /** Start building a WebSocket connection to the given path. */
74
+ wsReq(path: string): TestWSRequest;
75
+ /**
76
+ * Internal: ensure HTTP server is running for WebSocket connections.
77
+ * Starts on a random port.
78
+ */
79
+ _ensureServer(): Promise<string>;
80
+ /**
81
+ * Internal: register a WS connection for cleanup.
82
+ */
83
+ _trackConnection(conn: TestWSConnection): void;
84
+ /**
85
+ * Cleanup all WebSocket connections and stop the server.
86
+ */
87
+ close(): Promise<void>;
88
+ }
89
+ /** Start building a WebSocket test connection. */
90
+ export declare class TestWSRequest {
91
+ private app;
92
+ private path;
93
+ private _timeout;
94
+ constructor(app: TestApp, path: string);
95
+ /** Set the timeout for operations (default: 5000ms). */
96
+ timeout(ms: number): this;
97
+ /**
98
+ * Connect to the WebSocket endpoint.
99
+ * Starts a real HTTP server (random port) if not already running.
100
+ */
101
+ connect(): Promise<TestWSConnection>;
102
+ }
103
+ /**
104
+ * A connected WebSocket for testing.
105
+ *
106
+ * ```ts
107
+ * const conn = await app.wsReq('/echo').connect()
108
+ * conn.send('hello')
109
+ * const msg = await conn.receive()
110
+ * assert.equal(msg, 'hello')
111
+ * conn.close()
112
+ * ```
113
+ */
114
+ export declare class TestWSConnection {
115
+ private ws;
116
+ private _timeout;
117
+ private messageQueue;
118
+ private resolveQueue;
119
+ private _closed;
120
+ constructor(ws: WSWebSocket, timeout?: number);
121
+ /** Send a text message. */
122
+ send(data: string): void;
123
+ /** Send a JSON message. */
124
+ json(data: unknown): void;
125
+ /**
126
+ * Wait for the next message. Returns the raw text.
127
+ * Throws on timeout or if the connection is closed.
128
+ */
129
+ receive(timeout?: number): Promise<string>;
130
+ /** Wait for the next message and parse as JSON. */
131
+ receiveJson<T = unknown>(): Promise<T>;
132
+ /**
133
+ * Assert that no message is received within the given silence period.
134
+ * Useful for verifying that something did NOT happen.
135
+ */
136
+ expectSilent(ms: number): Promise<void>;
137
+ /** Close the connection. */
138
+ close(): void;
139
+ /** Whether the connection is closed. */
140
+ get closed(): boolean;
63
141
  }
64
142
  /** Create a new test app */
65
143
  export declare function testApp(): TestApp;
@@ -84,7 +162,7 @@ export interface TestDb {
84
162
  *
85
163
  * ```ts
86
164
  * const db = await createTestDb()
87
- * await db.sql\`CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)\`
165
+ * await db.sql`CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)`
88
166
  * // ... run tests ...
89
167
  * await db.destroy() // drops the schema
90
168
  * ```
@@ -103,7 +181,7 @@ export declare function createTestDb(options?: {
103
181
  *
104
182
  * ```ts
105
183
  * await withTestDb(async (sql) => {
106
- * await sql\`INSERT INTO users ...\`
184
+ * await sql`INSERT INTO users ...`
107
185
  * // All changes are rolled back after this callback returns
108
186
  * })
109
187
  * ```
@@ -81,6 +81,8 @@ export interface UserOptions {
81
81
  * If not provided and no pg, `ctx.user` is set to `{ id: userId }`.
82
82
  */
83
83
  resolveUser?: (userId: unknown) => unknown | Promise<unknown>;
84
+ /** Enable API key management (per-user keys with scopes). */
85
+ apiKeys?: boolean;
84
86
  /** Enable OAuth2 server mode (authorization code flow). */
85
87
  oauth2?: OAuth2ServerOptions;
86
88
  /**
@@ -152,6 +154,25 @@ export interface UserModule extends Router, Closeable {
152
154
  getClient: (clientId: string) => Promise<OAuth2Client | null>;
153
155
  /** Revoke an OAuth2 client. */
154
156
  revokeClient: (clientId: string) => Promise<void>;
157
+ /** Create a new API key for a user. Returns the full key (only shown once). */
158
+ createApiKey: (userId: number, name: string, scopes?: string[]) => Promise<{
159
+ id: number;
160
+ key: string;
161
+ }>;
162
+ /** List user\'s API keys (masked — only prefix + last 4 chars visible). */
163
+ listApiKeys: (userId: number) => Promise<ApiKeyInfo[]>;
164
+ /** Revoke an API key by ID. */
165
+ revokeApiKey: (userId: number, keyId: number) => Promise<void>;
155
166
  /** Close the underlying DB connection. */
156
167
  close: () => Promise<void>;
157
168
  }
169
+ /** An API key record (as returned by listApiKeys — masked). */
170
+ export interface ApiKeyInfo {
171
+ id: number;
172
+ name: string;
173
+ prefix: string;
174
+ scopes: string[];
175
+ last_used_at: string | null;
176
+ created_at: string;
177
+ revoked: boolean;
178
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.24.3",
4
+ "version": "0.25.0",
5
5
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",