weifuwu 0.9.4 → 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 +394 -138
- package/dist/rate-limit.d.ts +3 -1
- package/dist/router.d.ts +1 -0
- package/dist/types.d.ts +2 -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("/");
|
|
@@ -422,10 +440,16 @@ var Router = class _Router {
|
|
|
422
440
|
const remainingSegments = segments.slice(remainingIdx);
|
|
423
441
|
const delegate = (req2, ctx2) => sub.handle(req2, ctx2, remainingSegments, query);
|
|
424
442
|
const allMws = this.globalMws.length + match.pathMws.length === 0 ? [] : [...this.globalMws, ...match.pathMws];
|
|
443
|
+
const levelMount = "/" + segments.slice(0, remainingIdx).join("/");
|
|
425
444
|
try {
|
|
426
|
-
return await this.runChain(allMws, delegate, req, {
|
|
445
|
+
return await this.runChain(allMws, delegate, req, {
|
|
446
|
+
...ctx,
|
|
447
|
+
params: { ...ctx.params, ...match.params },
|
|
448
|
+
mountPath: (ctx.mountPath || "") + levelMount
|
|
449
|
+
});
|
|
427
450
|
} catch (e) {
|
|
428
|
-
|
|
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 });
|
|
429
453
|
}
|
|
430
454
|
}
|
|
431
455
|
if (match?.handler) {
|
|
@@ -435,7 +459,8 @@ var Router = class _Router {
|
|
|
435
459
|
try {
|
|
436
460
|
return await this.runChain(allMws, handler, req, ctxWithMatch);
|
|
437
461
|
} catch (e) {
|
|
438
|
-
|
|
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 });
|
|
439
464
|
}
|
|
440
465
|
}
|
|
441
466
|
if (this.globalMws.length > 0) {
|
|
@@ -443,7 +468,8 @@ var Router = class _Router {
|
|
|
443
468
|
const delegate = () => new Response("Not Found", { status: 404 });
|
|
444
469
|
return await this.runChain(this.globalMws, delegate, req, ctx);
|
|
445
470
|
} catch (e) {
|
|
446
|
-
|
|
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 });
|
|
447
473
|
}
|
|
448
474
|
}
|
|
449
475
|
return new Response("Not Found", { status: 404 });
|
|
@@ -528,8 +554,13 @@ var isDev = process.env.NODE_ENV !== "production";
|
|
|
528
554
|
var _uiDir = "";
|
|
529
555
|
var _allFiles = [];
|
|
530
556
|
var _outDir = "";
|
|
557
|
+
var _router = null;
|
|
558
|
+
var _pagesDir = "";
|
|
559
|
+
var _nodeEntries = {};
|
|
531
560
|
var tailwindCssUrl = null;
|
|
532
561
|
var tailwindCssCode = "";
|
|
562
|
+
var _tailwindPlugin = null;
|
|
563
|
+
var _postcss = null;
|
|
533
564
|
var _cjsRequire = createRequire(import.meta.url);
|
|
534
565
|
var _vmCtx = vm.createContext(Object.create(globalThis));
|
|
535
566
|
function loadSSRModule(code) {
|
|
@@ -708,7 +739,7 @@ function startFileWatcher() {
|
|
|
708
739
|
let timeout = null;
|
|
709
740
|
const pending = /* @__PURE__ */ new Set();
|
|
710
741
|
chokidar.watch(_uiDir, {
|
|
711
|
-
ignored: /(^|[/\\])\.(?!\.)|node_modules
|
|
742
|
+
ignored: /(^|[/\\])\.(?!\.)|node_modules|[/\\]\.weifuwu[/\\]|[/\\]dist[/\\]/,
|
|
712
743
|
persistent: false,
|
|
713
744
|
ignoreInitial: true
|
|
714
745
|
}).on("all", async (event, filePath) => {
|
|
@@ -726,6 +757,8 @@ function startFileWatcher() {
|
|
|
726
757
|
);
|
|
727
758
|
if (allKnown) {
|
|
728
759
|
for (const f of exists) await recompileAndSwap(f, _outDir);
|
|
760
|
+
await reprocessTailwind();
|
|
761
|
+
broadcastReload();
|
|
729
762
|
} else {
|
|
730
763
|
await recompileAll();
|
|
731
764
|
}
|
|
@@ -751,6 +784,7 @@ async function recompileAndSwap(filePath, outDir) {
|
|
|
751
784
|
const name15 = basename(filePath);
|
|
752
785
|
if (name15 === "layout.tsx") {
|
|
753
786
|
layoutModules.set(filePath, mod);
|
|
787
|
+
clientBundleCache.clear();
|
|
754
788
|
} else if (name15 === "route.ts") {
|
|
755
789
|
const handlers = /* @__PURE__ */ new Map();
|
|
756
790
|
for (const m of ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]) {
|
|
@@ -763,14 +797,30 @@ async function recompileAndSwap(filePath, outDir) {
|
|
|
763
797
|
pageModules.set(filePath, mod);
|
|
764
798
|
clientBundleCache.delete(id(filePath));
|
|
765
799
|
}
|
|
766
|
-
await reprocessTailwind();
|
|
767
|
-
broadcastReload();
|
|
768
800
|
} catch (err) {
|
|
769
801
|
console.error("recompile failed:", err.message);
|
|
770
802
|
}
|
|
771
803
|
}
|
|
772
804
|
async function recompileAll() {
|
|
773
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];
|
|
774
824
|
const result = await esbuild.build({
|
|
775
825
|
entryPoints: Object.fromEntries(_allFiles.map((f) => [id(f), f])),
|
|
776
826
|
outdir: _outDir,
|
|
@@ -789,9 +839,54 @@ async function recompileAll() {
|
|
|
789
839
|
const srcPath = _allFiles.find((f) => file.path.endsWith(id(f) + ".js"));
|
|
790
840
|
if (!srcPath) continue;
|
|
791
841
|
const name15 = basename(srcPath);
|
|
792
|
-
if (name15 === "layout.tsx")
|
|
793
|
-
|
|
794
|
-
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;
|
|
795
890
|
}
|
|
796
891
|
clientBundleCache.clear();
|
|
797
892
|
await reprocessTailwind();
|
|
@@ -801,10 +896,9 @@ async function recompileAll() {
|
|
|
801
896
|
}
|
|
802
897
|
}
|
|
803
898
|
async function setupTailwind(uiDir, router) {
|
|
804
|
-
let tailwindPlugin, postcss;
|
|
805
899
|
try {
|
|
806
|
-
|
|
807
|
-
|
|
900
|
+
_tailwindPlugin = (await import("@tailwindcss/postcss")).default;
|
|
901
|
+
_postcss = (await import("postcss")).default;
|
|
808
902
|
} catch {
|
|
809
903
|
return;
|
|
810
904
|
}
|
|
@@ -816,7 +910,7 @@ async function setupTailwind(uiDir, router) {
|
|
|
816
910
|
}
|
|
817
911
|
try {
|
|
818
912
|
const src = readFileSync(inputFile, "utf-8");
|
|
819
|
-
const result = await
|
|
913
|
+
const result = await _postcss([_tailwindPlugin()]).process(src, { from: inputFile });
|
|
820
914
|
tailwindCssCode = result.css;
|
|
821
915
|
} catch (err) {
|
|
822
916
|
console.warn("Tailwind CSS processing failed:", err.message);
|
|
@@ -830,7 +924,7 @@ async function setupTailwind(uiDir, router) {
|
|
|
830
924
|
chokidar.watch(inputFile, { persistent: false }).on("change", async () => {
|
|
831
925
|
try {
|
|
832
926
|
const newSrc = readFileSync(inputFile, "utf-8");
|
|
833
|
-
const newResult = await
|
|
927
|
+
const newResult = await _postcss([_tailwindPlugin()]).process(newSrc, { from: inputFile });
|
|
834
928
|
tailwindCssCode = newResult.css;
|
|
835
929
|
broadcastReload();
|
|
836
930
|
} catch (err) {
|
|
@@ -840,62 +934,77 @@ async function setupTailwind(uiDir, router) {
|
|
|
840
934
|
}
|
|
841
935
|
}
|
|
842
936
|
async function reprocessTailwind() {
|
|
843
|
-
if (!tailwindCssUrl) return;
|
|
937
|
+
if (!tailwindCssUrl || !_postcss || !_tailwindPlugin) return;
|
|
844
938
|
try {
|
|
845
939
|
const inputFile = resolve(_uiDir, "app.css");
|
|
846
940
|
if (!existsSync(inputFile)) return;
|
|
847
|
-
const tailwindPlugin = (await import("@tailwindcss/postcss")).default;
|
|
848
|
-
const postcss = (await import("postcss")).default;
|
|
849
941
|
const src = readFileSync(inputFile, "utf-8");
|
|
850
|
-
const result = await
|
|
942
|
+
const result = await _postcss([_tailwindPlugin()]).process(src, { from: inputFile });
|
|
851
943
|
tailwindCssCode = result.css;
|
|
852
944
|
} catch {
|
|
853
945
|
}
|
|
854
946
|
}
|
|
855
947
|
var clientBundleCache = /* @__PURE__ */ new Map();
|
|
856
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
|
+
}
|
|
857
986
|
async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router) {
|
|
858
987
|
const key = id(entryPath);
|
|
859
988
|
const url = `/__wfw/client/${key}.js`;
|
|
989
|
+
clientBuildParams.set(key, { entryPath, layoutPaths, pagesDir });
|
|
860
990
|
if (!clientRouteLog.get(router)?.has(url)) {
|
|
861
991
|
if (!clientBundleCache.has(key)) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
`let el=createElement(P,p);`,
|
|
878
|
-
layoutsWrap,
|
|
879
|
-
`hydrateRoot(document.getElementById('__weifuwu_root'),el);`
|
|
880
|
-
].join("");
|
|
881
|
-
const result = await esbuild.build({
|
|
882
|
-
stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
|
|
883
|
-
bundle: true,
|
|
884
|
-
format: "esm",
|
|
885
|
-
jsx: "automatic",
|
|
886
|
-
jsxImportSource: "react",
|
|
887
|
-
alias: resolveAliases(),
|
|
888
|
-
write: false,
|
|
889
|
-
minify: true
|
|
890
|
-
});
|
|
891
|
-
clientBundleCache.set(key, result.outputFiles[0].contents);
|
|
892
|
-
} catch (err) {
|
|
893
|
-
console.error("hydration bundle failed:", err);
|
|
894
|
-
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
|
+
}
|
|
895
1007
|
}
|
|
896
|
-
}
|
|
897
|
-
router.get(url, () => {
|
|
898
|
-
const buf = clientBundleCache.get(key);
|
|
899
1008
|
return buf ? new Response(buf, {
|
|
900
1009
|
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
901
1010
|
}) : new Response("", { status: 500 });
|
|
@@ -908,6 +1017,7 @@ async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router)
|
|
|
908
1017
|
}
|
|
909
1018
|
function makeSsrHandler(entryPath, layoutPaths, loadPath, pagesDir, router) {
|
|
910
1019
|
return async (req, ctx) => {
|
|
1020
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
911
1021
|
const pageMod = pageModules.get(entryPath);
|
|
912
1022
|
if (!pageMod) return new Response("", { status: 500 });
|
|
913
1023
|
const Component = pageMod.default;
|
|
@@ -936,21 +1046,21 @@ function makeSsrHandler(entryPath, layoutPaths, loadPath, pagesDir, router) {
|
|
|
936
1046
|
scripts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(allProps)}</script>`);
|
|
937
1047
|
const bundle = await getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router);
|
|
938
1048
|
if (bundle) {
|
|
939
|
-
scripts.push(`<script type="module" src="${bundle.url}"></script>`);
|
|
1049
|
+
scripts.push(`<script type="module" src="${base}${bundle.url}"></script>`);
|
|
940
1050
|
}
|
|
941
|
-
let html = `<!DOCTYPE html>
|
|
942
|
-
${body}
|
|
943
|
-
|
|
1051
|
+
let html = body.startsWith("<!DOCTYPE html>") ? body : `<!DOCTYPE html>
|
|
1052
|
+
${body}`;
|
|
1053
|
+
html += "\n" + scripts.join("\n");
|
|
944
1054
|
if (tailwindCssUrl && html.includes("</head>")) {
|
|
945
1055
|
html = html.replace(
|
|
946
1056
|
"</head>",
|
|
947
|
-
`<link rel="stylesheet" href="${tailwindCssUrl}" />
|
|
1057
|
+
`<link rel="stylesheet" href="${base}${tailwindCssUrl}" />
|
|
948
1058
|
</head>`
|
|
949
1059
|
);
|
|
950
1060
|
}
|
|
951
1061
|
if (isDev) {
|
|
952
1062
|
html += `
|
|
953
|
-
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`;
|
|
1063
|
+
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${base}/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`;
|
|
954
1064
|
}
|
|
955
1065
|
return new Response(html, {
|
|
956
1066
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -961,6 +1071,7 @@ async function tsx(options) {
|
|
|
961
1071
|
const uiDir = resolve(options.dir);
|
|
962
1072
|
const pagesDir = existsSync(join(uiDir, "pages")) ? join(uiDir, "pages") : uiDir;
|
|
963
1073
|
_uiDir = uiDir;
|
|
1074
|
+
_pagesDir = pagesDir;
|
|
964
1075
|
const outDir = join(uiDir, ".weifuwu", "ssr");
|
|
965
1076
|
_outDir = outDir;
|
|
966
1077
|
const pages = scanPages(pagesDir);
|
|
@@ -983,8 +1094,11 @@ async function tsx(options) {
|
|
|
983
1094
|
_allFiles = [...allFiles];
|
|
984
1095
|
await compileAll(_allFiles, outDir, "node", resolveAliases());
|
|
985
1096
|
const router = new Router();
|
|
1097
|
+
_router = router;
|
|
986
1098
|
const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
987
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 };
|
|
988
1102
|
if (p.routeOnly && p.routePath) {
|
|
989
1103
|
const rUrl = compiledUrl(p.routePath, outDir);
|
|
990
1104
|
const modR = await import(rUrl);
|
|
@@ -1049,6 +1163,7 @@ async function tsx(options) {
|
|
|
1049
1163
|
}
|
|
1050
1164
|
}
|
|
1051
1165
|
const handler = async (req, ctx) => {
|
|
1166
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
1052
1167
|
const nfMod = pageModules.get(nfPath);
|
|
1053
1168
|
if (!nfMod) return new Response("Not Found", { status: 404 });
|
|
1054
1169
|
const NfComponent = nfMod.default;
|
|
@@ -1063,18 +1178,18 @@ async function tsx(options) {
|
|
|
1063
1178
|
}, element);
|
|
1064
1179
|
const stream = await renderToReadableStream(element);
|
|
1065
1180
|
const body = await readStream(stream);
|
|
1066
|
-
let html = `<!DOCTYPE html>
|
|
1181
|
+
let html = body.startsWith("<!DOCTYPE html>") ? body : `<!DOCTYPE html>
|
|
1067
1182
|
${body}`;
|
|
1068
1183
|
if (tailwindCssUrl && html.includes("</head>")) {
|
|
1069
1184
|
html = html.replace(
|
|
1070
1185
|
"</head>",
|
|
1071
|
-
`<link rel="stylesheet" href="${tailwindCssUrl}" />
|
|
1186
|
+
`<link rel="stylesheet" href="${base}${tailwindCssUrl}" />
|
|
1072
1187
|
</head>`
|
|
1073
1188
|
);
|
|
1074
1189
|
}
|
|
1075
1190
|
if (isDev) {
|
|
1076
1191
|
html += `
|
|
1077
|
-
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`;
|
|
1192
|
+
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${base}/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`;
|
|
1078
1193
|
}
|
|
1079
1194
|
return new Response(html, {
|
|
1080
1195
|
status: 404,
|
|
@@ -1120,7 +1235,12 @@ function cors(options) {
|
|
|
1120
1235
|
...options
|
|
1121
1236
|
};
|
|
1122
1237
|
function resolveOrigin(requestOrigin) {
|
|
1123
|
-
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
|
+
}
|
|
1124
1244
|
if (Array.isArray(opts.origin)) {
|
|
1125
1245
|
return opts.origin.includes(requestOrigin) ? requestOrigin : "";
|
|
1126
1246
|
}
|
|
@@ -1135,7 +1255,7 @@ function cors(options) {
|
|
|
1135
1255
|
headers.set("Access-Control-Allow-Origin", acao);
|
|
1136
1256
|
if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
|
|
1137
1257
|
if (opts.exposedHeaders?.length) headers.set("Access-Control-Expose-Headers", opts.exposedHeaders.join(", "));
|
|
1138
|
-
headers.set("Vary", "Origin");
|
|
1258
|
+
if (acao !== "*") headers.set("Vary", "Origin");
|
|
1139
1259
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
1140
1260
|
}
|
|
1141
1261
|
return (req, ctx, next) => {
|
|
@@ -1148,7 +1268,7 @@ function cors(options) {
|
|
|
1148
1268
|
headers.set("Access-Control-Allow-Headers", opts.allowedHeaders.join(", "));
|
|
1149
1269
|
if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
|
|
1150
1270
|
if (opts.maxAge != null) headers.set("Access-Control-Max-Age", String(opts.maxAge));
|
|
1151
|
-
headers.set("Vary", "Origin");
|
|
1271
|
+
if (acao !== "*") headers.set("Vary", "Origin");
|
|
1152
1272
|
return new Response(null, { status: 204, headers });
|
|
1153
1273
|
}
|
|
1154
1274
|
if (!acao) return next(req, ctx);
|
|
@@ -1156,20 +1276,23 @@ function cors(options) {
|
|
|
1156
1276
|
};
|
|
1157
1277
|
}
|
|
1158
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
|
+
}
|
|
1159
1282
|
return async (req, ctx, next) => {
|
|
1160
1283
|
const headerName = options.header ?? "Authorization";
|
|
1161
1284
|
let from = "header";
|
|
1162
1285
|
let header = req.headers.get(headerName);
|
|
1163
1286
|
let token = "";
|
|
1164
1287
|
if (header) {
|
|
1165
|
-
token = header;
|
|
1288
|
+
token = header.trim();
|
|
1166
1289
|
if (headerName.toLowerCase() === "authorization") {
|
|
1167
1290
|
const parts = header.split(" ");
|
|
1168
1291
|
if (parts[0]?.toLowerCase() === "bearer") {
|
|
1169
|
-
token = parts.slice(1).join(" ");
|
|
1292
|
+
token = parts.slice(1).join(" ").trim();
|
|
1170
1293
|
}
|
|
1171
1294
|
}
|
|
1172
|
-
} else {
|
|
1295
|
+
} else if (!options.header) {
|
|
1173
1296
|
const url = new URL(req.url);
|
|
1174
1297
|
const qsToken = url.searchParams.get("access_token");
|
|
1175
1298
|
if (qsToken) {
|
|
@@ -1184,7 +1307,12 @@ function auth(options) {
|
|
|
1184
1307
|
});
|
|
1185
1308
|
}
|
|
1186
1309
|
if (options.proxy) {
|
|
1187
|
-
|
|
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
|
+
}
|
|
1188
1316
|
const proxyHeaders = {};
|
|
1189
1317
|
if (from === "header" && header) {
|
|
1190
1318
|
proxyHeaders[headerName] = header;
|
|
@@ -1234,7 +1362,7 @@ function auth(options) {
|
|
|
1234
1362
|
|
|
1235
1363
|
// static.ts
|
|
1236
1364
|
import { createHash as createHash2 } from "node:crypto";
|
|
1237
|
-
import { open } from "node:fs/promises";
|
|
1365
|
+
import { open, realpath } from "node:fs/promises";
|
|
1238
1366
|
import { extname, resolve as resolve2, normalize, sep as sep2 } from "node:path";
|
|
1239
1367
|
function serveStatic(root, options) {
|
|
1240
1368
|
const rootDir = resolve2(root);
|
|
@@ -1252,7 +1380,12 @@ function serveStatic(root, options) {
|
|
|
1252
1380
|
let fileHandle;
|
|
1253
1381
|
try {
|
|
1254
1382
|
fileHandle = await open(filePath, "r");
|
|
1255
|
-
|
|
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
|
+
}
|
|
1256
1389
|
if (stat.isDirectory()) {
|
|
1257
1390
|
await fileHandle.close();
|
|
1258
1391
|
const indexFile = opts.index ?? "index.html";
|
|
@@ -1261,8 +1394,8 @@ function serveStatic(root, options) {
|
|
|
1261
1394
|
return new Response("Forbidden", { status: 403 });
|
|
1262
1395
|
}
|
|
1263
1396
|
fileHandle = await open(filePath, "r");
|
|
1264
|
-
|
|
1265
|
-
if (!
|
|
1397
|
+
stat = await fileHandle.stat();
|
|
1398
|
+
if (!stat.isFile()) {
|
|
1266
1399
|
await fileHandle.close();
|
|
1267
1400
|
return new Response("Not Found", { status: 404 });
|
|
1268
1401
|
}
|
|
@@ -1302,8 +1435,8 @@ var MIME_TYPES = {
|
|
|
1302
1435
|
".html": "text/html; charset=utf-8",
|
|
1303
1436
|
".htm": "text/html; charset=utf-8",
|
|
1304
1437
|
".css": "text/css; charset=utf-8",
|
|
1305
|
-
".js": "
|
|
1306
|
-
".mjs": "
|
|
1438
|
+
".js": "text/javascript; charset=utf-8",
|
|
1439
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
1307
1440
|
".json": "application/json",
|
|
1308
1441
|
".png": "image/png",
|
|
1309
1442
|
".jpg": "image/jpeg",
|
|
@@ -1323,7 +1456,16 @@ var MIME_TYPES = {
|
|
|
1323
1456
|
".pdf": "application/pdf",
|
|
1324
1457
|
".zip": "application/zip",
|
|
1325
1458
|
".wasm": "application/wasm",
|
|
1326
|
-
".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"
|
|
1327
1469
|
};
|
|
1328
1470
|
|
|
1329
1471
|
// validate.ts
|
|
@@ -1369,18 +1511,28 @@ function validate(schemas) {
|
|
|
1369
1511
|
}
|
|
1370
1512
|
}
|
|
1371
1513
|
if (schemas.body) {
|
|
1372
|
-
if (req.
|
|
1514
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
1515
|
+
} else if (req.body === null) {
|
|
1373
1516
|
issues.push({ path: ["body"], message: "Request body is required" });
|
|
1374
1517
|
} else {
|
|
1375
1518
|
const bodyText = await req.text();
|
|
1376
|
-
if (!bodyText
|
|
1519
|
+
if (!bodyText) {
|
|
1377
1520
|
issues.push({ path: ["body"], message: "Request body is required" });
|
|
1378
1521
|
} else {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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")) {
|
|
1383
1530
|
bodyValue = bodyText;
|
|
1531
|
+
} else {
|
|
1532
|
+
try {
|
|
1533
|
+
bodyValue = JSON.parse(bodyText);
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1384
1536
|
}
|
|
1385
1537
|
const result = schemas.body.safeParse(bodyValue);
|
|
1386
1538
|
if (result.success) {
|
|
@@ -1410,14 +1562,20 @@ function getCookies(req) {
|
|
|
1410
1562
|
for (const pair of header.split(";")) {
|
|
1411
1563
|
const idx = pair.indexOf("=");
|
|
1412
1564
|
if (idx === -1) continue;
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
if (name15)
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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;
|
|
1421
1579
|
}
|
|
1422
1580
|
}
|
|
1423
1581
|
return cookies;
|
|
@@ -1444,7 +1602,11 @@ function setCookie(res, name15, value, options) {
|
|
|
1444
1602
|
}
|
|
1445
1603
|
function deleteCookie(res, name15, options) {
|
|
1446
1604
|
const headers = new Headers(res.headers);
|
|
1447
|
-
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
|
+
}));
|
|
1448
1610
|
return new Response(res.body, {
|
|
1449
1611
|
status: res.status,
|
|
1450
1612
|
statusText: res.statusText,
|
|
@@ -1455,12 +1617,43 @@ function deleteCookie(res, name15, options) {
|
|
|
1455
1617
|
// upload.ts
|
|
1456
1618
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
1457
1619
|
import { randomUUID } from "node:crypto";
|
|
1458
|
-
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
|
+
}
|
|
1459
1650
|
function upload(options) {
|
|
1460
1651
|
const saveDir = options?.dir;
|
|
1461
1652
|
return async (req, ctx, next) => {
|
|
1462
1653
|
const ct = req.headers.get("content-type") ?? "";
|
|
1463
1654
|
if (!ct.includes("multipart/form-data")) return next(req, ctx);
|
|
1655
|
+
if (saveDir) await mkdir(saveDir, { recursive: true }).catch(() => {
|
|
1656
|
+
});
|
|
1464
1657
|
let formData;
|
|
1465
1658
|
try {
|
|
1466
1659
|
formData = await req.formData();
|
|
@@ -1471,8 +1664,13 @@ function upload(options) {
|
|
|
1471
1664
|
const fields = {};
|
|
1472
1665
|
for (const [key, value] of formData) {
|
|
1473
1666
|
if (value instanceof File) {
|
|
1474
|
-
if (options?.allowedTypes
|
|
1475
|
-
|
|
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
|
+
}
|
|
1476
1674
|
}
|
|
1477
1675
|
if (options?.maxFileSize && value.size > options.maxFileSize) {
|
|
1478
1676
|
return Response.json({ error: `File too large: ${value.name}` }, { status: 413 });
|
|
@@ -1485,9 +1683,8 @@ function upload(options) {
|
|
|
1485
1683
|
buffer: saveDir ? void 0 : buf
|
|
1486
1684
|
};
|
|
1487
1685
|
if (saveDir) {
|
|
1488
|
-
const safeName = value.name.replace(/[
|
|
1686
|
+
const safeName = value.name.replace(/[/\\\0]/g, "_");
|
|
1489
1687
|
const filePath = join2(saveDir, `${randomUUID()}-${safeName}`);
|
|
1490
|
-
await mkdir(saveDir, { recursive: true });
|
|
1491
1688
|
await writeFile(filePath, buf);
|
|
1492
1689
|
uf.path = filePath;
|
|
1493
1690
|
}
|
|
@@ -1513,7 +1710,11 @@ function rateLimit(options) {
|
|
|
1513
1710
|
const getKey = options?.key ?? ((req) => {
|
|
1514
1711
|
const forwarded = req.headers.get("x-forwarded-for");
|
|
1515
1712
|
if (forwarded) return forwarded.split(",")[0].trim();
|
|
1516
|
-
|
|
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";
|
|
1517
1718
|
});
|
|
1518
1719
|
const message = options?.message ?? "Too Many Requests";
|
|
1519
1720
|
const hits = /* @__PURE__ */ new Map();
|
|
@@ -1524,7 +1725,7 @@ function rateLimit(options) {
|
|
|
1524
1725
|
}
|
|
1525
1726
|
}, window);
|
|
1526
1727
|
if (interval.unref) interval.unref();
|
|
1527
|
-
|
|
1728
|
+
const mw = async (req, ctx, next) => {
|
|
1528
1729
|
const key = getKey(req);
|
|
1529
1730
|
const now = Date.now();
|
|
1530
1731
|
const entry = hits.get(key);
|
|
@@ -1557,10 +1758,15 @@ function rateLimit(options) {
|
|
|
1557
1758
|
headers.set("X-RateLimit-Reset", String(Math.ceil(entry.reset / 1e3)));
|
|
1558
1759
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
1559
1760
|
};
|
|
1761
|
+
mw.stop = () => {
|
|
1762
|
+
clearInterval(interval);
|
|
1763
|
+
hits.clear();
|
|
1764
|
+
};
|
|
1765
|
+
return mw;
|
|
1560
1766
|
}
|
|
1561
1767
|
|
|
1562
1768
|
// compress.ts
|
|
1563
|
-
import { gzipSync, brotliCompressSync, constants } from "node:zlib";
|
|
1769
|
+
import { gzipSync, brotliCompressSync, deflateSync, constants } from "node:zlib";
|
|
1564
1770
|
function compress(options) {
|
|
1565
1771
|
const level = options?.level ?? 6;
|
|
1566
1772
|
const threshold = options?.threshold ?? 1024;
|
|
@@ -1573,7 +1779,7 @@ function compress(options) {
|
|
|
1573
1779
|
return next(req, ctx);
|
|
1574
1780
|
}
|
|
1575
1781
|
const res = await next(req, ctx);
|
|
1576
|
-
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) {
|
|
1577
1783
|
return res;
|
|
1578
1784
|
}
|
|
1579
1785
|
const ce = res.headers.get("content-encoding");
|
|
@@ -1595,14 +1801,15 @@ function compress(options) {
|
|
|
1595
1801
|
compressed = gzipSync(body, { level: Math.min(level, 9) });
|
|
1596
1802
|
encoding = "gzip";
|
|
1597
1803
|
} else {
|
|
1598
|
-
compressed =
|
|
1804
|
+
compressed = deflateSync(body, { level: Math.min(level, 9) });
|
|
1599
1805
|
encoding = "deflate";
|
|
1600
1806
|
}
|
|
1601
1807
|
const headers = new Headers(res.headers);
|
|
1602
1808
|
headers.set("Content-Encoding", encoding);
|
|
1603
1809
|
headers.set("Content-Length", String(compressed.byteLength));
|
|
1604
1810
|
headers.delete("Content-Range");
|
|
1605
|
-
headers.
|
|
1811
|
+
const existingVary = headers.get("Vary");
|
|
1812
|
+
headers.set("Vary", existingVary ? `${existingVary}, Accept-Encoding` : "Accept-Encoding");
|
|
1606
1813
|
return new Response(compressed, {
|
|
1607
1814
|
status: res.status,
|
|
1608
1815
|
statusText: res.statusText,
|
|
@@ -1653,9 +1860,10 @@ async function executeQuery(schema, params, options, req, ctx) {
|
|
|
1653
1860
|
variableValues: params.variables,
|
|
1654
1861
|
operationName: params.operationName
|
|
1655
1862
|
});
|
|
1656
|
-
return Response.json(result, { status: result.errors ?
|
|
1863
|
+
return Response.json(result, { status: result.errors ? 200 : 200 });
|
|
1657
1864
|
}
|
|
1658
1865
|
function graphiqlHTML(endpoint) {
|
|
1866
|
+
const safeEndpoint = endpoint.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/</g, "\\x3C");
|
|
1659
1867
|
return `<!doctype html>
|
|
1660
1868
|
<html lang="en">
|
|
1661
1869
|
<head>
|
|
@@ -1687,7 +1895,7 @@ function graphiqlHTML(endpoint) {
|
|
|
1687
1895
|
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
1688
1896
|
import 'graphiql/setup-workers/esm.sh';
|
|
1689
1897
|
|
|
1690
|
-
const fetcher = createGraphiQLFetcher({ url: "${
|
|
1898
|
+
const fetcher = createGraphiQLFetcher({ url: "${safeEndpoint}" });
|
|
1691
1899
|
|
|
1692
1900
|
function App() {
|
|
1693
1901
|
return React.createElement(GraphiQL, { fetcher });
|
|
@@ -1734,12 +1942,17 @@ function graphql(handler) {
|
|
|
1734
1942
|
}
|
|
1735
1943
|
|
|
1736
1944
|
// ai.ts
|
|
1737
|
-
|
|
1738
|
-
|
|
1945
|
+
var _ai = {
|
|
1946
|
+
streamText: null
|
|
1947
|
+
};
|
|
1948
|
+
async function ai(handler) {
|
|
1949
|
+
if (!_ai.streamText) {
|
|
1950
|
+
_ai.streamText = (await import("ai")).streamText;
|
|
1951
|
+
}
|
|
1739
1952
|
const r = new Router();
|
|
1740
1953
|
r.post("/", async (req, ctx) => {
|
|
1741
1954
|
const options = await handler(req, ctx);
|
|
1742
|
-
const result = streamText(options);
|
|
1955
|
+
const result = _ai.streamText(options);
|
|
1743
1956
|
return result.toTextStreamResponse();
|
|
1744
1957
|
});
|
|
1745
1958
|
return r;
|
|
@@ -1756,9 +1969,11 @@ function tool(def) {
|
|
|
1756
1969
|
}
|
|
1757
1970
|
|
|
1758
1971
|
// workflow/reference.ts
|
|
1972
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1759
1973
|
function getByPath(obj, path2) {
|
|
1760
1974
|
let current = obj;
|
|
1761
1975
|
for (const key of path2) {
|
|
1976
|
+
if (DANGEROUS_KEYS.has(key)) return void 0;
|
|
1762
1977
|
if (current === null || current === void 0) return void 0;
|
|
1763
1978
|
if (typeof current === "object" && key in current) {
|
|
1764
1979
|
current = current[key];
|
|
@@ -1828,27 +2043,42 @@ function evaluateExpression(expr, ctx) {
|
|
|
1828
2043
|
{ op: "!==", fn: (a, b) => a !== b },
|
|
1829
2044
|
{ op: ">=", fn: (a, b) => Number(a) >= Number(b) },
|
|
1830
2045
|
{ op: "<=", fn: (a, b) => Number(a) <= Number(b) },
|
|
1831
|
-
{ op: ">", fn: (a, b) => Number(a) > Number(b) },
|
|
1832
|
-
{ op: "<", fn: (a, b) => Number(a) < Number(b) },
|
|
1833
2046
|
{ op: "==", fn: (a, b) => a == b },
|
|
1834
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) },
|
|
1835
2052
|
{ op: "+", fn: (a, b) => Number(a) + Number(b) },
|
|
1836
2053
|
{ op: "-", fn: (a, b) => Number(a) - Number(b) },
|
|
1837
2054
|
{ op: "*", fn: (a, b) => Number(a) * Number(b) },
|
|
1838
2055
|
{ op: "/", fn: (a, b) => Number(a) / Number(b) },
|
|
1839
|
-
{ op: "%", fn: (a, b) => Number(a) % Number(b) }
|
|
1840
|
-
{ op: "&&", fn: (a, b) => Boolean(a) && Boolean(b) },
|
|
1841
|
-
{ op: "||", fn: (a, b) => Boolean(a) || Boolean(b) }
|
|
2056
|
+
{ op: "%", fn: (a, b) => Number(a) % Number(b) }
|
|
1842
2057
|
];
|
|
2058
|
+
let bestIdx = -1;
|
|
2059
|
+
let bestOp = null;
|
|
2060
|
+
let bestFn = null;
|
|
1843
2061
|
for (const { op, fn } of operators) {
|
|
1844
2062
|
const idx = expr.indexOf(op);
|
|
1845
|
-
if (idx > 0) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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);
|
|
1852
2082
|
}
|
|
1853
2083
|
const trimmed = expr.trim();
|
|
1854
2084
|
if (trimmed === "true") return true;
|
|
@@ -2228,6 +2458,7 @@ data: ${JSON.stringify(event.data)}
|
|
|
2228
2458
|
}
|
|
2229
2459
|
|
|
2230
2460
|
// workflow/route.ts
|
|
2461
|
+
import crypto from "node:crypto";
|
|
2231
2462
|
function workflow(handler) {
|
|
2232
2463
|
const r = new Router();
|
|
2233
2464
|
const sseManager = createSSEManager();
|
|
@@ -2790,7 +3021,12 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2790
3021
|
if (!codeVerifier) {
|
|
2791
3022
|
return Response.json({ error: "invalid_grant", error_description: "code_verifier required" }, { status: 400 });
|
|
2792
3023
|
}
|
|
2793
|
-
|
|
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
|
+
}
|
|
2794
3030
|
if (expected !== stored.code_challenge) {
|
|
2795
3031
|
return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
|
|
2796
3032
|
}
|
|
@@ -3259,7 +3495,7 @@ function sqlTypeForField(field) {
|
|
|
3259
3495
|
}
|
|
3260
3496
|
var RESERVED_SLUGS = /* @__PURE__ */ new Set(["sys", "graphql", "auth"]);
|
|
3261
3497
|
function validateSlug(slug) {
|
|
3262
|
-
if (!/^[a-z][a-z0-9_]
|
|
3498
|
+
if (!/^[a-z][a-z0-9_]*$/.test(slug)) {
|
|
3263
3499
|
return "Slug must start with a letter and contain only lowercase letters, numbers, and underscores";
|
|
3264
3500
|
}
|
|
3265
3501
|
if (RESERVED_SLUGS.has(slug)) {
|
|
@@ -13681,15 +13917,16 @@ function buildRouter2(deps) {
|
|
|
13681
13917
|
return Response.json(rows);
|
|
13682
13918
|
});
|
|
13683
13919
|
r.delete("/agents/:id/knowledge/:docId", async (_req, ctx) => {
|
|
13920
|
+
const agentId = parseInt(ctx.params.id, 10);
|
|
13684
13921
|
const docId = parseInt(ctx.params.docId, 10);
|
|
13685
|
-
await sql`DELETE FROM "_knowledge_documents" WHERE id = ${docId}`;
|
|
13922
|
+
await sql`DELETE FROM "_knowledge_documents" WHERE id = ${docId} AND agent_id = ${agentId}`;
|
|
13686
13923
|
return Response.json({ ok: true });
|
|
13687
13924
|
});
|
|
13688
13925
|
return r;
|
|
13689
13926
|
}
|
|
13690
13927
|
|
|
13691
13928
|
// agent/run.ts
|
|
13692
|
-
import { streamText
|
|
13929
|
+
import { streamText, generateText as generateText2, embed } from "ai";
|
|
13693
13930
|
import { z as z27 } from "zod";
|
|
13694
13931
|
function chunkContent(content, chunkSize = 512, overlap = 64) {
|
|
13695
13932
|
const paragraphs = content.split(/\n\n+/);
|
|
@@ -13754,7 +13991,7 @@ function createRunner(deps) {
|
|
|
13754
13991
|
}
|
|
13755
13992
|
const system = agent2.system_prompt || void 0;
|
|
13756
13993
|
if (params.stream) {
|
|
13757
|
-
const result =
|
|
13994
|
+
const result = streamText({
|
|
13758
13995
|
model,
|
|
13759
13996
|
system,
|
|
13760
13997
|
messages,
|
|
@@ -14325,7 +14562,9 @@ function createManager(config, apps, manager) {
|
|
|
14325
14562
|
if (!config.deployToken) return next(req, ctx);
|
|
14326
14563
|
const header = req.headers.get("authorization") ?? "";
|
|
14327
14564
|
const token = header.replace("Bearer ", "");
|
|
14328
|
-
|
|
14565
|
+
const tokenBuf = Buffer.from(token);
|
|
14566
|
+
const secretBuf = Buffer.from(config.deployToken);
|
|
14567
|
+
if (tokenBuf.length !== secretBuf.length || !crypto4.timingSafeEqual(tokenBuf, secretBuf)) {
|
|
14329
14568
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
14330
14569
|
}
|
|
14331
14570
|
return next(req, ctx);
|
|
@@ -14424,15 +14663,22 @@ function createManager(config, apps, manager) {
|
|
|
14424
14663
|
}
|
|
14425
14664
|
});
|
|
14426
14665
|
router.post("/webhook", async (req) => {
|
|
14666
|
+
const rawBody = await req.text();
|
|
14427
14667
|
if (config.webhookSecret) {
|
|
14428
14668
|
const sig = req.headers.get("x-hub-signature-256") ?? "";
|
|
14429
|
-
const
|
|
14430
|
-
const
|
|
14431
|
-
|
|
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)) {
|
|
14432
14673
|
return Response.json({ error: "invalid signature" }, { status: 401 });
|
|
14433
14674
|
}
|
|
14434
14675
|
}
|
|
14435
|
-
|
|
14676
|
+
let payload;
|
|
14677
|
+
try {
|
|
14678
|
+
payload = JSON.parse(rawBody);
|
|
14679
|
+
} catch {
|
|
14680
|
+
payload = {};
|
|
14681
|
+
}
|
|
14436
14682
|
const repoUrl = payload?.repository?.clone_url ?? payload?.repository?.html_url ?? "";
|
|
14437
14683
|
if (!repoUrl) return Response.json({ deployed: [] });
|
|
14438
14684
|
const deployed = [];
|
|
@@ -14560,6 +14806,12 @@ async function deploy(config) {
|
|
|
14560
14806
|
if (logs.length > 1e3) logs.splice(0, logs.length - 1e3);
|
|
14561
14807
|
};
|
|
14562
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
|
+
}
|
|
14563
14815
|
if (fs.existsSync(path.join(appDir, ".git"))) {
|
|
14564
14816
|
execSync("git pull", { cwd: appDir, stdio: "pipe", timeout: 12e4 });
|
|
14565
14817
|
log("[deploy] git pull done");
|
|
@@ -14567,7 +14819,7 @@ async function deploy(config) {
|
|
|
14567
14819
|
if (fs.existsSync(appDir)) {
|
|
14568
14820
|
fs.rmSync(appDir, { recursive: true });
|
|
14569
14821
|
}
|
|
14570
|
-
execSync(`git clone ${ac.repo} ${appDir}`, { stdio: "pipe", timeout: 12e4 });
|
|
14822
|
+
execSync(`git clone --depth=1 ${ac.repo} ${appDir}`, { stdio: "pipe", timeout: 12e4 });
|
|
14571
14823
|
log("[deploy] git clone done");
|
|
14572
14824
|
if (ac.branch) {
|
|
14573
14825
|
execSync(`git checkout ${ac.branch}`, { cwd: appDir, stdio: "pipe", timeout: 3e4 });
|
|
@@ -14594,6 +14846,10 @@ async function deploy(config) {
|
|
|
14594
14846
|
return;
|
|
14595
14847
|
}
|
|
14596
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
|
+
}
|
|
14597
14853
|
try {
|
|
14598
14854
|
execSync(ac.buildCommand, { cwd: appDir, stdio: "pipe", timeout: 12e4 });
|
|
14599
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
|
@@ -3,7 +3,8 @@ export interface Context {
|
|
|
3
3
|
query: Record<string, string>;
|
|
4
4
|
user?: unknown;
|
|
5
5
|
parsed?: Record<string, unknown>;
|
|
6
|
+
mountPath?: string;
|
|
6
7
|
}
|
|
7
8
|
export type Handler = (req: Request, ctx: Context) => Response | Promise<Response>;
|
|
8
9
|
export type Middleware = (req: Request, ctx: Context, next: Handler) => Response | Promise<Response>;
|
|
9
|
-
export type ErrorHandler = (error: Error, req: Request, ctx: Context) => Response
|
|
10
|
+
export type ErrorHandler = (error: Error, req: Request, ctx: Context) => Response | Promise<Response>;
|