weifuwu 0.9.5 → 0.9.6

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/ai.d.ts CHANGED
@@ -1,7 +1,19 @@
1
- import { streamText } from 'ai';
2
1
  import type { Context } from './types.ts';
3
2
  import { Router } from './router.ts';
4
- type StreamTextParams = Parameters<typeof streamText>[0];
3
+ type StreamTextParams = {
4
+ model: unknown;
5
+ prompt?: string;
6
+ system?: string;
7
+ messages?: unknown[];
8
+ maxTokens?: number;
9
+ temperature?: number;
10
+ [key: string]: unknown;
11
+ };
5
12
  export type AIHandler = (req: Request, ctx: Context) => StreamTextParams | Promise<StreamTextParams>;
6
- export declare function ai(handler: AIHandler): Router;
13
+ export declare const _ai: {
14
+ streamText: (params: StreamTextParams) => {
15
+ toTextStreamResponse: () => Response;
16
+ };
17
+ };
18
+ export declare function ai(handler: AIHandler): Promise<Router>;
7
19
  export {};
package/dist/index.js CHANGED
@@ -41,7 +41,12 @@ function createRequest(req, body) {
41
41
  async function sendResponse(res, response) {
42
42
  const headers = {};
43
43
  response.headers.forEach((value, key) => {
44
- headers[key] = value;
44
+ if (key.toLowerCase() === "set-cookie") {
45
+ const existing = headers[key];
46
+ headers[key] = existing ? Array.isArray(existing) ? [...existing, value] : [existing, value] : value;
47
+ } else {
48
+ headers[key] = value;
49
+ }
45
50
  });
46
51
  res.writeHead(response.status, response.statusText, headers);
47
52
  if (response.body) {
@@ -104,6 +109,10 @@ function serve(handler, options) {
104
109
  server.close();
105
110
  }, { once: true });
106
111
  }
112
+ server.on("error", (err) => {
113
+ console.error("Failed to start server:", err.message);
114
+ process.exit(1);
115
+ });
107
116
  server.listen(port, hostname, () => {
108
117
  resolveReady();
109
118
  });
@@ -167,6 +176,10 @@ var matchTrieNode = (node, segment, params) => {
167
176
  return null;
168
177
  };
169
178
  var getWsNode = (node, segment) => {
179
+ if (segment === "*") {
180
+ node.wildcard = true;
181
+ return node;
182
+ }
170
183
  if (segment.startsWith(":")) {
171
184
  if (!node.children.has(":")) {
172
185
  const child2 = createWsNode();
@@ -193,6 +206,7 @@ var matchWsNode = (node, segment, params) => {
193
206
  if (child.param) params[child.param] = segment;
194
207
  return child;
195
208
  }
209
+ if (node.wildcard) return node;
196
210
  return null;
197
211
  };
198
212
  var Router = class _Router {
@@ -200,6 +214,7 @@ var Router = class _Router {
200
214
  wsRoot = createWsNode();
201
215
  globalMws = [];
202
216
  errorHandler;
217
+ wss = new WebSocketServer({ noServer: true });
203
218
  use(arg1, arg2) {
204
219
  if (typeof arg1 === "string") {
205
220
  if (arg2 instanceof _Router) {
@@ -255,6 +270,10 @@ var Router = class _Router {
255
270
  let node = this.root;
256
271
  for (const segment of segments) {
257
272
  if (segment === "*") {
273
+ const remaining = segments.indexOf("*") < segments.length - 1;
274
+ if (remaining) {
275
+ console.warn(`Route "${path2}": segments after "*" are ignored`);
276
+ }
258
277
  node.wildcard = true;
259
278
  node.handlers.set(method, handler);
260
279
  if (middlewares.length > 0) node.middlewares.set(method, middlewares);
@@ -285,7 +304,6 @@ var Router = class _Router {
285
304
  };
286
305
  }
287
306
  websocketHandler() {
288
- const wss = new WebSocketServer({ noServer: true });
289
307
  const wsRoot = this.wsRoot;
290
308
  const router = this;
291
309
  return (req, socket, head) => {
@@ -302,7 +320,7 @@ var Router = class _Router {
302
320
  });
303
321
  const ctx = { params: match.params, query };
304
322
  if (match.middlewares.length === 0) {
305
- upgradeSocket(wss, req, socket, head, match.handler, ctx);
323
+ upgradeSocket(router.wss, req, socket, head, match.handler, ctx);
306
324
  return;
307
325
  }
308
326
  let index = 0;
@@ -313,7 +331,7 @@ var Router = class _Router {
313
331
  }
314
332
  return await new Promise((resolve3) => {
315
333
  try {
316
- upgradeSocket(wss, req, socket, head, match.handler, ctx2);
334
+ upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
317
335
  resolve3(new Response(null, { status: 101 }));
318
336
  } catch {
319
337
  socket.destroy();
@@ -346,7 +364,7 @@ var Router = class _Router {
346
364
  matchTrie(method, segments) {
347
365
  let node = this.root;
348
366
  const params = {};
349
- const pathMws = [...this.root.pathMws];
367
+ const pathMws = [];
350
368
  let wildcardHandler = null;
351
369
  let wildcardMws = [];
352
370
  let wildcardIdx = -1;
@@ -380,6 +398,7 @@ var Router = class _Router {
380
398
  }
381
399
  node = next;
382
400
  }
401
+ pathMws.push(...node.pathMws);
383
402
  if (node.subRouter) {
384
403
  return {
385
404
  pathMws,
@@ -388,7 +407,6 @@ var Router = class _Router {
388
407
  subRouter: { router: node.subRouter, remainingIdx: segments.length }
389
408
  };
390
409
  }
391
- pathMws.push(...node.pathMws);
392
410
  const handler = node.handlers.get(method) || node.handlers.get("*");
393
411
  if (handler) {
394
412
  if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
@@ -430,7 +448,8 @@ var Router = class _Router {
430
448
  mountPath: (ctx.mountPath || "") + levelMount
431
449
  });
432
450
  } catch (e) {
433
- return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
451
+ const err = e instanceof Error ? e : new Error(String(e));
452
+ return this.errorHandler ? this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
434
453
  }
435
454
  }
436
455
  if (match?.handler) {
@@ -440,7 +459,8 @@ var Router = class _Router {
440
459
  try {
441
460
  return await this.runChain(allMws, handler, req, ctxWithMatch);
442
461
  } catch (e) {
443
- return this.errorHandler ? this.errorHandler(e, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
462
+ const err = e instanceof Error ? e : new Error(String(e));
463
+ return this.errorHandler ? this.errorHandler(err, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
444
464
  }
445
465
  }
446
466
  if (this.globalMws.length > 0) {
@@ -448,7 +468,8 @@ var Router = class _Router {
448
468
  const delegate = () => new Response("Not Found", { status: 404 });
449
469
  return await this.runChain(this.globalMws, delegate, req, ctx);
450
470
  } catch (e) {
451
- return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
471
+ const err = e instanceof Error ? e : new Error(String(e));
472
+ return this.errorHandler ? this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
452
473
  }
453
474
  }
454
475
  return new Response("Not Found", { status: 404 });
@@ -533,8 +554,13 @@ var isDev = process.env.NODE_ENV !== "production";
533
554
  var _uiDir = "";
534
555
  var _allFiles = [];
535
556
  var _outDir = "";
557
+ var _router = null;
558
+ var _pagesDir = "";
559
+ var _nodeEntries = {};
536
560
  var tailwindCssUrl = null;
537
561
  var tailwindCssCode = "";
562
+ var _tailwindPlugin = null;
563
+ var _postcss = null;
538
564
  var _cjsRequire = createRequire(import.meta.url);
539
565
  var _vmCtx = vm.createContext(Object.create(globalThis));
540
566
  function loadSSRModule(code) {
@@ -713,7 +739,7 @@ function startFileWatcher() {
713
739
  let timeout = null;
714
740
  const pending = /* @__PURE__ */ new Set();
715
741
  chokidar.watch(_uiDir, {
716
- ignored: /(^|[/\\])\.(?!\.)|node_modules|\.weifuwu|dist/,
742
+ ignored: /(^|[/\\])\.(?!\.)|node_modules|[/\\]\.weifuwu[/\\]|[/\\]dist[/\\]/,
717
743
  persistent: false,
718
744
  ignoreInitial: true
719
745
  }).on("all", async (event, filePath) => {
@@ -731,6 +757,8 @@ function startFileWatcher() {
731
757
  );
732
758
  if (allKnown) {
733
759
  for (const f of exists) await recompileAndSwap(f, _outDir);
760
+ await reprocessTailwind();
761
+ broadcastReload();
734
762
  } else {
735
763
  await recompileAll();
736
764
  }
@@ -756,6 +784,7 @@ async function recompileAndSwap(filePath, outDir) {
756
784
  const name15 = basename(filePath);
757
785
  if (name15 === "layout.tsx") {
758
786
  layoutModules.set(filePath, mod);
787
+ clientBundleCache.clear();
759
788
  } else if (name15 === "route.ts") {
760
789
  const handlers = /* @__PURE__ */ new Map();
761
790
  for (const m of ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]) {
@@ -768,14 +797,30 @@ async function recompileAndSwap(filePath, outDir) {
768
797
  pageModules.set(filePath, mod);
769
798
  clientBundleCache.delete(id(filePath));
770
799
  }
771
- await reprocessTailwind();
772
- broadcastReload();
773
800
  } catch (err) {
774
801
  console.error("recompile failed:", err.message);
775
802
  }
776
803
  }
777
804
  async function recompileAll() {
778
805
  try {
806
+ const freshPages = scanPages(_pagesDir);
807
+ const freshFiles = /* @__PURE__ */ new Set();
808
+ const nodeEntries = {};
809
+ for (const p of freshPages) {
810
+ const nodeKey = p.entryPath || p.routePath || "";
811
+ nodeEntries[nodeKey] = { route: p.route, entryPath: p.entryPath, layouts: p.layouts, loadPath: p.loadPath, routePath: p.routePath };
812
+ if (p.entryPath) freshFiles.add(p.entryPath);
813
+ if (p.loadPath) freshFiles.add(p.loadPath);
814
+ for (const lp of p.layouts) freshFiles.add(lp);
815
+ if (p.routePath) freshFiles.add(p.routePath);
816
+ }
817
+ const nfPath = join(_pagesDir, "not-found.tsx");
818
+ if (existsSync(nfPath)) {
819
+ freshFiles.add(nfPath);
820
+ const rootLayouts = resolveLayouts(_pagesDir, _pagesDir);
821
+ for (const lp of rootLayouts) freshFiles.add(lp);
822
+ }
823
+ _allFiles = [...freshFiles];
779
824
  const result = await esbuild.build({
780
825
  entryPoints: Object.fromEntries(_allFiles.map((f) => [id(f), f])),
781
826
  outdir: _outDir,
@@ -794,9 +839,54 @@ async function recompileAll() {
794
839
  const srcPath = _allFiles.find((f) => file.path.endsWith(id(f) + ".js"));
795
840
  if (!srcPath) continue;
796
841
  const name15 = basename(srcPath);
797
- if (name15 === "layout.tsx") layoutModules.set(srcPath, mod);
798
- else if (name15 === "load.ts") loadModules.set(srcPath, mod);
799
- else pageModules.set(srcPath, mod);
842
+ if (name15 === "layout.tsx") {
843
+ layoutModules.set(srcPath, mod);
844
+ } else if (name15 === "route.ts") {
845
+ const handlers = /* @__PURE__ */ new Map();
846
+ for (const m of ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]) {
847
+ if (mod[m]) handlers.set(m, mod[m]);
848
+ }
849
+ routeModules.set(srcPath, handlers);
850
+ } else if (name15 === "load.ts") {
851
+ loadModules.set(srcPath, mod);
852
+ } else if (name15 !== "not-found.tsx") {
853
+ pageModules.set(srcPath, mod);
854
+ }
855
+ }
856
+ if (_router) {
857
+ const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
858
+ for (const [key, entry] of Object.entries(nodeEntries)) {
859
+ if (_nodeEntries[key]) continue;
860
+ if (entry.routePath && !entry.entryPath) {
861
+ _router.route(
862
+ "GET",
863
+ entry.route,
864
+ (req, ctx) => routeModules.get(entry.routePath)?.get("GET")?.(req, ctx) ?? new Response("", { status: 501 })
865
+ );
866
+ for (const m of methods) {
867
+ _router.route(
868
+ m,
869
+ entry.route,
870
+ (req, ctx) => routeModules.get(entry.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
871
+ );
872
+ }
873
+ }
874
+ if (entry.entryPath) {
875
+ const handler = makeSsrHandler(entry.entryPath, entry.layouts, entry.loadPath, _pagesDir, _router);
876
+ _router.get(entry.route, handler);
877
+ if (entry.routePath) {
878
+ for (const m of methods) {
879
+ _router.route(
880
+ m,
881
+ entry.route,
882
+ (req, ctx) => routeModules.get(entry.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
883
+ );
884
+ }
885
+ }
886
+ }
887
+ console.log("\u2139 weifuwu/tsx: registered new route " + entry.route);
888
+ }
889
+ _nodeEntries = nodeEntries;
800
890
  }
801
891
  clientBundleCache.clear();
802
892
  await reprocessTailwind();
@@ -806,10 +896,9 @@ async function recompileAll() {
806
896
  }
807
897
  }
808
898
  async function setupTailwind(uiDir, router) {
809
- let tailwindPlugin, postcss;
810
899
  try {
811
- tailwindPlugin = (await import("@tailwindcss/postcss")).default;
812
- postcss = (await import("postcss")).default;
900
+ _tailwindPlugin = (await import("@tailwindcss/postcss")).default;
901
+ _postcss = (await import("postcss")).default;
813
902
  } catch {
814
903
  return;
815
904
  }
@@ -821,7 +910,7 @@ async function setupTailwind(uiDir, router) {
821
910
  }
822
911
  try {
823
912
  const src = readFileSync(inputFile, "utf-8");
824
- const result = await postcss([tailwindPlugin()]).process(src, { from: inputFile });
913
+ const result = await _postcss([_tailwindPlugin()]).process(src, { from: inputFile });
825
914
  tailwindCssCode = result.css;
826
915
  } catch (err) {
827
916
  console.warn("Tailwind CSS processing failed:", err.message);
@@ -835,7 +924,7 @@ async function setupTailwind(uiDir, router) {
835
924
  chokidar.watch(inputFile, { persistent: false }).on("change", async () => {
836
925
  try {
837
926
  const newSrc = readFileSync(inputFile, "utf-8");
838
- const newResult = await postcss([tailwindPlugin()]).process(newSrc, { from: inputFile });
927
+ const newResult = await _postcss([_tailwindPlugin()]).process(newSrc, { from: inputFile });
839
928
  tailwindCssCode = newResult.css;
840
929
  broadcastReload();
841
930
  } catch (err) {
@@ -845,62 +934,77 @@ async function setupTailwind(uiDir, router) {
845
934
  }
846
935
  }
847
936
  async function reprocessTailwind() {
848
- if (!tailwindCssUrl) return;
937
+ if (!tailwindCssUrl || !_postcss || !_tailwindPlugin) return;
849
938
  try {
850
939
  const inputFile = resolve(_uiDir, "app.css");
851
940
  if (!existsSync(inputFile)) return;
852
- const tailwindPlugin = (await import("@tailwindcss/postcss")).default;
853
- const postcss = (await import("postcss")).default;
854
941
  const src = readFileSync(inputFile, "utf-8");
855
- const result = await postcss([tailwindPlugin()]).process(src, { from: inputFile });
942
+ const result = await _postcss([_tailwindPlugin()]).process(src, { from: inputFile });
856
943
  tailwindCssCode = result.css;
857
944
  } catch {
858
945
  }
859
946
  }
860
947
  var clientBundleCache = /* @__PURE__ */ new Map();
861
948
  var clientRouteLog = /* @__PURE__ */ new WeakMap();
949
+ var clientBuildParams = /* @__PURE__ */ new Map();
950
+ async function buildClientBundle(entryPath, layoutPaths, pagesDir) {
951
+ try {
952
+ const nested = layoutPaths.slice(1);
953
+ const layoutsImport = nested.map(
954
+ (p, i) => `import L${i} from${JSON.stringify(p)};`
955
+ ).join("");
956
+ const layoutsWrap = nested.map((_, i) => {
957
+ const idx = nested.length - 1 - i;
958
+ return `el=createElement(L${idx},null,el);`;
959
+ }).join("");
960
+ const code = [
961
+ `import{hydrateRoot}from'react-dom/client';`,
962
+ `import{createElement}from'react';`,
963
+ `import P from${JSON.stringify(entryPath)};`,
964
+ layoutsImport,
965
+ `const p=window.__WEIFUWU_PROPS;`,
966
+ `let el=createElement(P,p);`,
967
+ layoutsWrap,
968
+ `hydrateRoot(document.getElementById('__weifuwu_root'),el);`
969
+ ].join("");
970
+ const result = await esbuild.build({
971
+ stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
972
+ bundle: true,
973
+ format: "esm",
974
+ jsx: "automatic",
975
+ jsxImportSource: "react",
976
+ alias: resolveAliases(),
977
+ write: false,
978
+ minify: true
979
+ });
980
+ return result.outputFiles[0].contents;
981
+ } catch (err) {
982
+ console.error("hydration bundle failed:", err);
983
+ return null;
984
+ }
985
+ }
862
986
  async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router) {
863
987
  const key = id(entryPath);
864
988
  const url = `/__wfw/client/${key}.js`;
989
+ clientBuildParams.set(key, { entryPath, layoutPaths, pagesDir });
865
990
  if (!clientRouteLog.get(router)?.has(url)) {
866
991
  if (!clientBundleCache.has(key)) {
867
- try {
868
- const nested = layoutPaths.slice(1);
869
- const layoutsImport = nested.map(
870
- (p, i) => `import L${i} from${JSON.stringify(p)};`
871
- ).join("");
872
- const layoutsWrap = nested.map((_, i) => {
873
- const idx = nested.length - 1 - i;
874
- return `el=createElement(L${idx},null,el);`;
875
- }).join("");
876
- const code = [
877
- `import{hydrateRoot}from'react-dom/client';`,
878
- `import{createElement}from'react';`,
879
- `import P from${JSON.stringify(entryPath)};`,
880
- layoutsImport,
881
- `const p=window.__WEIFUWU_PROPS;`,
882
- `let el=createElement(P,p);`,
883
- layoutsWrap,
884
- `hydrateRoot(document.getElementById('__weifuwu_root'),el);`
885
- ].join("");
886
- const result = await esbuild.build({
887
- stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
888
- bundle: true,
889
- format: "esm",
890
- jsx: "automatic",
891
- jsxImportSource: "react",
892
- alias: resolveAliases(),
893
- write: false,
894
- minify: true
895
- });
896
- clientBundleCache.set(key, result.outputFiles[0].contents);
897
- } catch (err) {
898
- console.error("hydration bundle failed:", err);
899
- return null;
992
+ const buf = await buildClientBundle(entryPath, layoutPaths, pagesDir);
993
+ if (!buf) return null;
994
+ clientBundleCache.set(key, buf);
995
+ }
996
+ router.get(url, async () => {
997
+ let buf = clientBundleCache.get(key);
998
+ if (!buf) {
999
+ const params = clientBuildParams.get(key);
1000
+ if (params) {
1001
+ const rebuilt = await buildClientBundle(params.entryPath, params.layoutPaths, params.pagesDir);
1002
+ if (rebuilt) {
1003
+ clientBundleCache.set(key, rebuilt);
1004
+ buf = rebuilt;
1005
+ }
1006
+ }
900
1007
  }
901
- }
902
- router.get(url, () => {
903
- const buf = clientBundleCache.get(key);
904
1008
  return buf ? new Response(buf, {
905
1009
  headers: { "content-type": "application/javascript; charset=utf-8" }
906
1010
  }) : new Response("", { status: 500 });
@@ -913,7 +1017,7 @@ async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router)
913
1017
  }
914
1018
  function makeSsrHandler(entryPath, layoutPaths, loadPath, pagesDir, router) {
915
1019
  return async (req, ctx) => {
916
- const base = ctx.mountPath || "";
1020
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
917
1021
  const pageMod = pageModules.get(entryPath);
918
1022
  if (!pageMod) return new Response("", { status: 500 });
919
1023
  const Component = pageMod.default;
@@ -944,9 +1048,9 @@ function makeSsrHandler(entryPath, layoutPaths, loadPath, pagesDir, router) {
944
1048
  if (bundle) {
945
1049
  scripts.push(`<script type="module" src="${base}${bundle.url}"></script>`);
946
1050
  }
947
- let html = `<!DOCTYPE html>
948
- ${body}
949
- ${scripts.join("\n")}`;
1051
+ let html = body.startsWith("<!DOCTYPE html>") ? body : `<!DOCTYPE html>
1052
+ ${body}`;
1053
+ html += "\n" + scripts.join("\n");
950
1054
  if (tailwindCssUrl && html.includes("</head>")) {
951
1055
  html = html.replace(
952
1056
  "</head>",
@@ -967,6 +1071,7 @@ async function tsx(options) {
967
1071
  const uiDir = resolve(options.dir);
968
1072
  const pagesDir = existsSync(join(uiDir, "pages")) ? join(uiDir, "pages") : uiDir;
969
1073
  _uiDir = uiDir;
1074
+ _pagesDir = pagesDir;
970
1075
  const outDir = join(uiDir, ".weifuwu", "ssr");
971
1076
  _outDir = outDir;
972
1077
  const pages = scanPages(pagesDir);
@@ -989,8 +1094,11 @@ async function tsx(options) {
989
1094
  _allFiles = [...allFiles];
990
1095
  await compileAll(_allFiles, outDir, "node", resolveAliases());
991
1096
  const router = new Router();
1097
+ _router = router;
992
1098
  const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
993
1099
  for (const p of pages) {
1100
+ const nodeKey = p.entryPath || p.routePath || "";
1101
+ _nodeEntries[nodeKey] = { route: p.route, entryPath: p.entryPath || "", layouts: p.layouts, loadPath: p.loadPath, routePath: p.routePath };
994
1102
  if (p.routeOnly && p.routePath) {
995
1103
  const rUrl = compiledUrl(p.routePath, outDir);
996
1104
  const modR = await import(rUrl);
@@ -1055,7 +1163,7 @@ async function tsx(options) {
1055
1163
  }
1056
1164
  }
1057
1165
  const handler = async (req, ctx) => {
1058
- const base = ctx.mountPath || "";
1166
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
1059
1167
  const nfMod = pageModules.get(nfPath);
1060
1168
  if (!nfMod) return new Response("Not Found", { status: 404 });
1061
1169
  const NfComponent = nfMod.default;
@@ -1070,7 +1178,7 @@ async function tsx(options) {
1070
1178
  }, element);
1071
1179
  const stream = await renderToReadableStream(element);
1072
1180
  const body = await readStream(stream);
1073
- let html = `<!DOCTYPE html>
1181
+ let html = body.startsWith("<!DOCTYPE html>") ? body : `<!DOCTYPE html>
1074
1182
  ${body}`;
1075
1183
  if (tailwindCssUrl && html.includes("</head>")) {
1076
1184
  html = html.replace(
@@ -1127,7 +1235,12 @@ function cors(options) {
1127
1235
  ...options
1128
1236
  };
1129
1237
  function resolveOrigin(requestOrigin) {
1130
- if (typeof opts.origin === "string") return opts.origin === "*" ? "*" : opts.origin;
1238
+ if (typeof opts.origin === "string") {
1239
+ if (opts.origin === "*") {
1240
+ return opts.credentials ? requestOrigin : "*";
1241
+ }
1242
+ return opts.origin;
1243
+ }
1131
1244
  if (Array.isArray(opts.origin)) {
1132
1245
  return opts.origin.includes(requestOrigin) ? requestOrigin : "";
1133
1246
  }
@@ -1142,7 +1255,7 @@ function cors(options) {
1142
1255
  headers.set("Access-Control-Allow-Origin", acao);
1143
1256
  if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
1144
1257
  if (opts.exposedHeaders?.length) headers.set("Access-Control-Expose-Headers", opts.exposedHeaders.join(", "));
1145
- headers.set("Vary", "Origin");
1258
+ if (acao !== "*") headers.set("Vary", "Origin");
1146
1259
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
1147
1260
  }
1148
1261
  return (req, ctx, next) => {
@@ -1155,7 +1268,7 @@ function cors(options) {
1155
1268
  headers.set("Access-Control-Allow-Headers", opts.allowedHeaders.join(", "));
1156
1269
  if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
1157
1270
  if (opts.maxAge != null) headers.set("Access-Control-Max-Age", String(opts.maxAge));
1158
- headers.set("Vary", "Origin");
1271
+ if (acao !== "*") headers.set("Vary", "Origin");
1159
1272
  return new Response(null, { status: 204, headers });
1160
1273
  }
1161
1274
  if (!acao) return next(req, ctx);
@@ -1163,20 +1276,23 @@ function cors(options) {
1163
1276
  };
1164
1277
  }
1165
1278
  function auth(options) {
1279
+ if (!options.token && !options.verify && !options.proxy) {
1280
+ throw new Error("auth() requires at least one of: token, verify, or proxy");
1281
+ }
1166
1282
  return async (req, ctx, next) => {
1167
1283
  const headerName = options.header ?? "Authorization";
1168
1284
  let from = "header";
1169
1285
  let header = req.headers.get(headerName);
1170
1286
  let token = "";
1171
1287
  if (header) {
1172
- token = header;
1288
+ token = header.trim();
1173
1289
  if (headerName.toLowerCase() === "authorization") {
1174
1290
  const parts = header.split(" ");
1175
1291
  if (parts[0]?.toLowerCase() === "bearer") {
1176
- token = parts.slice(1).join(" ");
1292
+ token = parts.slice(1).join(" ").trim();
1177
1293
  }
1178
1294
  }
1179
- } else {
1295
+ } else if (!options.header) {
1180
1296
  const url = new URL(req.url);
1181
1297
  const qsToken = url.searchParams.get("access_token");
1182
1298
  if (qsToken) {
@@ -1191,7 +1307,12 @@ function auth(options) {
1191
1307
  });
1192
1308
  }
1193
1309
  if (options.proxy) {
1194
- const proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
1310
+ let proxyUrl;
1311
+ try {
1312
+ proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
1313
+ } catch {
1314
+ return new Response("Invalid proxy URL", { status: 500 });
1315
+ }
1195
1316
  const proxyHeaders = {};
1196
1317
  if (from === "header" && header) {
1197
1318
  proxyHeaders[headerName] = header;
@@ -1241,7 +1362,7 @@ function auth(options) {
1241
1362
 
1242
1363
  // static.ts
1243
1364
  import { createHash as createHash2 } from "node:crypto";
1244
- import { open } from "node:fs/promises";
1365
+ import { open, realpath } from "node:fs/promises";
1245
1366
  import { extname, resolve as resolve2, normalize, sep as sep2 } from "node:path";
1246
1367
  function serveStatic(root, options) {
1247
1368
  const rootDir = resolve2(root);
@@ -1259,7 +1380,12 @@ function serveStatic(root, options) {
1259
1380
  let fileHandle;
1260
1381
  try {
1261
1382
  fileHandle = await open(filePath, "r");
1262
- const stat = await fileHandle.stat();
1383
+ let stat = await fileHandle.stat();
1384
+ const realPath = await realpath(filePath);
1385
+ if (!realPath.startsWith(rootDir + sep2) && realPath !== rootDir) {
1386
+ await fileHandle.close();
1387
+ return new Response("Forbidden", { status: 403 });
1388
+ }
1263
1389
  if (stat.isDirectory()) {
1264
1390
  await fileHandle.close();
1265
1391
  const indexFile = opts.index ?? "index.html";
@@ -1268,8 +1394,8 @@ function serveStatic(root, options) {
1268
1394
  return new Response("Forbidden", { status: 403 });
1269
1395
  }
1270
1396
  fileHandle = await open(filePath, "r");
1271
- const dirStat = await fileHandle.stat();
1272
- if (!dirStat.isFile()) {
1397
+ stat = await fileHandle.stat();
1398
+ if (!stat.isFile()) {
1273
1399
  await fileHandle.close();
1274
1400
  return new Response("Not Found", { status: 404 });
1275
1401
  }
@@ -1309,8 +1435,8 @@ var MIME_TYPES = {
1309
1435
  ".html": "text/html; charset=utf-8",
1310
1436
  ".htm": "text/html; charset=utf-8",
1311
1437
  ".css": "text/css; charset=utf-8",
1312
- ".js": "application/javascript; charset=utf-8",
1313
- ".mjs": "application/javascript; charset=utf-8",
1438
+ ".js": "text/javascript; charset=utf-8",
1439
+ ".mjs": "text/javascript; charset=utf-8",
1314
1440
  ".json": "application/json",
1315
1441
  ".png": "image/png",
1316
1442
  ".jpg": "image/jpeg",
@@ -1330,7 +1456,16 @@ var MIME_TYPES = {
1330
1456
  ".pdf": "application/pdf",
1331
1457
  ".zip": "application/zip",
1332
1458
  ".wasm": "application/wasm",
1333
- ".map": "application/json"
1459
+ ".map": "application/json",
1460
+ ".ts": "application/x-typescript",
1461
+ ".tsx": "application/x-typescript",
1462
+ ".md": "text/markdown; charset=utf-8",
1463
+ ".yaml": "application/x-yaml",
1464
+ ".yml": "application/x-yaml",
1465
+ ".csv": "text/csv; charset=utf-8",
1466
+ ".mp4": "video/mp4",
1467
+ ".mp3": "audio/mpeg",
1468
+ ".wav": "audio/wav"
1334
1469
  };
1335
1470
 
1336
1471
  // validate.ts
@@ -1376,18 +1511,28 @@ function validate(schemas) {
1376
1511
  }
1377
1512
  }
1378
1513
  if (schemas.body) {
1379
- if (req.body === null) {
1514
+ if (req.method === "GET" || req.method === "HEAD") {
1515
+ } else if (req.body === null) {
1380
1516
  issues.push({ path: ["body"], message: "Request body is required" });
1381
1517
  } else {
1382
1518
  const bodyText = await req.text();
1383
- if (!bodyText && req.method !== "GET" && req.method !== "HEAD") {
1519
+ if (!bodyText) {
1384
1520
  issues.push({ path: ["body"], message: "Request body is required" });
1385
1521
  } else {
1386
- let bodyValue;
1387
- try {
1388
- bodyValue = JSON.parse(bodyText);
1389
- } catch {
1522
+ const ct = req.headers.get("content-type") ?? "";
1523
+ let bodyValue = bodyText;
1524
+ if (ct.includes("application/json") || ct.includes("text/") || ct.includes("*/json")) {
1525
+ try {
1526
+ bodyValue = JSON.parse(bodyText);
1527
+ } catch {
1528
+ }
1529
+ } else if (ct.includes("application/x-www-form-urlencoded") || ct.includes("multipart/form-data")) {
1390
1530
  bodyValue = bodyText;
1531
+ } else {
1532
+ try {
1533
+ bodyValue = JSON.parse(bodyText);
1534
+ } catch {
1535
+ }
1391
1536
  }
1392
1537
  const result = schemas.body.safeParse(bodyValue);
1393
1538
  if (result.success) {
@@ -1417,14 +1562,20 @@ function getCookies(req) {
1417
1562
  for (const pair of header.split(";")) {
1418
1563
  const idx = pair.indexOf("=");
1419
1564
  if (idx === -1) continue;
1420
- const name15 = pair.slice(0, idx).trim();
1421
- const value = pair.slice(idx + 1).trim();
1422
- if (name15) {
1423
- try {
1424
- cookies[name15] = decodeURIComponent(value);
1425
- } catch {
1426
- cookies[name15] = value;
1427
- }
1565
+ let name15 = pair.slice(0, idx).trim();
1566
+ let value = pair.slice(idx + 1).trim();
1567
+ if (!name15) continue;
1568
+ try {
1569
+ name15 = decodeURIComponent(name15);
1570
+ } catch {
1571
+ }
1572
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
1573
+ value = value.slice(1, -1);
1574
+ }
1575
+ try {
1576
+ cookies[name15] = decodeURIComponent(value);
1577
+ } catch {
1578
+ cookies[name15] = value;
1428
1579
  }
1429
1580
  }
1430
1581
  return cookies;
@@ -1451,7 +1602,11 @@ function setCookie(res, name15, value, options) {
1451
1602
  }
1452
1603
  function deleteCookie(res, name15, options) {
1453
1604
  const headers = new Headers(res.headers);
1454
- headers.append("Set-Cookie", serializeCookie(name15, "", { ...options, maxAge: 0 }));
1605
+ headers.append("Set-Cookie", serializeCookie(name15, "", {
1606
+ ...options,
1607
+ maxAge: 0,
1608
+ expires: /* @__PURE__ */ new Date(0)
1609
+ }));
1455
1610
  return new Response(res.body, {
1456
1611
  status: res.status,
1457
1612
  statusText: res.statusText,
@@ -1462,12 +1617,43 @@ function deleteCookie(res, name15, options) {
1462
1617
  // upload.ts
1463
1618
  import { writeFile, mkdir } from "node:fs/promises";
1464
1619
  import { randomUUID } from "node:crypto";
1465
- import { join as join2 } from "node:path";
1620
+ import { join as join2, extname as extname2 } from "node:path";
1621
+ var extensionMimeMap = {
1622
+ ".jpg": "image/jpeg",
1623
+ ".jpeg": "image/jpeg",
1624
+ ".png": "image/png",
1625
+ ".gif": "image/gif",
1626
+ ".webp": "image/webp",
1627
+ ".svg": "image/svg+xml",
1628
+ ".pdf": "application/pdf",
1629
+ ".doc": "application/msword",
1630
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1631
+ ".xls": "application/vnd.ms-excel",
1632
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1633
+ ".zip": "application/zip",
1634
+ ".gz": "application/gzip",
1635
+ ".mp4": "video/mp4",
1636
+ ".mp3": "audio/mpeg",
1637
+ ".wav": "audio/wav",
1638
+ ".json": "application/json",
1639
+ ".csv": "text/csv",
1640
+ ".txt": "text/plain",
1641
+ ".html": "text/html",
1642
+ ".css": "text/css",
1643
+ ".js": "text/javascript",
1644
+ ".ts": "application/x-typescript",
1645
+ ".tsx": "application/x-typescript"
1646
+ };
1647
+ function detectMimeFromExtension(filename) {
1648
+ return extensionMimeMap[extname2(filename).toLowerCase()];
1649
+ }
1466
1650
  function upload(options) {
1467
1651
  const saveDir = options?.dir;
1468
1652
  return async (req, ctx, next) => {
1469
1653
  const ct = req.headers.get("content-type") ?? "";
1470
1654
  if (!ct.includes("multipart/form-data")) return next(req, ctx);
1655
+ if (saveDir) await mkdir(saveDir, { recursive: true }).catch(() => {
1656
+ });
1471
1657
  let formData;
1472
1658
  try {
1473
1659
  formData = await req.formData();
@@ -1478,8 +1664,13 @@ function upload(options) {
1478
1664
  const fields = {};
1479
1665
  for (const [key, value] of formData) {
1480
1666
  if (value instanceof File) {
1481
- if (options?.allowedTypes && !options.allowedTypes.includes(value.type)) {
1482
- return Response.json({ error: `File type not allowed: ${value.type}` }, { status: 415 });
1667
+ if (options?.allowedTypes) {
1668
+ const clientOk = options.allowedTypes.includes(value.type);
1669
+ const extType = detectMimeFromExtension(value.name);
1670
+ const extOk = extType ? options.allowedTypes.includes(extType) : false;
1671
+ if (!clientOk && !extOk) {
1672
+ return Response.json({ error: `File type not allowed: ${value.type}` }, { status: 415 });
1673
+ }
1483
1674
  }
1484
1675
  if (options?.maxFileSize && value.size > options.maxFileSize) {
1485
1676
  return Response.json({ error: `File too large: ${value.name}` }, { status: 413 });
@@ -1492,9 +1683,8 @@ function upload(options) {
1492
1683
  buffer: saveDir ? void 0 : buf
1493
1684
  };
1494
1685
  if (saveDir) {
1495
- const safeName = value.name.replace(/[/\\]/g, "");
1686
+ const safeName = value.name.replace(/[/\\\0]/g, "_");
1496
1687
  const filePath = join2(saveDir, `${randomUUID()}-${safeName}`);
1497
- await mkdir(saveDir, { recursive: true });
1498
1688
  await writeFile(filePath, buf);
1499
1689
  uf.path = filePath;
1500
1690
  }
@@ -1520,7 +1710,11 @@ function rateLimit(options) {
1520
1710
  const getKey = options?.key ?? ((req) => {
1521
1711
  const forwarded = req.headers.get("x-forwarded-for");
1522
1712
  if (forwarded) return forwarded.split(",")[0].trim();
1523
- return new URL(req.url).hostname;
1713
+ const realIp = req.headers.get("x-real-ip");
1714
+ if (realIp) return realIp;
1715
+ const cfIp = req.headers.get("cf-connecting-ip");
1716
+ if (cfIp) return cfIp;
1717
+ return req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "global";
1524
1718
  });
1525
1719
  const message = options?.message ?? "Too Many Requests";
1526
1720
  const hits = /* @__PURE__ */ new Map();
@@ -1531,7 +1725,7 @@ function rateLimit(options) {
1531
1725
  }
1532
1726
  }, window);
1533
1727
  if (interval.unref) interval.unref();
1534
- return async (req, ctx, next) => {
1728
+ const mw = async (req, ctx, next) => {
1535
1729
  const key = getKey(req);
1536
1730
  const now = Date.now();
1537
1731
  const entry = hits.get(key);
@@ -1564,10 +1758,15 @@ function rateLimit(options) {
1564
1758
  headers.set("X-RateLimit-Reset", String(Math.ceil(entry.reset / 1e3)));
1565
1759
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
1566
1760
  };
1761
+ mw.stop = () => {
1762
+ clearInterval(interval);
1763
+ hits.clear();
1764
+ };
1765
+ return mw;
1567
1766
  }
1568
1767
 
1569
1768
  // compress.ts
1570
- import { gzipSync, brotliCompressSync, constants } from "node:zlib";
1769
+ import { gzipSync, brotliCompressSync, deflateSync, constants } from "node:zlib";
1571
1770
  function compress(options) {
1572
1771
  const level = options?.level ?? 6;
1573
1772
  const threshold = options?.threshold ?? 1024;
@@ -1580,7 +1779,7 @@ function compress(options) {
1580
1779
  return next(req, ctx);
1581
1780
  }
1582
1781
  const res = await next(req, ctx);
1583
- if (res.status === 304 || res.status === 204 || res.status < 200 || res.status >= 300) {
1782
+ if (res.status === 304 || res.status === 204 || res.status === 206 || res.status < 200 || res.status >= 300) {
1584
1783
  return res;
1585
1784
  }
1586
1785
  const ce = res.headers.get("content-encoding");
@@ -1602,14 +1801,15 @@ function compress(options) {
1602
1801
  compressed = gzipSync(body, { level: Math.min(level, 9) });
1603
1802
  encoding = "gzip";
1604
1803
  } else {
1605
- compressed = gzipSync(body, { level: Math.min(level, 9) });
1804
+ compressed = deflateSync(body, { level: Math.min(level, 9) });
1606
1805
  encoding = "deflate";
1607
1806
  }
1608
1807
  const headers = new Headers(res.headers);
1609
1808
  headers.set("Content-Encoding", encoding);
1610
1809
  headers.set("Content-Length", String(compressed.byteLength));
1611
1810
  headers.delete("Content-Range");
1612
- headers.set("Vary", "Accept-Encoding");
1811
+ const existingVary = headers.get("Vary");
1812
+ headers.set("Vary", existingVary ? `${existingVary}, Accept-Encoding` : "Accept-Encoding");
1613
1813
  return new Response(compressed, {
1614
1814
  status: res.status,
1615
1815
  statusText: res.statusText,
@@ -1660,9 +1860,10 @@ async function executeQuery(schema, params, options, req, ctx) {
1660
1860
  variableValues: params.variables,
1661
1861
  operationName: params.operationName
1662
1862
  });
1663
- return Response.json(result, { status: result.errors ? 400 : 200 });
1863
+ return Response.json(result, { status: result.errors ? 200 : 200 });
1664
1864
  }
1665
1865
  function graphiqlHTML(endpoint) {
1866
+ const safeEndpoint = endpoint.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/</g, "\\x3C");
1666
1867
  return `<!doctype html>
1667
1868
  <html lang="en">
1668
1869
  <head>
@@ -1694,7 +1895,7 @@ function graphiqlHTML(endpoint) {
1694
1895
  import { createGraphiQLFetcher } from '@graphiql/toolkit';
1695
1896
  import 'graphiql/setup-workers/esm.sh';
1696
1897
 
1697
- const fetcher = createGraphiQLFetcher({ url: "${endpoint}" });
1898
+ const fetcher = createGraphiQLFetcher({ url: "${safeEndpoint}" });
1698
1899
 
1699
1900
  function App() {
1700
1901
  return React.createElement(GraphiQL, { fetcher });
@@ -1741,12 +1942,17 @@ function graphql(handler) {
1741
1942
  }
1742
1943
 
1743
1944
  // ai.ts
1744
- import { streamText } from "ai";
1745
- function ai(handler) {
1945
+ var _ai = {
1946
+ streamText: null
1947
+ };
1948
+ async function ai(handler) {
1949
+ if (!_ai.streamText) {
1950
+ _ai.streamText = (await import("ai")).streamText;
1951
+ }
1746
1952
  const r = new Router();
1747
1953
  r.post("/", async (req, ctx) => {
1748
1954
  const options = await handler(req, ctx);
1749
- const result = streamText(options);
1955
+ const result = _ai.streamText(options);
1750
1956
  return result.toTextStreamResponse();
1751
1957
  });
1752
1958
  return r;
@@ -1763,9 +1969,11 @@ function tool(def) {
1763
1969
  }
1764
1970
 
1765
1971
  // workflow/reference.ts
1972
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1766
1973
  function getByPath(obj, path2) {
1767
1974
  let current = obj;
1768
1975
  for (const key of path2) {
1976
+ if (DANGEROUS_KEYS.has(key)) return void 0;
1769
1977
  if (current === null || current === void 0) return void 0;
1770
1978
  if (typeof current === "object" && key in current) {
1771
1979
  current = current[key];
@@ -1835,27 +2043,42 @@ function evaluateExpression(expr, ctx) {
1835
2043
  { op: "!==", fn: (a, b) => a !== b },
1836
2044
  { op: ">=", fn: (a, b) => Number(a) >= Number(b) },
1837
2045
  { op: "<=", fn: (a, b) => Number(a) <= Number(b) },
1838
- { op: ">", fn: (a, b) => Number(a) > Number(b) },
1839
- { op: "<", fn: (a, b) => Number(a) < Number(b) },
1840
2046
  { op: "==", fn: (a, b) => a == b },
1841
2047
  { op: "!=", fn: (a, b) => a != b },
2048
+ { op: "&&", fn: (a, b) => Boolean(a) && Boolean(b) },
2049
+ { op: "||", fn: (a, b) => Boolean(a) || Boolean(b) },
2050
+ { op: ">", fn: (a, b) => Number(a) > Number(b) },
2051
+ { op: "<", fn: (a, b) => Number(a) < Number(b) },
1842
2052
  { op: "+", fn: (a, b) => Number(a) + Number(b) },
1843
2053
  { op: "-", fn: (a, b) => Number(a) - Number(b) },
1844
2054
  { op: "*", fn: (a, b) => Number(a) * Number(b) },
1845
2055
  { op: "/", fn: (a, b) => Number(a) / Number(b) },
1846
- { op: "%", fn: (a, b) => Number(a) % Number(b) },
1847
- { op: "&&", fn: (a, b) => Boolean(a) && Boolean(b) },
1848
- { op: "||", fn: (a, b) => Boolean(a) || Boolean(b) }
2056
+ { op: "%", fn: (a, b) => Number(a) % Number(b) }
1849
2057
  ];
2058
+ let bestIdx = -1;
2059
+ let bestOp = null;
2060
+ let bestFn = null;
1850
2061
  for (const { op, fn } of operators) {
1851
2062
  const idx = expr.indexOf(op);
1852
- if (idx > 0) {
1853
- const leftRaw = expr.slice(0, idx).trim();
1854
- const rightRaw = expr.slice(idx + op.length).trim();
1855
- const left = resolveValue(leftRaw, ctx);
1856
- const right = resolveValue(rightRaw, ctx);
1857
- return fn(left, right);
1858
- }
2063
+ if (idx > 0 && (bestIdx === -1 || idx < bestIdx)) {
2064
+ bestIdx = idx;
2065
+ bestOp = op;
2066
+ bestFn = fn;
2067
+ }
2068
+ }
2069
+ function resolveOperand(raw) {
2070
+ const trimmed2 = raw.trim();
2071
+ if (/^[\d.]+$/.test(trimmed2)) return Number(trimmed2);
2072
+ const subIdx = operators.findIndex(({ op }) => trimmed2.includes(op));
2073
+ if (subIdx !== -1) return evaluateExpression(trimmed2, ctx);
2074
+ return resolveValue(trimmed2, ctx);
2075
+ }
2076
+ if (bestIdx > 0 && bestOp && bestFn) {
2077
+ const leftRaw = expr.slice(0, bestIdx).trim();
2078
+ const rightRaw = expr.slice(bestIdx + bestOp.length).trim();
2079
+ const left = resolveOperand(leftRaw);
2080
+ const right = resolveOperand(rightRaw);
2081
+ return bestFn(left, right);
1859
2082
  }
1860
2083
  const trimmed = expr.trim();
1861
2084
  if (trimmed === "true") return true;
@@ -2235,6 +2458,7 @@ data: ${JSON.stringify(event.data)}
2235
2458
  }
2236
2459
 
2237
2460
  // workflow/route.ts
2461
+ import crypto from "node:crypto";
2238
2462
  function workflow(handler) {
2239
2463
  const r = new Router();
2240
2464
  const sseManager = createSSEManager();
@@ -2797,7 +3021,12 @@ h2{color:#dc2626}.desc{color:#555}</style>
2797
3021
  if (!codeVerifier) {
2798
3022
  return Response.json({ error: "invalid_grant", error_description: "code_verifier required" }, { status: 400 });
2799
3023
  }
2800
- const expected = crypto2.createHash("sha256").update(codeVerifier).digest().toString("base64url");
3024
+ let expected;
3025
+ if (stored.code_challenge_method === "plain") {
3026
+ expected = codeVerifier;
3027
+ } else {
3028
+ expected = crypto2.createHash("sha256").update(codeVerifier).digest().toString("base64url");
3029
+ }
2801
3030
  if (expected !== stored.code_challenge) {
2802
3031
  return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
2803
3032
  }
@@ -3266,7 +3495,7 @@ function sqlTypeForField(field) {
3266
3495
  }
3267
3496
  var RESERVED_SLUGS = /* @__PURE__ */ new Set(["sys", "graphql", "auth"]);
3268
3497
  function validateSlug(slug) {
3269
- if (!/^[a-z][a-z0-9_]+$/.test(slug)) {
3498
+ if (!/^[a-z][a-z0-9_]*$/.test(slug)) {
3270
3499
  return "Slug must start with a letter and contain only lowercase letters, numbers, and underscores";
3271
3500
  }
3272
3501
  if (RESERVED_SLUGS.has(slug)) {
@@ -13688,15 +13917,16 @@ function buildRouter2(deps) {
13688
13917
  return Response.json(rows);
13689
13918
  });
13690
13919
  r.delete("/agents/:id/knowledge/:docId", async (_req, ctx) => {
13920
+ const agentId = parseInt(ctx.params.id, 10);
13691
13921
  const docId = parseInt(ctx.params.docId, 10);
13692
- await sql`DELETE FROM "_knowledge_documents" WHERE id = ${docId}`;
13922
+ await sql`DELETE FROM "_knowledge_documents" WHERE id = ${docId} AND agent_id = ${agentId}`;
13693
13923
  return Response.json({ ok: true });
13694
13924
  });
13695
13925
  return r;
13696
13926
  }
13697
13927
 
13698
13928
  // agent/run.ts
13699
- import { streamText as streamText2, generateText as generateText2, embed } from "ai";
13929
+ import { streamText, generateText as generateText2, embed } from "ai";
13700
13930
  import { z as z27 } from "zod";
13701
13931
  function chunkContent(content, chunkSize = 512, overlap = 64) {
13702
13932
  const paragraphs = content.split(/\n\n+/);
@@ -13761,7 +13991,7 @@ function createRunner(deps) {
13761
13991
  }
13762
13992
  const system = agent2.system_prompt || void 0;
13763
13993
  if (params.stream) {
13764
- const result = streamText2({
13994
+ const result = streamText({
13765
13995
  model,
13766
13996
  system,
13767
13997
  messages,
@@ -14332,7 +14562,9 @@ function createManager(config, apps, manager) {
14332
14562
  if (!config.deployToken) return next(req, ctx);
14333
14563
  const header = req.headers.get("authorization") ?? "";
14334
14564
  const token = header.replace("Bearer ", "");
14335
- if (token !== config.deployToken) {
14565
+ const tokenBuf = Buffer.from(token);
14566
+ const secretBuf = Buffer.from(config.deployToken);
14567
+ if (tokenBuf.length !== secretBuf.length || !crypto4.timingSafeEqual(tokenBuf, secretBuf)) {
14336
14568
  return Response.json({ error: "Unauthorized" }, { status: 401 });
14337
14569
  }
14338
14570
  return next(req, ctx);
@@ -14431,15 +14663,22 @@ function createManager(config, apps, manager) {
14431
14663
  }
14432
14664
  });
14433
14665
  router.post("/webhook", async (req) => {
14666
+ const rawBody = await req.text();
14434
14667
  if (config.webhookSecret) {
14435
14668
  const sig = req.headers.get("x-hub-signature-256") ?? "";
14436
- const body = await req.clone().text();
14437
- const hmac = crypto4.createHmac("sha256", config.webhookSecret).update(body).digest("hex");
14438
- if (sig !== `sha256=${hmac}`) {
14669
+ const expected = `sha256=${crypto4.createHmac("sha256", config.webhookSecret).update(rawBody).digest("hex")}`;
14670
+ const sigBuf = Buffer.from(sig);
14671
+ const expectedBuf = Buffer.from(expected);
14672
+ if (sigBuf.length !== expectedBuf.length || !crypto4.timingSafeEqual(sigBuf, expectedBuf)) {
14439
14673
  return Response.json({ error: "invalid signature" }, { status: 401 });
14440
14674
  }
14441
14675
  }
14442
- const payload = await req.json();
14676
+ let payload;
14677
+ try {
14678
+ payload = JSON.parse(rawBody);
14679
+ } catch {
14680
+ payload = {};
14681
+ }
14443
14682
  const repoUrl = payload?.repository?.clone_url ?? payload?.repository?.html_url ?? "";
14444
14683
  if (!repoUrl) return Response.json({ deployed: [] });
14445
14684
  const deployed = [];
@@ -14567,6 +14806,12 @@ async function deploy(config) {
14567
14806
  if (logs.length > 1e3) logs.splice(0, logs.length - 1e3);
14568
14807
  };
14569
14808
  try {
14809
+ if (typeof ac.repo !== "string" || !/^https?:\/\/[^\s"']+\/[^\s"']+/.test(ac.repo) && !/^git@[^\s"']+:[^\s"']+/.test(ac.repo)) {
14810
+ throw new Error(`Invalid repo URL: ${ac.repo}`);
14811
+ }
14812
+ if (ac.branch && typeof ac.branch === "string" && !/^[\w.\-/]+$/.test(ac.branch)) {
14813
+ throw new Error(`Invalid branch name: ${ac.branch}`);
14814
+ }
14570
14815
  if (fs.existsSync(path.join(appDir, ".git"))) {
14571
14816
  execSync("git pull", { cwd: appDir, stdio: "pipe", timeout: 12e4 });
14572
14817
  log("[deploy] git pull done");
@@ -14574,7 +14819,7 @@ async function deploy(config) {
14574
14819
  if (fs.existsSync(appDir)) {
14575
14820
  fs.rmSync(appDir, { recursive: true });
14576
14821
  }
14577
- execSync(`git clone ${ac.repo} ${appDir}`, { stdio: "pipe", timeout: 12e4 });
14822
+ execSync(`git clone --depth=1 ${ac.repo} ${appDir}`, { stdio: "pipe", timeout: 12e4 });
14578
14823
  log("[deploy] git clone done");
14579
14824
  if (ac.branch) {
14580
14825
  execSync(`git checkout ${ac.branch}`, { cwd: appDir, stdio: "pipe", timeout: 3e4 });
@@ -14601,6 +14846,10 @@ async function deploy(config) {
14601
14846
  return;
14602
14847
  }
14603
14848
  if (ac.buildCommand) {
14849
+ if (typeof ac.buildCommand !== "string" || /[;&|`$()]/.test(ac.buildCommand)) {
14850
+ log(`[deploy] invalid build command (rejected): ${ac.buildCommand}`);
14851
+ return;
14852
+ }
14604
14853
  try {
14605
14854
  execSync(ac.buildCommand, { cwd: appDir, stdio: "pipe", timeout: 12e4 });
14606
14855
  log("[deploy] build done");
@@ -5,4 +5,6 @@ export interface RateLimitOptions {
5
5
  key?: (req: Request) => string;
6
6
  message?: string;
7
7
  }
8
- export declare function rateLimit(options?: RateLimitOptions): Middleware;
8
+ export declare function rateLimit(options?: RateLimitOptions): Middleware & {
9
+ stop: () => void;
10
+ };
package/dist/router.d.ts CHANGED
@@ -14,6 +14,7 @@ export declare class Router {
14
14
  private wsRoot;
15
15
  private globalMws;
16
16
  private errorHandler?;
17
+ private wss;
17
18
  use(mw: Middleware): this;
18
19
  use(path: string, router: Router): this;
19
20
  use(path: string, mw: Middleware): this;
package/dist/types.d.ts CHANGED
@@ -7,4 +7,4 @@ export interface Context {
7
7
  }
8
8
  export type Handler = (req: Request, ctx: Context) => Response | Promise<Response>;
9
9
  export type Middleware = (req: Request, ctx: Context, next: Handler) => Response | Promise<Response>;
10
- export type ErrorHandler = (error: Error, req: Request, ctx: Context) => Response;
10
+ export type ErrorHandler = (error: Error, req: Request, ctx: Context) => Response | Promise<Response>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",