weifuwu 0.25.0 → 0.25.1

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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
2
+ export { HttpError } from './types.ts';
2
3
  export { currentTraceId, currentTrace, runWithTrace, traceElapsed, trace } from './trace.ts';
3
4
  export type { TraceContext, TraceInjected, TraceOptions } from './trace.ts';
4
5
  export { loadEnv, isDev, isProd, isBundled, getPublicEnv, env } from './env.ts';
package/dist/index.js CHANGED
@@ -4,6 +4,16 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // types.ts
8
+ var HttpError = class extends Error {
9
+ status;
10
+ constructor(message, status) {
11
+ super(message);
12
+ this.name = "HttpError";
13
+ this.status = status;
14
+ }
15
+ };
16
+
7
17
  // trace.ts
8
18
  import crypto2 from "node:crypto";
9
19
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -110,14 +120,6 @@ function env() {
110
120
 
111
121
  // serve.ts
112
122
  import http from "node:http";
113
- var HttpError = class extends Error {
114
- status;
115
- constructor(message, status) {
116
- super(message);
117
- this.status = status;
118
- this.name = "HttpError";
119
- }
120
- };
121
123
  var DEFAULT_MAX_BODY = 10 * 1024 * 1024;
122
124
  async function readBody(req, maxSize) {
123
125
  const limit = maxSize ?? DEFAULT_MAX_BODY;
@@ -1271,7 +1273,7 @@ function parseBody(text2, ct) {
1271
1273
  return text2;
1272
1274
  }
1273
1275
  function validate(schemas) {
1274
- return async (req, ctx, next) => {
1276
+ const mw = async (req, ctx, next) => {
1275
1277
  const parsed = {};
1276
1278
  const issues = [];
1277
1279
  if (schemas?.params) {
@@ -1358,6 +1360,8 @@ function validate(schemas) {
1358
1360
  ctx.parsed = { ...ctx.parsed, ...parsed };
1359
1361
  return next(req, ctx);
1360
1362
  };
1363
+ mw.__meta = { injects: ["parsed"], depends: [] };
1364
+ return mw;
1361
1365
  }
1362
1366
 
1363
1367
  // cookie.ts
@@ -1461,7 +1465,7 @@ function detectMimeFromExtension(filename) {
1461
1465
  }
1462
1466
  function upload(options) {
1463
1467
  const saveDir = options?.dir;
1464
- return async (req, ctx, next) => {
1468
+ const mw = async (req, ctx, next) => {
1465
1469
  const ct = req.headers.get("content-type") ?? "";
1466
1470
  if (!ct.includes("multipart/form-data")) return next(req, ctx);
1467
1471
  try {
@@ -1517,6 +1521,8 @@ function upload(options) {
1517
1521
  ctx.parsed = { ...ctx.parsed, files, fields };
1518
1522
  return next(req, ctx);
1519
1523
  };
1524
+ mw.__meta = { injects: ["parsed"], depends: [] };
1525
+ return mw;
1520
1526
  }
1521
1527
 
1522
1528
  // rate-limit.ts
@@ -1601,7 +1607,7 @@ function rateLimit(options) {
1601
1607
  return addRateLimitHeaders(res, max, remaining, reset);
1602
1608
  };
1603
1609
  mw.__meta = { injects: [], depends: [] };
1604
- mw.close = () => {
1610
+ mw.close = async () => {
1605
1611
  if (interval) clearInterval(interval);
1606
1612
  hits.clear();
1607
1613
  };
@@ -1728,7 +1734,7 @@ import crypto3 from "node:crypto";
1728
1734
  function requestId(options) {
1729
1735
  const header = options?.header ?? "X-Request-ID";
1730
1736
  const gen = options?.generator ?? (() => crypto3.randomUUID());
1731
- return async (req, ctx, next) => {
1737
+ const mw = async (req, ctx, next) => {
1732
1738
  const existing = req.headers.get(header);
1733
1739
  const id2 = existing ?? gen();
1734
1740
  ctx.requestId = id2;
@@ -1738,6 +1744,8 @@ function requestId(options) {
1738
1744
  h.set(header, id2);
1739
1745
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
1740
1746
  };
1747
+ mw.__meta = { injects: ["requestId"], depends: [] };
1748
+ return mw;
1741
1749
  }
1742
1750
 
1743
1751
  // sse.ts
@@ -1833,6 +1841,7 @@ var TestRequest = class {
1833
1841
  }
1834
1842
  /** Shortcut: set ctx.user */
1835
1843
  withUser(user2) {
1844
+ ;
1836
1845
  this.ctxMixin.user = user2;
1837
1846
  return this;
1838
1847
  }
@@ -3749,6 +3758,7 @@ var BUILTIN_PROVIDERS = {
3749
3758
  tokenUrl: "https://oauth2.googleapis.com/token",
3750
3759
  userUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
3751
3760
  scope: "openid email profile",
3761
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3752
3762
  parseUser: (data) => ({
3753
3763
  id: data.id,
3754
3764
  email: data.email,
@@ -3761,6 +3771,7 @@ var BUILTIN_PROVIDERS = {
3761
3771
  tokenUrl: "https://github.com/login/oauth/access_token",
3762
3772
  userUrl: "https://api.github.com/user",
3763
3773
  scope: "read:user user:email",
3774
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3764
3775
  parseUser: (data) => ({
3765
3776
  id: String(data.id),
3766
3777
  email: data.email ?? "",
@@ -3857,8 +3868,9 @@ function registerOAuthLoginRoutes(router, deps, providers) {
3857
3868
  const state = crypto5.randomUUID();
3858
3869
  const redirectUri = new URL(req.url);
3859
3870
  redirectUri.pathname = redirectUri.pathname.replace(/\/[^/]+$/, "/") + providerName + "/callback";
3860
- if (ctx.session) {
3861
- ctx.session.oauthState = { state, provider: providerName };
3871
+ const sess = ctx.session;
3872
+ if (sess) {
3873
+ sess.oauthState = { state, provider: providerName };
3862
3874
  }
3863
3875
  const scope = config.scope ?? meta.scope;
3864
3876
  const params = new URLSearchParams({
@@ -3885,11 +3897,12 @@ function registerOAuthLoginRoutes(router, deps, providers) {
3885
3897
  if (!code || !state) {
3886
3898
  return Response.json({ error: "Missing code or state parameter" }, { status: 400 });
3887
3899
  }
3888
- const savedState = ctx.session?.oauthState;
3900
+ const sess = ctx.session;
3901
+ const savedState = sess?.oauthState;
3889
3902
  if (!savedState || savedState.state !== state || savedState.provider !== providerName) {
3890
3903
  return Response.json({ error: "Invalid state \u2014 possible CSRF attack" }, { status: 403 });
3891
3904
  }
3892
- if (ctx.session) delete ctx.session.oauthState;
3905
+ if (sess) delete sess.oauthState;
3893
3906
  const redirectUri = url.origin + url.pathname.replace(/\/callback$/, "");
3894
3907
  let tokenRes;
3895
3908
  try {
@@ -3947,9 +3960,10 @@ function registerOAuthLoginRoutes(router, deps, providers) {
3947
3960
  return Response.json({ error: "Failed to create/link user" }, { status: 500 });
3948
3961
  }
3949
3962
  const token = signToken(user2);
3950
- if (ctx.session) {
3951
- ctx.session.userId = user2.id;
3952
- ctx.session.role = user2.role;
3963
+ const sess2 = ctx.session;
3964
+ if (sess2) {
3965
+ sess2.userId = user2.id;
3966
+ sess2.role = user2.role;
3953
3967
  }
3954
3968
  const accept = req.headers.get("accept") ?? "";
3955
3969
  if (accept.includes("application/json")) {
@@ -4147,9 +4161,7 @@ function user(options) {
4147
4161
  const { email, password, name } = RegisterSchema.parse(data);
4148
4162
  const existing = await findByEmail(email);
4149
4163
  if (existing) {
4150
- const err = new Error("Email already registered");
4151
- err.status = 409;
4152
- throw err;
4164
+ throw new HttpError("Email already registered", 409);
4153
4165
  }
4154
4166
  const hashed = hashPassword(password);
4155
4167
  const row = await _users.insert({ email, password: hashed, name });
@@ -4162,14 +4174,10 @@ function user(options) {
4162
4174
  const { data: rows } = await _users.readMany({ email });
4163
4175
  const row = rows[0];
4164
4176
  if (!row) {
4165
- const err = new Error("Invalid email or password");
4166
- err.status = 401;
4167
- throw err;
4177
+ throw new HttpError("Invalid email or password", 401);
4168
4178
  }
4169
4179
  if (!verifyPassword(password, row.password)) {
4170
- const err = new Error("Invalid email or password");
4171
- err.status = 401;
4172
- throw err;
4180
+ throw new HttpError("Invalid email or password", 401);
4173
4181
  }
4174
4182
  const userData = row;
4175
4183
  const token = signToken(userData);
@@ -4359,7 +4367,7 @@ function user(options) {
4359
4367
  return null;
4360
4368
  }
4361
4369
  function middleware() {
4362
- return async (req, ctx, next) => {
4370
+ const mw = async (req, ctx, next) => {
4363
4371
  const userData = await resolveUser(req, ctx);
4364
4372
  if (userData) {
4365
4373
  ctx.user = userData;
@@ -4370,9 +4378,11 @@ function user(options) {
4370
4378
  headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
4371
4379
  });
4372
4380
  };
4381
+ mw.__meta = { injects: ["user"], depends: [] };
4382
+ return mw;
4373
4383
  }
4374
4384
  function middlewareOptional(_opts) {
4375
- return async (req, ctx, next) => {
4385
+ const mw = async (req, ctx, next) => {
4376
4386
  const userData = await resolveUser(req, ctx);
4377
4387
  if (userData) {
4378
4388
  ;
@@ -4380,6 +4390,8 @@ function user(options) {
4380
4390
  }
4381
4391
  return next(req, ctx);
4382
4392
  };
4393
+ mw.__meta = { injects: ["user"], depends: [] };
4394
+ return mw;
4383
4395
  }
4384
4396
  async function parseBody2(req) {
4385
4397
  const ct = req.headers.get("content-type") || "";
@@ -4404,8 +4416,9 @@ function user(options) {
4404
4416
  if (err instanceof z2.ZodError) {
4405
4417
  return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
4406
4418
  }
4407
- const status = err.status ?? 500;
4408
- return Response.json({ error: err.message }, { status });
4419
+ const status = err instanceof HttpError ? err.status : 500;
4420
+ const message = err instanceof Error ? err.message : String(err);
4421
+ return Response.json({ error: message }, { status });
4409
4422
  }
4410
4423
  });
4411
4424
  r.post("/login", async (req, ctx) => {
@@ -4426,8 +4439,9 @@ function user(options) {
4426
4439
  if (err instanceof z2.ZodError) {
4427
4440
  return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
4428
4441
  }
4429
- const status = err.status ?? 500;
4430
- return Response.json({ error: err.message }, { status });
4442
+ const status = err instanceof HttpError ? err.status : 500;
4443
+ const message = err instanceof Error ? err.message : String(err);
4444
+ return Response.json({ error: message }, { status });
4431
4445
  }
4432
4446
  });
4433
4447
  }
@@ -4446,7 +4460,8 @@ function user(options) {
4446
4460
  if (err instanceof z2.ZodError) {
4447
4461
  return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
4448
4462
  }
4449
- return Response.json({ error: err.message }, { status: 500 });
4463
+ const message = err instanceof Error ? err.message : String(err);
4464
+ return Response.json({ error: message }, { status: 500 });
4450
4465
  }
4451
4466
  });
4452
4467
  r.delete("/api-keys/:id", middleware(), async (req, ctx) => {
@@ -4522,7 +4537,7 @@ function user(options) {
4522
4537
  return mod;
4523
4538
  }
4524
4539
 
4525
- // redis/index.ts
4540
+ // redis/client.ts
4526
4541
  import { Redis as IORedis } from "ioredis";
4527
4542
  function redis(opts) {
4528
4543
  const options = typeof opts === "string" ? { url: opts } : opts ?? {};
@@ -4718,15 +4733,12 @@ function createMemoryQueue(opts) {
4718
4733
  running = true;
4719
4734
  poll();
4720
4735
  };
4721
- mw.stop = function stop2() {
4736
+ mw.close = async function close() {
4722
4737
  running = false;
4723
4738
  if (pollTimer) {
4724
4739
  clearTimeout(pollTimer);
4725
4740
  pollTimer = null;
4726
4741
  }
4727
- };
4728
- mw.close = async function close() {
4729
- mw.stop();
4730
4742
  while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
4731
4743
  };
4732
4744
  mw.jobs = async function(limit) {
@@ -4891,15 +4903,12 @@ function createPgQueue(opts) {
4891
4903
  running = true;
4892
4904
  poll();
4893
4905
  };
4894
- mw.stop = function stop2() {
4906
+ mw.close = async function close() {
4895
4907
  running = false;
4896
4908
  if (pollTimer) {
4897
4909
  clearTimeout(pollTimer);
4898
4910
  pollTimer = null;
4899
4911
  }
4900
- };
4901
- mw.close = async function close() {
4902
- mw.stop();
4903
4912
  while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
4904
4913
  };
4905
4914
  mw.jobs = async function jobs(limit) {
@@ -5058,16 +5067,13 @@ function createRedisQueue(opts) {
5058
5067
  running = true;
5059
5068
  poll();
5060
5069
  };
5061
- mw.stop = function stop2() {
5070
+ mw.close = async function close() {
5062
5071
  running = false;
5063
5072
  epoch++;
5064
5073
  if (pollTimer) {
5065
5074
  clearTimeout(pollTimer);
5066
5075
  pollTimer = null;
5067
5076
  }
5068
- };
5069
- mw.close = async function close() {
5070
- mw.stop();
5071
5077
  while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
5072
5078
  redis2.disconnect();
5073
5079
  };
@@ -6065,7 +6071,7 @@ function tenant(options) {
6065
6071
  );
6066
6072
  }
6067
6073
  function middleware() {
6068
- return async (req, ctx, next) => {
6074
+ const mw = async (req, ctx, next) => {
6069
6075
  const user2 = ctx.user;
6070
6076
  if (!user2) {
6071
6077
  return new Response("Unauthorized", { status: 401 });
@@ -6101,6 +6107,8 @@ function tenant(options) {
6101
6107
  ctx.tenant = { id: member.id, name: member.name, role: member.role };
6102
6108
  return next(req, ctx);
6103
6109
  };
6110
+ mw.__meta = { injects: ["tenant"], depends: ["user"] };
6111
+ return mw;
6104
6112
  }
6105
6113
  const r = buildRouter(sql2, usersTable);
6106
6114
  const mod = r;
@@ -6179,17 +6187,13 @@ function buildRouter2(deps) {
6179
6187
  if (!body.input && !body.messages) {
6180
6188
  return Response.json({ error: "input or messages is required" }, { status: 400 });
6181
6189
  }
6182
- try {
6183
- const result = await runner.run(id2, body);
6184
- if ("stream" in result) {
6185
- return new Response(result.stream, {
6186
- headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" }
6187
- });
6188
- }
6189
- return Response.json(result);
6190
- } catch (err) {
6191
- return Response.json({ error: err.message }, { status: 500 });
6190
+ const result = await runner.run(id2, body);
6191
+ if ("stream" in result) {
6192
+ return new Response(result.stream, {
6193
+ headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" }
6194
+ });
6192
6195
  }
6196
+ return Response.json(result);
6193
6197
  });
6194
6198
  r.get("/agents/:id/runs", async (_req, ctx) => {
6195
6199
  const agentId = parseInt(ctx.params.id, 10);
@@ -6220,13 +6224,26 @@ function buildRouter2(deps) {
6220
6224
  { orderBy: { created_at: "desc" } }
6221
6225
  );
6222
6226
  const total = rows.length;
6223
- const success = rows.filter((r2) => r2.status === "success" || r2.status === "stream").length;
6227
+ const success = rows.filter(
6228
+ (r2) => r2.status === "success" || r2.status === "stream"
6229
+ ).length;
6224
6230
  const error = rows.filter((r2) => r2.status === "error").length;
6225
- const totalTokensIn = rows.reduce((sum, r2) => sum + (r2.tokens_in || 0), 0);
6226
- const totalTokensOut = rows.reduce((sum, r2) => sum + (r2.tokens_out || 0), 0);
6227
- const totalElapsed = rows.reduce((sum, r2) => sum + (r2.elapsed_ms || 0), 0);
6231
+ const totalTokensIn = rows.reduce(
6232
+ (sum, r2) => sum + (r2.tokens_in || 0),
6233
+ 0
6234
+ );
6235
+ const totalTokensOut = rows.reduce(
6236
+ (sum, r2) => sum + (r2.tokens_out || 0),
6237
+ 0
6238
+ );
6239
+ const totalElapsed = rows.reduce(
6240
+ (sum, r2) => sum + (r2.elapsed_ms || 0),
6241
+ 0
6242
+ );
6228
6243
  const avgElapsed = total > 0 ? Math.round(totalElapsed / total) : 0;
6229
- const sorted = [...rows].sort((a, b) => (a.elapsed_ms || 0) - (b.elapsed_ms || 0));
6244
+ const sorted = [...rows].sort(
6245
+ (a, b) => (a.elapsed_ms || 0) - (b.elapsed_ms || 0)
6246
+ );
6230
6247
  const p95Idx = Math.ceil(sorted.length * 0.95) - 1;
6231
6248
  const p95Elapsed = sorted.length > 0 ? sorted[p95Idx]?.elapsed_ms || 0 : 0;
6232
6249
  return Response.json({
@@ -6252,7 +6269,10 @@ function buildRouter2(deps) {
6252
6269
  const doc = await runner.addKnowledge(agentId, body.title || "", body.content);
6253
6270
  return Response.json(doc, { status: 201 });
6254
6271
  } catch (err) {
6255
- return Response.json({ error: err.message }, { status: 500 });
6272
+ return Response.json(
6273
+ { error: err instanceof Error ? err.message : String(err) },
6274
+ { status: 500 }
6275
+ );
6256
6276
  }
6257
6277
  });
6258
6278
  r.get("/agents/:id/knowledge", async (_req, ctx) => {
@@ -6294,6 +6314,7 @@ function chunkContent(content, chunkSize, overlap) {
6294
6314
  // agent/run.ts
6295
6315
  function hasKnowledgeDocs(sql2, agentId) {
6296
6316
  return sql2`SELECT 1 FROM "_knowledge_documents" WHERE agent_id = ${agentId} LIMIT 1`.then(
6317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6297
6318
  (r) => r.length > 0
6298
6319
  );
6299
6320
  }
@@ -6304,7 +6325,12 @@ async function searchKnowledge(sql2, provider, agentId, query, limit = 5) {
6304
6325
  `SELECT id, title, content, metadata, embedding <=> $1::vector AS _score FROM "_knowledge_documents" WHERE agent_id = $2 ORDER BY embedding <=> $1::vector LIMIT $3`,
6305
6326
  [vec, agentId, limit]
6306
6327
  );
6307
- return docs.map((d) => ({ id: d.id, title: d.title, content: d.content, score: d._score }));
6328
+ return docs.map((d) => ({
6329
+ id: d.id,
6330
+ title: d.title,
6331
+ content: d.content,
6332
+ score: d._score
6333
+ }));
6308
6334
  }
6309
6335
  async function loadAgent(agents, agentId) {
6310
6336
  const row = await agents.read(agentId);
@@ -7734,11 +7760,12 @@ function buildHeadPayload(opts) {
7734
7760
  flash: ctx.flash,
7735
7761
  loaderData
7736
7762
  };
7737
- if (ctx.user && typeof ctx.user === "object") {
7763
+ const rawUser = ctx.user;
7764
+ if (rawUser && typeof rawUser === "object") {
7738
7765
  const safeUser = {};
7739
7766
  for (const k of ["id", "name", "email", "role", "avatar"]) {
7740
- if (k in ctx.user) {
7741
- safeUser[k] = ctx.user[k];
7767
+ if (k in rawUser) {
7768
+ safeUser[k] = rawUser[k];
7742
7769
  }
7743
7770
  }
7744
7771
  ctxData.user = safeUser;
@@ -9250,17 +9277,17 @@ async function buildRouter4(deps) {
9250
9277
  FROM "_opencode_messages"
9251
9278
  WHERE session_id = ${sessionId}
9252
9279
  `;
9253
- const stats = rows[0] || {
9280
+ const raw = rows[0] || {
9254
9281
  message_count: 0,
9255
9282
  total_tokens_in: 0,
9256
9283
  total_tokens_out: 0
9257
9284
  };
9258
9285
  return Response.json({
9259
9286
  session_id: sessionId,
9260
- message_count: stats.message_count,
9261
- tokens_in: stats.total_tokens_in,
9262
- tokens_out: stats.total_tokens_out,
9263
- tokens_total: stats.total_tokens_in + stats.total_tokens_out
9287
+ message_count: raw.message_count,
9288
+ tokens_in: raw.total_tokens_in,
9289
+ tokens_out: raw.total_tokens_out,
9290
+ tokens_total: Number(raw.total_tokens_in) + Number(raw.total_tokens_out)
9264
9291
  });
9265
9292
  });
9266
9293
  try {
@@ -9316,8 +9343,13 @@ function createWSHandler2(deps) {
9316
9343
  client.mountPath
9317
9344
  );
9318
9345
  ws.send(JSON.stringify({ type: "session_created", session: session2 }));
9319
- } catch (e) {
9320
- ws.send(JSON.stringify({ type: "error", error: e.message }));
9346
+ } catch (err) {
9347
+ ws.send(
9348
+ JSON.stringify({
9349
+ type: "error",
9350
+ error: err instanceof Error ? err.message : String(err)
9351
+ })
9352
+ );
9321
9353
  }
9322
9354
  break;
9323
9355
  }
@@ -9371,9 +9403,9 @@ function createWSHandler2(deps) {
9371
9403
  break;
9372
9404
  }
9373
9405
  }
9374
- } catch (e) {
9375
- if (e.name !== "AbortError") {
9376
- ws.send(JSON.stringify({ type: "error", error: e.message }));
9406
+ } catch (err) {
9407
+ if (err instanceof Error && err.name !== "AbortError") {
9408
+ ws.send(JSON.stringify({ type: "error", error: err.message }));
9377
9409
  }
9378
9410
  }
9379
9411
  break;
@@ -9856,6 +9888,7 @@ function theme(options) {
9856
9888
  };
9857
9889
  return next(req, ctx);
9858
9890
  };
9891
+ mw.__meta = { injects: ["theme"], depends: [] };
9859
9892
  class ThemeRouter extends Router {
9860
9893
  middleware() {
9861
9894
  return mw;
@@ -9957,6 +9990,7 @@ function i18n(options) {
9957
9990
  };
9958
9991
  return next(req, ctx);
9959
9992
  };
9993
+ mw.__meta = { injects: ["i18n"], depends: [] };
9960
9994
  class I18nRouter extends Router {
9961
9995
  middleware() {
9962
9996
  return mw;
@@ -10001,7 +10035,7 @@ function makeSetFlash(name, location) {
10001
10035
  }
10002
10036
  function flash(options) {
10003
10037
  const name = options?.name ?? "flash";
10004
- return async (req, ctx, next) => {
10038
+ const mw = async (req, ctx, next) => {
10005
10039
  const raw = getCookies(req)[name] ?? null;
10006
10040
  const referer = req.headers.get("referer") || "/";
10007
10041
  let value = void 0;
@@ -10024,6 +10058,8 @@ function flash(options) {
10024
10058
  }
10025
10059
  return res;
10026
10060
  };
10061
+ mw.__meta = { injects: ["flash"], depends: [] };
10062
+ return mw;
10027
10063
  }
10028
10064
 
10029
10065
  // seo.ts
@@ -10206,7 +10242,7 @@ function csrf(options) {
10206
10242
  const headerName = options?.header ?? "x-csrf-token";
10207
10243
  const bodyKey = options?.key ?? "_csrf";
10208
10244
  const excluded = new Set(options?.excludeMethods ?? ["GET", "HEAD", "OPTIONS"]);
10209
- return async (req, ctx, next) => {
10245
+ const mw = async (req, ctx, next) => {
10210
10246
  const method = req.method.toUpperCase();
10211
10247
  if (excluded.has(method)) {
10212
10248
  let token = getCookies(req)[cookieName];
@@ -10243,6 +10279,8 @@ function csrf(options) {
10243
10279
  }
10244
10280
  return next(req, ctx);
10245
10281
  };
10282
+ mw.__meta = { injects: ["csrf"], depends: [] };
10283
+ return mw;
10246
10284
  }
10247
10285
 
10248
10286
  // logdb/rest.ts
@@ -11880,6 +11918,7 @@ function s3(options) {
11880
11918
  mw.url = url;
11881
11919
  mw.list = list;
11882
11920
  mw.client = client;
11921
+ mw.__meta = { injects: ["s3"], depends: [] };
11883
11922
  return mw;
11884
11923
  }
11885
11924
 
@@ -12142,6 +12181,7 @@ function permissions(options) {
12142
12181
  mw.requireRole = requireRole;
12143
12182
  mw.requirePermission = requirePermission;
12144
12183
  mw.migrate = migrate;
12184
+ mw.__meta = { injects: ["permissions"], depends: ["user"] };
12145
12185
  return mw;
12146
12186
  }
12147
12187
 
@@ -12615,6 +12655,7 @@ function notifier(opts) {
12615
12655
  }
12616
12656
  export {
12617
12657
  DEFAULT_MAX_BODY,
12658
+ HttpError,
12618
12659
  MIGRATIONS_TABLE,
12619
12660
  MemoryCache,
12620
12661
  MemoryStore,
@@ -1,8 +1,9 @@
1
1
  import { Router } from '../router.ts';
2
2
  import type { LanguageModel } from 'ai';
3
+ import type { SqlClient } from '../vendor.ts';
3
4
  import type { SkillDef, SkillRegistry, OpencodePermissions, PendingQuestion } from './types.ts';
4
5
  interface RestDeps {
5
- sql: any;
6
+ sql: SqlClient;
6
7
  model: LanguageModel;
7
8
  workspace: string;
8
9
  systemPrompt?: string;
@@ -1,9 +1,9 @@
1
- import type { WebSocket } from '../vendor.ts';
1
+ import type { WebSocket, SqlClient } from '../vendor.ts';
2
2
  import type { LanguageModel } from 'ai';
3
3
  import type { Context } from '../types.ts';
4
4
  import type { PendingQuestion, SkillDef, SkillRegistry, OpencodePermissions } from './types.ts';
5
5
  interface WsDeps {
6
- sql: any;
6
+ sql: SqlClient;
7
7
  model: LanguageModel;
8
8
  workspace: string;
9
9
  systemPrompt?: string;
@@ -43,7 +43,6 @@ export interface Queue extends Middleware<Context, Context & QueueInjected>, Clo
43
43
  }): Promise<string>;
44
44
  process<T>(type: string, handler: (job: QueueJob<T>) => Promise<void>): void;
45
45
  run(): Promise<void>;
46
- stop(): void;
47
46
  stats(): {
48
47
  running: boolean;
49
48
  inflight: number;
@@ -1,5 +1,5 @@
1
1
  import type { Redis } from './vendor.ts';
2
- import type { Context, Middleware } from './types.ts';
2
+ import type { Context, Middleware, Closeable } from './types.ts';
3
3
  /** Options for {@link rateLimit}. */
4
4
  export interface RateLimitOptions {
5
5
  /** Maximum requests within the window (default: 100). */
@@ -17,6 +17,14 @@ export interface RateLimitOptions {
17
17
  /** Redis key prefix (default: `'ratelimit:'`). */
18
18
  prefix?: string;
19
19
  }
20
+ /** Rate limit module — middleware + stats. */
21
+ export interface RateLimitModule extends Middleware<Context, Context>, Closeable {
22
+ stats(): {
23
+ store: string;
24
+ entries?: number;
25
+ maxEntries: number;
26
+ };
27
+ }
20
28
  /**
21
29
  * Rate limiting middleware (in-memory or Redis-backed).
22
30
  *
@@ -34,7 +42,4 @@ export interface RateLimitOptions {
34
42
  * app.use(rateLimit({ store: 'redis', redis: new Redis(), max: 100 }))
35
43
  * ```
36
44
  */
37
- export declare function rateLimit(options?: RateLimitOptions): Middleware<Context, Context> & {
38
- close: () => void;
39
- stop?: () => void;
40
- };
45
+ export declare function rateLimit(options?: RateLimitOptions): RateLimitModule;
@@ -0,0 +1,2 @@
1
+ import type { RedisOptions, RedisClient } from './types.ts';
2
+ export declare function redis(opts?: string | RedisOptions): RedisClient;
@@ -1,3 +1,2 @@
1
- import type { RedisOptions, RedisClient } from './types.ts';
2
- export { redis as default };
3
- export declare function redis(opts?: string | RedisOptions): RedisClient;
1
+ export { redis } from './client.ts';
2
+ export type { RedisOptions, RedisClient, RedisInjected } from './types.ts';
package/dist/serve.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type IncomingMessage, type ServerResponse } from 'node:http';
2
2
  import type { Duplex } from 'node:stream';
3
- import type { Handler } from './types.ts';
3
+ import { type Handler } from './types.ts';
4
4
  export interface ServeOptions {
5
5
  port?: number;
6
6
  hostname?: string;
package/dist/session.d.ts CHANGED
@@ -80,6 +80,10 @@ export declare class MemoryStore implements SessionStore {
80
80
  /** Testing only: return approximate count. */
81
81
  get size(): number;
82
82
  }
83
+ /**
84
+ * Redis-backed session store.
85
+ * Pass to `session({ store: new RedisStore({ redis }) })`.
86
+ */
83
87
  export declare class RedisStore implements SessionStore {
84
88
  private redis;
85
89
  private prefix;
@@ -90,6 +94,22 @@ export declare class RedisStore implements SessionStore {
90
94
  destroy(sid: string): Promise<void>;
91
95
  close(): Promise<void>;
92
96
  }
97
+ /**
98
+ * Session middleware. Injects `ctx.session` with a persistent key-value store
99
+ * scoped to the request. Data is automatically saved to the store on response.
100
+ *
101
+ * Defaults to memory store. Use `{ store: 'redis', redis }` for multi-process setups.
102
+ *
103
+ * ```ts
104
+ * import { session } from 'weifuwu'
105
+ * app.use(session())
106
+ *
107
+ * app.get('/visit', (req, ctx) => {
108
+ * ctx.session.count = (ctx.session.count ?? 0) + 1
109
+ * return Response.json({ visits: ctx.session.count })
110
+ * })
111
+ * ```
112
+ */
93
113
  export declare function session(options?: SessionOptions): Middleware<Context, Context & SessionInjected> & {
94
114
  close: () => Promise<void>;
95
115
  store: SessionStore;
package/dist/types.d.ts CHANGED
@@ -19,3 +19,16 @@ export interface Closeable {
19
19
  /** Release all resources. Call once when shutting down. */
20
20
  close(): Promise<void>;
21
21
  }
22
+ /**
23
+ * HTTP error with an explicit status code.
24
+ * Throw from a handler or middleware to return a non-200 response.
25
+ *
26
+ * ```ts
27
+ * if (!resource) throw new HttpError('Not found', 404)
28
+ * serve() catches it and returns the status code.
29
+ * ```
30
+ */
31
+ export declare class HttpError extends Error {
32
+ status: number;
33
+ constructor(message: string, status: number);
34
+ }
@@ -1,4 +1,9 @@
1
- import type { UserOptions, UserModule } from './types.ts';
1
+ import type { UserOptions, UserData, UserModule } from './types.ts';
2
+ declare module '../types.ts' {
3
+ interface Context {
4
+ user: UserData;
5
+ }
6
+ }
2
7
  /**
3
8
  * User authentication module — local register/login, JWT verification, OAuth2 server, social login.
4
9
  * Supports DB-less auth via tokens/verify/proxy options.
@@ -11,4 +11,20 @@ export interface ValidationSchemas {
11
11
  params?: ZodSchema;
12
12
  headers?: ZodSchema;
13
13
  }
14
+ /**
15
+ * Request validation middleware using Zod schemas.
16
+ *
17
+ * Validates `params`, `query`, `body`, and/or `headers` against schemas.
18
+ * Returns 422 with error details on mismatch.
19
+ * Injects `ctx.parsed` with validated-and-transformed values.
20
+ *
21
+ * ```ts
22
+ * import { z } from 'zod'
23
+ *
24
+ * app.get('/users/:id', validate({
25
+ * params: z.object({ id: z.string() }),
26
+ * query: z.object({ include: z.string().optional() }),
27
+ * }), handler)
28
+ * ```
29
+ */
14
30
  export declare function validate(schemas?: ValidationSchemas): Middleware;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.25.0",
4
+ "version": "0.25.1",
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",