weifuwu 0.23.4 → 0.24.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.
Files changed (46) hide show
  1. package/README.md +431 -152
  2. package/cli.ts +3 -0
  3. package/dist/agent/types.d.ts +2 -1
  4. package/dist/ai/provider.d.ts +10 -1
  5. package/dist/analytics.d.ts +2 -2
  6. package/dist/cache.d.ts +4 -4
  7. package/dist/cli.js +3 -0
  8. package/dist/cors.d.ts +2 -2
  9. package/dist/csrf.d.ts +11 -5
  10. package/dist/deploy/types.d.ts +2 -2
  11. package/dist/env.d.ts +33 -0
  12. package/dist/flash.d.ts +5 -0
  13. package/dist/helmet.d.ts +2 -2
  14. package/dist/hub.d.ts +2 -1
  15. package/dist/i18n.d.ts +27 -2
  16. package/dist/iii/register-worker.d.ts +1 -1
  17. package/dist/iii/types.d.ts +5 -3
  18. package/dist/index.d.ts +8 -10
  19. package/dist/index.js +434 -359
  20. package/dist/kb/types.d.ts +8 -0
  21. package/dist/logdb/types.d.ts +2 -1
  22. package/dist/mailer.d.ts +2 -1
  23. package/dist/messager/types.d.ts +2 -1
  24. package/dist/opencode/types.d.ts +2 -1
  25. package/dist/permissions.d.ts +2 -2
  26. package/dist/postgres/module.d.ts +2 -1
  27. package/dist/postgres/types.d.ts +2 -2
  28. package/dist/queue/types.d.ts +2 -2
  29. package/dist/rate-limit.d.ts +3 -2
  30. package/dist/react.js +6 -6
  31. package/dist/redis/types.d.ts +2 -2
  32. package/dist/request-id.d.ts +16 -7
  33. package/dist/router.d.ts +3 -0
  34. package/dist/seo.d.ts +2 -2
  35. package/dist/serve.d.ts +1 -1
  36. package/dist/session.d.ts +9 -5
  37. package/dist/tailwind.d.ts +9 -0
  38. package/dist/tenant/types.d.ts +3 -3
  39. package/dist/theme.d.ts +25 -2
  40. package/dist/trace.d.ts +44 -0
  41. package/dist/types.d.ts +8 -17
  42. package/dist/upload.d.ts +9 -2
  43. package/dist/user/client.d.ts +11 -3
  44. package/dist/user/types.d.ts +21 -6
  45. package/dist/validate.d.ts +5 -0
  46. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // trace.ts
8
+ import crypto2 from "node:crypto";
8
9
  import { AsyncLocalStorage } from "node:async_hooks";
9
- import { randomUUID } from "node:crypto";
10
10
  var als = new AsyncLocalStorage();
11
11
  function currentTraceId() {
12
12
  return als.getStore()?.traceId;
@@ -15,7 +15,7 @@ function currentTrace() {
15
15
  return als.getStore();
16
16
  }
17
17
  function runWithTrace(incomingTraceId, fn) {
18
- const traceId = incomingTraceId || randomUUID();
18
+ const traceId = incomingTraceId || crypto2.randomUUID();
19
19
  const startTime = Date.now();
20
20
  return als.run({ traceId, startTime }, fn);
21
21
  }
@@ -24,10 +24,43 @@ function traceElapsed() {
24
24
  if (!ctx) return 0;
25
25
  return Date.now() - ctx.startTime;
26
26
  }
27
+ function trace(options) {
28
+ const header = options?.header ?? "X-Request-ID";
29
+ const gen = options?.generator ?? (() => crypto2.randomUUID());
30
+ return async (req, ctx, next) => {
31
+ const existing = req.headers.get(header);
32
+ const requestId2 = existing ?? gen();
33
+ const tc = als.getStore();
34
+ ctx.trace = {
35
+ requestId: requestId2,
36
+ traceId: tc?.traceId ?? requestId2,
37
+ startTime: tc?.startTime ?? Date.now(),
38
+ elapsed: () => {
39
+ const t = als.getStore();
40
+ return t ? Date.now() - t.startTime : 0;
41
+ }
42
+ };
43
+ const res = await next(req, ctx);
44
+ if (res.headers.has(header)) return res;
45
+ const h = new Headers(res.headers);
46
+ h.set(header, requestId2);
47
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
48
+ };
49
+ }
27
50
 
28
51
  // env.ts
29
52
  import { readFileSync } from "node:fs";
30
53
  import { resolve } from "node:path";
54
+ var PUBLIC_PREFIX = "WEIFUWU_PUBLIC_";
55
+ function getPublicEnv() {
56
+ const result = {};
57
+ for (const [key, value] of Object.entries(process.env)) {
58
+ if (key.startsWith(PUBLIC_PREFIX) && value !== void 0) {
59
+ result[key.slice(PUBLIC_PREFIX.length)] = value;
60
+ }
61
+ }
62
+ return result;
63
+ }
31
64
  function isBundled() {
32
65
  return true ? true : false;
33
66
  }
@@ -65,6 +98,14 @@ function loadEnv(path2) {
65
98
  process.env[key] = value;
66
99
  }
67
100
  }
101
+ function env() {
102
+ const entries = getPublicEnv();
103
+ return async (req, ctx, next) => {
104
+ ;
105
+ ctx.env = entries;
106
+ return next(req, ctx);
107
+ };
108
+ }
68
109
 
69
110
  // serve.ts
70
111
  import http from "node:http";
@@ -142,8 +183,8 @@ async function sendResponse(res, response, opts) {
142
183
  }
143
184
  res.end();
144
185
  }
145
- async function createTestServer(handler) {
146
- const server = serve(handler, { port: 0, shutdown: false });
186
+ async function createTestServer(handler, options) {
187
+ const server = serve(handler, { ...options, port: options?.port ?? 0, shutdown: false });
147
188
  await server.ready;
148
189
  return { server, url: `http://localhost:${server.port}` };
149
190
  }
@@ -447,6 +488,10 @@ var Router = class _Router {
447
488
  }
448
489
  } else if (typeof arg1 === "function") {
449
490
  this.globalMws.push(arg1);
491
+ } else if (typeof arg1 === "object" && arg1 !== null && "middleware" in arg1 && typeof arg1.middleware === "function" && arg1 instanceof _Router) {
492
+ const mod = arg1;
493
+ this.globalMws.push(mod.middleware());
494
+ this._mountRouter("/", mod);
450
495
  }
451
496
  return this;
452
497
  }
@@ -991,114 +1036,6 @@ function cors(options) {
991
1036
  };
992
1037
  }
993
1038
 
994
- // auth.ts
995
- function auth(options) {
996
- if (!options.token && !options.verify && !options.proxy && !options.session) {
997
- throw new Error("auth() requires at least one of: token, verify, proxy, or session");
998
- }
999
- return async (req, ctx, next) => {
1000
- if (options.session) {
1001
- const sessionUserId = ctx.session?.userId;
1002
- if (sessionUserId !== void 0 && sessionUserId !== null) {
1003
- if (options.resolveUser) {
1004
- const userData = await options.resolveUser(sessionUserId);
1005
- if (userData) {
1006
- ctx.user = userData;
1007
- return next(req, ctx);
1008
- }
1009
- if (typeof ctx.session?.destroy === "function") {
1010
- ;
1011
- ctx.session.destroy();
1012
- }
1013
- console.warn(`[${currentTraceId()}] auth: session userId ${sessionUserId} resolved to null`);
1014
- } else {
1015
- ctx.user = { id: sessionUserId };
1016
- return next(req, ctx);
1017
- }
1018
- }
1019
- }
1020
- const headerName = options.header ?? "Authorization";
1021
- let from = "header";
1022
- let header = req.headers.get(headerName);
1023
- let token = "";
1024
- if (header) {
1025
- token = header.trim();
1026
- if (headerName.toLowerCase() === "authorization") {
1027
- const parts = header.split(" ");
1028
- if (parts[0]?.toLowerCase() === "bearer") {
1029
- token = parts.slice(1).join(" ").trim();
1030
- }
1031
- }
1032
- } else if (!options.header) {
1033
- const url = new URL(req.url);
1034
- const qsToken = url.searchParams.get("access_token");
1035
- if (qsToken) {
1036
- token = qsToken;
1037
- from = "query";
1038
- }
1039
- }
1040
- if (!token) {
1041
- return new Response("Unauthorized", {
1042
- status: 401,
1043
- headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
1044
- });
1045
- }
1046
- if (options.proxy) {
1047
- let proxyUrl;
1048
- try {
1049
- proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
1050
- } catch {
1051
- return new Response("Invalid proxy URL", { status: 500 });
1052
- }
1053
- const proxyHeaders = {};
1054
- if (from === "header" && header) {
1055
- proxyHeaders[headerName] = header;
1056
- } else {
1057
- proxyUrl.searchParams.set("access_token", token);
1058
- }
1059
- for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
1060
- const v = req.headers.get(name);
1061
- if (v) proxyHeaders[name] = v;
1062
- }
1063
- const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
1064
- if (proxyRes.status >= 400) {
1065
- return new Response(await proxyRes.text() || "Forbidden", { status: proxyRes.status });
1066
- }
1067
- let userData = void 0;
1068
- if (proxyRes.status === 200) {
1069
- const ct = proxyRes.headers.get("content-type");
1070
- if (ct?.includes("application/json")) {
1071
- try {
1072
- userData = await proxyRes.json();
1073
- } catch {
1074
- }
1075
- }
1076
- }
1077
- ctx.user = userData;
1078
- return next(req, ctx);
1079
- }
1080
- if (options.token) {
1081
- if (token !== options.token) {
1082
- console.warn(`[${currentTraceId()}] auth: invalid static token`);
1083
- return new Response("Forbidden", { status: 403 });
1084
- }
1085
- return next(req, ctx);
1086
- }
1087
- if (options.verify) {
1088
- const result = await options.verify(token, req);
1089
- if (!result) {
1090
- console.warn(`[${currentTraceId()}] auth: verify failed for token`);
1091
- return new Response("Forbidden", { status: 403 });
1092
- }
1093
- if (typeof result === "object" && result !== null) {
1094
- ctx.user = result;
1095
- }
1096
- return next(req, ctx);
1097
- }
1098
- return next(req, ctx);
1099
- };
1100
- }
1101
-
1102
1039
  // static.ts
1103
1040
  import { open, realpath } from "node:fs/promises";
1104
1041
  import { extname, resolve as resolve2, normalize, sep } from "node:path";
@@ -1382,7 +1319,7 @@ function deleteCookie(res, name, options) {
1382
1319
 
1383
1320
  // upload.ts
1384
1321
  import { writeFile, mkdir } from "node:fs/promises";
1385
- import { randomUUID as randomUUID2 } from "node:crypto";
1322
+ import { randomUUID } from "node:crypto";
1386
1323
  import { join, extname as extname2 } from "node:path";
1387
1324
  var extensionMimeMap = {
1388
1325
  ".jpg": "image/jpeg",
@@ -1454,7 +1391,7 @@ function upload(options) {
1454
1391
  };
1455
1392
  if (saveDir) {
1456
1393
  const safeName = value.name.replace(/[/\\\0]/g, "_").replace(/\.\./g, "_");
1457
- const filePath = join(saveDir, `${randomUUID2()}-${safeName}`);
1394
+ const filePath = join(saveDir, `${randomUUID()}-${safeName}`);
1458
1395
  await writeFile(filePath, buf);
1459
1396
  uf.path = filePath;
1460
1397
  }
@@ -1551,10 +1488,11 @@ function rateLimit(options) {
1551
1488
  const res = await next(req, ctx);
1552
1489
  return addRateLimitHeaders(res, max, remaining, reset);
1553
1490
  };
1554
- mw.stop = () => {
1491
+ mw.close = () => {
1555
1492
  if (interval) clearInterval(interval);
1556
1493
  hits.clear();
1557
1494
  };
1495
+ mw.stop = mw.close;
1558
1496
  mw.stats = () => ({
1559
1497
  store: storeType,
1560
1498
  entries: storeType === "memory" ? hits.size : void 0,
@@ -1672,10 +1610,10 @@ var DEFAULTS = {
1672
1610
  };
1673
1611
 
1674
1612
  // request-id.ts
1675
- import crypto2 from "node:crypto";
1613
+ import crypto3 from "node:crypto";
1676
1614
  function requestId(options) {
1677
1615
  const header = options?.header ?? "X-Request-ID";
1678
- const gen = options?.generator ?? (() => crypto2.randomUUID());
1616
+ const gen = options?.generator ?? (() => crypto3.randomUUID());
1679
1617
  return async (req, ctx, next) => {
1680
1618
  const existing = req.headers.get(header);
1681
1619
  const id2 = existing ?? gen();
@@ -2425,7 +2363,7 @@ function aiProvider(options) {
2425
2363
  const client = createOpenAI({ baseURL, apiKey });
2426
2364
  let _model;
2427
2365
  let _embedModel;
2428
- return {
2366
+ const provider = {
2429
2367
  get dimension() {
2430
2368
  return dimension;
2431
2369
  },
@@ -2454,6 +2392,12 @@ function aiProvider(options) {
2454
2392
  return aiStreamText({ ...params, model: this.model() });
2455
2393
  }
2456
2394
  };
2395
+ const mw = async (req, ctx, next) => {
2396
+ ;
2397
+ ctx.ai = provider;
2398
+ return next(req, ctx);
2399
+ };
2400
+ return Object.assign(mw, provider);
2457
2401
  }
2458
2402
 
2459
2403
  // ai-sdk.ts
@@ -2751,9 +2695,10 @@ var Table = class {
2751
2695
  _buildSET(data) {
2752
2696
  const sets = [];
2753
2697
  const values = [];
2698
+ const d = data;
2754
2699
  for (const { prop, db } of this.colEntries) {
2755
- if (prop in data && data[prop] !== void 0) {
2756
- const val = data[prop];
2700
+ if (prop in d && d[prop] !== void 0) {
2701
+ const val = d[prop];
2757
2702
  if (val instanceof SQL) {
2758
2703
  sets.push(`"${db}" = ${val.toSQL()}`);
2759
2704
  } else {
@@ -2762,7 +2707,7 @@ var Table = class {
2762
2707
  }
2763
2708
  }
2764
2709
  }
2765
- if (this.hasColumn("updated_at") && !data.updated_at) {
2710
+ if (this.hasColumn("updated_at") && !d.updated_at) {
2766
2711
  sets.push('"updated_at" = NOW()');
2767
2712
  }
2768
2713
  return { sets, values };
@@ -3126,7 +3071,7 @@ import jwt2 from "jsonwebtoken";
3126
3071
  import { z as z2 } from "zod";
3127
3072
 
3128
3073
  // user/oauth2.ts
3129
- import crypto3 from "node:crypto";
3074
+ import crypto4 from "node:crypto";
3130
3075
  import jwt from "jsonwebtoken";
3131
3076
  function createOAuth2Server(deps) {
3132
3077
  const { pg, users, jwtSecret, expiresIn } = deps;
@@ -3145,8 +3090,8 @@ function createOAuth2Server(deps) {
3145
3090
  };
3146
3091
  }
3147
3092
  async function registerClient(data) {
3148
- const clientId = crypto3.randomUUID();
3149
- const clientSecret = crypto3.randomBytes(32).toString("hex");
3093
+ const clientId = crypto4.randomUUID();
3094
+ const clientSecret = crypto4.randomBytes(32).toString("hex");
3150
3095
  const [row] = await pg.sql`
3151
3096
  INSERT INTO "_oauth2_clients" ("name", "client_id", "client_secret", "redirect_uris")
3152
3097
  VALUES (${data.name}, ${clientId}, ${clientSecret}, ${pg.sql.array(data.redirectUris)})
@@ -3287,18 +3232,18 @@ h2{color:#dc2626}.desc{color:#555}</style>
3287
3232
  const redirectUri = form.get("redirect_uri") || "";
3288
3233
  const scope = form.get("scope") || "";
3289
3234
  const state = form.get("state") || "";
3290
- const userId = parseInt(form.get("user_id") || "0", 10);
3235
+ const userId2 = parseInt(form.get("user_id") || "0", 10);
3291
3236
  const codeChallenge = form.get("code_challenge") || "";
3292
3237
  const codeChallengeMethod = form.get("code_challenge_method") || "";
3293
3238
  if (!approve) {
3294
3239
  const loc2 = `${redirectUri}?error=access_denied${state ? `&state=${state}` : ""}`;
3295
3240
  return Response.redirect(loc2, 302);
3296
3241
  }
3297
- const code = crypto3.randomUUID();
3242
+ const code = crypto4.randomUUID();
3298
3243
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
3299
3244
  await pg.sql`
3300
3245
  INSERT INTO "_oauth2_codes" ("code", "client_id", "user_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "expires_at")
3301
- VALUES (${code}, ${clientId}, ${userId}, ${redirectUri}, ${codeChallenge || null}, ${codeChallengeMethod || null}, ${scope || null}, ${expiresAt})
3246
+ VALUES (${code}, ${clientId}, ${userId2}, ${redirectUri}, ${codeChallenge || null}, ${codeChallengeMethod || null}, ${scope || null}, ${expiresAt})
3302
3247
  `;
3303
3248
  const loc = `${redirectUri}?code=${code}${state ? `&state=${state}` : ""}`;
3304
3249
  return Response.redirect(loc, 302);
@@ -3359,7 +3304,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
3359
3304
  if (stored.code_challenge_method === "plain") {
3360
3305
  expected = codeVerifier;
3361
3306
  } else {
3362
- expected = crypto3.createHash("sha256").update(codeVerifier).digest().toString("base64url");
3307
+ expected = crypto4.createHash("sha256").update(codeVerifier).digest().toString("base64url");
3363
3308
  }
3364
3309
  if (expected !== stored.code_challenge) {
3365
3310
  return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
@@ -3376,7 +3321,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
3376
3321
  jwtSecret,
3377
3322
  { expiresIn }
3378
3323
  );
3379
- const refreshToken = crypto3.randomUUID();
3324
+ const refreshToken = crypto4.randomUUID();
3380
3325
  const refreshExpires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3);
3381
3326
  await pg.sql`
3382
3327
  INSERT INTO "_oauth2_tokens" ("token", "client_id", "user_id", "scope", "expires_at")
@@ -3414,7 +3359,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
3414
3359
  }
3415
3360
 
3416
3361
  // user/oauth-login.ts
3417
- import crypto4 from "node:crypto";
3362
+ import crypto5 from "node:crypto";
3418
3363
  var BUILTIN_PROVIDERS = {
3419
3364
  google: {
3420
3365
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
@@ -3474,12 +3419,12 @@ function registerOAuthLoginRoutes(router, deps, providers) {
3474
3419
  );
3475
3420
  return row ?? null;
3476
3421
  }
3477
- async function linkProvider(userId, provider, providerId, email, name, avatarUrl) {
3422
+ async function linkProvider(userId2, provider, providerId, email, name, avatarUrl) {
3478
3423
  await sql2.unsafe(
3479
3424
  `INSERT INTO ${escapeIdent(providerTable)} (user_id, provider, provider_id, email, name, avatar_url)
3480
3425
  VALUES ($1, $2, $3, $4, $5, $6)
3481
3426
  ON CONFLICT (provider, provider_id) DO NOTHING`,
3482
- [userId, provider, providerId, email, name, avatarUrl]
3427
+ [userId2, provider, providerId, email, name, avatarUrl]
3483
3428
  );
3484
3429
  }
3485
3430
  async function findOrCreateUser(provider, providerId, email, name, avatarUrl) {
@@ -3526,7 +3471,7 @@ function registerOAuthLoginRoutes(router, deps, providers) {
3526
3471
  return Response.json({ error: `Unsupported provider: ${providerName}` }, { status: 400 });
3527
3472
  }
3528
3473
  const { config, meta } = resolved;
3529
- const state = crypto4.randomUUID();
3474
+ const state = crypto5.randomUUID();
3530
3475
  const redirectUri = new URL(req.url);
3531
3476
  redirectUri.pathname = redirectUri.pathname.replace(/\/[^/]+$/, "/") + providerName + "/callback";
3532
3477
  if (ctx.session) {
@@ -3657,14 +3602,40 @@ function verifyPassword(password, stored) {
3657
3602
  if (hash.length !== verify.length) return false;
3658
3603
  return timingSafeEqual(Buffer.from(hash), Buffer.from(verify));
3659
3604
  }
3605
+ function extractToken(req, headerName, cookieName) {
3606
+ const header = req.headers.get(headerName);
3607
+ if (header) {
3608
+ if (headerName.toLowerCase() === "authorization") {
3609
+ const parts = header.split(" ");
3610
+ if (parts[0]?.toLowerCase() === "bearer") {
3611
+ return parts.slice(1).join(" ").trim();
3612
+ }
3613
+ }
3614
+ return header.trim();
3615
+ }
3616
+ if (headerName.toLowerCase() === "authorization") {
3617
+ const url = new URL(req.url);
3618
+ const qsToken = url.searchParams.get("access_token");
3619
+ if (qsToken) return qsToken;
3620
+ }
3621
+ if (cookieName) {
3622
+ const cookies = req.headers.get("cookie")?.split(";").map((c) => c.trim()).filter(Boolean) || [];
3623
+ for (const c of cookies) {
3624
+ const eq2 = c.indexOf("=");
3625
+ if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
3626
+ }
3627
+ }
3628
+ return null;
3629
+ }
3660
3630
  function user(options) {
3631
+ const hasDb = !!options.pg;
3661
3632
  const table = options.table ?? "_users";
3662
3633
  const pg = options.pg;
3663
3634
  const secret = options.jwtSecret;
3664
3635
  const expiresIn = options.expiresIn ?? "24h";
3665
3636
  const oauth2Enabled = options.oauth2?.server ?? false;
3666
- const base = new PgModule(pg);
3667
- const users = pg.table(table, {
3637
+ const base = hasDb ? new PgModule(pg) : null;
3638
+ const users = hasDb ? pg.table(table, {
3668
3639
  id: serial("id").primaryKey(),
3669
3640
  email: text("email").unique().notNull(),
3670
3641
  password: text("password").notNull(),
@@ -3672,15 +3643,17 @@ function user(options) {
3672
3643
  role: text("role").default("user"),
3673
3644
  created_at: timestamptz("created_at").default(sql`NOW()`),
3674
3645
  updated_at: timestamptz("updated_at").default(sql`NOW()`)
3675
- });
3646
+ }) : null;
3647
+ const _pg = pg;
3648
+ const _users = users;
3676
3649
  let oauth2 = null;
3677
3650
  if (oauth2Enabled) {
3678
- oauth2 = createOAuth2Server({ pg, users, jwtSecret: secret, expiresIn });
3651
+ oauth2 = createOAuth2Server({ pg: _pg, users: _users, jwtSecret: secret, expiresIn });
3679
3652
  }
3680
3653
  async function migrate() {
3681
- await users.create();
3654
+ await _users.create();
3682
3655
  if (options.oauthLogin) {
3683
- await pg.sql.unsafe(`
3656
+ await _pg.sql.unsafe(`
3684
3657
  CREATE TABLE IF NOT EXISTS "_auth_providers" (
3685
3658
  id SERIAL PRIMARY KEY,
3686
3659
  user_id INTEGER NOT NULL REFERENCES ${escapeIdent2(table)}(id) ON DELETE CASCADE,
@@ -3693,13 +3666,13 @@ function user(options) {
3693
3666
  UNIQUE(provider, provider_id)
3694
3667
  )
3695
3668
  `);
3696
- await pg.sql.unsafe(`
3669
+ await _pg.sql.unsafe(`
3697
3670
  CREATE INDEX IF NOT EXISTS "_auth_providers_user_idx"
3698
3671
  ON "_auth_providers"(user_id)
3699
3672
  `);
3700
3673
  }
3701
3674
  if (!oauth2Enabled) return;
3702
- const clients3 = pg.table("_oauth2_clients", {
3675
+ const clients3 = _pg.table("_oauth2_clients", {
3703
3676
  id: serial("id").primaryKey(),
3704
3677
  name: text("name").notNull(),
3705
3678
  client_id: text("client_id").unique().notNull(),
@@ -3709,7 +3682,7 @@ function user(options) {
3709
3682
  created_at: timestamptz("created_at").default(sql`NOW()`)
3710
3683
  });
3711
3684
  await clients3.create();
3712
- const codes = pg.table("_oauth2_codes", {
3685
+ const codes = _pg.table("_oauth2_codes", {
3713
3686
  id: serial("id").primaryKey(),
3714
3687
  code: text("code").unique().notNull(),
3715
3688
  client_id: text("client_id").notNull(),
@@ -3722,7 +3695,7 @@ function user(options) {
3722
3695
  used: boolean_("used").default(false)
3723
3696
  });
3724
3697
  await codes.create();
3725
- const tokens = pg.table("_oauth2_tokens", {
3698
+ const tokens = _pg.table("_oauth2_tokens", {
3726
3699
  id: serial("id").primaryKey(),
3727
3700
  token: text("token").unique().notNull(),
3728
3701
  client_id: text("client_id").notNull(),
@@ -3745,15 +3718,15 @@ function user(options) {
3745
3718
  return user2;
3746
3719
  }
3747
3720
  async function findByEmail(email) {
3748
- const { data: rows } = await users.readMany({ email });
3721
+ const { data: rows } = await _users.readMany({ email });
3749
3722
  return rows[0];
3750
3723
  }
3751
3724
  async function findById(id2) {
3752
- return await users.read(id2);
3725
+ return await _users.read(id2);
3753
3726
  }
3754
3727
  async function createPlaceholderUser(email, name) {
3755
3728
  const randomPassword = randomBytes(32).toString("hex");
3756
- const row = await users.insert({ email, password: randomPassword, name });
3729
+ const row = await _users.insert({ email, password: randomPassword, name });
3757
3730
  return row;
3758
3731
  }
3759
3732
  async function register(data) {
@@ -3765,14 +3738,14 @@ function user(options) {
3765
3738
  throw err;
3766
3739
  }
3767
3740
  const hashed = hashPassword(password);
3768
- const row = await users.insert({ email, password: hashed, name });
3741
+ const row = await _users.insert({ email, password: hashed, name });
3769
3742
  const userData = row;
3770
3743
  const token = signToken(userData);
3771
3744
  return { user: stripPassword(userData), token };
3772
3745
  }
3773
3746
  async function login(data) {
3774
3747
  const { email, password } = LoginSchema.parse(data);
3775
- const { data: rows } = await users.readMany({ email });
3748
+ const { data: rows } = await _users.readMany({ email });
3776
3749
  const row = rows[0];
3777
3750
  if (!row) {
3778
3751
  const err = new Error("Invalid email or password");
@@ -3792,61 +3765,121 @@ function user(options) {
3792
3765
  try {
3793
3766
  const payload = jwt2.verify(token, secret);
3794
3767
  if (payload.token_type === "client_credentials") return null;
3795
- const row = await findById(payload.sub);
3768
+ if (!hasDb || !findById) return null;
3769
+ const row = await findById(Number(payload.sub));
3796
3770
  if (!row) return null;
3797
3771
  return stripPassword(row);
3798
3772
  } catch {
3799
3773
  return null;
3800
3774
  }
3801
3775
  }
3802
- function extractToken(req, cookieName) {
3803
- const header = req.headers.get("Authorization");
3804
- if (header?.startsWith("Bearer ")) return header.slice(7);
3805
- if (cookieName) {
3806
- const cookies = req.headers.get("cookie")?.split(";").map((c) => c.trim()).filter(Boolean) || [];
3807
- for (const c of cookies) {
3808
- const eq2 = c.indexOf("=");
3809
- if (eq2 > 0 && c.slice(0, eq2) === cookieName) return c.slice(eq2 + 1);
3810
- }
3811
- }
3812
- return null;
3813
- }
3814
- function middleware() {
3815
- return async (req, ctx, next) => {
3816
- const sessionUserId = ctx.session?.userId;
3817
- if (sessionUserId) {
3776
+ const headerName = options.header ?? "Authorization";
3777
+ async function resolveUser(req, ctx) {
3778
+ const s = ctx;
3779
+ const sessionUserId = s.session?.userId;
3780
+ if (sessionUserId !== void 0 && sessionUserId !== null) {
3781
+ if (hasDb) {
3818
3782
  const row = await findById(sessionUserId);
3819
3783
  if (row) {
3820
- ctx.user = stripPassword(row);
3821
- return next(req, ctx);
3784
+ return stripPassword(row);
3822
3785
  }
3823
- if (typeof ctx.session?.destroy === "function") {
3824
- ;
3825
- ctx.session.destroy();
3826
- } else {
3827
- delete ctx.session?.userId;
3786
+ if (typeof s.session?.destroy === "function") {
3787
+ s.session.destroy();
3788
+ } else if (s.session) {
3789
+ delete s.session.userId;
3828
3790
  }
3829
- }
3830
- const token = extractToken(req);
3831
- if (token) {
3832
- const userData = await verify(token);
3791
+ } else if (options.resolveUser) {
3792
+ const userData = await options.resolveUser(sessionUserId);
3833
3793
  if (userData) {
3834
- ctx.user = userData;
3835
- return next(req, ctx);
3794
+ return userData;
3795
+ }
3796
+ if (typeof s.session?.destroy === "function") {
3797
+ s.session.destroy();
3798
+ }
3799
+ console.warn(`[${currentTraceId()}] user: session userId ${sessionUserId} resolved to null`);
3800
+ } else {
3801
+ return { id: sessionUserId };
3802
+ }
3803
+ }
3804
+ const token = extractToken(req, headerName);
3805
+ if (!token) return null;
3806
+ if (options.tokens?.length) {
3807
+ if (options.tokens.includes(token)) {
3808
+ return { id: token };
3809
+ }
3810
+ console.warn(`[${currentTraceId()}] user: invalid static token`);
3811
+ return null;
3812
+ }
3813
+ if (options.verify) {
3814
+ const result = await options.verify(token, req);
3815
+ if (!result) {
3816
+ console.warn(`[${currentTraceId()}] user: verify failed for token`);
3817
+ return null;
3818
+ }
3819
+ return result;
3820
+ }
3821
+ if (options.proxy) {
3822
+ let proxyUrl;
3823
+ try {
3824
+ proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
3825
+ } catch {
3826
+ return null;
3827
+ }
3828
+ const proxyHeaders = {};
3829
+ proxyHeaders[headerName] = req.headers.get(headerName) ?? `Bearer ${token}`;
3830
+ for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
3831
+ const v = req.headers.get(name);
3832
+ if (v) proxyHeaders[name] = v;
3833
+ }
3834
+ try {
3835
+ const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
3836
+ if (proxyRes.status >= 400) {
3837
+ console.warn(`[${currentTraceId()}] user: proxy auth rejected (${proxyRes.status})`);
3838
+ return null;
3836
3839
  }
3840
+ const ct = proxyRes.headers.get("content-type");
3841
+ if (ct?.includes("application/json")) {
3842
+ try {
3843
+ return await proxyRes.json();
3844
+ } catch {
3845
+ }
3846
+ }
3847
+ return { id: token };
3848
+ } catch (err) {
3849
+ console.warn(`[${currentTraceId()}] user: proxy auth error: ${err}`);
3850
+ return null;
3851
+ }
3852
+ }
3853
+ if (secret && hasDb) {
3854
+ try {
3855
+ const payload = jwt2.verify(token, secret);
3856
+ if (payload.token_type === "client_credentials") return null;
3857
+ const row = await findById(Number(payload.sub));
3858
+ if (row) return stripPassword(row);
3859
+ } catch {
3860
+ }
3861
+ return null;
3862
+ }
3863
+ return null;
3864
+ }
3865
+ function middleware() {
3866
+ return async (req, ctx, next) => {
3867
+ const userData = await resolveUser(req, ctx);
3868
+ if (userData) {
3869
+ ctx.user = userData;
3870
+ return next(req, ctx);
3837
3871
  }
3838
- return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
3872
+ return new Response("Unauthorized", {
3873
+ status: 401,
3874
+ headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
3875
+ });
3839
3876
  };
3840
3877
  }
3841
- function middlewareOptional(opts) {
3842
- const cookieName = opts?.cookie;
3878
+ function middlewareOptional(_opts) {
3843
3879
  return async (req, ctx, next) => {
3844
- const token = extractToken(req, cookieName);
3845
- if (token) {
3846
- const userData = await verify(token);
3847
- if (userData) {
3848
- ctx.user = userData;
3849
- }
3880
+ const userData = await resolveUser(req, ctx);
3881
+ if (userData) {
3882
+ ctx.user = userData;
3850
3883
  }
3851
3884
  return next(req, ctx);
3852
3885
  };
@@ -3863,9 +3896,9 @@ function user(options) {
3863
3896
  }
3864
3897
  return obj;
3865
3898
  }
3866
- function router() {
3867
- const r2 = new Router();
3868
- r2.post("/register", async (req) => {
3899
+ const r = new Router();
3900
+ if (hasDb) {
3901
+ r.post("/register", async (req) => {
3869
3902
  try {
3870
3903
  const body = await parseBody2(req);
3871
3904
  const result = await register(body);
@@ -3878,17 +3911,17 @@ function user(options) {
3878
3911
  return Response.json({ error: err.message }, { status });
3879
3912
  }
3880
3913
  });
3881
- r2.post("/login", async (req, ctx) => {
3914
+ r.post("/login", async (req, ctx) => {
3882
3915
  try {
3883
3916
  const body = await parseBody2(req);
3884
3917
  const result = await login(body);
3885
- if (ctx.session) {
3886
- ;
3887
- ctx.session.userId = result.user.id;
3888
- ctx.session.role = result.user.role;
3918
+ const s = ctx;
3919
+ if (s.session) {
3920
+ s.session.userId = result.user.id;
3921
+ s.session.role = result.user.role;
3889
3922
  }
3890
3923
  const res = Response.json(result);
3891
- if (!ctx.session) {
3924
+ if (!s.session) {
3892
3925
  res.headers.set("Set-Cookie", `session=${result.token}; HttpOnly; SameSite=Lax; Path=/`);
3893
3926
  }
3894
3927
  return res;
@@ -3900,17 +3933,15 @@ function user(options) {
3900
3933
  return Response.json({ error: err.message }, { status });
3901
3934
  }
3902
3935
  });
3903
- if (oauth2) {
3904
- r2.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
3905
- r2.post("/oauth/consent", (req) => oauth2.consentHandler(req));
3906
- r2.post("/oauth/token", (req) => oauth2.tokenHandler(req));
3907
- }
3908
- return r2;
3909
3936
  }
3910
- const r = router();
3911
- if (options.oauthLogin) {
3937
+ if (oauth2) {
3938
+ r.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
3939
+ r.post("/oauth/consent", (req) => oauth2.consentHandler(req));
3940
+ r.post("/oauth/token", (req) => oauth2.tokenHandler(req));
3941
+ }
3942
+ if (hasDb && options.oauthLogin) {
3912
3943
  registerOAuthLoginRoutes(r, {
3913
- sql: pg.sql,
3944
+ sql: _pg.sql,
3914
3945
  jwtSecret: secret,
3915
3946
  expiresIn,
3916
3947
  usersTable: table,
@@ -3925,10 +3956,15 @@ function user(options) {
3925
3956
  const mod = r;
3926
3957
  mod.middleware = middleware;
3927
3958
  mod.middlewareOptional = middlewareOptional;
3928
- mod.migrate = migrate;
3929
- mod.register = register;
3930
- mod.login = login;
3931
- mod.verify = verify;
3959
+ mod.migrate = hasDb ? migrate : async () => {
3960
+ };
3961
+ mod.register = hasDb ? register : async () => {
3962
+ throw new Error("user(): pg required for register");
3963
+ };
3964
+ mod.login = hasDb ? login : async () => {
3965
+ throw new Error("user(): pg required for login");
3966
+ };
3967
+ mod.verify = hasDb ? verify : async () => null;
3932
3968
  mod.registerClient = oauth2 ? (data) => oauth2.registerClient(data) : async () => {
3933
3969
  throw new Error("OAuth2 server is not enabled");
3934
3970
  };
@@ -3938,7 +3974,8 @@ function user(options) {
3938
3974
  mod.revokeClient = oauth2 ? (clientId) => oauth2.revokeClient(clientId) : async () => {
3939
3975
  throw new Error("OAuth2 server is not enabled");
3940
3976
  };
3941
- mod.close = () => base.close();
3977
+ mod.close = hasDb ? () => base.close() : async () => {
3978
+ };
3942
3979
  return mod;
3943
3980
  }
3944
3981
 
@@ -3960,7 +3997,7 @@ function redis(opts) {
3960
3997
 
3961
3998
  // queue/index.ts
3962
3999
  import { Redis as IORedis2 } from "ioredis";
3963
- import crypto5 from "node:crypto";
4000
+ import crypto6 from "node:crypto";
3964
4001
 
3965
4002
  // cron-utils.ts
3966
4003
  function parseField(field, min, max) {
@@ -4046,7 +4083,7 @@ function escapeIdent3(s) {
4046
4083
  function attachCron(q, handlers) {
4047
4084
  ;
4048
4085
  q.cron = function(pattern, handler) {
4049
- const id2 = "__cron_" + pattern.replace(/[^a-zA-Z0-9]/g, "_") + "_" + crypto5.randomUUID().slice(0, 8);
4086
+ const id2 = "__cron_" + pattern.replace(/[^a-zA-Z0-9]/g, "_") + "_" + crypto6.randomUUID().slice(0, 8);
4050
4087
  q.process(id2, async () => {
4051
4088
  await handler();
4052
4089
  });
@@ -4085,7 +4122,7 @@ function createMemoryQueue(opts) {
4085
4122
  }
4086
4123
  if (job.schedule) {
4087
4124
  try {
4088
- insertJob({ ...job, id: crypto5.randomUUID(), runAt: cronNext(job.schedule), createdAt: Date.now() });
4125
+ insertJob({ ...job, id: crypto6.randomUUID(), runAt: cronNext(job.schedule), createdAt: Date.now() });
4089
4126
  } catch (e) {
4090
4127
  console.error("[queue] cron re-queue failed:", e.message);
4091
4128
  }
@@ -4107,7 +4144,7 @@ function createMemoryQueue(opts) {
4107
4144
  });
4108
4145
  const q = mw;
4109
4146
  mw.add = function add(type, payload, opts2) {
4110
- const id2 = crypto5.randomUUID();
4147
+ const id2 = crypto6.randomUUID();
4111
4148
  let runAt;
4112
4149
  if (opts2?.schedule) {
4113
4150
  try {
@@ -4209,7 +4246,7 @@ function createPgQueue(opts) {
4209
4246
  if (job.schedule) {
4210
4247
  try {
4211
4248
  const nextRun = cronNext(job.schedule);
4212
- await sql2.unsafe(`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`, [crypto5.randomUUID(), job.type, JSON.stringify(job.payload), new Date(nextRun).toISOString(), job.schedule]);
4249
+ await sql2.unsafe(`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`, [crypto6.randomUUID(), job.type, JSON.stringify(job.payload), new Date(nextRun).toISOString(), job.schedule]);
4213
4250
  } catch (e) {
4214
4251
  console.error("[queue] cron re-queue failed:", e.message);
4215
4252
  }
@@ -4243,7 +4280,7 @@ function createPgQueue(opts) {
4243
4280
  const q = mw;
4244
4281
  mw.add = function add(type, payload, opts2) {
4245
4282
  return (async () => {
4246
- const id2 = crypto5.randomUUID();
4283
+ const id2 = crypto6.randomUUID();
4247
4284
  let runAt;
4248
4285
  if (opts2?.schedule) {
4249
4286
  try {
@@ -4330,7 +4367,7 @@ function createRedisQueue(opts) {
4330
4367
  if (job.schedule) {
4331
4368
  try {
4332
4369
  const nextRun = cronNext(job.schedule);
4333
- await redis2.zadd(jobKey, nextRun, JSON.stringify({ ...job, id: crypto5.randomUUID(), runAt: nextRun, createdAt: Date.now() }));
4370
+ await redis2.zadd(jobKey, nextRun, JSON.stringify({ ...job, id: crypto6.randomUUID(), runAt: nextRun, createdAt: Date.now() }));
4334
4371
  } catch (e) {
4335
4372
  console.error("[queue] cron re-queue failed:", e.message);
4336
4373
  }
@@ -4369,7 +4406,7 @@ function createRedisQueue(opts) {
4369
4406
  });
4370
4407
  const q = mw;
4371
4408
  mw.add = function add(type, payload, opts2) {
4372
- const id2 = crypto5.randomUUID();
4409
+ const id2 = crypto6.randomUUID();
4373
4410
  let runAt;
4374
4411
  if (opts2?.schedule) {
4375
4412
  runAt = cronNext(opts2.schedule);
@@ -4658,6 +4695,27 @@ function buildColumnDDL(tenantId, field) {
4658
4695
  }
4659
4696
 
4660
4697
  // tenant/rest.ts
4698
+ function userId(ctx) {
4699
+ return ctx.user?.id ?? null;
4700
+ }
4701
+ function extractCount(rows) {
4702
+ return Number(rows[0]?.count ?? 0);
4703
+ }
4704
+ function asJson(val) {
4705
+ return val;
4706
+ }
4707
+ function tableRef(s, name) {
4708
+ return s(name);
4709
+ }
4710
+ function withTenant(ctx, data) {
4711
+ ;
4712
+ data.tenant_id = ctx.tenant.id;
4713
+ return data;
4714
+ }
4715
+ function withoutTenant(data) {
4716
+ delete data.tenant_id;
4717
+ return data;
4718
+ }
4661
4719
  function zodType(field) {
4662
4720
  let t;
4663
4721
  switch (field.type) {
@@ -4715,7 +4773,7 @@ function buildRouter(sql2, usersTable) {
4715
4773
  `;
4716
4774
  await sql2`
4717
4775
  INSERT INTO "_tenant_members" ("tenant_id", "user_id", "role")
4718
- VALUES (${tenant2.id}, ${ctx.user.id}, 'admin')
4776
+ VALUES (${tenant2.id}, ${userId(ctx)}, 'admin')
4719
4777
  `;
4720
4778
  return Response.json(tenant2, { status: 201 });
4721
4779
  });
@@ -4723,7 +4781,7 @@ function buildRouter(sql2, usersTable) {
4723
4781
  const rows = await sql2`
4724
4782
  SELECT t.*, tm.role FROM "_tenants" t
4725
4783
  JOIN "_tenant_members" tm ON tm.tenant_id = t.id
4726
- WHERE tm.user_id = ${ctx.user.id}
4784
+ WHERE tm.user_id = ${userId(ctx)}
4727
4785
  `;
4728
4786
  return Response.json(rows);
4729
4787
  });
@@ -4732,7 +4790,7 @@ function buildRouter(sql2, usersTable) {
4732
4790
  if (err) return err;
4733
4791
  const { email, role = "member" } = await req.json();
4734
4792
  const [user2] = await sql2`
4735
- SELECT id FROM ${sql2(usersTable)} WHERE "email" = ${email} LIMIT 1
4793
+ SELECT id FROM ${tableRef(sql2, usersTable)} WHERE "email" = ${email} LIMIT 1
4736
4794
  `;
4737
4795
  if (!user2) return Response.json({ error: "User not found" }, { status: 404 });
4738
4796
  const [existing] = await sql2`
@@ -4749,10 +4807,10 @@ function buildRouter(sql2, usersTable) {
4749
4807
  r.delete("/sys/tenants/members/:userId", async (req, ctx) => {
4750
4808
  const err = requireAdmin(ctx);
4751
4809
  if (err) return err;
4752
- const userId = parseInt(ctx.params.userId, 10);
4810
+ const userId2 = parseInt(ctx.params.userId, 10);
4753
4811
  await sql2`
4754
4812
  DELETE FROM "_tenant_members"
4755
- WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${userId}
4813
+ WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${userId2}
4756
4814
  `;
4757
4815
  return Response.json({ ok: true });
4758
4816
  });
@@ -4778,7 +4836,7 @@ function buildRouter(sql2, usersTable) {
4778
4836
  }
4779
4837
  const [row] = await sql2`
4780
4838
  INSERT INTO "_user_tables" ("tenant_id", "slug", "label", "fields")
4781
- VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${body.fields})
4839
+ VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${asJson(body.fields)})
4782
4840
  RETURNING *
4783
4841
  `;
4784
4842
  return Response.json(row, { status: 201 });
@@ -4813,7 +4871,7 @@ function buildRouter(sql2, usersTable) {
4813
4871
  const merged = [...table.fields, ...newFields];
4814
4872
  await sql2`
4815
4873
  UPDATE "_user_tables"
4816
- SET fields = ${merged}
4874
+ SET fields = ${asJson(merged)}
4817
4875
  WHERE id = ${table.id}
4818
4876
  `;
4819
4877
  return Response.json({ ...table, fields: merged });
@@ -4861,7 +4919,7 @@ function buildRouter(sql2, usersTable) {
4861
4919
  [ctx.tenant.id]
4862
4920
  )
4863
4921
  ]);
4864
- return Response.json({ rows: rows2, count: Number(countResult2[0]?.count ?? 0) });
4922
+ return Response.json({ rows: rows2, count: extractCount(countResult2) });
4865
4923
  } catch {
4866
4924
  return Response.json({ error: "Invalid search_vector" }, { status: 400 });
4867
4925
  }
@@ -4877,7 +4935,7 @@ function buildRouter(sql2, usersTable) {
4877
4935
  [ctx.tenant.id]
4878
4936
  )
4879
4937
  ]);
4880
- return Response.json({ rows, count: Number(countResult[0]?.count ?? 0) });
4938
+ return Response.json({ rows, count: extractCount(countResult) });
4881
4939
  });
4882
4940
  r.post("/:_slug", async (req, ctx) => {
4883
4941
  const table = await resolveTable(ctx);
@@ -4888,11 +4946,10 @@ function buildRouter(sql2, usersTable) {
4888
4946
  shape[f.name] = zodType(f);
4889
4947
  }
4890
4948
  const zodSchema = z3.object(shape);
4891
- const parsed = zodSchema.parse(data);
4892
- parsed.tenant_id = ctx.tenant.id;
4949
+ const parsed = withTenant(ctx, zodSchema.parse(data));
4893
4950
  delete parsed.id;
4894
4951
  const name = internalName(ctx);
4895
- const [row] = await sql2`INSERT INTO ${sql2(name)} ${sql2(parsed)} RETURNING *`;
4952
+ const [row] = await sql2`INSERT INTO ${tableRef(sql2, name)} ${sql2(parsed)} RETURNING *`;
4896
4953
  return Response.json(row, { status: 201 });
4897
4954
  });
4898
4955
  r.get("/:_slug/:id", async (_req, ctx) => {
@@ -4900,7 +4957,7 @@ function buildRouter(sql2, usersTable) {
4900
4957
  if (!table) return Response.json({ error: "Table not found" }, { status: 404 });
4901
4958
  const name = internalName(ctx);
4902
4959
  const [row] = await sql2`
4903
- SELECT * FROM ${sql2(name)}
4960
+ SELECT * FROM ${tableRef(sql2, name)}
4904
4961
  WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
4905
4962
  LIMIT 1
4906
4963
  `;
@@ -4916,13 +4973,12 @@ function buildRouter(sql2, usersTable) {
4916
4973
  shape[f.name] = zodType(f);
4917
4974
  }
4918
4975
  const zodSchema = z3.object(shape).partial();
4919
- const parsed = zodSchema.parse(data);
4976
+ const parsed = withoutTenant(zodSchema.parse(data));
4920
4977
  delete parsed.id;
4921
- delete parsed.tenant_id;
4922
4978
  if (Object.keys(parsed).length === 0) {
4923
4979
  const name2 = internalName(ctx);
4924
4980
  const [row2] = await sql2`
4925
- SELECT * FROM ${sql2(name2)}
4981
+ SELECT * FROM ${tableRef(sql2, name2)}
4926
4982
  WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
4927
4983
  LIMIT 1
4928
4984
  `;
@@ -4930,7 +4986,7 @@ function buildRouter(sql2, usersTable) {
4930
4986
  }
4931
4987
  const name = internalName(ctx);
4932
4988
  const [row] = await sql2`
4933
- UPDATE ${sql2(name)} SET ${sql2(parsed)}
4989
+ UPDATE ${tableRef(sql2, name)} SET ${sql2(parsed)}
4934
4990
  WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
4935
4991
  RETURNING *
4936
4992
  `;
@@ -4940,7 +4996,7 @@ function buildRouter(sql2, usersTable) {
4940
4996
  r.delete("/:_slug/:id", async (_req, ctx) => {
4941
4997
  const name = internalName(ctx);
4942
4998
  const result = await sql2`
4943
- DELETE FROM ${sql2(name)}
4999
+ DELETE FROM ${tableRef(sql2, name)}
4944
5000
  WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
4945
5001
  RETURNING 1
4946
5002
  `;
@@ -4981,7 +5037,7 @@ function buildRouter(sql2, usersTable) {
4981
5037
  [parentId2, ctx.tenant.id]
4982
5038
  )
4983
5039
  ]);
4984
- return Response.json({ rows, count: Number(countResult[0]?.count ?? 0) });
5040
+ return Response.json({ rows, count: extractCount(countResult) });
4985
5041
  }
4986
5042
  return Response.json({ error: "POST not supported on M2M nested routes" }, { status: 400 });
4987
5043
  }
@@ -4998,7 +5054,7 @@ function buildRouter(sql2, usersTable) {
4998
5054
  [parentId, ctx.tenant.id]
4999
5055
  )
5000
5056
  ]);
5001
- return Response.json({ rows, count: Number(countResult[0]?.count ?? 0) });
5057
+ return Response.json({ rows, count: extractCount(countResult) });
5002
5058
  }
5003
5059
  const body = await req.json();
5004
5060
  const shape = {};
@@ -5010,7 +5066,7 @@ function buildRouter(sql2, usersTable) {
5010
5066
  parsed.tenant_id = ctx.tenant.id;
5011
5067
  parsed[relField.name] = parentId;
5012
5068
  delete parsed.id;
5013
- const [row] = await sql2`INSERT INTO ${sql2(childName)} ${sql2(parsed)} RETURNING *`;
5069
+ const [row] = await sql2`INSERT INTO ${tableRef(sql2, childName)} ${sql2(parsed)} RETURNING *`;
5014
5070
  return Response.json(row, { status: 201 });
5015
5071
  }
5016
5072
  r.get("/:_slug/:id/:_nested", async (req, ctx) => handleNested(req, ctx, "GET"));
@@ -5080,7 +5136,6 @@ function buildObjectType(table, ctx) {
5080
5136
  if (other.id === table.id) continue;
5081
5137
  const relField = findRelation(other.fields, table.slug);
5082
5138
  if (relField) {
5083
- const otherName = pascalCase(other.slug);
5084
5139
  fields[other.slug] = {
5085
5140
  type: new GraphQLList(new GraphQLNonNull(buildObjectType(other, ctx))),
5086
5141
  args: {
@@ -5595,7 +5650,7 @@ async function loadAgent(agents, agentId) {
5595
5650
  return row ?? null;
5596
5651
  }
5597
5652
  function createRunner(deps) {
5598
- const { sql: sql2, agents, runs, provider, modelName, userTools } = deps;
5653
+ const { sql: sql2, agents, runs, provider, userTools } = deps;
5599
5654
  function truncate(s, max = 200) {
5600
5655
  return s.length > max ? s.slice(0, max) + "..." : s;
5601
5656
  }
@@ -5905,32 +5960,32 @@ function createWSHandler(deps) {
5905
5960
  prefix: "messager:"
5906
5961
  });
5907
5962
  const userConnections = /* @__PURE__ */ new Map();
5908
- function trackConnection(userId, ws) {
5909
- let conns = userConnections.get(userId);
5963
+ function trackConnection(userId2, ws) {
5964
+ let conns = userConnections.get(userId2);
5910
5965
  if (!conns) {
5911
5966
  conns = /* @__PURE__ */ new Set();
5912
- userConnections.set(userId, conns);
5967
+ userConnections.set(userId2, conns);
5913
5968
  }
5914
5969
  conns.add(ws);
5915
5970
  }
5916
5971
  function untrackConnection(ws) {
5917
- for (const [userId, conns] of userConnections) {
5972
+ for (const [userId2, conns] of userConnections) {
5918
5973
  conns.delete(ws);
5919
- if (conns.size === 0) userConnections.delete(userId);
5974
+ if (conns.size === 0) userConnections.delete(userId2);
5920
5975
  }
5921
5976
  }
5922
5977
  return {
5923
5978
  handler: {
5924
5979
  open(ws, ctx) {
5925
- const userId = ctx.user?.id;
5926
- if (!userId) {
5980
+ const userId2 = ctx.user?.id;
5981
+ if (!userId2) {
5927
5982
  ws.close(4001, "Unauthorized");
5928
5983
  return;
5929
5984
  }
5930
5985
  },
5931
5986
  async message(ws, ctx, data) {
5932
- const userId = ctx.user?.id;
5933
- if (!userId) return;
5987
+ const userId2 = ctx.user?.id;
5988
+ if (!userId2) return;
5934
5989
  let msg;
5935
5990
  try {
5936
5991
  msg = JSON.parse(data.toString());
@@ -5944,12 +5999,12 @@ function createWSHandler(deps) {
5944
5999
  if (!content || !channel_id) return;
5945
6000
  const [row] = await sql2`
5946
6001
  INSERT INTO "_messages" ("channel_id", "sender_id", "sender_type", "content")
5947
- VALUES (${channel_id}, ${userId}, 'user', ${content})
6002
+ VALUES (${channel_id}, ${userId2}, 'user', ${content})
5948
6003
  RETURNING *
5949
6004
  `;
5950
6005
  const message = row;
5951
6006
  hub.join(`messager:${channel_id}`, ws);
5952
- trackConnection(userId, ws);
6007
+ trackConnection(userId2, ws);
5953
6008
  broadcastToChannel(hub, channel_id, { type: "message", data: message });
5954
6009
  if (agents) {
5955
6010
  const insertMsg = (data2) => sql2`
@@ -5968,7 +6023,7 @@ function createWSHandler(deps) {
5968
6023
  broadcastToChannel(hub, channel_id, {
5969
6024
  type: "typing",
5970
6025
  channel_id,
5971
- user_id: userId,
6026
+ user_id: userId2,
5972
6027
  is_typing: is_typing ?? false
5973
6028
  });
5974
6029
  break;
@@ -5979,12 +6034,12 @@ function createWSHandler(deps) {
5979
6034
  await sql2`
5980
6035
  UPDATE "_channel_members"
5981
6036
  SET last_read_id = ${last_message_id}, last_read_at = NOW()
5982
- WHERE channel_id = ${channel_id} AND member_id = ${userId} AND member_type = 'user'
6037
+ WHERE channel_id = ${channel_id} AND member_id = ${userId2} AND member_type = 'user'
5983
6038
  `;
5984
6039
  broadcastToChannel(hub, channel_id, {
5985
6040
  type: "read",
5986
6041
  channel_id,
5987
- user_id: userId,
6042
+ user_id: userId2,
5988
6043
  last_message_id
5989
6044
  });
5990
6045
  break;
@@ -6038,7 +6093,7 @@ function buildRouter3(deps) {
6038
6093
  return Response.json(channel, { status: 201 });
6039
6094
  });
6040
6095
  r.get("/channels", async (_req, ctx) => {
6041
- const userId = ctx.user?.id ?? 1;
6096
+ const userId2 = ctx.user?.id ?? 1;
6042
6097
  const rows = await sql2`
6043
6098
  SELECT c.*, (
6044
6099
  SELECT content FROM "_messages"
@@ -6047,7 +6102,7 @@ function buildRouter3(deps) {
6047
6102
  ) AS last_message
6048
6103
  FROM "_channels" c
6049
6104
  JOIN "_channel_members" m ON m.channel_id = c.id
6050
- WHERE m.member_id = ${userId} AND m.member_type = 'user'
6105
+ WHERE m.member_id = ${userId2} AND m.member_type = 'user'
6051
6106
  ORDER BY c.created_at DESC
6052
6107
  `;
6053
6108
  return Response.json(rows);
@@ -6120,9 +6175,9 @@ function buildRouter3(deps) {
6120
6175
  r.post("/channels/:id/read", async (req, ctx) => {
6121
6176
  const channelId = parseInt(ctx.params.id, 10);
6122
6177
  const body = await req.json();
6123
- const userId = body.user_id ?? ctx.user?.id ?? 1;
6178
+ const userId2 = body.user_id ?? ctx.user?.id ?? 1;
6124
6179
  await members.updateMany(
6125
- [eq("channel_id", channelId), eq("member_id", userId), eq("member_type", "user")],
6180
+ [eq("channel_id", channelId), eq("member_id", userId2), eq("member_type", "user")],
6126
6181
  { last_read_id: body.last_message_id }
6127
6182
  );
6128
6183
  return Response.json({ ok: true });
@@ -6294,7 +6349,7 @@ function createGateway(config, getPort) {
6294
6349
  }
6295
6350
 
6296
6351
  // deploy/manager.ts
6297
- import crypto6 from "node:crypto";
6352
+ import crypto7 from "node:crypto";
6298
6353
 
6299
6354
  // deploy/process.ts
6300
6355
  import { fork } from "node:child_process";
@@ -6347,27 +6402,27 @@ async function healthCheck(port, path2 = "/") {
6347
6402
  // deploy/manager.ts
6348
6403
  function createManager(config, apps, manager) {
6349
6404
  const router = new Router();
6350
- const auth2 = (req, ctx, next) => {
6405
+ const auth = (req, ctx, next) => {
6351
6406
  if (!config.deployToken) return next(req, ctx);
6352
6407
  const header = req.headers.get("authorization") ?? "";
6353
6408
  const token = header.replace("Bearer ", "");
6354
6409
  const tokenBuf = Buffer.from(token);
6355
6410
  const secretBuf = Buffer.from(config.deployToken);
6356
- if (tokenBuf.length !== secretBuf.length || !crypto6.timingSafeEqual(tokenBuf, secretBuf)) {
6411
+ if (tokenBuf.length !== secretBuf.length || !crypto7.timingSafeEqual(tokenBuf, secretBuf)) {
6357
6412
  return Response.json({ error: "Unauthorized" }, { status: 401 });
6358
6413
  }
6359
6414
  return next(req, ctx);
6360
6415
  };
6361
- router.get("/apps", auth2, () => {
6416
+ router.get("/apps", auth, () => {
6362
6417
  const list = Array.from(apps.values()).map((a) => a.status);
6363
6418
  return Response.json(list);
6364
6419
  });
6365
- router.get("/apps/:name", auth2, (req, ctx) => {
6420
+ router.get("/apps/:name", auth, (req, ctx) => {
6366
6421
  const app = apps.get(ctx.params.name);
6367
6422
  if (!app) return new Response("Not Found", { status: 404 });
6368
6423
  return Response.json(app.status);
6369
6424
  });
6370
- router.post("/apps/:name/deploy", auth2, async (req, ctx) => {
6425
+ router.post("/apps/:name/deploy", auth, async (req, ctx) => {
6371
6426
  const app = apps.get(ctx.params.name);
6372
6427
  if (!app) return new Response("Not Found", { status: 404 });
6373
6428
  try {
@@ -6378,7 +6433,7 @@ function createManager(config, apps, manager) {
6378
6433
  return Response.json({ error: msg }, { status: 500 });
6379
6434
  }
6380
6435
  });
6381
- router.post("/apps/:name/restart", auth2, async (req, ctx) => {
6436
+ router.post("/apps/:name/restart", auth, async (req, ctx) => {
6382
6437
  const app = apps.get(ctx.params.name);
6383
6438
  if (!app) return new Response("Not Found", { status: 404 });
6384
6439
  try {
@@ -6389,7 +6444,7 @@ function createManager(config, apps, manager) {
6389
6444
  return Response.json({ error: msg }, { status: 500 });
6390
6445
  }
6391
6446
  });
6392
- router.post("/apps/:name/stop", auth2, async (req, ctx) => {
6447
+ router.post("/apps/:name/stop", auth, async (req, ctx) => {
6393
6448
  const app = apps.get(ctx.params.name);
6394
6449
  if (!app) return new Response("Not Found", { status: 404 });
6395
6450
  if (app.process) {
@@ -6399,7 +6454,7 @@ function createManager(config, apps, manager) {
6399
6454
  app.status = { ...app.status, status: "stopped", pid: void 0 };
6400
6455
  return Response.json({ success: true });
6401
6456
  });
6402
- router.post("/apps/:name/start", auth2, async (req, ctx) => {
6457
+ router.post("/apps/:name/start", auth, async (req, ctx) => {
6403
6458
  const app = apps.get(ctx.params.name);
6404
6459
  if (!app) return new Response("Not Found", { status: 404 });
6405
6460
  try {
@@ -6410,7 +6465,7 @@ function createManager(config, apps, manager) {
6410
6465
  return Response.json({ error: msg }, { status: 500 });
6411
6466
  }
6412
6467
  });
6413
- router.get("/apps/:name/logs", auth2, (req, ctx) => {
6468
+ router.get("/apps/:name/logs", auth, (req, ctx) => {
6414
6469
  const app = apps.get(ctx.params.name);
6415
6470
  if (!app) return new Response("Not Found", { status: 404 });
6416
6471
  let index = app.logs.length;
@@ -6462,9 +6517,9 @@ function defineConfig(config) {
6462
6517
  async function deploy(config) {
6463
6518
  const apps = /* @__PURE__ */ new Map();
6464
6519
  let httpServer;
6465
- async function forkAndCheck(name, cwd, entry, port, env, onLog, healthEndpoint) {
6520
+ async function forkAndCheck(name, cwd, entry, port, env2, onLog, healthEndpoint) {
6466
6521
  try {
6467
- const mp = forkApp({ cwd, entry, port, env, onLog });
6522
+ const mp = forkApp({ cwd, entry, port, env: env2, onLog });
6468
6523
  onLog(`[deploy] forked ${name} (pid ${mp.child.pid}) on port ${mp.port}`);
6469
6524
  const healthy = await healthCheck(port, healthEndpoint ?? "/");
6470
6525
  if (healthy) onLog(`[deploy] health check passed`);
@@ -6903,7 +6958,7 @@ async function compileHotComponent(path2) {
6903
6958
  // stream.ts
6904
6959
  import { TextDecoder as TextDecoder2, TextEncoder as TextEncoder2 } from "node:util";
6905
6960
  var _publicEnv = null;
6906
- function getPublicEnv() {
6961
+ function getPublicEnv2() {
6907
6962
  if (_publicEnv) return _publicEnv;
6908
6963
  _publicEnv = {};
6909
6964
  for (const key of Object.keys(process.env)) {
@@ -6954,7 +7009,7 @@ function buildHeadPayload(opts) {
6954
7009
  }
6955
7010
  ctxData.user = safeUser;
6956
7011
  }
6957
- const publicEnv = getPublicEnv();
7012
+ const publicEnv = getPublicEnv2();
6958
7013
  if (Object.keys(publicEnv).length > 0) {
6959
7014
  ctxData.env = publicEnv;
6960
7015
  }
@@ -7312,8 +7367,9 @@ function errorBoundary(errorPath) {
7312
7367
  const mod = await compile(errorPath);
7313
7368
  const ErrorComponent = mod.default;
7314
7369
  if (!ErrorComponent) throw err;
7315
- const layouts = (ctx.layoutStack || []).map((l) => l.component);
7316
- const base = (ctx.mountPath || "").replace(/\/$/, "");
7370
+ const ctx2 = ctx;
7371
+ const layouts = (ctx2.layoutStack || []).map((l) => l.component);
7372
+ const base = (ctx2.mountPath || "").replace(/\/$/, "");
7317
7373
  let element = createElement2(ErrorComponent, {
7318
7374
  error: err instanceof Error ? err : new Error(String(err)),
7319
7375
  reset: () => {
@@ -7323,10 +7379,10 @@ function errorBoundary(errorPath) {
7323
7379
  const { renderToReadableStream } = await import("react-dom/server");
7324
7380
  const stream = await renderToReadableStream(element);
7325
7381
  return streamResponse(stream, {
7326
- ctx,
7382
+ ctx: ctx2,
7327
7383
  base,
7328
7384
  isDev: isDev(),
7329
- tailwind: ctx.tailwind,
7385
+ tailwind: ctx2.tailwind,
7330
7386
  status: 500
7331
7387
  });
7332
7388
  }
@@ -7662,7 +7718,7 @@ function ssr(opts) {
7662
7718
  }
7663
7719
 
7664
7720
  // opencode/session.ts
7665
- import { randomUUID as randomUUID3 } from "node:crypto";
7721
+ import { randomUUID as randomUUID2 } from "node:crypto";
7666
7722
  import { join as join6 } from "node:path";
7667
7723
  import { mkdir as mkdir2 } from "node:fs/promises";
7668
7724
  var sessions = pgTable("_opencode_sessions", {
@@ -7688,7 +7744,7 @@ var messages = pgTable("_opencode_messages", {
7688
7744
  created_at: timestamptz("created_at")
7689
7745
  });
7690
7746
  async function createSession(sql2, opts, cwd, mountPath) {
7691
- const id2 = randomUUID3();
7747
+ const id2 = randomUUID2();
7692
7748
  const ws = computeSessionWorkspace(cwd, mountPath, id2);
7693
7749
  await mkdir2(ws, { recursive: true });
7694
7750
  const [row] = await sql2`
@@ -7706,10 +7762,10 @@ async function getSession(sql2, id2) {
7706
7762
  const { data: rows } = await sessions.readMany(sql2, { id: id2, active: true });
7707
7763
  return rows[0] ?? null;
7708
7764
  }
7709
- async function listSessions(sql2, userId) {
7765
+ async function listSessions(sql2, userId2) {
7710
7766
  const opts = { orderBy: { updated_at: "desc" } };
7711
- if (userId !== void 0) {
7712
- const { data: rows2 } = await sessions.readMany(sql2, { user_id: userId, active: true }, opts);
7767
+ if (userId2 !== void 0) {
7768
+ const { data: rows2 } = await sessions.readMany(sql2, { user_id: userId2, active: true }, opts);
7713
7769
  return rows2;
7714
7770
  }
7715
7771
  const { data: rows } = await sessions.readMany(sql2, { active: true }, opts);
@@ -8330,9 +8386,9 @@ function createWSHandler2(deps) {
8330
8386
  const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions: permissions2, pendingQuestions } = deps;
8331
8387
  return {
8332
8388
  open(ws, ctx) {
8333
- const userId = ctx.user?.id ?? 0;
8389
+ const userId2 = ctx.user?.id ?? 0;
8334
8390
  const mountPath = ctx.mountPath ?? "";
8335
- clients2.set(ws, { userId, mountPath });
8391
+ clients2.set(ws, { userId: userId2, mountPath });
8336
8392
  },
8337
8393
  async message(ws, ctx, data) {
8338
8394
  const client = clients2.get(ws);
@@ -8841,7 +8897,7 @@ function analytics(options) {
8841
8897
  if (pg) await migratePg(pg.sql, pg.table);
8842
8898
  };
8843
8899
  const close = async () => {
8844
- if (store2) store2.stopCleanup();
8900
+ store2?.stopCleanup();
8845
8901
  };
8846
8902
  const mod = r;
8847
8903
  mod.middleware = middleware;
@@ -8860,30 +8916,37 @@ function makeSetTheme(cookie, location) {
8860
8916
  }
8861
8917
  function theme(options) {
8862
8918
  const opts = { default: "system", cookie: "theme", ...options };
8863
- return async (req, ctx, next) => {
8864
- const url = new URL(req.url);
8865
- const match = url.pathname.match(/^\/__theme\/([\w-]+)$/);
8866
- if (match && req.method === "GET") {
8867
- const value = match[1];
8868
- const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
8869
- const accept = req.headers.get("accept") ?? "";
8870
- if (accept.includes("application/json")) {
8871
- return Response.json({ ok: true, theme: value }, { headers: { "Set-Cookie": cookie } });
8872
- }
8873
- const referer = req.headers.get("referer") || "/";
8874
- return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
8875
- }
8919
+ const mw = async (req, ctx, next) => {
8876
8920
  let themeValue = opts.default;
8877
8921
  if (opts.cookie) {
8878
8922
  const fromCookie = getCookies(req)[opts.cookie];
8879
8923
  if (fromCookie) themeValue = fromCookie;
8880
8924
  }
8925
+ ;
8881
8926
  ctx.theme = {
8882
8927
  value: themeValue,
8883
8928
  set: makeSetTheme(opts.cookie, req.headers.get("referer") || "/")
8884
8929
  };
8885
8930
  return next(req, ctx);
8886
8931
  };
8932
+ class ThemeRouter extends Router {
8933
+ middleware() {
8934
+ return mw;
8935
+ }
8936
+ }
8937
+ const router = new ThemeRouter();
8938
+ router.get("/__theme/:value", (req) => {
8939
+ const url = new URL(req.url);
8940
+ const value = url.pathname.split("/__theme/")[1] ?? "";
8941
+ const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
8942
+ const accept = req.headers.get("accept") ?? "";
8943
+ if (accept.includes("application/json")) {
8944
+ return Response.json({ ok: true, theme: value }, { headers: { "Set-Cookie": cookie } });
8945
+ }
8946
+ const referer = req.headers.get("referer") || "/";
8947
+ return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
8948
+ });
8949
+ return router;
8887
8950
  }
8888
8951
 
8889
8952
  // i18n.ts
@@ -8949,23 +9012,7 @@ function i18n(options) {
8949
9012
  }
8950
9013
  return opts.default;
8951
9014
  }
8952
- return async (req, ctx, next) => {
8953
- const url = new URL(req.url);
8954
- const match = url.pathname.match(/^\/__lang\/([\w-]+)$/);
8955
- if (match && req.method === "GET") {
8956
- const value = match[1];
8957
- const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
8958
- const messages2 = await loadMessages(value);
8959
- const accept = req.headers.get("accept") ?? "";
8960
- if (accept.includes("application/json")) {
8961
- return Response.json(
8962
- { ok: true, locale: value, messages: Object.keys(messages2).length > 0 ? messages2 : void 0 },
8963
- { headers: { "Set-Cookie": cookie } }
8964
- );
8965
- }
8966
- const referer = req.headers.get("referer") || "/";
8967
- return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
8968
- }
9015
+ const mw = async (req, ctx, next) => {
8969
9016
  const locale = detectLocale(req);
8970
9017
  const msgs = await loadMessages(locale);
8971
9018
  ctx.i18n = {
@@ -8980,6 +9027,28 @@ function i18n(options) {
8980
9027
  };
8981
9028
  return next(req, ctx);
8982
9029
  };
9030
+ class I18nRouter extends Router {
9031
+ middleware() {
9032
+ return mw;
9033
+ }
9034
+ }
9035
+ const router = new I18nRouter();
9036
+ router.get("/__lang/:locale", async (req) => {
9037
+ const url = new URL(req.url);
9038
+ const value = url.pathname.split("/__lang/")[1] ?? "";
9039
+ const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
9040
+ const messages2 = await loadMessages(value);
9041
+ const accept = req.headers.get("accept") ?? "";
9042
+ if (accept.includes("application/json")) {
9043
+ return Response.json(
9044
+ { ok: true, locale: value, messages: Object.keys(messages2).length > 0 ? messages2 : void 0 },
9045
+ { headers: { "Set-Cookie": cookie } }
9046
+ );
9047
+ }
9048
+ const referer = req.headers.get("referer") || "/";
9049
+ return new Response(null, { status: 302, headers: { Location: referer, "Set-Cookie": cookie } });
9050
+ });
9051
+ return router;
8983
9052
  }
8984
9053
 
8985
9054
  // flash.ts
@@ -9209,13 +9278,13 @@ function csrf(options) {
9209
9278
  let token = getCookies(req)[cookieName];
9210
9279
  if (!token) {
9211
9280
  token = crypto.randomUUID();
9212
- ctx.csrfToken = token;
9281
+ ctx.csrf = { token };
9213
9282
  } else {
9214
9283
  ;
9215
- ctx.csrfToken = token;
9284
+ ctx.csrf = { token };
9216
9285
  }
9217
9286
  const res = await next(req, ctx);
9218
- const tokenToSet = ctx.csrfToken;
9287
+ const tokenToSet = ctx.csrf?.token;
9219
9288
  if (tokenToSet && !getCookies(req)[cookieName]) {
9220
9289
  return setCookie(res, cookieName, tokenToSet, {
9221
9290
  httpOnly: true,
@@ -9400,7 +9469,7 @@ function logdb(options) {
9400
9469
  }
9401
9470
 
9402
9471
  // iii/client.ts
9403
- import crypto7 from "node:crypto";
9472
+ import crypto8 from "node:crypto";
9404
9473
 
9405
9474
  // iii/stream.ts
9406
9475
  function notify(channels, stream, group, item, event, data) {
@@ -9929,7 +9998,7 @@ function iii(opts = {}) {
9929
9998
  registerBuiltin("stream::send", (p) => stream.send(p.stream_name, p.group_id, p.type, p.data, p.id));
9930
9999
  registerBuiltin("stream::update", (p) => stream.update(p.stream_name, p.group_id, p.item_id, p.ops));
9931
10000
  function addLocalWorker(worker) {
9932
- const workerId = crypto7.randomUUID();
10001
+ const workerId = crypto8.randomUUID();
9933
10002
  const reg = {
9934
10003
  id: workerId,
9935
10004
  name: worker.name,
@@ -9944,7 +10013,7 @@ function iii(opts = {}) {
9944
10013
  const triggerIds = [];
9945
10014
  for (const t of worker.getTriggers()) {
9946
10015
  if (t.input.function_id === fn.id) {
9947
- const tid = crypto7.randomUUID();
10016
+ const tid = crypto8.randomUUID();
9948
10017
  triggers.set(tid, {
9949
10018
  id: tid,
9950
10019
  type: t.input.type,
@@ -9973,7 +10042,7 @@ function iii(opts = {}) {
9973
10042
  if (!worker) return;
9974
10043
  const handler = async (payload) => {
9975
10044
  if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
9976
- const invocationId = crypto7.randomUUID();
10045
+ const invocationId = crypto8.randomUUID();
9977
10046
  return new Promise((resolve14, reject) => {
9978
10047
  const timer = setTimeout(() => {
9979
10048
  pending.delete(invocationId);
@@ -10008,7 +10077,7 @@ function iii(opts = {}) {
10008
10077
  let engineRef = null;
10009
10078
  const wsHandler = createWsHandler({
10010
10079
  registerRemoteWorker(ws, name) {
10011
- const id2 = crypto7.randomUUID();
10080
+ const id2 = crypto8.randomUUID();
10012
10081
  workers.set(id2, { id: id2, name, ws, functions: [], triggers: [] });
10013
10082
  return id2;
10014
10083
  },
@@ -10019,7 +10088,7 @@ function iii(opts = {}) {
10019
10088
  addRemoteFunction(workerId, id2);
10020
10089
  },
10021
10090
  registerRemoteTrigger(workerId, input) {
10022
- const tid = crypto7.randomUUID();
10091
+ const tid = crypto8.randomUUID();
10023
10092
  const reg = { id: tid, ...input, workerId };
10024
10093
  triggers.set(tid, reg);
10025
10094
  const worker = workers.get(workerId);
@@ -10143,7 +10212,7 @@ function iii(opts = {}) {
10143
10212
  mod.migrate = async () => {
10144
10213
  await stream.migrate();
10145
10214
  };
10146
- mod.shutdown = async () => {
10215
+ mod.close = async () => {
10147
10216
  for (const [, p] of pending) {
10148
10217
  clearTimeout(p.timer);
10149
10218
  p.reject(new Error("Engine shutting down"));
@@ -10359,7 +10428,7 @@ function registerWorker(url) {
10359
10428
  onStream(handler) {
10360
10429
  handlers.set("__stream__", handler);
10361
10430
  },
10362
- shutdown() {
10431
+ close() {
10363
10432
  intentionalClose = true;
10364
10433
  if (reconnectTimer) clearTimeout(reconnectTimer);
10365
10434
  ws?.close();
@@ -10369,7 +10438,7 @@ function registerWorker(url) {
10369
10438
  }
10370
10439
 
10371
10440
  // session.ts
10372
- import crypto8 from "node:crypto";
10441
+ import crypto9 from "node:crypto";
10373
10442
  var kSaved = /* @__PURE__ */ Symbol("session.saved");
10374
10443
  var kDestroyed = /* @__PURE__ */ Symbol("session.destroyed");
10375
10444
  var kId = /* @__PURE__ */ Symbol("session.id");
@@ -10408,7 +10477,7 @@ var MemoryStore = class {
10408
10477
  if (entry.expires < now) this.store.delete(key);
10409
10478
  }
10410
10479
  }
10411
- close() {
10480
+ async close() {
10412
10481
  clearInterval(this.interval);
10413
10482
  this.store.clear();
10414
10483
  }
@@ -10444,10 +10513,13 @@ var RedisStore = class {
10444
10513
  async destroy(sid) {
10445
10514
  await this.redis.del(this.key(sid));
10446
10515
  }
10516
+ async close() {
10517
+ this.redis.disconnect();
10518
+ }
10447
10519
  };
10448
10520
  var COOKIE_SEPARATOR = ".";
10449
10521
  function signSessionId(sid, secret) {
10450
- const hmac = crypto8.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
10522
+ const hmac = crypto9.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
10451
10523
  return sid + COOKIE_SEPARATOR + hmac;
10452
10524
  }
10453
10525
  function unsignSessionId(value, secret) {
@@ -10455,10 +10527,10 @@ function unsignSessionId(value, secret) {
10455
10527
  if (dot === -1) return null;
10456
10528
  const sid = value.slice(0, dot);
10457
10529
  const sig = value.slice(dot + 1);
10458
- const expected = crypto8.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
10530
+ const expected = crypto9.createHmac("sha256", secret).update(sid).digest("base64url").slice(0, 16);
10459
10531
  if (sig.length !== expected.length) return null;
10460
10532
  try {
10461
- return crypto8.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ? sid : null;
10533
+ return crypto9.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ? sid : null;
10462
10534
  } catch {
10463
10535
  return null;
10464
10536
  }
@@ -10517,6 +10589,7 @@ function session(options) {
10517
10589
  } else if (options?.store === "redis") {
10518
10590
  if (!options.redis) throw new Error('session: redis client required when store: "redis"');
10519
10591
  store2 = new RedisStore(options.redis);
10592
+ closeStore = () => store2.close();
10520
10593
  } else {
10521
10594
  const mem = new MemoryStore();
10522
10595
  store2 = mem;
@@ -10546,11 +10619,11 @@ function session(options) {
10546
10619
  }
10547
10620
  } else {
10548
10621
  loadedSid = null;
10549
- session2 = createSessionObject({}, crypto8.randomUUID(), store2, ttl, Date.now());
10622
+ session2 = createSessionObject({}, crypto9.randomUUID(), store2, ttl, Date.now());
10550
10623
  }
10551
10624
  } else {
10552
10625
  loadedSid = null;
10553
- session2 = createSessionObject({}, crypto8.randomUUID(), store2, ttl, Date.now());
10626
+ session2 = createSessionObject({}, crypto9.randomUUID(), store2, ttl, Date.now());
10554
10627
  }
10555
10628
  const snapshot = isSessionActive(session2) ? JSON.stringify(session2) : null;
10556
10629
  ctx.session = session2;
@@ -10563,7 +10636,7 @@ function session(options) {
10563
10636
  return deleteCookie(res, cookieName, cookieOpts);
10564
10637
  }
10565
10638
  if (needsRotation && loadedSid) {
10566
- const newId = crypto8.randomUUID();
10639
+ const newId = crypto9.randomUUID();
10567
10640
  const data = JSON.parse(JSON.stringify(currentSession));
10568
10641
  data[kCreatedAt] = Date.now();
10569
10642
  await store2.set(newId, data, ttl);
@@ -10596,15 +10669,15 @@ function session(options) {
10596
10669
  }
10597
10670
  return res;
10598
10671
  });
10599
- mw.close = () => {
10600
- closeStore?.();
10672
+ mw.close = async () => {
10673
+ await closeStore?.();
10601
10674
  };
10602
10675
  mw.store = store2;
10603
10676
  return mw;
10604
10677
  }
10605
10678
 
10606
10679
  // cache.ts
10607
- import crypto9 from "node:crypto";
10680
+ import crypto10 from "node:crypto";
10608
10681
  var BINARY_PREFIXES = [
10609
10682
  "image/",
10610
10683
  "audio/",
@@ -10624,7 +10697,7 @@ function isCacheableStatus(status, allowed) {
10624
10697
  return allowed.includes(status);
10625
10698
  }
10626
10699
  function defaultCacheKey(req) {
10627
- const hash = crypto9.createHash("sha256");
10700
+ const hash = crypto10.createHash("sha256");
10628
10701
  hash.update(req.method);
10629
10702
  hash.update(req.url);
10630
10703
  return hash.digest("hex");
@@ -10691,7 +10764,7 @@ var MemoryCache = class {
10691
10764
  }
10692
10765
  }
10693
10766
  }
10694
- close() {
10767
+ async close() {
10695
10768
  clearInterval(this.interval);
10696
10769
  this.store.clear();
10697
10770
  this.tagIndex.clear();
@@ -10832,17 +10905,17 @@ function cache2(options) {
10832
10905
  mw.store = store2;
10833
10906
  mw.invalidate = async (tag) => store2.invalidate(tag);
10834
10907
  mw.flush = async () => store2.flush();
10835
- mw.close = () => {
10836
- closeStore?.();
10908
+ mw.close = async () => {
10909
+ await closeStore?.();
10837
10910
  };
10838
10911
  return mw;
10839
10912
  }
10840
10913
 
10841
10914
  // webhook.ts
10842
- import crypto10 from "node:crypto";
10915
+ import crypto11 from "node:crypto";
10843
10916
  function timingSafeEqual2(a, b) {
10844
10917
  try {
10845
- return crypto10.timingSafeEqual(Buffer.from(a), Buffer.from(b));
10918
+ return crypto11.timingSafeEqual(Buffer.from(a), Buffer.from(b));
10846
10919
  } catch {
10847
10920
  return false;
10848
10921
  }
@@ -10860,7 +10933,7 @@ function createStripeVerifier(config) {
10860
10933
  const signature = parts["v1"];
10861
10934
  if (!timestamp || !signature) return { valid: false, provider: "stripe", event: "", id: void 0 };
10862
10935
  const signed = `${timestamp}.${body}`;
10863
- const expected = crypto10.createHmac("sha256", config.secret).update(signed).digest("hex");
10936
+ const expected = crypto11.createHmac("sha256", config.secret).update(signed).digest("hex");
10864
10937
  const valid = timingSafeEqual2(signature, expected);
10865
10938
  let event = "";
10866
10939
  let id2;
@@ -10877,7 +10950,7 @@ function createGitHubVerifier(config) {
10877
10950
  return (body, headers) => {
10878
10951
  const sig = headers["x-hub-signature-256"];
10879
10952
  if (!sig) return { valid: false, provider: "github", event: "", id: void 0 };
10880
- const expected = `sha256=${crypto10.createHmac("sha256", config.secret).update(body).digest("hex")}`;
10953
+ const expected = `sha256=${crypto11.createHmac("sha256", config.secret).update(body).digest("hex")}`;
10881
10954
  const valid = timingSafeEqual2(sig, expected);
10882
10955
  let event = headers["x-github-event"] ?? "";
10883
10956
  let id2;
@@ -10900,7 +10973,7 @@ function createSlackVerifier(config) {
10900
10973
  return { valid: false, provider: "slack", event: "", id: void 0 };
10901
10974
  }
10902
10975
  const sigBase = `v0:${timestamp}:${body}`;
10903
- const expected = `v0=${crypto10.createHmac("sha256", config.secret).update(sigBase).digest("hex")}`;
10976
+ const expected = `v0=${crypto11.createHmac("sha256", config.secret).update(sigBase).digest("hex")}`;
10904
10977
  const valid = timingSafeEqual2(signature, expected);
10905
10978
  let event = "";
10906
10979
  let id2;
@@ -11417,17 +11490,17 @@ function permissions(options) {
11417
11490
  );
11418
11491
  return created.id;
11419
11492
  }
11420
- async function assignRole(userId, role) {
11493
+ async function assignRole(userId2, role) {
11421
11494
  const roleId = await ensureRole(role);
11422
11495
  await sql2.unsafe(
11423
11496
  `INSERT INTO ${escapeIdent6(userRolesTable)} (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
11424
- [userId, roleId]
11497
+ [userId2, roleId]
11425
11498
  );
11426
11499
  }
11427
- async function removeRole(userId, role) {
11500
+ async function removeRole(userId2, role) {
11428
11501
  await sql2.unsafe(
11429
11502
  `DELETE FROM ${escapeIdent6(userRolesTable)} WHERE user_id = $1 AND role_id = (SELECT id FROM ${escapeIdent6(rolesTable)} WHERE name = $2)`,
11430
- [userId, role]
11503
+ [userId2, role]
11431
11504
  );
11432
11505
  }
11433
11506
  async function grantPermission(role, permission) {
@@ -11443,31 +11516,31 @@ function permissions(options) {
11443
11516
  [role, permission]
11444
11517
  );
11445
11518
  }
11446
- async function getUserRoles(userId) {
11519
+ async function getUserRoles(userId2) {
11447
11520
  const rows = await sql2.unsafe(
11448
11521
  `SELECT r.name FROM ${escapeIdent6(userRolesTable)} ur
11449
11522
  JOIN ${escapeIdent6(rolesTable)} r ON r.id = ur.role_id
11450
11523
  WHERE ur.user_id = $1 ORDER BY r.name`,
11451
- [userId]
11524
+ [userId2]
11452
11525
  );
11453
11526
  return rows.map((r) => r.name);
11454
11527
  }
11455
- async function getUserPermissions(userId) {
11528
+ async function getUserPermissions(userId2) {
11456
11529
  const rows = await sql2.unsafe(
11457
11530
  `SELECT DISTINCT rp.permission FROM ${escapeIdent6(userRolesTable)} ur
11458
11531
  JOIN ${escapeIdent6(rolePermsTable)} rp ON rp.role_id = ur.role_id
11459
11532
  WHERE ur.user_id = $1 ORDER BY rp.permission`,
11460
- [userId]
11533
+ [userId2]
11461
11534
  );
11462
11535
  return rows.map((r) => r.permission);
11463
11536
  }
11464
11537
  const mw = (async (req, ctx, next) => {
11465
- const userId = ctx.user?.id;
11538
+ const userId2 = ctx.user?.id;
11466
11539
  let roles = /* @__PURE__ */ new Set();
11467
11540
  let perms = /* @__PURE__ */ new Set();
11468
- if (userId) {
11469
- const userRoles = await getUserRoles(userId);
11470
- const userPerms = userId ? await getUserPermissions(userId) : [];
11541
+ if (userId2) {
11542
+ const userRoles = await getUserRoles(userId2);
11543
+ const userPerms = userId2 ? await getUserPermissions(userId2) : [];
11471
11544
  roles = new Set(userRoles);
11472
11545
  perms = new Set(userPerms);
11473
11546
  const hasWildcard = userPerms.includes("*");
@@ -11534,7 +11607,6 @@ export {
11534
11607
  aiProvider,
11535
11608
  aiStream,
11536
11609
  analytics,
11537
- auth,
11538
11610
  cache2 as cache,
11539
11611
  compress,
11540
11612
  cors,
@@ -11552,6 +11624,7 @@ export {
11552
11624
  deploy,
11553
11625
  embed,
11554
11626
  embedMany,
11627
+ env,
11555
11628
  flash,
11556
11629
  formatSSE,
11557
11630
  formatSSEData,
@@ -11559,6 +11632,7 @@ export {
11559
11632
  generateObject,
11560
11633
  generateText2 as generateText,
11561
11634
  getCookies,
11635
+ getPublicEnv,
11562
11636
  graphql,
11563
11637
  health,
11564
11638
  helmet,
@@ -11600,6 +11674,7 @@ export {
11600
11674
  testApp,
11601
11675
  theme,
11602
11676
  tool2 as tool,
11677
+ trace,
11603
11678
  traceElapsed,
11604
11679
  upload,
11605
11680
  user,