weifuwu 0.11.0 → 0.12.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/README.md CHANGED
@@ -17,7 +17,7 @@ Everything follows the same `(req, ctx) => Response` contract. The Router handle
17
17
  - **React SSR + Hydration** — `tsx({ dir })` — page.tsx / load.ts / layout.tsx / route.ts / not-found.tsx
18
18
  - **WebSocket** — `router.ws()` with upgrade middleware (auth before connect)
19
19
  - **GraphQL** — `graphql(handler)` sub-Router with GraphiQL IDE
20
- - **AI streaming** — `ai(handler)` sub-Router via Vercel AI SDK
20
+ - **AI streaming** — `aiStream(handler)` sub-Router via Vercel AI SDK
21
21
  - **DAG workflow tool** — `runWorkflow()` — multi-step execution engine as a single AI SDK `Tool`
22
22
  - **AI Agent** — `agent()` — server-side AI agents with chat/tool-use/knowledge types, OpenAI-compatible, Ollama-ready
23
23
  - **Messaging** — `messager()` — real-time chat with channels, WebSocket, agent routing, webhook support
@@ -851,7 +851,7 @@ GraphQL endpoint with GraphiQL IDE. Mount as a sub-Router:
851
851
  import { serve, Router, graphql } from 'weifuwu'
852
852
 
853
853
  const app = new Router()
854
- app.use('/graphql', graphql(() => ({
854
+ const gql = graphql(() => ({
855
855
  schema: `
856
856
  type Query { hello: String }
857
857
  type Mutation { setMessage(msg: String!): String }
@@ -861,7 +861,8 @@ app.use('/graphql', graphql(() => ({
861
861
  Mutation: { setMessage: (_, { msg }) => msg },
862
862
  },
863
863
  graphiql: true,
864
- })))
864
+ }))
865
+ app.use('/graphql', gql.router())
865
866
 
866
867
  serve(app.handler(), { port: 3000 })
867
868
  ```
@@ -873,14 +874,15 @@ The handler receives `(req, ctx)` so you can customize the schema based on the r
873
874
  Server-sent event streaming via the Vercel AI SDK:
874
875
 
875
876
  ```ts
876
- import { serve, Router, ai } from 'weifuwu'
877
+ import { serve, Router, aiStream } from 'weifuwu'
877
878
  import { openai } from '@ai-sdk/openai'
878
879
 
879
880
  const app = new Router()
880
- app.use('/chat', ai(async (req, ctx) => {
881
+ const chat = await aiStream(async (req, ctx) => {
881
882
  const { messages } = await req.json()
882
883
  return { model: openai('gpt-4o'), messages }
883
- }))
884
+ })
885
+ app.use('/chat', chat.router())
884
886
 
885
887
  serve(app.handler(), { port: 3000 })
886
888
  ```
@@ -1153,15 +1155,81 @@ export default function NotFound() {
1153
1155
  }
1154
1156
  ```
1155
1157
 
1158
+ ## Health check
1159
+
1160
+ ```ts
1161
+ import { serve, Router, health } from 'weifuwu'
1162
+
1163
+ const app = new Router()
1164
+ app.use(health()) // GET /health → 200
1165
+ app.use(health({ path: '/healthz' })) // custom path
1166
+ app.use(health({
1167
+ check: async () => { await db.sql`SELECT 1` }, // fail → 503
1168
+ }))
1169
+ serve(app.handler(), { port: 3000 })
1170
+ ```
1171
+
1172
+ Returns a `Router` — mount with `app.use()`.
1173
+
1174
+ ## Internationalization
1175
+
1176
+ ```ts
1177
+ import { i18n } from 'weifuwu'
1178
+
1179
+ app.use(i18n({ dir: './locales', defaultLocale: 'en' }))
1180
+
1181
+ // In any handler after i18n middleware:
1182
+ app.get('/hello', (req, ctx) => {
1183
+ const msg = ctx.t('greeting', { name: 'World' })
1184
+ return Response.json({ message: msg, locale: ctx.locale })
1185
+ })
1186
+ ```
1187
+
1188
+ Locale detection: `Cookie: locale=zh` → `Accept-Language: zh-CN` → `defaultLocale`.
1189
+
1190
+ ## Email
1191
+
1192
+ ```ts
1193
+ import { mailer } from 'weifuwu'
1194
+
1195
+ // SMTP transport
1196
+ const mail = mailer({
1197
+ transport: 'smtp://user:pass@smtp.example.com',
1198
+ from: 'noreply@example.com',
1199
+ })
1200
+ await mail.send({ to: 'user@example.com', subject: 'Welcome', html: '<h1>Hi!</h1>' })
1201
+ await mail.close()
1202
+
1203
+ // Custom transport (Resend, SES, SendGrid, etc.)
1204
+ const mail2 = mailer({
1205
+ send: async (msg) => { await resend.emails.send(msg) },
1206
+ })
1207
+ await mail2.send({ to: 'user@example.com', subject: 'Hi', text: 'Hello' })
1208
+ await mail2.close()
1209
+ ```
1210
+
1211
+ ## Test utilities
1212
+
1213
+ ```ts
1214
+ import { createTestServer } from 'weifuwu'
1215
+
1216
+ const { server, url } = await createTestServer(app.handler())
1217
+ const res = await fetch(`${url}/api/users`)
1218
+ assert.equal(res.status, 200)
1219
+ server.stop()
1220
+ ```
1221
+
1156
1222
  ## Usage within a full app
1157
1223
 
1158
1224
  ```ts
1159
- import { serve, Router, ai, graphql } from 'weifuwu'
1225
+ import { serve, Router, aiStream, graphql } from 'weifuwu'
1160
1226
 
1161
1227
  const app = new Router()
1162
1228
  app.use('/', await tsx({ dir: './pages/' }))
1163
- app.use('/chat', ai(async (req) => ({ model: openai('gpt-4o'), messages: (await req.json()).messages })))
1164
- app.use('/graphql', graphql(() => ({ schema: `type Query { hello: String }`, resolvers: { Query: { hello: () => 'world' } } })))
1229
+ const chat = await aiStream(async (req) => ({ model: openai('gpt-4o'), messages: (await req.json()).messages }))
1230
+ app.use('/chat', chat.router())
1231
+ const gql = graphql(() => ({ schema: `type Query { hello: String }`, resolvers: { Query: { hello: () => 'world' } } }))
1232
+ app.use('/graphql', gql.router())
1165
1233
  app.ws('/chat', { message(ws, _, data) { ws.send(data) } })
1166
1234
 
1167
1235
  serve(app.handler(), { websocket: app.websocketHandler() })
@@ -1382,7 +1450,7 @@ serve(app.handler(), { websocket: app.websocketHandler() })
1382
1450
  | `messager(options)` | Real-time messaging — channels, WebSocket, agent routing, webhooks |
1383
1451
  | `opencode(options)` | AI programming assistant — chat agents with tools, skills, permissions, isolated workspaces |
1384
1452
  | `graphql(handler)` | GraphQL endpoint (GET/POST + GraphiQL) |
1385
- | `ai(handler)` | AI streaming endpoint (POST) |
1453
+ | `aiStream(handler)` | AI streaming endpoint (POST) |
1386
1454
  | `runWorkflow(options)` | DAG execution engine as an AI SDK `Tool` — use with `streamText()` |
1387
1455
 
1388
1456
  ### Deploy
package/dist/ai.d.ts CHANGED
@@ -1,19 +1,7 @@
1
1
  import type { Context } from './types.ts';
2
2
  import { Router } from './router.ts';
3
- type StreamTextParams = {
4
- model: unknown;
5
- prompt?: string;
6
- system?: string;
7
- messages?: unknown[];
8
- maxTokens?: number;
9
- temperature?: number;
10
- [key: string]: unknown;
11
- };
12
- export type AIHandler = (req: Request, ctx: Context) => StreamTextParams | Promise<StreamTextParams>;
13
- export declare const _ai: {
14
- streamText: (params: StreamTextParams) => {
15
- toTextStreamResponse: () => Response;
16
- };
17
- };
18
- export declare function ai(handler: AIHandler): Promise<Router>;
19
- export {};
3
+ export type AIHandler = (req: Request, ctx: Context) => Record<string, unknown> | Promise<Record<string, unknown>>;
4
+ export declare const _ai: Record<string, any>;
5
+ export declare function aiStream(handler: AIHandler): Promise<{
6
+ router(): Router;
7
+ }>;
@@ -37,7 +37,7 @@ export interface AppStatus {
37
37
  error?: string;
38
38
  }
39
39
  export interface DeployServer {
40
- stop(): Promise<void>;
40
+ close(): Promise<void>;
41
41
  ready: Promise<void>;
42
42
  url: string;
43
43
  apps: {
package/dist/graphql.d.ts CHANGED
@@ -9,4 +9,6 @@ export interface GraphQLOptions {
9
9
  graphiql?: boolean;
10
10
  }
11
11
  export type GraphQLHandler = (req: Request, ctx: Context) => GraphQLOptions | Promise<GraphQLOptions>;
12
- export declare function graphql(handler: GraphQLHandler): Router;
12
+ export declare function graphql(handler: GraphQLHandler): {
13
+ router(): Router;
14
+ };
@@ -0,0 +1,6 @@
1
+ import { Router } from './router.ts';
2
+ export interface HealthOptions {
3
+ path?: string;
4
+ check?: () => Promise<void>;
5
+ }
6
+ export declare function health(options?: HealthOptions): Router;
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Middleware } from './types.ts';
2
+ export interface I18nOptions {
3
+ dir: string;
4
+ defaultLocale?: string;
5
+ }
6
+ export declare function i18n(options: I18nOptions): Middleware;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
2
- export { serve } from './serve.ts';
2
+ export { serve, createTestServer } from './serve.ts';
3
3
  export type { ServeOptions, Server } from './serve.ts';
4
4
  export { Router } from './router.ts';
5
5
  export type { WebSocketHandler } from './router.ts';
@@ -21,7 +21,7 @@ export { compress } from './compress.ts';
21
21
  export type { CompressOptions } from './compress.ts';
22
22
  export { graphql } from './graphql.ts';
23
23
  export type { GraphQLOptions, GraphQLHandler } from './graphql.ts';
24
- export { ai } from './ai.ts';
24
+ export { aiStream } from './ai.ts';
25
25
  export type { AIHandler } from './ai.ts';
26
26
  export { runWorkflow } from './ai/workflow.ts';
27
27
  export { postgres } from './postgres/index.ts';
@@ -42,3 +42,9 @@ export { deploy, defineConfig } from './deploy/index.ts';
42
42
  export type { DeployConfig, AppConfig, DeployServer, AppStatus } from './deploy/types.ts';
43
43
  export { opencode } from './opencode/index.ts';
44
44
  export type { OpencodeOptions, OpencodeModule, SkillDef, OpencodePermissions, Session as OpencodeSession } from './opencode/types.ts';
45
+ export { health } from './health.ts';
46
+ export type { HealthOptions } from './health.ts';
47
+ export { i18n } from './i18n.ts';
48
+ export type { I18nOptions } from './i18n.ts';
49
+ export { mailer } from './mailer.ts';
50
+ export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
package/dist/index.js CHANGED
@@ -63,6 +63,11 @@ async function sendResponse(res, response) {
63
63
  }
64
64
  res.end();
65
65
  }
66
+ async function createTestServer(handler) {
67
+ const server = serve(handler, { port: 0 });
68
+ await server.ready;
69
+ return { server, url: `http://localhost:${server.port}` };
70
+ }
66
71
  function serve(handler, options) {
67
72
  const port = options?.port ?? 0;
68
73
  const hostname = options?.hostname ?? "0.0.0.0";
@@ -329,13 +334,13 @@ var Router = class _Router {
329
334
  const mw = match.middlewares[index++];
330
335
  return mw(innerReq, ctx2, dispatch);
331
336
  }
332
- return await new Promise((resolve9) => {
337
+ return await new Promise((resolve10) => {
333
338
  try {
334
339
  upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
335
- resolve9(new Response(null, { status: 101 }));
340
+ resolve10(new Response(null, { status: 101 }));
336
341
  } catch {
337
342
  socket.destroy();
338
- resolve9(new Response("WebSocket upgrade failed", { status: 500 }));
343
+ resolve10(new Response("WebSocket upgrade failed", { status: 500 }));
339
344
  }
340
345
  });
341
346
  };
@@ -1946,24 +1951,34 @@ function graphql(handler) {
1946
1951
  }
1947
1952
  return executeQuery(schema, params, options, req, ctx);
1948
1953
  });
1949
- return r;
1954
+ return { router: () => r };
1950
1955
  }
1951
1956
 
1952
1957
  // ai.ts
1953
- var _ai = {
1954
- streamText: null
1955
- };
1956
- async function ai(handler) {
1957
- if (!_ai.streamText) {
1958
- _ai.streamText = (await import("ai")).streamText;
1959
- }
1958
+ var _ai = {};
1959
+ async function getStreamText() {
1960
+ if (!_ai.streamText) _ai.streamText = (await import("ai")).streamText;
1961
+ return _ai.streamText;
1962
+ }
1963
+ async function getStreamObject() {
1964
+ if (!_ai.streamObject) _ai.streamObject = (await import("ai")).streamObject;
1965
+ return _ai.streamObject;
1966
+ }
1967
+ async function aiStream(handler) {
1960
1968
  const r = new Router();
1961
1969
  r.post("/", async (req, ctx) => {
1962
1970
  const options = await handler(req, ctx);
1963
- const result = _ai.streamText(options);
1971
+ if (options.schema) {
1972
+ const streamObject = await getStreamObject();
1973
+ const { schema, ...params } = options;
1974
+ const result2 = streamObject({ ...params, schema, output: "object" });
1975
+ return result2.toTextStreamResponse();
1976
+ }
1977
+ const streamText3 = await getStreamText();
1978
+ const result = streamText3(options);
1964
1979
  return result.toTextStreamResponse();
1965
1980
  });
1966
- return r;
1981
+ return { router: () => r };
1967
1982
  }
1968
1983
 
1969
1984
  // ai/workflow.ts
@@ -2090,7 +2105,10 @@ async function executeNode(node, ctx) {
2090
2105
  const test = typeof c.test === "string" ? Boolean(resolveValue(c.test, ctx)) : c.test;
2091
2106
  if (test && c.body) {
2092
2107
  let last;
2093
- for (const n of c.body) last = await executeNode(n, ctx);
2108
+ for (const n of c.body) {
2109
+ last = await executeNode(n, ctx);
2110
+ ctx.nodeOutputs.set(n.id, last);
2111
+ }
2094
2112
  return last;
2095
2113
  }
2096
2114
  }
@@ -2106,7 +2124,10 @@ async function executeNode(node, ctx) {
2106
2124
  ctx.stepCount++;
2107
2125
  if (ctx.stepCount > ctx.maxSteps) throw new Error(`Step limit exceeded`);
2108
2126
  if (!Boolean(evaluateExpression(conditionExpr, ctx))) break;
2109
- for (const n of body ?? []) last = await executeNode(n, ctx);
2127
+ for (const n of body ?? []) {
2128
+ last = await executeNode(n, ctx);
2129
+ ctx.nodeOutputs.set(n.id, last);
2130
+ }
2110
2131
  }
2111
2132
  return last;
2112
2133
  }
@@ -5085,14 +5106,14 @@ function forkApp(opts) {
5085
5106
  return { child, port: opts.port };
5086
5107
  }
5087
5108
  function stopProcess(mp, timeout = 1e4) {
5088
- return new Promise((resolve9) => {
5109
+ return new Promise((resolve10) => {
5089
5110
  const timer = setTimeout(() => {
5090
5111
  mp.child.kill("SIGKILL");
5091
- resolve9();
5112
+ resolve10();
5092
5113
  }, timeout);
5093
5114
  mp.child.on("exit", () => {
5094
5115
  clearTimeout(timer);
5095
- resolve9();
5116
+ resolve10();
5096
5117
  });
5097
5118
  mp.child.kill("SIGTERM");
5098
5119
  });
@@ -5311,7 +5332,7 @@ async function deploy(config) {
5311
5332
  });
5312
5333
  const portSuffix = config.port !== 80 ? `:${config.port}` : "";
5313
5334
  return {
5314
- stop: async () => {
5335
+ close: async () => {
5315
5336
  for (const [, app] of apps) {
5316
5337
  if (app.restartTimer) clearTimeout(app.restartTimer);
5317
5338
  if (app.process) {
@@ -5651,10 +5672,10 @@ function createBashTool(ctx) {
5651
5672
  return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
5652
5673
  }
5653
5674
  const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
5654
- return new Promise((resolve9) => {
5675
+ return new Promise((resolve10) => {
5655
5676
  const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
5656
5677
  const truncated = stdout.length > 1e6 || stderr.length > 1e6;
5657
- resolve9({
5678
+ resolve10({
5658
5679
  stdout: stdout.slice(0, 1e6),
5659
5680
  stderr: stderr.slice(0, 1e6),
5660
5681
  exitCode: error?.code ?? 0,
@@ -5882,7 +5903,7 @@ function createQuestionTool(ctx) {
5882
5903
  options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
5883
5904
  }),
5884
5905
  execute: async ({ question, options }, { toolCallId }) => {
5885
- return new Promise((resolve9, reject) => {
5906
+ return new Promise((resolve10, reject) => {
5886
5907
  const timeout = setTimeout(() => {
5887
5908
  ctx.pendingQuestions.delete(toolCallId);
5888
5909
  reject(new Error("Question timed out"));
@@ -5890,7 +5911,7 @@ function createQuestionTool(ctx) {
5890
5911
  ctx.pendingQuestions.set(toolCallId, {
5891
5912
  resolve: (answer) => {
5892
5913
  clearTimeout(timeout);
5893
- resolve9(answer);
5914
+ resolve10(answer);
5894
5915
  },
5895
5916
  reject: (err) => {
5896
5917
  clearTimeout(timeout);
@@ -6275,20 +6296,124 @@ async function opencode(options) {
6275
6296
  close: () => base.close()
6276
6297
  };
6277
6298
  }
6299
+
6300
+ // health.ts
6301
+ function health(options) {
6302
+ const path2 = options?.path ?? "/health";
6303
+ const r = new Router();
6304
+ const handler = async () => {
6305
+ try {
6306
+ await options?.check?.();
6307
+ return new Response("OK", { status: 200 });
6308
+ } catch {
6309
+ return new Response("Service Unavailable", { status: 503 });
6310
+ }
6311
+ };
6312
+ r.get(path2, handler);
6313
+ r.head(path2, handler);
6314
+ return r;
6315
+ }
6316
+
6317
+ // i18n.ts
6318
+ import { readFile as readFile2 } from "node:fs/promises";
6319
+ import { existsSync as existsSync3 } from "node:fs";
6320
+ import { join as join4, resolve as resolve9 } from "node:path";
6321
+ function i18n(options) {
6322
+ const dir = resolve9(options.dir);
6323
+ const defaultLocale = options.defaultLocale ?? "en";
6324
+ const cache = /* @__PURE__ */ new Map();
6325
+ async function load(locale) {
6326
+ const cached = cache.get(locale);
6327
+ if (cached) return cached;
6328
+ const filePath = join4(dir, `${locale}.json`);
6329
+ if (!existsSync3(filePath)) return {};
6330
+ try {
6331
+ const content = await readFile2(filePath, "utf-8");
6332
+ const data = JSON.parse(content);
6333
+ cache.set(locale, data);
6334
+ return data;
6335
+ } catch {
6336
+ return {};
6337
+ }
6338
+ }
6339
+ function detect(req) {
6340
+ const url = new URL(req.url);
6341
+ const fromCookie = extractCookie(req, "locale");
6342
+ if (fromCookie) return fromCookie;
6343
+ const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.split("-")[0];
6344
+ if (fromHeader) return fromHeader;
6345
+ return defaultLocale;
6346
+ }
6347
+ function t(localeMsgs, key, params) {
6348
+ let msg = localeMsgs[key];
6349
+ if (msg === void 0) msg = key;
6350
+ if (params) {
6351
+ for (const [k, v] of Object.entries(params)) {
6352
+ msg = msg.replace(`{${k}}`, v);
6353
+ }
6354
+ }
6355
+ return msg;
6356
+ }
6357
+ return async (req, ctx, next) => {
6358
+ const locale = detect(req);
6359
+ const msgs = await load(locale);
6360
+ ctx.locale = locale;
6361
+ ctx.t = (key, params) => t(msgs, key, params);
6362
+ return next(req, ctx);
6363
+ };
6364
+ }
6365
+ function extractCookie(req, name) {
6366
+ const cookie = req.headers.get("cookie");
6367
+ if (!cookie) return null;
6368
+ for (const part of cookie.split(";")) {
6369
+ const [k, v] = part.trim().split("=");
6370
+ if (k === name && v) return decodeURIComponent(v);
6371
+ }
6372
+ return null;
6373
+ }
6374
+
6375
+ // mailer.ts
6376
+ import { createTransport } from "nodemailer";
6377
+ function mailer(options) {
6378
+ const sender = options.send;
6379
+ const from = options.from;
6380
+ let transporter = null;
6381
+ if (!sender && options.transport) {
6382
+ transporter = typeof options.transport === "string" ? createTransport(options.transport) : options.transport;
6383
+ }
6384
+ async function send(opts) {
6385
+ if (sender) {
6386
+ await sender(opts);
6387
+ return;
6388
+ }
6389
+ if (!transporter) {
6390
+ throw new Error("mailer: no transport configured \u2014 provide `transport` or `send` option");
6391
+ }
6392
+ await transporter.sendMail({ ...opts, from: opts.from ?? from });
6393
+ }
6394
+ async function close() {
6395
+ if (transporter) transporter.close();
6396
+ }
6397
+ return { send, close };
6398
+ }
6278
6399
  export {
6279
6400
  Router,
6280
6401
  TsxContext,
6281
6402
  agent,
6282
- ai,
6403
+ aiStream,
6283
6404
  auth,
6284
6405
  compress,
6285
6406
  cors,
6407
+ createTestServer,
6286
6408
  defineConfig,
6287
6409
  deleteCookie,
6288
6410
  deploy,
6289
6411
  getCookies,
6290
6412
  graphql,
6413
+ health,
6414
+ i18n,
6291
6415
  logger,
6416
+ mailer,
6292
6417
  messager,
6293
6418
  opencode,
6294
6419
  postgres,
@@ -0,0 +1,20 @@
1
+ import type { Transporter } from 'nodemailer';
2
+ export interface MailOptions {
3
+ to: string | string[];
4
+ subject: string;
5
+ text?: string;
6
+ html?: string;
7
+ from?: string;
8
+ cc?: string | string[];
9
+ bcc?: string | string[];
10
+ }
11
+ export interface MailerOptions {
12
+ transport?: string | Transporter;
13
+ from?: string;
14
+ send?: (opts: MailOptions) => Promise<void>;
15
+ }
16
+ export interface Mailer {
17
+ send: (opts: MailOptions) => Promise<void>;
18
+ close: () => Promise<void>;
19
+ }
20
+ export declare function mailer(options: MailerOptions): Mailer;
package/dist/serve.d.ts CHANGED
@@ -17,4 +17,8 @@ export interface Server {
17
17
  export declare function readBody(req: IncomingMessage, maxSize?: number): Promise<Buffer>;
18
18
  export declare function createRequest(req: IncomingMessage, body: Buffer): [Request, Record<string, string>];
19
19
  export declare function sendResponse(res: ServerResponse, response: Response): Promise<void>;
20
+ export declare function createTestServer(handler: Handler): Promise<{
21
+ server: Server;
22
+ url: string;
23
+ }>;
20
24
  export declare function serve(handler: Handler, options?: ServeOptions): Server;
package/dist/types.d.ts CHANGED
@@ -4,6 +4,8 @@ export interface Context {
4
4
  user?: unknown;
5
5
  parsed?: Record<string, unknown>;
6
6
  mountPath?: string;
7
+ locale?: string;
8
+ t?: (key: string, params?: Record<string, string>) => string;
7
9
  }
8
10
  export type Handler = (req: Request, ctx: Context) => Response | Promise<Response>;
9
11
  export type Middleware = (req: Request, ctx: Context, next: Handler) => Response | Promise<Response>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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",
@@ -28,6 +28,7 @@
28
28
  "graphql": "^16",
29
29
  "ioredis": "^5.11.0",
30
30
  "jsonwebtoken": "^9.0.3",
31
+ "nodemailer": "^8.0.10",
31
32
  "postgres": "^3.4.9",
32
33
  "react": "^19",
33
34
  "react-dom": "^19",
@@ -39,6 +40,7 @@
39
40
  "license": "MIT",
40
41
  "devDependencies": {
41
42
  "@types/jsonwebtoken": "^9.0.10",
43
+ "@types/nodemailer": "^8.0.0",
42
44
  "@types/react": "^19",
43
45
  "@types/react-dom": "^19",
44
46
  "@types/ws": "^8.18.1",