weifuwu 0.24.0 → 0.24.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 +970 -756
- package/cli/template/app.ts +5 -1
- package/cli/template/index.ts +4 -1
- package/cli/template/locales/en.json +6 -1
- package/cli/template/locales/zh-CN.json +6 -1
- package/cli/template/locales/zh-TW.json +6 -1
- package/cli/template/locales/zh.json +6 -1
- package/cli/template/ui/app/globals.css +1 -1
- package/cli/template/ui/app/page.tsx +55 -16
- package/cli.ts +148 -104
- package/dist/agent/rest.d.ts +1 -1
- package/dist/agent/run.d.ts +2 -2
- package/dist/agent/types.d.ts +2 -1
- package/dist/ai/workflow.d.ts +1 -1
- package/dist/ai-sdk.d.ts +1 -1
- package/dist/analytics.d.ts +2 -2
- package/dist/cache.d.ts +4 -4
- package/dist/cli.js +135 -97
- package/dist/cookie.d.ts +24 -0
- package/dist/cors.d.ts +2 -2
- package/dist/deploy/types.d.ts +2 -2
- package/dist/fts.d.ts +5 -5
- package/dist/helmet.d.ts +2 -2
- package/dist/hub.d.ts +2 -1
- package/dist/iii/index.d.ts +1 -1
- package/dist/iii/register-worker.d.ts +1 -1
- package/dist/iii/types.d.ts +4 -4
- package/dist/index.d.ts +5 -5
- package/dist/index.js +905 -442
- package/dist/kb/types.d.ts +8 -0
- package/dist/live.d.ts +2 -3
- package/dist/logdb/rest.d.ts +1 -1
- package/dist/logdb/types.d.ts +2 -1
- package/dist/mailer.d.ts +3 -2
- package/dist/messager/agent.d.ts +2 -2
- package/dist/messager/rest.d.ts +3 -3
- package/dist/messager/types.d.ts +2 -1
- package/dist/messager/ws.d.ts +3 -3
- package/dist/opencode/index.d.ts +1 -1
- package/dist/opencode/permissions.d.ts +1 -1
- package/dist/opencode/run.d.ts +1 -1
- package/dist/opencode/session.d.ts +9 -9
- package/dist/opencode/tools/web.d.ts +1 -1
- package/dist/opencode/types.d.ts +2 -1
- package/dist/opencode/ws.d.ts +1 -2
- package/dist/permissions.d.ts +4 -4
- package/dist/postgres/module.d.ts +5 -4
- package/dist/postgres/schema/index.d.ts +1 -1
- package/dist/postgres/schema/table.d.ts +22 -20
- package/dist/postgres/types.d.ts +6 -6
- package/dist/queue/types.d.ts +3 -3
- package/dist/rate-limit.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +141 -96
- package/dist/redis/types.d.ts +2 -2
- package/dist/router.d.ts +10 -10
- package/dist/seo.d.ts +2 -2
- package/dist/serve.d.ts +1 -1
- package/dist/session.d.ts +8 -5
- package/dist/tailwind.d.ts +9 -0
- package/dist/tenant/graphql.d.ts +2 -2
- package/dist/tenant/index.d.ts +1 -1
- package/dist/tenant/rest.d.ts +2 -2
- package/dist/tenant/types.d.ts +3 -3
- package/dist/test-utils.d.ts +3 -3
- package/dist/types.d.ts +8 -0
- package/dist/upload.d.ts +4 -2
- package/dist/user/index.d.ts +1 -1
- package/dist/user/oauth-login.d.ts +2 -2
- package/dist/user/types.d.ts +2 -2
- package/dist/vendor.d.ts +4 -0
- package/opencode/ui/app/globals.css +1 -1
- package/opencode/ui/app/layout.tsx +2 -3
- package/opencode/ui/app/page.tsx +302 -73
- package/package.json +33 -10
- package/cli/template/.weifuwu/ssr/2e3a7e60.js +0 -112
package/dist/index.js
CHANGED
|
@@ -183,8 +183,8 @@ async function sendResponse(res, response, opts) {
|
|
|
183
183
|
}
|
|
184
184
|
res.end();
|
|
185
185
|
}
|
|
186
|
-
async function createTestServer(handler) {
|
|
187
|
-
const server = serve(handler, { port: 0, shutdown: false });
|
|
186
|
+
async function createTestServer(handler, options) {
|
|
187
|
+
const server = serve(handler, { ...options, port: options?.port ?? 0, shutdown: false });
|
|
188
188
|
await server.ready;
|
|
189
189
|
return { server, url: `http://localhost:${server.port}` };
|
|
190
190
|
}
|
|
@@ -262,9 +262,13 @@ function serve(handler, options) {
|
|
|
262
262
|
}
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
|
-
options.signal.addEventListener(
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
options.signal.addEventListener(
|
|
266
|
+
"abort",
|
|
267
|
+
() => {
|
|
268
|
+
server.close();
|
|
269
|
+
},
|
|
270
|
+
{ once: true }
|
|
271
|
+
);
|
|
268
272
|
}
|
|
269
273
|
server.on("error", (err) => {
|
|
270
274
|
console.error("Failed to start server:", err.message);
|
|
@@ -488,7 +492,8 @@ var Router = class _Router {
|
|
|
488
492
|
}
|
|
489
493
|
} else if (typeof arg1 === "function") {
|
|
490
494
|
this.globalMws.push(arg1);
|
|
491
|
-
} else if (typeof arg1 === "object" && arg1 !== null && "middleware" in arg1 &&
|
|
495
|
+
} else if (typeof arg1 === "object" && arg1 !== null && "middleware" in arg1 && // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
496
|
+
typeof arg1.middleware === "function" && arg1 instanceof _Router) {
|
|
492
497
|
const mod = arg1;
|
|
493
498
|
this.globalMws.push(mod.middleware());
|
|
494
499
|
this._mountRouter("/", mod);
|
|
@@ -529,6 +534,7 @@ var Router = class _Router {
|
|
|
529
534
|
return this._routeImpl(method, path2, args);
|
|
530
535
|
}
|
|
531
536
|
/** Internal route registration — no type constraints (used by _mountRouter). */
|
|
537
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
532
538
|
_routeImpl(method, path2, args) {
|
|
533
539
|
const last = args[args.length - 1];
|
|
534
540
|
if (last instanceof _Router) {
|
|
@@ -554,9 +560,7 @@ var Router = class _Router {
|
|
|
554
560
|
node = getOrCreateChild(node, segment, createTrieNode, false);
|
|
555
561
|
}
|
|
556
562
|
if (!isProd() && node.handlers.has(method)) {
|
|
557
|
-
console.warn(
|
|
558
|
-
`[router] route conflict: ${method} ${path2} overwrites existing handler`
|
|
559
|
-
);
|
|
563
|
+
console.warn(`[router] route conflict: ${method} ${path2} overwrites existing handler`);
|
|
560
564
|
}
|
|
561
565
|
node.handlers.set(method, handler);
|
|
562
566
|
if (middlewares.length > 0) node.middlewares.set(method, middlewares);
|
|
@@ -591,7 +595,7 @@ var Router = class _Router {
|
|
|
591
595
|
return result;
|
|
592
596
|
}
|
|
593
597
|
_collectRoutes(node, prefix, result) {
|
|
594
|
-
for (const [method
|
|
598
|
+
for (const [method] of node.handlers) {
|
|
595
599
|
const m = method === "*" ? "ANY" : method;
|
|
596
600
|
const path2 = (prefix || "/") + (node.wildcard ? "/*" : "");
|
|
597
601
|
const middlewares = node.middlewares.get(method);
|
|
@@ -669,7 +673,12 @@ var Router = class _Router {
|
|
|
669
673
|
const wsRoutes = [];
|
|
670
674
|
this._collectWs(sub.wsRoot, "", wsRoutes);
|
|
671
675
|
for (const { path: path2, handler, middlewares } of wsRoutes) {
|
|
672
|
-
this.ws(
|
|
676
|
+
this.ws(
|
|
677
|
+
base + path2,
|
|
678
|
+
...allExtra,
|
|
679
|
+
...middlewares,
|
|
680
|
+
handler
|
|
681
|
+
);
|
|
673
682
|
}
|
|
674
683
|
}
|
|
675
684
|
mergeMws(base, extra) {
|
|
@@ -682,7 +691,12 @@ var Router = class _Router {
|
|
|
682
691
|
for (const [method, handler] of node.handlers) {
|
|
683
692
|
const rmws = node.middlewares.get(method) || [];
|
|
684
693
|
const suffix = node.wildcard ? "/*" : "";
|
|
685
|
-
result.push({
|
|
694
|
+
result.push({
|
|
695
|
+
method,
|
|
696
|
+
path: (prefix || "/") + suffix,
|
|
697
|
+
handler,
|
|
698
|
+
middlewares: this.mergeMws(mws, rmws)
|
|
699
|
+
});
|
|
686
700
|
}
|
|
687
701
|
for (const [seg, child] of node.children) {
|
|
688
702
|
const next = seg === ":" ? `/:${child.param}` : `/${seg}`;
|
|
@@ -744,7 +758,12 @@ var Router = class _Router {
|
|
|
744
758
|
return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
|
|
745
759
|
}
|
|
746
760
|
if (node.handlers.size > 0) {
|
|
747
|
-
return {
|
|
761
|
+
return {
|
|
762
|
+
middlewares: [],
|
|
763
|
+
pathMws,
|
|
764
|
+
params,
|
|
765
|
+
allowedMethods: [...node.handlers.keys()].filter((k) => k !== "*")
|
|
766
|
+
};
|
|
748
767
|
}
|
|
749
768
|
return null;
|
|
750
769
|
}
|
|
@@ -779,38 +798,54 @@ var Router = class _Router {
|
|
|
779
798
|
if (match.allowedMethods && match.allowedMethods.length > 0) {
|
|
780
799
|
if (this.globalMws.length > 0) {
|
|
781
800
|
try {
|
|
782
|
-
return await this.runChain(
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
801
|
+
return await this.runChain(
|
|
802
|
+
this.globalMws,
|
|
803
|
+
() => new Response("Method Not Allowed", {
|
|
804
|
+
status: 405,
|
|
805
|
+
headers: { Allow: match.allowedMethods.join(", ") }
|
|
806
|
+
}),
|
|
807
|
+
req,
|
|
808
|
+
ctx
|
|
809
|
+
);
|
|
786
810
|
} catch (e) {
|
|
787
811
|
return this.handleError(e, req, ctx);
|
|
788
812
|
}
|
|
789
813
|
}
|
|
790
814
|
return new Response("Method Not Allowed", {
|
|
791
815
|
status: 405,
|
|
792
|
-
headers: {
|
|
816
|
+
headers: { Allow: match.allowedMethods.join(", ") }
|
|
793
817
|
});
|
|
794
818
|
}
|
|
795
819
|
}
|
|
796
820
|
if (this.globalMws.length > 0) {
|
|
797
821
|
try {
|
|
798
|
-
return await this.runChain(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
822
|
+
return await this.runChain(
|
|
823
|
+
this.globalMws,
|
|
824
|
+
() => {
|
|
825
|
+
if (!isProd()) {
|
|
826
|
+
return Response.json(
|
|
827
|
+
{ error: "Not Found", path: "/" + segments.join("/"), method: req.method },
|
|
828
|
+
{ status: 404 }
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
return new Response("Not Found", { status: 404 });
|
|
832
|
+
},
|
|
833
|
+
req,
|
|
834
|
+
ctx
|
|
835
|
+
);
|
|
804
836
|
} catch (e) {
|
|
805
837
|
return this.handleError(e, req, ctx);
|
|
806
838
|
}
|
|
807
839
|
}
|
|
808
840
|
if (!isProd()) {
|
|
809
|
-
return Response.json(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
841
|
+
return Response.json(
|
|
842
|
+
{
|
|
843
|
+
error: "Not Found",
|
|
844
|
+
path: "/" + segments.join("/"),
|
|
845
|
+
method: req.method
|
|
846
|
+
},
|
|
847
|
+
{ status: 404 }
|
|
848
|
+
);
|
|
814
849
|
}
|
|
815
850
|
return new Response("Not Found", { status: 404 });
|
|
816
851
|
}
|
|
@@ -825,7 +860,9 @@ function runChainLoop(middlewares, index, finalHandler, req, ctx) {
|
|
|
825
860
|
let called = false;
|
|
826
861
|
const dispatch = (r, c) => {
|
|
827
862
|
if (called) {
|
|
828
|
-
console.warn(
|
|
863
|
+
console.warn(
|
|
864
|
+
"[router] next() called more than once in middleware \u2014 ignoring duplicate call"
|
|
865
|
+
);
|
|
829
866
|
return Promise.resolve(new Response("Internal Server Error", { status: 500 }));
|
|
830
867
|
}
|
|
831
868
|
called = true;
|
|
@@ -849,7 +886,7 @@ function upgradeSocket(wss, req, socket, head, handler, ctx, hub) {
|
|
|
849
886
|
join(room) {
|
|
850
887
|
hub.join(room, ws);
|
|
851
888
|
},
|
|
852
|
-
leave(
|
|
889
|
+
leave(_room) {
|
|
853
890
|
hub.leave(ws);
|
|
854
891
|
},
|
|
855
892
|
sendRoom(room, data) {
|
|
@@ -899,7 +936,15 @@ function sendHttpResponseOnSocket(socket, response) {
|
|
|
899
936
|
|
|
900
937
|
// tsx-context.ts
|
|
901
938
|
import { useSyncExternalStore, createContext } from "react";
|
|
902
|
-
var DEFAULT_CTX = {
|
|
939
|
+
var DEFAULT_CTX = {
|
|
940
|
+
params: {},
|
|
941
|
+
query: {},
|
|
942
|
+
parsed: {},
|
|
943
|
+
loaderData: {},
|
|
944
|
+
env: {},
|
|
945
|
+
user: {},
|
|
946
|
+
flash: {}
|
|
947
|
+
};
|
|
903
948
|
var KEY = "__WEIFUWU_CTX_STORE";
|
|
904
949
|
function getStore() {
|
|
905
950
|
if (typeof globalThis !== "undefined" && globalThis[KEY]) {
|
|
@@ -907,12 +952,22 @@ function getStore() {
|
|
|
907
952
|
}
|
|
908
953
|
const s = {
|
|
909
954
|
_ctx: DEFAULT_CTX,
|
|
910
|
-
_snapshot: {
|
|
955
|
+
_snapshot: {
|
|
956
|
+
params: DEFAULT_CTX.params,
|
|
957
|
+
query: DEFAULT_CTX.query,
|
|
958
|
+
user: DEFAULT_CTX.user,
|
|
959
|
+
parsed: DEFAULT_CTX.parsed,
|
|
960
|
+
theme: DEFAULT_CTX.theme,
|
|
961
|
+
i18n: DEFAULT_CTX.i18n,
|
|
962
|
+
loaderData: DEFAULT_CTX.loaderData,
|
|
963
|
+
env: DEFAULT_CTX.env
|
|
964
|
+
},
|
|
911
965
|
_listeners: /* @__PURE__ */ new Set(),
|
|
912
966
|
_rebuilders: [],
|
|
913
967
|
_alsGetStore: null
|
|
914
968
|
};
|
|
915
969
|
if (typeof globalThis !== "undefined") {
|
|
970
|
+
;
|
|
916
971
|
globalThis[KEY] = s;
|
|
917
972
|
}
|
|
918
973
|
return s;
|
|
@@ -929,7 +984,16 @@ function setCtx(value) {
|
|
|
929
984
|
}
|
|
930
985
|
}
|
|
931
986
|
store._ctx = { ...store._ctx, ...value };
|
|
932
|
-
store._snapshot = {
|
|
987
|
+
store._snapshot = {
|
|
988
|
+
params: store._ctx.params,
|
|
989
|
+
query: store._ctx.query,
|
|
990
|
+
user: store._ctx.user,
|
|
991
|
+
parsed: store._ctx.parsed,
|
|
992
|
+
theme: store._ctx.theme,
|
|
993
|
+
i18n: store._ctx.i18n,
|
|
994
|
+
loaderData: store._ctx.loaderData,
|
|
995
|
+
env: store._ctx.env
|
|
996
|
+
};
|
|
933
997
|
if (typeof window !== "undefined") {
|
|
934
998
|
;
|
|
935
999
|
window.__WEIFUWU_CTX = { ...window.__WEIFUWU_CTX, ...value };
|
|
@@ -1014,7 +1078,8 @@ function cors(options) {
|
|
|
1014
1078
|
const headers = new Headers(res.headers);
|
|
1015
1079
|
headers.set("Access-Control-Allow-Origin", acao);
|
|
1016
1080
|
if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
|
|
1017
|
-
if (opts.exposedHeaders?.length)
|
|
1081
|
+
if (opts.exposedHeaders?.length)
|
|
1082
|
+
headers.set("Access-Control-Expose-Headers", opts.exposedHeaders.join(", "));
|
|
1018
1083
|
if (acao !== "*") headers.set("Vary", "Origin");
|
|
1019
1084
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
1020
1085
|
}
|
|
@@ -1091,7 +1156,7 @@ function serveStatic(root, options) {
|
|
|
1091
1156
|
const headers = {
|
|
1092
1157
|
"Content-Type": mimeType,
|
|
1093
1158
|
"Content-Length": String(stat3.size),
|
|
1094
|
-
|
|
1159
|
+
ETag: etag,
|
|
1095
1160
|
"Last-Modified": stat3.mtime.toUTCString(),
|
|
1096
1161
|
"Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
|
|
1097
1162
|
};
|
|
@@ -1181,10 +1246,12 @@ function validate(schemas) {
|
|
|
1181
1246
|
if (result.success) {
|
|
1182
1247
|
parsed.params = result.data;
|
|
1183
1248
|
} else {
|
|
1184
|
-
issues.push(
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1249
|
+
issues.push(
|
|
1250
|
+
...result.error.issues.map((i) => ({
|
|
1251
|
+
path: ["params", ...i.path.map(String)],
|
|
1252
|
+
message: i.message
|
|
1253
|
+
}))
|
|
1254
|
+
);
|
|
1188
1255
|
}
|
|
1189
1256
|
}
|
|
1190
1257
|
if (schemas?.query) {
|
|
@@ -1192,10 +1259,12 @@ function validate(schemas) {
|
|
|
1192
1259
|
if (result.success) {
|
|
1193
1260
|
parsed.query = result.data;
|
|
1194
1261
|
} else {
|
|
1195
|
-
issues.push(
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1262
|
+
issues.push(
|
|
1263
|
+
...result.error.issues.map((i) => ({
|
|
1264
|
+
path: ["query", ...i.path.map(String)],
|
|
1265
|
+
message: i.message
|
|
1266
|
+
}))
|
|
1267
|
+
);
|
|
1199
1268
|
}
|
|
1200
1269
|
}
|
|
1201
1270
|
if (schemas?.headers) {
|
|
@@ -1207,10 +1276,12 @@ function validate(schemas) {
|
|
|
1207
1276
|
if (result.success) {
|
|
1208
1277
|
parsed.headers = result.data;
|
|
1209
1278
|
} else {
|
|
1210
|
-
issues.push(
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1279
|
+
issues.push(
|
|
1280
|
+
...result.error.issues.map((i) => ({
|
|
1281
|
+
path: ["headers", ...i.path.map(String)],
|
|
1282
|
+
message: i.message
|
|
1283
|
+
}))
|
|
1284
|
+
);
|
|
1214
1285
|
}
|
|
1215
1286
|
}
|
|
1216
1287
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
@@ -1234,10 +1305,12 @@ function validate(schemas) {
|
|
|
1234
1305
|
if (result.success) {
|
|
1235
1306
|
parsed.body = result.data;
|
|
1236
1307
|
} else {
|
|
1237
|
-
issues.push(
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1308
|
+
issues.push(
|
|
1309
|
+
...result.error.issues.map((i) => ({
|
|
1310
|
+
path: ["body", ...i.path.map(String)],
|
|
1311
|
+
message: i.message
|
|
1312
|
+
}))
|
|
1313
|
+
);
|
|
1241
1314
|
}
|
|
1242
1315
|
} else {
|
|
1243
1316
|
parsed.body = bodyValue;
|
|
@@ -1305,11 +1378,14 @@ function setCookie(res, name, value, options) {
|
|
|
1305
1378
|
}
|
|
1306
1379
|
function deleteCookie(res, name, options) {
|
|
1307
1380
|
const headers = new Headers(res.headers);
|
|
1308
|
-
headers.append(
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1381
|
+
headers.append(
|
|
1382
|
+
"Set-Cookie",
|
|
1383
|
+
serializeCookie(name, "", {
|
|
1384
|
+
...options,
|
|
1385
|
+
maxAge: 0,
|
|
1386
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
1387
|
+
})
|
|
1388
|
+
);
|
|
1313
1389
|
return new Response(res.body, {
|
|
1314
1390
|
status: res.status,
|
|
1315
1391
|
statusText: res.statusText,
|
|
@@ -1433,21 +1509,24 @@ function rateLimit(options) {
|
|
|
1433
1509
|
const keyPrefix = options?.prefix ?? "ratelimit:";
|
|
1434
1510
|
const MAX_ENTRIES2 = 1e4;
|
|
1435
1511
|
const hits = /* @__PURE__ */ new Map();
|
|
1436
|
-
const interval = storeType === "memory" ? setInterval(
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1512
|
+
const interval = storeType === "memory" ? setInterval(
|
|
1513
|
+
() => {
|
|
1514
|
+
const now = Date.now();
|
|
1515
|
+
for (const [key, entry] of hits) {
|
|
1516
|
+
if (entry.reset < now) hits.delete(key);
|
|
1517
|
+
}
|
|
1518
|
+
if (hits.size > MAX_ENTRIES2) {
|
|
1519
|
+
const toDelete = hits.size - MAX_ENTRIES2;
|
|
1520
|
+
let deleted = 0;
|
|
1521
|
+
for (const key of hits.keys()) {
|
|
1522
|
+
if (deleted >= toDelete) break;
|
|
1523
|
+
hits.delete(key);
|
|
1524
|
+
deleted++;
|
|
1525
|
+
}
|
|
1448
1526
|
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1527
|
+
},
|
|
1528
|
+
Math.min(window2, 3e4)
|
|
1529
|
+
) : null;
|
|
1451
1530
|
if (interval?.unref) interval.unref();
|
|
1452
1531
|
async function checkAndIncrement(key) {
|
|
1453
1532
|
const now = Date.now();
|
|
@@ -1461,7 +1540,7 @@ function rateLimit(options) {
|
|
|
1461
1540
|
const reset = pttl > 0 ? now + pttl : now + window2;
|
|
1462
1541
|
return { count, reset };
|
|
1463
1542
|
}
|
|
1464
|
-
|
|
1543
|
+
const entry = hits.get(key);
|
|
1465
1544
|
if (!entry || entry.reset < now) {
|
|
1466
1545
|
hits.set(key, { count: 1, reset: now + window2 });
|
|
1467
1546
|
return { count: 1, reset: now + window2 };
|
|
@@ -1492,7 +1571,6 @@ function rateLimit(options) {
|
|
|
1492
1571
|
if (interval) clearInterval(interval);
|
|
1493
1572
|
hits.clear();
|
|
1494
1573
|
};
|
|
1495
|
-
mw.stop = mw.close;
|
|
1496
1574
|
mw.stats = () => ({
|
|
1497
1575
|
store: storeType,
|
|
1498
1576
|
entries: storeType === "memory" ? hits.size : void 0,
|
|
@@ -1536,7 +1614,9 @@ function compress(options) {
|
|
|
1536
1614
|
let compressed;
|
|
1537
1615
|
try {
|
|
1538
1616
|
if (encoding === "br") {
|
|
1539
|
-
compressed = await brotliCompressAsync(body, {
|
|
1617
|
+
compressed = await brotliCompressAsync(body, {
|
|
1618
|
+
params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) }
|
|
1619
|
+
});
|
|
1540
1620
|
} else if (encoding === "gzip") {
|
|
1541
1621
|
compressed = await gzipAsync(body, { level: Math.min(level, 9) });
|
|
1542
1622
|
} else {
|
|
@@ -1650,9 +1730,7 @@ function createSSEStream(iterable, opts) {
|
|
|
1650
1730
|
}
|
|
1651
1731
|
} catch (e) {
|
|
1652
1732
|
if (e.name !== "AbortError") {
|
|
1653
|
-
controller.enqueue(
|
|
1654
|
-
encoder.encode(formatSSE("error", { error: e.message }))
|
|
1655
|
-
);
|
|
1733
|
+
controller.enqueue(encoder.encode(formatSSE("error", { error: e.message })));
|
|
1656
1734
|
}
|
|
1657
1735
|
} finally {
|
|
1658
1736
|
controller.close();
|
|
@@ -1777,26 +1855,31 @@ var TestApp = class {
|
|
|
1777
1855
|
}
|
|
1778
1856
|
/** Register a GET route — supports route-level middleware via spread args. */
|
|
1779
1857
|
get(path2, ...args) {
|
|
1858
|
+
;
|
|
1780
1859
|
this.router.get(path2, ...args);
|
|
1781
1860
|
return this;
|
|
1782
1861
|
}
|
|
1783
1862
|
/** Register a POST route. */
|
|
1784
1863
|
post(path2, ...args) {
|
|
1864
|
+
;
|
|
1785
1865
|
this.router.post(path2, ...args);
|
|
1786
1866
|
return this;
|
|
1787
1867
|
}
|
|
1788
1868
|
/** Register a PUT route. */
|
|
1789
1869
|
put(path2, ...args) {
|
|
1870
|
+
;
|
|
1790
1871
|
this.router.put(path2, ...args);
|
|
1791
1872
|
return this;
|
|
1792
1873
|
}
|
|
1793
1874
|
/** Register a PATCH route. */
|
|
1794
1875
|
patch(path2, ...args) {
|
|
1876
|
+
;
|
|
1795
1877
|
this.router.patch(path2, ...args);
|
|
1796
1878
|
return this;
|
|
1797
1879
|
}
|
|
1798
1880
|
/** Register a DELETE route. */
|
|
1799
1881
|
delete(path2, ...args) {
|
|
1882
|
+
;
|
|
1800
1883
|
this.router.delete(path2, ...args);
|
|
1801
1884
|
return this;
|
|
1802
1885
|
}
|
|
@@ -1879,7 +1962,12 @@ async function withTestDb(optionsOrFn, fn) {
|
|
|
1879
1962
|
}
|
|
1880
1963
|
|
|
1881
1964
|
// graphql.ts
|
|
1882
|
-
import {
|
|
1965
|
+
import {
|
|
1966
|
+
buildSchema,
|
|
1967
|
+
graphql as executeGraphQL,
|
|
1968
|
+
validate as validateQuery,
|
|
1969
|
+
parse
|
|
1970
|
+
} from "graphql";
|
|
1883
1971
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
1884
1972
|
function parseParamsFromGet(url) {
|
|
1885
1973
|
const query = url.searchParams.get("query");
|
|
@@ -1940,11 +2028,17 @@ async function executeQuery(schema, params, options, req, ctx) {
|
|
|
1940
2028
|
const doc = parse(params.query);
|
|
1941
2029
|
const depth = queryDepth(doc);
|
|
1942
2030
|
if (depth > maxDepth) {
|
|
1943
|
-
return Response.json(
|
|
2031
|
+
return Response.json(
|
|
2032
|
+
{ errors: [{ message: `Query depth ${depth} exceeds limit ${maxDepth}` }] },
|
|
2033
|
+
{ status: 400 }
|
|
2034
|
+
);
|
|
1944
2035
|
}
|
|
1945
2036
|
const validationErrors = validateQuery(schema, doc);
|
|
1946
2037
|
if (validationErrors.length > 0) {
|
|
1947
|
-
return Response.json(
|
|
2038
|
+
return Response.json(
|
|
2039
|
+
{ errors: validationErrors.map((e) => ({ message: e.message })) },
|
|
2040
|
+
{ status: 400 }
|
|
2041
|
+
);
|
|
1948
2042
|
}
|
|
1949
2043
|
} catch (err) {
|
|
1950
2044
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2125,7 +2219,10 @@ function resolveRef(path2, ctx) {
|
|
|
2125
2219
|
const propPath = after.slice(dot + 1);
|
|
2126
2220
|
const output = ctx.nodeOutputs.get(id2);
|
|
2127
2221
|
if (output === void 0) throw new Error(`Node "${id2}" has no output yet`);
|
|
2128
|
-
return getByPath(
|
|
2222
|
+
return getByPath(
|
|
2223
|
+
output,
|
|
2224
|
+
propPath.startsWith("output") ? propPath.slice(7).split(".").filter(Boolean) : propPath.split(".")
|
|
2225
|
+
);
|
|
2129
2226
|
}
|
|
2130
2227
|
if (path2.startsWith("$var.")) {
|
|
2131
2228
|
const name = path2.slice(5);
|
|
@@ -2145,7 +2242,8 @@ function resolveValue(v, ctx) {
|
|
|
2145
2242
|
if (Array.isArray(v)) return v.map((item) => resolveValue(item, ctx));
|
|
2146
2243
|
if (typeof v === "object" && v !== null) {
|
|
2147
2244
|
const result = {};
|
|
2148
|
-
for (const [k, val] of Object.entries(v))
|
|
2245
|
+
for (const [k, val] of Object.entries(v))
|
|
2246
|
+
result[k] = resolveValue(val, ctx);
|
|
2149
2247
|
return result;
|
|
2150
2248
|
}
|
|
2151
2249
|
return v;
|
|
@@ -2241,7 +2339,7 @@ async function executeNode(node, ctx) {
|
|
|
2241
2339
|
iters++;
|
|
2242
2340
|
ctx.stepCount++;
|
|
2243
2341
|
if (ctx.stepCount > ctx.maxSteps) throw new Error(`Step limit exceeded`);
|
|
2244
|
-
if (!
|
|
2342
|
+
if (!evaluateExpression(conditionExpr, ctx)) break;
|
|
2245
2343
|
for (const n of body ?? []) {
|
|
2246
2344
|
last = await executeNode(n, ctx);
|
|
2247
2345
|
ctx.nodeOutputs.set(n.id, last);
|
|
@@ -2273,7 +2371,10 @@ async function executeNode(node, ctx) {
|
|
|
2273
2371
|
signal: controller.signal
|
|
2274
2372
|
});
|
|
2275
2373
|
const ct = res.headers.get("content-type") ?? "";
|
|
2276
|
-
return {
|
|
2374
|
+
return {
|
|
2375
|
+
status: res.status,
|
|
2376
|
+
body: ct.includes("json") ? await res.json() : await res.text()
|
|
2377
|
+
};
|
|
2277
2378
|
} finally {
|
|
2278
2379
|
clearTimeout(timer);
|
|
2279
2380
|
}
|
|
@@ -2293,20 +2394,25 @@ function runWorkflow(opts = {}) {
|
|
|
2293
2394
|
description: "Execute a multi-step workflow. Supports eval, set, get, if, while, call, http nodes. Use $var.x for variables, $nodes.id.output for previous node results, $input.x for input parameters. Call nodes invoke registered tools.",
|
|
2294
2395
|
inputSchema: z.object({
|
|
2295
2396
|
goal: z.string().describe("What the workflow should accomplish"),
|
|
2296
|
-
nodes: z.array(
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2397
|
+
nodes: z.array(
|
|
2398
|
+
z.object({
|
|
2399
|
+
id: z.string(),
|
|
2400
|
+
tool: z.string(),
|
|
2401
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
2402
|
+
conditions: z.array(z.object({ test: z.any(), body: z.any() })).optional(),
|
|
2403
|
+
body: z.array(z.any()).optional()
|
|
2404
|
+
})
|
|
2405
|
+
).optional().describe("Workflow nodes. Skip this and provide model for LLM to generate from goal.")
|
|
2303
2406
|
}),
|
|
2304
2407
|
execute: async (input) => {
|
|
2305
2408
|
let nodes;
|
|
2306
2409
|
if (input.nodes && input.nodes.length > 0) {
|
|
2307
2410
|
nodes = input.nodes;
|
|
2308
2411
|
} else {
|
|
2309
|
-
if (!opts.provider && !opts.model)
|
|
2412
|
+
if (!opts.provider && !opts.model)
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
'Provide either "nodes", a "model", or a "provider" with a model to generate the workflow from "goal"'
|
|
2415
|
+
);
|
|
2310
2416
|
const toolsDesc = Object.entries(opts.tools ?? {}).map(([k, t]) => `- ${k}: ${t.description}`).join("\n");
|
|
2311
2417
|
const system = [
|
|
2312
2418
|
"You are a workflow generator. Given a user goal and available tools, output a workflow JSON.",
|
|
@@ -2318,7 +2424,10 @@ function runWorkflow(opts = {}) {
|
|
|
2318
2424
|
"Reference syntax: $var.name, $nodes.id.output, $nodes.id.output.field, $input.field",
|
|
2319
2425
|
"Output ONLY valid JSON. No explanation, no markdown."
|
|
2320
2426
|
].filter(Boolean).join("\n");
|
|
2321
|
-
const genParams = {
|
|
2427
|
+
const genParams = {
|
|
2428
|
+
system,
|
|
2429
|
+
messages: [{ role: "user", content: input.goal }]
|
|
2430
|
+
};
|
|
2322
2431
|
const result = opts.provider ? await opts.provider.generateText(genParams) : await generateText({ ...genParams, model: opts.model });
|
|
2323
2432
|
const text2 = result.text.trim();
|
|
2324
2433
|
const jsonStart = text2.indexOf("{");
|
|
@@ -2411,10 +2520,7 @@ import {
|
|
|
2411
2520
|
embedMany,
|
|
2412
2521
|
smoothStream
|
|
2413
2522
|
} from "ai";
|
|
2414
|
-
import {
|
|
2415
|
-
openai,
|
|
2416
|
-
createOpenAI as createOpenAI2
|
|
2417
|
-
} from "@ai-sdk/openai";
|
|
2523
|
+
import { openai, createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
2418
2524
|
|
|
2419
2525
|
// postgres/client.ts
|
|
2420
2526
|
import postgresFactory from "postgres";
|
|
@@ -2657,7 +2763,10 @@ var Table = class {
|
|
|
2657
2763
|
const using = opts?.type ? `USING ${opts.type.toUpperCase()}` : "";
|
|
2658
2764
|
const colList = cols.map((c) => opts?.desc ? `"${c}" DESC` : `"${c}"`).join(", ");
|
|
2659
2765
|
const operator = opts?.operator ? ` ${opts.operator}` : "";
|
|
2660
|
-
const ddl = `CREATE ${unique} INDEX IF NOT EXISTS ${name} ON "${this.tableName}" ${using} (${colList}${operator})`.replace(
|
|
2766
|
+
const ddl = `CREATE ${unique} INDEX IF NOT EXISTS ${name} ON "${this.tableName}" ${using} (${colList}${operator})`.replace(
|
|
2767
|
+
/\s+/g,
|
|
2768
|
+
" "
|
|
2769
|
+
);
|
|
2661
2770
|
await sql2.unsafe(ddl);
|
|
2662
2771
|
}
|
|
2663
2772
|
async createUniqueIndex(sql2, columns) {
|
|
@@ -2695,9 +2804,10 @@ var Table = class {
|
|
|
2695
2804
|
_buildSET(data) {
|
|
2696
2805
|
const sets = [];
|
|
2697
2806
|
const values = [];
|
|
2807
|
+
const d = data;
|
|
2698
2808
|
for (const { prop, db } of this.colEntries) {
|
|
2699
|
-
if (prop in
|
|
2700
|
-
const val =
|
|
2809
|
+
if (prop in d && d[prop] !== void 0) {
|
|
2810
|
+
const val = d[prop];
|
|
2701
2811
|
if (val instanceof SQL) {
|
|
2702
2812
|
sets.push(`"${db}" = ${val.toSQL()}`);
|
|
2703
2813
|
} else {
|
|
@@ -2706,7 +2816,7 @@ var Table = class {
|
|
|
2706
2816
|
}
|
|
2707
2817
|
}
|
|
2708
2818
|
}
|
|
2709
|
-
if (this.hasColumn("updated_at") && !
|
|
2819
|
+
if (this.hasColumn("updated_at") && !d.updated_at) {
|
|
2710
2820
|
sets.push('"updated_at" = NOW()');
|
|
2711
2821
|
}
|
|
2712
2822
|
return { sets, values };
|
|
@@ -2760,7 +2870,10 @@ var Table = class {
|
|
|
2760
2870
|
const softDel = this._softDeleteFilter(where, opts);
|
|
2761
2871
|
if (softDel) conditions.push(softDel);
|
|
2762
2872
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
2763
|
-
const [countRow] = await sql2.unsafe(
|
|
2873
|
+
const [countRow] = await sql2.unsafe(
|
|
2874
|
+
`SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
|
|
2875
|
+
values
|
|
2876
|
+
);
|
|
2764
2877
|
const count = Number(countRow._total);
|
|
2765
2878
|
if (conditions.length === 0 && !opts?.orderBy && !opts?.limit && !opts?.offset && !opts?.select) {
|
|
2766
2879
|
const rows2 = await sql2`SELECT * FROM ${sql2(this.tableName)}`;
|
|
@@ -2793,7 +2906,10 @@ var Table = class {
|
|
|
2793
2906
|
async updateMany(sql2, where, data) {
|
|
2794
2907
|
const { sets, values: setValues } = this._buildSET(data);
|
|
2795
2908
|
if (sets.length === 0) return 0;
|
|
2796
|
-
const { conditions: wConditions, values: wValues } = this._buildConditions(
|
|
2909
|
+
const { conditions: wConditions, values: wValues } = this._buildConditions(
|
|
2910
|
+
where,
|
|
2911
|
+
setValues.length
|
|
2912
|
+
);
|
|
2797
2913
|
if (wConditions.length === 0) return 0;
|
|
2798
2914
|
const rows = await sql2.unsafe(
|
|
2799
2915
|
`UPDATE "${this.tableName}" SET ${sets.join(", ")} WHERE ${wConditions.join(" AND ")} RETURNING 1`,
|
|
@@ -2882,13 +2998,20 @@ var Table = class {
|
|
|
2882
2998
|
const softDel = this._softDeleteFilter(where);
|
|
2883
2999
|
if (softDel) conditions.push(softDel);
|
|
2884
3000
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
2885
|
-
const [row] = await sql2.unsafe(
|
|
3001
|
+
const [row] = await sql2.unsafe(
|
|
3002
|
+
`SELECT COUNT(*) AS _total FROM "${this.tableName}"${whereClause}`,
|
|
3003
|
+
values
|
|
3004
|
+
);
|
|
2886
3005
|
return Number(row._total);
|
|
2887
3006
|
}
|
|
2888
3007
|
};
|
|
2889
3008
|
var BoundTable = class _BoundTable {
|
|
2890
3009
|
inner;
|
|
2891
3010
|
sql;
|
|
3011
|
+
/** The underlying table name. */
|
|
3012
|
+
get tableName() {
|
|
3013
|
+
return this.inner.tableName;
|
|
3014
|
+
}
|
|
2892
3015
|
constructor(sql2, tableName, builders) {
|
|
2893
3016
|
this.inner = new Table(tableName, builders);
|
|
2894
3017
|
this.sql = sql2;
|
|
@@ -2976,13 +3099,17 @@ function postgres(opts) {
|
|
|
2976
3099
|
connect_timeout: options.connect_timeout
|
|
2977
3100
|
});
|
|
2978
3101
|
if (options.signal) {
|
|
2979
|
-
options.signal.addEventListener(
|
|
2980
|
-
|
|
2981
|
-
|
|
3102
|
+
options.signal.addEventListener(
|
|
3103
|
+
"abort",
|
|
3104
|
+
() => {
|
|
3105
|
+
sql2.end();
|
|
3106
|
+
},
|
|
3107
|
+
{ once: true }
|
|
3108
|
+
);
|
|
2982
3109
|
}
|
|
2983
3110
|
const closeTimeout = options.closeTimeout ?? 5;
|
|
2984
|
-
|
|
2985
|
-
|
|
3111
|
+
const _active = 0;
|
|
3112
|
+
const _waiting = 0;
|
|
2986
3113
|
const poolMax = options.max ?? 10;
|
|
2987
3114
|
const mw = ((req, ctx, next) => {
|
|
2988
3115
|
ctx.sql = sql2;
|
|
@@ -3010,10 +3137,9 @@ function postgres(opts) {
|
|
|
3010
3137
|
);
|
|
3011
3138
|
};
|
|
3012
3139
|
mw.isMigrated = async (moduleName) => {
|
|
3013
|
-
const [row] = await sql2.unsafe(
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
);
|
|
3140
|
+
const [row] = await sql2.unsafe(`SELECT 1 FROM "${MIGRATIONS_TABLE}" WHERE name = $1`, [
|
|
3141
|
+
moduleName
|
|
3142
|
+
]);
|
|
3017
3143
|
return !!row;
|
|
3018
3144
|
};
|
|
3019
3145
|
mw.transaction = (async (fn, retryOpts) => {
|
|
@@ -3145,10 +3271,9 @@ function createOAuth2Server(deps) {
|
|
|
3145
3271
|
return null;
|
|
3146
3272
|
}
|
|
3147
3273
|
function consentPage(client, params) {
|
|
3148
|
-
const fields = Object.entries(params).map(
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
return new Response(`<!DOCTYPE html>
|
|
3274
|
+
const fields = Object.entries(params).map(([k, v]) => `<input type="hidden" name="${k}" value="${v.replace(/"/g, """)}">`).join("\n ");
|
|
3275
|
+
return new Response(
|
|
3276
|
+
`<!DOCTYPE html>
|
|
3152
3277
|
<html lang="en">
|
|
3153
3278
|
<head><meta charset="utf-8"><title>Authorize</title>
|
|
3154
3279
|
<style>
|
|
@@ -3175,17 +3300,22 @@ function createOAuth2Server(deps) {
|
|
|
3175
3300
|
</form>
|
|
3176
3301
|
</div>
|
|
3177
3302
|
</body>
|
|
3178
|
-
</html>`,
|
|
3303
|
+
</html>`,
|
|
3304
|
+
{ headers: { "Content-Type": "text/html; charset=utf-8" } }
|
|
3305
|
+
);
|
|
3179
3306
|
}
|
|
3180
3307
|
function errorPage2(error, description) {
|
|
3181
|
-
return new Response(
|
|
3308
|
+
return new Response(
|
|
3309
|
+
`<!DOCTYPE html>
|
|
3182
3310
|
<html lang="en">
|
|
3183
3311
|
<head><meta charset="utf-8"><title>Error</title>
|
|
3184
3312
|
<style>body{font-family:sans-serif;max-width:480px;margin:80px auto;padding:0 20px}
|
|
3185
3313
|
h2{color:#dc2626}.desc{color:#555}</style>
|
|
3186
3314
|
</head>
|
|
3187
3315
|
<body><h2>${error}</h2>${description ? `<p class="desc">${description}</p>` : ""}</body>
|
|
3188
|
-
</html>`,
|
|
3316
|
+
</html>`,
|
|
3317
|
+
{ status: 400, headers: { "Content-Type": "text/html; charset=utf-8" } }
|
|
3318
|
+
);
|
|
3189
3319
|
}
|
|
3190
3320
|
async function authorizeHandler(req, _ctx) {
|
|
3191
3321
|
const url = new URL(req.url);
|
|
@@ -3204,7 +3334,10 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3204
3334
|
return errorPage2("Invalid client_id", "No client found with the given client_id.");
|
|
3205
3335
|
}
|
|
3206
3336
|
if (!client.redirectUris.includes(redirectUri)) {
|
|
3207
|
-
return errorPage2(
|
|
3337
|
+
return errorPage2(
|
|
3338
|
+
"Invalid redirect_uri",
|
|
3339
|
+
"The redirect_uri is not registered for this client."
|
|
3340
|
+
);
|
|
3208
3341
|
}
|
|
3209
3342
|
const user2 = extractUser(req);
|
|
3210
3343
|
if (!user2) {
|
|
@@ -3231,7 +3364,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3231
3364
|
const redirectUri = form.get("redirect_uri") || "";
|
|
3232
3365
|
const scope = form.get("scope") || "";
|
|
3233
3366
|
const state = form.get("state") || "";
|
|
3234
|
-
const
|
|
3367
|
+
const userId2 = parseInt(form.get("user_id") || "0", 10);
|
|
3235
3368
|
const codeChallenge = form.get("code_challenge") || "";
|
|
3236
3369
|
const codeChallengeMethod = form.get("code_challenge_method") || "";
|
|
3237
3370
|
if (!approve) {
|
|
@@ -3242,7 +3375,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3242
3375
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
3243
3376
|
await pg.sql`
|
|
3244
3377
|
INSERT INTO "_oauth2_codes" ("code", "client_id", "user_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "expires_at")
|
|
3245
|
-
VALUES (${code}, ${clientId}, ${
|
|
3378
|
+
VALUES (${code}, ${clientId}, ${userId2}, ${redirectUri}, ${codeChallenge || null}, ${codeChallengeMethod || null}, ${scope || null}, ${expiresAt})
|
|
3246
3379
|
`;
|
|
3247
3380
|
const loc = `${redirectUri}?code=${code}${state ? `&state=${state}` : ""}`;
|
|
3248
3381
|
return Response.redirect(loc, 302);
|
|
@@ -3290,14 +3423,23 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3290
3423
|
return Response.json({ error: "invalid_grant" }, { status: 400 });
|
|
3291
3424
|
}
|
|
3292
3425
|
if (new Date(stored.expires_at) < /* @__PURE__ */ new Date()) {
|
|
3293
|
-
return Response.json(
|
|
3426
|
+
return Response.json(
|
|
3427
|
+
{ error: "invalid_grant", error_description: "Code expired" },
|
|
3428
|
+
{ status: 400 }
|
|
3429
|
+
);
|
|
3294
3430
|
}
|
|
3295
3431
|
if (stored.redirect_uri !== redirectUri) {
|
|
3296
|
-
return Response.json(
|
|
3432
|
+
return Response.json(
|
|
3433
|
+
{ error: "invalid_grant", error_description: "redirect_uri mismatch" },
|
|
3434
|
+
{ status: 400 }
|
|
3435
|
+
);
|
|
3297
3436
|
}
|
|
3298
3437
|
if (stored.code_challenge) {
|
|
3299
3438
|
if (!codeVerifier) {
|
|
3300
|
-
return Response.json(
|
|
3439
|
+
return Response.json(
|
|
3440
|
+
{ error: "invalid_grant", error_description: "code_verifier required" },
|
|
3441
|
+
{ status: 400 }
|
|
3442
|
+
);
|
|
3301
3443
|
}
|
|
3302
3444
|
let expected;
|
|
3303
3445
|
if (stored.code_challenge_method === "plain") {
|
|
@@ -3306,7 +3448,10 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3306
3448
|
expected = crypto4.createHash("sha256").update(codeVerifier).digest().toString("base64url");
|
|
3307
3449
|
}
|
|
3308
3450
|
if (expected !== stored.code_challenge) {
|
|
3309
|
-
return Response.json(
|
|
3451
|
+
return Response.json(
|
|
3452
|
+
{ error: "invalid_grant", error_description: "code_verifier mismatch" },
|
|
3453
|
+
{ status: 400 }
|
|
3454
|
+
);
|
|
3310
3455
|
}
|
|
3311
3456
|
}
|
|
3312
3457
|
await pg.sql`UPDATE "_oauth2_codes" SET "used" = TRUE WHERE "id" = ${stored.id}`;
|
|
@@ -3418,12 +3563,12 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3418
3563
|
);
|
|
3419
3564
|
return row ?? null;
|
|
3420
3565
|
}
|
|
3421
|
-
async function linkProvider(
|
|
3566
|
+
async function linkProvider(userId2, provider, providerId, email, name, avatarUrl) {
|
|
3422
3567
|
await sql2.unsafe(
|
|
3423
3568
|
`INSERT INTO ${escapeIdent(providerTable)} (user_id, provider, provider_id, email, name, avatar_url)
|
|
3424
3569
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
3425
3570
|
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
|
3426
|
-
[
|
|
3571
|
+
[userId2, provider, providerId, email, name, avatarUrl]
|
|
3427
3572
|
);
|
|
3428
3573
|
}
|
|
3429
3574
|
async function findOrCreateUser(provider, providerId, email, name, avatarUrl) {
|
|
@@ -3511,7 +3656,7 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3511
3656
|
try {
|
|
3512
3657
|
tokenRes = await fetch(meta.tokenUrl, {
|
|
3513
3658
|
method: "POST",
|
|
3514
|
-
headers: { "Content-Type": "application/json",
|
|
3659
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
3515
3660
|
body: JSON.stringify({
|
|
3516
3661
|
code,
|
|
3517
3662
|
client_id: config.clientId,
|
|
@@ -3521,7 +3666,10 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3521
3666
|
})
|
|
3522
3667
|
});
|
|
3523
3668
|
} catch (err) {
|
|
3524
|
-
console.error(
|
|
3669
|
+
console.error(
|
|
3670
|
+
`[oauth] token exchange network error for ${providerName}:`,
|
|
3671
|
+
err.message
|
|
3672
|
+
);
|
|
3525
3673
|
return Response.json({ error: "Failed to connect to OAuth provider" }, { status: 502 });
|
|
3526
3674
|
}
|
|
3527
3675
|
if (!tokenRes.ok) {
|
|
@@ -3538,7 +3686,10 @@ function registerOAuthLoginRoutes(router, deps, providers) {
|
|
|
3538
3686
|
try {
|
|
3539
3687
|
userRes = await fetch(meta.userUrl, { headers: { Authorization: "Bearer " + accessToken } });
|
|
3540
3688
|
} catch (err) {
|
|
3541
|
-
console.error(
|
|
3689
|
+
console.error(
|
|
3690
|
+
"[oauth] user info network error for " + providerName + ":",
|
|
3691
|
+
err.message
|
|
3692
|
+
);
|
|
3542
3693
|
return Response.json({ error: "Failed to connect to OAuth provider" }, { status: 502 });
|
|
3543
3694
|
}
|
|
3544
3695
|
if (!userRes.ok) {
|
|
@@ -3706,11 +3857,9 @@ function user(options) {
|
|
|
3706
3857
|
await tokens.create();
|
|
3707
3858
|
}
|
|
3708
3859
|
function signToken(user2) {
|
|
3709
|
-
return jwt2.sign(
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
{ expiresIn }
|
|
3713
|
-
);
|
|
3860
|
+
return jwt2.sign({ sub: user2.id, email: user2.email, role: user2.role }, secret, {
|
|
3861
|
+
expiresIn
|
|
3862
|
+
});
|
|
3714
3863
|
}
|
|
3715
3864
|
function stripPassword(row) {
|
|
3716
3865
|
const { password: _, ...user2 } = row;
|
|
@@ -3765,7 +3914,7 @@ function user(options) {
|
|
|
3765
3914
|
const payload = jwt2.verify(token, secret);
|
|
3766
3915
|
if (payload.token_type === "client_credentials") return null;
|
|
3767
3916
|
if (!hasDb || !findById) return null;
|
|
3768
|
-
const row = await findById(payload.sub);
|
|
3917
|
+
const row = await findById(Number(payload.sub));
|
|
3769
3918
|
if (!row) return null;
|
|
3770
3919
|
return stripPassword(row);
|
|
3771
3920
|
} catch {
|
|
@@ -3774,27 +3923,26 @@ function user(options) {
|
|
|
3774
3923
|
}
|
|
3775
3924
|
const headerName = options.header ?? "Authorization";
|
|
3776
3925
|
async function resolveUser(req, ctx) {
|
|
3777
|
-
const
|
|
3926
|
+
const s = ctx;
|
|
3927
|
+
const sessionUserId = s.session?.userId;
|
|
3778
3928
|
if (sessionUserId !== void 0 && sessionUserId !== null) {
|
|
3779
3929
|
if (hasDb) {
|
|
3780
3930
|
const row = await findById(sessionUserId);
|
|
3781
3931
|
if (row) {
|
|
3782
3932
|
return stripPassword(row);
|
|
3783
3933
|
}
|
|
3784
|
-
if (typeof
|
|
3785
|
-
;
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
delete ctx.session?.userId;
|
|
3934
|
+
if (typeof s.session?.destroy === "function") {
|
|
3935
|
+
s.session.destroy();
|
|
3936
|
+
} else if (s.session) {
|
|
3937
|
+
delete s.session.userId;
|
|
3789
3938
|
}
|
|
3790
3939
|
} else if (options.resolveUser) {
|
|
3791
3940
|
const userData = await options.resolveUser(sessionUserId);
|
|
3792
3941
|
if (userData) {
|
|
3793
3942
|
return userData;
|
|
3794
3943
|
}
|
|
3795
|
-
if (typeof
|
|
3796
|
-
;
|
|
3797
|
-
ctx.session.destroy();
|
|
3944
|
+
if (typeof s.session?.destroy === "function") {
|
|
3945
|
+
s.session.destroy();
|
|
3798
3946
|
}
|
|
3799
3947
|
console.warn(`[${currentTraceId()}] user: session userId ${sessionUserId} resolved to null`);
|
|
3800
3948
|
} else {
|
|
@@ -3854,7 +4002,7 @@ function user(options) {
|
|
|
3854
4002
|
try {
|
|
3855
4003
|
const payload = jwt2.verify(token, secret);
|
|
3856
4004
|
if (payload.token_type === "client_credentials") return null;
|
|
3857
|
-
const row = await findById(payload.sub);
|
|
4005
|
+
const row = await findById(Number(payload.sub));
|
|
3858
4006
|
if (row) return stripPassword(row);
|
|
3859
4007
|
} catch {
|
|
3860
4008
|
}
|
|
@@ -3879,6 +4027,7 @@ function user(options) {
|
|
|
3879
4027
|
return async (req, ctx, next) => {
|
|
3880
4028
|
const userData = await resolveUser(req, ctx);
|
|
3881
4029
|
if (userData) {
|
|
4030
|
+
;
|
|
3882
4031
|
ctx.user = userData;
|
|
3883
4032
|
}
|
|
3884
4033
|
return next(req, ctx);
|
|
@@ -3915,13 +4064,13 @@ function user(options) {
|
|
|
3915
4064
|
try {
|
|
3916
4065
|
const body = await parseBody2(req);
|
|
3917
4066
|
const result = await login(body);
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
4067
|
+
const s = ctx;
|
|
4068
|
+
if (s.session) {
|
|
4069
|
+
s.session.userId = result.user.id;
|
|
4070
|
+
s.session.role = result.user.role;
|
|
3922
4071
|
}
|
|
3923
4072
|
const res = Response.json(result);
|
|
3924
|
-
if (!
|
|
4073
|
+
if (!s.session) {
|
|
3925
4074
|
res.headers.set("Set-Cookie", `session=${result.token}; HttpOnly; SameSite=Lax; Path=/`);
|
|
3926
4075
|
}
|
|
3927
4076
|
return res;
|
|
@@ -3940,18 +4089,22 @@ function user(options) {
|
|
|
3940
4089
|
r.post("/oauth/token", (req) => oauth2.tokenHandler(req));
|
|
3941
4090
|
}
|
|
3942
4091
|
if (hasDb && options.oauthLogin) {
|
|
3943
|
-
registerOAuthLoginRoutes(
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
4092
|
+
registerOAuthLoginRoutes(
|
|
4093
|
+
r,
|
|
4094
|
+
{
|
|
4095
|
+
sql: _pg.sql,
|
|
4096
|
+
jwtSecret: secret,
|
|
4097
|
+
expiresIn,
|
|
4098
|
+
usersTable: table,
|
|
4099
|
+
providerTable: "_auth_providers",
|
|
4100
|
+
redirectUrl: options.oauthLogin.redirectUrl || "/",
|
|
4101
|
+
signToken,
|
|
4102
|
+
createPlaceholderUser,
|
|
4103
|
+
findUserById: findById,
|
|
4104
|
+
findUserByEmail: findByEmail
|
|
4105
|
+
},
|
|
4106
|
+
options.oauthLogin.providers
|
|
4107
|
+
);
|
|
3955
4108
|
}
|
|
3956
4109
|
const mod = r;
|
|
3957
4110
|
mod.middleware = middleware;
|
|
@@ -4048,9 +4201,7 @@ var FIELD_RANGES = [
|
|
|
4048
4201
|
function parsePattern(pattern) {
|
|
4049
4202
|
const fields = pattern.trim().split(/\s+/);
|
|
4050
4203
|
if (fields.length !== 5) {
|
|
4051
|
-
throw new Error(
|
|
4052
|
-
`Invalid cron pattern "${pattern}": expected 5 fields, got ${fields.length}`
|
|
4053
|
-
);
|
|
4204
|
+
throw new Error(`Invalid cron pattern "${pattern}": expected 5 fields, got ${fields.length}`);
|
|
4054
4205
|
}
|
|
4055
4206
|
return fields.map((f, i) => parseField(f, FIELD_RANGES[i][0], FIELD_RANGES[i][1]));
|
|
4056
4207
|
}
|
|
@@ -4059,7 +4210,7 @@ function matches(fields, date) {
|
|
|
4059
4210
|
}
|
|
4060
4211
|
function cronNext(expr, from = /* @__PURE__ */ new Date()) {
|
|
4061
4212
|
const fields = parsePattern(expr);
|
|
4062
|
-
|
|
4213
|
+
const candidate = new Date(from.getTime() + 6e4);
|
|
4063
4214
|
candidate.setSeconds(0, 0);
|
|
4064
4215
|
for (let i = 0; i < 525600; i++) {
|
|
4065
4216
|
if (fields[4].has(candidate.getDay()) && fields[3].has(candidate.getMonth() + 1) && fields[2].has(candidate.getDate()) && fields[1].has(candidate.getHours()) && fields[0].has(candidate.getMinutes())) {
|
|
@@ -4122,7 +4273,12 @@ function createMemoryQueue(opts) {
|
|
|
4122
4273
|
}
|
|
4123
4274
|
if (job.schedule) {
|
|
4124
4275
|
try {
|
|
4125
|
-
insertJob({
|
|
4276
|
+
insertJob({
|
|
4277
|
+
...job,
|
|
4278
|
+
id: crypto6.randomUUID(),
|
|
4279
|
+
runAt: cronNext(job.schedule),
|
|
4280
|
+
createdAt: Date.now()
|
|
4281
|
+
});
|
|
4126
4282
|
} catch (e) {
|
|
4127
4283
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4128
4284
|
}
|
|
@@ -4210,7 +4366,14 @@ function createMemoryQueue(opts) {
|
|
|
4210
4366
|
mw.dashboard = function dashboard() {
|
|
4211
4367
|
return buildDashboard(q);
|
|
4212
4368
|
};
|
|
4213
|
-
mw.stats = () => ({
|
|
4369
|
+
mw.stats = () => ({
|
|
4370
|
+
running,
|
|
4371
|
+
inflight,
|
|
4372
|
+
processed: _processed,
|
|
4373
|
+
failed: _failed,
|
|
4374
|
+
handlers: handlers.size,
|
|
4375
|
+
maxConcurrent: MAX_CONCURRENT
|
|
4376
|
+
});
|
|
4214
4377
|
attachCron(q, handlers);
|
|
4215
4378
|
return q;
|
|
4216
4379
|
}
|
|
@@ -4225,8 +4388,12 @@ function createPgQueue(opts) {
|
|
|
4225
4388
|
const MAX_FAILED = 1e3;
|
|
4226
4389
|
async function ensureTable() {
|
|
4227
4390
|
if (ready) return;
|
|
4228
|
-
await sql2.unsafe(
|
|
4229
|
-
|
|
4391
|
+
await sql2.unsafe(
|
|
4392
|
+
`CREATE TABLE IF NOT EXISTS ${escapeIdent3(table)} (id UUID PRIMARY KEY, type TEXT NOT NULL, payload JSONB NOT NULL DEFAULT '{}', run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), schedule TEXT, status TEXT NOT NULL DEFAULT 'pending', error TEXT, failed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW())`
|
|
4393
|
+
);
|
|
4394
|
+
await sql2.unsafe(
|
|
4395
|
+
`CREATE INDEX IF NOT EXISTS ${escapeIdent3(table + "_run_at_idx")} ON ${escapeIdent3(table)} (run_at, status)`
|
|
4396
|
+
);
|
|
4230
4397
|
ready = true;
|
|
4231
4398
|
}
|
|
4232
4399
|
async function processJob(job, handler) {
|
|
@@ -4239,14 +4406,26 @@ function createPgQueue(opts) {
|
|
|
4239
4406
|
_failed++;
|
|
4240
4407
|
const msg = e.message;
|
|
4241
4408
|
console.error("[queue] handler error:", msg);
|
|
4242
|
-
await sql2.unsafe(
|
|
4409
|
+
await sql2.unsafe(
|
|
4410
|
+
`UPDATE ${escapeIdent3(table)} SET status = 'failed', error = $2, failed_at = NOW() WHERE id = $1`,
|
|
4411
|
+
[job.id, msg]
|
|
4412
|
+
);
|
|
4243
4413
|
} finally {
|
|
4244
4414
|
inflight--;
|
|
4245
4415
|
}
|
|
4246
4416
|
if (job.schedule) {
|
|
4247
4417
|
try {
|
|
4248
4418
|
const nextRun = cronNext(job.schedule);
|
|
4249
|
-
await sql2.unsafe(
|
|
4419
|
+
await sql2.unsafe(
|
|
4420
|
+
`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`,
|
|
4421
|
+
[
|
|
4422
|
+
crypto6.randomUUID(),
|
|
4423
|
+
job.type,
|
|
4424
|
+
JSON.stringify(job.payload),
|
|
4425
|
+
new Date(nextRun).toISOString(),
|
|
4426
|
+
job.schedule
|
|
4427
|
+
]
|
|
4428
|
+
);
|
|
4250
4429
|
} catch (e) {
|
|
4251
4430
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4252
4431
|
}
|
|
@@ -4256,10 +4435,20 @@ function createPgQueue(opts) {
|
|
|
4256
4435
|
if (!running) return;
|
|
4257
4436
|
try {
|
|
4258
4437
|
while (running && inflight < MAX_CONCURRENT) {
|
|
4259
|
-
const rows = await sql2.unsafe(
|
|
4438
|
+
const rows = await sql2.unsafe(
|
|
4439
|
+
`UPDATE ${escapeIdent3(table)} SET status = 'running' WHERE id = (SELECT id FROM ${escapeIdent3(table)} WHERE run_at <= NOW() AND status = 'pending' ORDER BY run_at LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING *`
|
|
4440
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4441
|
+
);
|
|
4260
4442
|
if (rows.length === 0) break;
|
|
4261
4443
|
const row = rows[0];
|
|
4262
|
-
const job = {
|
|
4444
|
+
const job = {
|
|
4445
|
+
id: row.id,
|
|
4446
|
+
type: row.type,
|
|
4447
|
+
payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload,
|
|
4448
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
4449
|
+
runAt: new Date(row.run_at).getTime(),
|
|
4450
|
+
schedule: row.schedule || void 0
|
|
4451
|
+
};
|
|
4263
4452
|
const handler = handlers.get(job.type);
|
|
4264
4453
|
if (handler) processJob(job, handler);
|
|
4265
4454
|
}
|
|
@@ -4294,7 +4483,10 @@ function createPgQueue(opts) {
|
|
|
4294
4483
|
} else {
|
|
4295
4484
|
runAt = /* @__PURE__ */ new Date();
|
|
4296
4485
|
}
|
|
4297
|
-
await sql2.unsafe(
|
|
4486
|
+
await sql2.unsafe(
|
|
4487
|
+
`INSERT INTO ${escapeIdent3(table)} (id, type, payload, run_at, schedule) VALUES ($1, $2, $3::jsonb, $4, $5)`,
|
|
4488
|
+
[id2, type, JSON.stringify(payload), runAt.toISOString(), opts2?.schedule || null]
|
|
4489
|
+
);
|
|
4298
4490
|
return id2;
|
|
4299
4491
|
})();
|
|
4300
4492
|
};
|
|
@@ -4320,25 +4512,64 @@ function createPgQueue(opts) {
|
|
|
4320
4512
|
while (inflight > 0) await new Promise((r) => setTimeout(r, 50));
|
|
4321
4513
|
};
|
|
4322
4514
|
mw.jobs = async function jobs(limit) {
|
|
4323
|
-
const rows = await sql2.unsafe(
|
|
4324
|
-
|
|
4515
|
+
const rows = await sql2.unsafe(
|
|
4516
|
+
`SELECT * FROM ${escapeIdent3(table)} WHERE status = 'pending' ORDER BY run_at LIMIT $1`,
|
|
4517
|
+
[limit ?? 50]
|
|
4518
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4519
|
+
);
|
|
4520
|
+
return rows.map((r) => ({
|
|
4521
|
+
id: r.id,
|
|
4522
|
+
type: r.type,
|
|
4523
|
+
payload: typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload,
|
|
4524
|
+
createdAt: new Date(r.created_at).getTime(),
|
|
4525
|
+
runAt: new Date(r.run_at).getTime(),
|
|
4526
|
+
schedule: r.schedule || void 0
|
|
4527
|
+
}));
|
|
4325
4528
|
};
|
|
4326
4529
|
mw.failedJobs = async function failedJobs(limit) {
|
|
4327
|
-
const rows = await sql2.unsafe(
|
|
4328
|
-
|
|
4530
|
+
const rows = await sql2.unsafe(
|
|
4531
|
+
`SELECT * FROM ${escapeIdent3(table)} WHERE status = 'failed' ORDER BY failed_at DESC LIMIT $1`,
|
|
4532
|
+
[limit ?? 50]
|
|
4533
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4534
|
+
);
|
|
4535
|
+
return rows.map((r) => ({
|
|
4536
|
+
id: r.id,
|
|
4537
|
+
type: r.type,
|
|
4538
|
+
payload: typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload,
|
|
4539
|
+
createdAt: new Date(r.created_at).getTime(),
|
|
4540
|
+
runAt: new Date(r.run_at).getTime(),
|
|
4541
|
+
schedule: r.schedule || void 0,
|
|
4542
|
+
error: r.error || "",
|
|
4543
|
+
failedAt: new Date(r.failed_at).getTime()
|
|
4544
|
+
}));
|
|
4329
4545
|
};
|
|
4330
4546
|
mw.retryFailed = async function retryFailed(jobId) {
|
|
4331
|
-
const result = await sql2.unsafe(
|
|
4547
|
+
const result = await sql2.unsafe(
|
|
4548
|
+
`UPDATE ${escapeIdent3(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE id = $1 AND status = 'failed' RETURNING id`,
|
|
4549
|
+
[jobId]
|
|
4550
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4551
|
+
);
|
|
4332
4552
|
return result.length > 0;
|
|
4333
4553
|
};
|
|
4334
4554
|
mw.retryAllFailed = async function retryAllFailed(type) {
|
|
4335
|
-
const result = await sql2.unsafe(
|
|
4555
|
+
const result = await sql2.unsafe(
|
|
4556
|
+
type ? `UPDATE ${escapeIdent3(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE status = 'failed' AND type = $1 RETURNING id` : `UPDATE ${escapeIdent3(table)} SET status = 'pending', error = NULL, failed_at = NULL, run_at = NOW() WHERE status = 'failed' RETURNING id`,
|
|
4557
|
+
type ? [type] : []
|
|
4558
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4559
|
+
);
|
|
4336
4560
|
return result.length;
|
|
4337
4561
|
};
|
|
4338
4562
|
mw.dashboard = function dashboard() {
|
|
4339
4563
|
return buildDashboard(q);
|
|
4340
4564
|
};
|
|
4341
|
-
mw.stats = () => ({
|
|
4565
|
+
mw.stats = () => ({
|
|
4566
|
+
running,
|
|
4567
|
+
inflight,
|
|
4568
|
+
processed: _processed,
|
|
4569
|
+
failed: _failed,
|
|
4570
|
+
handlers: handlers.size,
|
|
4571
|
+
maxConcurrent: MAX_CONCURRENT
|
|
4572
|
+
});
|
|
4342
4573
|
attachCron(q, handlers);
|
|
4343
4574
|
return q;
|
|
4344
4575
|
}
|
|
@@ -4367,7 +4598,16 @@ function createRedisQueue(opts) {
|
|
|
4367
4598
|
if (job.schedule) {
|
|
4368
4599
|
try {
|
|
4369
4600
|
const nextRun = cronNext(job.schedule);
|
|
4370
|
-
await redis2.zadd(
|
|
4601
|
+
await redis2.zadd(
|
|
4602
|
+
jobKey,
|
|
4603
|
+
nextRun,
|
|
4604
|
+
JSON.stringify({
|
|
4605
|
+
...job,
|
|
4606
|
+
id: crypto6.randomUUID(),
|
|
4607
|
+
runAt: nextRun,
|
|
4608
|
+
createdAt: Date.now()
|
|
4609
|
+
})
|
|
4610
|
+
);
|
|
4371
4611
|
} catch (e) {
|
|
4372
4612
|
console.error("[queue] cron re-queue failed:", e.message);
|
|
4373
4613
|
}
|
|
@@ -4501,7 +4741,14 @@ function createRedisQueue(opts) {
|
|
|
4501
4741
|
mw.dashboard = function dashboard() {
|
|
4502
4742
|
return buildDashboard(q);
|
|
4503
4743
|
};
|
|
4504
|
-
mw.stats = () => ({
|
|
4744
|
+
mw.stats = () => ({
|
|
4745
|
+
running,
|
|
4746
|
+
inflight,
|
|
4747
|
+
processed: _processed,
|
|
4748
|
+
failed: _failed,
|
|
4749
|
+
handlers: handlers.size,
|
|
4750
|
+
maxConcurrent: MAX_CONCURRENT
|
|
4751
|
+
});
|
|
4505
4752
|
attachCron(q, handlers);
|
|
4506
4753
|
return q;
|
|
4507
4754
|
}
|
|
@@ -4649,10 +4896,14 @@ function createIndexesSQL(tenantId, slug, fields) {
|
|
|
4649
4896
|
const name = internalTableName(tenantId, slug);
|
|
4650
4897
|
const statements = [];
|
|
4651
4898
|
statements.push(`CREATE INDEX IF NOT EXISTS "${name}_tenant_idx" ON "${name}" ("tenant_id")`);
|
|
4652
|
-
statements.push(
|
|
4899
|
+
statements.push(
|
|
4900
|
+
`CREATE INDEX IF NOT EXISTS "${name}_tenant_id_idx" ON "${name}" ("tenant_id", "id")`
|
|
4901
|
+
);
|
|
4653
4902
|
for (const f of fields) {
|
|
4654
4903
|
if (f.unique) {
|
|
4655
|
-
statements.push(
|
|
4904
|
+
statements.push(
|
|
4905
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "${name}_${f.name}_uidx" ON "${name}" ("${f.name}")`
|
|
4906
|
+
);
|
|
4656
4907
|
} else if (f.index === "hnsw") {
|
|
4657
4908
|
statements.push(
|
|
4658
4909
|
`CREATE INDEX IF NOT EXISTS "${name}_${f.name}_hnsw_idx" ON "${name}" USING hnsw ("${f.name}" vector_cosine_ops)`
|
|
@@ -4695,6 +4946,27 @@ function buildColumnDDL(tenantId, field) {
|
|
|
4695
4946
|
}
|
|
4696
4947
|
|
|
4697
4948
|
// tenant/rest.ts
|
|
4949
|
+
function userId(ctx) {
|
|
4950
|
+
return ctx.user?.id ?? null;
|
|
4951
|
+
}
|
|
4952
|
+
function extractCount(rows) {
|
|
4953
|
+
return Number(rows[0]?.count ?? 0);
|
|
4954
|
+
}
|
|
4955
|
+
function asJson(val) {
|
|
4956
|
+
return val;
|
|
4957
|
+
}
|
|
4958
|
+
function tableRef(s, name) {
|
|
4959
|
+
return s(name);
|
|
4960
|
+
}
|
|
4961
|
+
function withTenant(ctx, data) {
|
|
4962
|
+
;
|
|
4963
|
+
data.tenant_id = ctx.tenant.id;
|
|
4964
|
+
return data;
|
|
4965
|
+
}
|
|
4966
|
+
function withoutTenant(data) {
|
|
4967
|
+
delete data.tenant_id;
|
|
4968
|
+
return data;
|
|
4969
|
+
}
|
|
4698
4970
|
function zodType(field) {
|
|
4699
4971
|
let t;
|
|
4700
4972
|
switch (field.type) {
|
|
@@ -4752,7 +5024,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4752
5024
|
`;
|
|
4753
5025
|
await sql2`
|
|
4754
5026
|
INSERT INTO "_tenant_members" ("tenant_id", "user_id", "role")
|
|
4755
|
-
VALUES (${tenant2.id}, ${ctx
|
|
5027
|
+
VALUES (${tenant2.id}, ${userId(ctx)}, 'admin')
|
|
4756
5028
|
`;
|
|
4757
5029
|
return Response.json(tenant2, { status: 201 });
|
|
4758
5030
|
});
|
|
@@ -4760,7 +5032,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4760
5032
|
const rows = await sql2`
|
|
4761
5033
|
SELECT t.*, tm.role FROM "_tenants" t
|
|
4762
5034
|
JOIN "_tenant_members" tm ON tm.tenant_id = t.id
|
|
4763
|
-
WHERE tm.user_id = ${ctx
|
|
5035
|
+
WHERE tm.user_id = ${userId(ctx)}
|
|
4764
5036
|
`;
|
|
4765
5037
|
return Response.json(rows);
|
|
4766
5038
|
});
|
|
@@ -4769,7 +5041,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4769
5041
|
if (err) return err;
|
|
4770
5042
|
const { email, role = "member" } = await req.json();
|
|
4771
5043
|
const [user2] = await sql2`
|
|
4772
|
-
SELECT id FROM ${sql2
|
|
5044
|
+
SELECT id FROM ${tableRef(sql2, usersTable)} WHERE "email" = ${email} LIMIT 1
|
|
4773
5045
|
`;
|
|
4774
5046
|
if (!user2) return Response.json({ error: "User not found" }, { status: 404 });
|
|
4775
5047
|
const [existing] = await sql2`
|
|
@@ -4786,10 +5058,10 @@ function buildRouter(sql2, usersTable) {
|
|
|
4786
5058
|
r.delete("/sys/tenants/members/:userId", async (req, ctx) => {
|
|
4787
5059
|
const err = requireAdmin(ctx);
|
|
4788
5060
|
if (err) return err;
|
|
4789
|
-
const
|
|
5061
|
+
const userId2 = parseInt(ctx.params.userId, 10);
|
|
4790
5062
|
await sql2`
|
|
4791
5063
|
DELETE FROM "_tenant_members"
|
|
4792
|
-
WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${
|
|
5064
|
+
WHERE tenant_id = ${ctx.tenant.id} AND user_id = ${userId2}
|
|
4793
5065
|
`;
|
|
4794
5066
|
return Response.json({ ok: true });
|
|
4795
5067
|
});
|
|
@@ -4815,7 +5087,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4815
5087
|
}
|
|
4816
5088
|
const [row] = await sql2`
|
|
4817
5089
|
INSERT INTO "_user_tables" ("tenant_id", "slug", "label", "fields")
|
|
4818
|
-
VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${body.fields})
|
|
5090
|
+
VALUES (${ctx.tenant.id}, ${body.slug}, ${body.label || ""}, ${asJson(body.fields)})
|
|
4819
5091
|
RETURNING *
|
|
4820
5092
|
`;
|
|
4821
5093
|
return Response.json(row, { status: 201 });
|
|
@@ -4850,7 +5122,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4850
5122
|
const merged = [...table.fields, ...newFields];
|
|
4851
5123
|
await sql2`
|
|
4852
5124
|
UPDATE "_user_tables"
|
|
4853
|
-
SET fields = ${merged}
|
|
5125
|
+
SET fields = ${asJson(merged)}
|
|
4854
5126
|
WHERE id = ${table.id}
|
|
4855
5127
|
`;
|
|
4856
5128
|
return Response.json({ ...table, fields: merged });
|
|
@@ -4893,12 +5165,11 @@ function buildRouter(sql2, usersTable) {
|
|
|
4893
5165
|
`SELECT *, "${searchField}" ${operator} $1::vector AS "_distance" FROM "${name2}" WHERE tenant_id = $2 ORDER BY "_distance" LIMIT $3 OFFSET $4`,
|
|
4894
5166
|
[parsed, ctx.tenant.id, limit, offset]
|
|
4895
5167
|
),
|
|
4896
|
-
sql2.unsafe(
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
)
|
|
5168
|
+
sql2.unsafe(`SELECT count(*) as count FROM "${name2}" WHERE tenant_id = $1`, [
|
|
5169
|
+
ctx.tenant.id
|
|
5170
|
+
])
|
|
4900
5171
|
]);
|
|
4901
|
-
return Response.json({ rows: rows2, count:
|
|
5172
|
+
return Response.json({ rows: rows2, count: extractCount(countResult2) });
|
|
4902
5173
|
} catch {
|
|
4903
5174
|
return Response.json({ error: "Invalid search_vector" }, { status: 400 });
|
|
4904
5175
|
}
|
|
@@ -4909,12 +5180,9 @@ function buildRouter(sql2, usersTable) {
|
|
|
4909
5180
|
`SELECT * FROM "${name}" WHERE tenant_id = $1 ORDER BY "${orderCol}" ${orderDir} LIMIT $2 OFFSET $3`,
|
|
4910
5181
|
[ctx.tenant.id, limit, offset]
|
|
4911
5182
|
),
|
|
4912
|
-
sql2.unsafe(
|
|
4913
|
-
`SELECT count(*) as count FROM "${name}" WHERE tenant_id = $1`,
|
|
4914
|
-
[ctx.tenant.id]
|
|
4915
|
-
)
|
|
5183
|
+
sql2.unsafe(`SELECT count(*) as count FROM "${name}" WHERE tenant_id = $1`, [ctx.tenant.id])
|
|
4916
5184
|
]);
|
|
4917
|
-
return Response.json({ rows, count:
|
|
5185
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
4918
5186
|
});
|
|
4919
5187
|
r.post("/:_slug", async (req, ctx) => {
|
|
4920
5188
|
const table = await resolveTable(ctx);
|
|
@@ -4925,11 +5193,10 @@ function buildRouter(sql2, usersTable) {
|
|
|
4925
5193
|
shape[f.name] = zodType(f);
|
|
4926
5194
|
}
|
|
4927
5195
|
const zodSchema = z3.object(shape);
|
|
4928
|
-
const parsed = zodSchema.parse(data);
|
|
4929
|
-
parsed.tenant_id = ctx.tenant.id;
|
|
5196
|
+
const parsed = withTenant(ctx, zodSchema.parse(data));
|
|
4930
5197
|
delete parsed.id;
|
|
4931
5198
|
const name = internalName(ctx);
|
|
4932
|
-
const [row] = await sql2`INSERT INTO ${sql2
|
|
5199
|
+
const [row] = await sql2`INSERT INTO ${tableRef(sql2, name)} ${sql2(parsed)} RETURNING *`;
|
|
4933
5200
|
return Response.json(row, { status: 201 });
|
|
4934
5201
|
});
|
|
4935
5202
|
r.get("/:_slug/:id", async (_req, ctx) => {
|
|
@@ -4937,7 +5204,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4937
5204
|
if (!table) return Response.json({ error: "Table not found" }, { status: 404 });
|
|
4938
5205
|
const name = internalName(ctx);
|
|
4939
5206
|
const [row] = await sql2`
|
|
4940
|
-
SELECT * FROM ${sql2
|
|
5207
|
+
SELECT * FROM ${tableRef(sql2, name)}
|
|
4941
5208
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4942
5209
|
LIMIT 1
|
|
4943
5210
|
`;
|
|
@@ -4953,13 +5220,12 @@ function buildRouter(sql2, usersTable) {
|
|
|
4953
5220
|
shape[f.name] = zodType(f);
|
|
4954
5221
|
}
|
|
4955
5222
|
const zodSchema = z3.object(shape).partial();
|
|
4956
|
-
const parsed = zodSchema.parse(data);
|
|
5223
|
+
const parsed = withoutTenant(zodSchema.parse(data));
|
|
4957
5224
|
delete parsed.id;
|
|
4958
|
-
delete parsed.tenant_id;
|
|
4959
5225
|
if (Object.keys(parsed).length === 0) {
|
|
4960
5226
|
const name2 = internalName(ctx);
|
|
4961
5227
|
const [row2] = await sql2`
|
|
4962
|
-
SELECT * FROM ${sql2
|
|
5228
|
+
SELECT * FROM ${tableRef(sql2, name2)}
|
|
4963
5229
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4964
5230
|
LIMIT 1
|
|
4965
5231
|
`;
|
|
@@ -4967,7 +5233,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4967
5233
|
}
|
|
4968
5234
|
const name = internalName(ctx);
|
|
4969
5235
|
const [row] = await sql2`
|
|
4970
|
-
UPDATE ${sql2
|
|
5236
|
+
UPDATE ${tableRef(sql2, name)} SET ${sql2(parsed)}
|
|
4971
5237
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4972
5238
|
RETURNING *
|
|
4973
5239
|
`;
|
|
@@ -4977,7 +5243,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
4977
5243
|
r.delete("/:_slug/:id", async (_req, ctx) => {
|
|
4978
5244
|
const name = internalName(ctx);
|
|
4979
5245
|
const result = await sql2`
|
|
4980
|
-
DELETE FROM ${sql2
|
|
5246
|
+
DELETE FROM ${tableRef(sql2, name)}
|
|
4981
5247
|
WHERE id = ${parseInt(ctx.params.id, 10)} AND tenant_id = ${ctx.tenant.id}
|
|
4982
5248
|
RETURNING 1
|
|
4983
5249
|
`;
|
|
@@ -4996,7 +5262,10 @@ function buildRouter(sql2, usersTable) {
|
|
|
4996
5262
|
if (!childTable) return Response.json({ error: "Nested table not found" }, { status: 404 });
|
|
4997
5263
|
const relField = findRelation(childTable.fields, ctx.params["_slug"]);
|
|
4998
5264
|
if (!relField) {
|
|
4999
|
-
return Response.json(
|
|
5265
|
+
return Response.json(
|
|
5266
|
+
{ error: `No relation from "${nestedSlug}" to "${ctx.params["_slug"]}"` },
|
|
5267
|
+
{ status: 400 }
|
|
5268
|
+
);
|
|
5000
5269
|
}
|
|
5001
5270
|
const relFields = getRelationFields(childTable.fields);
|
|
5002
5271
|
if (relFields.length === 2) {
|
|
@@ -5018,7 +5287,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
5018
5287
|
[parentId2, ctx.tenant.id]
|
|
5019
5288
|
)
|
|
5020
5289
|
]);
|
|
5021
|
-
return Response.json({ rows, count:
|
|
5290
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
5022
5291
|
}
|
|
5023
5292
|
return Response.json({ error: "POST not supported on M2M nested routes" }, { status: 400 });
|
|
5024
5293
|
}
|
|
@@ -5035,7 +5304,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
5035
5304
|
[parentId, ctx.tenant.id]
|
|
5036
5305
|
)
|
|
5037
5306
|
]);
|
|
5038
|
-
return Response.json({ rows, count:
|
|
5307
|
+
return Response.json({ rows, count: extractCount(countResult) });
|
|
5039
5308
|
}
|
|
5040
5309
|
const body = await req.json();
|
|
5041
5310
|
const shape = {};
|
|
@@ -5047,7 +5316,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
5047
5316
|
parsed.tenant_id = ctx.tenant.id;
|
|
5048
5317
|
parsed[relField.name] = parentId;
|
|
5049
5318
|
delete parsed.id;
|
|
5050
|
-
const [row] = await sql2`INSERT INTO ${sql2
|
|
5319
|
+
const [row] = await sql2`INSERT INTO ${tableRef(sql2, childName)} ${sql2(parsed)} RETURNING *`;
|
|
5051
5320
|
return Response.json(row, { status: 201 });
|
|
5052
5321
|
}
|
|
5053
5322
|
r.get("/:_slug/:id/:_nested", async (req, ctx) => handleNested(req, ctx, "GET"));
|
|
@@ -5067,11 +5336,11 @@ import {
|
|
|
5067
5336
|
GraphQLID,
|
|
5068
5337
|
GraphQLList,
|
|
5069
5338
|
GraphQLNonNull,
|
|
5070
|
-
GraphQLEnumType
|
|
5339
|
+
GraphQLEnumType,
|
|
5340
|
+
graphql as executeGraphQL2
|
|
5071
5341
|
} from "graphql";
|
|
5072
|
-
import { graphql as executeGraphQL2 } from "graphql";
|
|
5073
5342
|
function graphqlType(field, required) {
|
|
5074
|
-
let t;
|
|
5343
|
+
let t = GraphQLString;
|
|
5075
5344
|
switch (field.type) {
|
|
5076
5345
|
case "integer":
|
|
5077
5346
|
t = GraphQLInt;
|
|
@@ -5088,15 +5357,11 @@ function graphqlType(field, required) {
|
|
|
5088
5357
|
name: `Enum_${field.name}`,
|
|
5089
5358
|
values: Object.fromEntries(field.options.map((o) => [o, { value: o }]))
|
|
5090
5359
|
});
|
|
5091
|
-
} else {
|
|
5092
|
-
t = GraphQLString;
|
|
5093
5360
|
}
|
|
5094
5361
|
break;
|
|
5095
5362
|
case "vector":
|
|
5096
5363
|
t = GraphQLString;
|
|
5097
5364
|
break;
|
|
5098
|
-
default:
|
|
5099
|
-
t = GraphQLString;
|
|
5100
5365
|
}
|
|
5101
5366
|
return required ? new GraphQLNonNull(t) : t;
|
|
5102
5367
|
}
|
|
@@ -5117,13 +5382,13 @@ function buildObjectType(table, ctx) {
|
|
|
5117
5382
|
if (other.id === table.id) continue;
|
|
5118
5383
|
const relField = findRelation(other.fields, table.slug);
|
|
5119
5384
|
if (relField) {
|
|
5120
|
-
const otherName = pascalCase(other.slug);
|
|
5121
5385
|
fields[other.slug] = {
|
|
5122
5386
|
type: new GraphQLList(new GraphQLNonNull(buildObjectType(other, ctx))),
|
|
5123
5387
|
args: {
|
|
5124
5388
|
limit: { type: GraphQLInt, defaultValue: 20 },
|
|
5125
5389
|
offset: { type: GraphQLInt, defaultValue: 0 }
|
|
5126
5390
|
},
|
|
5391
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5127
5392
|
resolve: async (parent) => {
|
|
5128
5393
|
const childName = internalTableName(ctx.tenantId, other.slug);
|
|
5129
5394
|
const rows = await ctx.sql.unsafe(
|
|
@@ -5146,6 +5411,7 @@ function buildObjectType(table, ctx) {
|
|
|
5146
5411
|
limit: { type: GraphQLInt, defaultValue: 20 },
|
|
5147
5412
|
offset: { type: GraphQLInt, defaultValue: 0 }
|
|
5148
5413
|
},
|
|
5414
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5149
5415
|
resolve: async (parent) => {
|
|
5150
5416
|
const childName = internalTableName(ctx.tenantId, other.slug);
|
|
5151
5417
|
const targetName = internalTableName(ctx.tenantId, targetSlug);
|
|
@@ -5167,6 +5433,7 @@ function buildObjectType(table, ctx) {
|
|
|
5167
5433
|
if (!targetTable) continue;
|
|
5168
5434
|
fields[targetSlug] = {
|
|
5169
5435
|
type: buildObjectType(targetTable, ctx),
|
|
5436
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5170
5437
|
resolve: async (parent) => {
|
|
5171
5438
|
const name = internalTableName(ctx.tenantId, targetSlug);
|
|
5172
5439
|
const [row] = await ctx.sql.unsafe(
|
|
@@ -5187,9 +5454,7 @@ function buildInputType(table, prefix) {
|
|
|
5187
5454
|
const typeName = pascalCase(`${prefix}_${table.slug}`) + "Input";
|
|
5188
5455
|
return new GraphQLInputObjectType({
|
|
5189
5456
|
name: typeName,
|
|
5190
|
-
fields: Object.fromEntries(
|
|
5191
|
-
table.fields.map((f) => [f.name, { type: inputGraphqlType(f) }])
|
|
5192
|
-
)
|
|
5457
|
+
fields: Object.fromEntries(table.fields.map((f) => [f.name, { type: inputGraphqlType(f) }]))
|
|
5193
5458
|
});
|
|
5194
5459
|
}
|
|
5195
5460
|
function buildQueryFields(tables, ctx) {
|
|
@@ -5204,6 +5469,7 @@ function buildQueryFields(tables, ctx) {
|
|
|
5204
5469
|
limit: { type: GraphQLInt, defaultValue: 20 },
|
|
5205
5470
|
offset: { type: GraphQLInt, defaultValue: 0 }
|
|
5206
5471
|
},
|
|
5472
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5207
5473
|
resolve: async (_, args) => {
|
|
5208
5474
|
const name = internalTableName(ctx.tenantId, slug);
|
|
5209
5475
|
const rows = await ctx.sql.unsafe(
|
|
@@ -5216,6 +5482,7 @@ function buildQueryFields(tables, ctx) {
|
|
|
5216
5482
|
fields[`get${pascal}`] = {
|
|
5217
5483
|
type: objType,
|
|
5218
5484
|
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
|
|
5485
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5219
5486
|
resolve: async (_, args) => {
|
|
5220
5487
|
const name = internalTableName(ctx.tenantId, slug);
|
|
5221
5488
|
const [row] = await ctx.sql.unsafe(
|
|
@@ -5238,6 +5505,7 @@ function buildMutationFields(tables, ctx) {
|
|
|
5238
5505
|
fields[`create${pascal}`] = {
|
|
5239
5506
|
type: objType,
|
|
5240
5507
|
args: { data: { type: new GraphQLNonNull(inputType) } },
|
|
5508
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5241
5509
|
resolve: async (_, args) => {
|
|
5242
5510
|
const name = internalTableName(ctx.tenantId, table.slug);
|
|
5243
5511
|
const data = { ...args.data, tenant_id: ctx.tenantId };
|
|
@@ -5254,6 +5522,7 @@ function buildMutationFields(tables, ctx) {
|
|
|
5254
5522
|
id: { type: new GraphQLNonNull(GraphQLID) },
|
|
5255
5523
|
data: { type: new GraphQLNonNull(patchType) }
|
|
5256
5524
|
},
|
|
5525
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5257
5526
|
resolve: async (_, args) => {
|
|
5258
5527
|
const name = internalTableName(ctx.tenantId, table.slug);
|
|
5259
5528
|
const setClauses = table.fields.filter((f) => args.data[f.name] !== void 0).map((f, i) => `"${f.name}" = $${i + 1}`);
|
|
@@ -5276,6 +5545,7 @@ function buildMutationFields(tables, ctx) {
|
|
|
5276
5545
|
fields[`delete${pascal}`] = {
|
|
5277
5546
|
type: GraphQLBoolean,
|
|
5278
5547
|
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
|
|
5548
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5279
5549
|
resolve: async (_, args) => {
|
|
5280
5550
|
const name = internalTableName(ctx.tenantId, table.slug);
|
|
5281
5551
|
const result = await ctx.sql.unsafe(
|
|
@@ -5386,7 +5656,9 @@ function tenant(options) {
|
|
|
5386
5656
|
});
|
|
5387
5657
|
await members.create();
|
|
5388
5658
|
await members.createIndex("user_id");
|
|
5389
|
-
await sql2.unsafe(
|
|
5659
|
+
await sql2.unsafe(
|
|
5660
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "_tenant_members_unique_idx" ON "_tenant_members" ("tenant_id", "user_id")`
|
|
5661
|
+
);
|
|
5390
5662
|
const tables = pg.table("_user_tables", {
|
|
5391
5663
|
id: serial("id").primaryKey(),
|
|
5392
5664
|
tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
|
|
@@ -5397,7 +5669,9 @@ function tenant(options) {
|
|
|
5397
5669
|
});
|
|
5398
5670
|
await tables.create();
|
|
5399
5671
|
await tables.createIndex("tenant_id");
|
|
5400
|
-
await sql2.unsafe(
|
|
5672
|
+
await sql2.unsafe(
|
|
5673
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "_user_tables_unique_idx" ON "_user_tables" ("tenant_id", "slug")`
|
|
5674
|
+
);
|
|
5401
5675
|
}
|
|
5402
5676
|
function middleware() {
|
|
5403
5677
|
return async (req, ctx, next) => {
|
|
@@ -5421,10 +5695,13 @@ function tenant(options) {
|
|
|
5421
5695
|
}
|
|
5422
5696
|
const headerId = req.headers.get("X-Tenant-ID");
|
|
5423
5697
|
if (!headerId) {
|
|
5424
|
-
return Response.json(
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5698
|
+
return Response.json(
|
|
5699
|
+
{
|
|
5700
|
+
error: "Multiple tenants. Set X-Tenant-ID header.",
|
|
5701
|
+
tenants: members.map((m) => ({ id: m.id, name: m.name, role: m.role }))
|
|
5702
|
+
},
|
|
5703
|
+
{ status: 300 }
|
|
5704
|
+
);
|
|
5428
5705
|
}
|
|
5429
5706
|
const member = members.find((m) => m.id === headerId);
|
|
5430
5707
|
if (!member) {
|
|
@@ -5465,7 +5742,9 @@ function buildRouter2(deps) {
|
|
|
5465
5742
|
return Response.json(row, { status: 201 });
|
|
5466
5743
|
});
|
|
5467
5744
|
r.get("/agents", async () => {
|
|
5468
|
-
const { data: rows } = await agentsTable.readMany(void 0, {
|
|
5745
|
+
const { data: rows } = await agentsTable.readMany(void 0, {
|
|
5746
|
+
orderBy: { created_at: "desc" }
|
|
5747
|
+
});
|
|
5469
5748
|
return Response.json(rows);
|
|
5470
5749
|
});
|
|
5471
5750
|
r.get("/agents/:id", async (_req, ctx) => {
|
|
@@ -5479,7 +5758,14 @@ function buildRouter2(deps) {
|
|
|
5479
5758
|
if (!agent2) return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
5480
5759
|
const body = await req.json();
|
|
5481
5760
|
const updateData = {};
|
|
5482
|
-
for (const key of [
|
|
5761
|
+
for (const key of [
|
|
5762
|
+
"name",
|
|
5763
|
+
"description",
|
|
5764
|
+
"type",
|
|
5765
|
+
"model",
|
|
5766
|
+
"system_prompt",
|
|
5767
|
+
"active"
|
|
5768
|
+
]) {
|
|
5483
5769
|
if (body[key] !== void 0) {
|
|
5484
5770
|
updateData[key] = body[key];
|
|
5485
5771
|
}
|
|
@@ -5616,7 +5902,9 @@ function chunkContent(content, chunkSize, overlap) {
|
|
|
5616
5902
|
|
|
5617
5903
|
// agent/run.ts
|
|
5618
5904
|
function hasKnowledgeDocs(sql2, agentId) {
|
|
5619
|
-
return sql2`SELECT 1 FROM "_knowledge_documents" WHERE agent_id = ${agentId} LIMIT 1`.then(
|
|
5905
|
+
return sql2`SELECT 1 FROM "_knowledge_documents" WHERE agent_id = ${agentId} LIMIT 1`.then(
|
|
5906
|
+
(r) => r.length > 0
|
|
5907
|
+
);
|
|
5620
5908
|
}
|
|
5621
5909
|
async function searchKnowledge(sql2, provider, agentId, query, limit = 5) {
|
|
5622
5910
|
const embedding = await provider.embed(query);
|
|
@@ -5632,7 +5920,7 @@ async function loadAgent(agents, agentId) {
|
|
|
5632
5920
|
return row ?? null;
|
|
5633
5921
|
}
|
|
5634
5922
|
function createRunner(deps) {
|
|
5635
|
-
const { sql: sql2, agents, runs, provider,
|
|
5923
|
+
const { sql: sql2, agents, runs, provider, userTools } = deps;
|
|
5636
5924
|
function truncate(s, max = 200) {
|
|
5637
5925
|
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
5638
5926
|
}
|
|
@@ -5790,7 +6078,14 @@ function agent(options) {
|
|
|
5790
6078
|
trace_id: text("trace_id"),
|
|
5791
6079
|
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
5792
6080
|
});
|
|
5793
|
-
const runner = createRunner({
|
|
6081
|
+
const runner = createRunner({
|
|
6082
|
+
sql: sql2,
|
|
6083
|
+
agents: agentsTable,
|
|
6084
|
+
runs: runsTable,
|
|
6085
|
+
knowledge: knowledgeTable,
|
|
6086
|
+
provider: resolvedProvider,
|
|
6087
|
+
userTools: options.tools
|
|
6088
|
+
});
|
|
5794
6089
|
const base = new PgModule(pg);
|
|
5795
6090
|
const r = buildRouter2({ agents: agentsTable, runs: runsTable, knowledge: knowledgeTable, runner });
|
|
5796
6091
|
const mod = r;
|
|
@@ -5942,32 +6237,32 @@ function createWSHandler(deps) {
|
|
|
5942
6237
|
prefix: "messager:"
|
|
5943
6238
|
});
|
|
5944
6239
|
const userConnections = /* @__PURE__ */ new Map();
|
|
5945
|
-
function trackConnection(
|
|
5946
|
-
let conns = userConnections.get(
|
|
6240
|
+
function trackConnection(userId2, ws) {
|
|
6241
|
+
let conns = userConnections.get(userId2);
|
|
5947
6242
|
if (!conns) {
|
|
5948
6243
|
conns = /* @__PURE__ */ new Set();
|
|
5949
|
-
userConnections.set(
|
|
6244
|
+
userConnections.set(userId2, conns);
|
|
5950
6245
|
}
|
|
5951
6246
|
conns.add(ws);
|
|
5952
6247
|
}
|
|
5953
6248
|
function untrackConnection(ws) {
|
|
5954
|
-
for (const [
|
|
6249
|
+
for (const [userId2, conns] of userConnections) {
|
|
5955
6250
|
conns.delete(ws);
|
|
5956
|
-
if (conns.size === 0) userConnections.delete(
|
|
6251
|
+
if (conns.size === 0) userConnections.delete(userId2);
|
|
5957
6252
|
}
|
|
5958
6253
|
}
|
|
5959
6254
|
return {
|
|
5960
6255
|
handler: {
|
|
5961
6256
|
open(ws, ctx) {
|
|
5962
|
-
const
|
|
5963
|
-
if (!
|
|
6257
|
+
const userId2 = ctx.user?.id;
|
|
6258
|
+
if (!userId2) {
|
|
5964
6259
|
ws.close(4001, "Unauthorized");
|
|
5965
6260
|
return;
|
|
5966
6261
|
}
|
|
5967
6262
|
},
|
|
5968
6263
|
async message(ws, ctx, data) {
|
|
5969
|
-
const
|
|
5970
|
-
if (!
|
|
6264
|
+
const userId2 = ctx.user?.id;
|
|
6265
|
+
if (!userId2) return;
|
|
5971
6266
|
let msg;
|
|
5972
6267
|
try {
|
|
5973
6268
|
msg = JSON.parse(data.toString());
|
|
@@ -5981,12 +6276,12 @@ function createWSHandler(deps) {
|
|
|
5981
6276
|
if (!content || !channel_id) return;
|
|
5982
6277
|
const [row] = await sql2`
|
|
5983
6278
|
INSERT INTO "_messages" ("channel_id", "sender_id", "sender_type", "content")
|
|
5984
|
-
VALUES (${channel_id}, ${
|
|
6279
|
+
VALUES (${channel_id}, ${userId2}, 'user', ${content})
|
|
5985
6280
|
RETURNING *
|
|
5986
6281
|
`;
|
|
5987
6282
|
const message = row;
|
|
5988
6283
|
hub.join(`messager:${channel_id}`, ws);
|
|
5989
|
-
trackConnection(
|
|
6284
|
+
trackConnection(userId2, ws);
|
|
5990
6285
|
broadcastToChannel(hub, channel_id, { type: "message", data: message });
|
|
5991
6286
|
if (agents) {
|
|
5992
6287
|
const insertMsg = (data2) => sql2`
|
|
@@ -6005,7 +6300,7 @@ function createWSHandler(deps) {
|
|
|
6005
6300
|
broadcastToChannel(hub, channel_id, {
|
|
6006
6301
|
type: "typing",
|
|
6007
6302
|
channel_id,
|
|
6008
|
-
user_id:
|
|
6303
|
+
user_id: userId2,
|
|
6009
6304
|
is_typing: is_typing ?? false
|
|
6010
6305
|
});
|
|
6011
6306
|
break;
|
|
@@ -6016,12 +6311,12 @@ function createWSHandler(deps) {
|
|
|
6016
6311
|
await sql2`
|
|
6017
6312
|
UPDATE "_channel_members"
|
|
6018
6313
|
SET last_read_id = ${last_message_id}, last_read_at = NOW()
|
|
6019
|
-
WHERE channel_id = ${channel_id} AND member_id = ${
|
|
6314
|
+
WHERE channel_id = ${channel_id} AND member_id = ${userId2} AND member_type = 'user'
|
|
6020
6315
|
`;
|
|
6021
6316
|
broadcastToChannel(hub, channel_id, {
|
|
6022
6317
|
type: "read",
|
|
6023
6318
|
channel_id,
|
|
6024
|
-
user_id:
|
|
6319
|
+
user_id: userId2,
|
|
6025
6320
|
last_message_id
|
|
6026
6321
|
});
|
|
6027
6322
|
break;
|
|
@@ -6075,7 +6370,7 @@ function buildRouter3(deps) {
|
|
|
6075
6370
|
return Response.json(channel, { status: 201 });
|
|
6076
6371
|
});
|
|
6077
6372
|
r.get("/channels", async (_req, ctx) => {
|
|
6078
|
-
const
|
|
6373
|
+
const userId2 = ctx.user?.id ?? 1;
|
|
6079
6374
|
const rows = await sql2`
|
|
6080
6375
|
SELECT c.*, (
|
|
6081
6376
|
SELECT content FROM "_messages"
|
|
@@ -6084,7 +6379,7 @@ function buildRouter3(deps) {
|
|
|
6084
6379
|
) AS last_message
|
|
6085
6380
|
FROM "_channels" c
|
|
6086
6381
|
JOIN "_channel_members" m ON m.channel_id = c.id
|
|
6087
|
-
WHERE m.member_id = ${
|
|
6382
|
+
WHERE m.member_id = ${userId2} AND m.member_type = 'user'
|
|
6088
6383
|
ORDER BY c.created_at DESC
|
|
6089
6384
|
`;
|
|
6090
6385
|
return Response.json(rows);
|
|
@@ -6157,9 +6452,9 @@ function buildRouter3(deps) {
|
|
|
6157
6452
|
r.post("/channels/:id/read", async (req, ctx) => {
|
|
6158
6453
|
const channelId = parseInt(ctx.params.id, 10);
|
|
6159
6454
|
const body = await req.json();
|
|
6160
|
-
const
|
|
6455
|
+
const userId2 = body.user_id ?? ctx.user?.id ?? 1;
|
|
6161
6456
|
await members.updateMany(
|
|
6162
|
-
[eq("channel_id", channelId), eq("member_id",
|
|
6457
|
+
[eq("channel_id", channelId), eq("member_id", userId2), eq("member_type", "user")],
|
|
6163
6458
|
{ last_read_id: body.last_message_id }
|
|
6164
6459
|
);
|
|
6165
6460
|
return Response.json({ ok: true });
|
|
@@ -6559,12 +6854,24 @@ async function deploy(config) {
|
|
|
6559
6854
|
if (ac.ports && old?.process) {
|
|
6560
6855
|
targetPort = old.currentPort === ac.ports[0] ? ac.ports[1] : ac.ports[0];
|
|
6561
6856
|
}
|
|
6562
|
-
const mp = await forkAndCheck(
|
|
6857
|
+
const mp = await forkAndCheck(
|
|
6858
|
+
name,
|
|
6859
|
+
appDir,
|
|
6860
|
+
ac.entry,
|
|
6861
|
+
targetPort,
|
|
6862
|
+
ac.env,
|
|
6863
|
+
log,
|
|
6864
|
+
ac.healthEndpoint
|
|
6865
|
+
);
|
|
6563
6866
|
if (!mp) {
|
|
6564
6867
|
log("[deploy] new process failed to start, keeping old running");
|
|
6565
6868
|
if (old?.process) apps.set(name, old);
|
|
6566
6869
|
else {
|
|
6567
|
-
setAppRuntime(name, ac, logs, {
|
|
6870
|
+
setAppRuntime(name, ac, logs, {
|
|
6871
|
+
status: "error",
|
|
6872
|
+
port: targetPort,
|
|
6873
|
+
error: "failed to start"
|
|
6874
|
+
});
|
|
6568
6875
|
}
|
|
6569
6876
|
return;
|
|
6570
6877
|
}
|
|
@@ -6607,7 +6914,7 @@ async function deploy(config) {
|
|
|
6607
6914
|
function setAppRuntime(name, ac, logs, overrides) {
|
|
6608
6915
|
apps.set(name, {
|
|
6609
6916
|
config: ac,
|
|
6610
|
-
status: { name, ...overrides },
|
|
6917
|
+
status: { name, status: "starting", port: 0, ...overrides },
|
|
6611
6918
|
logs,
|
|
6612
6919
|
process: null,
|
|
6613
6920
|
currentPort: overrides.port ?? ac.port ?? 0,
|
|
@@ -6808,7 +7115,7 @@ async function compileVendorBundle() {
|
|
|
6808
7115
|
if (vendorBundle) return vendorBundle;
|
|
6809
7116
|
if (!_userRequire) _userRequire = createRequire(join2(process.cwd(), "package.json"));
|
|
6810
7117
|
const modules = {
|
|
6811
|
-
|
|
7118
|
+
react: [],
|
|
6812
7119
|
"react-dom": ["react"],
|
|
6813
7120
|
"react-dom/client": ["react"],
|
|
6814
7121
|
"react/jsx-runtime": ["react"]
|
|
@@ -6845,10 +7152,12 @@ async function compileVendorBundle() {
|
|
|
6845
7152
|
const stmts = [""];
|
|
6846
7153
|
for (const [request, keys] of Object.entries(modules)) {
|
|
6847
7154
|
const unique = keys.filter((k) => !used.has(k) && used.add(k));
|
|
6848
|
-
if (unique.length > 0)
|
|
7155
|
+
if (unique.length > 0)
|
|
7156
|
+
stmts.push(`export { ${unique.join(", ")} } from ${JSON.stringify(request)};`);
|
|
6849
7157
|
}
|
|
6850
7158
|
const uidWfw = wfwKeys.filter((k) => !used.has(k) && used.add(k));
|
|
6851
|
-
if (uidWfw.length > 0)
|
|
7159
|
+
if (uidWfw.length > 0)
|
|
7160
|
+
stmts.push(`export { ${uidWfw.join(", ")} } from ${JSON.stringify(reactAbsPath)};`);
|
|
6852
7161
|
const result = await esbuild.build({
|
|
6853
7162
|
stdin: { contents: stmts.join("\n"), resolveDir: process.cwd() },
|
|
6854
7163
|
format: "esm",
|
|
@@ -6891,7 +7200,14 @@ async function compileBrowser(path2, outDir) {
|
|
|
6891
7200
|
jsx: "automatic",
|
|
6892
7201
|
jsxImportSource: "react",
|
|
6893
7202
|
bundle: true,
|
|
6894
|
-
external: [
|
|
7203
|
+
external: [
|
|
7204
|
+
"react",
|
|
7205
|
+
"react-dom",
|
|
7206
|
+
"react-dom/client",
|
|
7207
|
+
"react/jsx-runtime",
|
|
7208
|
+
"weifuwu",
|
|
7209
|
+
"weifuwu/react"
|
|
7210
|
+
],
|
|
6895
7211
|
plugins: [plugin],
|
|
6896
7212
|
write: true,
|
|
6897
7213
|
allowOverwrite: true
|
|
@@ -6925,7 +7241,14 @@ async function compileHotComponent(path2) {
|
|
|
6925
7241
|
jsx: "automatic",
|
|
6926
7242
|
jsxImportSource: "react",
|
|
6927
7243
|
bundle: true,
|
|
6928
|
-
external: [
|
|
7244
|
+
external: [
|
|
7245
|
+
"react",
|
|
7246
|
+
"react-dom",
|
|
7247
|
+
"react-dom/client",
|
|
7248
|
+
"react/jsx-runtime",
|
|
7249
|
+
"weifuwu",
|
|
7250
|
+
"weifuwu/react"
|
|
7251
|
+
],
|
|
6929
7252
|
plugins: [plugin],
|
|
6930
7253
|
write: false
|
|
6931
7254
|
});
|
|
@@ -7129,7 +7452,7 @@ function tailwindRouter(dir) {
|
|
|
7129
7452
|
const cssDir = resolve4(dir);
|
|
7130
7453
|
const cssPath = join3(cssDir, "app", "globals.css");
|
|
7131
7454
|
const r = new Router();
|
|
7132
|
-
r.get("/__wfw/style/:hash.css", async (
|
|
7455
|
+
r.get("/__wfw/style/:hash.css", async (_req, _ctx) => {
|
|
7133
7456
|
if (!cssCache.has(cssPath)) {
|
|
7134
7457
|
await compileTailwindCss(cssPath, cssDir);
|
|
7135
7458
|
}
|
|
@@ -7213,7 +7536,7 @@ function liveWs() {
|
|
|
7213
7536
|
}
|
|
7214
7537
|
};
|
|
7215
7538
|
}
|
|
7216
|
-
function liveRouter(
|
|
7539
|
+
function liveRouter(_dir) {
|
|
7217
7540
|
const r = new Router();
|
|
7218
7541
|
compileVendorBundle().catch(() => {
|
|
7219
7542
|
});
|
|
@@ -7349,8 +7672,9 @@ function errorBoundary(errorPath) {
|
|
|
7349
7672
|
const mod = await compile(errorPath);
|
|
7350
7673
|
const ErrorComponent = mod.default;
|
|
7351
7674
|
if (!ErrorComponent) throw err;
|
|
7352
|
-
const
|
|
7353
|
-
const
|
|
7675
|
+
const ctx2 = ctx;
|
|
7676
|
+
const layouts = (ctx2.layoutStack || []).map((l) => l.component);
|
|
7677
|
+
const base = (ctx2.mountPath || "").replace(/\/$/, "");
|
|
7354
7678
|
let element = createElement2(ErrorComponent, {
|
|
7355
7679
|
error: err instanceof Error ? err : new Error(String(err)),
|
|
7356
7680
|
reset: () => {
|
|
@@ -7360,10 +7684,10 @@ function errorBoundary(errorPath) {
|
|
|
7360
7684
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
7361
7685
|
const stream = await renderToReadableStream(element);
|
|
7362
7686
|
return streamResponse(stream, {
|
|
7363
|
-
ctx,
|
|
7687
|
+
ctx: ctx2,
|
|
7364
7688
|
base,
|
|
7365
7689
|
isDev: isDev(),
|
|
7366
|
-
tailwind:
|
|
7690
|
+
tailwind: ctx2.tailwind,
|
|
7367
7691
|
status: 500
|
|
7368
7692
|
});
|
|
7369
7693
|
}
|
|
@@ -7484,7 +7808,13 @@ async function resolveRoute(ssrDir, segments, routeCache) {
|
|
|
7484
7808
|
if (d === appDir) break;
|
|
7485
7809
|
d = dirname3(d);
|
|
7486
7810
|
}
|
|
7487
|
-
const result = {
|
|
7811
|
+
const result = {
|
|
7812
|
+
routePath: "/" + routeParams.join("/"),
|
|
7813
|
+
pageFile,
|
|
7814
|
+
layoutFiles,
|
|
7815
|
+
errorFiles,
|
|
7816
|
+
notFoundFile
|
|
7817
|
+
};
|
|
7488
7818
|
routeCache.set(cacheKey, result);
|
|
7489
7819
|
return result;
|
|
7490
7820
|
}
|
|
@@ -7541,7 +7871,6 @@ function renderPage(pageFile, outDir) {
|
|
|
7541
7871
|
const absPath = resolve6(pageFile);
|
|
7542
7872
|
const entryId = hashId(absPath);
|
|
7543
7873
|
ssrEntries.set(entryId, { path: absPath });
|
|
7544
|
-
const bundleKey = `/__ssr/${entryId}.js`;
|
|
7545
7874
|
return async (req, ctx) => {
|
|
7546
7875
|
let pageMod;
|
|
7547
7876
|
try {
|
|
@@ -7555,7 +7884,6 @@ function renderPage(pageFile, outDir) {
|
|
|
7555
7884
|
if (!Component) return errorPage("Missing default export", pageFile);
|
|
7556
7885
|
const layouts = ctx.layoutStack || [];
|
|
7557
7886
|
const layoutComponents = layouts.map((l) => l.component);
|
|
7558
|
-
const layoutPaths = layouts.map((l) => l.path);
|
|
7559
7887
|
const base = (ctx.mountPath || "").replace(/\/$/, "");
|
|
7560
7888
|
const loaderData = serializeLoaderData(ctx);
|
|
7561
7889
|
const ctxValue = {
|
|
@@ -7575,22 +7903,22 @@ function renderPage(pageFile, outDir) {
|
|
|
7575
7903
|
let element = createElement3(
|
|
7576
7904
|
"div",
|
|
7577
7905
|
{ id: "__weifuwu_root" },
|
|
7578
|
-
createElement3(
|
|
7579
|
-
TsxContext.Provider,
|
|
7580
|
-
{ value: ctxValue },
|
|
7581
|
-
createElement3(Component, null)
|
|
7582
|
-
)
|
|
7906
|
+
createElement3(TsxContext.Provider, { value: ctxValue }, createElement3(Component, null))
|
|
7583
7907
|
);
|
|
7584
7908
|
element = buildHtmlShell("weifuwu", element, layoutComponents);
|
|
7585
7909
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
7586
7910
|
const stream = await renderToReadableStream(element);
|
|
7587
|
-
return streamResponse(
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7911
|
+
return streamResponse(
|
|
7912
|
+
stream,
|
|
7913
|
+
{
|
|
7914
|
+
ctx,
|
|
7915
|
+
base,
|
|
7916
|
+
isDev: isDev2,
|
|
7917
|
+
loaderData,
|
|
7918
|
+
tailwind: ctx.tailwind
|
|
7919
|
+
},
|
|
7920
|
+
buildHydrationScript(entryId, JSON.stringify(ctxValue), base)
|
|
7921
|
+
);
|
|
7594
7922
|
});
|
|
7595
7923
|
};
|
|
7596
7924
|
}
|
|
@@ -7674,13 +8002,16 @@ function ssr(opts) {
|
|
|
7674
8002
|
if (!resolved) {
|
|
7675
8003
|
if (isDev2) {
|
|
7676
8004
|
const pages = discoverRoutes(dir).map((p) => p.path).sort();
|
|
7677
|
-
return Response.json(
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
8005
|
+
return Response.json(
|
|
8006
|
+
{
|
|
8007
|
+
error: "Not Found",
|
|
8008
|
+
path: "/" + segments.join("/"),
|
|
8009
|
+
method: req.method,
|
|
8010
|
+
hint: "Available SSR pages",
|
|
8011
|
+
pages
|
|
8012
|
+
},
|
|
8013
|
+
{ status: 404 }
|
|
8014
|
+
);
|
|
7684
8015
|
}
|
|
7685
8016
|
return new Response("Not Found", { status: 404 });
|
|
7686
8017
|
}
|
|
@@ -7743,10 +8074,14 @@ async function getSession(sql2, id2) {
|
|
|
7743
8074
|
const { data: rows } = await sessions.readMany(sql2, { id: id2, active: true });
|
|
7744
8075
|
return rows[0] ?? null;
|
|
7745
8076
|
}
|
|
7746
|
-
async function listSessions(sql2,
|
|
8077
|
+
async function listSessions(sql2, userId2) {
|
|
7747
8078
|
const opts = { orderBy: { updated_at: "desc" } };
|
|
7748
|
-
if (
|
|
7749
|
-
const { data: rows2 } = await sessions.readMany(
|
|
8079
|
+
if (userId2 !== void 0) {
|
|
8080
|
+
const { data: rows2 } = await sessions.readMany(
|
|
8081
|
+
sql2,
|
|
8082
|
+
{ user_id: userId2, active: true },
|
|
8083
|
+
opts
|
|
8084
|
+
);
|
|
7750
8085
|
return rows2;
|
|
7751
8086
|
}
|
|
7752
8087
|
const { data: rows } = await sessions.readMany(sql2, { active: true }, opts);
|
|
@@ -7825,7 +8160,14 @@ async function* executeGenerator(opts) {
|
|
|
7825
8160
|
totalTokens: result2.usage?.totalTokens ?? 0
|
|
7826
8161
|
};
|
|
7827
8162
|
try {
|
|
7828
|
-
await addTextMessage(
|
|
8163
|
+
await addTextMessage(
|
|
8164
|
+
sql2,
|
|
8165
|
+
sessionId,
|
|
8166
|
+
"assistant",
|
|
8167
|
+
currentAssistantText,
|
|
8168
|
+
currentUsage.promptTokens,
|
|
8169
|
+
currentUsage.completionTokens
|
|
8170
|
+
);
|
|
7829
8171
|
} catch (e) {
|
|
7830
8172
|
console.error("[opencode] save message failed:", e);
|
|
7831
8173
|
}
|
|
@@ -7868,11 +8210,7 @@ var DENIED_COMMANDS = [
|
|
|
7868
8210
|
/^:\(\)\{.*\}:;$/,
|
|
7869
8211
|
/^fork\s+bomb/i
|
|
7870
8212
|
];
|
|
7871
|
-
var DENIED_PATHS = [
|
|
7872
|
-
/\/\.env$/,
|
|
7873
|
-
/\/\.env\.\w+$/,
|
|
7874
|
-
/\/node_modules\//
|
|
7875
|
-
];
|
|
8213
|
+
var DENIED_PATHS = [/\/\.env$/, /\/\.env\.\w+$/, /\/node_modules\//];
|
|
7876
8214
|
function isCommandAllowed(command) {
|
|
7877
8215
|
const trimmed = command.trim();
|
|
7878
8216
|
for (const re of DENIED_COMMANDS) {
|
|
@@ -7880,7 +8218,7 @@ function isCommandAllowed(command) {
|
|
|
7880
8218
|
}
|
|
7881
8219
|
return true;
|
|
7882
8220
|
}
|
|
7883
|
-
function isPathAllowed(resolvedPath, workspace,
|
|
8221
|
+
function isPathAllowed(resolvedPath, workspace, _perms) {
|
|
7884
8222
|
if (!resolvedPath.startsWith(workspace)) return false;
|
|
7885
8223
|
for (const re of DENIED_PATHS) {
|
|
7886
8224
|
if (re.test(resolvedPath)) return false;
|
|
@@ -7935,16 +8273,20 @@ function createBashTool(ctx) {
|
|
|
7935
8273
|
}
|
|
7936
8274
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
7937
8275
|
return new Promise((resolve14) => {
|
|
7938
|
-
const child = exec(
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
8276
|
+
const child = exec(
|
|
8277
|
+
command,
|
|
8278
|
+
{ cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 },
|
|
8279
|
+
(error, stdout, stderr) => {
|
|
8280
|
+
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
8281
|
+
resolve14({
|
|
8282
|
+
stdout: stdout.slice(0, 1e6),
|
|
8283
|
+
stderr: stderr.slice(0, 1e6),
|
|
8284
|
+
exitCode: error?.code ?? 0,
|
|
8285
|
+
signal: error?.signal ?? null,
|
|
8286
|
+
truncated
|
|
8287
|
+
});
|
|
8288
|
+
}
|
|
8289
|
+
);
|
|
7948
8290
|
});
|
|
7949
8291
|
}
|
|
7950
8292
|
});
|
|
@@ -8051,7 +8393,10 @@ function createEditTool(ctx) {
|
|
|
8051
8393
|
}
|
|
8052
8394
|
const secondIdx = content.indexOf(oldString, firstIdx + 1);
|
|
8053
8395
|
if (secondIdx !== -1) {
|
|
8054
|
-
return {
|
|
8396
|
+
return {
|
|
8397
|
+
error: "Found multiple matches. Provide more surrounding context in oldString.",
|
|
8398
|
+
replaced: 0
|
|
8399
|
+
};
|
|
8055
8400
|
}
|
|
8056
8401
|
const result = content.replace(oldString, newString);
|
|
8057
8402
|
writeFileSync3(resolved, result, "utf-8");
|
|
@@ -8093,7 +8438,11 @@ function createGrepTool(ctx) {
|
|
|
8093
8438
|
stdout = execFileSync("grep", args, { timeout: 15e3, maxBuffer: 1024 * 1024 }).toString();
|
|
8094
8439
|
}
|
|
8095
8440
|
const lines = stdout.split("\n").filter(Boolean);
|
|
8096
|
-
return {
|
|
8441
|
+
return {
|
|
8442
|
+
matches: lines.length,
|
|
8443
|
+
results: lines.slice(0, 200),
|
|
8444
|
+
truncated: lines.length > 200
|
|
8445
|
+
};
|
|
8097
8446
|
} catch (e) {
|
|
8098
8447
|
if (e.status === 1) {
|
|
8099
8448
|
return { matches: 0, results: [], truncated: false };
|
|
@@ -8119,19 +8468,20 @@ function createGlobTool(ctx) {
|
|
|
8119
8468
|
execute: async ({ pattern, path: path2 }) => {
|
|
8120
8469
|
const searchDir = path2 ? resolve11(ctx.workspace, path2) : ctx.workspace;
|
|
8121
8470
|
try {
|
|
8122
|
-
const stdout = execFileSync2(
|
|
8123
|
-
|
|
8124
|
-
"-name",
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
timeout: 1e4,
|
|
8131
|
-
maxBuffer: 1024 * 1024
|
|
8132
|
-
}).toString();
|
|
8471
|
+
const stdout = execFileSync2(
|
|
8472
|
+
"find",
|
|
8473
|
+
[searchDir, "-name", pattern, "-not", "-path", "*/node_modules/*"],
|
|
8474
|
+
{
|
|
8475
|
+
timeout: 1e4,
|
|
8476
|
+
maxBuffer: 1024 * 1024
|
|
8477
|
+
}
|
|
8478
|
+
).toString();
|
|
8133
8479
|
const lines = stdout.split("\n").filter(Boolean).slice(0, 200);
|
|
8134
|
-
return {
|
|
8480
|
+
return {
|
|
8481
|
+
files: lines,
|
|
8482
|
+
total: lines.length,
|
|
8483
|
+
truncated: stdout.split("\n").filter(Boolean).length > 200
|
|
8484
|
+
};
|
|
8135
8485
|
} catch {
|
|
8136
8486
|
return { files: [], total: 0, truncated: false };
|
|
8137
8487
|
}
|
|
@@ -8142,7 +8492,7 @@ function createGlobTool(ctx) {
|
|
|
8142
8492
|
// opencode/tools/web.ts
|
|
8143
8493
|
import { tool as tool9 } from "ai";
|
|
8144
8494
|
import { z as z11 } from "zod";
|
|
8145
|
-
function createWebTool(
|
|
8495
|
+
function createWebTool(_ctx) {
|
|
8146
8496
|
return tool9({
|
|
8147
8497
|
description: "Fetch a URL and return the content as text.",
|
|
8148
8498
|
inputSchema: z11.object({
|
|
@@ -8271,15 +8621,29 @@ function createTools(ctx) {
|
|
|
8271
8621
|
|
|
8272
8622
|
// opencode/rest.ts
|
|
8273
8623
|
async function buildRouter4(deps) {
|
|
8274
|
-
const {
|
|
8624
|
+
const {
|
|
8625
|
+
sql: sql2,
|
|
8626
|
+
model,
|
|
8627
|
+
workspace,
|
|
8628
|
+
systemPrompt,
|
|
8629
|
+
skills,
|
|
8630
|
+
skillsRegistry,
|
|
8631
|
+
permissions: permissions2,
|
|
8632
|
+
pendingQuestions
|
|
8633
|
+
} = deps;
|
|
8275
8634
|
const router = new Router();
|
|
8276
8635
|
router.post("/sessions", async (req, ctx) => {
|
|
8277
8636
|
const body = await req.json().catch(() => ({}));
|
|
8278
|
-
const session2 = await createSession(
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8637
|
+
const session2 = await createSession(
|
|
8638
|
+
sql2,
|
|
8639
|
+
{
|
|
8640
|
+
title: body.title,
|
|
8641
|
+
model: body.model,
|
|
8642
|
+
systemPrompt: body.systemPrompt || systemPrompt
|
|
8643
|
+
},
|
|
8644
|
+
workspace,
|
|
8645
|
+
ctx.mountPath || ""
|
|
8646
|
+
);
|
|
8283
8647
|
return Response.json(session2, { status: 201 });
|
|
8284
8648
|
});
|
|
8285
8649
|
router.get("/sessions", async () => {
|
|
@@ -8343,7 +8707,11 @@ async function buildRouter4(deps) {
|
|
|
8343
8707
|
FROM "_opencode_messages"
|
|
8344
8708
|
WHERE session_id = ${sessionId}
|
|
8345
8709
|
`;
|
|
8346
|
-
const stats = rows[0] || {
|
|
8710
|
+
const stats = rows[0] || {
|
|
8711
|
+
message_count: 0,
|
|
8712
|
+
total_tokens_in: 0,
|
|
8713
|
+
total_tokens_out: 0
|
|
8714
|
+
};
|
|
8347
8715
|
return Response.json({
|
|
8348
8716
|
session_id: sessionId,
|
|
8349
8717
|
message_count: stats.message_count,
|
|
@@ -8364,12 +8732,21 @@ async function buildRouter4(deps) {
|
|
|
8364
8732
|
// opencode/ws.ts
|
|
8365
8733
|
var clients2 = /* @__PURE__ */ new WeakMap();
|
|
8366
8734
|
function createWSHandler2(deps) {
|
|
8367
|
-
const {
|
|
8735
|
+
const {
|
|
8736
|
+
sql: sql2,
|
|
8737
|
+
model,
|
|
8738
|
+
workspace,
|
|
8739
|
+
systemPrompt,
|
|
8740
|
+
skills,
|
|
8741
|
+
skillsRegistry,
|
|
8742
|
+
permissions: permissions2,
|
|
8743
|
+
pendingQuestions
|
|
8744
|
+
} = deps;
|
|
8368
8745
|
return {
|
|
8369
8746
|
open(ws, ctx) {
|
|
8370
|
-
const
|
|
8747
|
+
const userId2 = ctx.user?.id ?? 0;
|
|
8371
8748
|
const mountPath = ctx.mountPath ?? "";
|
|
8372
|
-
clients2.set(ws, { userId, mountPath });
|
|
8749
|
+
clients2.set(ws, { userId: userId2, mountPath });
|
|
8373
8750
|
},
|
|
8374
8751
|
async message(ws, ctx, data) {
|
|
8375
8752
|
const client = clients2.get(ws);
|
|
@@ -8384,12 +8761,17 @@ function createWSHandler2(deps) {
|
|
|
8384
8761
|
switch (msg.type) {
|
|
8385
8762
|
case "create": {
|
|
8386
8763
|
try {
|
|
8387
|
-
const session2 = await createSession(
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8764
|
+
const session2 = await createSession(
|
|
8765
|
+
sql2,
|
|
8766
|
+
{
|
|
8767
|
+
userId: client.userId,
|
|
8768
|
+
title: msg.title,
|
|
8769
|
+
model: msg.model,
|
|
8770
|
+
systemPrompt: msg.systemPrompt || systemPrompt
|
|
8771
|
+
},
|
|
8772
|
+
workspace,
|
|
8773
|
+
client.mountPath
|
|
8774
|
+
);
|
|
8393
8775
|
ws.send(JSON.stringify({ type: "session_created", session: session2 }));
|
|
8394
8776
|
} catch (e) {
|
|
8395
8777
|
ws.send(JSON.stringify({ type: "error", error: e.message }));
|
|
@@ -8486,8 +8868,7 @@ function createWSHandler2(deps) {
|
|
|
8486
8868
|
}
|
|
8487
8869
|
|
|
8488
8870
|
// opencode/skills.ts
|
|
8489
|
-
import { readFile } from "node:fs/promises";
|
|
8490
|
-
import { glob } from "node:fs/promises";
|
|
8871
|
+
import { readFile, glob } from "node:fs/promises";
|
|
8491
8872
|
import { homedir } from "node:os";
|
|
8492
8873
|
import { resolve as resolve12 } from "node:path";
|
|
8493
8874
|
import { parse as parseYaml } from "yaml";
|
|
@@ -8584,7 +8965,16 @@ async function opencode(options) {
|
|
|
8584
8965
|
const model = provider.chat(modelName);
|
|
8585
8966
|
const pendingQuestions = /* @__PURE__ */ new Map();
|
|
8586
8967
|
const base = new PgModule(pg);
|
|
8587
|
-
const r = await buildRouter4({
|
|
8968
|
+
const r = await buildRouter4({
|
|
8969
|
+
sql: sql2,
|
|
8970
|
+
model,
|
|
8971
|
+
workspace,
|
|
8972
|
+
systemPrompt,
|
|
8973
|
+
skills: manualSkills,
|
|
8974
|
+
skillsRegistry,
|
|
8975
|
+
permissions: permissions2,
|
|
8976
|
+
pendingQuestions
|
|
8977
|
+
});
|
|
8588
8978
|
const mod = r;
|
|
8589
8979
|
mod.migrate = async () => {
|
|
8590
8980
|
const sessions2 = pg.table("_opencode_sessions", {
|
|
@@ -8617,7 +9007,16 @@ async function opencode(options) {
|
|
|
8617
9007
|
await messages2.create();
|
|
8618
9008
|
await messages2.createIndex(["session_id", "created_at"]);
|
|
8619
9009
|
};
|
|
8620
|
-
mod.wsHandler = () => createWSHandler2({
|
|
9010
|
+
mod.wsHandler = () => createWSHandler2({
|
|
9011
|
+
sql: sql2,
|
|
9012
|
+
model,
|
|
9013
|
+
workspace,
|
|
9014
|
+
systemPrompt,
|
|
9015
|
+
skills: manualSkills,
|
|
9016
|
+
skillsRegistry,
|
|
9017
|
+
permissions: permissions2,
|
|
9018
|
+
pendingQuestions
|
|
9019
|
+
});
|
|
8621
9020
|
mod.close = () => base.close();
|
|
8622
9021
|
return mod;
|
|
8623
9022
|
}
|
|
@@ -8726,7 +9125,10 @@ var MemStore = class {
|
|
|
8726
9125
|
daily,
|
|
8727
9126
|
top_pages: topPages,
|
|
8728
9127
|
referrers: [...refMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([domain, count]) => ({ domain, count })),
|
|
8729
|
-
devices: {
|
|
9128
|
+
devices: {
|
|
9129
|
+
mobile: Math.round(totalMobile / total * 1e3) / 10,
|
|
9130
|
+
desktop: Math.round(totalDesktop / total * 1e3) / 10
|
|
9131
|
+
}
|
|
8730
9132
|
};
|
|
8731
9133
|
}
|
|
8732
9134
|
};
|
|
@@ -8779,7 +9181,10 @@ async function queryPg(sql2, days) {
|
|
|
8779
9181
|
daily: daily.map((d) => ({ date: d.date, pv: d.pv, uv: d.uv })),
|
|
8780
9182
|
top_pages: pageRows.map((p) => ({ path: p.path, pv: p.pv })),
|
|
8781
9183
|
referrers: [],
|
|
8782
|
-
devices: {
|
|
9184
|
+
devices: {
|
|
9185
|
+
mobile: Math.round(t.total_mobile / denom * 1e3) / 10,
|
|
9186
|
+
desktop: Math.round(t.total_desktop / denom * 1e3) / 10
|
|
9187
|
+
}
|
|
8783
9188
|
};
|
|
8784
9189
|
}
|
|
8785
9190
|
function escapeHtml2(s) {
|
|
@@ -8794,9 +9199,7 @@ function renderDashboard(days, data) {
|
|
|
8794
9199
|
const rows = top_pages.map(
|
|
8795
9200
|
(p, i) => `<tr><td class="num">${i + 1}</td><td class="path">${escapeHtml2(p.path)}</td><td class="num">${p.pv}</td></tr>`
|
|
8796
9201
|
).join("");
|
|
8797
|
-
const refRows = referrers.map(
|
|
8798
|
-
(r) => `<tr><td>${escapeHtml2(r.domain)}</td><td class="num">${r.count}</td></tr>`
|
|
8799
|
-
).join("");
|
|
9202
|
+
const refRows = referrers.map((r) => `<tr><td>${escapeHtml2(r.domain)}</td><td class="num">${r.count}</td></tr>`).join("");
|
|
8800
9203
|
return `<!DOCTYPE html><html lang="en">
|
|
8801
9204
|
<head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Analytics - weifuwu</title>
|
|
8802
9205
|
<style>*,:before,:after{box-sizing:border-box;margin:0;padding:0}
|
|
@@ -8878,7 +9281,7 @@ function analytics(options) {
|
|
|
8878
9281
|
if (pg) await migratePg(pg.sql, pg.table);
|
|
8879
9282
|
};
|
|
8880
9283
|
const close = async () => {
|
|
8881
|
-
|
|
9284
|
+
store2?.stopCleanup();
|
|
8882
9285
|
};
|
|
8883
9286
|
const mod = r;
|
|
8884
9287
|
mod.middleware = middleware;
|
|
@@ -9003,7 +9406,10 @@ function i18n(options) {
|
|
|
9003
9406
|
set: (value, loc) => {
|
|
9004
9407
|
const cookie = `${opts.cookie}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`;
|
|
9005
9408
|
const location = loc ?? (req.headers.get("referer") || "/");
|
|
9006
|
-
return new Response(null, {
|
|
9409
|
+
return new Response(null, {
|
|
9410
|
+
status: 302,
|
|
9411
|
+
headers: { Location: location, "Set-Cookie": cookie }
|
|
9412
|
+
});
|
|
9007
9413
|
}
|
|
9008
9414
|
};
|
|
9009
9415
|
return next(req, ctx);
|
|
@@ -9022,7 +9428,11 @@ function i18n(options) {
|
|
|
9022
9428
|
const accept = req.headers.get("accept") ?? "";
|
|
9023
9429
|
if (accept.includes("application/json")) {
|
|
9024
9430
|
return Response.json(
|
|
9025
|
-
{
|
|
9431
|
+
{
|
|
9432
|
+
ok: true,
|
|
9433
|
+
locale: value,
|
|
9434
|
+
messages: Object.keys(messages2).length > 0 ? messages2 : void 0
|
|
9435
|
+
},
|
|
9026
9436
|
{ headers: { "Set-Cookie": cookie } }
|
|
9027
9437
|
);
|
|
9028
9438
|
}
|
|
@@ -9340,10 +9750,11 @@ function listHandler(entries) {
|
|
|
9340
9750
|
if (before) conditions.push(lt("created_at", before));
|
|
9341
9751
|
const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
|
|
9342
9752
|
const offset = parseInt(url.searchParams.get("offset") ?? "0", 10);
|
|
9343
|
-
const { count, data } = await entries.readMany(
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9753
|
+
const { count, data } = await entries.readMany(conditions.length > 0 ? conditions : void 0, {
|
|
9754
|
+
orderBy: { created_at: "desc" },
|
|
9755
|
+
limit,
|
|
9756
|
+
offset
|
|
9757
|
+
});
|
|
9347
9758
|
return Response.json({ entries: data.map(parseMetadata), total: count });
|
|
9348
9759
|
};
|
|
9349
9760
|
}
|
|
@@ -9454,12 +9865,15 @@ import crypto8 from "node:crypto";
|
|
|
9454
9865
|
|
|
9455
9866
|
// iii/stream.ts
|
|
9456
9867
|
function notify(channels, stream, group, item, event, data) {
|
|
9457
|
-
const keys = [
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9868
|
+
const keys = [`${stream}`, `${stream}:${group}`, `${stream}:${group}:${item}`];
|
|
9869
|
+
const msg = JSON.stringify({
|
|
9870
|
+
type: "stream",
|
|
9871
|
+
stream_name: stream,
|
|
9872
|
+
group_id: group,
|
|
9873
|
+
item_id: item,
|
|
9874
|
+
event,
|
|
9875
|
+
data
|
|
9876
|
+
});
|
|
9463
9877
|
for (const key of keys) {
|
|
9464
9878
|
const subs = channels.get(key);
|
|
9465
9879
|
if (!subs) continue;
|
|
@@ -9678,10 +10092,13 @@ function createRedisStore(channels, redis2, ttl) {
|
|
|
9678
10092
|
async set(stream, group, item, data) {
|
|
9679
10093
|
const hk = hashKey(stream, group);
|
|
9680
10094
|
const oldRaw = await redis2.hget(hk, item);
|
|
9681
|
-
|
|
10095
|
+
const old = oldRaw ? JSON.parse(oldRaw) : null;
|
|
9682
10096
|
await redis2.hset(hk, item, JSON.stringify(data));
|
|
9683
10097
|
setTTL(hk);
|
|
9684
|
-
await redis2.publish(
|
|
10098
|
+
await redis2.publish(
|
|
10099
|
+
`iii:stream:${stream}`,
|
|
10100
|
+
JSON.stringify({ event: "set", group, item, data })
|
|
10101
|
+
);
|
|
9685
10102
|
notify(channels, stream, group, item, "set", data);
|
|
9686
10103
|
return { old_value: old, new_value: deepClone(data) };
|
|
9687
10104
|
},
|
|
@@ -9757,7 +10174,10 @@ function createRedisStore(channels, redis2, ttl) {
|
|
|
9757
10174
|
const newVal = applyOps(old, ops);
|
|
9758
10175
|
await redis2.hset(hk, item, JSON.stringify(newVal));
|
|
9759
10176
|
setTTL(hk);
|
|
9760
|
-
await redis2.publish(
|
|
10177
|
+
await redis2.publish(
|
|
10178
|
+
`iii:stream:${stream}`,
|
|
10179
|
+
JSON.stringify({ event: "update", group, item, data: newVal })
|
|
10180
|
+
);
|
|
9761
10181
|
notify(channels, stream, group, item, "update", newVal);
|
|
9762
10182
|
return { old_value: old, new_value: deepClone(newVal) };
|
|
9763
10183
|
}
|
|
@@ -9837,10 +10257,7 @@ function createWsHandler(deps) {
|
|
|
9837
10257
|
}
|
|
9838
10258
|
switch (msg.type) {
|
|
9839
10259
|
case "register_worker": {
|
|
9840
|
-
const workerId = deps.registerRemoteWorker(
|
|
9841
|
-
ws,
|
|
9842
|
-
msg.worker_name || `remote-${Date.now()}`
|
|
9843
|
-
);
|
|
10260
|
+
const workerId = deps.registerRemoteWorker(ws, msg.worker_name || `remote-${Date.now()}`);
|
|
9844
10261
|
wsToWorkerId.set(ws, workerId);
|
|
9845
10262
|
ws.send(JSON.stringify({ type: "registered", worker_id: workerId }));
|
|
9846
10263
|
break;
|
|
@@ -9944,10 +10361,7 @@ function buildRouter5(engine, wsHandler) {
|
|
|
9944
10361
|
}
|
|
9945
10362
|
return Response.json(result);
|
|
9946
10363
|
} catch (err) {
|
|
9947
|
-
return Response.json(
|
|
9948
|
-
{ error: err.message || "Internal error" },
|
|
9949
|
-
{ status: 500 }
|
|
9950
|
-
);
|
|
10364
|
+
return Response.json({ error: err.message || "Internal error" }, { status: 500 });
|
|
9951
10365
|
}
|
|
9952
10366
|
});
|
|
9953
10367
|
r.ws("/worker", wsHandler);
|
|
@@ -9970,14 +10384,23 @@ function iii(opts = {}) {
|
|
|
9970
10384
|
triggers: []
|
|
9971
10385
|
});
|
|
9972
10386
|
}
|
|
9973
|
-
registerBuiltin(
|
|
10387
|
+
registerBuiltin(
|
|
10388
|
+
"stream::set",
|
|
10389
|
+
(p) => stream.set(p.stream_name, p.group_id, p.item_id, p.data)
|
|
10390
|
+
);
|
|
9974
10391
|
registerBuiltin("stream::get", (p) => stream.get(p.stream_name, p.group_id, p.item_id));
|
|
9975
10392
|
registerBuiltin("stream::delete", (p) => stream.delete(p.stream_name, p.group_id, p.item_id));
|
|
9976
10393
|
registerBuiltin("stream::list", (p) => stream.list(p.stream_name, p.group_id));
|
|
9977
10394
|
registerBuiltin("stream::list_groups", (p) => stream.list_groups(p.stream_name));
|
|
9978
10395
|
registerBuiltin("stream::list_all", () => stream.list_all());
|
|
9979
|
-
registerBuiltin(
|
|
9980
|
-
|
|
10396
|
+
registerBuiltin(
|
|
10397
|
+
"stream::send",
|
|
10398
|
+
(p) => stream.send(p.stream_name, p.group_id, p.type, p.data, p.id)
|
|
10399
|
+
);
|
|
10400
|
+
registerBuiltin(
|
|
10401
|
+
"stream::update",
|
|
10402
|
+
(p) => stream.update(p.stream_name, p.group_id, p.item_id, p.ops)
|
|
10403
|
+
);
|
|
9981
10404
|
function addLocalWorker(worker) {
|
|
9982
10405
|
const workerId = crypto8.randomUUID();
|
|
9983
10406
|
const reg = {
|
|
@@ -10030,12 +10453,14 @@ function iii(opts = {}) {
|
|
|
10030
10453
|
reject(new Error(`Invocation timed out for "${id2}"`));
|
|
10031
10454
|
}, 3e4);
|
|
10032
10455
|
pending.set(invocationId, { resolve: resolve14, reject, timer });
|
|
10033
|
-
worker.ws.send(
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10456
|
+
worker.ws.send(
|
|
10457
|
+
JSON.stringify({
|
|
10458
|
+
type: "invoke",
|
|
10459
|
+
invocation_id: invocationId,
|
|
10460
|
+
function_id: id2,
|
|
10461
|
+
payload
|
|
10462
|
+
})
|
|
10463
|
+
);
|
|
10039
10464
|
});
|
|
10040
10465
|
};
|
|
10041
10466
|
const fnReg = {
|
|
@@ -10119,18 +10544,26 @@ function iii(opts = {}) {
|
|
|
10119
10544
|
handleInvoke(ws, invocationId, functionId, payload) {
|
|
10120
10545
|
const fn = functions.get(functionId);
|
|
10121
10546
|
if (!fn) {
|
|
10122
|
-
ws.send(
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10547
|
+
ws.send(
|
|
10548
|
+
JSON.stringify({
|
|
10549
|
+
type: "invoke_error",
|
|
10550
|
+
invocation_id: invocationId,
|
|
10551
|
+
error: `Function "${functionId}" not found`
|
|
10552
|
+
})
|
|
10553
|
+
);
|
|
10127
10554
|
return;
|
|
10128
10555
|
}
|
|
10129
10556
|
const ctx = { engine: engineRef, functionId, workerName: fn.workerName };
|
|
10130
10557
|
Promise.resolve(fn.handler(payload, ctx)).then((result) => {
|
|
10131
10558
|
ws.send(JSON.stringify({ type: "invoke_result", invocation_id: invocationId, result }));
|
|
10132
10559
|
}).catch((err) => {
|
|
10133
|
-
ws.send(
|
|
10560
|
+
ws.send(
|
|
10561
|
+
JSON.stringify({
|
|
10562
|
+
type: "invoke_error",
|
|
10563
|
+
invocation_id: invocationId,
|
|
10564
|
+
error: err.message
|
|
10565
|
+
})
|
|
10566
|
+
);
|
|
10134
10567
|
});
|
|
10135
10568
|
}
|
|
10136
10569
|
});
|
|
@@ -10193,7 +10626,7 @@ function iii(opts = {}) {
|
|
|
10193
10626
|
mod.migrate = async () => {
|
|
10194
10627
|
await stream.migrate();
|
|
10195
10628
|
};
|
|
10196
|
-
mod.
|
|
10629
|
+
mod.close = async () => {
|
|
10197
10630
|
for (const [, p] of pending) {
|
|
10198
10631
|
clearTimeout(p.timer);
|
|
10199
10632
|
p.reject(new Error("Engine shutting down"));
|
|
@@ -10205,7 +10638,6 @@ function iii(opts = {}) {
|
|
|
10205
10638
|
triggers.clear();
|
|
10206
10639
|
await stream.close();
|
|
10207
10640
|
};
|
|
10208
|
-
mod.close = mod.shutdown;
|
|
10209
10641
|
return mod;
|
|
10210
10642
|
}
|
|
10211
10643
|
|
|
@@ -10295,25 +10727,31 @@ function registerWorker(url) {
|
|
|
10295
10727
|
case "invoke": {
|
|
10296
10728
|
const handler = handlers.get(msg.function_id);
|
|
10297
10729
|
if (!handler) {
|
|
10298
|
-
ws?.send(
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10730
|
+
ws?.send(
|
|
10731
|
+
JSON.stringify({
|
|
10732
|
+
type: "invoke_error",
|
|
10733
|
+
invocation_id: msg.invocation_id,
|
|
10734
|
+
error: `Function "${msg.function_id}" not found`
|
|
10735
|
+
})
|
|
10736
|
+
);
|
|
10303
10737
|
return;
|
|
10304
10738
|
}
|
|
10305
10739
|
Promise.resolve(handler(msg.payload, {})).then((result) => {
|
|
10306
|
-
ws?.send(
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10740
|
+
ws?.send(
|
|
10741
|
+
JSON.stringify({
|
|
10742
|
+
type: "invoke_result",
|
|
10743
|
+
invocation_id: msg.invocation_id,
|
|
10744
|
+
result
|
|
10745
|
+
})
|
|
10746
|
+
);
|
|
10311
10747
|
}).catch((err) => {
|
|
10312
|
-
ws?.send(
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10748
|
+
ws?.send(
|
|
10749
|
+
JSON.stringify({
|
|
10750
|
+
type: "invoke_error",
|
|
10751
|
+
invocation_id: msg.invocation_id,
|
|
10752
|
+
error: err.message
|
|
10753
|
+
})
|
|
10754
|
+
);
|
|
10317
10755
|
});
|
|
10318
10756
|
break;
|
|
10319
10757
|
}
|
|
@@ -10367,7 +10805,12 @@ function registerWorker(url) {
|
|
|
10367
10805
|
},
|
|
10368
10806
|
registerTrigger(input) {
|
|
10369
10807
|
registeredTriggers.add(JSON.stringify(input));
|
|
10370
|
-
send({
|
|
10808
|
+
send({
|
|
10809
|
+
type: "register_trigger",
|
|
10810
|
+
function_id: input.function_id,
|
|
10811
|
+
trigger_type: input.type,
|
|
10812
|
+
config: input.config
|
|
10813
|
+
});
|
|
10371
10814
|
},
|
|
10372
10815
|
unregisterTrigger(functionId) {
|
|
10373
10816
|
for (const key of registeredTriggers) {
|
|
@@ -10410,7 +10853,7 @@ function registerWorker(url) {
|
|
|
10410
10853
|
onStream(handler) {
|
|
10411
10854
|
handlers.set("__stream__", handler);
|
|
10412
10855
|
},
|
|
10413
|
-
|
|
10856
|
+
close() {
|
|
10414
10857
|
intentionalClose = true;
|
|
10415
10858
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
10416
10859
|
ws?.close();
|
|
@@ -10459,7 +10902,7 @@ var MemoryStore = class {
|
|
|
10459
10902
|
if (entry.expires < now) this.store.delete(key);
|
|
10460
10903
|
}
|
|
10461
10904
|
}
|
|
10462
|
-
close() {
|
|
10905
|
+
async close() {
|
|
10463
10906
|
clearInterval(this.interval);
|
|
10464
10907
|
this.store.clear();
|
|
10465
10908
|
}
|
|
@@ -10495,6 +10938,9 @@ var RedisStore = class {
|
|
|
10495
10938
|
async destroy(sid) {
|
|
10496
10939
|
await this.redis.del(this.key(sid));
|
|
10497
10940
|
}
|
|
10941
|
+
async close() {
|
|
10942
|
+
this.redis.disconnect();
|
|
10943
|
+
}
|
|
10498
10944
|
};
|
|
10499
10945
|
var COOKIE_SEPARATOR = ".";
|
|
10500
10946
|
function signSessionId(sid, secret) {
|
|
@@ -10539,8 +10985,18 @@ function createSessionObject(data, sid, store2, ttl, createdAt) {
|
|
|
10539
10985
|
enumerable: false,
|
|
10540
10986
|
configurable: false
|
|
10541
10987
|
});
|
|
10542
|
-
Object.defineProperty(obj, "save", {
|
|
10543
|
-
|
|
10988
|
+
Object.defineProperty(obj, "save", {
|
|
10989
|
+
enumerable: false,
|
|
10990
|
+
configurable: true,
|
|
10991
|
+
writable: true,
|
|
10992
|
+
value: obj.save
|
|
10993
|
+
});
|
|
10994
|
+
Object.defineProperty(obj, "destroy", {
|
|
10995
|
+
enumerable: false,
|
|
10996
|
+
configurable: true,
|
|
10997
|
+
writable: true,
|
|
10998
|
+
value: obj.destroy
|
|
10999
|
+
});
|
|
10544
11000
|
return obj;
|
|
10545
11001
|
}
|
|
10546
11002
|
function isSessionActive(session2) {
|
|
@@ -10568,6 +11024,7 @@ function session(options) {
|
|
|
10568
11024
|
} else if (options?.store === "redis") {
|
|
10569
11025
|
if (!options.redis) throw new Error('session: redis client required when store: "redis"');
|
|
10570
11026
|
store2 = new RedisStore(options.redis);
|
|
11027
|
+
closeStore = () => store2.close();
|
|
10571
11028
|
} else {
|
|
10572
11029
|
const mem = new MemoryStore();
|
|
10573
11030
|
store2 = mem;
|
|
@@ -10647,8 +11104,8 @@ function session(options) {
|
|
|
10647
11104
|
}
|
|
10648
11105
|
return res;
|
|
10649
11106
|
});
|
|
10650
|
-
mw.close = () => {
|
|
10651
|
-
closeStore?.();
|
|
11107
|
+
mw.close = async () => {
|
|
11108
|
+
await closeStore?.();
|
|
10652
11109
|
};
|
|
10653
11110
|
mw.store = store2;
|
|
10654
11111
|
return mw;
|
|
@@ -10742,7 +11199,7 @@ var MemoryCache = class {
|
|
|
10742
11199
|
}
|
|
10743
11200
|
}
|
|
10744
11201
|
}
|
|
10745
|
-
close() {
|
|
11202
|
+
async close() {
|
|
10746
11203
|
clearInterval(this.interval);
|
|
10747
11204
|
this.store.clear();
|
|
10748
11205
|
this.tagIndex.clear();
|
|
@@ -10883,8 +11340,8 @@ function cache2(options) {
|
|
|
10883
11340
|
mw.store = store2;
|
|
10884
11341
|
mw.invalidate = async (tag) => store2.invalidate(tag);
|
|
10885
11342
|
mw.flush = async () => store2.flush();
|
|
10886
|
-
mw.close = () => {
|
|
10887
|
-
closeStore?.();
|
|
11343
|
+
mw.close = async () => {
|
|
11344
|
+
await closeStore?.();
|
|
10888
11345
|
};
|
|
10889
11346
|
return mw;
|
|
10890
11347
|
}
|
|
@@ -10909,7 +11366,8 @@ function createStripeVerifier(config) {
|
|
|
10909
11366
|
}, {});
|
|
10910
11367
|
const timestamp = parts["t"];
|
|
10911
11368
|
const signature = parts["v1"];
|
|
10912
|
-
if (!timestamp || !signature)
|
|
11369
|
+
if (!timestamp || !signature)
|
|
11370
|
+
return { valid: false, provider: "stripe", event: "", id: void 0 };
|
|
10913
11371
|
const signed = `${timestamp}.${body}`;
|
|
10914
11372
|
const expected = crypto11.createHmac("sha256", config.secret).update(signed).digest("hex");
|
|
10915
11373
|
const valid = timingSafeEqual2(signature, expected);
|
|
@@ -10930,7 +11388,7 @@ function createGitHubVerifier(config) {
|
|
|
10930
11388
|
if (!sig) return { valid: false, provider: "github", event: "", id: void 0 };
|
|
10931
11389
|
const expected = `sha256=${crypto11.createHmac("sha256", config.secret).update(body).digest("hex")}`;
|
|
10932
11390
|
const valid = timingSafeEqual2(sig, expected);
|
|
10933
|
-
|
|
11391
|
+
const event = headers["x-github-event"] ?? "";
|
|
10934
11392
|
let id2;
|
|
10935
11393
|
try {
|
|
10936
11394
|
const parsed = JSON.parse(body);
|
|
@@ -10944,7 +11402,8 @@ function createSlackVerifier(config) {
|
|
|
10944
11402
|
return (body, headers) => {
|
|
10945
11403
|
const signature = headers["x-slack-signature"];
|
|
10946
11404
|
const timestamp = headers["x-slack-request-timestamp"];
|
|
10947
|
-
if (!signature || !timestamp)
|
|
11405
|
+
if (!signature || !timestamp)
|
|
11406
|
+
return { valid: false, provider: "slack", event: "", id: void 0 };
|
|
10948
11407
|
const now = Math.floor(Date.now() / 1e3);
|
|
10949
11408
|
const ts = parseInt(timestamp, 10);
|
|
10950
11409
|
if (isNaN(ts) || Math.abs(now - ts) > 300) {
|
|
@@ -11106,11 +11565,7 @@ __export(fts_exports, {
|
|
|
11106
11565
|
suggest: () => suggest
|
|
11107
11566
|
});
|
|
11108
11567
|
function resolveTableName(table) {
|
|
11109
|
-
|
|
11110
|
-
if (!name || typeof name !== "string") {
|
|
11111
|
-
throw new Error("fts: could not determine table name. Ensure you pass a pg.table() result.");
|
|
11112
|
-
}
|
|
11113
|
-
return name;
|
|
11568
|
+
return table.tableName;
|
|
11114
11569
|
}
|
|
11115
11570
|
function escapeIdent4(s) {
|
|
11116
11571
|
return `"${s.replace(/"/g, '""')}"`;
|
|
@@ -11322,7 +11777,15 @@ function escapeIdent5(s) {
|
|
|
11322
11777
|
return `"${s.replace(/"/g, '""')}"`;
|
|
11323
11778
|
}
|
|
11324
11779
|
function knowledgeBase(options) {
|
|
11325
|
-
const {
|
|
11780
|
+
const {
|
|
11781
|
+
pg,
|
|
11782
|
+
provider,
|
|
11783
|
+
table = "_kb_docs",
|
|
11784
|
+
chunkSize = 512,
|
|
11785
|
+
chunkOverlap = 64,
|
|
11786
|
+
searchLimit = 5,
|
|
11787
|
+
searchThreshold = 0
|
|
11788
|
+
} = options;
|
|
11326
11789
|
const sql2 = pg.sql;
|
|
11327
11790
|
const dimension = provider.dimension;
|
|
11328
11791
|
const docsTable = pg.table(table, {
|
|
@@ -11468,17 +11931,17 @@ function permissions(options) {
|
|
|
11468
11931
|
);
|
|
11469
11932
|
return created.id;
|
|
11470
11933
|
}
|
|
11471
|
-
async function assignRole(
|
|
11934
|
+
async function assignRole(userId2, role) {
|
|
11472
11935
|
const roleId = await ensureRole(role);
|
|
11473
11936
|
await sql2.unsafe(
|
|
11474
11937
|
`INSERT INTO ${escapeIdent6(userRolesTable)} (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
|
|
11475
|
-
[
|
|
11938
|
+
[userId2, roleId]
|
|
11476
11939
|
);
|
|
11477
11940
|
}
|
|
11478
|
-
async function removeRole(
|
|
11941
|
+
async function removeRole(userId2, role) {
|
|
11479
11942
|
await sql2.unsafe(
|
|
11480
11943
|
`DELETE FROM ${escapeIdent6(userRolesTable)} WHERE user_id = $1 AND role_id = (SELECT id FROM ${escapeIdent6(rolesTable)} WHERE name = $2)`,
|
|
11481
|
-
[
|
|
11944
|
+
[userId2, role]
|
|
11482
11945
|
);
|
|
11483
11946
|
}
|
|
11484
11947
|
async function grantPermission(role, permission) {
|
|
@@ -11494,31 +11957,31 @@ function permissions(options) {
|
|
|
11494
11957
|
[role, permission]
|
|
11495
11958
|
);
|
|
11496
11959
|
}
|
|
11497
|
-
async function getUserRoles(
|
|
11960
|
+
async function getUserRoles(userId2) {
|
|
11498
11961
|
const rows = await sql2.unsafe(
|
|
11499
11962
|
`SELECT r.name FROM ${escapeIdent6(userRolesTable)} ur
|
|
11500
11963
|
JOIN ${escapeIdent6(rolesTable)} r ON r.id = ur.role_id
|
|
11501
11964
|
WHERE ur.user_id = $1 ORDER BY r.name`,
|
|
11502
|
-
[
|
|
11965
|
+
[userId2]
|
|
11503
11966
|
);
|
|
11504
11967
|
return rows.map((r) => r.name);
|
|
11505
11968
|
}
|
|
11506
|
-
async function getUserPermissions(
|
|
11969
|
+
async function getUserPermissions(userId2) {
|
|
11507
11970
|
const rows = await sql2.unsafe(
|
|
11508
11971
|
`SELECT DISTINCT rp.permission FROM ${escapeIdent6(userRolesTable)} ur
|
|
11509
11972
|
JOIN ${escapeIdent6(rolePermsTable)} rp ON rp.role_id = ur.role_id
|
|
11510
11973
|
WHERE ur.user_id = $1 ORDER BY rp.permission`,
|
|
11511
|
-
[
|
|
11974
|
+
[userId2]
|
|
11512
11975
|
);
|
|
11513
11976
|
return rows.map((r) => r.permission);
|
|
11514
11977
|
}
|
|
11515
11978
|
const mw = (async (req, ctx, next) => {
|
|
11516
|
-
const
|
|
11979
|
+
const userId2 = ctx.user?.id;
|
|
11517
11980
|
let roles = /* @__PURE__ */ new Set();
|
|
11518
11981
|
let perms = /* @__PURE__ */ new Set();
|
|
11519
|
-
if (
|
|
11520
|
-
const userRoles = await getUserRoles(
|
|
11521
|
-
const userPerms =
|
|
11982
|
+
if (userId2) {
|
|
11983
|
+
const userRoles = await getUserRoles(userId2);
|
|
11984
|
+
const userPerms = userId2 ? await getUserPermissions(userId2) : [];
|
|
11522
11985
|
roles = new Set(userRoles);
|
|
11523
11986
|
perms = new Set(userPerms);
|
|
11524
11987
|
const hasWildcard = userPerms.includes("*");
|