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 +15 -3
- package/dist/index.js +383 -134
- package/dist/rate-limit.d.ts +3 -1
- package/dist/router.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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")
|
|
798
|
-
|
|
799
|
-
else
|
|
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
|
-
|
|
812
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1272
|
-
if (!
|
|
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": "
|
|
1313
|
-
".mjs": "
|
|
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.
|
|
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
|
|
1519
|
+
if (!bodyText) {
|
|
1384
1520
|
issues.push({ path: ["body"], message: "Request body is required" });
|
|
1385
1521
|
} else {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
-
|
|
1421
|
-
|
|
1422
|
-
if (name15)
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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, "", {
|
|
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
|
|
1482
|
-
|
|
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(/[
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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 ?
|
|
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: "${
|
|
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
|
-
|
|
1745
|
-
|
|
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
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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
|
-
|
|
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_]
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
14437
|
-
const
|
|
14438
|
-
|
|
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
|
-
|
|
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");
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -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
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>;
|