weifuwu 0.17.13 → 0.17.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/iii/stream.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +601 -497
- package/dist/messager/agent.d.ts +6 -0
- package/dist/messager/rest.d.ts +2 -0
- package/dist/messager/ws.d.ts +6 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +27 -8
- package/dist/tsx-context.d.ts +11 -0
- package/dist/tsx-instance.d.ts +13 -3
- package/dist/tsx.d.ts +5 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -571,12 +571,15 @@ import { pathToFileURL } from "node:url";
|
|
|
571
571
|
import { createHash } from "node:crypto";
|
|
572
572
|
import vm from "node:vm";
|
|
573
573
|
import { createRequire } from "node:module";
|
|
574
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
574
575
|
import chokidar from "chokidar";
|
|
575
576
|
|
|
576
577
|
// tsx-context.ts
|
|
577
578
|
import { useSyncExternalStore, createContext } from "react";
|
|
578
579
|
var fallbackT = (key, _params, fallback) => fallback ?? key;
|
|
579
|
-
var
|
|
580
|
+
var DEFAULT_CTX = { params: {}, query: {}, parsed: {}, prefs: {}, env: {}, t: fallbackT, user: {} };
|
|
581
|
+
var _ctx = DEFAULT_CTX;
|
|
582
|
+
var _snapshot = { params: _ctx.params, query: _ctx.query, user: _ctx.user, parsed: _ctx.parsed, prefs: _ctx.prefs, env: _ctx.env };
|
|
580
583
|
var _listeners = /* @__PURE__ */ new Set();
|
|
581
584
|
var subscribe = (cb) => {
|
|
582
585
|
_listeners.add(cb);
|
|
@@ -584,16 +587,26 @@ var subscribe = (cb) => {
|
|
|
584
587
|
_listeners.delete(cb);
|
|
585
588
|
};
|
|
586
589
|
};
|
|
587
|
-
var getSnapshot = () =>
|
|
590
|
+
var getSnapshot = () => _snapshot;
|
|
588
591
|
var getServerSnapshot = getSnapshot;
|
|
592
|
+
var _alsGetStore = null;
|
|
593
|
+
function __registerAls(getStore) {
|
|
594
|
+
_alsGetStore = getStore;
|
|
595
|
+
}
|
|
589
596
|
function setCtx(value) {
|
|
590
597
|
_ctx = { ..._ctx, ...value };
|
|
598
|
+
_snapshot = { params: _ctx.params, query: _ctx.query, user: _ctx.user, parsed: _ctx.parsed, prefs: _ctx.prefs, env: _ctx.env };
|
|
591
599
|
_listeners.forEach((fn) => fn());
|
|
592
600
|
}
|
|
601
|
+
var _cachedT = null;
|
|
593
602
|
function _buildT() {
|
|
603
|
+
if (_cachedT) return _cachedT;
|
|
594
604
|
const messages2 = typeof window !== "undefined" ? window.__LOCALE_DATA__ : globalThis.__LOCALE_DATA__;
|
|
595
|
-
if (!messages2)
|
|
596
|
-
|
|
605
|
+
if (!messages2) {
|
|
606
|
+
_cachedT = fallbackT;
|
|
607
|
+
return fallbackT;
|
|
608
|
+
}
|
|
609
|
+
_cachedT = (key, params, fallback) => {
|
|
597
610
|
const msg = key.split(".").reduce((o, k) => o?.[k], messages2);
|
|
598
611
|
if (msg === void 0 || msg === null) return fallback ?? key;
|
|
599
612
|
if (!params) return String(msg);
|
|
@@ -601,16 +614,27 @@ function _buildT() {
|
|
|
601
614
|
for (const [k, v] of Object.entries(params)) result = result.replace(`{${k}}`, v);
|
|
602
615
|
return result;
|
|
603
616
|
};
|
|
617
|
+
return _cachedT;
|
|
618
|
+
}
|
|
619
|
+
function _readCtx() {
|
|
620
|
+
const alsStore = _alsGetStore?.();
|
|
621
|
+
const base = alsStore ?? _ctx;
|
|
622
|
+
const data = typeof window !== "undefined" ? window.__WEIFUWU_CTX : null;
|
|
623
|
+
const t = typeof base.t === "function" && base.t !== fallbackT ? base.t : _buildT();
|
|
624
|
+
return { ...base, ...data, t };
|
|
604
625
|
}
|
|
605
626
|
function useCtx() {
|
|
606
627
|
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
628
|
+
return _readCtx();
|
|
629
|
+
}
|
|
630
|
+
function getCtx() {
|
|
631
|
+
return _readCtx();
|
|
610
632
|
}
|
|
611
|
-
var TsxContext = createContext(
|
|
633
|
+
var TsxContext = createContext(DEFAULT_CTX);
|
|
612
634
|
|
|
613
635
|
// tsx-instance.ts
|
|
636
|
+
var als = new AsyncLocalStorage();
|
|
637
|
+
__registerAls(() => als.getStore());
|
|
614
638
|
var liveReloadClients = /* @__PURE__ */ new Set();
|
|
615
639
|
function broadcastReload() {
|
|
616
640
|
for (const ws of liveReloadClients) {
|
|
@@ -625,13 +649,13 @@ var isDev = process.env.NODE_ENV !== "production";
|
|
|
625
649
|
var _tailwindPlugin = null;
|
|
626
650
|
var _postcss = null;
|
|
627
651
|
var _cjsRequire = createRequire(import.meta.url);
|
|
628
|
-
var _vmCtx = vm.createContext(Object.create(globalThis));
|
|
629
652
|
function loadSSRModule(code) {
|
|
653
|
+
const ctx = vm.createContext(Object.create(globalThis));
|
|
630
654
|
const mod = { exports: {} };
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
new vm.Script(code).runInContext(
|
|
655
|
+
ctx.require = (name) => _cjsRequire(name);
|
|
656
|
+
ctx.module = mod;
|
|
657
|
+
ctx.exports = mod.exports;
|
|
658
|
+
new vm.Script(code).runInContext(ctx);
|
|
635
659
|
return mod.exports;
|
|
636
660
|
}
|
|
637
661
|
function id(s) {
|
|
@@ -794,6 +818,10 @@ var TsxInstance = class {
|
|
|
794
818
|
clientBundleCache = /* @__PURE__ */ new Map();
|
|
795
819
|
clientBuildParams = /* @__PURE__ */ new Map();
|
|
796
820
|
clientRouteLog = /* @__PURE__ */ new Set();
|
|
821
|
+
// file watchers (dev mode, stored for cleanup)
|
|
822
|
+
watcher = null;
|
|
823
|
+
twWatcher = null;
|
|
824
|
+
debounceTimer = null;
|
|
797
825
|
constructor(options) {
|
|
798
826
|
this.uiDir = resolve2(options.dir);
|
|
799
827
|
this.pagesDir = existsSync2(join(this.uiDir, "pages")) ? join(this.uiDir, "pages") : this.uiDir;
|
|
@@ -802,7 +830,7 @@ var TsxInstance = class {
|
|
|
802
830
|
}
|
|
803
831
|
async build() {
|
|
804
832
|
const pages = scanPages(this.pagesDir);
|
|
805
|
-
if (pages.length === 0) return this.router;
|
|
833
|
+
if (pages.length === 0) return attachStop(this.router, this);
|
|
806
834
|
const allFiles = /* @__PURE__ */ new Set();
|
|
807
835
|
for (const p of pages) {
|
|
808
836
|
if (p.entryPath) allFiles.add(p.entryPath);
|
|
@@ -892,28 +920,35 @@ var TsxInstance = class {
|
|
|
892
920
|
const nfMod = this.pageModules.get(nfPath);
|
|
893
921
|
if (!nfMod) return new Response("Not Found", { status: 404 });
|
|
894
922
|
const NfComponent = nfMod.default;
|
|
895
|
-
|
|
923
|
+
const ctxValue = {
|
|
896
924
|
params: ctx.params,
|
|
897
925
|
query: ctx.query,
|
|
898
|
-
user: ctx.user,
|
|
899
|
-
parsed: ctx.parsed,
|
|
900
|
-
prefs: ctx.prefs,
|
|
901
|
-
t: ctx.t,
|
|
902
|
-
env: ctx.env
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
926
|
+
user: ctx.user ?? {},
|
|
927
|
+
parsed: ctx.parsed ?? {},
|
|
928
|
+
prefs: ctx.prefs ?? {},
|
|
929
|
+
t: ctx.t ?? ((key) => key),
|
|
930
|
+
env: ctx.env ?? {}
|
|
931
|
+
};
|
|
932
|
+
return als.run(ctxValue, async () => {
|
|
933
|
+
setCtx(ctxValue);
|
|
934
|
+
let element = createElement(
|
|
935
|
+
TsxContext.Provider,
|
|
936
|
+
{ value: ctxValue },
|
|
937
|
+
createElement(NfComponent, { params: ctx.params, query: ctx.query })
|
|
938
|
+
);
|
|
939
|
+
for (let i = rootLayouts.length - 1; i >= 0; i--) {
|
|
940
|
+
const LMod = this.layoutModules.get(rootLayouts[i]);
|
|
941
|
+
if (!LMod) continue;
|
|
942
|
+
element = createElement(LMod.default, { children: element });
|
|
943
|
+
}
|
|
944
|
+
const stream = await renderToReadableStream(element);
|
|
945
|
+
return streamResponse(stream, {
|
|
946
|
+
ctx,
|
|
947
|
+
base,
|
|
948
|
+
compiledTailwindCss: this.compiledTailwindCss,
|
|
949
|
+
isDev,
|
|
950
|
+
status: 404
|
|
951
|
+
});
|
|
917
952
|
});
|
|
918
953
|
};
|
|
919
954
|
this.router.all("/*", handler);
|
|
@@ -935,7 +970,17 @@ var TsxInstance = class {
|
|
|
935
970
|
});
|
|
936
971
|
this.startFileWatcher();
|
|
937
972
|
}
|
|
938
|
-
return this.router;
|
|
973
|
+
return attachStop(this.router, this);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Clean up file watchers and pending timers. Call when shutting down
|
|
977
|
+
* to prevent resource leaks.
|
|
978
|
+
*/
|
|
979
|
+
stop() {
|
|
980
|
+
this.watcher?.close();
|
|
981
|
+
this.twWatcher?.close();
|
|
982
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
983
|
+
this.debounceTimer = null;
|
|
939
984
|
}
|
|
940
985
|
// ── Tailwind CSS ──────────────────────────────────────────────────────────
|
|
941
986
|
async compileTailwind() {
|
|
@@ -972,7 +1017,8 @@ ${src}`;
|
|
|
972
1017
|
}));
|
|
973
1018
|
if (isDev) {
|
|
974
1019
|
const inputFile = resolve2(this.uiDir, "app.css");
|
|
975
|
-
chokidar.watch(inputFile, { persistent: false })
|
|
1020
|
+
this.twWatcher = chokidar.watch(inputFile, { persistent: false });
|
|
1021
|
+
this.twWatcher.on("change", async () => {
|
|
976
1022
|
this.compiledTailwindCss = "";
|
|
977
1023
|
await this.compileTailwind();
|
|
978
1024
|
broadcastReload();
|
|
@@ -1068,77 +1114,84 @@ ${src}`;
|
|
|
1068
1114
|
const pageMod = this.pageModules.get(entryPath);
|
|
1069
1115
|
if (!pageMod) return new Response("", { status: 500 });
|
|
1070
1116
|
const Component = pageMod.default;
|
|
1071
|
-
const
|
|
1072
|
-
const loadFn = loadMod?.default;
|
|
1073
|
-
const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {};
|
|
1074
|
-
const allProps = { ...loadProps, params: ctx.params, query: ctx.query };
|
|
1075
|
-
setCtx({
|
|
1117
|
+
const ctxValue = {
|
|
1076
1118
|
params: ctx.params,
|
|
1077
1119
|
query: ctx.query,
|
|
1078
|
-
user: ctx.user,
|
|
1079
|
-
parsed: ctx.parsed,
|
|
1080
|
-
prefs: ctx.prefs,
|
|
1081
|
-
t: ctx.t,
|
|
1082
|
-
env: ctx.env
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
element = createElement(
|
|
1091
|
-
|
|
1092
|
-
{
|
|
1120
|
+
user: ctx.user ?? {},
|
|
1121
|
+
parsed: ctx.parsed ?? {},
|
|
1122
|
+
prefs: ctx.prefs ?? {},
|
|
1123
|
+
t: ctx.t ?? ((key) => key),
|
|
1124
|
+
env: ctx.env ?? {}
|
|
1125
|
+
};
|
|
1126
|
+
return als.run(ctxValue, async () => {
|
|
1127
|
+
setCtx(ctxValue);
|
|
1128
|
+
const loadMod = loadPath ? this.loadModules.get(loadPath) : void 0;
|
|
1129
|
+
const loadFn = loadMod?.default;
|
|
1130
|
+
const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {};
|
|
1131
|
+
const allProps = { ...loadProps, params: ctx.params, query: ctx.query };
|
|
1132
|
+
let element = createElement(
|
|
1133
|
+
TsxContext.Provider,
|
|
1134
|
+
{ value: ctxValue },
|
|
1093
1135
|
createElement(
|
|
1094
|
-
"
|
|
1095
|
-
|
|
1096
|
-
createElement(
|
|
1097
|
-
|
|
1098
|
-
createElement("title", null, "weifuwu")
|
|
1099
|
-
),
|
|
1100
|
-
createElement("body", null, element)
|
|
1136
|
+
"div",
|
|
1137
|
+
{ id: "__weifuwu_root" },
|
|
1138
|
+
createElement(Component, allProps)
|
|
1139
|
+
)
|
|
1101
1140
|
);
|
|
1102
|
-
|
|
1103
|
-
for (let i = layoutPaths.length - 1; i >= 0; i--) {
|
|
1104
|
-
const lp = layoutPaths[i];
|
|
1105
|
-
const LMod = this.layoutModules.get(lp);
|
|
1106
|
-
if (!LMod) continue;
|
|
1107
|
-
const Layout = LMod.default;
|
|
1108
|
-
const isRoot = i === 0;
|
|
1141
|
+
if (layoutPaths.length === 0) {
|
|
1109
1142
|
element = createElement(
|
|
1110
|
-
|
|
1111
|
-
|
|
1143
|
+
"html",
|
|
1144
|
+
{ lang: "en" },
|
|
1145
|
+
createElement(
|
|
1146
|
+
"head",
|
|
1147
|
+
null,
|
|
1148
|
+
createElement("meta", { charSet: "utf-8" }),
|
|
1149
|
+
createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
1150
|
+
createElement("title", null, "weifuwu")
|
|
1151
|
+
),
|
|
1152
|
+
createElement("body", null, element)
|
|
1112
1153
|
);
|
|
1154
|
+
} else {
|
|
1155
|
+
for (let i = layoutPaths.length - 1; i >= 0; i--) {
|
|
1156
|
+
const lp = layoutPaths[i];
|
|
1157
|
+
const LMod = this.layoutModules.get(lp);
|
|
1158
|
+
if (!LMod) continue;
|
|
1159
|
+
const Layout = LMod.default;
|
|
1160
|
+
const isRoot = i === 0;
|
|
1161
|
+
element = createElement(
|
|
1162
|
+
Layout,
|
|
1163
|
+
isRoot ? { children: element, req } : { children: element }
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1113
1166
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1167
|
+
const bundle = await this.getOrBuildClientBundle(entryPath, layoutPaths, this.pagesDir);
|
|
1168
|
+
const stream = await renderToReadableStream(element);
|
|
1169
|
+
return streamResponse(stream, {
|
|
1170
|
+
ctx,
|
|
1171
|
+
base,
|
|
1172
|
+
compiledTailwindCss: this.compiledTailwindCss,
|
|
1173
|
+
isDev,
|
|
1174
|
+
bundle,
|
|
1175
|
+
allProps
|
|
1176
|
+
});
|
|
1124
1177
|
});
|
|
1125
1178
|
};
|
|
1126
1179
|
}
|
|
1127
1180
|
// ── dev file watcher ──────────────────────────────────────────────────────
|
|
1128
1181
|
startFileWatcher() {
|
|
1129
|
-
let timeout = null;
|
|
1130
1182
|
const pending = /* @__PURE__ */ new Set();
|
|
1131
|
-
chokidar.watch(this.uiDir, {
|
|
1183
|
+
this.watcher = chokidar.watch(this.uiDir, {
|
|
1132
1184
|
ignored: /(^|[/\\])\.(?!\.)|node_modules|[/\\]\.weifuwu[/\\]|[/\\]dist[/\\]/,
|
|
1133
1185
|
persistent: false,
|
|
1134
1186
|
ignoreInitial: true
|
|
1135
|
-
})
|
|
1187
|
+
});
|
|
1188
|
+
this.watcher.on("all", async (event, filePath) => {
|
|
1136
1189
|
if (event !== "change" && event !== "add") return;
|
|
1137
1190
|
if (!/\.tsx?$/.test(filePath)) return;
|
|
1138
1191
|
pending.add(filePath);
|
|
1139
|
-
if (
|
|
1140
|
-
|
|
1141
|
-
|
|
1192
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1193
|
+
this.debounceTimer = setTimeout(async () => {
|
|
1194
|
+
this.debounceTimer = null;
|
|
1142
1195
|
const files = [...pending];
|
|
1143
1196
|
pending.clear();
|
|
1144
1197
|
const exists = files.filter((f) => existsSync2(f));
|
|
@@ -1286,6 +1339,11 @@ ${src}`;
|
|
|
1286
1339
|
}
|
|
1287
1340
|
}
|
|
1288
1341
|
};
|
|
1342
|
+
function attachStop(router, instance) {
|
|
1343
|
+
;
|
|
1344
|
+
router.stop = () => instance.stop();
|
|
1345
|
+
return router;
|
|
1346
|
+
}
|
|
1289
1347
|
function streamResponse(reactStream, opts) {
|
|
1290
1348
|
const decoder = new TextDecoder();
|
|
1291
1349
|
const encoder2 = new TextEncoder();
|
|
@@ -1295,47 +1353,54 @@ function streamResponse(reactStream, opts) {
|
|
|
1295
1353
|
let extractedHead = "";
|
|
1296
1354
|
const output = new ReadableStream({
|
|
1297
1355
|
async start(controller) {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1356
|
+
try {
|
|
1357
|
+
const reader = reactStream.getReader();
|
|
1358
|
+
async function push(chunk) {
|
|
1359
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
1360
|
+
if (!extractedHead) {
|
|
1361
|
+
const m = buffer.match(/<template id="__wfw_head">([\s\S]*?)<\/template>/);
|
|
1362
|
+
if (m) {
|
|
1363
|
+
extractedHead = m[1];
|
|
1364
|
+
buffer = buffer.replace(m[0], "");
|
|
1365
|
+
}
|
|
1306
1366
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1367
|
+
if (!headFlushed) {
|
|
1368
|
+
const idx = buffer.indexOf("</head>");
|
|
1369
|
+
if (idx !== -1) {
|
|
1370
|
+
const before = buffer.slice(0, idx);
|
|
1371
|
+
let injection = "";
|
|
1372
|
+
if (extractedHead) injection += "\n" + extractedHead;
|
|
1373
|
+
injection += headPayload;
|
|
1374
|
+
controller.enqueue(encoder2.encode(before + injection));
|
|
1375
|
+
buffer = buffer.slice(idx);
|
|
1376
|
+
headFlushed = true;
|
|
1377
|
+
}
|
|
1378
|
+
return;
|
|
1318
1379
|
}
|
|
1319
|
-
|
|
1380
|
+
controller.enqueue(encoder2.encode(buffer));
|
|
1381
|
+
buffer = "";
|
|
1320
1382
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
controller.enqueue(encoder2.encode(
|
|
1334
|
-
`
|
|
1383
|
+
while (true) {
|
|
1384
|
+
const { done, value } = await reader.read();
|
|
1385
|
+
if (done) break;
|
|
1386
|
+
await push(value);
|
|
1387
|
+
}
|
|
1388
|
+
buffer = buffer.replace(/<template id="__wfw_head">[\s\S]*?<\/template>/g, "");
|
|
1389
|
+
if (buffer) controller.enqueue(encoder2.encode(buffer));
|
|
1390
|
+
const body = buildBodyScripts(opts);
|
|
1391
|
+
if (body) controller.enqueue(encoder2.encode("\n" + body));
|
|
1392
|
+
if (opts.isDev) {
|
|
1393
|
+
controller.enqueue(encoder2.encode(
|
|
1394
|
+
`
|
|
1335
1395
|
<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>`
|
|
1336
|
-
|
|
1396
|
+
));
|
|
1397
|
+
}
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
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>`;
|
|
1400
|
+
controller.enqueue(encoder2.encode(fallback));
|
|
1401
|
+
} finally {
|
|
1402
|
+
controller.close();
|
|
1337
1403
|
}
|
|
1338
|
-
controller.close();
|
|
1339
1404
|
}
|
|
1340
1405
|
});
|
|
1341
1406
|
return new Response(output, {
|
|
@@ -1343,6 +1408,17 @@ function streamResponse(reactStream, opts) {
|
|
|
1343
1408
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
1344
1409
|
});
|
|
1345
1410
|
}
|
|
1411
|
+
var _publicEnv = null;
|
|
1412
|
+
function getPublicEnv() {
|
|
1413
|
+
if (_publicEnv) return _publicEnv;
|
|
1414
|
+
_publicEnv = {};
|
|
1415
|
+
for (const key of Object.keys(process.env)) {
|
|
1416
|
+
if (key.startsWith("WEIFUWU_PUBLIC_")) {
|
|
1417
|
+
_publicEnv[key] = process.env[key];
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return _publicEnv;
|
|
1421
|
+
}
|
|
1346
1422
|
function buildHeadPayload(opts) {
|
|
1347
1423
|
const { ctx, base, compiledTailwindCss } = opts;
|
|
1348
1424
|
let result = "";
|
|
@@ -1354,7 +1430,7 @@ function buildHeadPayload(opts) {
|
|
|
1354
1430
|
result += `<link rel="stylesheet" href="${base}/__wfw/style.css" />
|
|
1355
1431
|
`;
|
|
1356
1432
|
}
|
|
1357
|
-
const localeData =
|
|
1433
|
+
const localeData = ctx.parsed?.__localeData;
|
|
1358
1434
|
if (localeData && Object.keys(localeData).length > 0) {
|
|
1359
1435
|
result += `<script>window.__LOCALE_DATA__=${JSON.stringify(localeData)}</script>
|
|
1360
1436
|
`;
|
|
@@ -1366,12 +1442,7 @@ function buildHeadPayload(opts) {
|
|
|
1366
1442
|
parsed: ctx.parsed,
|
|
1367
1443
|
prefs: ctx.prefs
|
|
1368
1444
|
};
|
|
1369
|
-
const publicEnv =
|
|
1370
|
-
for (const key of Object.keys(process.env)) {
|
|
1371
|
-
if (key.startsWith("WEIFUWU_PUBLIC_")) {
|
|
1372
|
-
publicEnv[key] = process.env[key];
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1445
|
+
const publicEnv = getPublicEnv();
|
|
1375
1446
|
if (Object.keys(publicEnv).length > 0) {
|
|
1376
1447
|
ctxData.env = publicEnv;
|
|
1377
1448
|
}
|
|
@@ -1400,14 +1471,20 @@ function logger(options) {
|
|
|
1400
1471
|
return async (req, ctx, next) => {
|
|
1401
1472
|
const start = Date.now();
|
|
1402
1473
|
const url = new URL(req.url);
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1474
|
+
try {
|
|
1475
|
+
const res = await next(req, ctx);
|
|
1476
|
+
const ms = Date.now() - start;
|
|
1477
|
+
if (options?.format === "combined") {
|
|
1478
|
+
console.log(`${req.method} ${url.pathname}${url.search} ${res.status} ${ms}ms`);
|
|
1479
|
+
} else {
|
|
1480
|
+
console.log(`${req.method} ${url.pathname} ${res.status} ${ms}ms`);
|
|
1481
|
+
}
|
|
1482
|
+
return res;
|
|
1483
|
+
} catch (err) {
|
|
1484
|
+
const ms = Date.now() - start;
|
|
1485
|
+
console.log(`${req.method} ${url.pathname} 500 ${ms}ms`);
|
|
1486
|
+
throw err;
|
|
1409
1487
|
}
|
|
1410
|
-
return res;
|
|
1411
1488
|
};
|
|
1412
1489
|
}
|
|
1413
1490
|
function cors(options) {
|
|
@@ -1547,6 +1624,7 @@ function auth(options) {
|
|
|
1547
1624
|
import { createHash as createHash2 } from "node:crypto";
|
|
1548
1625
|
import { open, realpath } from "node:fs/promises";
|
|
1549
1626
|
import { extname, resolve as resolve3, normalize, sep as sep2 } from "node:path";
|
|
1627
|
+
import { Readable } from "node:stream";
|
|
1550
1628
|
function serveStatic(root, options) {
|
|
1551
1629
|
const rootDir = resolve3(root);
|
|
1552
1630
|
const opts = options ?? {};
|
|
@@ -1602,8 +1680,13 @@ function serveStatic(root, options) {
|
|
|
1602
1680
|
"Last-Modified": stat.mtime.toUTCString(),
|
|
1603
1681
|
"Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
|
|
1604
1682
|
};
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1683
|
+
const readStream = fileHandle.createReadStream();
|
|
1684
|
+
const cleanup = () => fileHandle.close().catch(() => {
|
|
1685
|
+
});
|
|
1686
|
+
readStream.on("close", cleanup);
|
|
1687
|
+
readStream.on("error", cleanup);
|
|
1688
|
+
const webStream = Readable.toWeb(readStream);
|
|
1689
|
+
return new Response(webStream, { headers });
|
|
1607
1690
|
} catch (err) {
|
|
1608
1691
|
if (fileHandle) await fileHandle.close().catch(() => {
|
|
1609
1692
|
});
|
|
@@ -1900,20 +1983,26 @@ function rateLimit(options) {
|
|
|
1900
1983
|
return req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "global";
|
|
1901
1984
|
});
|
|
1902
1985
|
const message = options?.message ?? "Too Many Requests";
|
|
1986
|
+
const MAX_ENTRIES = 1e4;
|
|
1903
1987
|
const hits = /* @__PURE__ */ new Map();
|
|
1904
1988
|
const interval = setInterval(() => {
|
|
1905
1989
|
const now = Date.now();
|
|
1906
1990
|
for (const [key, entry] of hits) {
|
|
1907
1991
|
if (entry.reset < now) hits.delete(key);
|
|
1908
1992
|
}
|
|
1909
|
-
|
|
1993
|
+
if (hits.size > MAX_ENTRIES) {
|
|
1994
|
+
const toDelete = [...hits.entries()].sort((a, b) => a[1].reset - b[1].reset).slice(0, hits.size - MAX_ENTRIES);
|
|
1995
|
+
for (const [k] of toDelete) hits.delete(k);
|
|
1996
|
+
}
|
|
1997
|
+
}, Math.min(window2, 3e4));
|
|
1910
1998
|
if (interval.unref) interval.unref();
|
|
1911
1999
|
const mw = async (req, ctx, next) => {
|
|
1912
2000
|
const key = getKey(req);
|
|
1913
2001
|
const now = Date.now();
|
|
1914
|
-
|
|
2002
|
+
let entry = hits.get(key);
|
|
1915
2003
|
if (!entry || entry.reset < now) {
|
|
1916
2004
|
hits.set(key, { count: 1, reset: now + window2 });
|
|
2005
|
+
entry = { count: 1, reset: now + window2 };
|
|
1917
2006
|
const res2 = await next(req, ctx);
|
|
1918
2007
|
const headers2 = new Headers(res2.headers);
|
|
1919
2008
|
headers2.set("X-RateLimit-Limit", String(max));
|
|
@@ -1949,24 +2038,19 @@ function rateLimit(options) {
|
|
|
1949
2038
|
}
|
|
1950
2039
|
|
|
1951
2040
|
// compress.ts
|
|
1952
|
-
import { gzipSync, brotliCompressSync, deflateSync
|
|
2041
|
+
import { constants, gzipSync, brotliCompressSync, deflateSync } from "node:zlib";
|
|
1953
2042
|
function compress(options) {
|
|
1954
2043
|
const level = options?.level ?? 6;
|
|
1955
2044
|
const threshold = options?.threshold ?? 1024;
|
|
1956
2045
|
return async (req, ctx, next) => {
|
|
1957
2046
|
const accept = req.headers.get("accept-encoding") ?? "";
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
const useDeflate = !useBrotli && !useGzip && accept.includes("deflate");
|
|
1961
|
-
if (!useBrotli && !useGzip && !useDeflate) {
|
|
1962
|
-
return next(req, ctx);
|
|
1963
|
-
}
|
|
2047
|
+
const encoding = accept.includes("br") ? "br" : accept.includes("gzip") ? "gzip" : accept.includes("deflate") ? "deflate" : "";
|
|
2048
|
+
if (!encoding) return next(req, ctx);
|
|
1964
2049
|
const res = await next(req, ctx);
|
|
1965
2050
|
if (res.status === 304 || res.status === 204 || res.status === 206 || res.status < 200 || res.status >= 300) {
|
|
1966
2051
|
return res;
|
|
1967
2052
|
}
|
|
1968
|
-
|
|
1969
|
-
if (ce) return res;
|
|
2053
|
+
if (res.headers.get("content-encoding")) return res;
|
|
1970
2054
|
const ct = res.headers.get("content-type") ?? "";
|
|
1971
2055
|
if (!ct || ct.startsWith("audio/") || ct.startsWith("video/") || ct.startsWith("image/") || ct === "application/zip") {
|
|
1972
2056
|
return res;
|
|
@@ -1974,21 +2058,19 @@ function compress(options) {
|
|
|
1974
2058
|
const body = await res.bytes();
|
|
1975
2059
|
if (body.byteLength < threshold) return res;
|
|
1976
2060
|
let compressed;
|
|
1977
|
-
let
|
|
1978
|
-
if (
|
|
1979
|
-
compressed = brotliCompressSync(body, {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
encoding = "br";
|
|
1983
|
-
} else if (useGzip) {
|
|
2061
|
+
let enc;
|
|
2062
|
+
if (encoding === "br") {
|
|
2063
|
+
compressed = brotliCompressSync(body, { params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) } });
|
|
2064
|
+
enc = "br";
|
|
2065
|
+
} else if (encoding === "gzip") {
|
|
1984
2066
|
compressed = gzipSync(body, { level: Math.min(level, 9) });
|
|
1985
|
-
|
|
2067
|
+
enc = "gzip";
|
|
1986
2068
|
} else {
|
|
1987
2069
|
compressed = deflateSync(body, { level: Math.min(level, 9) });
|
|
1988
|
-
|
|
2070
|
+
enc = "deflate";
|
|
1989
2071
|
}
|
|
1990
2072
|
const headers = new Headers(res.headers);
|
|
1991
|
-
headers.set("Content-Encoding",
|
|
2073
|
+
headers.set("Content-Encoding", enc);
|
|
1992
2074
|
headers.set("Content-Length", String(compressed.byteLength));
|
|
1993
2075
|
headers.delete("Content-Range");
|
|
1994
2076
|
const existingVary = headers.get("Vary");
|
|
@@ -2155,7 +2237,7 @@ async function executeQuery(schema, params, options, req, ctx) {
|
|
|
2155
2237
|
variableValues: params.variables,
|
|
2156
2238
|
operationName: params.operationName
|
|
2157
2239
|
});
|
|
2158
|
-
return Response.json(result, { status: result.errors ?
|
|
2240
|
+
return Response.json(result, { status: result.errors ? 400 : 200 });
|
|
2159
2241
|
}
|
|
2160
2242
|
function graphiqlHTML(endpoint) {
|
|
2161
2243
|
const safeEndpoint = endpoint.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/</g, "\\x3C");
|
|
@@ -3596,8 +3678,7 @@ function redis(opts) {
|
|
|
3596
3678
|
const options = typeof opts === "string" ? { url: opts } : opts ?? {};
|
|
3597
3679
|
const url = options.url ?? process.env.REDIS_URL ?? "redis://localhost:6379";
|
|
3598
3680
|
const client = new IORedis(url, options);
|
|
3599
|
-
client.on("error", () =>
|
|
3600
|
-
});
|
|
3681
|
+
client.on("error", (err) => console.error("[redis]", err.message));
|
|
3601
3682
|
const mw = ((req, ctx, next) => {
|
|
3602
3683
|
ctx.redis = client;
|
|
3603
3684
|
return next(req, ctx);
|
|
@@ -3610,7 +3691,8 @@ function redis(opts) {
|
|
|
3610
3691
|
// hub.ts
|
|
3611
3692
|
function createHub(opts) {
|
|
3612
3693
|
const prefix = opts?.prefix ?? "hub:";
|
|
3613
|
-
const
|
|
3694
|
+
const channels = /* @__PURE__ */ new Map();
|
|
3695
|
+
const wsKeys = /* @__PURE__ */ new Map();
|
|
3614
3696
|
let redisPub;
|
|
3615
3697
|
let redisSub = null;
|
|
3616
3698
|
if (opts?.redis) {
|
|
@@ -3619,7 +3701,7 @@ function createHub(opts) {
|
|
|
3619
3701
|
redisSub.on("message", (rawChannel, rawData) => {
|
|
3620
3702
|
if (!rawChannel.startsWith(prefix)) return;
|
|
3621
3703
|
const key = rawChannel.slice(prefix.length);
|
|
3622
|
-
const members =
|
|
3704
|
+
const members = channels.get(key);
|
|
3623
3705
|
if (!members) return;
|
|
3624
3706
|
for (const ws of members) {
|
|
3625
3707
|
try {
|
|
@@ -3630,20 +3712,30 @@ function createHub(opts) {
|
|
|
3630
3712
|
});
|
|
3631
3713
|
}
|
|
3632
3714
|
function join5(key, ws) {
|
|
3633
|
-
if (!
|
|
3634
|
-
|
|
3715
|
+
if (!channels.has(key)) {
|
|
3716
|
+
channels.set(key, /* @__PURE__ */ new Set());
|
|
3635
3717
|
redisSub?.subscribe(`${prefix}${key}`);
|
|
3636
3718
|
}
|
|
3637
|
-
|
|
3719
|
+
channels.get(key).add(ws);
|
|
3720
|
+
let keys = wsKeys.get(ws);
|
|
3721
|
+
if (!keys) {
|
|
3722
|
+
keys = /* @__PURE__ */ new Set();
|
|
3723
|
+
wsKeys.set(ws, keys);
|
|
3724
|
+
}
|
|
3725
|
+
keys.add(key);
|
|
3638
3726
|
}
|
|
3639
3727
|
function leave(ws) {
|
|
3640
|
-
|
|
3641
|
-
|
|
3728
|
+
const keys = wsKeys.get(ws);
|
|
3729
|
+
if (keys) {
|
|
3730
|
+
for (const key of keys) {
|
|
3731
|
+
channels.get(key)?.delete(ws);
|
|
3732
|
+
}
|
|
3733
|
+
wsKeys.delete(ws);
|
|
3642
3734
|
}
|
|
3643
3735
|
}
|
|
3644
3736
|
function broadcast(key, data) {
|
|
3645
3737
|
const msg = JSON.stringify(data);
|
|
3646
|
-
const members =
|
|
3738
|
+
const members = channels.get(key);
|
|
3647
3739
|
if (members) {
|
|
3648
3740
|
for (const ws of members) {
|
|
3649
3741
|
try {
|
|
@@ -3655,7 +3747,7 @@ function createHub(opts) {
|
|
|
3655
3747
|
redisPub?.publish(`${prefix}${key}`, msg);
|
|
3656
3748
|
}
|
|
3657
3749
|
async function close() {
|
|
3658
|
-
|
|
3750
|
+
channels.clear();
|
|
3659
3751
|
if (redisSub) {
|
|
3660
3752
|
await redisSub.quit();
|
|
3661
3753
|
}
|
|
@@ -3725,17 +3817,40 @@ function queue(opts) {
|
|
|
3725
3817
|
const handlers = /* @__PURE__ */ new Map();
|
|
3726
3818
|
let running = false;
|
|
3727
3819
|
let pollTimer = null;
|
|
3820
|
+
let epoch = 0;
|
|
3728
3821
|
const jobKey = `${prefix}:jobs`;
|
|
3729
3822
|
const mw = ((req, ctx, next) => {
|
|
3730
3823
|
ctx.queue = q;
|
|
3731
3824
|
return next(req, ctx);
|
|
3732
3825
|
});
|
|
3733
3826
|
const q = mw;
|
|
3827
|
+
const MAX_CONCURRENT = 16;
|
|
3828
|
+
let inflight = 0;
|
|
3829
|
+
async function processJob(job, jobHandler) {
|
|
3830
|
+
inflight++;
|
|
3831
|
+
try {
|
|
3832
|
+
await jobHandler(job);
|
|
3833
|
+
} catch (e) {
|
|
3834
|
+
console.error("[queue] handler error:", e.message);
|
|
3835
|
+
} finally {
|
|
3836
|
+
inflight--;
|
|
3837
|
+
}
|
|
3838
|
+
if (job.schedule) {
|
|
3839
|
+
try {
|
|
3840
|
+
const nextRun = cronNext(job.schedule);
|
|
3841
|
+
const nextJob = { ...job, id: crypto4.randomUUID(), runAt: nextRun, createdAt: Date.now() };
|
|
3842
|
+
await redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob));
|
|
3843
|
+
} catch (e) {
|
|
3844
|
+
console.error("[queue] cron re-queue failed:", e.message);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3734
3848
|
async function poll() {
|
|
3849
|
+
const currentEpoch = epoch;
|
|
3735
3850
|
if (!running) return;
|
|
3736
3851
|
try {
|
|
3737
3852
|
const now = Date.now();
|
|
3738
|
-
while (
|
|
3853
|
+
while (running && inflight < MAX_CONCURRENT) {
|
|
3739
3854
|
const result = await redis2.zpopmin(jobKey);
|
|
3740
3855
|
if (result.length < 2) break;
|
|
3741
3856
|
const raw = result[0];
|
|
@@ -3750,26 +3865,15 @@ function queue(opts) {
|
|
|
3750
3865
|
} catch {
|
|
3751
3866
|
continue;
|
|
3752
3867
|
}
|
|
3753
|
-
const
|
|
3754
|
-
if (
|
|
3755
|
-
|
|
3756
|
-
if (job.schedule) {
|
|
3757
|
-
try {
|
|
3758
|
-
const nextRun = cronNext(job.schedule);
|
|
3759
|
-
const nextJob = { ...job, id: crypto4.randomUUID(), runAt: nextRun, createdAt: Date.now() };
|
|
3760
|
-
redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob)).catch(() => {
|
|
3761
|
-
});
|
|
3762
|
-
} catch {
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
}).catch((e) => {
|
|
3766
|
-
console.error("[queue] handler error:", e);
|
|
3767
|
-
});
|
|
3868
|
+
const jobHandler = handlers.get(job.type);
|
|
3869
|
+
if (jobHandler) {
|
|
3870
|
+
processJob(job, jobHandler);
|
|
3768
3871
|
}
|
|
3769
3872
|
}
|
|
3770
|
-
} catch {
|
|
3873
|
+
} catch (e) {
|
|
3874
|
+
console.error("[queue] poll error:", e.message);
|
|
3771
3875
|
}
|
|
3772
|
-
if (running) {
|
|
3876
|
+
if (running && currentEpoch === epoch) {
|
|
3773
3877
|
pollTimer = setTimeout(poll, pollInterval);
|
|
3774
3878
|
}
|
|
3775
3879
|
}
|
|
@@ -3797,6 +3901,7 @@ function queue(opts) {
|
|
|
3797
3901
|
};
|
|
3798
3902
|
mw.stop = function stop() {
|
|
3799
3903
|
running = false;
|
|
3904
|
+
epoch++;
|
|
3800
3905
|
if (pollTimer) {
|
|
3801
3906
|
clearTimeout(pollTimer);
|
|
3802
3907
|
pollTimer = null;
|
|
@@ -3804,6 +3909,7 @@ function queue(opts) {
|
|
|
3804
3909
|
};
|
|
3805
3910
|
mw.close = async function close() {
|
|
3806
3911
|
mw.stop();
|
|
3912
|
+
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
3807
3913
|
redis2.disconnect();
|
|
3808
3914
|
};
|
|
3809
3915
|
return q;
|
|
@@ -4375,9 +4481,8 @@ function graphqlType(field, required) {
|
|
|
4375
4481
|
function inputGraphqlType(field) {
|
|
4376
4482
|
return graphqlType(field, false);
|
|
4377
4483
|
}
|
|
4378
|
-
var typeCache = /* @__PURE__ */ new Map();
|
|
4379
4484
|
function buildObjectType(table, ctx) {
|
|
4380
|
-
const cached = typeCache.get(table.id);
|
|
4485
|
+
const cached = ctx.typeCache.get(table.id);
|
|
4381
4486
|
if (cached) return cached;
|
|
4382
4487
|
const typeName = pascalCase(table.slug);
|
|
4383
4488
|
const fieldsThunk = () => {
|
|
@@ -4453,7 +4558,7 @@ function buildObjectType(table, ctx) {
|
|
|
4453
4558
|
return fields;
|
|
4454
4559
|
};
|
|
4455
4560
|
const type = new GraphQLObjectType({ name: typeName, fields: fieldsThunk });
|
|
4456
|
-
typeCache.set(table.id, type);
|
|
4561
|
+
ctx.typeCache.set(table.id, type);
|
|
4457
4562
|
return type;
|
|
4458
4563
|
}
|
|
4459
4564
|
function buildInputType(table, prefix) {
|
|
@@ -4569,7 +4674,7 @@ function buildGraphQLHandler(sql2) {
|
|
|
4569
4674
|
WHERE tenant_id = ${ctx.tenant.id}
|
|
4570
4675
|
ORDER BY created_at ASC
|
|
4571
4676
|
`;
|
|
4572
|
-
const buildCtx = { sql: sql2, tenantId: ctx.tenant.id, tables };
|
|
4677
|
+
const buildCtx = { sql: sql2, tenantId: ctx.tenant.id, tables, typeCache: /* @__PURE__ */ new Map() };
|
|
4573
4678
|
const schema = new GraphQLSchema({
|
|
4574
4679
|
query: new GraphQLObjectType({
|
|
4575
4680
|
name: "Query",
|
|
@@ -4609,7 +4714,7 @@ function buildGraphQLHandler(sql2) {
|
|
|
4609
4714
|
WHERE tenant_id = ${_ctx2.tenant.id}
|
|
4610
4715
|
ORDER BY created_at ASC
|
|
4611
4716
|
`;
|
|
4612
|
-
const buildCtx = { sql: sql2, tenantId: _ctx2.tenant.id, tables };
|
|
4717
|
+
const buildCtx = { sql: sql2, tenantId: _ctx2.tenant.id, tables, typeCache: /* @__PURE__ */ new Map() };
|
|
4613
4718
|
const schema = new GraphQLSchema({
|
|
4614
4719
|
query: new GraphQLObjectType({
|
|
4615
4720
|
name: "Query",
|
|
@@ -4957,16 +5062,12 @@ function agent(options) {
|
|
|
4957
5062
|
const model = options.model;
|
|
4958
5063
|
const embeddingModel = options.embeddingModel;
|
|
4959
5064
|
const dimension = options.embeddingDimension ?? 1024;
|
|
4960
|
-
|
|
5065
|
+
const defaultModels = !model || !embeddingModel ? createModelsFromEnv() : null;
|
|
4961
5066
|
function getModel() {
|
|
4962
|
-
|
|
4963
|
-
if (!defaultModels) defaultModels = createModelsFromEnv();
|
|
4964
|
-
return defaultModels.model;
|
|
5067
|
+
return model ?? defaultModels.model;
|
|
4965
5068
|
}
|
|
4966
5069
|
function getEmbeddingModel() {
|
|
4967
|
-
|
|
4968
|
-
if (!defaultModels) defaultModels = createModelsFromEnv();
|
|
4969
|
-
return defaultModels.embeddingModel;
|
|
5070
|
+
return embeddingModel ?? defaultModels.embeddingModel;
|
|
4970
5071
|
}
|
|
4971
5072
|
const agentsTable = pg.table("_agents", {
|
|
4972
5073
|
id: serial("id").primaryKey(),
|
|
@@ -5006,127 +5107,151 @@ function agent(options) {
|
|
|
5006
5107
|
};
|
|
5007
5108
|
}
|
|
5008
5109
|
|
|
5110
|
+
// messager/agent.ts
|
|
5111
|
+
async function runAgentRouting(sql2, messages2, agents, hub, channelId, content) {
|
|
5112
|
+
if (!agents) return;
|
|
5113
|
+
const agentMembers = await sql2`
|
|
5114
|
+
SELECT member_id FROM "_channel_members"
|
|
5115
|
+
WHERE channel_id = ${channelId} AND member_type = 'agent'
|
|
5116
|
+
`;
|
|
5117
|
+
for (const am of agentMembers) {
|
|
5118
|
+
agents.run(am.member_id, { input: content, stream: false }).then((result) => {
|
|
5119
|
+
if ("output" in result && result.output) {
|
|
5120
|
+
messages2.insert({
|
|
5121
|
+
channel_id: channelId,
|
|
5122
|
+
sender_id: am.member_id,
|
|
5123
|
+
sender_type: "agent",
|
|
5124
|
+
content: result.output
|
|
5125
|
+
}).then((r) => {
|
|
5126
|
+
hub.broadcast(`messager:${channelId}`, { type: "message", data: r });
|
|
5127
|
+
}).catch((e) => {
|
|
5128
|
+
console.error("[messager] agent reply insert failed:", e);
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
5131
|
+
}).catch((e) => {
|
|
5132
|
+
console.error("[messager] agent run failed:", e);
|
|
5133
|
+
});
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5009
5137
|
// messager/ws.ts
|
|
5010
|
-
|
|
5011
|
-
var hub;
|
|
5012
|
-
function broadcastToChannel(channelId, data) {
|
|
5138
|
+
function broadcastToChannel(hub, channelId, data) {
|
|
5013
5139
|
hub?.broadcast(`messager:${channelId}`, data);
|
|
5014
5140
|
}
|
|
5015
5141
|
function createWSHandler(deps) {
|
|
5016
5142
|
const { sql: sql2, agents } = deps;
|
|
5017
|
-
hub = createHub({
|
|
5143
|
+
const hub = createHub({
|
|
5018
5144
|
redis: deps.redis,
|
|
5019
5145
|
prefix: "messager:"
|
|
5020
5146
|
});
|
|
5147
|
+
const userConnections = /* @__PURE__ */ new Map();
|
|
5148
|
+
function trackConnection(userId, ws) {
|
|
5149
|
+
let conns = userConnections.get(userId);
|
|
5150
|
+
if (!conns) {
|
|
5151
|
+
conns = /* @__PURE__ */ new Set();
|
|
5152
|
+
userConnections.set(userId, conns);
|
|
5153
|
+
}
|
|
5154
|
+
conns.add(ws);
|
|
5155
|
+
}
|
|
5156
|
+
function untrackConnection(ws) {
|
|
5157
|
+
for (const [userId, conns] of userConnections) {
|
|
5158
|
+
conns.delete(ws);
|
|
5159
|
+
if (conns.size === 0) userConnections.delete(userId);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5021
5162
|
return {
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
const message = row;
|
|
5049
|
-
hub.join(`messager:${channel_id}`, ws);
|
|
5050
|
-
if (!userConnections.has(userId)) userConnections.set(userId, /* @__PURE__ */ new Set());
|
|
5051
|
-
userConnections.get(userId).add(ws);
|
|
5052
|
-
broadcastToChannel(channel_id, { type: "message", data: message });
|
|
5053
|
-
if (agents) {
|
|
5054
|
-
const agentMembers = await sql2`
|
|
5055
|
-
SELECT member_id FROM "_channel_members"
|
|
5056
|
-
WHERE channel_id = ${channel_id} AND member_type = 'agent'
|
|
5163
|
+
handler: {
|
|
5164
|
+
open(ws, ctx) {
|
|
5165
|
+
const userId = ctx.user?.id;
|
|
5166
|
+
if (!userId) {
|
|
5167
|
+
ws.close(4001, "Unauthorized");
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5170
|
+
},
|
|
5171
|
+
async message(ws, ctx, data) {
|
|
5172
|
+
const userId = ctx.user?.id;
|
|
5173
|
+
if (!userId) return;
|
|
5174
|
+
let msg;
|
|
5175
|
+
try {
|
|
5176
|
+
msg = JSON.parse(data.toString());
|
|
5177
|
+
} catch {
|
|
5178
|
+
ws.send(JSON.stringify({ type: "error", message: "Invalid JSON" }));
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
5181
|
+
const { type, channel_id, content, is_typing, last_message_id } = msg;
|
|
5182
|
+
switch (type) {
|
|
5183
|
+
case "message": {
|
|
5184
|
+
if (!content || !channel_id) return;
|
|
5185
|
+
const [row] = await sql2`
|
|
5186
|
+
INSERT INTO "_messages" ("channel_id", "sender_id", "sender_type", "content")
|
|
5187
|
+
VALUES (${channel_id}, ${userId}, 'user', ${content})
|
|
5188
|
+
RETURNING *
|
|
5057
5189
|
`;
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
}
|
|
5070
|
-
}).catch((e) => {
|
|
5071
|
-
console.error("[messager] agent run failed:", e);
|
|
5072
|
-
});
|
|
5190
|
+
const message = row;
|
|
5191
|
+
hub.join(`messager:${channel_id}`, ws);
|
|
5192
|
+
trackConnection(userId, ws);
|
|
5193
|
+
broadcastToChannel(hub, channel_id, { type: "message", data: message });
|
|
5194
|
+
if (agents) {
|
|
5195
|
+
const insertMsg = (data2) => sql2`
|
|
5196
|
+
INSERT INTO "_messages" ("channel_id", "sender_id", "sender_type", "content")
|
|
5197
|
+
VALUES (${data2.channel_id}, ${data2.sender_id}, ${data2.sender_type}, ${data2.content})
|
|
5198
|
+
RETURNING *
|
|
5199
|
+
`.then(([r]) => r);
|
|
5200
|
+
runAgentRouting(sql2, { insert: insertMsg }, agents, hub, channel_id, content);
|
|
5073
5201
|
}
|
|
5202
|
+
break;
|
|
5074
5203
|
}
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5204
|
+
case "typing": {
|
|
5205
|
+
if (channel_id) {
|
|
5206
|
+
hub.join(`messager:${channel_id}`, ws);
|
|
5207
|
+
}
|
|
5208
|
+
broadcastToChannel(hub, channel_id, {
|
|
5209
|
+
type: "typing",
|
|
5210
|
+
channel_id,
|
|
5211
|
+
user_id: userId,
|
|
5212
|
+
is_typing: is_typing ?? false
|
|
5213
|
+
});
|
|
5214
|
+
break;
|
|
5215
|
+
}
|
|
5216
|
+
case "read": {
|
|
5217
|
+
if (!channel_id || !last_message_id) return;
|
|
5079
5218
|
hub.join(`messager:${channel_id}`, ws);
|
|
5219
|
+
await sql2`
|
|
5220
|
+
UPDATE "_channel_members"
|
|
5221
|
+
SET last_read_id = ${last_message_id}, last_read_at = NOW()
|
|
5222
|
+
WHERE channel_id = ${channel_id} AND member_id = ${userId} AND member_type = 'user'
|
|
5223
|
+
`;
|
|
5224
|
+
broadcastToChannel(hub, channel_id, {
|
|
5225
|
+
type: "read",
|
|
5226
|
+
channel_id,
|
|
5227
|
+
user_id: userId,
|
|
5228
|
+
last_message_id
|
|
5229
|
+
});
|
|
5230
|
+
break;
|
|
5080
5231
|
}
|
|
5081
|
-
broadcastToChannel(channel_id, {
|
|
5082
|
-
type: "typing",
|
|
5083
|
-
channel_id,
|
|
5084
|
-
user_id: userId,
|
|
5085
|
-
is_typing: is_typing ?? false
|
|
5086
|
-
});
|
|
5087
|
-
break;
|
|
5088
5232
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
broadcastToChannel(channel_id, {
|
|
5098
|
-
type: "read",
|
|
5099
|
-
channel_id,
|
|
5100
|
-
user_id: userId,
|
|
5101
|
-
last_message_id
|
|
5102
|
-
});
|
|
5103
|
-
break;
|
|
5104
|
-
}
|
|
5105
|
-
}
|
|
5106
|
-
},
|
|
5107
|
-
close(ws) {
|
|
5108
|
-
hub?.leave(ws);
|
|
5109
|
-
for (const [, conns] of userConnections) {
|
|
5110
|
-
conns.delete(ws);
|
|
5233
|
+
},
|
|
5234
|
+
close(ws) {
|
|
5235
|
+
hub.leave(ws);
|
|
5236
|
+
untrackConnection(ws);
|
|
5237
|
+
},
|
|
5238
|
+
error(ws) {
|
|
5239
|
+
hub.leave(ws);
|
|
5240
|
+
untrackConnection(ws);
|
|
5111
5241
|
}
|
|
5112
5242
|
},
|
|
5113
|
-
|
|
5114
|
-
hub?.leave(ws);
|
|
5115
|
-
for (const [, conns] of userConnections) {
|
|
5116
|
-
conns.delete(ws);
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5243
|
+
hub
|
|
5119
5244
|
};
|
|
5120
5245
|
}
|
|
5121
5246
|
|
|
5122
5247
|
// messager/rest.ts
|
|
5123
5248
|
function buildRouter3(deps) {
|
|
5124
|
-
const { sql: sql2, channels
|
|
5249
|
+
const { sql: sql2, channels, members, messages: messages2, agents, hub } = deps;
|
|
5125
5250
|
const r = new Router();
|
|
5126
5251
|
r.post("/channels", async (req) => {
|
|
5127
5252
|
const body = await req.json();
|
|
5128
5253
|
if (!body.name) return Response.json({ error: "name is required" }, { status: 400 });
|
|
5129
|
-
const channel = await
|
|
5254
|
+
const channel = await channels.insert({
|
|
5130
5255
|
name: body.name,
|
|
5131
5256
|
type: body.type || "channel",
|
|
5132
5257
|
created_by: body.created_by || 1
|
|
@@ -5169,14 +5294,14 @@ function buildRouter3(deps) {
|
|
|
5169
5294
|
});
|
|
5170
5295
|
r.get("/channels/:id", async (_req, ctx) => {
|
|
5171
5296
|
const id2 = parseInt(ctx.params.id, 10);
|
|
5172
|
-
const ch = await
|
|
5297
|
+
const ch = await channels.read(id2);
|
|
5173
5298
|
if (!ch) return Response.json({ error: "Channel not found" }, { status: 404 });
|
|
5174
5299
|
const { data: memberRows } = await members.readMany({ channel_id: id2 });
|
|
5175
5300
|
return Response.json({ channel: ch, members: memberRows });
|
|
5176
5301
|
});
|
|
5177
5302
|
r.delete("/channels/:id", async (_req, ctx) => {
|
|
5178
5303
|
const id2 = parseInt(ctx.params.id, 10);
|
|
5179
|
-
await
|
|
5304
|
+
await channels.delete(id2);
|
|
5180
5305
|
return Response.json({ ok: true });
|
|
5181
5306
|
});
|
|
5182
5307
|
r.post("/channels/:id/members", async (req, ctx) => {
|
|
@@ -5228,31 +5353,8 @@ function buildRouter3(deps) {
|
|
|
5228
5353
|
type: body.type || "text",
|
|
5229
5354
|
content: body.content
|
|
5230
5355
|
});
|
|
5231
|
-
broadcastToChannel(channelId, { type: "message", data: msg });
|
|
5232
|
-
|
|
5233
|
-
const agentMembers = await sql2`
|
|
5234
|
-
SELECT member_id FROM "_channel_members"
|
|
5235
|
-
WHERE channel_id = ${channelId} AND member_type = 'agent'
|
|
5236
|
-
`;
|
|
5237
|
-
for (const am of agentMembers) {
|
|
5238
|
-
agents.run(am.member_id, { input: body.content, stream: false }).then((result) => {
|
|
5239
|
-
if ("output" in result && result.output) {
|
|
5240
|
-
messages2.insert({
|
|
5241
|
-
channel_id: channelId,
|
|
5242
|
-
sender_id: am.member_id,
|
|
5243
|
-
sender_type: "agent",
|
|
5244
|
-
content: result.output
|
|
5245
|
-
}).then((r2) => {
|
|
5246
|
-
broadcastToChannel(channelId, { type: "message", data: r2 });
|
|
5247
|
-
}).catch((e) => {
|
|
5248
|
-
console.error("[messager] agent reply insert failed:", e);
|
|
5249
|
-
});
|
|
5250
|
-
}
|
|
5251
|
-
}).catch((e) => {
|
|
5252
|
-
console.error("[messager] agent run failed:", e);
|
|
5253
|
-
});
|
|
5254
|
-
}
|
|
5255
|
-
}
|
|
5356
|
+
broadcastToChannel(hub, channelId, { type: "message", data: msg });
|
|
5357
|
+
runAgentRouting(sql2, messages2, agents, hub, channelId, body.content);
|
|
5256
5358
|
return Response.json(msg, { status: 201 });
|
|
5257
5359
|
});
|
|
5258
5360
|
r.post("/channels/:id/read", async (req, ctx) => {
|
|
@@ -5279,7 +5381,9 @@ function messager(options) {
|
|
|
5279
5381
|
const agents = options.agents;
|
|
5280
5382
|
const redis2 = options.redis;
|
|
5281
5383
|
const base = new PgModule(pg);
|
|
5282
|
-
const
|
|
5384
|
+
const wsResult = createWSHandler({ sql: sql2, agents, redis: redis2 });
|
|
5385
|
+
const hub = wsResult.hub;
|
|
5386
|
+
const channels = pg.table("_channels", {
|
|
5283
5387
|
id: serial("id").primaryKey(),
|
|
5284
5388
|
tenant_id: text("tenant_id"),
|
|
5285
5389
|
name: text("name").notNull().default(""),
|
|
@@ -5311,16 +5415,16 @@ function messager(options) {
|
|
|
5311
5415
|
});
|
|
5312
5416
|
return {
|
|
5313
5417
|
migrate: async () => {
|
|
5314
|
-
await
|
|
5315
|
-
await
|
|
5418
|
+
await channels.create();
|
|
5419
|
+
await channels.createIndex("tenant_id");
|
|
5316
5420
|
await members.create();
|
|
5317
5421
|
await members.createIndex("member_id");
|
|
5318
5422
|
await members.createIndex(["channel_id", "member_id", "member_type"], { unique: true });
|
|
5319
5423
|
await messages2.create();
|
|
5320
5424
|
await messages2.createIndex(["channel_id", "created_at"], { desc: true });
|
|
5321
5425
|
},
|
|
5322
|
-
router: () => buildRouter3({ sql: sql2, channels
|
|
5323
|
-
wsHandler: () =>
|
|
5426
|
+
router: () => buildRouter3({ sql: sql2, channels, members, messages: messages2, agents, hub }),
|
|
5427
|
+
wsHandler: () => wsResult.handler,
|
|
5324
5428
|
async send(channelId, content, opts) {
|
|
5325
5429
|
const msg = await messages2.insert({
|
|
5326
5430
|
channel_id: channelId,
|
|
@@ -5329,7 +5433,7 @@ function messager(options) {
|
|
|
5329
5433
|
type: opts?.type ?? "text",
|
|
5330
5434
|
content
|
|
5331
5435
|
});
|
|
5332
|
-
broadcastToChannel(channelId, { type: "message", data: msg });
|
|
5436
|
+
broadcastToChannel(hub, channelId, { type: "message", data: msg });
|
|
5333
5437
|
return msg;
|
|
5334
5438
|
},
|
|
5335
5439
|
close: () => base.close()
|
|
@@ -5429,6 +5533,56 @@ function createGateway(config, getPort) {
|
|
|
5429
5533
|
|
|
5430
5534
|
// deploy/manager.ts
|
|
5431
5535
|
import crypto5 from "node:crypto";
|
|
5536
|
+
|
|
5537
|
+
// deploy/process.ts
|
|
5538
|
+
import { fork } from "node:child_process";
|
|
5539
|
+
function forkApp(opts) {
|
|
5540
|
+
const child = fork(opts.entry, [], {
|
|
5541
|
+
cwd: opts.cwd,
|
|
5542
|
+
env: {
|
|
5543
|
+
...process.env,
|
|
5544
|
+
...opts.env,
|
|
5545
|
+
PORT: String(opts.port)
|
|
5546
|
+
},
|
|
5547
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
5548
|
+
});
|
|
5549
|
+
child.stdout?.on("data", (chunk) => {
|
|
5550
|
+
for (const line of chunk.toString().split("\n").filter(Boolean)) {
|
|
5551
|
+
opts.onLog?.(line);
|
|
5552
|
+
}
|
|
5553
|
+
});
|
|
5554
|
+
child.stderr?.on("data", (chunk) => {
|
|
5555
|
+
for (const line of chunk.toString().split("\n").filter(Boolean)) {
|
|
5556
|
+
opts.onLog?.(`[error] ${line}`);
|
|
5557
|
+
}
|
|
5558
|
+
});
|
|
5559
|
+
return { child, port: opts.port };
|
|
5560
|
+
}
|
|
5561
|
+
function stopProcess(mp, timeout = 1e4) {
|
|
5562
|
+
return new Promise((resolve11) => {
|
|
5563
|
+
const timer = setTimeout(() => {
|
|
5564
|
+
mp.child.kill("SIGKILL");
|
|
5565
|
+
resolve11();
|
|
5566
|
+
}, timeout);
|
|
5567
|
+
mp.child.on("exit", () => {
|
|
5568
|
+
clearTimeout(timer);
|
|
5569
|
+
resolve11();
|
|
5570
|
+
});
|
|
5571
|
+
mp.child.kill("SIGTERM");
|
|
5572
|
+
});
|
|
5573
|
+
}
|
|
5574
|
+
async function healthCheck(port, path2 = "/") {
|
|
5575
|
+
try {
|
|
5576
|
+
const res = await fetch(`http://127.0.0.1:${port}${path2}`, {
|
|
5577
|
+
signal: AbortSignal.timeout(5e3)
|
|
5578
|
+
});
|
|
5579
|
+
return res.ok;
|
|
5580
|
+
} catch {
|
|
5581
|
+
return false;
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5585
|
+
// deploy/manager.ts
|
|
5432
5586
|
function createManager(config, apps, manager) {
|
|
5433
5587
|
const router = new Router();
|
|
5434
5588
|
const auth2 = (req, ctx, next) => {
|
|
@@ -5477,7 +5631,7 @@ function createManager(config, apps, manager) {
|
|
|
5477
5631
|
const app = apps.get(ctx.params.name);
|
|
5478
5632
|
if (!app) return new Response("Not Found", { status: 404 });
|
|
5479
5633
|
if (app.process) {
|
|
5480
|
-
app.process.
|
|
5634
|
+
await stopProcess({ child: app.process, port: app.currentPort });
|
|
5481
5635
|
app.process = null;
|
|
5482
5636
|
}
|
|
5483
5637
|
app.status = { ...app.status, status: "stopped", pid: void 0 };
|
|
@@ -5567,54 +5721,6 @@ function createManager(config, apps, manager) {
|
|
|
5567
5721
|
return router;
|
|
5568
5722
|
}
|
|
5569
5723
|
|
|
5570
|
-
// deploy/process.ts
|
|
5571
|
-
import { fork } from "node:child_process";
|
|
5572
|
-
function forkApp(opts) {
|
|
5573
|
-
const child = fork(opts.entry, [], {
|
|
5574
|
-
cwd: opts.cwd,
|
|
5575
|
-
env: {
|
|
5576
|
-
...process.env,
|
|
5577
|
-
...opts.env,
|
|
5578
|
-
PORT: String(opts.port)
|
|
5579
|
-
},
|
|
5580
|
-
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
5581
|
-
});
|
|
5582
|
-
child.stdout?.on("data", (chunk) => {
|
|
5583
|
-
for (const line of chunk.toString().split("\n").filter(Boolean)) {
|
|
5584
|
-
opts.onLog?.(line);
|
|
5585
|
-
}
|
|
5586
|
-
});
|
|
5587
|
-
child.stderr?.on("data", (chunk) => {
|
|
5588
|
-
for (const line of chunk.toString().split("\n").filter(Boolean)) {
|
|
5589
|
-
opts.onLog?.(`[error] ${line}`);
|
|
5590
|
-
}
|
|
5591
|
-
});
|
|
5592
|
-
return { child, port: opts.port };
|
|
5593
|
-
}
|
|
5594
|
-
function stopProcess(mp, timeout = 1e4) {
|
|
5595
|
-
return new Promise((resolve11) => {
|
|
5596
|
-
const timer = setTimeout(() => {
|
|
5597
|
-
mp.child.kill("SIGKILL");
|
|
5598
|
-
resolve11();
|
|
5599
|
-
}, timeout);
|
|
5600
|
-
mp.child.on("exit", () => {
|
|
5601
|
-
clearTimeout(timer);
|
|
5602
|
-
resolve11();
|
|
5603
|
-
});
|
|
5604
|
-
mp.child.kill("SIGTERM");
|
|
5605
|
-
});
|
|
5606
|
-
}
|
|
5607
|
-
async function healthCheck(port, path2 = "/") {
|
|
5608
|
-
try {
|
|
5609
|
-
const res = await fetch(`http://127.0.0.1:${port}${path2}`, {
|
|
5610
|
-
signal: AbortSignal.timeout(5e3)
|
|
5611
|
-
});
|
|
5612
|
-
return res.ok;
|
|
5613
|
-
} catch {
|
|
5614
|
-
return false;
|
|
5615
|
-
}
|
|
5616
|
-
}
|
|
5617
|
-
|
|
5618
5724
|
// deploy/config.ts
|
|
5619
5725
|
function defineConfig(config) {
|
|
5620
5726
|
if (!config.domain) throw new Error("deploy: domain is required");
|
|
@@ -6254,7 +6360,7 @@ function createEditTool(ctx) {
|
|
|
6254
6360
|
// opencode/tools/grep.ts
|
|
6255
6361
|
import { tool as tool7 } from "ai";
|
|
6256
6362
|
import { z as z9 } from "zod";
|
|
6257
|
-
import {
|
|
6363
|
+
import { execFileSync } from "node:child_process";
|
|
6258
6364
|
import { resolve as resolve7 } from "node:path";
|
|
6259
6365
|
import { existsSync as existsSync3 } from "node:fs";
|
|
6260
6366
|
function createGrepTool(ctx) {
|
|
@@ -6268,15 +6374,21 @@ function createGrepTool(ctx) {
|
|
|
6268
6374
|
}),
|
|
6269
6375
|
execute: async ({ pattern, include, path: path2, context }) => {
|
|
6270
6376
|
const searchDir = path2 ? resolve7(ctx.workspace, path2) : ctx.workspace;
|
|
6271
|
-
const contextArg = context > 0 ? `-C ${context}` : "";
|
|
6272
|
-
let cmd;
|
|
6273
|
-
if (existsSync3("/usr/bin/rg") || existsSync3("/usr/local/bin/rg")) {
|
|
6274
|
-
cmd = `rg -n ${contextArg} ${include ? `-g '${include}'` : ""} '${pattern.replace(/'/g, "'\\''")}' '${searchDir}'`;
|
|
6275
|
-
} else {
|
|
6276
|
-
cmd = `grep -rn ${contextArg} ${include ? `--include='${include}'` : ""} '${pattern.replace(/'/g, "'\\''")}' '${searchDir}'`;
|
|
6277
|
-
}
|
|
6278
6377
|
try {
|
|
6279
|
-
|
|
6378
|
+
let stdout;
|
|
6379
|
+
if (existsSync3("/usr/bin/rg") || existsSync3("/usr/local/bin/rg")) {
|
|
6380
|
+
const args = ["-n"];
|
|
6381
|
+
if (context > 0) args.push("-C", String(context));
|
|
6382
|
+
if (include) args.push("-g", include);
|
|
6383
|
+
args.push(pattern, searchDir);
|
|
6384
|
+
stdout = execFileSync("rg", args, { timeout: 15e3, maxBuffer: 1024 * 1024 }).toString();
|
|
6385
|
+
} else {
|
|
6386
|
+
const args = ["-rn"];
|
|
6387
|
+
if (context > 0) args.push("-C", String(context));
|
|
6388
|
+
if (include) args.push("--include", include);
|
|
6389
|
+
args.push(pattern, searchDir);
|
|
6390
|
+
stdout = execFileSync("grep", args, { timeout: 15e3, maxBuffer: 1024 * 1024 }).toString();
|
|
6391
|
+
}
|
|
6280
6392
|
const lines = stdout.split("\n").filter(Boolean);
|
|
6281
6393
|
return { matches: lines.length, results: lines.slice(0, 200), truncated: lines.length > 200 };
|
|
6282
6394
|
} catch (e) {
|
|
@@ -6292,7 +6404,7 @@ function createGrepTool(ctx) {
|
|
|
6292
6404
|
// opencode/tools/glob.ts
|
|
6293
6405
|
import { tool as tool8 } from "ai";
|
|
6294
6406
|
import { z as z10 } from "zod";
|
|
6295
|
-
import {
|
|
6407
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6296
6408
|
import { resolve as resolve8 } from "node:path";
|
|
6297
6409
|
function createGlobTool(ctx) {
|
|
6298
6410
|
return tool8({
|
|
@@ -6304,12 +6416,19 @@ function createGlobTool(ctx) {
|
|
|
6304
6416
|
execute: async ({ pattern, path: path2 }) => {
|
|
6305
6417
|
const searchDir = path2 ? resolve8(ctx.workspace, path2) : ctx.workspace;
|
|
6306
6418
|
try {
|
|
6307
|
-
const stdout =
|
|
6419
|
+
const stdout = execFileSync2("find", [
|
|
6420
|
+
searchDir,
|
|
6421
|
+
"-name",
|
|
6422
|
+
pattern,
|
|
6423
|
+
"-not",
|
|
6424
|
+
"-path",
|
|
6425
|
+
"*/node_modules/*"
|
|
6426
|
+
], {
|
|
6308
6427
|
timeout: 1e4,
|
|
6309
6428
|
maxBuffer: 1024 * 1024
|
|
6310
6429
|
}).toString();
|
|
6311
|
-
const
|
|
6312
|
-
return { files, total:
|
|
6430
|
+
const lines = stdout.split("\n").filter(Boolean).slice(0, 200);
|
|
6431
|
+
return { files: lines, total: lines.length, truncated: stdout.split("\n").filter(Boolean).length > 200 };
|
|
6313
6432
|
} catch {
|
|
6314
6433
|
return { files: [], total: 0, truncated: false };
|
|
6315
6434
|
}
|
|
@@ -6799,10 +6918,20 @@ function health(options) {
|
|
|
6799
6918
|
|
|
6800
6919
|
// analytics.ts
|
|
6801
6920
|
var DEFAULT_EXCLUDED = ["/__analytics", "/__wfw", "/static"];
|
|
6921
|
+
var MAX_MEM_ENTRIES = 1e4;
|
|
6802
6922
|
var MemStore = class {
|
|
6803
6923
|
days = /* @__PURE__ */ new Map();
|
|
6804
6924
|
pages = /* @__PURE__ */ new Map();
|
|
6805
6925
|
refs = /* @__PURE__ */ new Map();
|
|
6926
|
+
evict() {
|
|
6927
|
+
if (this.days.size <= MAX_MEM_ENTRIES) return;
|
|
6928
|
+
const sorted = [...this.days.keys()].sort();
|
|
6929
|
+
const toDelete = sorted.slice(0, this.days.size - MAX_MEM_ENTRIES);
|
|
6930
|
+
for (const d of toDelete) {
|
|
6931
|
+
this.days.delete(d);
|
|
6932
|
+
this.refs.delete(d);
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6806
6935
|
record(path2, date, refDomain, mobile) {
|
|
6807
6936
|
let day = this.days.get(date);
|
|
6808
6937
|
if (!day) {
|
|
@@ -6827,6 +6956,7 @@ var MemStore = class {
|
|
|
6827
6956
|
}
|
|
6828
6957
|
refs.set(refDomain, (refs.get(refDomain) || 0) + 1);
|
|
6829
6958
|
}
|
|
6959
|
+
this.evict();
|
|
6830
6960
|
}
|
|
6831
6961
|
query(days) {
|
|
6832
6962
|
const since = /* @__PURE__ */ new Date();
|
|
@@ -7062,8 +7192,12 @@ function preferences(options) {
|
|
|
7062
7192
|
const localeOpts = { ...defaults.locale, ...options.locale };
|
|
7063
7193
|
const themeOpts = { ...defaults.theme, ...options.theme };
|
|
7064
7194
|
const cache = /* @__PURE__ */ new Map();
|
|
7195
|
+
function validLocale(locale) {
|
|
7196
|
+
return /^[\w-]+$/.test(locale) && !locale.includes("..");
|
|
7197
|
+
}
|
|
7065
7198
|
async function load(locale) {
|
|
7066
7199
|
if (!dir) return {};
|
|
7200
|
+
if (!validLocale(locale)) return {};
|
|
7067
7201
|
const cached = cache.get(locale);
|
|
7068
7202
|
if (cached) return cached;
|
|
7069
7203
|
const filePath = join4(dir, `${locale}.json`);
|
|
@@ -7093,7 +7227,7 @@ function preferences(options) {
|
|
|
7093
7227
|
if (dir) {
|
|
7094
7228
|
const msgs = await load(locale);
|
|
7095
7229
|
ctx.t = (key, params, fallback) => translate(msgs, key, params, fallback);
|
|
7096
|
-
|
|
7230
|
+
ctx.parsed = { ...ctx.parsed, __localeData: msgs };
|
|
7097
7231
|
}
|
|
7098
7232
|
ctx.setPref = (name, value) => {
|
|
7099
7233
|
const cookieOpts = [`${name}=${encodeURIComponent(value)}`, "Path=/", "SameSite=Lax"];
|
|
@@ -7311,7 +7445,7 @@ function mailer(options) {
|
|
|
7311
7445
|
await transporter.sendMail({ ...opts, from: opts.from ?? from });
|
|
7312
7446
|
}
|
|
7313
7447
|
async function close() {
|
|
7314
|
-
|
|
7448
|
+
transporter?.close();
|
|
7315
7449
|
}
|
|
7316
7450
|
return { send, close };
|
|
7317
7451
|
}
|
|
@@ -7345,11 +7479,11 @@ function csrf(options) {
|
|
|
7345
7479
|
return res;
|
|
7346
7480
|
}
|
|
7347
7481
|
const cookieToken = getCookies(req)[cookieName];
|
|
7348
|
-
let headerToken = req.headers.get(headerName);
|
|
7349
|
-
if (!headerToken) {
|
|
7482
|
+
let headerToken = req.headers.get(headerName) ?? req.headers.get("x-xsrf-token") ?? "";
|
|
7483
|
+
if (!headerToken && (req.method === "POST" || req.method === "PUT" || req.method === "PATCH" || req.method === "DELETE")) {
|
|
7350
7484
|
try {
|
|
7351
7485
|
const body = await req.clone().json();
|
|
7352
|
-
headerToken = body[bodyKey];
|
|
7486
|
+
headerToken = body[bodyKey] ?? "";
|
|
7353
7487
|
} catch {
|
|
7354
7488
|
}
|
|
7355
7489
|
}
|
|
@@ -7361,6 +7495,15 @@ function csrf(options) {
|
|
|
7361
7495
|
}
|
|
7362
7496
|
|
|
7363
7497
|
// logdb/rest.ts
|
|
7498
|
+
function parseMetadata(row) {
|
|
7499
|
+
if (typeof row.metadata === "string") {
|
|
7500
|
+
try {
|
|
7501
|
+
row.metadata = JSON.parse(row.metadata);
|
|
7502
|
+
} catch {
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
return row;
|
|
7506
|
+
}
|
|
7364
7507
|
function createHandler(entries) {
|
|
7365
7508
|
return async (req, ctx) => {
|
|
7366
7509
|
const body = await req.json();
|
|
@@ -7377,13 +7520,7 @@ function createHandler(entries) {
|
|
|
7377
7520
|
message: body.message,
|
|
7378
7521
|
metadata
|
|
7379
7522
|
});
|
|
7380
|
-
|
|
7381
|
-
try {
|
|
7382
|
-
row.metadata = JSON.parse(row.metadata);
|
|
7383
|
-
} catch {
|
|
7384
|
-
}
|
|
7385
|
-
}
|
|
7386
|
-
return Response.json(row, { status: 201 });
|
|
7523
|
+
return Response.json(parseMetadata(row), { status: 201 });
|
|
7387
7524
|
};
|
|
7388
7525
|
}
|
|
7389
7526
|
function listHandler(entries) {
|
|
@@ -7409,15 +7546,7 @@ function listHandler(entries) {
|
|
|
7409
7546
|
conditions.length > 0 ? conditions : void 0,
|
|
7410
7547
|
{ orderBy: { created_at: "desc" }, limit, offset }
|
|
7411
7548
|
);
|
|
7412
|
-
|
|
7413
|
-
if (typeof row.metadata === "string") {
|
|
7414
|
-
try {
|
|
7415
|
-
row.metadata = JSON.parse(row.metadata);
|
|
7416
|
-
} catch {
|
|
7417
|
-
}
|
|
7418
|
-
}
|
|
7419
|
-
}
|
|
7420
|
-
return Response.json({ entries: data, total: count });
|
|
7549
|
+
return Response.json({ entries: data.map(parseMetadata), total: count });
|
|
7421
7550
|
};
|
|
7422
7551
|
}
|
|
7423
7552
|
function getHandler(entries) {
|
|
@@ -7426,13 +7555,7 @@ function getHandler(entries) {
|
|
|
7426
7555
|
if (!id2) return Response.json({ error: "id is required" }, { status: 400 });
|
|
7427
7556
|
const row = await entries.read(parseInt(id2));
|
|
7428
7557
|
if (!row) return Response.json({ error: "not found" }, { status: 404 });
|
|
7429
|
-
|
|
7430
|
-
try {
|
|
7431
|
-
row.metadata = JSON.parse(row.metadata);
|
|
7432
|
-
} catch {
|
|
7433
|
-
}
|
|
7434
|
-
}
|
|
7435
|
-
return Response.json(row);
|
|
7558
|
+
return Response.json(parseMetadata(row));
|
|
7436
7559
|
};
|
|
7437
7560
|
}
|
|
7438
7561
|
|
|
@@ -7522,7 +7645,8 @@ function logdb(options) {
|
|
|
7522
7645
|
await ensurePartitions(sql2, tableName);
|
|
7523
7646
|
},
|
|
7524
7647
|
clean,
|
|
7525
|
-
close: () =>
|
|
7648
|
+
close: async () => {
|
|
7649
|
+
}
|
|
7526
7650
|
};
|
|
7527
7651
|
}
|
|
7528
7652
|
|
|
@@ -7530,8 +7654,7 @@ function logdb(options) {
|
|
|
7530
7654
|
import crypto6 from "node:crypto";
|
|
7531
7655
|
|
|
7532
7656
|
// iii/stream.ts
|
|
7533
|
-
|
|
7534
|
-
function notify(stream, group, item, event, data) {
|
|
7657
|
+
function notify(channels, stream, group, item, event, data) {
|
|
7535
7658
|
const keys = [
|
|
7536
7659
|
`${stream}`,
|
|
7537
7660
|
`${stream}:${group}`,
|
|
@@ -7583,7 +7706,7 @@ function applyOps(value, ops) {
|
|
|
7583
7706
|
}
|
|
7584
7707
|
return current;
|
|
7585
7708
|
}
|
|
7586
|
-
function createMemoryStore() {
|
|
7709
|
+
function createMemoryStore(channels) {
|
|
7587
7710
|
const store = /* @__PURE__ */ new Map();
|
|
7588
7711
|
function key(stream, group, item) {
|
|
7589
7712
|
return `${stream}:${group}:${item}`;
|
|
@@ -7593,7 +7716,7 @@ function createMemoryStore() {
|
|
|
7593
7716
|
const k = key(stream, group, item);
|
|
7594
7717
|
const old = store.get(k) ?? null;
|
|
7595
7718
|
store.set(k, deepClone(data));
|
|
7596
|
-
notify(stream, group, item, "set", data);
|
|
7719
|
+
notify(channels, stream, group, item, "set", data);
|
|
7597
7720
|
return { old_value: old, new_value: deepClone(data) };
|
|
7598
7721
|
},
|
|
7599
7722
|
async get(stream, group, item) {
|
|
@@ -7604,7 +7727,7 @@ function createMemoryStore() {
|
|
|
7604
7727
|
const k = key(stream, group, item);
|
|
7605
7728
|
const old = store.get(k) ?? null;
|
|
7606
7729
|
store.delete(k);
|
|
7607
|
-
notify(stream, group, item, "delete", null);
|
|
7730
|
+
notify(channels, stream, group, item, "delete", null);
|
|
7608
7731
|
return { old_value: old };
|
|
7609
7732
|
},
|
|
7610
7733
|
async list(stream, group) {
|
|
@@ -7648,19 +7771,19 @@ function createMemoryStore() {
|
|
|
7648
7771
|
return { streams, count: streams.length };
|
|
7649
7772
|
},
|
|
7650
7773
|
async send(stream, group, type, data, id2) {
|
|
7651
|
-
notify(stream, group, id2 ?? "", "send", { type, data });
|
|
7774
|
+
notify(channels, stream, group, id2 ?? "", "send", { type, data });
|
|
7652
7775
|
},
|
|
7653
7776
|
async update(stream, group, item, ops) {
|
|
7654
7777
|
const k = key(stream, group, item);
|
|
7655
7778
|
const old = deepClone(store.get(k) ?? null);
|
|
7656
7779
|
const newVal = applyOps(old, ops);
|
|
7657
7780
|
store.set(k, deepClone(newVal));
|
|
7658
|
-
notify(stream, group, item, "update", newVal);
|
|
7781
|
+
notify(channels, stream, group, item, "update", newVal);
|
|
7659
7782
|
return { old_value: old, new_value: deepClone(newVal) };
|
|
7660
7783
|
}
|
|
7661
7784
|
};
|
|
7662
7785
|
}
|
|
7663
|
-
function createPgStore(pg) {
|
|
7786
|
+
function createPgStore(channels, pg) {
|
|
7664
7787
|
const sql2 = pg.sql;
|
|
7665
7788
|
return {
|
|
7666
7789
|
async set(stream, group, item, data) {
|
|
@@ -7671,7 +7794,7 @@ function createPgStore(pg) {
|
|
|
7671
7794
|
DO UPDATE SET data = ${data}, updated_at = NOW()
|
|
7672
7795
|
RETURNING data
|
|
7673
7796
|
`;
|
|
7674
|
-
notify(stream, group, item, "set", data);
|
|
7797
|
+
notify(channels, stream, group, item, "set", data);
|
|
7675
7798
|
return { old_value: null, new_value: data };
|
|
7676
7799
|
},
|
|
7677
7800
|
async get(stream, group, item) {
|
|
@@ -7691,7 +7814,7 @@ function createPgStore(pg) {
|
|
|
7691
7814
|
RETURNING data
|
|
7692
7815
|
`;
|
|
7693
7816
|
const old = rows[0]?.data ?? null;
|
|
7694
|
-
notify(stream, group, item, "delete", null);
|
|
7817
|
+
notify(channels, stream, group, item, "delete", null);
|
|
7695
7818
|
return { old_value: old };
|
|
7696
7819
|
},
|
|
7697
7820
|
async list(stream, group) {
|
|
@@ -7729,7 +7852,7 @@ function createPgStore(pg) {
|
|
|
7729
7852
|
return { streams, count: streams.length };
|
|
7730
7853
|
},
|
|
7731
7854
|
async send(stream, group, type, data, id2) {
|
|
7732
|
-
notify(stream, group, id2 ?? "", "send", { type, data });
|
|
7855
|
+
notify(channels, stream, group, id2 ?? "", "send", { type, data });
|
|
7733
7856
|
},
|
|
7734
7857
|
async update(stream, group, item, ops) {
|
|
7735
7858
|
const { value: oldVal } = await this.get(stream, group, item);
|
|
@@ -7740,12 +7863,12 @@ function createPgStore(pg) {
|
|
|
7740
7863
|
ON CONFLICT (stream_name, group_id, item_id)
|
|
7741
7864
|
DO UPDATE SET data = ${newVal}, updated_at = NOW()
|
|
7742
7865
|
`;
|
|
7743
|
-
notify(stream, group, item, "update", newVal);
|
|
7866
|
+
notify(channels, stream, group, item, "update", newVal);
|
|
7744
7867
|
return { old_value: oldVal, new_value: deepClone(newVal) };
|
|
7745
7868
|
}
|
|
7746
7869
|
};
|
|
7747
7870
|
}
|
|
7748
|
-
function createRedisStore(redis2, ttl) {
|
|
7871
|
+
function createRedisStore(channels, redis2, ttl) {
|
|
7749
7872
|
function hashKey(stream, group) {
|
|
7750
7873
|
return `iii:stream:${stream}:${group}`;
|
|
7751
7874
|
}
|
|
@@ -7760,7 +7883,7 @@ function createRedisStore(redis2, ttl) {
|
|
|
7760
7883
|
await redis2.hset(hk, item, JSON.stringify(data));
|
|
7761
7884
|
setTTL(hk);
|
|
7762
7885
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "set", group, item, data }));
|
|
7763
|
-
notify(stream, group, item, "set", data);
|
|
7886
|
+
notify(channels, stream, group, item, "set", data);
|
|
7764
7887
|
return { old_value: old, new_value: deepClone(data) };
|
|
7765
7888
|
},
|
|
7766
7889
|
async get(stream, group, item) {
|
|
@@ -7775,7 +7898,7 @@ function createRedisStore(redis2, ttl) {
|
|
|
7775
7898
|
const remaining = await redis2.hlen(hk);
|
|
7776
7899
|
if (remaining === 0) await redis2.del(hk);
|
|
7777
7900
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "delete", group, item }));
|
|
7778
|
-
notify(stream, group, item, "delete", null);
|
|
7901
|
+
notify(channels, stream, group, item, "delete", null);
|
|
7779
7902
|
return { old_value: old };
|
|
7780
7903
|
},
|
|
7781
7904
|
async list(stream, group) {
|
|
@@ -7826,7 +7949,7 @@ function createRedisStore(redis2, ttl) {
|
|
|
7826
7949
|
return { streams, count: streams.length };
|
|
7827
7950
|
},
|
|
7828
7951
|
async send(stream, group, type, data, id2) {
|
|
7829
|
-
notify(stream, group, id2 ?? "", "send", { type, data });
|
|
7952
|
+
notify(channels, stream, group, id2 ?? "", "send", { type, data });
|
|
7830
7953
|
},
|
|
7831
7954
|
async update(stream, group, item, ops) {
|
|
7832
7955
|
const hk = hashKey(stream, group);
|
|
@@ -7836,13 +7959,14 @@ function createRedisStore(redis2, ttl) {
|
|
|
7836
7959
|
await redis2.hset(hk, item, JSON.stringify(newVal));
|
|
7837
7960
|
setTTL(hk);
|
|
7838
7961
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "update", group, item, data: newVal }));
|
|
7839
|
-
notify(stream, group, item, "update", newVal);
|
|
7962
|
+
notify(channels, stream, group, item, "update", newVal);
|
|
7840
7963
|
return { old_value: old, new_value: deepClone(newVal) };
|
|
7841
7964
|
}
|
|
7842
7965
|
};
|
|
7843
7966
|
}
|
|
7844
7967
|
function createStream(opts) {
|
|
7845
|
-
const
|
|
7968
|
+
const channels = /* @__PURE__ */ new Map();
|
|
7969
|
+
const store = opts?.pg ? createPgStore(channels, opts.pg) : opts?.redis ? createRedisStore(channels, opts.redis, opts.streamTTL ?? 3600) : createMemoryStore(channels);
|
|
7846
7970
|
let redisSub = null;
|
|
7847
7971
|
if (opts?.redis) {
|
|
7848
7972
|
redisSub = opts.redis.duplicate();
|
|
@@ -7852,9 +7976,9 @@ function createStream(opts) {
|
|
|
7852
7976
|
try {
|
|
7853
7977
|
const msg = JSON.parse(rawData);
|
|
7854
7978
|
if (msg.event === "set" || msg.event === "update") {
|
|
7855
|
-
notify(stream, msg.group, msg.item, msg.event, msg.data);
|
|
7979
|
+
notify(channels, stream, msg.group, msg.item, msg.event, msg.data);
|
|
7856
7980
|
} else if (msg.event === "delete") {
|
|
7857
|
-
notify(stream, msg.group, msg.item, "delete", null);
|
|
7981
|
+
notify(channels, stream, msg.group, msg.item, "delete", null);
|
|
7858
7982
|
}
|
|
7859
7983
|
} catch {
|
|
7860
7984
|
}
|
|
@@ -7888,6 +8012,9 @@ function createStream(opts) {
|
|
|
7888
8012
|
`;
|
|
7889
8013
|
await sql2`CREATE INDEX IF NOT EXISTS idx_iii_stream_group ON "_iii_stream" (stream_name, group_id)`;
|
|
7890
8014
|
}
|
|
8015
|
+
},
|
|
8016
|
+
async close() {
|
|
8017
|
+
if (redisSub) await redisSub.quit();
|
|
7891
8018
|
}
|
|
7892
8019
|
};
|
|
7893
8020
|
}
|
|
@@ -8207,60 +8334,12 @@ function iii(opts = {}) {
|
|
|
8207
8334
|
});
|
|
8208
8335
|
}
|
|
8209
8336
|
});
|
|
8210
|
-
function doTrigger(request) {
|
|
8211
|
-
const fn = functions.get(request.function_id);
|
|
8212
|
-
if (!fn) throw new Error(`Function "${request.function_id}" not found`);
|
|
8213
|
-
const ctx = { engine: module, functionId: request.function_id, workerName: fn.workerName };
|
|
8214
|
-
if (request.action === "void") {
|
|
8215
|
-
queueMicrotask(() => fn.handler(request.payload, ctx));
|
|
8216
|
-
return Promise.resolve(void 0);
|
|
8217
|
-
}
|
|
8218
|
-
return Promise.resolve(fn.handler(request.payload, ctx));
|
|
8219
|
-
}
|
|
8220
|
-
const router = buildRouter5({
|
|
8221
|
-
listWorkers: () => Array.from(workers.values()).map((w) => ({
|
|
8222
|
-
id: w.id,
|
|
8223
|
-
name: w.name,
|
|
8224
|
-
status: "connected",
|
|
8225
|
-
connectedAt: Date.now(),
|
|
8226
|
-
functionCount: w.functions.length,
|
|
8227
|
-
triggerCount: w.triggers.length
|
|
8228
|
-
})),
|
|
8229
|
-
listFunctions: () => Array.from(functions.values()).map((f) => ({
|
|
8230
|
-
id: f.id,
|
|
8231
|
-
workerId: f.workerId,
|
|
8232
|
-
workerName: f.workerName,
|
|
8233
|
-
triggers: f.triggers
|
|
8234
|
-
})),
|
|
8235
|
-
listTriggers: () => Array.from(triggers.values()).map((t) => ({
|
|
8236
|
-
id: t.id,
|
|
8237
|
-
type: t.type,
|
|
8238
|
-
function_id: t.function_id,
|
|
8239
|
-
config: t.config,
|
|
8240
|
-
workerId: t.workerId
|
|
8241
|
-
})),
|
|
8242
|
-
trigger: doTrigger,
|
|
8243
|
-
addWorker: addLocalWorker,
|
|
8244
|
-
removeWorker: (worker) => {
|
|
8245
|
-
for (const [wid, reg] of workers) {
|
|
8246
|
-
if (reg.name === worker.name) {
|
|
8247
|
-
removeWorker(wid);
|
|
8248
|
-
return;
|
|
8249
|
-
}
|
|
8250
|
-
}
|
|
8251
|
-
},
|
|
8252
|
-
shutdown: async () => {
|
|
8253
|
-
for (const [, reg] of workers) reg.ws?.close();
|
|
8254
|
-
workers.clear();
|
|
8255
|
-
functions.clear();
|
|
8256
|
-
triggers.clear();
|
|
8257
|
-
},
|
|
8258
|
-
migrate: async () => {
|
|
8259
|
-
await stream.migrate();
|
|
8260
|
-
}
|
|
8261
|
-
}, wsHandler);
|
|
8262
8337
|
const module = {
|
|
8263
|
-
router: () =>
|
|
8338
|
+
router: () => {
|
|
8339
|
+
const r = buildRouter5(module, wsHandler);
|
|
8340
|
+
module.router = () => r;
|
|
8341
|
+
return r;
|
|
8342
|
+
},
|
|
8264
8343
|
wsHandler: () => wsHandler,
|
|
8265
8344
|
addWorker: addLocalWorker,
|
|
8266
8345
|
removeWorker: (worker) => {
|
|
@@ -8271,7 +8350,16 @@ function iii(opts = {}) {
|
|
|
8271
8350
|
}
|
|
8272
8351
|
}
|
|
8273
8352
|
},
|
|
8274
|
-
trigger
|
|
8353
|
+
trigger(request) {
|
|
8354
|
+
const fn = functions.get(request.function_id);
|
|
8355
|
+
if (!fn) throw new Error(`Function "${request.function_id}" not found`);
|
|
8356
|
+
const ctx = { engine: module, functionId: request.function_id, workerName: fn.workerName };
|
|
8357
|
+
if (request.action === "void") {
|
|
8358
|
+
queueMicrotask(() => fn.handler(request.payload, ctx));
|
|
8359
|
+
return Promise.resolve(void 0);
|
|
8360
|
+
}
|
|
8361
|
+
return Promise.resolve(fn.handler(request.payload, ctx));
|
|
8362
|
+
},
|
|
8275
8363
|
listWorkers: () => Array.from(workers.values()).map((w) => ({
|
|
8276
8364
|
id: w.id,
|
|
8277
8365
|
name: w.name,
|
|
@@ -8297,10 +8385,16 @@ function iii(opts = {}) {
|
|
|
8297
8385
|
await stream.migrate();
|
|
8298
8386
|
},
|
|
8299
8387
|
shutdown: async () => {
|
|
8388
|
+
for (const [, p] of pending) {
|
|
8389
|
+
clearTimeout(p.timer);
|
|
8390
|
+
p.reject(new Error("Engine shutting down"));
|
|
8391
|
+
}
|
|
8392
|
+
pending.clear();
|
|
8300
8393
|
for (const [, reg] of workers) reg.ws?.close();
|
|
8301
8394
|
workers.clear();
|
|
8302
8395
|
functions.clear();
|
|
8303
8396
|
triggers.clear();
|
|
8397
|
+
await stream.close();
|
|
8304
8398
|
}
|
|
8305
8399
|
};
|
|
8306
8400
|
return module;
|
|
@@ -8467,7 +8561,16 @@ function registerWorker(url) {
|
|
|
8467
8561
|
send({ type: "register_trigger", function_id: input.function_id, trigger_type: input.type, config: input.config });
|
|
8468
8562
|
},
|
|
8469
8563
|
unregisterTrigger(functionId) {
|
|
8470
|
-
registeredTriggers
|
|
8564
|
+
for (const key of registeredTriggers) {
|
|
8565
|
+
try {
|
|
8566
|
+
const parsed = JSON.parse(key);
|
|
8567
|
+
if (parsed.function_id === functionId) {
|
|
8568
|
+
registeredTriggers.delete(key);
|
|
8569
|
+
break;
|
|
8570
|
+
}
|
|
8571
|
+
} catch {
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8471
8574
|
send({ type: "unregister_trigger", function_id: functionId });
|
|
8472
8575
|
},
|
|
8473
8576
|
trigger(request) {
|
|
@@ -8531,6 +8634,7 @@ export {
|
|
|
8531
8634
|
generateObject,
|
|
8532
8635
|
generateText2 as generateText,
|
|
8533
8636
|
getCookies,
|
|
8637
|
+
getCtx,
|
|
8534
8638
|
graphql,
|
|
8535
8639
|
health,
|
|
8536
8640
|
helmet,
|