weifuwu 0.18.0 → 0.18.2
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/README.md +232 -122
- package/cli.ts +7 -6
- package/dist/agent/types.d.ts +2 -2
- package/dist/ai.d.ts +1 -3
- package/dist/analytics.d.ts +1 -2
- package/dist/cli.js +7 -6
- package/dist/graphql.d.ts +1 -3
- package/dist/iii/types.d.ts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +766 -1244
- package/dist/logdb/types.d.ts +1 -2
- package/dist/messager/types.d.ts +2 -2
- package/dist/opencode/types.d.ts +1 -2
- package/dist/router.d.ts +1 -0
- package/dist/ssr/compile.d.ts +2 -0
- package/dist/ssr/error-boundary.d.ts +2 -0
- package/dist/ssr/index.d.ts +7 -0
- package/dist/ssr/index.js +936 -0
- package/dist/ssr/layout.d.ts +2 -0
- package/dist/ssr/live.d.ts +6 -0
- package/dist/ssr/not-found.d.ts +2 -0
- package/dist/ssr/ssr.d.ts +3 -0
- package/dist/ssr/stream.d.ts +14 -0
- package/dist/ssr/tailwind.d.ts +2 -0
- package/dist/tenant/types.d.ts +2 -2
- package/dist/types.d.ts +5 -0
- package/dist/user/types.d.ts +1 -2
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -270,6 +270,8 @@ var Router = class _Router {
|
|
|
270
270
|
}
|
|
271
271
|
node.pathMws.push(arg2);
|
|
272
272
|
}
|
|
273
|
+
} else if (arg1 instanceof _Router) {
|
|
274
|
+
this._mountRouter("/", arg1);
|
|
273
275
|
} else if (typeof arg1 === "function") {
|
|
274
276
|
this.globalMws.push(arg1);
|
|
275
277
|
}
|
|
@@ -369,13 +371,13 @@ var Router = class _Router {
|
|
|
369
371
|
const mw = match.middlewares[index++];
|
|
370
372
|
return mw(innerReq, ctx2, dispatch);
|
|
371
373
|
}
|
|
372
|
-
return await new Promise((
|
|
374
|
+
return await new Promise((resolve12) => {
|
|
373
375
|
try {
|
|
374
376
|
upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
|
|
375
|
-
|
|
377
|
+
resolve12(new Response(null, { status: 101 }));
|
|
376
378
|
} catch {
|
|
377
379
|
socket.destroy();
|
|
378
|
-
|
|
380
|
+
resolve12(new Response("WebSocket upgrade failed", { status: 500 }));
|
|
379
381
|
}
|
|
380
382
|
});
|
|
381
383
|
};
|
|
@@ -561,19 +563,6 @@ function sendHttpResponseOnSocket(socket, response) {
|
|
|
561
563
|
});
|
|
562
564
|
}
|
|
563
565
|
|
|
564
|
-
// tsx-instance.ts
|
|
565
|
-
import { createElement } from "react";
|
|
566
|
-
import { renderToReadableStream } from "react-dom/server";
|
|
567
|
-
import * as esbuild from "esbuild";
|
|
568
|
-
import { readdirSync, statSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
569
|
-
import { join, relative, resolve as resolve2, sep, dirname, basename } from "node:path";
|
|
570
|
-
import { pathToFileURL } from "node:url";
|
|
571
|
-
import { createHash } from "node:crypto";
|
|
572
|
-
import vm from "node:vm";
|
|
573
|
-
import { createRequire } from "node:module";
|
|
574
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
575
|
-
import chokidar from "chokidar";
|
|
576
|
-
|
|
577
566
|
// tsx-context.ts
|
|
578
567
|
import { createContext } from "react";
|
|
579
568
|
var DEFAULT_CTX = { params: {}, query: {}, parsed: {}, prefs: {}, loaderData: {}, env: {}, user: {} };
|
|
@@ -604,845 +593,6 @@ function setCtx(value) {
|
|
|
604
593
|
}
|
|
605
594
|
var TsxContext = createContext(DEFAULT_CTX);
|
|
606
595
|
|
|
607
|
-
// tsx-instance.ts
|
|
608
|
-
var als = new AsyncLocalStorage();
|
|
609
|
-
__registerAls(() => als.getStore());
|
|
610
|
-
var liveReloadClients = /* @__PURE__ */ new Set();
|
|
611
|
-
function broadcastReload() {
|
|
612
|
-
for (const ws of liveReloadClients) {
|
|
613
|
-
try {
|
|
614
|
-
ws.send("reload");
|
|
615
|
-
} catch {
|
|
616
|
-
liveReloadClients.delete(ws);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
var isDev = process.env.NODE_ENV !== "production";
|
|
621
|
-
var _tailwindPlugin = null;
|
|
622
|
-
var _postcss = null;
|
|
623
|
-
var _cjsRequire = createRequire(import.meta.url);
|
|
624
|
-
function loadSSRModule(code) {
|
|
625
|
-
const ctx = vm.createContext(Object.create(globalThis));
|
|
626
|
-
const mod = { exports: {} };
|
|
627
|
-
ctx.require = (name) => _cjsRequire(name);
|
|
628
|
-
ctx.module = mod;
|
|
629
|
-
ctx.exports = mod.exports;
|
|
630
|
-
new vm.Script(code).runInContext(ctx);
|
|
631
|
-
return mod.exports;
|
|
632
|
-
}
|
|
633
|
-
function id(s) {
|
|
634
|
-
return createHash("md5").update(s).digest("hex").slice(0, 8);
|
|
635
|
-
}
|
|
636
|
-
var _alias = null;
|
|
637
|
-
function resolveAliases() {
|
|
638
|
-
if (_alias) return _alias;
|
|
639
|
-
const configFiles = ["tsconfig.json", "jsconfig.json"];
|
|
640
|
-
for (const file of configFiles) {
|
|
641
|
-
const p = resolve2(file);
|
|
642
|
-
if (existsSync2(p)) {
|
|
643
|
-
try {
|
|
644
|
-
const config = JSON.parse(readFileSync2(p, "utf-8"));
|
|
645
|
-
const paths = config.compilerOptions?.paths;
|
|
646
|
-
if (paths) {
|
|
647
|
-
const alias = {};
|
|
648
|
-
for (const [key, values] of Object.entries(paths)) {
|
|
649
|
-
const cleanKey = key.replace("/*", "");
|
|
650
|
-
const val = values[0]?.replace("/*", "");
|
|
651
|
-
if (val) alias[cleanKey] = resolve2(dirname(p), val);
|
|
652
|
-
}
|
|
653
|
-
_alias = alias;
|
|
654
|
-
return alias;
|
|
655
|
-
}
|
|
656
|
-
} catch {
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
_alias = {};
|
|
661
|
-
return {};
|
|
662
|
-
}
|
|
663
|
-
function scanPages(dir) {
|
|
664
|
-
const pages = [];
|
|
665
|
-
function walk(current) {
|
|
666
|
-
let entries;
|
|
667
|
-
try {
|
|
668
|
-
entries = readdirSync(current);
|
|
669
|
-
} catch {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const dirs = [];
|
|
673
|
-
for (const name of entries) {
|
|
674
|
-
const full = join(current, name);
|
|
675
|
-
const st = statSync(full);
|
|
676
|
-
if (st.isDirectory()) {
|
|
677
|
-
if (!name.startsWith(".")) dirs.push(full);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
const pagePath = join(current, "page.tsx");
|
|
681
|
-
const tsPagePath = join(current, "page.ts");
|
|
682
|
-
let entryPath = "";
|
|
683
|
-
if (existsSync2(pagePath)) {
|
|
684
|
-
entryPath = pagePath;
|
|
685
|
-
} else if (existsSync2(tsPagePath)) {
|
|
686
|
-
entryPath = tsPagePath;
|
|
687
|
-
}
|
|
688
|
-
if (entryPath) {
|
|
689
|
-
let relPath = relative(dir, entryPath).replace(sep, "/");
|
|
690
|
-
relPath = relPath.replace(/\/page\.tsx?$/, "");
|
|
691
|
-
relPath = relPath.replace(/^page\.tsx?$/, "");
|
|
692
|
-
const route = filePathToRoute(relPath);
|
|
693
|
-
const layouts = resolveLayouts(current, dir);
|
|
694
|
-
const loadPath = existsSync2(join(current, "load.ts")) ? join(current, "load.ts") : void 0;
|
|
695
|
-
const rPath = existsSync2(join(current, "route.ts")) ? join(current, "route.ts") : void 0;
|
|
696
|
-
pages.push({
|
|
697
|
-
route,
|
|
698
|
-
entryPath,
|
|
699
|
-
loadPath,
|
|
700
|
-
layouts,
|
|
701
|
-
routePath: rPath
|
|
702
|
-
});
|
|
703
|
-
} else {
|
|
704
|
-
const rPath = join(current, "route.ts");
|
|
705
|
-
if (existsSync2(rPath)) {
|
|
706
|
-
let relPath = relative(dir, rPath).replace(sep, "/");
|
|
707
|
-
relPath = relPath.replace(/\/route\.tsx?$/, "");
|
|
708
|
-
const route = filePathToRoute(relPath);
|
|
709
|
-
pages.push({
|
|
710
|
-
route,
|
|
711
|
-
entryPath: "",
|
|
712
|
-
layouts: [],
|
|
713
|
-
routePath: rPath,
|
|
714
|
-
routeOnly: true
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
for (const d of dirs) walk(d);
|
|
719
|
-
}
|
|
720
|
-
walk(dir);
|
|
721
|
-
return pages;
|
|
722
|
-
}
|
|
723
|
-
function filePathToRoute(relPath) {
|
|
724
|
-
let route = relPath.replace(/\\/g, "/");
|
|
725
|
-
route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
|
|
726
|
-
route = route.replace(/\[(\w+)\]/g, ":$1");
|
|
727
|
-
return route.startsWith("/") ? route : "/" + route;
|
|
728
|
-
}
|
|
729
|
-
function resolveLayouts(dir, pagesDir) {
|
|
730
|
-
const layouts = [];
|
|
731
|
-
let current = dir;
|
|
732
|
-
while (current.startsWith(pagesDir)) {
|
|
733
|
-
const p = join(current, "layout.tsx");
|
|
734
|
-
if (existsSync2(p)) {
|
|
735
|
-
layouts.push(p);
|
|
736
|
-
}
|
|
737
|
-
const parent = dirname(current);
|
|
738
|
-
if (parent === current) break;
|
|
739
|
-
current = parent;
|
|
740
|
-
}
|
|
741
|
-
return layouts.reverse();
|
|
742
|
-
}
|
|
743
|
-
async function compileAll(files, outDir, platform, alias) {
|
|
744
|
-
const entryPoints = {};
|
|
745
|
-
for (const f of files) {
|
|
746
|
-
entryPoints[id(f)] = f;
|
|
747
|
-
}
|
|
748
|
-
const isBrowser = platform === "browser";
|
|
749
|
-
await esbuild.build({
|
|
750
|
-
entryPoints,
|
|
751
|
-
outdir: outDir,
|
|
752
|
-
format: "esm",
|
|
753
|
-
platform: "node",
|
|
754
|
-
jsx: "automatic",
|
|
755
|
-
jsxImportSource: "react",
|
|
756
|
-
bundle: true,
|
|
757
|
-
external: isBrowser ? void 0 : [
|
|
758
|
-
"react",
|
|
759
|
-
"react-dom",
|
|
760
|
-
"esbuild",
|
|
761
|
-
"graphql",
|
|
762
|
-
"ws",
|
|
763
|
-
"zod",
|
|
764
|
-
"@graphql-tools/schema",
|
|
765
|
-
"ai"
|
|
766
|
-
],
|
|
767
|
-
write: true,
|
|
768
|
-
alias,
|
|
769
|
-
allowOverwrite: true
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
function compiledUrl(filePath, outDir) {
|
|
773
|
-
const hash = id(join(outDir, id(filePath)));
|
|
774
|
-
const p = join(outDir, id(filePath) + ".js");
|
|
775
|
-
return pathToFileURL(p).href;
|
|
776
|
-
}
|
|
777
|
-
var TsxInstance = class {
|
|
778
|
-
uiDir;
|
|
779
|
-
pagesDir;
|
|
780
|
-
outDir;
|
|
781
|
-
router;
|
|
782
|
-
pageModules = /* @__PURE__ */ new Map();
|
|
783
|
-
layoutModules = /* @__PURE__ */ new Map();
|
|
784
|
-
loadModules = /* @__PURE__ */ new Map();
|
|
785
|
-
routeModules = /* @__PURE__ */ new Map();
|
|
786
|
-
allFiles = [];
|
|
787
|
-
nodeEntries = {};
|
|
788
|
-
compiledTailwindCss = "";
|
|
789
|
-
// client bundle cache (per-instance)
|
|
790
|
-
clientBundleCache = /* @__PURE__ */ new Map();
|
|
791
|
-
clientBuildParams = /* @__PURE__ */ new Map();
|
|
792
|
-
clientRouteLog = /* @__PURE__ */ new Set();
|
|
793
|
-
// file watchers (dev mode, stored for cleanup)
|
|
794
|
-
watcher = null;
|
|
795
|
-
twWatcher = null;
|
|
796
|
-
debounceTimer = null;
|
|
797
|
-
constructor(options) {
|
|
798
|
-
this.uiDir = resolve2(options.dir);
|
|
799
|
-
this.pagesDir = existsSync2(join(this.uiDir, "pages")) ? join(this.uiDir, "pages") : this.uiDir;
|
|
800
|
-
this.outDir = join(this.uiDir, ".weifuwu", "ssr");
|
|
801
|
-
this.router = new Router();
|
|
802
|
-
}
|
|
803
|
-
async build() {
|
|
804
|
-
const pages = scanPages(this.pagesDir);
|
|
805
|
-
if (pages.length === 0) return attachStop(this.router, this);
|
|
806
|
-
const allFiles = /* @__PURE__ */ new Set();
|
|
807
|
-
for (const p of pages) {
|
|
808
|
-
if (p.entryPath) allFiles.add(p.entryPath);
|
|
809
|
-
if (p.loadPath) allFiles.add(p.loadPath);
|
|
810
|
-
for (const lp of p.layouts) allFiles.add(lp);
|
|
811
|
-
if (p.routePath) allFiles.add(p.routePath);
|
|
812
|
-
}
|
|
813
|
-
const nfPath = join(this.pagesDir, "not-found.tsx");
|
|
814
|
-
const hasNotFound = existsSync2(nfPath);
|
|
815
|
-
if (hasNotFound) {
|
|
816
|
-
allFiles.add(nfPath);
|
|
817
|
-
const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
|
|
818
|
-
for (const lp of rootLayouts) allFiles.add(lp);
|
|
819
|
-
}
|
|
820
|
-
mkdirSync(this.outDir, { recursive: true });
|
|
821
|
-
this.allFiles = [...allFiles];
|
|
822
|
-
await compileAll(this.allFiles, this.outDir, "node", resolveAliases());
|
|
823
|
-
const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
824
|
-
for (const p of pages) {
|
|
825
|
-
const nodeKey = p.entryPath || p.routePath || "";
|
|
826
|
-
this.nodeEntries[nodeKey] = { route: p.route, entryPath: p.entryPath || "", layouts: p.layouts, loadPath: p.loadPath, routePath: p.routePath };
|
|
827
|
-
if (p.routeOnly && p.routePath) {
|
|
828
|
-
const rUrl = compiledUrl(p.routePath, this.outDir);
|
|
829
|
-
const modR = await import(rUrl);
|
|
830
|
-
const handlers = /* @__PURE__ */ new Map();
|
|
831
|
-
for (const m of ["GET", ...methods]) {
|
|
832
|
-
if (modR[m]) handlers.set(m, modR[m]);
|
|
833
|
-
}
|
|
834
|
-
this.routeModules.set(p.routePath, handlers);
|
|
835
|
-
this.router.route(
|
|
836
|
-
"GET",
|
|
837
|
-
p.route,
|
|
838
|
-
(req, ctx) => this.routeModules.get(p.routePath)?.get("GET")?.(req, ctx) ?? new Response("", { status: 501 })
|
|
839
|
-
);
|
|
840
|
-
for (const m of methods) {
|
|
841
|
-
this.router.route(
|
|
842
|
-
m,
|
|
843
|
-
p.route,
|
|
844
|
-
(req, ctx) => this.routeModules.get(p.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
|
|
845
|
-
);
|
|
846
|
-
}
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
const pageUrl = compiledUrl(p.entryPath, this.outDir);
|
|
850
|
-
this.pageModules.set(p.entryPath, await import(pageUrl));
|
|
851
|
-
if (p.loadPath) {
|
|
852
|
-
const loadUrl = compiledUrl(p.loadPath, this.outDir);
|
|
853
|
-
this.loadModules.set(p.loadPath, await import(loadUrl));
|
|
854
|
-
}
|
|
855
|
-
for (const lp of p.layouts) {
|
|
856
|
-
const lUrl = compiledUrl(lp, this.outDir);
|
|
857
|
-
this.layoutModules.set(lp, await import(lUrl));
|
|
858
|
-
}
|
|
859
|
-
if (p.routePath) {
|
|
860
|
-
const rUrl = compiledUrl(p.routePath, this.outDir);
|
|
861
|
-
const modR = await import(rUrl);
|
|
862
|
-
const handlers = /* @__PURE__ */ new Map();
|
|
863
|
-
for (const m of methods) {
|
|
864
|
-
if (modR[m]) handlers.set(m, modR[m]);
|
|
865
|
-
}
|
|
866
|
-
this.routeModules.set(p.routePath, handlers);
|
|
867
|
-
}
|
|
868
|
-
const handler = this.makeSsrHandler(p.entryPath, p.layouts, p.loadPath);
|
|
869
|
-
this.router.get(p.route, handler);
|
|
870
|
-
if (p.routePath) {
|
|
871
|
-
for (const m of methods) {
|
|
872
|
-
this.router.route(
|
|
873
|
-
m,
|
|
874
|
-
p.route,
|
|
875
|
-
(req, ctx) => this.routeModules.get(p.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
if (hasNotFound) {
|
|
881
|
-
const nfUrl = compiledUrl(nfPath, this.outDir);
|
|
882
|
-
this.pageModules.set(nfPath, await import(nfUrl));
|
|
883
|
-
const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
|
|
884
|
-
for (const lp of rootLayouts) {
|
|
885
|
-
if (!this.layoutModules.has(lp)) {
|
|
886
|
-
const lUrl = compiledUrl(lp, this.outDir);
|
|
887
|
-
this.layoutModules.set(lp, await import(lUrl));
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
const handler = async (req, ctx) => {
|
|
891
|
-
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
892
|
-
const nfMod = this.pageModules.get(nfPath);
|
|
893
|
-
if (!nfMod) return new Response("Not Found", { status: 404 });
|
|
894
|
-
const NfComponent = nfMod.default;
|
|
895
|
-
const ctxValue = {
|
|
896
|
-
params: ctx.params,
|
|
897
|
-
query: ctx.query,
|
|
898
|
-
user: ctx.user ?? {},
|
|
899
|
-
parsed: ctx.parsed ?? {},
|
|
900
|
-
prefs: ctx.prefs ?? {},
|
|
901
|
-
loaderData: {},
|
|
902
|
-
env: ctx.env ?? {}
|
|
903
|
-
};
|
|
904
|
-
return als.run(ctxValue, async () => {
|
|
905
|
-
setCtx(ctxValue);
|
|
906
|
-
let element = createElement(
|
|
907
|
-
TsxContext.Provider,
|
|
908
|
-
{ value: ctxValue },
|
|
909
|
-
createElement(NfComponent, { params: ctx.params, query: ctx.query })
|
|
910
|
-
);
|
|
911
|
-
for (let i = rootLayouts.length - 1; i >= 0; i--) {
|
|
912
|
-
const LMod = this.layoutModules.get(rootLayouts[i]);
|
|
913
|
-
if (!LMod) continue;
|
|
914
|
-
element = createElement(LMod.default, { children: element });
|
|
915
|
-
}
|
|
916
|
-
const stream = await renderToReadableStream(element);
|
|
917
|
-
return streamResponse(stream, {
|
|
918
|
-
ctx,
|
|
919
|
-
base,
|
|
920
|
-
compiledTailwindCss: this.compiledTailwindCss,
|
|
921
|
-
isDev,
|
|
922
|
-
status: 404
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
};
|
|
926
|
-
this.router.all("/*", handler);
|
|
927
|
-
}
|
|
928
|
-
for (const p of pages) {
|
|
929
|
-
if (p.entryPath) {
|
|
930
|
-
const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
|
|
931
|
-
this.registerClientBundleRoute(p.entryPath, p.layouts.length > 0 ? p.layouts : rootLayouts, this.pagesDir);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
await this.setupTailwind();
|
|
935
|
-
if (isDev) {
|
|
936
|
-
this.router.ws("/__weifuwu/livereload", {
|
|
937
|
-
open: (ws) => {
|
|
938
|
-
liveReloadClients.add(ws);
|
|
939
|
-
ws.on("close", () => liveReloadClients.delete(ws));
|
|
940
|
-
ws.on("error", () => liveReloadClients.delete(ws));
|
|
941
|
-
}
|
|
942
|
-
});
|
|
943
|
-
this.startFileWatcher();
|
|
944
|
-
}
|
|
945
|
-
return attachStop(this.router, this);
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Clean up file watchers and pending timers. Call when shutting down
|
|
949
|
-
* to prevent resource leaks.
|
|
950
|
-
*/
|
|
951
|
-
stop() {
|
|
952
|
-
this.watcher?.close();
|
|
953
|
-
this.twWatcher?.close();
|
|
954
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
955
|
-
this.debounceTimer = null;
|
|
956
|
-
}
|
|
957
|
-
// ── Tailwind CSS ──────────────────────────────────────────────────────────
|
|
958
|
-
async compileTailwind() {
|
|
959
|
-
if (this.compiledTailwindCss) return this.compiledTailwindCss;
|
|
960
|
-
try {
|
|
961
|
-
_tailwindPlugin ??= (await import("@tailwindcss/postcss")).default;
|
|
962
|
-
_postcss ??= (await import("postcss")).default;
|
|
963
|
-
} catch {
|
|
964
|
-
return "";
|
|
965
|
-
}
|
|
966
|
-
const inputFile = resolve2(this.uiDir, "app.css");
|
|
967
|
-
if (!existsSync2(inputFile)) {
|
|
968
|
-
mkdirSync(this.uiDir, { recursive: true });
|
|
969
|
-
writeFileSync(inputFile, '@import "tailwindcss"\n', "utf-8");
|
|
970
|
-
console.log("\u2139 weifuwu/tsx: created " + relative(process.cwd(), inputFile));
|
|
971
|
-
}
|
|
972
|
-
try {
|
|
973
|
-
let src = readFileSync2(inputFile, "utf-8");
|
|
974
|
-
const sourceRel = relative(this.uiDir, this.pagesDir) || ".";
|
|
975
|
-
const sourcePath = sourceRel === "." ? "./" : `./${sourceRel}/`;
|
|
976
|
-
src = `@source "${sourcePath}";
|
|
977
|
-
${src}`;
|
|
978
|
-
const result = await _postcss([_tailwindPlugin()]).process(src, { from: inputFile });
|
|
979
|
-
this.compiledTailwindCss = result.css;
|
|
980
|
-
} catch (err) {
|
|
981
|
-
console.warn("Tailwind CSS processing failed:", err.message);
|
|
982
|
-
}
|
|
983
|
-
return this.compiledTailwindCss;
|
|
984
|
-
}
|
|
985
|
-
async setupTailwind() {
|
|
986
|
-
await this.compileTailwind();
|
|
987
|
-
this.router.get("/__wfw/style.css", () => new Response(this.compiledTailwindCss || "", {
|
|
988
|
-
headers: { "content-type": "text/css; charset=utf-8" }
|
|
989
|
-
}));
|
|
990
|
-
if (isDev) {
|
|
991
|
-
const inputFile = resolve2(this.uiDir, "app.css");
|
|
992
|
-
this.twWatcher = chokidar.watch(inputFile, { persistent: false });
|
|
993
|
-
this.twWatcher.on("change", async () => {
|
|
994
|
-
this.compiledTailwindCss = "";
|
|
995
|
-
await this.compileTailwind();
|
|
996
|
-
broadcastReload();
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
// ── client bundle ─────────────────────────────────────────────────────────
|
|
1001
|
-
async buildClientBundle(entryPath, layoutPaths, pagesDir) {
|
|
1002
|
-
try {
|
|
1003
|
-
const layoutImports = layoutPaths.map((p) => `import${JSON.stringify(p)};`).join("");
|
|
1004
|
-
const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
|
|
1005
|
-
const code = [
|
|
1006
|
-
layoutImports,
|
|
1007
|
-
`import{hydrateRoot}from'react-dom/client';`,
|
|
1008
|
-
`import{createElement,useState,useEffect}from'react';`,
|
|
1009
|
-
`import{TsxContext}from'weifuwu/react';`,
|
|
1010
|
-
`import P from${JSON.stringify(entryPath)};`,
|
|
1011
|
-
`var setCtx=${_sc};`,
|
|
1012
|
-
`const c=document.getElementById('__weifuwu_root');`,
|
|
1013
|
-
`if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
|
|
1014
|
-
`if(!window.__WFW_ROOT){`,
|
|
1015
|
-
`function App(){`,
|
|
1016
|
-
`const[p,setP]=useState({C:P});`,
|
|
1017
|
-
`useEffect(()=>{window.__WFW_SET_PAGE=(C)=>{setCtx({loaderData:window.__WEIFUWU_PROPS});setP({C})}},[]);`,
|
|
1018
|
-
`const ctx=window.__WEIFUWU_CTX||{};`,
|
|
1019
|
-
`return createElement(TsxContext.Provider,{value:ctx},`,
|
|
1020
|
-
`createElement(p.C,null))`,
|
|
1021
|
-
`}`,
|
|
1022
|
-
`window.__WFW_ROOT=hydrateRoot(c,createElement(App));`,
|
|
1023
|
-
`}else{`,
|
|
1024
|
-
`window.__WFW_SET_PAGE?.(P);`,
|
|
1025
|
-
`}`
|
|
1026
|
-
].join("");
|
|
1027
|
-
const publicEnv = {};
|
|
1028
|
-
for (const key of Object.keys(process.env)) {
|
|
1029
|
-
if (key.startsWith("WEIFUWU_PUBLIC_")) {
|
|
1030
|
-
publicEnv[`process.env.${key}`] = JSON.stringify(process.env[key]);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
const result = await esbuild.build({
|
|
1034
|
-
stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
|
|
1035
|
-
bundle: true,
|
|
1036
|
-
format: "esm",
|
|
1037
|
-
jsx: "automatic",
|
|
1038
|
-
jsxImportSource: "react",
|
|
1039
|
-
alias: resolveAliases(),
|
|
1040
|
-
banner: { js: "self.process={env:{}};" },
|
|
1041
|
-
define: Object.keys(publicEnv).length > 0 ? publicEnv : void 0,
|
|
1042
|
-
loader: { ".node": "empty" },
|
|
1043
|
-
write: false,
|
|
1044
|
-
minify: true
|
|
1045
|
-
});
|
|
1046
|
-
return result.outputFiles[0].contents;
|
|
1047
|
-
} catch (err) {
|
|
1048
|
-
console.error("hydration bundle failed:", err);
|
|
1049
|
-
return null;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
async getOrBuildClientBundle(entryPath, layoutPaths, pagesDir) {
|
|
1053
|
-
const key = id(entryPath);
|
|
1054
|
-
const url = `/__wfw/client/${key}.js`;
|
|
1055
|
-
this.clientBuildParams.set(key, { entryPath, layoutPaths, pagesDir });
|
|
1056
|
-
if (!this.clientBundleCache.has(key)) {
|
|
1057
|
-
const buf = await this.buildClientBundle(entryPath, layoutPaths, pagesDir);
|
|
1058
|
-
if (!buf) return null;
|
|
1059
|
-
this.clientBundleCache.set(key, buf);
|
|
1060
|
-
}
|
|
1061
|
-
return { url };
|
|
1062
|
-
}
|
|
1063
|
-
registerClientBundleRoute(entryPath, layoutPaths, pagesDir) {
|
|
1064
|
-
const key = id(entryPath);
|
|
1065
|
-
const url = `/__wfw/client/${key}.js`;
|
|
1066
|
-
this.clientBuildParams.set(key, { entryPath, layoutPaths, pagesDir });
|
|
1067
|
-
if (!this.clientRouteLog.has(url)) {
|
|
1068
|
-
this.router.get(url, async () => {
|
|
1069
|
-
let buf = this.clientBundleCache.get(key);
|
|
1070
|
-
if (!buf) {
|
|
1071
|
-
const params = this.clientBuildParams.get(key);
|
|
1072
|
-
if (params) {
|
|
1073
|
-
const rebuilt = await this.buildClientBundle(params.entryPath, params.layoutPaths, params.pagesDir);
|
|
1074
|
-
if (rebuilt) {
|
|
1075
|
-
this.clientBundleCache.set(key, rebuilt);
|
|
1076
|
-
buf = rebuilt;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
return buf ? new Response(buf, {
|
|
1081
|
-
headers: { "content-type": "application/javascript; charset=utf-8" }
|
|
1082
|
-
}) : new Response("", { status: 500 });
|
|
1083
|
-
});
|
|
1084
|
-
this.clientRouteLog.add(url);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
// ── SSR handler ───────────────────────────────────────────────────────────
|
|
1088
|
-
makeSsrHandler(entryPath, layoutPaths, loadPath) {
|
|
1089
|
-
return async (req, ctx) => {
|
|
1090
|
-
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
1091
|
-
const pageMod = this.pageModules.get(entryPath);
|
|
1092
|
-
if (!pageMod) return new Response("", { status: 500 });
|
|
1093
|
-
const Component = pageMod.default;
|
|
1094
|
-
const loadMod = loadPath ? this.loadModules.get(loadPath) : void 0;
|
|
1095
|
-
const loadFn = loadMod?.default;
|
|
1096
|
-
const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {};
|
|
1097
|
-
const ctxValue = {
|
|
1098
|
-
params: ctx.params,
|
|
1099
|
-
query: ctx.query,
|
|
1100
|
-
user: ctx.user ?? {},
|
|
1101
|
-
parsed: ctx.parsed ?? {},
|
|
1102
|
-
prefs: ctx.prefs ?? {},
|
|
1103
|
-
loaderData: loadProps,
|
|
1104
|
-
env: ctx.env ?? {}
|
|
1105
|
-
};
|
|
1106
|
-
return als.run(ctxValue, async () => {
|
|
1107
|
-
setCtx(ctxValue);
|
|
1108
|
-
let element = createElement(
|
|
1109
|
-
TsxContext.Provider,
|
|
1110
|
-
{ value: ctxValue },
|
|
1111
|
-
createElement(
|
|
1112
|
-
"div",
|
|
1113
|
-
{ id: "__weifuwu_root" },
|
|
1114
|
-
createElement(Component, null)
|
|
1115
|
-
)
|
|
1116
|
-
);
|
|
1117
|
-
if (layoutPaths.length === 0) {
|
|
1118
|
-
element = createElement(
|
|
1119
|
-
"html",
|
|
1120
|
-
{ lang: "en" },
|
|
1121
|
-
createElement(
|
|
1122
|
-
"head",
|
|
1123
|
-
null,
|
|
1124
|
-
createElement("meta", { charSet: "utf-8" }),
|
|
1125
|
-
createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
1126
|
-
createElement("title", null, "weifuwu")
|
|
1127
|
-
),
|
|
1128
|
-
createElement("body", null, element)
|
|
1129
|
-
);
|
|
1130
|
-
} else {
|
|
1131
|
-
for (let i = layoutPaths.length - 1; i >= 0; i--) {
|
|
1132
|
-
const lp = layoutPaths[i];
|
|
1133
|
-
const LMod = this.layoutModules.get(lp);
|
|
1134
|
-
if (!LMod) continue;
|
|
1135
|
-
const Layout = LMod.default;
|
|
1136
|
-
const isRoot = i === 0;
|
|
1137
|
-
element = createElement(
|
|
1138
|
-
Layout,
|
|
1139
|
-
{ children: element }
|
|
1140
|
-
);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
const bundle = await this.getOrBuildClientBundle(entryPath, layoutPaths, this.pagesDir);
|
|
1144
|
-
const stream = await renderToReadableStream(element);
|
|
1145
|
-
return streamResponse(stream, {
|
|
1146
|
-
ctx,
|
|
1147
|
-
base,
|
|
1148
|
-
compiledTailwindCss: this.compiledTailwindCss,
|
|
1149
|
-
isDev,
|
|
1150
|
-
bundle,
|
|
1151
|
-
loaderData: loadProps
|
|
1152
|
-
});
|
|
1153
|
-
});
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
// ── dev file watcher ──────────────────────────────────────────────────────
|
|
1157
|
-
startFileWatcher() {
|
|
1158
|
-
const pending = /* @__PURE__ */ new Set();
|
|
1159
|
-
this.watcher = chokidar.watch(this.uiDir, {
|
|
1160
|
-
ignored: /(^|[/\\])\.(?!\.)|node_modules|[/\\]\.weifuwu[/\\]|[/\\]dist[/\\]/,
|
|
1161
|
-
persistent: false,
|
|
1162
|
-
ignoreInitial: true
|
|
1163
|
-
});
|
|
1164
|
-
this.watcher.on("all", async (event, filePath) => {
|
|
1165
|
-
if (event !== "change" && event !== "add") return;
|
|
1166
|
-
if (!/\.tsx?$/.test(filePath)) return;
|
|
1167
|
-
pending.add(filePath);
|
|
1168
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1169
|
-
this.debounceTimer = setTimeout(async () => {
|
|
1170
|
-
this.debounceTimer = null;
|
|
1171
|
-
const files = [...pending];
|
|
1172
|
-
pending.clear();
|
|
1173
|
-
const exists = files.filter((f) => existsSync2(f));
|
|
1174
|
-
const allKnown = exists.every(
|
|
1175
|
-
(f) => this.pageModules.has(f) || this.layoutModules.has(f) || this.loadModules.has(f) || this.routeModules.has(f)
|
|
1176
|
-
);
|
|
1177
|
-
if (allKnown) {
|
|
1178
|
-
for (const f of exists) await this.recompileAndSwap(f);
|
|
1179
|
-
this.compiledTailwindCss = "";
|
|
1180
|
-
await this.compileTailwind();
|
|
1181
|
-
broadcastReload();
|
|
1182
|
-
} else {
|
|
1183
|
-
await this.recompileAll();
|
|
1184
|
-
}
|
|
1185
|
-
}, 50);
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
async recompileAndSwap(filePath) {
|
|
1189
|
-
try {
|
|
1190
|
-
const result = await esbuild.build({
|
|
1191
|
-
entryPoints: { [id(filePath)]: filePath },
|
|
1192
|
-
outdir: this.outDir,
|
|
1193
|
-
format: "cjs",
|
|
1194
|
-
platform: "node",
|
|
1195
|
-
jsx: "automatic",
|
|
1196
|
-
jsxImportSource: "react",
|
|
1197
|
-
bundle: true,
|
|
1198
|
-
external: ["react", "react-dom", "esbuild", "graphql", "ws", "zod", "@graphql-tools/schema", "ai"],
|
|
1199
|
-
alias: resolveAliases(),
|
|
1200
|
-
write: false
|
|
1201
|
-
});
|
|
1202
|
-
const code = new TextDecoder().decode(result.outputFiles[0].contents);
|
|
1203
|
-
const mod = loadSSRModule(code);
|
|
1204
|
-
const name = basename(filePath);
|
|
1205
|
-
if (name === "layout.tsx") {
|
|
1206
|
-
this.layoutModules.set(filePath, mod);
|
|
1207
|
-
this.clientBundleCache.clear();
|
|
1208
|
-
} else if (name === "route.ts") {
|
|
1209
|
-
const handlers = /* @__PURE__ */ new Map();
|
|
1210
|
-
for (const m of ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]) {
|
|
1211
|
-
if (mod[m]) handlers.set(m, mod[m]);
|
|
1212
|
-
}
|
|
1213
|
-
this.routeModules.set(filePath, handlers);
|
|
1214
|
-
} else if (name === "load.ts") {
|
|
1215
|
-
this.loadModules.set(filePath, mod);
|
|
1216
|
-
} else {
|
|
1217
|
-
this.pageModules.set(filePath, mod);
|
|
1218
|
-
this.clientBundleCache.delete(id(filePath));
|
|
1219
|
-
}
|
|
1220
|
-
} catch (err) {
|
|
1221
|
-
console.error("recompile failed:", err.message);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
async recompileAll() {
|
|
1225
|
-
try {
|
|
1226
|
-
const freshPages = scanPages(this.pagesDir);
|
|
1227
|
-
const freshFiles = /* @__PURE__ */ new Set();
|
|
1228
|
-
const nodeEntries = {};
|
|
1229
|
-
for (const p of freshPages) {
|
|
1230
|
-
const nodeKey = p.entryPath || p.routePath || "";
|
|
1231
|
-
nodeEntries[nodeKey] = { route: p.route, entryPath: p.entryPath, layouts: p.layouts, loadPath: p.loadPath, routePath: p.routePath };
|
|
1232
|
-
if (p.entryPath) freshFiles.add(p.entryPath);
|
|
1233
|
-
if (p.loadPath) freshFiles.add(p.loadPath);
|
|
1234
|
-
for (const lp of p.layouts) freshFiles.add(lp);
|
|
1235
|
-
if (p.routePath) freshFiles.add(p.routePath);
|
|
1236
|
-
}
|
|
1237
|
-
const nfPath = join(this.pagesDir, "not-found.tsx");
|
|
1238
|
-
if (existsSync2(nfPath)) {
|
|
1239
|
-
freshFiles.add(nfPath);
|
|
1240
|
-
const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
|
|
1241
|
-
for (const lp of rootLayouts) freshFiles.add(lp);
|
|
1242
|
-
}
|
|
1243
|
-
this.allFiles = [...freshFiles];
|
|
1244
|
-
const result = await esbuild.build({
|
|
1245
|
-
entryPoints: Object.fromEntries(this.allFiles.map((f) => [id(f), f])),
|
|
1246
|
-
outdir: this.outDir,
|
|
1247
|
-
format: "cjs",
|
|
1248
|
-
platform: "node",
|
|
1249
|
-
jsx: "automatic",
|
|
1250
|
-
jsxImportSource: "react",
|
|
1251
|
-
bundle: true,
|
|
1252
|
-
external: ["react", "react-dom", "esbuild", "graphql", "ws", "zod", "@graphql-tools/schema", "ai"],
|
|
1253
|
-
alias: resolveAliases(),
|
|
1254
|
-
write: false
|
|
1255
|
-
});
|
|
1256
|
-
for (const file of result.outputFiles) {
|
|
1257
|
-
const code = new TextDecoder().decode(file.contents);
|
|
1258
|
-
const mod = loadSSRModule(code);
|
|
1259
|
-
const srcPath = this.allFiles.find((f) => file.path.endsWith(id(f) + ".js"));
|
|
1260
|
-
if (!srcPath) continue;
|
|
1261
|
-
const name = basename(srcPath);
|
|
1262
|
-
if (name === "layout.tsx") {
|
|
1263
|
-
this.layoutModules.set(srcPath, mod);
|
|
1264
|
-
} else if (name === "route.ts") {
|
|
1265
|
-
const handlers = /* @__PURE__ */ new Map();
|
|
1266
|
-
for (const m of ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]) {
|
|
1267
|
-
if (mod[m]) handlers.set(m, mod[m]);
|
|
1268
|
-
}
|
|
1269
|
-
this.routeModules.set(srcPath, handlers);
|
|
1270
|
-
} else if (name === "load.ts") {
|
|
1271
|
-
this.loadModules.set(srcPath, mod);
|
|
1272
|
-
} else if (name !== "not-found.tsx") {
|
|
1273
|
-
this.pageModules.set(srcPath, mod);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
1277
|
-
for (const [key, entry] of Object.entries(nodeEntries)) {
|
|
1278
|
-
if (this.nodeEntries[key]) continue;
|
|
1279
|
-
if (entry.routePath && !entry.entryPath) {
|
|
1280
|
-
this.router.route(
|
|
1281
|
-
"GET",
|
|
1282
|
-
entry.route,
|
|
1283
|
-
(req, ctx) => this.routeModules.get(entry.routePath)?.get("GET")?.(req, ctx) ?? new Response("", { status: 501 })
|
|
1284
|
-
);
|
|
1285
|
-
for (const m of methods) {
|
|
1286
|
-
this.router.route(
|
|
1287
|
-
m,
|
|
1288
|
-
entry.route,
|
|
1289
|
-
(req, ctx) => this.routeModules.get(entry.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
if (entry.entryPath) {
|
|
1294
|
-
const handler = this.makeSsrHandler(entry.entryPath, entry.layouts, entry.loadPath);
|
|
1295
|
-
this.router.get(entry.route, handler);
|
|
1296
|
-
if (entry.routePath) {
|
|
1297
|
-
for (const m of methods) {
|
|
1298
|
-
this.router.route(
|
|
1299
|
-
m,
|
|
1300
|
-
entry.route,
|
|
1301
|
-
(req, ctx) => this.routeModules.get(entry.routePath)?.get(m)?.(req, ctx) ?? new Response("", { status: 501 })
|
|
1302
|
-
);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
console.log("\u2139 weifuwu/tsx: registered new route " + entry.route);
|
|
1307
|
-
}
|
|
1308
|
-
this.nodeEntries = nodeEntries;
|
|
1309
|
-
this.clientBundleCache.clear();
|
|
1310
|
-
this.compiledTailwindCss = "";
|
|
1311
|
-
await this.compileTailwind();
|
|
1312
|
-
broadcastReload();
|
|
1313
|
-
} catch (err) {
|
|
1314
|
-
console.error("recompile all failed:", err.message);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
function attachStop(router, instance) {
|
|
1319
|
-
;
|
|
1320
|
-
router.stop = () => instance.stop();
|
|
1321
|
-
return router;
|
|
1322
|
-
}
|
|
1323
|
-
function streamResponse(reactStream, opts) {
|
|
1324
|
-
const decoder = new TextDecoder();
|
|
1325
|
-
const encoder2 = new TextEncoder();
|
|
1326
|
-
const headPayload = buildHeadPayload(opts);
|
|
1327
|
-
let buffer = "";
|
|
1328
|
-
let headFlushed = false;
|
|
1329
|
-
let extractedHead = "";
|
|
1330
|
-
const output = new ReadableStream({
|
|
1331
|
-
async start(controller) {
|
|
1332
|
-
try {
|
|
1333
|
-
const reader = reactStream.getReader();
|
|
1334
|
-
async function push(chunk) {
|
|
1335
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
1336
|
-
if (!extractedHead) {
|
|
1337
|
-
const m = buffer.match(/<template id="__wfw_head">([\s\S]*?)<\/template>/);
|
|
1338
|
-
if (m) {
|
|
1339
|
-
extractedHead = m[1];
|
|
1340
|
-
buffer = buffer.replace(m[0], "");
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
if (!headFlushed) {
|
|
1344
|
-
const idx = buffer.indexOf("</head>");
|
|
1345
|
-
if (idx !== -1) {
|
|
1346
|
-
const before = buffer.slice(0, idx);
|
|
1347
|
-
let injection = "";
|
|
1348
|
-
if (extractedHead) injection += "\n" + extractedHead;
|
|
1349
|
-
injection += headPayload;
|
|
1350
|
-
controller.enqueue(encoder2.encode(before + injection));
|
|
1351
|
-
buffer = buffer.slice(idx);
|
|
1352
|
-
headFlushed = true;
|
|
1353
|
-
}
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
controller.enqueue(encoder2.encode(buffer));
|
|
1357
|
-
buffer = "";
|
|
1358
|
-
}
|
|
1359
|
-
while (true) {
|
|
1360
|
-
const { done, value } = await reader.read();
|
|
1361
|
-
if (done) break;
|
|
1362
|
-
await push(value);
|
|
1363
|
-
}
|
|
1364
|
-
buffer = buffer.replace(/<template id="__wfw_head">[\s\S]*?<\/template>/g, "");
|
|
1365
|
-
if (buffer) controller.enqueue(encoder2.encode(buffer));
|
|
1366
|
-
const body = buildBodyScripts(opts);
|
|
1367
|
-
if (body) controller.enqueue(encoder2.encode("\n" + body));
|
|
1368
|
-
if (opts.isDev) {
|
|
1369
|
-
controller.enqueue(encoder2.encode(
|
|
1370
|
-
`
|
|
1371
|
-
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${opts.base}/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`
|
|
1372
|
-
));
|
|
1373
|
-
}
|
|
1374
|
-
} catch (err) {
|
|
1375
|
-
const fallback = `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>500</title></head><body><h1>500 - Internal Server Error</h1></body></html>`;
|
|
1376
|
-
controller.enqueue(encoder2.encode(fallback));
|
|
1377
|
-
} finally {
|
|
1378
|
-
controller.close();
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
return new Response(output, {
|
|
1383
|
-
status: opts.status ?? 200,
|
|
1384
|
-
headers: { "content-type": "text/html; charset=utf-8" }
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
var _publicEnv = null;
|
|
1388
|
-
function getPublicEnv() {
|
|
1389
|
-
if (_publicEnv) return _publicEnv;
|
|
1390
|
-
_publicEnv = {};
|
|
1391
|
-
for (const key of Object.keys(process.env)) {
|
|
1392
|
-
if (key.startsWith("WEIFUWU_PUBLIC_")) {
|
|
1393
|
-
_publicEnv[key] = process.env[key];
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
return _publicEnv;
|
|
1397
|
-
}
|
|
1398
|
-
function buildHeadPayload(opts) {
|
|
1399
|
-
const { ctx, base, compiledTailwindCss } = opts;
|
|
1400
|
-
let result = "";
|
|
1401
|
-
if (ctx.prefs?.theme) {
|
|
1402
|
-
result += `<script>!function(){var t=(document.cookie.match(/(?:^|;\\s*)theme=([^;]+)/)||[])[1]||'system';if(t==='system'){t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'}document.documentElement.setAttribute('data-theme',t)}()</script>
|
|
1403
|
-
`;
|
|
1404
|
-
}
|
|
1405
|
-
if (compiledTailwindCss) {
|
|
1406
|
-
result += `<link rel="stylesheet" href="${base}/__wfw/style.css" />
|
|
1407
|
-
`;
|
|
1408
|
-
}
|
|
1409
|
-
const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
|
|
1410
|
-
if (localeData && Object.keys(localeData).length > 0) {
|
|
1411
|
-
result += `<script>window.__LOCALE_DATA__=${JSON.stringify(localeData)}</script>
|
|
1412
|
-
`;
|
|
1413
|
-
}
|
|
1414
|
-
const ctxData = {
|
|
1415
|
-
params: ctx.params,
|
|
1416
|
-
query: ctx.query,
|
|
1417
|
-
user: ctx.user,
|
|
1418
|
-
parsed: ctx.parsed,
|
|
1419
|
-
prefs: ctx.prefs
|
|
1420
|
-
};
|
|
1421
|
-
const publicEnv = getPublicEnv();
|
|
1422
|
-
if (Object.keys(publicEnv).length > 0) {
|
|
1423
|
-
ctxData.env = publicEnv;
|
|
1424
|
-
}
|
|
1425
|
-
result += `<script>window.__WEIFUWU_CTX=${JSON.stringify(ctxData)}</script>
|
|
1426
|
-
`;
|
|
1427
|
-
return result;
|
|
1428
|
-
}
|
|
1429
|
-
function buildBodyScripts(opts) {
|
|
1430
|
-
const parts = [];
|
|
1431
|
-
if (opts.loaderData && Object.keys(opts.loaderData).length > 0) {
|
|
1432
|
-
parts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(opts.loaderData)}</script>`);
|
|
1433
|
-
}
|
|
1434
|
-
if (opts.bundle) {
|
|
1435
|
-
parts.push(`<script type="module" src="${opts.base}${opts.bundle.url}"></script>`);
|
|
1436
|
-
}
|
|
1437
|
-
return parts.join("\n");
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
// tsx.ts
|
|
1441
|
-
async function tsx(options) {
|
|
1442
|
-
const instance = new TsxInstance(options);
|
|
1443
|
-
return instance.build();
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
596
|
// middleware.ts
|
|
1447
597
|
function logger(options) {
|
|
1448
598
|
return async (req, ctx, next) => {
|
|
@@ -1598,12 +748,12 @@ function auth(options) {
|
|
|
1598
748
|
}
|
|
1599
749
|
|
|
1600
750
|
// static.ts
|
|
1601
|
-
import { createHash
|
|
751
|
+
import { createHash } from "node:crypto";
|
|
1602
752
|
import { open, realpath } from "node:fs/promises";
|
|
1603
|
-
import { extname, resolve as
|
|
753
|
+
import { extname, resolve as resolve2, normalize, sep } from "node:path";
|
|
1604
754
|
import { Readable } from "node:stream";
|
|
1605
755
|
function serveStatic(root, options) {
|
|
1606
|
-
const rootDir =
|
|
756
|
+
const rootDir = resolve2(root);
|
|
1607
757
|
const opts = options ?? {};
|
|
1608
758
|
return async (req, ctx) => {
|
|
1609
759
|
const relativePath = ctx.params["*"] ?? new URL(req.url).pathname.slice(1);
|
|
@@ -1611,8 +761,8 @@ function serveStatic(root, options) {
|
|
|
1611
761
|
if (decoded.includes("..") || decoded.includes("\0")) {
|
|
1612
762
|
return new Response("Forbidden", { status: 403 });
|
|
1613
763
|
}
|
|
1614
|
-
let filePath = normalize(
|
|
1615
|
-
if (!filePath.startsWith(rootDir +
|
|
764
|
+
let filePath = normalize(resolve2(rootDir, decoded));
|
|
765
|
+
if (!filePath.startsWith(rootDir + sep) && filePath !== rootDir) {
|
|
1616
766
|
return new Response("Forbidden", { status: 403 });
|
|
1617
767
|
}
|
|
1618
768
|
let fileHandle;
|
|
@@ -1620,15 +770,15 @@ function serveStatic(root, options) {
|
|
|
1620
770
|
fileHandle = await open(filePath, "r");
|
|
1621
771
|
let stat = await fileHandle.stat();
|
|
1622
772
|
const realPath = await realpath(filePath);
|
|
1623
|
-
if (!realPath.startsWith(rootDir +
|
|
773
|
+
if (!realPath.startsWith(rootDir + sep) && realPath !== rootDir) {
|
|
1624
774
|
await fileHandle.close();
|
|
1625
775
|
return new Response("Forbidden", { status: 403 });
|
|
1626
776
|
}
|
|
1627
777
|
if (stat.isDirectory()) {
|
|
1628
778
|
await fileHandle.close();
|
|
1629
779
|
const indexFile = opts.index ?? "index.html";
|
|
1630
|
-
filePath =
|
|
1631
|
-
if (!filePath.startsWith(rootDir +
|
|
780
|
+
filePath = resolve2(filePath, indexFile);
|
|
781
|
+
if (!filePath.startsWith(rootDir + sep)) {
|
|
1632
782
|
return new Response("Forbidden", { status: 403 });
|
|
1633
783
|
}
|
|
1634
784
|
fileHandle = await open(filePath, "r");
|
|
@@ -1639,7 +789,7 @@ function serveStatic(root, options) {
|
|
|
1639
789
|
}
|
|
1640
790
|
}
|
|
1641
791
|
const mimeType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
1642
|
-
const etag = `"${
|
|
792
|
+
const etag = `"${createHash("md5").update(`${stat.size}-${stat.mtimeMs}`).digest("hex")}"`;
|
|
1643
793
|
const ifNoneMatch = req.headers.get("if-none-match");
|
|
1644
794
|
if (ifNoneMatch === etag) {
|
|
1645
795
|
await fileHandle.close();
|
|
@@ -1860,7 +1010,7 @@ function deleteCookie(res, name, options) {
|
|
|
1860
1010
|
// upload.ts
|
|
1861
1011
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
1862
1012
|
import { randomUUID } from "node:crypto";
|
|
1863
|
-
import { join
|
|
1013
|
+
import { join, extname as extname2 } from "node:path";
|
|
1864
1014
|
var extensionMimeMap = {
|
|
1865
1015
|
".jpg": "image/jpeg",
|
|
1866
1016
|
".jpeg": "image/jpeg",
|
|
@@ -1927,7 +1077,7 @@ function upload(options) {
|
|
|
1927
1077
|
};
|
|
1928
1078
|
if (saveDir) {
|
|
1929
1079
|
const safeName = value.name.replace(/[/\\\0]/g, "_");
|
|
1930
|
-
const filePath =
|
|
1080
|
+
const filePath = join(saveDir, `${randomUUID()}-${safeName}`);
|
|
1931
1081
|
await writeFile(filePath, buf);
|
|
1932
1082
|
uf.path = filePath;
|
|
1933
1083
|
}
|
|
@@ -2117,12 +1267,12 @@ function requestId(options) {
|
|
|
2117
1267
|
const gen = options?.generator ?? (() => crypto2.randomUUID());
|
|
2118
1268
|
return async (req, ctx, next) => {
|
|
2119
1269
|
const existing = req.headers.get(header);
|
|
2120
|
-
const
|
|
2121
|
-
ctx.requestId =
|
|
1270
|
+
const id3 = existing ?? gen();
|
|
1271
|
+
ctx.requestId = id3;
|
|
2122
1272
|
const res = await next(req, ctx);
|
|
2123
1273
|
if (res.headers.has(header)) return res;
|
|
2124
1274
|
const h = new Headers(res.headers);
|
|
2125
|
-
h.set(header,
|
|
1275
|
+
h.set(header, id3);
|
|
2126
1276
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
2127
1277
|
};
|
|
2128
1278
|
}
|
|
@@ -2292,7 +1442,7 @@ function graphql(handler) {
|
|
|
2292
1442
|
}
|
|
2293
1443
|
return executeQuery(schema, params, options, req, ctx);
|
|
2294
1444
|
});
|
|
2295
|
-
return
|
|
1445
|
+
return r;
|
|
2296
1446
|
}
|
|
2297
1447
|
|
|
2298
1448
|
// ai.ts
|
|
@@ -2319,7 +1469,7 @@ async function aiStream(handler) {
|
|
|
2319
1469
|
const result = streamText4(options);
|
|
2320
1470
|
return result.toTextStreamResponse();
|
|
2321
1471
|
});
|
|
2322
|
-
return
|
|
1472
|
+
return r;
|
|
2323
1473
|
}
|
|
2324
1474
|
|
|
2325
1475
|
// ai/workflow.ts
|
|
@@ -2344,10 +1494,10 @@ function resolveRef(path2, ctx) {
|
|
|
2344
1494
|
const after = path2.slice(7);
|
|
2345
1495
|
const dot = after.indexOf(".");
|
|
2346
1496
|
if (dot === -1) return ctx.nodeOutputs.get(after);
|
|
2347
|
-
const
|
|
1497
|
+
const id3 = after.slice(0, dot);
|
|
2348
1498
|
const propPath = after.slice(dot + 1);
|
|
2349
|
-
const output = ctx.nodeOutputs.get(
|
|
2350
|
-
if (output === void 0) throw new Error(`Node "${
|
|
1499
|
+
const output = ctx.nodeOutputs.get(id3);
|
|
1500
|
+
if (output === void 0) throw new Error(`Node "${id3}" has no output yet`);
|
|
2351
1501
|
return getByPath(output, propPath.startsWith("output") ? propPath.slice(7).split(".").filter(Boolean) : propPath.split("."));
|
|
2352
1502
|
}
|
|
2353
1503
|
if (path2.startsWith("$var.")) {
|
|
@@ -2873,18 +2023,18 @@ var Table = class {
|
|
|
2873
2023
|
`;
|
|
2874
2024
|
return rows;
|
|
2875
2025
|
}
|
|
2876
|
-
async read(sql2,
|
|
2026
|
+
async read(sql2, id3, opts) {
|
|
2877
2027
|
if (opts?.select?.length) {
|
|
2878
2028
|
const columns = opts.select.map((c) => `"${c}"`).join(", ");
|
|
2879
2029
|
const [row2] = await sql2.unsafe(
|
|
2880
2030
|
`SELECT ${columns} FROM "${this.tableName}" WHERE id = $1 LIMIT 1`,
|
|
2881
|
-
[
|
|
2031
|
+
[id3]
|
|
2882
2032
|
);
|
|
2883
2033
|
return row2 ?? void 0;
|
|
2884
2034
|
}
|
|
2885
2035
|
const [row] = await sql2`
|
|
2886
2036
|
SELECT * FROM ${sql2(this.tableName)}
|
|
2887
|
-
WHERE ${sql2("id")} = ${
|
|
2037
|
+
WHERE ${sql2("id")} = ${id3} LIMIT 1
|
|
2888
2038
|
`;
|
|
2889
2039
|
return row ?? void 0;
|
|
2890
2040
|
}
|
|
@@ -2918,11 +2068,11 @@ var Table = class {
|
|
|
2918
2068
|
const rows = await sql2.unsafe(query, values);
|
|
2919
2069
|
return { count, data: rows };
|
|
2920
2070
|
}
|
|
2921
|
-
async update(sql2,
|
|
2071
|
+
async update(sql2, id3, data) {
|
|
2922
2072
|
const { sets, values: setValues } = this._buildSET(data);
|
|
2923
2073
|
if (sets.length === 0) return void 0;
|
|
2924
2074
|
const query = `UPDATE "${this.tableName}" AS t SET ${sets.join(", ")} FROM (SELECT ctid FROM "${this.tableName}" WHERE id = $${setValues.length + 1} LIMIT 1) AS sub WHERE t.ctid = sub.ctid RETURNING t.*`;
|
|
2925
|
-
const rows = await sql2.unsafe(query, [...setValues,
|
|
2075
|
+
const rows = await sql2.unsafe(query, [...setValues, id3]);
|
|
2926
2076
|
return rows[0] ?? void 0;
|
|
2927
2077
|
}
|
|
2928
2078
|
async updateMany(sql2, where, data) {
|
|
@@ -2936,22 +2086,22 @@ var Table = class {
|
|
|
2936
2086
|
);
|
|
2937
2087
|
return rows.length;
|
|
2938
2088
|
}
|
|
2939
|
-
async delete(sql2,
|
|
2089
|
+
async delete(sql2, id3) {
|
|
2940
2090
|
if (this.hasColumn("deleted_at")) {
|
|
2941
2091
|
const [row2] = await sql2.unsafe(
|
|
2942
2092
|
`UPDATE "${this.tableName}" SET "deleted_at" = NOW() WHERE id = $1 RETURNING *`,
|
|
2943
|
-
[
|
|
2093
|
+
[id3]
|
|
2944
2094
|
);
|
|
2945
2095
|
return row2 ?? void 0;
|
|
2946
2096
|
}
|
|
2947
2097
|
const [row] = await sql2`
|
|
2948
|
-
DELETE FROM ${sql2(this.tableName)} WHERE ${sql2("id")} = ${
|
|
2098
|
+
DELETE FROM ${sql2(this.tableName)} WHERE ${sql2("id")} = ${id3} RETURNING *
|
|
2949
2099
|
`;
|
|
2950
2100
|
return row ?? void 0;
|
|
2951
2101
|
}
|
|
2952
|
-
async hardDelete(sql2,
|
|
2102
|
+
async hardDelete(sql2, id3) {
|
|
2953
2103
|
const [row] = await sql2`
|
|
2954
|
-
DELETE FROM ${sql2(this.tableName)} WHERE ${sql2("id")} = ${
|
|
2104
|
+
DELETE FROM ${sql2(this.tableName)} WHERE ${sql2("id")} = ${id3} RETURNING *
|
|
2955
2105
|
`;
|
|
2956
2106
|
return row ?? void 0;
|
|
2957
2107
|
}
|
|
@@ -3047,23 +2197,23 @@ var BoundTable = class _BoundTable {
|
|
|
3047
2197
|
async insertMany(data) {
|
|
3048
2198
|
return await this.inner.insertMany(this.sql, data);
|
|
3049
2199
|
}
|
|
3050
|
-
async read(
|
|
3051
|
-
return await this.inner.read(this.sql,
|
|
2200
|
+
async read(id3, opts) {
|
|
2201
|
+
return await this.inner.read(this.sql, id3, opts);
|
|
3052
2202
|
}
|
|
3053
2203
|
async readMany(where, opts) {
|
|
3054
2204
|
return await this.inner.readMany(this.sql, where, opts);
|
|
3055
2205
|
}
|
|
3056
|
-
async update(
|
|
3057
|
-
return await this.inner.update(this.sql,
|
|
2206
|
+
async update(id3, data) {
|
|
2207
|
+
return await this.inner.update(this.sql, id3, data);
|
|
3058
2208
|
}
|
|
3059
2209
|
async updateMany(where, data) {
|
|
3060
2210
|
return await this.inner.updateMany(this.sql, where, data);
|
|
3061
2211
|
}
|
|
3062
|
-
async delete(
|
|
3063
|
-
return await this.inner.delete(this.sql,
|
|
2212
|
+
async delete(id3) {
|
|
2213
|
+
return await this.inner.delete(this.sql, id3);
|
|
3064
2214
|
}
|
|
3065
|
-
async hardDelete(
|
|
3066
|
-
return await this.inner.hardDelete(this.sql,
|
|
2215
|
+
async hardDelete(id3) {
|
|
2216
|
+
return await this.inner.hardDelete(this.sql, id3);
|
|
3067
2217
|
}
|
|
3068
2218
|
async deleteMany(where) {
|
|
3069
2219
|
return await this.inner.deleteMany(this.sql, where);
|
|
@@ -3530,8 +2680,8 @@ function user(options) {
|
|
|
3530
2680
|
const { data: rows } = await users.readMany({ email });
|
|
3531
2681
|
return rows[0];
|
|
3532
2682
|
}
|
|
3533
|
-
async function findById(
|
|
3534
|
-
return await users.read(
|
|
2683
|
+
async function findById(id3) {
|
|
2684
|
+
return await users.read(id3);
|
|
3535
2685
|
}
|
|
3536
2686
|
async function register(data) {
|
|
3537
2687
|
const { email, password, name } = RegisterSchema.parse(data);
|
|
@@ -3592,8 +2742,8 @@ function user(options) {
|
|
|
3592
2742
|
};
|
|
3593
2743
|
}
|
|
3594
2744
|
function router() {
|
|
3595
|
-
const
|
|
3596
|
-
|
|
2745
|
+
const r2 = new Router();
|
|
2746
|
+
r2.post("/register", async (req) => {
|
|
3597
2747
|
try {
|
|
3598
2748
|
const body = await req.json();
|
|
3599
2749
|
const result = await register(body);
|
|
@@ -3606,7 +2756,7 @@ function user(options) {
|
|
|
3606
2756
|
return Response.json({ error: err.message }, { status });
|
|
3607
2757
|
}
|
|
3608
2758
|
});
|
|
3609
|
-
|
|
2759
|
+
r2.post("/login", async (req) => {
|
|
3610
2760
|
try {
|
|
3611
2761
|
const body = await req.json();
|
|
3612
2762
|
const result = await login(body);
|
|
@@ -3622,30 +2772,29 @@ function user(options) {
|
|
|
3622
2772
|
}
|
|
3623
2773
|
});
|
|
3624
2774
|
if (oauth2) {
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
}
|
|
3629
|
-
return
|
|
3630
|
-
}
|
|
3631
|
-
const
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
},
|
|
3647
|
-
close: () => base.close()
|
|
2775
|
+
r2.get("/oauth/authorize", (req, ctx) => oauth2.authorizeHandler(req, ctx));
|
|
2776
|
+
r2.post("/oauth/consent", (req) => oauth2.consentHandler(req));
|
|
2777
|
+
r2.post("/oauth/token", (req) => oauth2.tokenHandler(req));
|
|
2778
|
+
}
|
|
2779
|
+
return r2;
|
|
2780
|
+
}
|
|
2781
|
+
const r = router();
|
|
2782
|
+
const mod = r;
|
|
2783
|
+
mod.middleware = middleware;
|
|
2784
|
+
mod.migrate = migrate;
|
|
2785
|
+
mod.register = register;
|
|
2786
|
+
mod.login = login;
|
|
2787
|
+
mod.verify = verify;
|
|
2788
|
+
mod.registerClient = oauth2 ? (data) => oauth2.registerClient(data) : async () => {
|
|
2789
|
+
throw new Error("OAuth2 server is not enabled");
|
|
2790
|
+
};
|
|
2791
|
+
mod.getClient = oauth2 ? (clientId) => oauth2.getClient(clientId) : async () => {
|
|
2792
|
+
throw new Error("OAuth2 server is not enabled");
|
|
2793
|
+
};
|
|
2794
|
+
mod.revokeClient = oauth2 ? (clientId) => oauth2.revokeClient(clientId) : async () => {
|
|
2795
|
+
throw new Error("OAuth2 server is not enabled");
|
|
3648
2796
|
};
|
|
2797
|
+
mod.close = () => base.close();
|
|
3649
2798
|
return mod;
|
|
3650
2799
|
}
|
|
3651
2800
|
|
|
@@ -3688,7 +2837,7 @@ function createHub(opts) {
|
|
|
3688
2837
|
}
|
|
3689
2838
|
});
|
|
3690
2839
|
}
|
|
3691
|
-
function
|
|
2840
|
+
function join6(key, ws) {
|
|
3692
2841
|
if (!channels.has(key)) {
|
|
3693
2842
|
channels.set(key, /* @__PURE__ */ new Set());
|
|
3694
2843
|
redisSub?.subscribe(`${prefix}${key}`);
|
|
@@ -3729,7 +2878,7 @@ function createHub(opts) {
|
|
|
3729
2878
|
await redisSub.quit();
|
|
3730
2879
|
}
|
|
3731
2880
|
}
|
|
3732
|
-
return { join:
|
|
2881
|
+
return { join: join6, leave, broadcast, close };
|
|
3733
2882
|
}
|
|
3734
2883
|
|
|
3735
2884
|
// queue/index.ts
|
|
@@ -3855,7 +3004,7 @@ function queue(opts) {
|
|
|
3855
3004
|
}
|
|
3856
3005
|
}
|
|
3857
3006
|
mw.add = function add(type, payload, opts2) {
|
|
3858
|
-
const
|
|
3007
|
+
const id3 = crypto4.randomUUID();
|
|
3859
3008
|
let runAt;
|
|
3860
3009
|
if (opts2?.schedule) {
|
|
3861
3010
|
runAt = cronNext(opts2.schedule);
|
|
@@ -3864,9 +3013,9 @@ function queue(opts) {
|
|
|
3864
3013
|
} else {
|
|
3865
3014
|
runAt = Date.now();
|
|
3866
3015
|
}
|
|
3867
|
-
const job = { id:
|
|
3016
|
+
const job = { id: id3, type, payload, createdAt: Date.now(), runAt };
|
|
3868
3017
|
if (opts2?.schedule) job.schedule = opts2.schedule;
|
|
3869
|
-
return redis2.zadd(jobKey, runAt, JSON.stringify(job)).then(() =>
|
|
3018
|
+
return redis2.zadd(jobKey, runAt, JSON.stringify(job)).then(() => id3);
|
|
3870
3019
|
};
|
|
3871
3020
|
mw.process = function process2(type, handler) {
|
|
3872
3021
|
handlers.set(type, handler);
|
|
@@ -4724,76 +3873,78 @@ function tenant(options) {
|
|
|
4724
3873
|
const sql2 = pg.sql;
|
|
4725
3874
|
const usersTable = options.usersTable;
|
|
4726
3875
|
const base = new PgModule(pg);
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
ctx.tenant = { id: m.id, name: m.name, role: m.role };
|
|
4776
|
-
return next(req, ctx);
|
|
4777
|
-
}
|
|
4778
|
-
const headerId = req.headers.get("X-Tenant-ID");
|
|
4779
|
-
if (!headerId) {
|
|
4780
|
-
return Response.json({
|
|
4781
|
-
error: "Multiple tenants. Set X-Tenant-ID header.",
|
|
4782
|
-
tenants: members.map((m) => ({ id: m.id, name: m.name, role: m.role }))
|
|
4783
|
-
}, { status: 300 });
|
|
4784
|
-
}
|
|
4785
|
-
const member = members.find((m) => m.id === headerId);
|
|
4786
|
-
if (!member) {
|
|
4787
|
-
return new Response("Tenant not found", { status: 403 });
|
|
4788
|
-
}
|
|
4789
|
-
ctx.tenant = { id: member.id, name: member.name, role: member.role };
|
|
3876
|
+
async function migrate() {
|
|
3877
|
+
await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "vector"`);
|
|
3878
|
+
const tenants = pgTable("_tenants", {
|
|
3879
|
+
id: text("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
3880
|
+
name: text("name").notNull(),
|
|
3881
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3882
|
+
});
|
|
3883
|
+
await tenants.create(sql2);
|
|
3884
|
+
const members = pgTable("_tenant_members", {
|
|
3885
|
+
id: serial("id").primaryKey(),
|
|
3886
|
+
tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
|
|
3887
|
+
user_id: integer("user_id").notNull(),
|
|
3888
|
+
role: text("role").notNull().default("member"),
|
|
3889
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3890
|
+
});
|
|
3891
|
+
await members.create(sql2);
|
|
3892
|
+
await members.createIndex(sql2, "user_id");
|
|
3893
|
+
await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_tenant_members_unique_idx" ON "_tenant_members" ("tenant_id", "user_id")`);
|
|
3894
|
+
const tables = pgTable("_user_tables", {
|
|
3895
|
+
id: serial("id").primaryKey(),
|
|
3896
|
+
tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
|
|
3897
|
+
slug: text("slug").notNull(),
|
|
3898
|
+
label: text("label").notNull().default(""),
|
|
3899
|
+
fields: jsonb("fields").notNull().default(sql`'[]'::jsonb`),
|
|
3900
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3901
|
+
});
|
|
3902
|
+
await tables.create(sql2);
|
|
3903
|
+
await tables.createIndex(sql2, "tenant_id");
|
|
3904
|
+
await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_user_tables_unique_idx" ON "_user_tables" ("tenant_id", "slug")`);
|
|
3905
|
+
}
|
|
3906
|
+
function middleware() {
|
|
3907
|
+
return async (req, ctx, next) => {
|
|
3908
|
+
const user2 = ctx.user;
|
|
3909
|
+
if (!user2) {
|
|
3910
|
+
return new Response("Unauthorized", { status: 401 });
|
|
3911
|
+
}
|
|
3912
|
+
const members = await sql2`
|
|
3913
|
+
SELECT tm.role, t.id, t.name
|
|
3914
|
+
FROM "_tenant_members" tm
|
|
3915
|
+
JOIN "_tenants" t ON t.id = tm.tenant_id
|
|
3916
|
+
WHERE tm.user_id = ${user2.id}
|
|
3917
|
+
`;
|
|
3918
|
+
if (members.length === 0) {
|
|
3919
|
+
return new Response("No tenant found. Create one via POST /sys/tenants.", { status: 403 });
|
|
3920
|
+
}
|
|
3921
|
+
if (members.length === 1) {
|
|
3922
|
+
const m = members[0];
|
|
3923
|
+
ctx.tenant = { id: m.id, name: m.name, role: m.role };
|
|
4790
3924
|
return next(req, ctx);
|
|
4791
|
-
}
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
3925
|
+
}
|
|
3926
|
+
const headerId = req.headers.get("X-Tenant-ID");
|
|
3927
|
+
if (!headerId) {
|
|
3928
|
+
return Response.json({
|
|
3929
|
+
error: "Multiple tenants. Set X-Tenant-ID header.",
|
|
3930
|
+
tenants: members.map((m) => ({ id: m.id, name: m.name, role: m.role }))
|
|
3931
|
+
}, { status: 300 });
|
|
3932
|
+
}
|
|
3933
|
+
const member = members.find((m) => m.id === headerId);
|
|
3934
|
+
if (!member) {
|
|
3935
|
+
return new Response("Tenant not found", { status: 403 });
|
|
3936
|
+
}
|
|
3937
|
+
ctx.tenant = { id: member.id, name: member.name, role: member.role };
|
|
3938
|
+
return next(req, ctx);
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
const r = buildRouter(sql2, usersTable);
|
|
3942
|
+
const mod = r;
|
|
3943
|
+
mod.migrate = migrate;
|
|
3944
|
+
mod.middleware = middleware;
|
|
3945
|
+
mod.graphql = () => buildGraphQLHandler(sql2);
|
|
3946
|
+
mod.close = () => base.close();
|
|
3947
|
+
return mod;
|
|
4797
3948
|
}
|
|
4798
3949
|
|
|
4799
3950
|
// agent/client.ts
|
|
@@ -4802,8 +3953,8 @@ import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
|
4802
3953
|
// agent/rest.ts
|
|
4803
3954
|
function buildRouter2(deps) {
|
|
4804
3955
|
const { agents: agentsTable, knowledge, runner } = deps;
|
|
4805
|
-
async function getAgent(
|
|
4806
|
-
const row = await agentsTable.read(
|
|
3956
|
+
async function getAgent(id3) {
|
|
3957
|
+
const row = await agentsTable.read(id3);
|
|
4807
3958
|
return row ?? null;
|
|
4808
3959
|
}
|
|
4809
3960
|
const r = new Router();
|
|
@@ -4830,8 +3981,8 @@ function buildRouter2(deps) {
|
|
|
4830
3981
|
return Response.json(agent2);
|
|
4831
3982
|
});
|
|
4832
3983
|
r.patch("/agents/:id", async (req, ctx) => {
|
|
4833
|
-
const
|
|
4834
|
-
const agent2 = await getAgent(
|
|
3984
|
+
const id3 = parseInt(ctx.params.id, 10);
|
|
3985
|
+
const agent2 = await getAgent(id3);
|
|
4835
3986
|
if (!agent2) return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
4836
3987
|
const body = await req.json();
|
|
4837
3988
|
const updateData = {};
|
|
@@ -4843,23 +3994,23 @@ function buildRouter2(deps) {
|
|
|
4843
3994
|
if (Object.keys(updateData).length === 0) {
|
|
4844
3995
|
return Response.json({ error: "No fields to update" }, { status: 400 });
|
|
4845
3996
|
}
|
|
4846
|
-
const row = await agentsTable.update(
|
|
3997
|
+
const row = await agentsTable.update(id3, updateData);
|
|
4847
3998
|
return Response.json(row);
|
|
4848
3999
|
});
|
|
4849
4000
|
r.delete("/agents/:id", async (_req, ctx) => {
|
|
4850
|
-
const
|
|
4851
|
-
const row = await agentsTable.delete(
|
|
4001
|
+
const id3 = parseInt(ctx.params.id, 10);
|
|
4002
|
+
const row = await agentsTable.delete(id3);
|
|
4852
4003
|
if (!row) return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
4853
4004
|
return Response.json({ ok: true });
|
|
4854
4005
|
});
|
|
4855
4006
|
r.post("/agents/:id/run", async (req, ctx) => {
|
|
4856
|
-
const
|
|
4007
|
+
const id3 = parseInt(ctx.params.id, 10);
|
|
4857
4008
|
const body = await req.json();
|
|
4858
4009
|
if (!body.input && !body.messages) {
|
|
4859
4010
|
return Response.json({ error: "input or messages is required" }, { status: 400 });
|
|
4860
4011
|
}
|
|
4861
4012
|
try {
|
|
4862
|
-
const result = await runner.run(
|
|
4013
|
+
const result = await runner.run(id3, body);
|
|
4863
4014
|
if ("stream" in result) {
|
|
4864
4015
|
return new Response(result.stream, {
|
|
4865
4016
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" }
|
|
@@ -5070,18 +4221,18 @@ function agent(options) {
|
|
|
5070
4221
|
});
|
|
5071
4222
|
const runner = createRunner({ sql: sql2, agents: agentsTable, knowledge: knowledgeTable, getModel, getEmbeddingModel, userTools: options.tools });
|
|
5072
4223
|
const base = new PgModule(pg);
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
router: () => buildRouter2({ agents: agentsTable, knowledge: knowledgeTable, runner }),
|
|
5081
|
-
run: (agentId, params) => runner.run(agentId, params),
|
|
5082
|
-
addKnowledge: (agentId, title, content) => runner.addKnowledge(agentId, title, content),
|
|
5083
|
-
close: () => base.close()
|
|
4224
|
+
const r = buildRouter2({ agents: agentsTable, knowledge: knowledgeTable, runner });
|
|
4225
|
+
const mod = r;
|
|
4226
|
+
mod.migrate = async () => {
|
|
4227
|
+
await agentsTable.create();
|
|
4228
|
+
await agentsTable.createIndex("tenant_id");
|
|
4229
|
+
await knowledgeTable.create();
|
|
4230
|
+
await knowledgeTable.createIndex("agent_id");
|
|
5084
4231
|
};
|
|
4232
|
+
mod.run = (agentId, params) => runner.run(agentId, params);
|
|
4233
|
+
mod.addKnowledge = (agentId, title, content) => runner.addKnowledge(agentId, title, content);
|
|
4234
|
+
mod.close = () => base.close();
|
|
4235
|
+
return mod;
|
|
5085
4236
|
}
|
|
5086
4237
|
|
|
5087
4238
|
// messager/agent.ts
|
|
@@ -5270,15 +4421,15 @@ function buildRouter3(deps) {
|
|
|
5270
4421
|
return Response.json(rows);
|
|
5271
4422
|
});
|
|
5272
4423
|
r.get("/channels/:id", async (_req, ctx) => {
|
|
5273
|
-
const
|
|
5274
|
-
const ch = await channels.read(
|
|
4424
|
+
const id3 = parseInt(ctx.params.id, 10);
|
|
4425
|
+
const ch = await channels.read(id3);
|
|
5275
4426
|
if (!ch) return Response.json({ error: "Channel not found" }, { status: 404 });
|
|
5276
|
-
const { data: memberRows } = await members.readMany({ channel_id:
|
|
4427
|
+
const { data: memberRows } = await members.readMany({ channel_id: id3 });
|
|
5277
4428
|
return Response.json({ channel: ch, members: memberRows });
|
|
5278
4429
|
});
|
|
5279
4430
|
r.delete("/channels/:id", async (_req, ctx) => {
|
|
5280
|
-
const
|
|
5281
|
-
await channels.delete(
|
|
4431
|
+
const id3 = parseInt(ctx.params.id, 10);
|
|
4432
|
+
await channels.delete(id3);
|
|
5282
4433
|
return Response.json({ ok: true });
|
|
5283
4434
|
});
|
|
5284
4435
|
r.post("/channels/:id/members", async (req, ctx) => {
|
|
@@ -5390,31 +4541,32 @@ function messager(options) {
|
|
|
5390
4541
|
mime_type: text("mime_type"),
|
|
5391
4542
|
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
5392
4543
|
});
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
}
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
return msg;
|
|
5415
|
-
},
|
|
5416
|
-
close: () => base.close()
|
|
4544
|
+
const r = buildRouter3({ sql: sql2, channels, members, messages: messages2, agents, hub });
|
|
4545
|
+
async function send(channelId, content, opts) {
|
|
4546
|
+
const msg = await messages2.insert({
|
|
4547
|
+
channel_id: channelId,
|
|
4548
|
+
sender_id: opts?.sender_id ?? 0,
|
|
4549
|
+
sender_type: opts?.sender_type ?? "system",
|
|
4550
|
+
type: opts?.type ?? "text",
|
|
4551
|
+
content
|
|
4552
|
+
});
|
|
4553
|
+
broadcastToChannel(hub, channelId, { type: "message", data: msg });
|
|
4554
|
+
return msg;
|
|
4555
|
+
}
|
|
4556
|
+
const mod = r;
|
|
4557
|
+
mod.migrate = async () => {
|
|
4558
|
+
await channels.create();
|
|
4559
|
+
await channels.createIndex("tenant_id");
|
|
4560
|
+
await members.create();
|
|
4561
|
+
await members.createIndex("member_id");
|
|
4562
|
+
await members.createIndex(["channel_id", "member_id", "member_type"], { unique: true });
|
|
4563
|
+
await messages2.create();
|
|
4564
|
+
await messages2.createIndex(["channel_id", "created_at"], { desc: true });
|
|
5417
4565
|
};
|
|
4566
|
+
mod.wsHandler = () => wsResult.handler;
|
|
4567
|
+
mod.send = send;
|
|
4568
|
+
mod.close = () => base.close();
|
|
4569
|
+
return mod;
|
|
5418
4570
|
}
|
|
5419
4571
|
|
|
5420
4572
|
// deploy/index.ts
|
|
@@ -5536,14 +4688,14 @@ function forkApp(opts) {
|
|
|
5536
4688
|
return { child, port: opts.port };
|
|
5537
4689
|
}
|
|
5538
4690
|
function stopProcess(mp, timeout = 1e4) {
|
|
5539
|
-
return new Promise((
|
|
4691
|
+
return new Promise((resolve12) => {
|
|
5540
4692
|
const timer = setTimeout(() => {
|
|
5541
4693
|
mp.child.kill("SIGKILL");
|
|
5542
|
-
|
|
4694
|
+
resolve12();
|
|
5543
4695
|
}, timeout);
|
|
5544
4696
|
mp.child.on("exit", () => {
|
|
5545
4697
|
clearTimeout(timer);
|
|
5546
|
-
|
|
4698
|
+
resolve12();
|
|
5547
4699
|
});
|
|
5548
4700
|
mp.child.kill("SIGTERM");
|
|
5549
4701
|
});
|
|
@@ -5972,6 +5124,366 @@ function ensureCertificates(config) {
|
|
|
5972
5124
|
// opencode/client.ts
|
|
5973
5125
|
import { createOpenAI as createOpenAI3 } from "@ai-sdk/openai";
|
|
5974
5126
|
|
|
5127
|
+
// opencode/rest.ts
|
|
5128
|
+
import { join as join4 } from "node:path";
|
|
5129
|
+
|
|
5130
|
+
// ssr/ssr.ts
|
|
5131
|
+
import { createElement } from "react";
|
|
5132
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5133
|
+
import { dirname as dirname2, resolve as resolve4 } from "node:path";
|
|
5134
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5135
|
+
|
|
5136
|
+
// ssr/compile.ts
|
|
5137
|
+
import * as esbuild from "esbuild";
|
|
5138
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
5139
|
+
import { join as join2, resolve as resolve3, dirname } from "node:path";
|
|
5140
|
+
import { pathToFileURL } from "node:url";
|
|
5141
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5142
|
+
var OUT_DIR = ".weifuwu/ssr";
|
|
5143
|
+
var cache = /* @__PURE__ */ new Map();
|
|
5144
|
+
var externals = [
|
|
5145
|
+
"react",
|
|
5146
|
+
"react-dom",
|
|
5147
|
+
"esbuild",
|
|
5148
|
+
"graphql",
|
|
5149
|
+
"ws",
|
|
5150
|
+
"zod",
|
|
5151
|
+
"@graphql-tools/schema",
|
|
5152
|
+
"ai"
|
|
5153
|
+
];
|
|
5154
|
+
var _alias = null;
|
|
5155
|
+
function resolveAliases() {
|
|
5156
|
+
if (_alias) return _alias;
|
|
5157
|
+
const configFiles = ["tsconfig.json", "jsconfig.json"];
|
|
5158
|
+
for (const file of configFiles) {
|
|
5159
|
+
const p = resolve3(file);
|
|
5160
|
+
if (existsSync2(p)) {
|
|
5161
|
+
try {
|
|
5162
|
+
const config = JSON.parse(readFileSync2(p, "utf-8"));
|
|
5163
|
+
const paths = config.compilerOptions?.paths;
|
|
5164
|
+
if (paths) {
|
|
5165
|
+
const alias = {};
|
|
5166
|
+
for (const [key, values] of Object.entries(paths)) {
|
|
5167
|
+
const cleanKey = key.replace("/*", "");
|
|
5168
|
+
const val = values[0]?.replace("/*", "");
|
|
5169
|
+
if (val) alias[cleanKey] = resolve3(dirname(p), val);
|
|
5170
|
+
}
|
|
5171
|
+
_alias = alias;
|
|
5172
|
+
return alias;
|
|
5173
|
+
}
|
|
5174
|
+
} catch {
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
_alias = {};
|
|
5179
|
+
return {};
|
|
5180
|
+
}
|
|
5181
|
+
function id(s) {
|
|
5182
|
+
return createHash2("md5").update(s).digest("hex").slice(0, 8);
|
|
5183
|
+
}
|
|
5184
|
+
async function compileTsx(path2) {
|
|
5185
|
+
if (cache.has(path2)) return cache.get(path2);
|
|
5186
|
+
const absPath = resolve3(path2);
|
|
5187
|
+
mkdirSync(OUT_DIR, { recursive: true });
|
|
5188
|
+
const hash = id(absPath);
|
|
5189
|
+
const outPath = join2(OUT_DIR, hash + ".js");
|
|
5190
|
+
await esbuild.build({
|
|
5191
|
+
entryPoints: { [hash]: absPath },
|
|
5192
|
+
outdir: OUT_DIR,
|
|
5193
|
+
format: "esm",
|
|
5194
|
+
platform: "node",
|
|
5195
|
+
jsx: "automatic",
|
|
5196
|
+
jsxImportSource: "react",
|
|
5197
|
+
bundle: true,
|
|
5198
|
+
external: externals,
|
|
5199
|
+
alias: resolveAliases(),
|
|
5200
|
+
write: true,
|
|
5201
|
+
allowOverwrite: true
|
|
5202
|
+
});
|
|
5203
|
+
const mod = await import(pathToFileURL(outPath).href);
|
|
5204
|
+
cache.set(path2, mod);
|
|
5205
|
+
return mod;
|
|
5206
|
+
}
|
|
5207
|
+
|
|
5208
|
+
// ssr/stream.ts
|
|
5209
|
+
import { TextDecoder, TextEncoder as TextEncoder2 } from "node:util";
|
|
5210
|
+
var _publicEnv = null;
|
|
5211
|
+
function getPublicEnv() {
|
|
5212
|
+
if (_publicEnv) return _publicEnv;
|
|
5213
|
+
_publicEnv = {};
|
|
5214
|
+
for (const key of Object.keys(process.env)) {
|
|
5215
|
+
if (key.startsWith("WEIFUWU_PUBLIC_")) {
|
|
5216
|
+
_publicEnv[key] = process.env[key];
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
return _publicEnv;
|
|
5220
|
+
}
|
|
5221
|
+
function buildHeadPayload(opts) {
|
|
5222
|
+
const { ctx, base, compiledTailwindCss } = opts;
|
|
5223
|
+
let result = "";
|
|
5224
|
+
if (ctx.prefs?.theme) {
|
|
5225
|
+
result += `<script>!function(){var t=(document.cookie.match(/(?:^|;\\s*)theme=([^;]+)/)||[])[1]||'system';if(t==='system'){t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'}document.documentElement.setAttribute('data-theme',t)}()</script>
|
|
5226
|
+
`;
|
|
5227
|
+
}
|
|
5228
|
+
if (compiledTailwindCss) {
|
|
5229
|
+
result += `<link rel="stylesheet" href="${base}/__wfw/style.css" />
|
|
5230
|
+
`;
|
|
5231
|
+
}
|
|
5232
|
+
const localeData = ctx.parsed?.__localeData ?? globalThis.__LOCALE_DATA__;
|
|
5233
|
+
if (localeData && Object.keys(localeData).length > 0) {
|
|
5234
|
+
result += `<script>window.__LOCALE_DATA__=${JSON.stringify(localeData)}</script>
|
|
5235
|
+
`;
|
|
5236
|
+
}
|
|
5237
|
+
const loaderData = opts.loaderData || {};
|
|
5238
|
+
const ctxData = {
|
|
5239
|
+
params: ctx.params,
|
|
5240
|
+
query: ctx.query,
|
|
5241
|
+
user: ctx.user,
|
|
5242
|
+
parsed: ctx.parsed,
|
|
5243
|
+
prefs: ctx.prefs,
|
|
5244
|
+
loaderData
|
|
5245
|
+
};
|
|
5246
|
+
const publicEnv = getPublicEnv();
|
|
5247
|
+
if (Object.keys(publicEnv).length > 0) {
|
|
5248
|
+
ctxData.env = publicEnv;
|
|
5249
|
+
}
|
|
5250
|
+
result += `<script>window.__WEIFUWU_CTX=${JSON.stringify(ctxData)}</script>
|
|
5251
|
+
`;
|
|
5252
|
+
return result;
|
|
5253
|
+
}
|
|
5254
|
+
function buildBodyScripts(opts) {
|
|
5255
|
+
const parts = [];
|
|
5256
|
+
if (opts.loaderData && Object.keys(opts.loaderData).length > 0) {
|
|
5257
|
+
parts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(opts.loaderData)}</script>`);
|
|
5258
|
+
}
|
|
5259
|
+
if (opts.bundle) {
|
|
5260
|
+
parts.push(`<script type="module" src="${opts.base}${opts.bundle.url}"></script>`);
|
|
5261
|
+
}
|
|
5262
|
+
return parts.join("\n");
|
|
5263
|
+
}
|
|
5264
|
+
function streamResponse(reactStream, opts) {
|
|
5265
|
+
const decoder = new TextDecoder();
|
|
5266
|
+
const encoder2 = new TextEncoder2();
|
|
5267
|
+
const headPayload = buildHeadPayload(opts);
|
|
5268
|
+
let buffer = "";
|
|
5269
|
+
let headFlushed = false;
|
|
5270
|
+
let extractedHead = "";
|
|
5271
|
+
const output = new ReadableStream({
|
|
5272
|
+
async start(controller) {
|
|
5273
|
+
try {
|
|
5274
|
+
const reader = reactStream.getReader();
|
|
5275
|
+
async function push(chunk) {
|
|
5276
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
5277
|
+
if (!extractedHead) {
|
|
5278
|
+
const m = buffer.match(/<template id="__wfw_head">([\s\S]*?)<\/template>/);
|
|
5279
|
+
if (m) {
|
|
5280
|
+
extractedHead = m[1];
|
|
5281
|
+
buffer = buffer.replace(m[0], "");
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
if (!headFlushed) {
|
|
5285
|
+
const idx = buffer.indexOf("</head>");
|
|
5286
|
+
if (idx !== -1) {
|
|
5287
|
+
const before = buffer.slice(0, idx);
|
|
5288
|
+
let injection = "";
|
|
5289
|
+
if (extractedHead) injection += "\n" + extractedHead;
|
|
5290
|
+
injection += headPayload;
|
|
5291
|
+
controller.enqueue(encoder2.encode(before + injection));
|
|
5292
|
+
buffer = buffer.slice(idx);
|
|
5293
|
+
headFlushed = true;
|
|
5294
|
+
}
|
|
5295
|
+
return;
|
|
5296
|
+
}
|
|
5297
|
+
controller.enqueue(encoder2.encode(buffer));
|
|
5298
|
+
buffer = "";
|
|
5299
|
+
}
|
|
5300
|
+
while (true) {
|
|
5301
|
+
const { done, value } = await reader.read();
|
|
5302
|
+
if (done) break;
|
|
5303
|
+
await push(value);
|
|
5304
|
+
}
|
|
5305
|
+
buffer = buffer.replace(/<template id="__wfw_head">[\s\S]*?<\/template>/g, "");
|
|
5306
|
+
if (buffer) controller.enqueue(encoder2.encode(buffer));
|
|
5307
|
+
const body = buildBodyScripts(opts);
|
|
5308
|
+
if (body) controller.enqueue(encoder2.encode("\n" + body));
|
|
5309
|
+
if (opts.isDev) {
|
|
5310
|
+
controller.enqueue(encoder2.encode(
|
|
5311
|
+
`
|
|
5312
|
+
<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${opts.base}/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`
|
|
5313
|
+
));
|
|
5314
|
+
}
|
|
5315
|
+
} catch {
|
|
5316
|
+
const fallback = `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>500</title></head><body><h1>500 - Internal Server Error</h1></body></html>`;
|
|
5317
|
+
controller.enqueue(encoder2.encode(fallback));
|
|
5318
|
+
} finally {
|
|
5319
|
+
controller.close();
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
});
|
|
5323
|
+
return new Response(output, {
|
|
5324
|
+
status: opts.status ?? 200,
|
|
5325
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
5326
|
+
});
|
|
5327
|
+
}
|
|
5328
|
+
|
|
5329
|
+
// ssr/ssr.ts
|
|
5330
|
+
var als = new AsyncLocalStorage();
|
|
5331
|
+
__registerAls(() => als.getStore());
|
|
5332
|
+
var isDev = process.env.NODE_ENV !== "production";
|
|
5333
|
+
function id2(s) {
|
|
5334
|
+
return createHash3("md5").update(s).digest("hex").slice(0, 8);
|
|
5335
|
+
}
|
|
5336
|
+
async function buildClientBundle(entryPath, layoutPaths) {
|
|
5337
|
+
try {
|
|
5338
|
+
const absEntry = resolve4(entryPath);
|
|
5339
|
+
const absLayouts = layoutPaths.map((p) => resolve4(p));
|
|
5340
|
+
const layoutImports = absLayouts.map((p) => `import${JSON.stringify(p)};`).join("");
|
|
5341
|
+
const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
|
|
5342
|
+
const code = [
|
|
5343
|
+
layoutImports,
|
|
5344
|
+
`import{hydrateRoot}from'react-dom/client';`,
|
|
5345
|
+
`import{createElement,useState,useEffect}from'react';`,
|
|
5346
|
+
`import{TsxContext}from'weifuwu/react';`,
|
|
5347
|
+
`import P from${JSON.stringify(absEntry)};`,
|
|
5348
|
+
`var setCtx=${_sc};`,
|
|
5349
|
+
`const c=document.getElementById('__weifuwu_root');`,
|
|
5350
|
+
`if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
|
|
5351
|
+
`if(!window.__WFW_ROOT){`,
|
|
5352
|
+
`function App(){`,
|
|
5353
|
+
`const[p,setP]=useState({C:P});`,
|
|
5354
|
+
`useEffect(()=>{window.__WFW_SET_PAGE=(C)=>{setCtx({loaderData:window.__WEIFUWU_PROPS});setP({C})}},[]);`,
|
|
5355
|
+
`const ctx=window.__WEIFUWU_CTX||{};`,
|
|
5356
|
+
`return createElement(TsxContext.Provider,{value:ctx},`,
|
|
5357
|
+
`createElement(p.C,null))`,
|
|
5358
|
+
`}`,
|
|
5359
|
+
`window.__WFW_ROOT=hydrateRoot(c,createElement(App));`,
|
|
5360
|
+
`}else{`,
|
|
5361
|
+
`window.__WFW_SET_PAGE?.(P);`,
|
|
5362
|
+
`}`
|
|
5363
|
+
].join("");
|
|
5364
|
+
const { default: esbuild2 } = await import("esbuild");
|
|
5365
|
+
const result = await esbuild2.build({
|
|
5366
|
+
stdin: { contents: code, loader: "tsx", resolveDir: dirname2(absEntry) },
|
|
5367
|
+
bundle: true,
|
|
5368
|
+
format: "esm",
|
|
5369
|
+
jsx: "automatic",
|
|
5370
|
+
jsxImportSource: "react",
|
|
5371
|
+
banner: { js: "self.process={env:{}};" },
|
|
5372
|
+
loader: { ".node": "empty" },
|
|
5373
|
+
write: false,
|
|
5374
|
+
minify: true
|
|
5375
|
+
});
|
|
5376
|
+
return result.outputFiles[0].contents;
|
|
5377
|
+
} catch (err) {
|
|
5378
|
+
console.error("hydration bundle failed:", err);
|
|
5379
|
+
return null;
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
var bundleRegistry = /* @__PURE__ */ new Map();
|
|
5383
|
+
function serializeLoaderData(ctx) {
|
|
5384
|
+
const data = {};
|
|
5385
|
+
for (const key of Object.keys(ctx)) {
|
|
5386
|
+
if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
|
|
5387
|
+
data[key] = ctx[key];
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
return data;
|
|
5391
|
+
}
|
|
5392
|
+
function ssr(path2) {
|
|
5393
|
+
const entryId = id2(resolve4(path2));
|
|
5394
|
+
const bundleKey = `/__ssr/${entryId}.js`;
|
|
5395
|
+
let bundleBuilt = false;
|
|
5396
|
+
return async (req, ctx) => {
|
|
5397
|
+
const pageMod = await compileTsx(path2);
|
|
5398
|
+
const Component = pageMod.default;
|
|
5399
|
+
if (!Component) return new Response("", { status: 500 });
|
|
5400
|
+
const layouts = ctx.layoutStack || [];
|
|
5401
|
+
const layoutComponents = layouts.map((l) => l.component);
|
|
5402
|
+
const layoutPaths = layouts.map((l) => l.path);
|
|
5403
|
+
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
5404
|
+
const loaderData = serializeLoaderData(ctx);
|
|
5405
|
+
const ctxValue = {
|
|
5406
|
+
params: ctx.params,
|
|
5407
|
+
query: ctx.query,
|
|
5408
|
+
user: ctx.user ?? {},
|
|
5409
|
+
parsed: ctx.parsed ?? {},
|
|
5410
|
+
prefs: ctx.prefs ?? {},
|
|
5411
|
+
loaderData,
|
|
5412
|
+
env: ctx.env ?? {}
|
|
5413
|
+
};
|
|
5414
|
+
return als.run(ctxValue, async () => {
|
|
5415
|
+
setCtx(ctxValue);
|
|
5416
|
+
let element = createElement(
|
|
5417
|
+
TsxContext.Provider,
|
|
5418
|
+
{ value: ctxValue },
|
|
5419
|
+
createElement(
|
|
5420
|
+
"div",
|
|
5421
|
+
{ id: "__weifuwu_root" },
|
|
5422
|
+
createElement(Component, null)
|
|
5423
|
+
)
|
|
5424
|
+
);
|
|
5425
|
+
if (layoutComponents.length === 0) {
|
|
5426
|
+
element = createElement(
|
|
5427
|
+
"html",
|
|
5428
|
+
{ lang: "en" },
|
|
5429
|
+
createElement(
|
|
5430
|
+
"head",
|
|
5431
|
+
null,
|
|
5432
|
+
createElement("meta", { charSet: "utf-8" }),
|
|
5433
|
+
createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
5434
|
+
createElement("title", null, "weifuwu")
|
|
5435
|
+
),
|
|
5436
|
+
createElement("body", null, element)
|
|
5437
|
+
);
|
|
5438
|
+
} else {
|
|
5439
|
+
for (const L of layoutComponents.toReversed()) {
|
|
5440
|
+
element = createElement(L, { children: element });
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
let bundle = null;
|
|
5444
|
+
if (!bundleBuilt) {
|
|
5445
|
+
const buf = await buildClientBundle(path2, layoutPaths);
|
|
5446
|
+
if (buf) {
|
|
5447
|
+
bundleRegistry.set(bundleKey, buf);
|
|
5448
|
+
bundleBuilt = true;
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
if (bundleRegistry.has(bundleKey)) {
|
|
5452
|
+
bundle = { url: bundleKey };
|
|
5453
|
+
}
|
|
5454
|
+
const { renderToReadableStream } = await import("react-dom/server");
|
|
5455
|
+
const stream = await renderToReadableStream(element);
|
|
5456
|
+
return streamResponse(stream, {
|
|
5457
|
+
ctx,
|
|
5458
|
+
base,
|
|
5459
|
+
isDev,
|
|
5460
|
+
bundle,
|
|
5461
|
+
loaderData
|
|
5462
|
+
});
|
|
5463
|
+
});
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5467
|
+
// ssr/layout.ts
|
|
5468
|
+
function layout(path2) {
|
|
5469
|
+
return async (req, ctx, next) => {
|
|
5470
|
+
const mod = await compileTsx(path2);
|
|
5471
|
+
const Component = mod.default;
|
|
5472
|
+
if (!Component) throw new Error(`Layout ${path2} has no default export`);
|
|
5473
|
+
ctx.layoutStack = [...ctx.layoutStack || [], { path: path2, component: Component }];
|
|
5474
|
+
return next(req, ctx);
|
|
5475
|
+
};
|
|
5476
|
+
}
|
|
5477
|
+
|
|
5478
|
+
// ssr/tailwind.ts
|
|
5479
|
+
var isDev2 = process.env.NODE_ENV !== "production";
|
|
5480
|
+
|
|
5481
|
+
// ssr/error-boundary.ts
|
|
5482
|
+
import { createElement as createElement2 } from "react";
|
|
5483
|
+
|
|
5484
|
+
// ssr/live.ts
|
|
5485
|
+
import chokidar from "chokidar";
|
|
5486
|
+
|
|
5975
5487
|
// opencode/session.ts
|
|
5976
5488
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5977
5489
|
import { join as join3 } from "node:path";
|
|
@@ -5999,12 +5511,12 @@ var messages = pgTable("_opencode_messages", {
|
|
|
5999
5511
|
created_at: timestamptz("created_at")
|
|
6000
5512
|
});
|
|
6001
5513
|
async function createSession(sql2, opts, cwd, mountPath) {
|
|
6002
|
-
const
|
|
6003
|
-
const ws = computeSessionWorkspace(cwd, mountPath,
|
|
5514
|
+
const id3 = randomUUID2();
|
|
5515
|
+
const ws = computeSessionWorkspace(cwd, mountPath, id3);
|
|
6004
5516
|
await mkdir2(ws, { recursive: true });
|
|
6005
5517
|
const [row] = await sql2`
|
|
6006
5518
|
INSERT INTO "_opencode_sessions" ("id", "user_id", "title", "model", "workspace", "system_prompt")
|
|
6007
|
-
VALUES (${
|
|
5519
|
+
VALUES (${id3}, ${opts.userId ?? 0}, ${opts.title ?? null}, ${opts.model ?? "deepseek-v4-flash"}, ${ws}, ${opts.systemPrompt ?? null})
|
|
6008
5520
|
RETURNING *
|
|
6009
5521
|
`;
|
|
6010
5522
|
return row;
|
|
@@ -6013,8 +5525,8 @@ function computeSessionWorkspace(cwd, mountPath, sessionId) {
|
|
|
6013
5525
|
const name = !mountPath || mountPath === "/" ? "default" : mountPath.replace(/^\//, "");
|
|
6014
5526
|
return join3(cwd, ".sessions", name, sessionId);
|
|
6015
5527
|
}
|
|
6016
|
-
async function getSession(sql2,
|
|
6017
|
-
const { data: rows } = await sessions.readMany(sql2, { id:
|
|
5528
|
+
async function getSession(sql2, id3) {
|
|
5529
|
+
const { data: rows } = await sessions.readMany(sql2, { id: id3, active: true });
|
|
6018
5530
|
return rows[0] ?? null;
|
|
6019
5531
|
}
|
|
6020
5532
|
async function listSessions(sql2, userId) {
|
|
@@ -6026,8 +5538,8 @@ async function listSessions(sql2, userId) {
|
|
|
6026
5538
|
const { data: rows } = await sessions.readMany(sql2, { active: true }, opts);
|
|
6027
5539
|
return rows;
|
|
6028
5540
|
}
|
|
6029
|
-
async function deleteSession(sql2,
|
|
6030
|
-
await sessions.update(sql2,
|
|
5541
|
+
async function deleteSession(sql2, id3) {
|
|
5542
|
+
await sessions.update(sql2, id3, { active: false, updated_at: sql`NOW()` });
|
|
6031
5543
|
}
|
|
6032
5544
|
async function getHistory(sql2, sessionId, limit = 50) {
|
|
6033
5545
|
const { data: rows } = await messages.readMany(sql2, { session_id: sessionId }, {
|
|
@@ -6208,10 +5720,10 @@ function createBashTool(ctx) {
|
|
|
6208
5720
|
return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
|
|
6209
5721
|
}
|
|
6210
5722
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
6211
|
-
return new Promise((
|
|
5723
|
+
return new Promise((resolve12) => {
|
|
6212
5724
|
const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
6213
5725
|
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
6214
|
-
|
|
5726
|
+
resolve12({
|
|
6215
5727
|
stdout: stdout.slice(0, 1e6),
|
|
6216
5728
|
stderr: stderr.slice(0, 1e6),
|
|
6217
5729
|
exitCode: error?.code ?? 0,
|
|
@@ -6228,7 +5740,7 @@ function createBashTool(ctx) {
|
|
|
6228
5740
|
import { tool as tool4 } from "ai";
|
|
6229
5741
|
import { z as z6 } from "zod";
|
|
6230
5742
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
6231
|
-
import { resolve as
|
|
5743
|
+
import { resolve as resolve5 } from "node:path";
|
|
6232
5744
|
function createReadTool(ctx) {
|
|
6233
5745
|
return tool4({
|
|
6234
5746
|
description: "Read file contents. Supports offset and limit for reading specific line ranges.",
|
|
@@ -6238,7 +5750,7 @@ function createReadTool(ctx) {
|
|
|
6238
5750
|
limit: z6.number().optional().describe("Number of lines to read")
|
|
6239
5751
|
}),
|
|
6240
5752
|
execute: async ({ path: path2, offset, limit }) => {
|
|
6241
|
-
const resolved =
|
|
5753
|
+
const resolved = resolve5(ctx.workspace, path2);
|
|
6242
5754
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
6243
5755
|
return { error: "Path not allowed", content: null, totalLines: 0 };
|
|
6244
5756
|
}
|
|
@@ -6269,8 +5781,8 @@ function createReadTool(ctx) {
|
|
|
6269
5781
|
// opencode/tools/write.ts
|
|
6270
5782
|
import { tool as tool5 } from "ai";
|
|
6271
5783
|
import { z as z7 } from "zod";
|
|
6272
|
-
import { writeFileSync
|
|
6273
|
-
import { resolve as
|
|
5784
|
+
import { writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
5785
|
+
import { resolve as resolve6, dirname as dirname3 } from "node:path";
|
|
6274
5786
|
function createWriteTool(ctx) {
|
|
6275
5787
|
return tool5({
|
|
6276
5788
|
description: "Create or overwrite a file. Parent directories are created automatically.",
|
|
@@ -6279,12 +5791,12 @@ function createWriteTool(ctx) {
|
|
|
6279
5791
|
content: z7.string().describe("File content")
|
|
6280
5792
|
}),
|
|
6281
5793
|
execute: async ({ path: path2, content }) => {
|
|
6282
|
-
const resolved =
|
|
5794
|
+
const resolved = resolve6(ctx.workspace, path2);
|
|
6283
5795
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
6284
5796
|
return { error: "Path not allowed" };
|
|
6285
5797
|
}
|
|
6286
|
-
mkdirSync2(
|
|
6287
|
-
|
|
5798
|
+
mkdirSync2(dirname3(resolved), { recursive: true });
|
|
5799
|
+
writeFileSync(resolved, content, "utf-8");
|
|
6288
5800
|
return { path: path2, size: content.length };
|
|
6289
5801
|
}
|
|
6290
5802
|
});
|
|
@@ -6293,8 +5805,8 @@ function createWriteTool(ctx) {
|
|
|
6293
5805
|
// opencode/tools/edit.ts
|
|
6294
5806
|
import { tool as tool6 } from "ai";
|
|
6295
5807
|
import { z as z8 } from "zod";
|
|
6296
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
6297
|
-
import { resolve as
|
|
5808
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5809
|
+
import { resolve as resolve7 } from "node:path";
|
|
6298
5810
|
function createEditTool(ctx) {
|
|
6299
5811
|
return tool6({
|
|
6300
5812
|
description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
|
|
@@ -6305,7 +5817,7 @@ function createEditTool(ctx) {
|
|
|
6305
5817
|
replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
|
|
6306
5818
|
}),
|
|
6307
5819
|
execute: async ({ path: path2, oldString, newString, replaceAll }) => {
|
|
6308
|
-
const resolved =
|
|
5820
|
+
const resolved = resolve7(ctx.workspace, path2);
|
|
6309
5821
|
if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
|
|
6310
5822
|
return { error: "Path not allowed" };
|
|
6311
5823
|
}
|
|
@@ -6316,7 +5828,7 @@ function createEditTool(ctx) {
|
|
|
6316
5828
|
}
|
|
6317
5829
|
const count = content.split(oldString).length - 1;
|
|
6318
5830
|
const result2 = content.replaceAll(oldString, newString);
|
|
6319
|
-
|
|
5831
|
+
writeFileSync2(resolved, result2, "utf-8");
|
|
6320
5832
|
return { path: path2, replaced: count };
|
|
6321
5833
|
}
|
|
6322
5834
|
const firstIdx = content.indexOf(oldString);
|
|
@@ -6328,7 +5840,7 @@ function createEditTool(ctx) {
|
|
|
6328
5840
|
return { error: "Found multiple matches. Provide more surrounding context in oldString.", replaced: 0 };
|
|
6329
5841
|
}
|
|
6330
5842
|
const result = content.replace(oldString, newString);
|
|
6331
|
-
|
|
5843
|
+
writeFileSync2(resolved, result, "utf-8");
|
|
6332
5844
|
return { path: path2, replaced: 1 };
|
|
6333
5845
|
}
|
|
6334
5846
|
});
|
|
@@ -6338,7 +5850,7 @@ function createEditTool(ctx) {
|
|
|
6338
5850
|
import { tool as tool7 } from "ai";
|
|
6339
5851
|
import { z as z9 } from "zod";
|
|
6340
5852
|
import { execFileSync } from "node:child_process";
|
|
6341
|
-
import { resolve as
|
|
5853
|
+
import { resolve as resolve8 } from "node:path";
|
|
6342
5854
|
import { existsSync as existsSync3 } from "node:fs";
|
|
6343
5855
|
function createGrepTool(ctx) {
|
|
6344
5856
|
return tool7({
|
|
@@ -6350,7 +5862,7 @@ function createGrepTool(ctx) {
|
|
|
6350
5862
|
context: z9.number().default(0).describe("Number of context lines before and after each match")
|
|
6351
5863
|
}),
|
|
6352
5864
|
execute: async ({ pattern, include, path: path2, context }) => {
|
|
6353
|
-
const searchDir = path2 ?
|
|
5865
|
+
const searchDir = path2 ? resolve8(ctx.workspace, path2) : ctx.workspace;
|
|
6354
5866
|
try {
|
|
6355
5867
|
let stdout;
|
|
6356
5868
|
if (existsSync3("/usr/bin/rg") || existsSync3("/usr/local/bin/rg")) {
|
|
@@ -6382,7 +5894,7 @@ function createGrepTool(ctx) {
|
|
|
6382
5894
|
import { tool as tool8 } from "ai";
|
|
6383
5895
|
import { z as z10 } from "zod";
|
|
6384
5896
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6385
|
-
import { resolve as
|
|
5897
|
+
import { resolve as resolve9 } from "node:path";
|
|
6386
5898
|
function createGlobTool(ctx) {
|
|
6387
5899
|
return tool8({
|
|
6388
5900
|
description: "Find files matching a glob pattern.",
|
|
@@ -6391,7 +5903,7 @@ function createGlobTool(ctx) {
|
|
|
6391
5903
|
path: z10.string().optional().describe("Subdirectory relative to workspace")
|
|
6392
5904
|
}),
|
|
6393
5905
|
execute: async ({ pattern, path: path2 }) => {
|
|
6394
|
-
const searchDir = path2 ?
|
|
5906
|
+
const searchDir = path2 ? resolve9(ctx.workspace, path2) : ctx.workspace;
|
|
6395
5907
|
try {
|
|
6396
5908
|
const stdout = execFileSync2("find", [
|
|
6397
5909
|
searchDir,
|
|
@@ -6452,7 +5964,7 @@ function createQuestionTool(ctx) {
|
|
|
6452
5964
|
options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
|
|
6453
5965
|
}),
|
|
6454
5966
|
execute: async ({ question, options }, { toolCallId }) => {
|
|
6455
|
-
return new Promise((
|
|
5967
|
+
return new Promise((resolve12, reject) => {
|
|
6456
5968
|
const timeout = setTimeout(() => {
|
|
6457
5969
|
ctx.pendingQuestions.delete(toolCallId);
|
|
6458
5970
|
reject(new Error("Question timed out"));
|
|
@@ -6460,7 +5972,7 @@ function createQuestionTool(ctx) {
|
|
|
6460
5972
|
ctx.pendingQuestions.set(toolCallId, {
|
|
6461
5973
|
resolve: (answer) => {
|
|
6462
5974
|
clearTimeout(timeout);
|
|
6463
|
-
|
|
5975
|
+
resolve12(answer);
|
|
6464
5976
|
},
|
|
6465
5977
|
reject: (err) => {
|
|
6466
5978
|
clearTimeout(timeout);
|
|
@@ -6561,15 +6073,15 @@ async function buildRouter4(deps) {
|
|
|
6561
6073
|
return Response.json(sessions2);
|
|
6562
6074
|
});
|
|
6563
6075
|
router.get("/sessions/:id", async (_req, ctx) => {
|
|
6564
|
-
const
|
|
6565
|
-
const session = await getSession(sql2,
|
|
6076
|
+
const id3 = ctx.params.id;
|
|
6077
|
+
const session = await getSession(sql2, id3);
|
|
6566
6078
|
if (!session) return new Response("Not Found", { status: 404 });
|
|
6567
|
-
const messages2 = await getHistory(sql2,
|
|
6079
|
+
const messages2 = await getHistory(sql2, id3);
|
|
6568
6080
|
return Response.json({ session, messages: messages2 });
|
|
6569
6081
|
});
|
|
6570
6082
|
router.delete("/sessions/:id", async (_req, ctx) => {
|
|
6571
|
-
const
|
|
6572
|
-
await deleteSession(sql2,
|
|
6083
|
+
const id3 = ctx.params.id;
|
|
6084
|
+
await deleteSession(sql2, id3);
|
|
6573
6085
|
return new Response(null, { status: 204 });
|
|
6574
6086
|
});
|
|
6575
6087
|
router.post("/sessions/:id/message", async (req, ctx) => {
|
|
@@ -6607,8 +6119,8 @@ async function buildRouter4(deps) {
|
|
|
6607
6119
|
});
|
|
6608
6120
|
try {
|
|
6609
6121
|
const uiDir = new URL("../opencode/ui/", import.meta.url).pathname;
|
|
6610
|
-
|
|
6611
|
-
router.
|
|
6122
|
+
router.use(layout(join4(uiDir, "layout.tsx")));
|
|
6123
|
+
router.get("/", ssr(join4(uiDir, "page.tsx")));
|
|
6612
6124
|
} catch (e) {
|
|
6613
6125
|
console.warn("[opencode] UI not available:", e);
|
|
6614
6126
|
}
|
|
@@ -6743,7 +6255,7 @@ function createWSHandler2(deps) {
|
|
|
6743
6255
|
import { readFile } from "node:fs/promises";
|
|
6744
6256
|
import { glob } from "node:fs/promises";
|
|
6745
6257
|
import { homedir } from "node:os";
|
|
6746
|
-
import { resolve as
|
|
6258
|
+
import { resolve as resolve10 } from "node:path";
|
|
6747
6259
|
import { parse as parseYaml } from "yaml";
|
|
6748
6260
|
var SEARCH_DIRS = [
|
|
6749
6261
|
(ws) => `${ws}/.opencode/skills`,
|
|
@@ -6781,7 +6293,7 @@ async function scanDir(dir) {
|
|
|
6781
6293
|
try {
|
|
6782
6294
|
const files = [];
|
|
6783
6295
|
for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
|
|
6784
|
-
const skill = await parseSkillFile(
|
|
6296
|
+
const skill = await parseSkillFile(resolve10(dir, entry));
|
|
6785
6297
|
if (skill) files.push(skill);
|
|
6786
6298
|
}
|
|
6787
6299
|
return files;
|
|
@@ -6838,47 +6350,47 @@ async function opencode(options) {
|
|
|
6838
6350
|
const model = provider.chat(modelName);
|
|
6839
6351
|
const pendingQuestions = /* @__PURE__ */ new Map();
|
|
6840
6352
|
const base = new PgModule(pg);
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
router: () => buildRouter4({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions }),
|
|
6874
|
-
wsHandler: () => createWSHandler2({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions }),
|
|
6875
|
-
close: () => base.close()
|
|
6353
|
+
const r = await buildRouter4({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions });
|
|
6354
|
+
const mod = r;
|
|
6355
|
+
mod.migrate = async () => {
|
|
6356
|
+
const sessions2 = pgTable("_opencode_sessions", {
|
|
6357
|
+
id: uuid("id").default(sql`gen_random_uuid()`).primaryKey(),
|
|
6358
|
+
tenant_id: text("tenant_id"),
|
|
6359
|
+
user_id: integer("user_id").default(0),
|
|
6360
|
+
title: text("title"),
|
|
6361
|
+
agent_type: text("agent_type").default("build"),
|
|
6362
|
+
model: text("model").default("deepseek-v4-flash"),
|
|
6363
|
+
system_prompt: text("system_prompt"),
|
|
6364
|
+
workspace: text("workspace"),
|
|
6365
|
+
metadata: jsonb("metadata").default(sql`'{}'::jsonb`),
|
|
6366
|
+
active: boolean_("active").default(true),
|
|
6367
|
+
created_at: timestamptz("created_at").default(sql`NOW()`),
|
|
6368
|
+
updated_at: timestamptz("updated_at").default(sql`NOW()`)
|
|
6369
|
+
});
|
|
6370
|
+
await sessions2.create(sql2);
|
|
6371
|
+
await sessions2.createIndex(sql2, "user_id");
|
|
6372
|
+
const messages2 = pgTable("_opencode_messages", {
|
|
6373
|
+
id: serial("id").primaryKey(),
|
|
6374
|
+
session_id: uuid("session_id").notNull().references("_opencode_sessions", "id", "cascade"),
|
|
6375
|
+
role: text("role").notNull(),
|
|
6376
|
+
content: text("content"),
|
|
6377
|
+
tool_calls: jsonb("tool_calls"),
|
|
6378
|
+
tool_results: jsonb("tool_results"),
|
|
6379
|
+
tokens_in: integer("tokens_in").default(0),
|
|
6380
|
+
tokens_out: integer("tokens_out").default(0),
|
|
6381
|
+
created_at: timestamptz("created_at").default(sql`NOW()`)
|
|
6382
|
+
});
|
|
6383
|
+
await messages2.create(sql2);
|
|
6384
|
+
await messages2.createIndex(sql2, ["session_id", "created_at"]);
|
|
6876
6385
|
};
|
|
6386
|
+
mod.wsHandler = () => createWSHandler2({ sql: sql2, model, workspace, systemPrompt, skills: manualSkills, skillsRegistry, permissions, pendingQuestions });
|
|
6387
|
+
mod.close = () => base.close();
|
|
6388
|
+
return mod;
|
|
6877
6389
|
}
|
|
6878
6390
|
|
|
6879
6391
|
// health.ts
|
|
6880
6392
|
function health(options) {
|
|
6881
|
-
const path2 = options?.path ?? "/
|
|
6393
|
+
const path2 = options?.path ?? "/";
|
|
6882
6394
|
const r = new Router();
|
|
6883
6395
|
const handler = async () => {
|
|
6884
6396
|
try {
|
|
@@ -7099,24 +6611,25 @@ function analytics(options) {
|
|
|
7099
6611
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
7100
6612
|
});
|
|
7101
6613
|
};
|
|
7102
|
-
const
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
r.get("/__analytics", handler);
|
|
7106
|
-
return r;
|
|
7107
|
-
};
|
|
6614
|
+
const r = new Router();
|
|
6615
|
+
r.get("/__analytics/data", handler);
|
|
6616
|
+
r.get("/__analytics", handler);
|
|
7108
6617
|
const migrate = async () => {
|
|
7109
6618
|
if (pg) await migratePg(pg.sql, pg.table);
|
|
7110
6619
|
};
|
|
7111
6620
|
const close = async () => {
|
|
7112
6621
|
};
|
|
7113
|
-
|
|
6622
|
+
const mod = r;
|
|
6623
|
+
mod.middleware = middleware;
|
|
6624
|
+
mod.migrate = migrate;
|
|
6625
|
+
mod.close = close;
|
|
6626
|
+
return mod;
|
|
7114
6627
|
}
|
|
7115
6628
|
|
|
7116
6629
|
// preferences.ts
|
|
7117
6630
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
7118
6631
|
import { existsSync as existsSync4 } from "node:fs";
|
|
7119
|
-
import { join as
|
|
6632
|
+
import { join as join5, resolve as resolve11 } from "node:path";
|
|
7120
6633
|
var defaults = {
|
|
7121
6634
|
locale: { default: "en", cookie: "locale", fromAcceptLanguage: true },
|
|
7122
6635
|
theme: { default: "system", cookie: "theme" }
|
|
@@ -7165,24 +6678,24 @@ async function handlePrefSwitch(req, value, cookieName, load) {
|
|
|
7165
6678
|
});
|
|
7166
6679
|
}
|
|
7167
6680
|
function preferences(options) {
|
|
7168
|
-
const dir = options.dir ?
|
|
6681
|
+
const dir = options.dir ? resolve11(options.dir) : void 0;
|
|
7169
6682
|
const localeOpts = { ...defaults.locale, ...options.locale };
|
|
7170
6683
|
const themeOpts = { ...defaults.theme, ...options.theme };
|
|
7171
|
-
const
|
|
6684
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
7172
6685
|
function validLocale(locale) {
|
|
7173
6686
|
return /^[\w-]+$/.test(locale) && !locale.includes("..");
|
|
7174
6687
|
}
|
|
7175
6688
|
async function load(locale) {
|
|
7176
6689
|
if (!dir) return {};
|
|
7177
6690
|
if (!validLocale(locale)) return {};
|
|
7178
|
-
const cached =
|
|
6691
|
+
const cached = cache2.get(locale);
|
|
7179
6692
|
if (cached) return cached;
|
|
7180
|
-
const filePath =
|
|
6693
|
+
const filePath = join5(dir, `${locale}.json`);
|
|
7181
6694
|
if (!existsSync4(filePath)) return {};
|
|
7182
6695
|
try {
|
|
7183
6696
|
const content = await readFile2(filePath, "utf-8");
|
|
7184
6697
|
const data = JSON.parse(content);
|
|
7185
|
-
|
|
6698
|
+
cache2.set(locale, data);
|
|
7186
6699
|
return data;
|
|
7187
6700
|
} catch {
|
|
7188
6701
|
return {};
|
|
@@ -7529,9 +7042,9 @@ function listHandler(entries) {
|
|
|
7529
7042
|
}
|
|
7530
7043
|
function getHandler(entries) {
|
|
7531
7044
|
return async (_req, ctx) => {
|
|
7532
|
-
const
|
|
7533
|
-
if (!
|
|
7534
|
-
const row = await entries.read(parseInt(
|
|
7045
|
+
const id3 = ctx.params?.id;
|
|
7046
|
+
if (!id3) return Response.json({ error: "id is required" }, { status: 400 });
|
|
7047
|
+
const row = await entries.read(parseInt(id3));
|
|
7535
7048
|
if (!row) return Response.json({ error: "not found" }, { status: 404 });
|
|
7536
7049
|
return Response.json(parseMetadata(row));
|
|
7537
7050
|
};
|
|
@@ -7585,11 +7098,11 @@ function logdb(options) {
|
|
|
7585
7098
|
return row;
|
|
7586
7099
|
}
|
|
7587
7100
|
function router() {
|
|
7588
|
-
const
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
return
|
|
7101
|
+
const r2 = new Router();
|
|
7102
|
+
r2.post("/", createHandler(entries));
|
|
7103
|
+
r2.get("/", listHandler(entries));
|
|
7104
|
+
r2.get("/:id", getHandler(entries));
|
|
7105
|
+
return r2;
|
|
7593
7106
|
}
|
|
7594
7107
|
async function clean(retentionMonths) {
|
|
7595
7108
|
const cutoff = /* @__PURE__ */ new Date();
|
|
@@ -7611,21 +7124,22 @@ function logdb(options) {
|
|
|
7611
7124
|
}
|
|
7612
7125
|
return dropped;
|
|
7613
7126
|
}
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7127
|
+
async function migrate() {
|
|
7128
|
+
await entries.create({ partitionBy: partitionBy("range", "created_at") });
|
|
7129
|
+
await entries.createIndex(["created_at", "id"]);
|
|
7130
|
+
await entries.createIndex(["level"]);
|
|
7131
|
+
await entries.createIndex(["source"]);
|
|
7132
|
+
await entries.createIndex(["level", "created_at"]);
|
|
7133
|
+
await ensurePartitions(sql2, tableName);
|
|
7134
|
+
}
|
|
7135
|
+
const r = router();
|
|
7136
|
+
const mod = r;
|
|
7137
|
+
mod.log = log;
|
|
7138
|
+
mod.migrate = migrate;
|
|
7139
|
+
mod.clean = clean;
|
|
7140
|
+
mod.close = async () => {
|
|
7628
7141
|
};
|
|
7142
|
+
return mod;
|
|
7629
7143
|
}
|
|
7630
7144
|
|
|
7631
7145
|
// iii/client.ts
|
|
@@ -7748,8 +7262,8 @@ function createMemoryStore(channels) {
|
|
|
7748
7262
|
}));
|
|
7749
7263
|
return { streams, count: streams.length };
|
|
7750
7264
|
},
|
|
7751
|
-
async send(stream, group, type, data,
|
|
7752
|
-
notify(channels, stream, group,
|
|
7265
|
+
async send(stream, group, type, data, id3) {
|
|
7266
|
+
notify(channels, stream, group, id3 ?? "", "send", { type, data });
|
|
7753
7267
|
},
|
|
7754
7268
|
async update(stream, group, item, ops) {
|
|
7755
7269
|
const k = key(stream, group, item);
|
|
@@ -7829,8 +7343,8 @@ function createPgStore(channels, pg) {
|
|
|
7829
7343
|
}));
|
|
7830
7344
|
return { streams, count: streams.length };
|
|
7831
7345
|
},
|
|
7832
|
-
async send(stream, group, type, data,
|
|
7833
|
-
notify(channels, stream, group,
|
|
7346
|
+
async send(stream, group, type, data, id3) {
|
|
7347
|
+
notify(channels, stream, group, id3 ?? "", "send", { type, data });
|
|
7834
7348
|
},
|
|
7835
7349
|
async update(stream, group, item, ops) {
|
|
7836
7350
|
const { value: oldVal } = await this.get(stream, group, item);
|
|
@@ -7926,8 +7440,8 @@ function createRedisStore(channels, redis2, ttl) {
|
|
|
7926
7440
|
}));
|
|
7927
7441
|
return { streams, count: streams.length };
|
|
7928
7442
|
},
|
|
7929
|
-
async send(stream, group, type, data,
|
|
7930
|
-
notify(channels, stream, group,
|
|
7443
|
+
async send(stream, group, type, data, id3) {
|
|
7444
|
+
notify(channels, stream, group, id3 ?? "", "send", { type, data });
|
|
7931
7445
|
},
|
|
7932
7446
|
async update(stream, group, item, ops) {
|
|
7933
7447
|
const hk = hashKey(stream, group);
|
|
@@ -8140,9 +7654,9 @@ function iii(opts = {}) {
|
|
|
8140
7654
|
const functions = /* @__PURE__ */ new Map();
|
|
8141
7655
|
const triggers = /* @__PURE__ */ new Map();
|
|
8142
7656
|
const pending = /* @__PURE__ */ new Map();
|
|
8143
|
-
function registerBuiltin(
|
|
8144
|
-
functions.set(
|
|
8145
|
-
id:
|
|
7657
|
+
function registerBuiltin(id3, handler) {
|
|
7658
|
+
functions.set(id3, {
|
|
7659
|
+
id: id3,
|
|
8146
7660
|
handler,
|
|
8147
7661
|
workerId: "__iii__",
|
|
8148
7662
|
workerName: "__iii__",
|
|
@@ -8197,34 +7711,34 @@ function iii(opts = {}) {
|
|
|
8197
7711
|
}
|
|
8198
7712
|
workers.set(workerId, reg);
|
|
8199
7713
|
}
|
|
8200
|
-
function addRemoteFunction(workerId,
|
|
7714
|
+
function addRemoteFunction(workerId, id3) {
|
|
8201
7715
|
const worker = workers.get(workerId);
|
|
8202
7716
|
if (!worker) return;
|
|
8203
7717
|
const handler = async (payload) => {
|
|
8204
7718
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
8205
7719
|
const invocationId = crypto6.randomUUID();
|
|
8206
|
-
return new Promise((
|
|
7720
|
+
return new Promise((resolve12, reject) => {
|
|
8207
7721
|
const timer = setTimeout(() => {
|
|
8208
7722
|
pending.delete(invocationId);
|
|
8209
|
-
reject(new Error(`Invocation timed out for "${
|
|
7723
|
+
reject(new Error(`Invocation timed out for "${id3}"`));
|
|
8210
7724
|
}, 3e4);
|
|
8211
|
-
pending.set(invocationId, { resolve:
|
|
7725
|
+
pending.set(invocationId, { resolve: resolve12, reject, timer });
|
|
8212
7726
|
worker.ws.send(JSON.stringify({
|
|
8213
7727
|
type: "invoke",
|
|
8214
7728
|
invocation_id: invocationId,
|
|
8215
|
-
function_id:
|
|
7729
|
+
function_id: id3,
|
|
8216
7730
|
payload
|
|
8217
7731
|
}));
|
|
8218
7732
|
});
|
|
8219
7733
|
};
|
|
8220
7734
|
const fnReg = {
|
|
8221
|
-
id:
|
|
7735
|
+
id: id3,
|
|
8222
7736
|
handler,
|
|
8223
7737
|
workerId,
|
|
8224
7738
|
workerName: worker.name,
|
|
8225
7739
|
triggers: []
|
|
8226
7740
|
};
|
|
8227
|
-
functions.set(
|
|
7741
|
+
functions.set(id3, fnReg);
|
|
8228
7742
|
worker.functions.push(fnReg);
|
|
8229
7743
|
}
|
|
8230
7744
|
function removeWorker(workerId) {
|
|
@@ -8234,17 +7748,18 @@ function iii(opts = {}) {
|
|
|
8234
7748
|
for (const t of reg.triggers) triggers.delete(t.id);
|
|
8235
7749
|
workers.delete(workerId);
|
|
8236
7750
|
}
|
|
7751
|
+
let engineRef = null;
|
|
8237
7752
|
const wsHandler = createWsHandler({
|
|
8238
7753
|
registerRemoteWorker(ws, name) {
|
|
8239
|
-
const
|
|
8240
|
-
workers.set(
|
|
8241
|
-
return
|
|
7754
|
+
const id3 = crypto6.randomUUID();
|
|
7755
|
+
workers.set(id3, { id: id3, name, ws, functions: [], triggers: [] });
|
|
7756
|
+
return id3;
|
|
8242
7757
|
},
|
|
8243
7758
|
unregisterRemoteWorker(workerId) {
|
|
8244
7759
|
removeWorker(workerId);
|
|
8245
7760
|
},
|
|
8246
|
-
registerRemoteFunction(workerId,
|
|
8247
|
-
addRemoteFunction(workerId,
|
|
7761
|
+
registerRemoteFunction(workerId, id3) {
|
|
7762
|
+
addRemoteFunction(workerId, id3);
|
|
8248
7763
|
},
|
|
8249
7764
|
registerRemoteTrigger(workerId, input) {
|
|
8250
7765
|
const tid = crypto6.randomUUID();
|
|
@@ -8253,11 +7768,11 @@ function iii(opts = {}) {
|
|
|
8253
7768
|
const worker = workers.get(workerId);
|
|
8254
7769
|
if (worker) worker.triggers.push(reg);
|
|
8255
7770
|
},
|
|
8256
|
-
unregisterRemoteFunction(workerId,
|
|
8257
|
-
functions.delete(
|
|
7771
|
+
unregisterRemoteFunction(workerId, id3) {
|
|
7772
|
+
functions.delete(id3);
|
|
8258
7773
|
const worker = workers.get(workerId);
|
|
8259
7774
|
if (worker) {
|
|
8260
|
-
worker.functions = worker.functions.filter((f) => f.id !==
|
|
7775
|
+
worker.functions = worker.functions.filter((f) => f.id !== id3);
|
|
8261
7776
|
}
|
|
8262
7777
|
},
|
|
8263
7778
|
unregisterRemoteTrigger(workerId, functionId) {
|
|
@@ -8304,7 +7819,7 @@ function iii(opts = {}) {
|
|
|
8304
7819
|
}));
|
|
8305
7820
|
return;
|
|
8306
7821
|
}
|
|
8307
|
-
const ctx = { engine:
|
|
7822
|
+
const ctx = { engine: engineRef, functionId, workerName: fn.workerName };
|
|
8308
7823
|
Promise.resolve(fn.handler(payload, ctx)).then((result) => {
|
|
8309
7824
|
ws.send(JSON.stringify({ type: "invoke_result", invocation_id: invocationId, result }));
|
|
8310
7825
|
}).catch((err) => {
|
|
@@ -8312,70 +7827,78 @@ function iii(opts = {}) {
|
|
|
8312
7827
|
});
|
|
8313
7828
|
}
|
|
8314
7829
|
});
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
},
|
|
8321
|
-
wsHandler: () => wsHandler,
|
|
8322
|
-
addWorker: addLocalWorker,
|
|
8323
|
-
removeWorker: (worker) => {
|
|
8324
|
-
for (const [wid, reg] of workers) {
|
|
8325
|
-
if (reg.name === worker.name) {
|
|
8326
|
-
removeWorker(wid);
|
|
8327
|
-
return;
|
|
8328
|
-
}
|
|
7830
|
+
function removeWorkerByName(worker) {
|
|
7831
|
+
for (const [wid, reg] of workers) {
|
|
7832
|
+
if (reg.name === worker.name) {
|
|
7833
|
+
removeWorker(wid);
|
|
7834
|
+
return;
|
|
8329
7835
|
}
|
|
8330
|
-
}
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
function trigger(request) {
|
|
7839
|
+
const fn = functions.get(request.function_id);
|
|
7840
|
+
if (!fn) throw new Error(`Function "${request.function_id}" not found`);
|
|
7841
|
+
const ctx = { engine: engineRef, functionId: request.function_id, workerName: fn.workerName };
|
|
7842
|
+
if (request.action === "void") {
|
|
7843
|
+
queueMicrotask(() => fn.handler(request.payload, ctx));
|
|
7844
|
+
return Promise.resolve(void 0);
|
|
7845
|
+
}
|
|
7846
|
+
return Promise.resolve(fn.handler(request.payload, ctx));
|
|
7847
|
+
}
|
|
7848
|
+
function listWorkers() {
|
|
7849
|
+
return Array.from(workers.values()).map((w) => ({
|
|
8342
7850
|
id: w.id,
|
|
8343
7851
|
name: w.name,
|
|
8344
7852
|
status: "connected",
|
|
8345
7853
|
connectedAt: Date.now(),
|
|
8346
7854
|
functionCount: w.functions.length,
|
|
8347
7855
|
triggerCount: w.triggers.length
|
|
8348
|
-
}))
|
|
8349
|
-
|
|
7856
|
+
}));
|
|
7857
|
+
}
|
|
7858
|
+
function listFunctions() {
|
|
7859
|
+
return Array.from(functions.values()).map((f) => ({
|
|
8350
7860
|
id: f.id,
|
|
8351
7861
|
workerId: f.workerId,
|
|
8352
7862
|
workerName: f.workerName,
|
|
8353
7863
|
triggers: f.triggers
|
|
8354
|
-
}))
|
|
8355
|
-
|
|
7864
|
+
}));
|
|
7865
|
+
}
|
|
7866
|
+
function listTriggers() {
|
|
7867
|
+
return Array.from(triggers.values()).map((t) => ({
|
|
8356
7868
|
id: t.id,
|
|
8357
7869
|
type: t.type,
|
|
8358
7870
|
function_id: t.function_id,
|
|
8359
7871
|
config: t.config,
|
|
8360
7872
|
workerId: t.workerId
|
|
8361
|
-
}))
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
}
|
|
7873
|
+
}));
|
|
7874
|
+
}
|
|
7875
|
+
const routerMethods = { listWorkers, listFunctions, listTriggers, trigger };
|
|
7876
|
+
const r = buildRouter5(routerMethods, wsHandler);
|
|
7877
|
+
engineRef = r;
|
|
7878
|
+
const mod = r;
|
|
7879
|
+
mod.wsHandler = () => wsHandler;
|
|
7880
|
+
mod.addWorker = addLocalWorker;
|
|
7881
|
+
mod.removeWorker = removeWorkerByName;
|
|
7882
|
+
mod.trigger = trigger;
|
|
7883
|
+
mod.listWorkers = listWorkers;
|
|
7884
|
+
mod.listFunctions = listFunctions;
|
|
7885
|
+
mod.listTriggers = listTriggers;
|
|
7886
|
+
mod.migrate = async () => {
|
|
7887
|
+
await stream.migrate();
|
|
8377
7888
|
};
|
|
8378
|
-
|
|
7889
|
+
mod.shutdown = async () => {
|
|
7890
|
+
for (const [, p] of pending) {
|
|
7891
|
+
clearTimeout(p.timer);
|
|
7892
|
+
p.reject(new Error("Engine shutting down"));
|
|
7893
|
+
}
|
|
7894
|
+
pending.clear();
|
|
7895
|
+
for (const [, reg] of workers) reg.ws?.close();
|
|
7896
|
+
workers.clear();
|
|
7897
|
+
functions.clear();
|
|
7898
|
+
triggers.clear();
|
|
7899
|
+
await stream.close();
|
|
7900
|
+
};
|
|
7901
|
+
return mod;
|
|
8379
7902
|
}
|
|
8380
7903
|
|
|
8381
7904
|
// iii/worker.ts
|
|
@@ -8384,12 +7907,12 @@ function createWorker(name) {
|
|
|
8384
7907
|
const triggers = /* @__PURE__ */ new Map();
|
|
8385
7908
|
return {
|
|
8386
7909
|
name,
|
|
8387
|
-
registerFunction(
|
|
8388
|
-
functions.set(
|
|
7910
|
+
registerFunction(id3, handler) {
|
|
7911
|
+
functions.set(id3, handler);
|
|
8389
7912
|
return this;
|
|
8390
7913
|
},
|
|
8391
|
-
unregisterFunction(
|
|
8392
|
-
functions.delete(
|
|
7914
|
+
unregisterFunction(id3) {
|
|
7915
|
+
functions.delete(id3);
|
|
8393
7916
|
return this;
|
|
8394
7917
|
},
|
|
8395
7918
|
registerTrigger(input) {
|
|
@@ -8401,10 +7924,10 @@ function createWorker(name) {
|
|
|
8401
7924
|
return this;
|
|
8402
7925
|
},
|
|
8403
7926
|
getFunctions() {
|
|
8404
|
-
return Array.from(functions.entries()).map(([
|
|
7927
|
+
return Array.from(functions.entries()).map(([id3, handler]) => ({ id: id3, handler }));
|
|
8405
7928
|
},
|
|
8406
7929
|
getTriggers() {
|
|
8407
|
-
return Array.from(triggers.entries()).map(([
|
|
7930
|
+
return Array.from(triggers.entries()).map(([id3, input]) => ({ id: id3, input }));
|
|
8408
7931
|
}
|
|
8409
7932
|
};
|
|
8410
7933
|
}
|
|
@@ -8440,8 +7963,8 @@ function registerWorker(url) {
|
|
|
8440
7963
|
function connect() {
|
|
8441
7964
|
if (intentionalClose) return;
|
|
8442
7965
|
ws = new WebSocket(url);
|
|
8443
|
-
ready = new Promise((
|
|
8444
|
-
resolveReady =
|
|
7966
|
+
ready = new Promise((resolve12) => {
|
|
7967
|
+
resolveReady = resolve12;
|
|
8445
7968
|
});
|
|
8446
7969
|
ws.onopen = () => {
|
|
8447
7970
|
reconnectAttempt = 0;
|
|
@@ -8524,15 +8047,15 @@ function registerWorker(url) {
|
|
|
8524
8047
|
}
|
|
8525
8048
|
connect();
|
|
8526
8049
|
return {
|
|
8527
|
-
registerFunction(
|
|
8528
|
-
handlers.set(
|
|
8529
|
-
registeredFunctionIds.add(
|
|
8530
|
-
send({ type: "register_function", id:
|
|
8050
|
+
registerFunction(id3, handler) {
|
|
8051
|
+
handlers.set(id3, handler);
|
|
8052
|
+
registeredFunctionIds.add(id3);
|
|
8053
|
+
send({ type: "register_function", id: id3 });
|
|
8531
8054
|
},
|
|
8532
|
-
unregisterFunction(
|
|
8533
|
-
handlers.delete(
|
|
8534
|
-
registeredFunctionIds.delete(
|
|
8535
|
-
send({ type: "unregister_function", id:
|
|
8055
|
+
unregisterFunction(id3) {
|
|
8056
|
+
handlers.delete(id3);
|
|
8057
|
+
registeredFunctionIds.delete(id3);
|
|
8058
|
+
send({ type: "unregister_function", id: id3 });
|
|
8536
8059
|
},
|
|
8537
8060
|
registerTrigger(input) {
|
|
8538
8061
|
registeredTriggers.add(JSON.stringify(input));
|
|
@@ -8561,13 +8084,13 @@ function registerWorker(url) {
|
|
|
8561
8084
|
}
|
|
8562
8085
|
return Promise.resolve(fn(request.payload, ctx));
|
|
8563
8086
|
}
|
|
8564
|
-
return new Promise((
|
|
8087
|
+
return new Promise((resolve12, reject) => {
|
|
8565
8088
|
const invocationId = genId();
|
|
8566
8089
|
const timer = setTimeout(() => {
|
|
8567
8090
|
pendingInvocations.delete(invocationId);
|
|
8568
8091
|
reject(new Error(`Invocation timed out for "${request.function_id}"`));
|
|
8569
8092
|
}, request.timeout_ms || 3e4);
|
|
8570
|
-
pendingInvocations.set(invocationId, { resolve:
|
|
8093
|
+
pendingInvocations.set(invocationId, { resolve: resolve12, reject, timer });
|
|
8571
8094
|
send({
|
|
8572
8095
|
type: "invoke",
|
|
8573
8096
|
invocation_id: invocationId,
|
|
@@ -8642,7 +8165,6 @@ export {
|
|
|
8642
8165
|
streamText,
|
|
8643
8166
|
tenant,
|
|
8644
8167
|
tool2 as tool,
|
|
8645
|
-
tsx,
|
|
8646
8168
|
upload,
|
|
8647
8169
|
user,
|
|
8648
8170
|
validate
|