weifuwu 0.3.0 → 0.5.0
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 +399 -226
- package/dist/ai.d.ts +7 -0
- package/dist/graphql.d.ts +12 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +900 -139
- package/dist/postgres/client.d.ts +2 -0
- package/dist/postgres/index.d.ts +2 -0
- package/dist/postgres/migrate.d.ts +3 -0
- package/dist/postgres/table.d.ts +4 -0
- package/dist/postgres/types.d.ts +51 -0
- package/dist/router.d.ts +0 -16
- package/dist/workflow/engine.d.ts +7 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/llm.d.ts +10 -0
- package/dist/workflow/nodes.d.ts +10 -0
- package/dist/workflow/reference.d.ts +3 -0
- package/dist/workflow/route.d.ts +11 -0
- package/dist/workflow/sse.d.ts +2 -0
- package/dist/workflow/tool.d.ts +8 -0
- package/dist/workflow/types.d.ts +86 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -107,9 +107,6 @@ function serve(handler, options) {
|
|
|
107
107
|
|
|
108
108
|
// router.ts
|
|
109
109
|
import { WebSocketServer } from "ws";
|
|
110
|
-
import { buildSchema, graphql } from "graphql";
|
|
111
|
-
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
112
|
-
import { streamText } from "ai";
|
|
113
110
|
var createTrieNode = () => ({
|
|
114
111
|
children: /* @__PURE__ */ new Map(),
|
|
115
112
|
handlers: /* @__PURE__ */ new Map(),
|
|
@@ -261,47 +258,6 @@ var Router = class _Router {
|
|
|
261
258
|
if (middlewares.length > 0) node.middlewares = middlewares;
|
|
262
259
|
return this;
|
|
263
260
|
}
|
|
264
|
-
graphql(path, ...args) {
|
|
265
|
-
const options = args.pop();
|
|
266
|
-
const middlewares = args;
|
|
267
|
-
const schema = typeof options.schema === "string" ? options.resolvers ? makeExecutableSchema({
|
|
268
|
-
typeDefs: options.schema,
|
|
269
|
-
resolvers: options.resolvers
|
|
270
|
-
}) : buildSchema(options.schema) : options.schema;
|
|
271
|
-
const handler = (req, ctx) => {
|
|
272
|
-
const url = new URL(req.url);
|
|
273
|
-
if (options.graphiql && req.method === "GET" && !url.searchParams.has("query")) {
|
|
274
|
-
return new Response(getGraphiQLHtml(url.pathname), {
|
|
275
|
-
status: 200,
|
|
276
|
-
headers: { "Content-Type": "text/html" }
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
if (req.method !== "GET" && req.method !== "POST") {
|
|
280
|
-
return new Response("Not Found", { status: 404 });
|
|
281
|
-
}
|
|
282
|
-
const paramsPromise = req.method === "GET" ? Promise.resolve(parseGraphQLParamsFromGet(url)) : parseGraphQLParamsFromPost(req);
|
|
283
|
-
return paramsPromise.then((params) => {
|
|
284
|
-
if (!params) {
|
|
285
|
-
return Response.json(
|
|
286
|
-
{ errors: [{ message: "Missing query" }] },
|
|
287
|
-
{ status: 400 }
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
return executeGraphQLQuery(schema, params, options, req, ctx);
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
return this.all(path, ...middlewares, handler);
|
|
294
|
-
}
|
|
295
|
-
ai(path, ...args) {
|
|
296
|
-
const handler = args.pop();
|
|
297
|
-
const middlewares = args;
|
|
298
|
-
const routeHandler = async (req, ctx) => {
|
|
299
|
-
const options = await handler(req, ctx);
|
|
300
|
-
const result = streamText(options);
|
|
301
|
-
return result.toTextStreamResponse();
|
|
302
|
-
};
|
|
303
|
-
return this.post(path, ...middlewares, routeHandler);
|
|
304
|
-
}
|
|
305
261
|
handler() {
|
|
306
262
|
return (req, ctx) => {
|
|
307
263
|
const url = new URL(req.url);
|
|
@@ -506,100 +462,6 @@ function sendHttpResponseOnSocket(socket, response) {
|
|
|
506
462
|
socket.end();
|
|
507
463
|
});
|
|
508
464
|
}
|
|
509
|
-
function parseGraphQLParamsFromGet(url) {
|
|
510
|
-
const query = url.searchParams.get("query");
|
|
511
|
-
if (!query) return null;
|
|
512
|
-
const variablesStr = url.searchParams.get("variables");
|
|
513
|
-
let variables = {};
|
|
514
|
-
if (variablesStr) {
|
|
515
|
-
try {
|
|
516
|
-
variables = JSON.parse(variablesStr);
|
|
517
|
-
} catch {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return {
|
|
522
|
-
query,
|
|
523
|
-
variables,
|
|
524
|
-
operationName: url.searchParams.get("operationName") || void 0
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
async function parseGraphQLParamsFromPost(req) {
|
|
528
|
-
try {
|
|
529
|
-
const body = await req.json();
|
|
530
|
-
if (!body.query) return null;
|
|
531
|
-
return {
|
|
532
|
-
query: body.query,
|
|
533
|
-
variables: body.variables || {},
|
|
534
|
-
operationName: body.operationName
|
|
535
|
-
};
|
|
536
|
-
} catch {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
async function executeGraphQLQuery(schema, params, options, req, ctx) {
|
|
541
|
-
const contextValue = options.context ? await options.context(req, ctx) : ctx;
|
|
542
|
-
const result = await graphql({
|
|
543
|
-
schema,
|
|
544
|
-
source: params.query,
|
|
545
|
-
rootValue: options.rootValue,
|
|
546
|
-
contextValue,
|
|
547
|
-
variableValues: params.variables,
|
|
548
|
-
operationName: params.operationName
|
|
549
|
-
});
|
|
550
|
-
return Response.json(result, { status: result.errors ? 400 : 200 });
|
|
551
|
-
}
|
|
552
|
-
function getGraphiQLHtml(endpoint) {
|
|
553
|
-
return `<!doctype html>
|
|
554
|
-
<html lang="en">
|
|
555
|
-
<head>
|
|
556
|
-
<meta charset="UTF-8" />
|
|
557
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
558
|
-
<title>GraphiQL</title>
|
|
559
|
-
<style>
|
|
560
|
-
body { margin: 0; }
|
|
561
|
-
#graphiql { height: 100dvh; }
|
|
562
|
-
</style>
|
|
563
|
-
<link rel="stylesheet" href="https://esm.sh/graphiql@5.2.2/dist/style.css" />
|
|
564
|
-
<script type="importmap">
|
|
565
|
-
{
|
|
566
|
-
"imports": {
|
|
567
|
-
"react": "https://esm.sh/react@19.2.5",
|
|
568
|
-
"react/": "https://esm.sh/react@19.2.5/",
|
|
569
|
-
"react-dom": "https://esm.sh/react-dom@19.2.5",
|
|
570
|
-
"react-dom/": "https://esm.sh/react-dom@19.2.5/",
|
|
571
|
-
"graphiql": "https://esm.sh/graphiql@5.2.2?standalone&external=react,react-dom,@graphiql/react,graphql",
|
|
572
|
-
"graphiql/": "https://esm.sh/graphiql@5.2.2/",
|
|
573
|
-
"@graphiql/react": "https://esm.sh/@graphiql/react@0.37.3?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
|
|
574
|
-
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql",
|
|
575
|
-
"graphql": "https://esm.sh/graphql@16.13.2",
|
|
576
|
-
"@emotion/is-prop-valid": "data:text/javascript,"
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
</script>
|
|
580
|
-
<script type="module">
|
|
581
|
-
import React from 'react';
|
|
582
|
-
import ReactDOM from 'react-dom/client';
|
|
583
|
-
import { GraphiQL } from 'graphiql';
|
|
584
|
-
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
585
|
-
import 'graphiql/setup-workers/esm.sh';
|
|
586
|
-
|
|
587
|
-
const fetcher = createGraphiQLFetcher({ url: "${endpoint}" });
|
|
588
|
-
|
|
589
|
-
function App() {
|
|
590
|
-
return React.createElement(GraphiQL, { fetcher });
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const container = document.getElementById('graphiql');
|
|
594
|
-
const root = ReactDOM.createRoot(container);
|
|
595
|
-
root.render(React.createElement(App));
|
|
596
|
-
</script>
|
|
597
|
-
</head>
|
|
598
|
-
<body>
|
|
599
|
-
<div id="graphiql">Loading\u2026</div>
|
|
600
|
-
</body>
|
|
601
|
-
</html>`;
|
|
602
|
-
}
|
|
603
465
|
|
|
604
466
|
// tsx.ts
|
|
605
467
|
import { createElement, createContext, useContext } from "react";
|
|
@@ -1466,21 +1328,920 @@ function compress(options) {
|
|
|
1466
1328
|
});
|
|
1467
1329
|
};
|
|
1468
1330
|
}
|
|
1331
|
+
|
|
1332
|
+
// graphql.ts
|
|
1333
|
+
import { buildSchema, graphql as executeGraphQL } from "graphql";
|
|
1334
|
+
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
1335
|
+
function parseParamsFromGet(url) {
|
|
1336
|
+
const query = url.searchParams.get("query");
|
|
1337
|
+
if (!query) return null;
|
|
1338
|
+
let variables = {};
|
|
1339
|
+
const variablesStr = url.searchParams.get("variables");
|
|
1340
|
+
if (variablesStr) {
|
|
1341
|
+
try {
|
|
1342
|
+
variables = JSON.parse(variablesStr);
|
|
1343
|
+
} catch {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return { query, variables, operationName: url.searchParams.get("operationName") || void 0 };
|
|
1348
|
+
}
|
|
1349
|
+
async function parseParamsFromPost(req) {
|
|
1350
|
+
try {
|
|
1351
|
+
const body = await req.json();
|
|
1352
|
+
if (!body.query) return null;
|
|
1353
|
+
return { query: body.query, variables: body.variables || {}, operationName: body.operationName };
|
|
1354
|
+
} catch {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function buildSchemaFromOptions(options) {
|
|
1359
|
+
if (typeof options.schema === "string") {
|
|
1360
|
+
return options.resolvers ? makeExecutableSchema({ typeDefs: options.schema, resolvers: options.resolvers }) : buildSchema(options.schema);
|
|
1361
|
+
}
|
|
1362
|
+
return options.schema;
|
|
1363
|
+
}
|
|
1364
|
+
async function executeQuery(schema, params, options, req, ctx) {
|
|
1365
|
+
const contextValue = options.context ? await options.context(req, ctx) : ctx;
|
|
1366
|
+
const result = await executeGraphQL({
|
|
1367
|
+
schema,
|
|
1368
|
+
source: params.query,
|
|
1369
|
+
rootValue: options.rootValue,
|
|
1370
|
+
contextValue,
|
|
1371
|
+
variableValues: params.variables,
|
|
1372
|
+
operationName: params.operationName
|
|
1373
|
+
});
|
|
1374
|
+
return Response.json(result, { status: result.errors ? 400 : 200 });
|
|
1375
|
+
}
|
|
1376
|
+
function graphiqlHTML(endpoint) {
|
|
1377
|
+
return `<!doctype html>
|
|
1378
|
+
<html lang="en">
|
|
1379
|
+
<head>
|
|
1380
|
+
<meta charset="UTF-8" />
|
|
1381
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1382
|
+
<title>GraphiQL</title>
|
|
1383
|
+
<style>body { margin: 0; } #graphiql { height: 100dvh; }</style>
|
|
1384
|
+
<link rel="stylesheet" href="https://esm.sh/graphiql@5.2.2/dist/style.css" />
|
|
1385
|
+
<script type="importmap">
|
|
1386
|
+
{
|
|
1387
|
+
"imports": {
|
|
1388
|
+
"react": "https://esm.sh/react@19.2.5",
|
|
1389
|
+
"react/": "https://esm.sh/react@19.2.5/",
|
|
1390
|
+
"react-dom": "https://esm.sh/react-dom@19.2.5",
|
|
1391
|
+
"react-dom/": "https://esm.sh/react-dom@19.2.5/",
|
|
1392
|
+
"graphiql": "https://esm.sh/graphiql@5.2.2?standalone&external=react,react-dom,@graphiql/react,graphql",
|
|
1393
|
+
"graphiql/": "https://esm.sh/graphiql@5.2.2/",
|
|
1394
|
+
"@graphiql/react": "https://esm.sh/@graphiql/react@0.37.3?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
|
|
1395
|
+
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql",
|
|
1396
|
+
"graphql": "https://esm.sh/graphql@16.13.2",
|
|
1397
|
+
"@emotion/is-prop-valid": "data:text/javascript,"
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
</script>
|
|
1401
|
+
<script type="module">
|
|
1402
|
+
import React from 'react';
|
|
1403
|
+
import ReactDOM from 'react-dom/client';
|
|
1404
|
+
import { GraphiQL } from 'graphiql';
|
|
1405
|
+
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
1406
|
+
import 'graphiql/setup-workers/esm.sh';
|
|
1407
|
+
|
|
1408
|
+
const fetcher = createGraphiQLFetcher({ url: "${endpoint}" });
|
|
1409
|
+
|
|
1410
|
+
function App() {
|
|
1411
|
+
return React.createElement(GraphiQL, { fetcher });
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const container = document.getElementById('graphiql');
|
|
1415
|
+
const root = ReactDOM.createRoot(container);
|
|
1416
|
+
root.render(React.createElement(App));
|
|
1417
|
+
</script>
|
|
1418
|
+
</head>
|
|
1419
|
+
<body>
|
|
1420
|
+
<div id="graphiql">Loading\u2026</div>
|
|
1421
|
+
</body>
|
|
1422
|
+
</html>`;
|
|
1423
|
+
}
|
|
1424
|
+
function graphql(handler) {
|
|
1425
|
+
const r = new Router();
|
|
1426
|
+
r.get("/", async (req, ctx) => {
|
|
1427
|
+
const options = await handler(req, ctx);
|
|
1428
|
+
const schema = buildSchemaFromOptions(options);
|
|
1429
|
+
const url = new URL(req.url);
|
|
1430
|
+
if (options.graphiql && !url.searchParams.has("query")) {
|
|
1431
|
+
return new Response(graphiqlHTML(url.pathname), {
|
|
1432
|
+
status: 200,
|
|
1433
|
+
headers: { "Content-Type": "text/html" }
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
const params = parseParamsFromGet(url);
|
|
1437
|
+
if (!params) {
|
|
1438
|
+
return Response.json({ errors: [{ message: "Missing query" }] }, { status: 400 });
|
|
1439
|
+
}
|
|
1440
|
+
return executeQuery(schema, params, options, req, ctx);
|
|
1441
|
+
});
|
|
1442
|
+
r.post("/", async (req, ctx) => {
|
|
1443
|
+
const options = await handler(req, ctx);
|
|
1444
|
+
const schema = buildSchemaFromOptions(options);
|
|
1445
|
+
const params = await parseParamsFromPost(req);
|
|
1446
|
+
if (!params) {
|
|
1447
|
+
return Response.json({ errors: [{ message: "Missing query" }] }, { status: 400 });
|
|
1448
|
+
}
|
|
1449
|
+
return executeQuery(schema, params, options, req, ctx);
|
|
1450
|
+
});
|
|
1451
|
+
return r;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// ai.ts
|
|
1455
|
+
import { streamText } from "ai";
|
|
1456
|
+
function ai(handler) {
|
|
1457
|
+
const r = new Router();
|
|
1458
|
+
r.post("/", async (req, ctx) => {
|
|
1459
|
+
const options = await handler(req, ctx);
|
|
1460
|
+
const result = streamText(options);
|
|
1461
|
+
return result.toTextStreamResponse();
|
|
1462
|
+
});
|
|
1463
|
+
return r;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// workflow/tool.ts
|
|
1467
|
+
function tool(def) {
|
|
1468
|
+
return {
|
|
1469
|
+
name: def.name ?? "",
|
|
1470
|
+
description: def.description,
|
|
1471
|
+
inputSchema: def.inputSchema,
|
|
1472
|
+
execute: def.execute
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// workflow/reference.ts
|
|
1477
|
+
function getByPath(obj, path) {
|
|
1478
|
+
let current = obj;
|
|
1479
|
+
for (const key of path) {
|
|
1480
|
+
if (current === null || current === void 0) return void 0;
|
|
1481
|
+
if (typeof current === "object" && key in current) {
|
|
1482
|
+
current = current[key];
|
|
1483
|
+
} else {
|
|
1484
|
+
return void 0;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return current;
|
|
1488
|
+
}
|
|
1489
|
+
function resolveRef(path, ctx) {
|
|
1490
|
+
if (path.startsWith("$nodes.")) {
|
|
1491
|
+
const afterNodes = path.slice(7);
|
|
1492
|
+
const dotIdx = afterNodes.indexOf(".");
|
|
1493
|
+
if (dotIdx === -1) {
|
|
1494
|
+
return ctx.nodeOutputs.get(afterNodes);
|
|
1495
|
+
}
|
|
1496
|
+
const id2 = afterNodes.slice(0, dotIdx);
|
|
1497
|
+
const propPath = afterNodes.slice(dotIdx + 1);
|
|
1498
|
+
const output = ctx.nodeOutputs.get(id2);
|
|
1499
|
+
if (output === void 0) {
|
|
1500
|
+
throw new Error(`Node "${id2}" has no output yet`);
|
|
1501
|
+
}
|
|
1502
|
+
if (propPath.startsWith("output")) {
|
|
1503
|
+
return getByPath(output, propPath.slice(7).split(".").filter(Boolean));
|
|
1504
|
+
}
|
|
1505
|
+
return getByPath(output, propPath.split("."));
|
|
1506
|
+
}
|
|
1507
|
+
if (path.startsWith("$var.")) {
|
|
1508
|
+
const name = path.slice(5);
|
|
1509
|
+
if (!ctx.variables.has(name)) {
|
|
1510
|
+
throw new Error(`Variable "${name}" is not defined`);
|
|
1511
|
+
}
|
|
1512
|
+
return ctx.variables.get(name);
|
|
1513
|
+
}
|
|
1514
|
+
if (path.startsWith("$input.")) {
|
|
1515
|
+
const key = path.slice(7);
|
|
1516
|
+
return ctx.input[key];
|
|
1517
|
+
}
|
|
1518
|
+
if (path === "true") return true;
|
|
1519
|
+
if (path === "false") return false;
|
|
1520
|
+
if (path === "null") return null;
|
|
1521
|
+
const num = Number(path);
|
|
1522
|
+
if (!isNaN(num) && path.trim() !== "") return num;
|
|
1523
|
+
return path;
|
|
1524
|
+
}
|
|
1525
|
+
function resolveValue(v, ctx) {
|
|
1526
|
+
if (typeof v === "string" && v.startsWith("$")) {
|
|
1527
|
+
return resolveRef(v, ctx);
|
|
1528
|
+
}
|
|
1529
|
+
if (Array.isArray(v)) {
|
|
1530
|
+
return v.map((item) => resolveValue(item, ctx));
|
|
1531
|
+
}
|
|
1532
|
+
if (typeof v === "object" && v !== null) {
|
|
1533
|
+
const result = {};
|
|
1534
|
+
for (const [k, val] of Object.entries(v)) {
|
|
1535
|
+
result[k] = resolveValue(val, ctx);
|
|
1536
|
+
}
|
|
1537
|
+
return result;
|
|
1538
|
+
}
|
|
1539
|
+
return v;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// workflow/nodes.ts
|
|
1543
|
+
function evaluateExpression(expr, ctx) {
|
|
1544
|
+
const operators = [
|
|
1545
|
+
{ op: "===", fn: (a, b) => a === b },
|
|
1546
|
+
{ op: "!==", fn: (a, b) => a !== b },
|
|
1547
|
+
{ op: ">=", fn: (a, b) => Number(a) >= Number(b) },
|
|
1548
|
+
{ op: "<=", fn: (a, b) => Number(a) <= Number(b) },
|
|
1549
|
+
{ op: ">", fn: (a, b) => Number(a) > Number(b) },
|
|
1550
|
+
{ op: "<", fn: (a, b) => Number(a) < Number(b) },
|
|
1551
|
+
{ op: "==", fn: (a, b) => a == b },
|
|
1552
|
+
{ op: "!=", fn: (a, b) => a != b },
|
|
1553
|
+
{ op: "+", fn: (a, b) => Number(a) + Number(b) },
|
|
1554
|
+
{ op: "-", fn: (a, b) => Number(a) - Number(b) },
|
|
1555
|
+
{ op: "*", fn: (a, b) => Number(a) * Number(b) },
|
|
1556
|
+
{ op: "/", fn: (a, b) => Number(a) / Number(b) },
|
|
1557
|
+
{ op: "%", fn: (a, b) => Number(a) % Number(b) },
|
|
1558
|
+
{ op: "&&", fn: (a, b) => Boolean(a) && Boolean(b) },
|
|
1559
|
+
{ op: "||", fn: (a, b) => Boolean(a) || Boolean(b) }
|
|
1560
|
+
];
|
|
1561
|
+
for (const { op, fn } of operators) {
|
|
1562
|
+
const idx = expr.indexOf(op);
|
|
1563
|
+
if (idx > 0) {
|
|
1564
|
+
const leftRaw = expr.slice(0, idx).trim();
|
|
1565
|
+
const rightRaw = expr.slice(idx + op.length).trim();
|
|
1566
|
+
const left = resolveValue(leftRaw, ctx);
|
|
1567
|
+
const right = resolveValue(rightRaw, ctx);
|
|
1568
|
+
return fn(left, right);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
const trimmed = expr.trim();
|
|
1572
|
+
if (trimmed === "true") return true;
|
|
1573
|
+
if (trimmed === "false") return false;
|
|
1574
|
+
if (trimmed === "null") return null;
|
|
1575
|
+
const num = Number(trimmed);
|
|
1576
|
+
if (!isNaN(num) && trimmed !== "") return num;
|
|
1577
|
+
return resolveValue(expr, ctx);
|
|
1578
|
+
}
|
|
1579
|
+
async function executeEval(node, ctx) {
|
|
1580
|
+
const expression = node.input.expression;
|
|
1581
|
+
if (!expression) throw new Error('eval node requires "expression" field');
|
|
1582
|
+
const result = evaluateExpression(expression, ctx);
|
|
1583
|
+
return { result };
|
|
1584
|
+
}
|
|
1585
|
+
async function executeSet(node, ctx) {
|
|
1586
|
+
const name = node.input.name;
|
|
1587
|
+
const value = node.input.value;
|
|
1588
|
+
if (!name) throw new Error('set node requires "name" field');
|
|
1589
|
+
let resolved;
|
|
1590
|
+
if (typeof value === "string") {
|
|
1591
|
+
resolved = evaluateExpression(value, ctx);
|
|
1592
|
+
} else {
|
|
1593
|
+
resolved = resolveValue(value ?? null, ctx);
|
|
1594
|
+
}
|
|
1595
|
+
ctx.variables.set(name, resolved);
|
|
1596
|
+
return resolved;
|
|
1597
|
+
}
|
|
1598
|
+
async function executeGet(node, ctx) {
|
|
1599
|
+
const name = node.input.name;
|
|
1600
|
+
if (!name) throw new Error('get node requires "name" field');
|
|
1601
|
+
if (!ctx.variables.has(name)) {
|
|
1602
|
+
throw new Error(`Variable "${name}" is not defined`);
|
|
1603
|
+
}
|
|
1604
|
+
return ctx.variables.get(name);
|
|
1605
|
+
}
|
|
1606
|
+
async function executeIf(node, ctx) {
|
|
1607
|
+
const conditions = node.conditions ?? [];
|
|
1608
|
+
for (const condition of conditions) {
|
|
1609
|
+
const test = typeof condition.test === "string" ? Boolean(resolveValue(condition.test, ctx)) : condition.test;
|
|
1610
|
+
if (test && condition.body) {
|
|
1611
|
+
let lastOutput = void 0;
|
|
1612
|
+
for (const bodyNode of condition.body) {
|
|
1613
|
+
lastOutput = await executeNode(bodyNode, ctx);
|
|
1614
|
+
}
|
|
1615
|
+
return lastOutput;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return void 0;
|
|
1619
|
+
}
|
|
1620
|
+
async function executeWhile(node, ctx) {
|
|
1621
|
+
const conditionExpr = node.input.condition;
|
|
1622
|
+
if (!conditionExpr) throw new Error('while node requires "condition" field');
|
|
1623
|
+
let lastOutput = void 0;
|
|
1624
|
+
let iterations = 0;
|
|
1625
|
+
const maxIterations = 1e3;
|
|
1626
|
+
while (iterations < maxIterations) {
|
|
1627
|
+
iterations++;
|
|
1628
|
+
ctx.stepCount++;
|
|
1629
|
+
if (ctx.stepCount > ctx.maxSteps) {
|
|
1630
|
+
throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
|
|
1631
|
+
}
|
|
1632
|
+
const condition = Boolean(evaluateExpression(conditionExpr, ctx));
|
|
1633
|
+
if (!condition) break;
|
|
1634
|
+
for (const bodyNode of node.body ?? []) {
|
|
1635
|
+
lastOutput = await executeNode(bodyNode, ctx);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return lastOutput;
|
|
1639
|
+
}
|
|
1640
|
+
async function executeCall(node, ctx) {
|
|
1641
|
+
const toolName = node.input.tool;
|
|
1642
|
+
const args = node.input.args ?? {};
|
|
1643
|
+
if (toolName && ctx.toolRegistry.has(toolName)) {
|
|
1644
|
+
const tool3 = ctx.toolRegistry.get(toolName);
|
|
1645
|
+
const resolvedInput = resolveValue(args, ctx);
|
|
1646
|
+
const parsed = tool3.inputSchema.parse(resolvedInput);
|
|
1647
|
+
return tool3.execute(parsed, {
|
|
1648
|
+
nodeId: node.id,
|
|
1649
|
+
workflowId: ctx.workflowId,
|
|
1650
|
+
onStream: async (event) => {
|
|
1651
|
+
if (ctx.sseManager && ctx.workflowId) {
|
|
1652
|
+
ctx.sseManager.send(ctx.workflowId, { event: "llm-stream", data: { nodeId: node.id, ...event } });
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
const functionName = node.input.function;
|
|
1658
|
+
if (functionName && ctx.functions[functionName]) {
|
|
1659
|
+
const fn = ctx.functions[functionName];
|
|
1660
|
+
const prevFunctions = ctx.functions;
|
|
1661
|
+
const prevInput = ctx.input;
|
|
1662
|
+
ctx.input = resolveValue(args, ctx);
|
|
1663
|
+
let lastOutput = void 0;
|
|
1664
|
+
for (const bodyNode of fn.workflow.nodes) {
|
|
1665
|
+
lastOutput = await executeNode(bodyNode, ctx);
|
|
1666
|
+
}
|
|
1667
|
+
ctx.input = prevInput;
|
|
1668
|
+
return lastOutput;
|
|
1669
|
+
}
|
|
1670
|
+
throw new Error(`call node: tool "${toolName ?? functionName}" not found`);
|
|
1671
|
+
}
|
|
1672
|
+
async function executeHttp(node, ctx) {
|
|
1673
|
+
const input = resolveValue(node.input, ctx);
|
|
1674
|
+
const url = input.url;
|
|
1675
|
+
if (!url) throw new Error('http node requires "url" field');
|
|
1676
|
+
const controller = new AbortController();
|
|
1677
|
+
const timeout = input.timeout ?? 3e4;
|
|
1678
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
1679
|
+
try {
|
|
1680
|
+
const fetchInit = {
|
|
1681
|
+
method: input.method ?? "GET",
|
|
1682
|
+
headers: input.headers ?? {},
|
|
1683
|
+
signal: controller.signal
|
|
1684
|
+
};
|
|
1685
|
+
if (input.body && fetchInit.method !== "GET") {
|
|
1686
|
+
fetchInit.body = JSON.stringify(input.body);
|
|
1687
|
+
}
|
|
1688
|
+
const response = await fetch(url, fetchInit);
|
|
1689
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1690
|
+
const body = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
1691
|
+
return {
|
|
1692
|
+
status: response.status,
|
|
1693
|
+
statusText: response.statusText,
|
|
1694
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
1695
|
+
body
|
|
1696
|
+
};
|
|
1697
|
+
} finally {
|
|
1698
|
+
clearTimeout(timer);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
var executors = {
|
|
1702
|
+
eval: executeEval,
|
|
1703
|
+
set: executeSet,
|
|
1704
|
+
get: executeGet,
|
|
1705
|
+
if: executeIf,
|
|
1706
|
+
while: executeWhile,
|
|
1707
|
+
call: executeCall,
|
|
1708
|
+
http: executeHttp
|
|
1709
|
+
};
|
|
1710
|
+
async function executeNode(node, ctx) {
|
|
1711
|
+
const executor = executors[node.tool];
|
|
1712
|
+
if (!executor) {
|
|
1713
|
+
throw new Error(`Unknown node type: "${node.tool}"`);
|
|
1714
|
+
}
|
|
1715
|
+
return executor(node, ctx);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// workflow/llm.ts
|
|
1719
|
+
function buildToolsDescription(tools) {
|
|
1720
|
+
return Object.entries(tools).map(([key, t]) => {
|
|
1721
|
+
const name = t.name || key;
|
|
1722
|
+
const schema = t.inputSchema;
|
|
1723
|
+
return `- ${name}: ${t.description}
|
|
1724
|
+
Input schema: describe as JSON object fields`;
|
|
1725
|
+
}).join("\n");
|
|
1726
|
+
}
|
|
1727
|
+
var SYSTEM_PROMPT_TEMPLATE = `You are a workflow generator. Given a user goal and available tools, output a workflow JSON.
|
|
1728
|
+
|
|
1729
|
+
Available tools:
|
|
1730
|
+
{{TOOLS}}
|
|
1731
|
+
|
|
1732
|
+
Workflow format:
|
|
1733
|
+
{
|
|
1734
|
+
"name": "workflow name",
|
|
1735
|
+
"nodes": [
|
|
1736
|
+
{
|
|
1737
|
+
"id": "step1",
|
|
1738
|
+
"tool": "set",
|
|
1739
|
+
"input": { "name": "varName", "value": "initialValue" }
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
"id": "step2",
|
|
1743
|
+
"tool": "call",
|
|
1744
|
+
"input": { "tool": "toolName", "args": { "param1": "$var.varName" } }
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
"id": "step3",
|
|
1748
|
+
"tool": "if",
|
|
1749
|
+
"input": {},
|
|
1750
|
+
"conditions": [
|
|
1751
|
+
{ "test": "$nodes.step2.output.someField", "body": [
|
|
1752
|
+
{ "id": "step4", "tool": "call", "input": { "tool": "toolName", "args": {} } }
|
|
1753
|
+
]}
|
|
1754
|
+
]
|
|
1755
|
+
}
|
|
1756
|
+
]
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
Node types:
|
|
1760
|
+
- eval: evaluate an expression. input: { expression: "..." }
|
|
1761
|
+
- set: assign a variable. input: { name, value }
|
|
1762
|
+
- get: read a variable. input: { name }
|
|
1763
|
+
- if: conditional branch. input: {}, conditions: [{ test, body }]
|
|
1764
|
+
- while: loop. input: { condition }, body: [nodes]
|
|
1765
|
+
- call: call a registered tool. input: { tool, args }
|
|
1766
|
+
- http: HTTP request. input: { url, method?, headers?, body? }
|
|
1767
|
+
|
|
1768
|
+
Reference syntax:
|
|
1769
|
+
- $var.name - read a variable
|
|
1770
|
+
- $nodes.id.output - output of a previous node
|
|
1771
|
+
- $nodes.id.output.field - specific field of a node's output
|
|
1772
|
+
- $input.field - workflow input parameter
|
|
1773
|
+
|
|
1774
|
+
Output ONLY valid JSON. No explanation, no markdown.`;
|
|
1775
|
+
async function generateWorkflow(goal, tools, generateFn) {
|
|
1776
|
+
const toolsDesc = buildToolsDescription(tools);
|
|
1777
|
+
const system = SYSTEM_PROMPT_TEMPLATE.replace("{{TOOLS}}", toolsDesc);
|
|
1778
|
+
const result = await generateFn({
|
|
1779
|
+
system,
|
|
1780
|
+
messages: [{ role: "user", content: goal }]
|
|
1781
|
+
});
|
|
1782
|
+
const text = result.text.trim();
|
|
1783
|
+
const jsonStart = text.indexOf("{");
|
|
1784
|
+
const jsonEnd = text.lastIndexOf("}");
|
|
1785
|
+
if (jsonStart === -1 || jsonEnd === -1) {
|
|
1786
|
+
throw new Error(`LLM output is not valid JSON: ${text.slice(0, 200)}`);
|
|
1787
|
+
}
|
|
1788
|
+
const jsonStr = text.slice(jsonStart, jsonEnd + 1);
|
|
1789
|
+
try {
|
|
1790
|
+
const workflow2 = JSON.parse(jsonStr);
|
|
1791
|
+
if (!workflow2.nodes || !Array.isArray(workflow2.nodes)) {
|
|
1792
|
+
throw new Error("Generated workflow has no nodes array");
|
|
1793
|
+
}
|
|
1794
|
+
return workflow2;
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
if (err instanceof SyntaxError) {
|
|
1797
|
+
throw new Error(`Failed to parse LLM output as JSON: ${err.message}`);
|
|
1798
|
+
}
|
|
1799
|
+
throw err;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// workflow/engine.ts
|
|
1804
|
+
import { generateText } from "ai";
|
|
1805
|
+
function createWorkflowEngine(options) {
|
|
1806
|
+
const toolRegistry = /* @__PURE__ */ new Map();
|
|
1807
|
+
for (const [key, t] of Object.entries(options.tools)) {
|
|
1808
|
+
t.name = t.name || key;
|
|
1809
|
+
toolRegistry.set(t.name, t);
|
|
1810
|
+
}
|
|
1811
|
+
const states = /* @__PURE__ */ new Map();
|
|
1812
|
+
async function execute(workflow2, opts) {
|
|
1813
|
+
const ctx = {
|
|
1814
|
+
variables: /* @__PURE__ */ new Map(),
|
|
1815
|
+
nodeOutputs: /* @__PURE__ */ new Map(),
|
|
1816
|
+
functions: workflow2.functions ?? {},
|
|
1817
|
+
stepCount: 0,
|
|
1818
|
+
maxSteps: opts?.maxSteps ?? 1e3,
|
|
1819
|
+
input: opts?.initialInput ?? {},
|
|
1820
|
+
toolRegistry,
|
|
1821
|
+
sseManager: options.sseManager,
|
|
1822
|
+
workflowId: opts?.workflowId
|
|
1823
|
+
};
|
|
1824
|
+
let lastOutput = void 0;
|
|
1825
|
+
for (const node of workflow2.nodes) {
|
|
1826
|
+
ctx.stepCount++;
|
|
1827
|
+
if (ctx.stepCount > ctx.maxSteps) {
|
|
1828
|
+
throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
|
|
1829
|
+
}
|
|
1830
|
+
options.sseManager?.send(ctx.workflowId ?? "", { event: "node-start", data: { nodeId: node.id, tool: node.tool, input: node.input } });
|
|
1831
|
+
const output = await executeNode(node, ctx);
|
|
1832
|
+
ctx.nodeOutputs.set(node.id, output);
|
|
1833
|
+
lastOutput = output;
|
|
1834
|
+
options.sseManager?.send(ctx.workflowId ?? "", { event: "node-end", data: { nodeId: node.id, output } });
|
|
1835
|
+
}
|
|
1836
|
+
return lastOutput;
|
|
1837
|
+
}
|
|
1838
|
+
async function runAsync(workflowId, workflow2, opts) {
|
|
1839
|
+
const state = {
|
|
1840
|
+
workflowId,
|
|
1841
|
+
status: "running",
|
|
1842
|
+
goal: workflow2.name ?? "",
|
|
1843
|
+
startTime: Date.now()
|
|
1844
|
+
};
|
|
1845
|
+
states.set(workflowId, state);
|
|
1846
|
+
const sse = options.sseManager;
|
|
1847
|
+
sse?.send(workflowId, { event: "workflow-start", data: { workflowId, goal: state.goal } });
|
|
1848
|
+
try {
|
|
1849
|
+
const result = await execute(workflow2, { ...opts, workflowId });
|
|
1850
|
+
state.status = "completed";
|
|
1851
|
+
state.result = result;
|
|
1852
|
+
state.endTime = Date.now();
|
|
1853
|
+
sse?.send(workflowId, { event: "complete", data: { result, duration: state.endTime - state.startTime } });
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
state.status = "error";
|
|
1856
|
+
state.error = err instanceof Error ? err.message : String(err);
|
|
1857
|
+
state.endTime = Date.now();
|
|
1858
|
+
sse?.send(workflowId, { event: "error", data: { error: state.error } });
|
|
1859
|
+
} finally {
|
|
1860
|
+
sse?.close(workflowId);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
async function generateWorkflow2(goal) {
|
|
1864
|
+
if (!options.model) {
|
|
1865
|
+
throw new Error('LLM model is required for generateWorkflow. Pass "model" to createWorkflowEngine.');
|
|
1866
|
+
}
|
|
1867
|
+
return generateWorkflow(goal, options.tools, async (prompt) => {
|
|
1868
|
+
const result = await generateText({
|
|
1869
|
+
model: options.model,
|
|
1870
|
+
system: prompt.system,
|
|
1871
|
+
messages: prompt.messages
|
|
1872
|
+
});
|
|
1873
|
+
return { text: result.text };
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
return {
|
|
1877
|
+
execute,
|
|
1878
|
+
runAsync,
|
|
1879
|
+
generateWorkflow: generateWorkflow2,
|
|
1880
|
+
getState(workflowId) {
|
|
1881
|
+
return states.get(workflowId);
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// workflow/sse.ts
|
|
1887
|
+
function createSSEManager() {
|
|
1888
|
+
const streams = /* @__PURE__ */ new Map();
|
|
1889
|
+
const encoder = new TextEncoder();
|
|
1890
|
+
function createStream(workflowId) {
|
|
1891
|
+
const state = {
|
|
1892
|
+
controller: null,
|
|
1893
|
+
encoder,
|
|
1894
|
+
closed: false,
|
|
1895
|
+
buffer: []
|
|
1896
|
+
};
|
|
1897
|
+
const stream = new ReadableStream({
|
|
1898
|
+
start(controller) {
|
|
1899
|
+
state.controller = controller;
|
|
1900
|
+
streams.set(workflowId, state);
|
|
1901
|
+
for (const event of state.buffer) {
|
|
1902
|
+
try {
|
|
1903
|
+
controller.enqueue(encoder.encode(event));
|
|
1904
|
+
} catch {
|
|
1905
|
+
break;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
state.buffer = [];
|
|
1909
|
+
},
|
|
1910
|
+
cancel() {
|
|
1911
|
+
state.closed = true;
|
|
1912
|
+
streams.delete(workflowId);
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
return stream;
|
|
1916
|
+
}
|
|
1917
|
+
function send(workflowId, event) {
|
|
1918
|
+
const state = streams.get(workflowId);
|
|
1919
|
+
if (!state || state.closed) return;
|
|
1920
|
+
const data = `event: ${event.event}
|
|
1921
|
+
data: ${JSON.stringify(event.data)}
|
|
1922
|
+
|
|
1923
|
+
`;
|
|
1924
|
+
if (state.controller) {
|
|
1925
|
+
try {
|
|
1926
|
+
state.controller.enqueue(encoder.encode(data));
|
|
1927
|
+
} catch {
|
|
1928
|
+
state.closed = true;
|
|
1929
|
+
streams.delete(workflowId);
|
|
1930
|
+
}
|
|
1931
|
+
} else {
|
|
1932
|
+
state.buffer.push(data);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
function close(workflowId) {
|
|
1936
|
+
const state = streams.get(workflowId);
|
|
1937
|
+
if (!state) return;
|
|
1938
|
+
state.closed = true;
|
|
1939
|
+
streams.delete(workflowId);
|
|
1940
|
+
try {
|
|
1941
|
+
state.controller?.close();
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return { createStream, send, close };
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// workflow/route.ts
|
|
1949
|
+
function workflow(handler) {
|
|
1950
|
+
const r = new Router();
|
|
1951
|
+
const sseManager = createSSEManager();
|
|
1952
|
+
r.get("/:workflowId/events", async (req, ctx) => {
|
|
1953
|
+
const stream = sseManager.createStream(ctx.params.workflowId);
|
|
1954
|
+
return new Response(stream, {
|
|
1955
|
+
headers: {
|
|
1956
|
+
"Content-Type": "text/event-stream",
|
|
1957
|
+
"Cache-Control": "no-cache",
|
|
1958
|
+
"Connection": "keep-alive"
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1961
|
+
});
|
|
1962
|
+
r.post("/", async (req, ctx) => {
|
|
1963
|
+
const options = await handler(req, ctx);
|
|
1964
|
+
const engine = createWorkflowEngine({
|
|
1965
|
+
tools: options.tools,
|
|
1966
|
+
model: options.model,
|
|
1967
|
+
sseManager: options.stream ? sseManager : void 0
|
|
1968
|
+
});
|
|
1969
|
+
const body = await req.json();
|
|
1970
|
+
let wf;
|
|
1971
|
+
if (body.goal && options.model) {
|
|
1972
|
+
wf = await engine.generateWorkflow(body.goal);
|
|
1973
|
+
} else if (body.workflow) {
|
|
1974
|
+
wf = body.workflow;
|
|
1975
|
+
} else if (body.nodes) {
|
|
1976
|
+
wf = { nodes: body.nodes };
|
|
1977
|
+
} else {
|
|
1978
|
+
return Response.json(
|
|
1979
|
+
{ error: 'Provide "goal" (with model) or "workflow"/"nodes"' },
|
|
1980
|
+
{ status: 400 }
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
if (options.stream && sseManager) {
|
|
1984
|
+
const workflowId = crypto.randomUUID();
|
|
1985
|
+
engine.runAsync(workflowId, wf);
|
|
1986
|
+
return Response.json({ workflowId, eventsUrl: `/${workflowId}/events` });
|
|
1987
|
+
}
|
|
1988
|
+
const result = await engine.execute(wf);
|
|
1989
|
+
return Response.json({ workflow: wf, result });
|
|
1990
|
+
});
|
|
1991
|
+
return r;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// postgres/client.ts
|
|
1995
|
+
import postgresFactory from "postgres";
|
|
1996
|
+
|
|
1997
|
+
// postgres/table.ts
|
|
1998
|
+
import { z } from "zod";
|
|
1999
|
+
function unwrap(field) {
|
|
2000
|
+
let inner = field;
|
|
2001
|
+
while (inner instanceof z.ZodOptional || inner instanceof z.ZodNullable || inner instanceof z.ZodDefault || (inner.constructor.name === "ZodTransform" || inner._def?.type === "transform")) {
|
|
2002
|
+
if (inner._def?.innerType) {
|
|
2003
|
+
inner = inner._def.innerType;
|
|
2004
|
+
} else {
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
return inner;
|
|
2009
|
+
}
|
|
2010
|
+
function isOptional(field) {
|
|
2011
|
+
let inner = field;
|
|
2012
|
+
while (true) {
|
|
2013
|
+
if (inner instanceof z.ZodOptional) return true;
|
|
2014
|
+
if (inner instanceof z.ZodNullable) return true;
|
|
2015
|
+
if (inner instanceof z.ZodDefault) {
|
|
2016
|
+
inner = inner._def.innerType;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (inner._def?.type === "transform") {
|
|
2020
|
+
inner = inner._def.innerType;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
return false;
|
|
2026
|
+
}
|
|
2027
|
+
function hasUUIDCheck(field) {
|
|
2028
|
+
const checks = field._def?.checks ?? [];
|
|
2029
|
+
return checks.some((c) => c.type === "string" && c.def?.format === "uuid");
|
|
2030
|
+
}
|
|
2031
|
+
function detectSqlType(name, field, isPk) {
|
|
2032
|
+
const inner = unwrap(field);
|
|
2033
|
+
const nullable = isOptional(field);
|
|
2034
|
+
const autoGenerate = isPk && name === "id";
|
|
2035
|
+
if (isPk && name === "id" && inner instanceof z.ZodNumber) {
|
|
2036
|
+
return { sqlType: "SERIAL", nullable: false, defaultExpr: null, autoGenerate: true };
|
|
2037
|
+
}
|
|
2038
|
+
if (isPk && name === "id" && typeof BigInt !== "undefined" && inner instanceof z.ZodBigInt) {
|
|
2039
|
+
return { sqlType: "BIGSERIAL", nullable: false, defaultExpr: null, autoGenerate: true };
|
|
2040
|
+
}
|
|
2041
|
+
if (isPk && name === "id" && inner instanceof z.ZodString) {
|
|
2042
|
+
if (hasUUIDCheck(inner)) {
|
|
2043
|
+
return { sqlType: "UUID", nullable: false, defaultExpr: "gen_random_uuid()", autoGenerate: true };
|
|
2044
|
+
}
|
|
2045
|
+
return { sqlType: "TEXT", nullable: false, defaultExpr: null, autoGenerate: false };
|
|
2046
|
+
}
|
|
2047
|
+
let sqlType;
|
|
2048
|
+
if (inner instanceof z.ZodNumber) {
|
|
2049
|
+
sqlType = "INTEGER";
|
|
2050
|
+
} else if (inner instanceof z.ZodString) {
|
|
2051
|
+
sqlType = "TEXT";
|
|
2052
|
+
} else if (inner instanceof z.ZodBoolean) {
|
|
2053
|
+
sqlType = "BOOLEAN";
|
|
2054
|
+
} else if (inner instanceof z.ZodDate) {
|
|
2055
|
+
sqlType = "TIMESTAMPTZ";
|
|
2056
|
+
} else if (inner instanceof z.ZodEnum) {
|
|
2057
|
+
sqlType = "TEXT";
|
|
2058
|
+
} else if (inner instanceof z.ZodArray) {
|
|
2059
|
+
sqlType = "JSONB";
|
|
2060
|
+
} else if (inner instanceof z.ZodObject) {
|
|
2061
|
+
sqlType = "JSONB";
|
|
2062
|
+
} else {
|
|
2063
|
+
sqlType = "TEXT";
|
|
2064
|
+
}
|
|
2065
|
+
return { sqlType, nullable: nullable || autoGenerate, defaultExpr: null, autoGenerate };
|
|
2066
|
+
}
|
|
2067
|
+
function parseColumns(schema) {
|
|
2068
|
+
const pkField = Object.keys(schema).find((k) => k === "id");
|
|
2069
|
+
return Object.entries(schema).map(([name, field]) => {
|
|
2070
|
+
const isPk = name === pkField;
|
|
2071
|
+
const { sqlType, nullable, defaultExpr, autoGenerate } = detectSqlType(name, field, isPk);
|
|
2072
|
+
return { name, sqlType, nullable: nullable || autoGenerate, isPrimaryKey: isPk, defaultExpr, autoGenerate };
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
function buildGet(sql, name, pk) {
|
|
2076
|
+
return async function get(id2) {
|
|
2077
|
+
if (!pk) throw new Error(`Table "${name}" has no primary key`);
|
|
2078
|
+
const [row] = await sql`SELECT * FROM ${sql(name)} WHERE ${sql(pk.name)} = ${id2} LIMIT 1`;
|
|
2079
|
+
return row ?? void 0;
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
function buildList(sql, name, columns) {
|
|
2083
|
+
return async function list(filter = {}, opts = {}) {
|
|
2084
|
+
const colNames = new Set(columns.map((c) => c.name));
|
|
2085
|
+
const whereClauses = [];
|
|
2086
|
+
const whereValues = [];
|
|
2087
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
2088
|
+
if (colNames.has(key)) {
|
|
2089
|
+
whereClauses.push(`"${key}" = $${whereValues.length + 1}`);
|
|
2090
|
+
whereValues.push(value);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const where = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
2094
|
+
const sortClauses = [];
|
|
2095
|
+
if (opts.sort) {
|
|
2096
|
+
for (const [key, dir] of Object.entries(opts.sort)) {
|
|
2097
|
+
if (colNames.has(key)) {
|
|
2098
|
+
sortClauses.push(`"${key}" ${dir.toUpperCase()}`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const orderBy = sortClauses.length > 0 ? `ORDER BY ${sortClauses.join(", ")}` : "";
|
|
2103
|
+
const limitClause = opts.limit != null ? `LIMIT ${opts.limit}` : "";
|
|
2104
|
+
const offsetClause = opts.offset != null ? `OFFSET ${opts.offset}` : "";
|
|
2105
|
+
const [rows, countResult] = await Promise.all([
|
|
2106
|
+
sql.unsafe(`SELECT * FROM "${name}" ${where} ${orderBy} ${limitClause} ${offsetClause}`.trim(), whereValues),
|
|
2107
|
+
sql.unsafe(`SELECT count(*) as count FROM "${name}" ${where}`.trim(), whereValues)
|
|
2108
|
+
]);
|
|
2109
|
+
return { rows, count: Number(countResult[0]?.count ?? 0) };
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
function buildCreate(sql, name, pk, zodSchema) {
|
|
2113
|
+
return async function create(data) {
|
|
2114
|
+
const validated = zodSchema.parse(data);
|
|
2115
|
+
if (pk?.autoGenerate) {
|
|
2116
|
+
delete validated[pk.name];
|
|
2117
|
+
}
|
|
2118
|
+
const [row] = await sql`INSERT INTO ${sql(name)} ${sql(validated)} RETURNING *`;
|
|
2119
|
+
return row;
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
function buildPatch(sql, name, pk, zodSchema) {
|
|
2123
|
+
return async function patch(id2, data) {
|
|
2124
|
+
if (!pk) throw new Error(`Table "${name}" has no primary key`);
|
|
2125
|
+
const validated = zodSchema.partial().parse(data);
|
|
2126
|
+
delete validated[pk.name];
|
|
2127
|
+
if (Object.keys(validated).length === 0) {
|
|
2128
|
+
const [row2] = await sql`SELECT * FROM ${sql(name)} WHERE ${sql(pk.name)} = ${id2} LIMIT 1`;
|
|
2129
|
+
return row2 ?? void 0;
|
|
2130
|
+
}
|
|
2131
|
+
const [row] = await sql`UPDATE ${sql(name)} SET ${sql(validated)} WHERE ${sql(pk.name)} = ${id2} RETURNING *`;
|
|
2132
|
+
return row ?? void 0;
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
function buildRemove(sql, name, pk) {
|
|
2136
|
+
return async function remove(id2) {
|
|
2137
|
+
if (!pk) throw new Error(`Table "${name}" has no primary key`);
|
|
2138
|
+
const rows = await sql`DELETE FROM ${sql(name)} WHERE ${sql(pk.name)} = ${id2} RETURNING 1`;
|
|
2139
|
+
return rows.length > 0;
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
function buildTable(sql, tables) {
|
|
2143
|
+
return function table(name, schema) {
|
|
2144
|
+
const zodSchema = z.object(schema);
|
|
2145
|
+
const columns = parseColumns(schema);
|
|
2146
|
+
const pk = columns.find((c) => c.isPrimaryKey) ?? void 0;
|
|
2147
|
+
tables.push({ name, columns });
|
|
2148
|
+
return {
|
|
2149
|
+
$type: void 0,
|
|
2150
|
+
$insert: void 0,
|
|
2151
|
+
get: buildGet(sql, name, pk),
|
|
2152
|
+
list: buildList(sql, name, columns),
|
|
2153
|
+
create: buildCreate(sql, name, pk, zodSchema),
|
|
2154
|
+
patch: buildPatch(sql, name, pk, zodSchema),
|
|
2155
|
+
remove: buildRemove(sql, name, pk)
|
|
2156
|
+
};
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// postgres/migrate.ts
|
|
2161
|
+
function toDDL(col) {
|
|
2162
|
+
const parts = [`"${col.name}"`, col.sqlType];
|
|
2163
|
+
if (col.isPrimaryKey) parts.push("PRIMARY KEY");
|
|
2164
|
+
if (!col.isPrimaryKey && !col.nullable) parts.push("NOT NULL");
|
|
2165
|
+
if (col.defaultExpr) parts.push(`DEFAULT ${col.defaultExpr}`);
|
|
2166
|
+
return parts.join(" ");
|
|
2167
|
+
}
|
|
2168
|
+
function createTableSQL(name, columns) {
|
|
2169
|
+
const cols = columns.map(toDDL);
|
|
2170
|
+
return `CREATE TABLE IF NOT EXISTS "${name}" (
|
|
2171
|
+
${cols.join(",\n ")}
|
|
2172
|
+
)`;
|
|
2173
|
+
}
|
|
2174
|
+
function addColumnSQL(name, col) {
|
|
2175
|
+
return `ALTER TABLE "${name}" ADD COLUMN IF NOT EXISTS ${toDDL(col)}`;
|
|
2176
|
+
}
|
|
2177
|
+
async function runMigrations(sql, tables) {
|
|
2178
|
+
for (const table of tables) {
|
|
2179
|
+
const existing = await sql`
|
|
2180
|
+
SELECT column_name FROM information_schema.columns
|
|
2181
|
+
WHERE table_schema = 'public' AND table_name = ${table.name}
|
|
2182
|
+
`;
|
|
2183
|
+
if (existing.length === 0) {
|
|
2184
|
+
await sql.unsafe(createTableSQL(table.name, table.columns));
|
|
2185
|
+
} else {
|
|
2186
|
+
const colNames = new Set(existing.map((r) => r.column_name));
|
|
2187
|
+
for (const col of table.columns) {
|
|
2188
|
+
if (!colNames.has(col.name)) {
|
|
2189
|
+
await sql.unsafe(addColumnSQL(table.name, col));
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// postgres/client.ts
|
|
2197
|
+
function postgres(opts) {
|
|
2198
|
+
const options = typeof opts === "string" ? { connection: opts } : opts ?? {};
|
|
2199
|
+
const connection = options.connection ?? process.env.DATABASE_URL;
|
|
2200
|
+
if (!connection) {
|
|
2201
|
+
throw new Error(
|
|
2202
|
+
"postgres: DATABASE_URL is not set. Pass a connection string or set the DATABASE_URL environment variable."
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
const sql = postgresFactory(connection);
|
|
2206
|
+
const tables = [];
|
|
2207
|
+
if (options.signal) {
|
|
2208
|
+
options.signal.addEventListener("abort", () => {
|
|
2209
|
+
sql.end();
|
|
2210
|
+
}, { once: true });
|
|
2211
|
+
}
|
|
2212
|
+
const mw = ((req, ctx, next) => {
|
|
2213
|
+
ctx.sql = sql;
|
|
2214
|
+
return next(req, ctx);
|
|
2215
|
+
});
|
|
2216
|
+
mw.sql = sql;
|
|
2217
|
+
mw.table = buildTable(sql, tables);
|
|
2218
|
+
mw.migrate = () => runMigrations(sql, tables);
|
|
2219
|
+
mw.close = () => sql.end({ timeout: 5 });
|
|
2220
|
+
return mw;
|
|
2221
|
+
}
|
|
1469
2222
|
export {
|
|
1470
2223
|
Router,
|
|
1471
2224
|
TsxContext,
|
|
2225
|
+
ai,
|
|
1472
2226
|
auth,
|
|
1473
2227
|
compress,
|
|
1474
2228
|
cors,
|
|
2229
|
+
createSSEManager,
|
|
2230
|
+
createWorkflowEngine,
|
|
1475
2231
|
deleteCookie,
|
|
2232
|
+
generateWorkflow,
|
|
1476
2233
|
getCookies,
|
|
2234
|
+
graphql,
|
|
1477
2235
|
logger,
|
|
2236
|
+
postgres,
|
|
1478
2237
|
rateLimit,
|
|
1479
2238
|
serve,
|
|
1480
2239
|
serveStatic,
|
|
1481
2240
|
setCookie,
|
|
2241
|
+
tool,
|
|
1482
2242
|
tsx,
|
|
1483
2243
|
upload,
|
|
1484
2244
|
useTsx,
|
|
1485
|
-
validate
|
|
2245
|
+
validate,
|
|
2246
|
+
workflow
|
|
1486
2247
|
};
|