weifuwu 0.4.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/dist/index.js CHANGED
@@ -107,1896 +107,2122 @@ 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
-
114
- // workflow/tool.ts
115
- function tool(def) {
116
- return {
117
- name: def.name ?? "",
118
- description: def.description,
119
- inputSchema: def.inputSchema,
120
- execute: def.execute
121
- };
122
- }
123
-
124
- // workflow/reference.ts
125
- function getByPath(obj, path) {
126
- let current = obj;
127
- for (const key of path) {
128
- if (current === null || current === void 0) return void 0;
129
- if (typeof current === "object" && key in current) {
130
- current = current[key];
131
- } else {
132
- return void 0;
110
+ var createTrieNode = () => ({
111
+ children: /* @__PURE__ */ new Map(),
112
+ handlers: /* @__PURE__ */ new Map(),
113
+ middlewares: /* @__PURE__ */ new Map(),
114
+ pathMws: []
115
+ });
116
+ var createWsNode = () => ({
117
+ children: /* @__PURE__ */ new Map(),
118
+ middlewares: []
119
+ });
120
+ var getTrieNode = (node, segment) => {
121
+ if (segment.startsWith(":")) {
122
+ if (!node.children.has(":")) {
123
+ const child2 = createTrieNode();
124
+ child2.param = segment.slice(1);
125
+ node.children.set(":", child2);
133
126
  }
134
- }
135
- return current;
136
- }
137
- function resolveRef(path, ctx) {
138
- if (path.startsWith("$nodes.")) {
139
- const afterNodes = path.slice(7);
140
- const dotIdx = afterNodes.indexOf(".");
141
- if (dotIdx === -1) {
142
- return ctx.nodeOutputs.get(afterNodes);
127
+ const child = node.children.get(":");
128
+ if (child.param !== segment.slice(1)) {
129
+ throw new Error(
130
+ `Param name conflict: ":${child.param}" already registered at this path position, cannot register ":"${segment.slice(1)}"`
131
+ );
143
132
  }
144
- const id2 = afterNodes.slice(0, dotIdx);
145
- const propPath = afterNodes.slice(dotIdx + 1);
146
- const output = ctx.nodeOutputs.get(id2);
147
- if (output === void 0) {
148
- throw new Error(`Node "${id2}" has no output yet`);
133
+ return child;
134
+ }
135
+ if (!node.children.has(segment)) {
136
+ node.children.set(segment, createTrieNode());
137
+ }
138
+ return node.children.get(segment);
139
+ };
140
+ var matchTrieNode = (node, segment, params) => {
141
+ if (node.children.has(segment)) return node.children.get(segment);
142
+ if (node.children.has(":")) {
143
+ const child = node.children.get(":");
144
+ if (child.param) params[child.param] = segment;
145
+ return child;
146
+ }
147
+ return null;
148
+ };
149
+ var getWsNode = (node, segment) => {
150
+ if (segment.startsWith(":")) {
151
+ if (!node.children.has(":")) {
152
+ const child2 = createWsNode();
153
+ child2.param = segment.slice(1);
154
+ node.children.set(":", child2);
149
155
  }
150
- if (propPath.startsWith("output")) {
151
- return getByPath(output, propPath.slice(7).split(".").filter(Boolean));
156
+ const child = node.children.get(":");
157
+ if (child.param !== segment.slice(1)) {
158
+ throw new Error(
159
+ `Param name conflict: ":${child.param}" already registered at this path position`
160
+ );
152
161
  }
153
- return getByPath(output, propPath.split("."));
162
+ return child;
154
163
  }
155
- if (path.startsWith("$var.")) {
156
- const name = path.slice(5);
157
- if (!ctx.variables.has(name)) {
158
- throw new Error(`Variable "${name}" is not defined`);
164
+ if (!node.children.has(segment)) {
165
+ node.children.set(segment, createWsNode());
166
+ }
167
+ return node.children.get(segment);
168
+ };
169
+ var matchWsNode = (node, segment, params) => {
170
+ if (node.children.has(segment)) return node.children.get(segment);
171
+ if (node.children.has(":")) {
172
+ const child = node.children.get(":");
173
+ if (child.param) params[child.param] = segment;
174
+ return child;
175
+ }
176
+ return null;
177
+ };
178
+ var Router = class _Router {
179
+ root = createTrieNode();
180
+ wsRoot = createWsNode();
181
+ globalMws = [];
182
+ errorHandler;
183
+ use(arg1, arg2) {
184
+ if (typeof arg1 === "string") {
185
+ if (arg2 instanceof _Router) {
186
+ let node = this.root;
187
+ for (const segment of this.splitPath(arg1)) {
188
+ node = getTrieNode(node, segment);
189
+ }
190
+ node.subRouter = arg2;
191
+ } else if (typeof arg2 === "function") {
192
+ let node = this.root;
193
+ for (const segment of this.splitPath(arg1)) {
194
+ node = getTrieNode(node, segment);
195
+ }
196
+ node.pathMws.push(arg2);
197
+ }
198
+ } else if (typeof arg1 === "function") {
199
+ this.globalMws.push(arg1);
159
200
  }
160
- return ctx.variables.get(name);
201
+ return this;
161
202
  }
162
- if (path.startsWith("$input.")) {
163
- const key = path.slice(7);
164
- return ctx.input[key];
203
+ get(path, ...args) {
204
+ return this.route("GET", path, ...args);
165
205
  }
166
- if (path === "true") return true;
167
- if (path === "false") return false;
168
- if (path === "null") return null;
169
- const num = Number(path);
170
- if (!isNaN(num) && path.trim() !== "") return num;
171
- return path;
172
- }
173
- function resolveValue(v, ctx) {
174
- if (typeof v === "string" && v.startsWith("$")) {
175
- return resolveRef(v, ctx);
206
+ post(path, ...args) {
207
+ return this.route("POST", path, ...args);
176
208
  }
177
- if (Array.isArray(v)) {
178
- return v.map((item) => resolveValue(item, ctx));
209
+ put(path, ...args) {
210
+ return this.route("PUT", path, ...args);
179
211
  }
180
- if (typeof v === "object" && v !== null) {
181
- const result = {};
182
- for (const [k, val] of Object.entries(v)) {
183
- result[k] = resolveValue(val, ctx);
184
- }
185
- return result;
212
+ delete(path, ...args) {
213
+ return this.route("DELETE", path, ...args);
186
214
  }
187
- return v;
188
- }
189
-
190
- // workflow/nodes.ts
191
- function evaluateExpression(expr, ctx) {
192
- const operators = [
193
- { op: "===", fn: (a, b) => a === b },
194
- { op: "!==", fn: (a, b) => a !== b },
195
- { op: ">=", fn: (a, b) => Number(a) >= Number(b) },
196
- { op: "<=", fn: (a, b) => Number(a) <= Number(b) },
197
- { op: ">", fn: (a, b) => Number(a) > Number(b) },
198
- { op: "<", fn: (a, b) => Number(a) < Number(b) },
199
- { op: "==", fn: (a, b) => a == b },
200
- { op: "!=", fn: (a, b) => a != b },
201
- { op: "+", fn: (a, b) => Number(a) + Number(b) },
202
- { op: "-", fn: (a, b) => Number(a) - Number(b) },
203
- { op: "*", fn: (a, b) => Number(a) * Number(b) },
204
- { op: "/", fn: (a, b) => Number(a) / Number(b) },
205
- { op: "%", fn: (a, b) => Number(a) % Number(b) },
206
- { op: "&&", fn: (a, b) => Boolean(a) && Boolean(b) },
207
- { op: "||", fn: (a, b) => Boolean(a) || Boolean(b) }
208
- ];
209
- for (const { op, fn } of operators) {
210
- const idx = expr.indexOf(op);
211
- if (idx > 0) {
212
- const leftRaw = expr.slice(0, idx).trim();
213
- const rightRaw = expr.slice(idx + op.length).trim();
214
- const left = resolveValue(leftRaw, ctx);
215
- const right = resolveValue(rightRaw, ctx);
216
- return fn(left, right);
217
- }
215
+ patch(path, ...args) {
216
+ return this.route("PATCH", path, ...args);
218
217
  }
219
- const trimmed = expr.trim();
220
- if (trimmed === "true") return true;
221
- if (trimmed === "false") return false;
222
- if (trimmed === "null") return null;
223
- const num = Number(trimmed);
224
- if (!isNaN(num) && trimmed !== "") return num;
225
- return resolveValue(expr, ctx);
226
- }
227
- async function executeEval(node, ctx) {
228
- const expression = node.input.expression;
229
- if (!expression) throw new Error('eval node requires "expression" field');
230
- const result = evaluateExpression(expression, ctx);
231
- return { result };
232
- }
233
- async function executeSet(node, ctx) {
234
- const name = node.input.name;
235
- const value = node.input.value;
236
- if (!name) throw new Error('set node requires "name" field');
237
- let resolved;
238
- if (typeof value === "string") {
239
- resolved = evaluateExpression(value, ctx);
240
- } else {
241
- resolved = resolveValue(value ?? null, ctx);
218
+ head(path, ...args) {
219
+ return this.route("HEAD", path, ...args);
242
220
  }
243
- ctx.variables.set(name, resolved);
244
- return resolved;
245
- }
246
- async function executeGet(node, ctx) {
247
- const name = node.input.name;
248
- if (!name) throw new Error('get node requires "name" field');
249
- if (!ctx.variables.has(name)) {
250
- throw new Error(`Variable "${name}" is not defined`);
221
+ options(path, ...args) {
222
+ return this.route("OPTIONS", path, ...args);
251
223
  }
252
- return ctx.variables.get(name);
253
- }
254
- async function executeIf(node, ctx) {
255
- const conditions = node.conditions ?? [];
256
- for (const condition of conditions) {
257
- const test = typeof condition.test === "string" ? Boolean(resolveValue(condition.test, ctx)) : condition.test;
258
- if (test && condition.body) {
259
- let lastOutput = void 0;
260
- for (const bodyNode of condition.body) {
261
- lastOutput = await executeNode(bodyNode, ctx);
262
- }
263
- return lastOutput;
264
- }
224
+ all(path, ...args) {
225
+ return this.route("*", path, ...args);
265
226
  }
266
- return void 0;
267
- }
268
- async function executeWhile(node, ctx) {
269
- const conditionExpr = node.input.condition;
270
- if (!conditionExpr) throw new Error('while node requires "condition" field');
271
- let lastOutput = void 0;
272
- let iterations = 0;
273
- const maxIterations = 1e3;
274
- while (iterations < maxIterations) {
275
- iterations++;
276
- ctx.stepCount++;
277
- if (ctx.stepCount > ctx.maxSteps) {
278
- throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
279
- }
280
- const condition = Boolean(evaluateExpression(conditionExpr, ctx));
281
- if (!condition) break;
282
- for (const bodyNode of node.body ?? []) {
283
- lastOutput = await executeNode(bodyNode, ctx);
227
+ onError(handler) {
228
+ this.errorHandler = handler;
229
+ return this;
230
+ }
231
+ route(method, path, ...args) {
232
+ const handler = args.pop();
233
+ const middlewares = args;
234
+ const segments = this.splitPath(path);
235
+ let node = this.root;
236
+ for (const segment of segments) {
237
+ if (segment === "*") {
238
+ node.wildcard = true;
239
+ node.handlers.set(method, handler);
240
+ if (middlewares.length > 0) node.middlewares.set(method, middlewares);
241
+ return this;
242
+ }
243
+ node = getTrieNode(node, segment);
284
244
  }
245
+ node.handlers.set(method, handler);
246
+ if (middlewares.length > 0) node.middlewares.set(method, middlewares);
247
+ return this;
285
248
  }
286
- return lastOutput;
287
- }
288
- async function executeCall(node, ctx) {
289
- const toolName = node.input.tool;
290
- const args = node.input.args ?? {};
291
- if (toolName && ctx.toolRegistry.has(toolName)) {
292
- const tool3 = ctx.toolRegistry.get(toolName);
293
- const resolvedInput = resolveValue(args, ctx);
294
- const parsed = tool3.inputSchema.parse(resolvedInput);
295
- return tool3.execute(parsed, {
296
- nodeId: node.id,
297
- workflowId: ctx.workflowId,
298
- onStream: async (event) => {
299
- if (ctx.sseManager && ctx.workflowId) {
300
- ctx.sseManager.send(ctx.workflowId, { event: "llm-stream", data: { nodeId: node.id, ...event } });
249
+ ws(path, ...args) {
250
+ const handler = args.pop();
251
+ const middlewares = args;
252
+ const segments = this.splitPath(path);
253
+ let node = this.wsRoot;
254
+ for (const segment of segments) {
255
+ node = getWsNode(node, segment);
256
+ }
257
+ node.handler = handler;
258
+ if (middlewares.length > 0) node.middlewares = middlewares;
259
+ return this;
260
+ }
261
+ handler() {
262
+ return (req, ctx) => {
263
+ const url = new URL(req.url);
264
+ return this.handle(req, ctx, this.splitPath(url.pathname), Object.fromEntries(url.searchParams));
265
+ };
266
+ }
267
+ websocketHandler() {
268
+ const wss = new WebSocketServer({ noServer: true });
269
+ const wsRoot = this.wsRoot;
270
+ const router = this;
271
+ return (req, socket, head) => {
272
+ const url = new URL(req.url ?? "/", "http://localhost");
273
+ const segments = url.pathname.split("/").filter(Boolean);
274
+ const query = Object.fromEntries(url.searchParams);
275
+ const match = router.matchWsTrie(wsRoot, segments);
276
+ if (!match) {
277
+ socket.destroy();
278
+ return;
279
+ }
280
+ const webReq = new Request(url.href, {
281
+ method: req.method ?? "GET",
282
+ headers: Object.fromEntries(
283
+ Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : v ?? ""])
284
+ )
285
+ });
286
+ const ctx = { params: match.params, query };
287
+ if (match.middlewares.length === 0) {
288
+ upgradeSocket(wss, req, socket, head, match.handler, ctx);
289
+ return;
290
+ }
291
+ let index = 0;
292
+ const dispatch = async (innerReq, ctx2) => {
293
+ if (index < match.middlewares.length) {
294
+ const mw = match.middlewares[index++];
295
+ return mw(innerReq, ctx2, dispatch);
296
+ }
297
+ return await new Promise((resolve3) => {
298
+ upgradeSocket(wss, req, socket, head, match.handler, ctx2);
299
+ resolve3(new Response(null, { status: 101 }));
300
+ });
301
+ };
302
+ Promise.resolve(dispatch(webReq, ctx)).then((result) => {
303
+ if (result.status !== 101) {
304
+ sendHttpResponseOnSocket(socket, result);
305
+ }
306
+ }).catch(() => {
307
+ socket.destroy();
308
+ });
309
+ };
310
+ }
311
+ splitPath(path) {
312
+ return path.split("/").filter(Boolean);
313
+ }
314
+ matchTrie(method, segments) {
315
+ let node = this.root;
316
+ const params = {};
317
+ const pathMws = [...this.root.pathMws];
318
+ let wildcardHandler = null;
319
+ let wildcardMws = [];
320
+ let wildcardIdx = -1;
321
+ for (let i = 0; i < segments.length; i++) {
322
+ pathMws.push(...node.pathMws);
323
+ if (node.wildcard) {
324
+ const h = node.handlers.get("*") || node.handlers.get(method);
325
+ if (h) {
326
+ wildcardHandler = h;
327
+ wildcardMws = node.middlewares.get(method) || node.middlewares.get("*") || [];
328
+ wildcardIdx = i;
301
329
  }
302
330
  }
303
- });
331
+ const segment = segments[i];
332
+ if (!segment) break;
333
+ const next = matchTrieNode(node, segment, params);
334
+ if (!next) {
335
+ if (node.subRouter) {
336
+ return {
337
+ pathMws,
338
+ params,
339
+ middlewares: [],
340
+ subRouter: { router: node.subRouter, remainingIdx: i }
341
+ };
342
+ }
343
+ if (wildcardHandler) {
344
+ params["*"] = segments.slice(wildcardIdx).join("/");
345
+ return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
346
+ }
347
+ return null;
348
+ }
349
+ node = next;
350
+ }
351
+ if (node.subRouter) {
352
+ return {
353
+ pathMws,
354
+ params,
355
+ middlewares: [],
356
+ subRouter: { router: node.subRouter, remainingIdx: segments.length }
357
+ };
358
+ }
359
+ pathMws.push(...node.pathMws);
360
+ const handler = node.handlers.get(method) || node.handlers.get("*");
361
+ if (handler) {
362
+ if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
363
+ return {
364
+ handler,
365
+ middlewares: node.middlewares.get(method) || node.middlewares.get("*") || [],
366
+ pathMws,
367
+ params
368
+ };
369
+ }
370
+ if (wildcardHandler) {
371
+ params["*"] = segments.slice(wildcardIdx).join("/");
372
+ return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
373
+ }
374
+ return null;
304
375
  }
305
- const functionName = node.input.function;
306
- if (functionName && ctx.functions[functionName]) {
307
- const fn = ctx.functions[functionName];
308
- const prevFunctions = ctx.functions;
309
- const prevInput = ctx.input;
310
- ctx.input = resolveValue(args, ctx);
311
- let lastOutput = void 0;
312
- for (const bodyNode of fn.workflow.nodes) {
313
- lastOutput = await executeNode(bodyNode, ctx);
376
+ matchWsTrie(root, segments) {
377
+ let node = root;
378
+ const params = {};
379
+ for (const segment of segments) {
380
+ const next = matchWsNode(node, segment, params);
381
+ if (!next) return null;
382
+ node = next;
314
383
  }
315
- ctx.input = prevInput;
316
- return lastOutput;
384
+ return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
317
385
  }
318
- throw new Error(`call node: tool "${toolName ?? functionName}" not found`);
319
- }
320
- async function executeHttp(node, ctx) {
321
- const input = resolveValue(node.input, ctx);
322
- const url = input.url;
323
- if (!url) throw new Error('http node requires "url" field');
324
- const controller = new AbortController();
325
- const timeout = input.timeout ?? 3e4;
326
- const timer = setTimeout(() => controller.abort(), timeout);
327
- try {
328
- const fetchInit = {
329
- method: input.method ?? "GET",
330
- headers: input.headers ?? {},
331
- signal: controller.signal
332
- };
333
- if (input.body && fetchInit.method !== "GET") {
334
- fetchInit.body = JSON.stringify(input.body);
386
+ async handle(req, ctx, segments, query) {
387
+ const match = this.matchTrie(req.method, segments);
388
+ if (match?.subRouter) {
389
+ const { router: sub, remainingIdx } = match.subRouter;
390
+ const remainingSegments = segments.slice(remainingIdx);
391
+ const delegate = (req2, ctx2) => sub.handle(req2, ctx2, remainingSegments, query);
392
+ const allMws = this.globalMws.length + match.pathMws.length === 0 ? [] : [...this.globalMws, ...match.pathMws];
393
+ try {
394
+ return await this.runChain(allMws, delegate, req, { ...ctx, params: { ...ctx.params, ...match.params } });
395
+ } catch (e) {
396
+ return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
397
+ }
335
398
  }
336
- const response = await fetch(url, fetchInit);
337
- const contentType = response.headers.get("content-type") ?? "";
338
- const body = contentType.includes("application/json") ? await response.json() : await response.text();
339
- return {
340
- status: response.status,
341
- statusText: response.statusText,
342
- headers: Object.fromEntries(response.headers.entries()),
343
- body
399
+ if (match?.handler) {
400
+ const { handler, middlewares: routeMws, pathMws, params } = match;
401
+ const allMws = this.globalMws.length + pathMws.length + routeMws.length === 0 ? [] : [...this.globalMws, ...pathMws, ...routeMws];
402
+ const ctxWithMatch = { ...ctx, params: { ...ctx.params, ...params } };
403
+ try {
404
+ return await this.runChain(allMws, handler, req, ctxWithMatch);
405
+ } catch (e) {
406
+ return this.errorHandler ? this.errorHandler(e, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
407
+ }
408
+ }
409
+ if (this.globalMws.length > 0) {
410
+ try {
411
+ const delegate = () => new Response("Not Found", { status: 404 });
412
+ return await this.runChain(this.globalMws, delegate, req, ctx);
413
+ } catch (e) {
414
+ return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
415
+ }
416
+ }
417
+ return new Response("Not Found", { status: 404 });
418
+ }
419
+ async runChain(middlewares, finalHandler, req, ctx) {
420
+ let index = 0;
421
+ const dispatch = async (req2, ctx2) => {
422
+ if (index < middlewares.length) {
423
+ const mw = middlewares[index++];
424
+ return mw ? await mw(req2, ctx2, dispatch) : new Response("Middleware error", { status: 500 });
425
+ }
426
+ return await finalHandler(req2, ctx2);
344
427
  };
345
- } finally {
346
- clearTimeout(timer);
428
+ return dispatch(req, ctx);
347
429
  }
348
- }
349
- var executors = {
350
- eval: executeEval,
351
- set: executeSet,
352
- get: executeGet,
353
- if: executeIf,
354
- while: executeWhile,
355
- call: executeCall,
356
- http: executeHttp
357
430
  };
358
- async function executeNode(node, ctx) {
359
- const executor = executors[node.tool];
360
- if (!executor) {
361
- throw new Error(`Unknown node type: "${node.tool}"`);
362
- }
363
- return executor(node, ctx);
431
+ function upgradeSocket(wss, req, socket, head, handler, ctx) {
432
+ wss.handleUpgrade(req, socket, head, (ws) => {
433
+ if (handler.open) {
434
+ handler.open(ws, ctx);
435
+ }
436
+ ws.on("message", (data) => {
437
+ handler.message?.(ws, ctx, data);
438
+ });
439
+ ws.on("close", () => {
440
+ handler.close?.(ws, ctx);
441
+ });
442
+ ws.on("error", (err) => {
443
+ handler.error?.(ws, ctx, err);
444
+ });
445
+ });
364
446
  }
365
-
366
- // workflow/llm.ts
367
- function buildToolsDescription(tools) {
368
- return Object.entries(tools).map(([key, t]) => {
369
- const name = t.name || key;
370
- const schema = t.inputSchema;
371
- return `- ${name}: ${t.description}
372
- Input schema: describe as JSON object fields`;
373
- }).join("\n");
447
+ function sendHttpResponseOnSocket(socket, response) {
448
+ const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
449
+ const headerLines = [statusLine];
450
+ response.headers.forEach((value, key) => {
451
+ headerLines.push(`${key}: ${value}`);
452
+ });
453
+ headerLines.push("Connection: close");
454
+ headerLines.push("");
455
+ const headerStr = headerLines.join("\r\n");
456
+ response.arrayBuffer().then((buf) => {
457
+ const body = Buffer.from(buf);
458
+ socket.write(headerStr + "\r\n" + body.toString());
459
+ socket.end();
460
+ }).catch(() => {
461
+ socket.write(headerStr + "\r\n");
462
+ socket.end();
463
+ });
374
464
  }
375
- var SYSTEM_PROMPT_TEMPLATE = `You are a workflow generator. Given a user goal and available tools, output a workflow JSON.
376
-
377
- Available tools:
378
- {{TOOLS}}
379
465
 
380
- Workflow format:
381
- {
382
- "name": "workflow name",
383
- "nodes": [
384
- {
385
- "id": "step1",
386
- "tool": "set",
387
- "input": { "name": "varName", "value": "initialValue" }
388
- },
389
- {
390
- "id": "step2",
391
- "tool": "call",
392
- "input": { "tool": "toolName", "args": { "param1": "$var.varName" } }
393
- },
394
- {
395
- "id": "step3",
396
- "tool": "if",
397
- "input": {},
398
- "conditions": [
399
- { "test": "$nodes.step2.output.someField", "body": [
400
- { "id": "step4", "tool": "call", "input": { "tool": "toolName", "args": {} } }
401
- ]}
402
- ]
403
- }
404
- ]
466
+ // tsx.ts
467
+ import { createElement, createContext, useContext } from "react";
468
+ import { renderToReadableStream } from "react-dom/server";
469
+ import * as esbuild from "esbuild";
470
+ import { readdirSync, statSync, existsSync, mkdirSync } from "node:fs";
471
+ import { join, relative, resolve, sep, dirname } from "node:path";
472
+ import { pathToFileURL } from "node:url";
473
+ import { createHash } from "node:crypto";
474
+ var TsxContext = createContext({ params: {}, query: {} });
475
+ function useTsx() {
476
+ return useContext(TsxContext);
405
477
  }
406
-
407
- Node types:
408
- - eval: evaluate an expression. input: { expression: "..." }
409
- - set: assign a variable. input: { name, value }
410
- - get: read a variable. input: { name }
411
- - if: conditional branch. input: {}, conditions: [{ test, body }]
412
- - while: loop. input: { condition }, body: [nodes]
413
- - call: call a registered tool. input: { tool, args }
414
- - http: HTTP request. input: { url, method?, headers?, body? }
415
-
416
- Reference syntax:
417
- - $var.name - read a variable
418
- - $nodes.id.output - output of a previous node
419
- - $nodes.id.output.field - specific field of a node's output
420
- - $input.field - workflow input parameter
421
-
422
- Output ONLY valid JSON. No explanation, no markdown.`;
423
- async function generateWorkflow(goal, tools, generateFn) {
424
- const toolsDesc = buildToolsDescription(tools);
425
- const system = SYSTEM_PROMPT_TEMPLATE.replace("{{TOOLS}}", toolsDesc);
426
- const result = await generateFn({
427
- system,
428
- messages: [{ role: "user", content: goal }]
429
- });
430
- const text = result.text.trim();
431
- const jsonStart = text.indexOf("{");
432
- const jsonEnd = text.lastIndexOf("}");
433
- if (jsonStart === -1 || jsonEnd === -1) {
434
- throw new Error(`LLM output is not valid JSON: ${text.slice(0, 200)}`);
478
+ function id(s) {
479
+ return createHash("md5").update(s).digest("hex").slice(0, 8);
480
+ }
481
+ function concatUint8(chunks) {
482
+ const len = chunks.reduce((a, c) => a + c.length, 0);
483
+ const out = new Uint8Array(len);
484
+ let off = 0;
485
+ for (const c of chunks) {
486
+ out.set(c, off);
487
+ off += c.length;
435
488
  }
436
- const jsonStr = text.slice(jsonStart, jsonEnd + 1);
437
- try {
438
- const workflow = JSON.parse(jsonStr);
439
- if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
440
- throw new Error("Generated workflow has no nodes array");
489
+ return out;
490
+ }
491
+ async function readStream(stream) {
492
+ const chunks = [];
493
+ const reader = stream.getReader();
494
+ while (true) {
495
+ const { done, value } = await reader.read();
496
+ if (done) break;
497
+ chunks.push(value);
498
+ }
499
+ return new TextDecoder().decode(concatUint8(chunks));
500
+ }
501
+ function scanPages(dir) {
502
+ const pages = [];
503
+ function walk(current) {
504
+ let entries;
505
+ try {
506
+ entries = readdirSync(current);
507
+ } catch {
508
+ return;
441
509
  }
442
- return workflow;
443
- } catch (err) {
444
- if (err instanceof SyntaxError) {
445
- throw new Error(`Failed to parse LLM output as JSON: ${err.message}`);
510
+ const dirs = [];
511
+ for (const name of entries) {
512
+ const full = join(current, name);
513
+ const st = statSync(full);
514
+ if (st.isDirectory()) {
515
+ if (!name.startsWith(".")) dirs.push(full);
516
+ }
446
517
  }
447
- throw err;
518
+ const pagePath = join(current, "page.tsx");
519
+ const tsPagePath = join(current, "page.ts");
520
+ let entryPath = "";
521
+ if (existsSync(pagePath)) {
522
+ entryPath = pagePath;
523
+ } else if (existsSync(tsPagePath)) {
524
+ entryPath = tsPagePath;
525
+ }
526
+ if (entryPath) {
527
+ let relPath = relative(dir, entryPath).replace(sep, "/");
528
+ relPath = relPath.replace(/\/page\.tsx?$/, "");
529
+ relPath = relPath.replace(/^page\.tsx?$/, "");
530
+ const route = filePathToRoute(relPath);
531
+ const layouts = resolveLayouts(current, dir);
532
+ const loadPath = existsSync(join(current, "load.ts")) ? join(current, "load.ts") : void 0;
533
+ const rPath = existsSync(join(current, "route.ts")) ? join(current, "route.ts") : void 0;
534
+ pages.push({
535
+ route,
536
+ entryPath,
537
+ loadPath,
538
+ layouts,
539
+ routePath: rPath
540
+ });
541
+ } else {
542
+ const rPath = join(current, "route.ts");
543
+ if (existsSync(rPath)) {
544
+ let relPath = relative(dir, rPath).replace(sep, "/");
545
+ relPath = relPath.replace(/\/route\.tsx?$/, "");
546
+ const route = filePathToRoute(relPath);
547
+ pages.push({
548
+ route,
549
+ entryPath: "",
550
+ layouts: [],
551
+ routePath: rPath,
552
+ routeOnly: true
553
+ });
554
+ }
555
+ }
556
+ for (const d of dirs) walk(d);
448
557
  }
558
+ walk(dir);
559
+ return pages;
560
+ }
561
+ function filePathToRoute(relPath) {
562
+ let route = relPath.replace(/\\/g, "/");
563
+ route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
564
+ route = route.replace(/\[(\w+)\]/g, ":$1");
565
+ return route.startsWith("/") ? route : "/" + route;
566
+ }
567
+ function resolveLayouts(dir, pagesDir) {
568
+ const layouts = [];
569
+ let current = dir;
570
+ while (current.startsWith(pagesDir)) {
571
+ const p = join(current, "layout.tsx");
572
+ if (existsSync(p)) {
573
+ layouts.push(p);
574
+ }
575
+ const parent = dirname(current);
576
+ if (parent === current) break;
577
+ current = parent;
578
+ }
579
+ return layouts.reverse();
580
+ }
581
+ async function compileAll(files, outDir, platform) {
582
+ const entryPoints = {};
583
+ for (const f of files) {
584
+ entryPoints[id(f)] = f;
585
+ }
586
+ const isBrowser = platform === "browser";
587
+ await esbuild.build({
588
+ entryPoints,
589
+ outdir: outDir,
590
+ format: "esm",
591
+ platform: "node",
592
+ jsx: "automatic",
593
+ jsxImportSource: "react",
594
+ bundle: true,
595
+ external: isBrowser ? void 0 : [
596
+ "react",
597
+ "react-dom",
598
+ "esbuild",
599
+ "graphql",
600
+ "ws",
601
+ "zod",
602
+ "@graphql-tools/schema",
603
+ "ai"
604
+ ],
605
+ write: true,
606
+ allowOverwrite: true
607
+ });
449
608
  }
450
-
451
- // workflow/engine.ts
452
- import { generateText } from "ai";
453
- function createWorkflowEngine(options) {
454
- const toolRegistry = /* @__PURE__ */ new Map();
455
- for (const [key, t] of Object.entries(options.tools)) {
456
- t.name = t.name || key;
457
- toolRegistry.set(t.name, t);
458
- }
459
- const states = /* @__PURE__ */ new Map();
460
- async function execute(workflow, opts) {
461
- const ctx = {
462
- variables: /* @__PURE__ */ new Map(),
463
- nodeOutputs: /* @__PURE__ */ new Map(),
464
- functions: workflow.functions ?? {},
465
- stepCount: 0,
466
- maxSteps: opts?.maxSteps ?? 1e3,
467
- input: opts?.initialInput ?? {},
468
- toolRegistry,
469
- sseManager: options.sseManager,
470
- workflowId: opts?.workflowId
471
- };
472
- let lastOutput = void 0;
473
- for (const node of workflow.nodes) {
474
- ctx.stepCount++;
475
- if (ctx.stepCount > ctx.maxSteps) {
476
- throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
609
+ function compiledUrl(filePath, outDir) {
610
+ const hash = id(join(outDir, id(filePath)));
611
+ const p = join(outDir, id(filePath) + ".js");
612
+ return pathToFileURL(p).href;
613
+ }
614
+ var clientBundleCache = /* @__PURE__ */ new Map();
615
+ var clientRouteLog = /* @__PURE__ */ new WeakMap();
616
+ async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router) {
617
+ const key = id(entryPath);
618
+ const url = `/__wfw/client/${key}.js`;
619
+ if (!clientRouteLog.get(router)?.has(url)) {
620
+ let buf = clientBundleCache.get(key);
621
+ if (!buf) {
622
+ try {
623
+ const nested = layoutPaths.slice(1);
624
+ const layoutsImport = nested.map(
625
+ (p, i) => `import L${i} from${JSON.stringify(p)};`
626
+ ).join("");
627
+ const layoutsWrap = nested.map((_, i) => {
628
+ const idx = nested.length - 1 - i;
629
+ return `el=createElement(L${idx},null,el);`;
630
+ }).join("");
631
+ const code = [
632
+ `import{hydrateRoot}from'react-dom/client';`,
633
+ `import{createElement}from'react';`,
634
+ `import P from${JSON.stringify(entryPath)};`,
635
+ layoutsImport,
636
+ `const p=window.__WEIFUWU_PROPS;`,
637
+ `let el=createElement(P,p);`,
638
+ layoutsWrap,
639
+ `hydrateRoot(document.getElementById('__weifuwu_root'),el);`
640
+ ].join("");
641
+ const result = await esbuild.build({
642
+ stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
643
+ bundle: true,
644
+ format: "esm",
645
+ jsx: "automatic",
646
+ jsxImportSource: "react",
647
+ write: false,
648
+ minify: true
649
+ });
650
+ buf = result.outputFiles[0].contents;
651
+ clientBundleCache.set(key, buf);
652
+ } catch (err) {
653
+ console.error("hydration bundle failed:", err);
654
+ return null;
477
655
  }
478
- options.sseManager?.send(ctx.workflowId ?? "", { event: "node-start", data: { nodeId: node.id, tool: node.tool, input: node.input } });
479
- const output = await executeNode(node, ctx);
480
- ctx.nodeOutputs.set(node.id, output);
481
- lastOutput = output;
482
- options.sseManager?.send(ctx.workflowId ?? "", { event: "node-end", data: { nodeId: node.id, output } });
483
656
  }
484
- return lastOutput;
657
+ router.get(url, () => new Response(buf, {
658
+ headers: { "content-type": "application/javascript; charset=utf-8" }
659
+ }));
660
+ const set = clientRouteLog.get(router) ?? /* @__PURE__ */ new Set();
661
+ set.add(url);
662
+ clientRouteLog.set(router, set);
485
663
  }
486
- async function runAsync(workflowId, workflow, opts) {
487
- const state = {
488
- workflowId,
489
- status: "running",
490
- goal: workflow.name ?? "",
491
- startTime: Date.now()
492
- };
493
- states.set(workflowId, state);
494
- const sse = options.sseManager;
495
- sse?.send(workflowId, { event: "workflow-start", data: { workflowId, goal: state.goal } });
496
- try {
497
- const result = await execute(workflow, { ...opts, workflowId });
498
- state.status = "completed";
499
- state.result = result;
500
- state.endTime = Date.now();
501
- sse?.send(workflowId, { event: "complete", data: { result, duration: state.endTime - state.startTime } });
502
- } catch (err) {
503
- state.status = "error";
504
- state.error = err instanceof Error ? err.message : String(err);
505
- state.endTime = Date.now();
506
- sse?.send(workflowId, { event: "error", data: { error: state.error } });
507
- } finally {
508
- sse?.close(workflowId);
664
+ return { url };
665
+ }
666
+ function makeSsrHandler(Component, loadFn, layouts, entryPath, layoutPaths, pagesDir, router) {
667
+ return async (req, ctx) => {
668
+ const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {};
669
+ const allProps = { ...loadProps, params: ctx.params, query: ctx.query };
670
+ let element = createElement(Component, allProps);
671
+ for (let i = layouts.length - 1; i >= 0; i--) {
672
+ const isRoot = i === 0;
673
+ element = createElement(
674
+ layouts[i],
675
+ isRoot ? { children: element, req, ctx } : { children: element }
676
+ );
509
677
  }
510
- }
511
- async function generateWorkflow2(goal) {
512
- if (!options.model) {
513
- throw new Error('LLM model is required for generateWorkflow. Pass "model" to createWorkflowEngine.');
678
+ element = createElement(TsxContext.Provider, {
679
+ value: { params: ctx.params, query: ctx.query, user: ctx.user, parsed: ctx.parsed }
680
+ }, element);
681
+ const stream = await renderToReadableStream(element);
682
+ const body = await readStream(stream);
683
+ const scripts = [];
684
+ scripts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(allProps)}</script>`);
685
+ const bundle = await getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router);
686
+ if (bundle) {
687
+ scripts.push(`<script type="module" src="${bundle.url}"></script>`);
514
688
  }
515
- return generateWorkflow(goal, options.tools, async (prompt) => {
516
- const result = await generateText({
517
- model: options.model,
518
- system: prompt.system,
519
- messages: prompt.messages
520
- });
521
- return { text: result.text };
689
+ const html = `<!DOCTYPE html>
690
+ ${body}
691
+ ${scripts.join("\n")}`;
692
+ return new Response(html, {
693
+ headers: { "content-type": "text/html; charset=utf-8" }
522
694
  });
523
- }
524
- return {
525
- execute,
526
- runAsync,
527
- generateWorkflow: generateWorkflow2,
528
- getState(workflowId) {
529
- return states.get(workflowId);
530
- }
531
695
  };
532
696
  }
533
-
534
- // workflow/sse.ts
535
- function createSSEManager() {
536
- const streams = /* @__PURE__ */ new Map();
537
- const encoder = new TextEncoder();
538
- function createStream(workflowId) {
539
- const state = {
540
- controller: null,
541
- encoder,
542
- closed: false,
543
- buffer: []
544
- };
545
- const stream = new ReadableStream({
546
- start(controller) {
547
- state.controller = controller;
548
- streams.set(workflowId, state);
549
- for (const event of state.buffer) {
550
- try {
551
- controller.enqueue(encoder.encode(event));
552
- } catch {
553
- break;
554
- }
697
+ async function tsx(options) {
698
+ const pagesDir = resolve(options.dir);
699
+ const outDir = join(pagesDir, "..", ".weifuwu", "ssr");
700
+ const pages = scanPages(pagesDir);
701
+ if (pages.length === 0) return new Router();
702
+ const allFiles = /* @__PURE__ */ new Set();
703
+ for (const p of pages) {
704
+ if (p.entryPath) allFiles.add(p.entryPath);
705
+ if (p.loadPath) allFiles.add(p.loadPath);
706
+ for (const lp of p.layouts) allFiles.add(lp);
707
+ if (p.routePath) allFiles.add(p.routePath);
708
+ }
709
+ const nfPath = join(pagesDir, "not-found.tsx");
710
+ const hasNotFound = existsSync(nfPath);
711
+ if (hasNotFound) {
712
+ allFiles.add(nfPath);
713
+ const rootLayouts = resolveLayouts(pagesDir, pagesDir);
714
+ for (const lp of rootLayouts) allFiles.add(lp);
715
+ }
716
+ mkdirSync(outDir, { recursive: true });
717
+ await compileAll([...allFiles], outDir, "node");
718
+ const router = new Router();
719
+ for (const p of pages) {
720
+ if (p.routeOnly && p.routePath) {
721
+ const rUrl = compiledUrl(p.routePath, outDir);
722
+ const modR = await import(rUrl);
723
+ const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
724
+ for (const method of methods) {
725
+ if (modR[method]) {
726
+ router.route(method, p.route, modR[method]);
555
727
  }
556
- state.buffer = [];
557
- },
558
- cancel() {
559
- state.closed = true;
560
- streams.delete(workflowId);
561
728
  }
562
- });
563
- return stream;
564
- }
565
- function send(workflowId, event) {
566
- const state = streams.get(workflowId);
567
- if (!state || state.closed) return;
568
- const data = `event: ${event.event}
569
- data: ${JSON.stringify(event.data)}
570
-
571
- `;
572
- if (state.controller) {
573
- try {
574
- state.controller.enqueue(encoder.encode(data));
575
- } catch {
576
- state.closed = true;
577
- streams.delete(workflowId);
729
+ continue;
730
+ }
731
+ const url = compiledUrl(p.entryPath, outDir);
732
+ const mod = await import(url);
733
+ const Component = mod.default;
734
+ let loadFn;
735
+ if (p.loadPath) {
736
+ const loadUrl = compiledUrl(p.loadPath, outDir);
737
+ const modLoad = await import(loadUrl);
738
+ loadFn = modLoad.default;
739
+ }
740
+ const layoutComponents = [];
741
+ for (const lp of p.layouts) {
742
+ const lUrl = compiledUrl(lp, outDir);
743
+ const modL = await import(lUrl);
744
+ layoutComponents.push(modL.default);
745
+ }
746
+ const handler = makeSsrHandler(
747
+ Component,
748
+ loadFn,
749
+ layoutComponents,
750
+ p.entryPath,
751
+ p.layouts,
752
+ pagesDir,
753
+ router
754
+ );
755
+ router.get(p.route, handler);
756
+ if (p.routePath) {
757
+ const rUrl = compiledUrl(p.routePath, outDir);
758
+ const modR = await import(rUrl);
759
+ const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
760
+ for (const method of methods) {
761
+ if (modR[method]) {
762
+ router.route(method, p.route, modR[method]);
763
+ }
578
764
  }
579
- } else {
580
- state.buffer.push(data);
581
765
  }
582
766
  }
583
- function close(workflowId) {
584
- const state = streams.get(workflowId);
585
- if (!state) return;
586
- state.closed = true;
587
- streams.delete(workflowId);
588
- try {
589
- state.controller?.close();
590
- } catch {
767
+ if (hasNotFound) {
768
+ const nfUrl = compiledUrl(nfPath, outDir);
769
+ const modNf = await import(nfUrl);
770
+ const NfComponent = modNf.default;
771
+ const nfLayouts = [];
772
+ const rootLayouts = resolveLayouts(pagesDir, pagesDir);
773
+ for (const lp of rootLayouts) {
774
+ const lUrl = compiledUrl(lp, outDir);
775
+ const modL = await import(lUrl);
776
+ nfLayouts.push(modL.default);
591
777
  }
778
+ const handler = async (req, ctx) => {
779
+ let element = createElement(NfComponent, { params: ctx.params, query: ctx.query });
780
+ for (let i = nfLayouts.length - 1; i >= 0; i--) {
781
+ element = createElement(nfLayouts[i], { children: element });
782
+ }
783
+ element = createElement(TsxContext.Provider, {
784
+ value: { params: ctx.params, query: ctx.query, user: ctx.user, parsed: ctx.parsed }
785
+ }, element);
786
+ const stream = await renderToReadableStream(element);
787
+ const body = await readStream(stream);
788
+ const html = `<!DOCTYPE html>
789
+ ${body}`;
790
+ return new Response(html, {
791
+ status: 404,
792
+ headers: { "content-type": "text/html; charset=utf-8" }
793
+ });
794
+ };
795
+ router.all("/*", handler);
592
796
  }
593
- return { createStream, send, close };
797
+ return router;
594
798
  }
595
799
 
596
- // router.ts
597
- var createTrieNode = () => ({
598
- children: /* @__PURE__ */ new Map(),
599
- handlers: /* @__PURE__ */ new Map(),
600
- middlewares: /* @__PURE__ */ new Map(),
601
- pathMws: []
602
- });
603
- var createWsNode = () => ({
604
- children: /* @__PURE__ */ new Map(),
605
- middlewares: []
606
- });
607
- var getTrieNode = (node, segment) => {
608
- if (segment.startsWith(":")) {
609
- if (!node.children.has(":")) {
610
- const child2 = createTrieNode();
611
- child2.param = segment.slice(1);
612
- node.children.set(":", child2);
613
- }
614
- const child = node.children.get(":");
615
- if (child.param !== segment.slice(1)) {
616
- throw new Error(
617
- `Param name conflict: ":${child.param}" already registered at this path position, cannot register ":"${segment.slice(1)}"`
618
- );
619
- }
620
- return child;
621
- }
622
- if (!node.children.has(segment)) {
623
- node.children.set(segment, createTrieNode());
624
- }
625
- return node.children.get(segment);
626
- };
627
- var matchTrieNode = (node, segment, params) => {
628
- if (node.children.has(segment)) return node.children.get(segment);
629
- if (node.children.has(":")) {
630
- const child = node.children.get(":");
631
- if (child.param) params[child.param] = segment;
632
- return child;
633
- }
634
- return null;
635
- };
636
- var getWsNode = (node, segment) => {
637
- if (segment.startsWith(":")) {
638
- if (!node.children.has(":")) {
639
- const child2 = createWsNode();
640
- child2.param = segment.slice(1);
641
- node.children.set(":", child2);
642
- }
643
- const child = node.children.get(":");
644
- if (child.param !== segment.slice(1)) {
645
- throw new Error(
646
- `Param name conflict: ":${child.param}" already registered at this path position`
647
- );
800
+ // middleware.ts
801
+ function logger(options) {
802
+ return async (req, ctx, next) => {
803
+ const start = Date.now();
804
+ const url = new URL(req.url);
805
+ const res = await next(req, ctx);
806
+ const ms = Date.now() - start;
807
+ if (options?.format === "combined") {
808
+ console.log(`${req.method} ${url.pathname}${url.search} ${res.status} ${ms}ms`);
809
+ } else {
810
+ console.log(`${req.method} ${url.pathname} ${res.status} ${ms}ms`);
648
811
  }
649
- return child;
650
- }
651
- if (!node.children.has(segment)) {
652
- node.children.set(segment, createWsNode());
653
- }
654
- return node.children.get(segment);
655
- };
656
- var matchWsNode = (node, segment, params) => {
657
- if (node.children.has(segment)) return node.children.get(segment);
658
- if (node.children.has(":")) {
659
- const child = node.children.get(":");
660
- if (child.param) params[child.param] = segment;
661
- return child;
662
- }
663
- return null;
664
- };
665
- var Router = class _Router {
666
- root = createTrieNode();
667
- wsRoot = createWsNode();
668
- globalMws = [];
669
- errorHandler;
670
- use(arg1, arg2) {
671
- if (typeof arg1 === "string") {
672
- if (arg2 instanceof _Router) {
673
- let node = this.root;
674
- for (const segment of this.splitPath(arg1)) {
675
- node = getTrieNode(node, segment);
676
- }
677
- node.subRouter = arg2;
678
- } else if (typeof arg2 === "function") {
679
- let node = this.root;
680
- for (const segment of this.splitPath(arg1)) {
681
- node = getTrieNode(node, segment);
682
- }
683
- node.pathMws.push(arg2);
684
- }
685
- } else if (typeof arg1 === "function") {
686
- this.globalMws.push(arg1);
812
+ return res;
813
+ };
814
+ }
815
+ function cors(options) {
816
+ const opts = {
817
+ origin: "*",
818
+ methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
819
+ allowedHeaders: ["Content-Type", "Authorization"],
820
+ ...options
821
+ };
822
+ function resolveOrigin(requestOrigin) {
823
+ if (typeof opts.origin === "string") return opts.origin === "*" ? "*" : opts.origin;
824
+ if (Array.isArray(opts.origin)) {
825
+ return opts.origin.includes(requestOrigin) ? requestOrigin : "";
687
826
  }
688
- return this;
689
- }
690
- get(path, ...args) {
691
- return this.route("GET", path, ...args);
692
- }
693
- post(path, ...args) {
694
- return this.route("POST", path, ...args);
695
- }
696
- put(path, ...args) {
697
- return this.route("PUT", path, ...args);
698
- }
699
- delete(path, ...args) {
700
- return this.route("DELETE", path, ...args);
701
- }
702
- patch(path, ...args) {
703
- return this.route("PATCH", path, ...args);
704
- }
705
- head(path, ...args) {
706
- return this.route("HEAD", path, ...args);
707
- }
708
- options(path, ...args) {
709
- return this.route("OPTIONS", path, ...args);
710
- }
711
- all(path, ...args) {
712
- return this.route("*", path, ...args);
713
- }
714
- onError(handler) {
715
- this.errorHandler = handler;
716
- return this;
827
+ const result = opts.origin(requestOrigin);
828
+ if (typeof result === "boolean") return result ? requestOrigin : "";
829
+ if (typeof result === "string") return result;
830
+ return "";
717
831
  }
718
- route(method, path, ...args) {
719
- const handler = args.pop();
720
- const middlewares = args;
721
- const segments = this.splitPath(path);
722
- let node = this.root;
723
- for (const segment of segments) {
724
- if (segment === "*") {
725
- node.wildcard = true;
726
- node.handlers.set(method, handler);
727
- if (middlewares.length > 0) node.middlewares.set(method, middlewares);
728
- return this;
729
- }
730
- node = getTrieNode(node, segment);
731
- }
732
- node.handlers.set(method, handler);
733
- if (middlewares.length > 0) node.middlewares.set(method, middlewares);
734
- return this;
832
+ function setCORSHeaders(res, acao) {
833
+ if (!acao) return res;
834
+ const headers = new Headers(res.headers);
835
+ headers.set("Access-Control-Allow-Origin", acao);
836
+ if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
837
+ if (opts.exposedHeaders?.length) headers.set("Access-Control-Expose-Headers", opts.exposedHeaders.join(", "));
838
+ headers.set("Vary", "Origin");
839
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
735
840
  }
736
- ws(path, ...args) {
737
- const handler = args.pop();
738
- const middlewares = args;
739
- const segments = this.splitPath(path);
740
- let node = this.wsRoot;
741
- for (const segment of segments) {
742
- node = getWsNode(node, segment);
841
+ return (req, ctx, next) => {
842
+ const requestOrigin = req.headers.get("origin") ?? "";
843
+ const acao = resolveOrigin(requestOrigin);
844
+ if (req.method === "OPTIONS" && acao) {
845
+ const headers = new Headers();
846
+ headers.set("Access-Control-Allow-Origin", acao);
847
+ headers.set("Access-Control-Allow-Methods", opts.methods.join(", "));
848
+ headers.set("Access-Control-Allow-Headers", opts.allowedHeaders.join(", "));
849
+ if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
850
+ if (opts.maxAge != null) headers.set("Access-Control-Max-Age", String(opts.maxAge));
851
+ headers.set("Vary", "Origin");
852
+ return new Response(null, { status: 204, headers });
743
853
  }
744
- node.handler = handler;
745
- if (middlewares.length > 0) node.middlewares = middlewares;
746
- return this;
747
- }
748
- graphql(path, ...args) {
749
- const options = args.pop();
750
- const middlewares = args;
751
- const schema = typeof options.schema === "string" ? options.resolvers ? makeExecutableSchema({
752
- typeDefs: options.schema,
753
- resolvers: options.resolvers
754
- }) : buildSchema(options.schema) : options.schema;
755
- const handler = (req, ctx) => {
756
- const url = new URL(req.url);
757
- if (options.graphiql && req.method === "GET" && !url.searchParams.has("query")) {
758
- return new Response(getGraphiQLHtml(url.pathname), {
759
- status: 200,
760
- headers: { "Content-Type": "text/html" }
761
- });
854
+ if (!acao) return next(req, ctx);
855
+ return Promise.resolve(next(req, ctx)).then((res) => setCORSHeaders(res, acao));
856
+ };
857
+ }
858
+ function auth(options) {
859
+ return async (req, ctx, next) => {
860
+ const headerName = options.header ?? "Authorization";
861
+ let from = "header";
862
+ let header = req.headers.get(headerName);
863
+ let token = "";
864
+ if (header) {
865
+ token = header;
866
+ if (headerName.toLowerCase() === "authorization") {
867
+ const parts = header.split(" ");
868
+ if (parts[0]?.toLowerCase() === "bearer") {
869
+ token = parts.slice(1).join(" ");
870
+ }
762
871
  }
763
- if (req.method !== "GET" && req.method !== "POST") {
764
- return new Response("Not Found", { status: 404 });
872
+ } else {
873
+ const url = new URL(req.url);
874
+ const qsToken = url.searchParams.get("access_token");
875
+ if (qsToken) {
876
+ token = qsToken;
877
+ from = "query";
765
878
  }
766
- const paramsPromise = req.method === "GET" ? Promise.resolve(parseGraphQLParamsFromGet(url)) : parseGraphQLParamsFromPost(req);
767
- return paramsPromise.then((params) => {
768
- if (!params) {
769
- return Response.json(
770
- { errors: [{ message: "Missing query" }] },
771
- { status: 400 }
772
- );
773
- }
774
- return executeGraphQLQuery(schema, params, options, req, ctx);
775
- });
776
- };
777
- return this.all(path, ...middlewares, handler);
778
- }
779
- ai(path, ...args) {
780
- const handler = args.pop();
781
- const middlewares = args;
782
- const routeHandler = async (req, ctx) => {
783
- const options = await handler(req, ctx);
784
- const result = streamText(options);
785
- return result.toTextStreamResponse();
786
- };
787
- return this.post(path, ...middlewares, routeHandler);
788
- }
789
- workflow(path, options) {
790
- const sseManager = options.stream ? createSSEManager() : void 0;
791
- if (options.stream && sseManager) {
792
- this.get(`${path}/:workflowId/events`, async (req, ctx) => {
793
- const stream = sseManager.createStream(ctx.params.workflowId);
794
- return new Response(stream, {
795
- headers: {
796
- "Content-Type": "text/event-stream",
797
- "Cache-Control": "no-cache",
798
- "Connection": "keep-alive"
799
- }
800
- });
801
- });
802
879
  }
803
- this.post(path, async (req) => {
804
- const body = await req.json();
805
- const engine = createWorkflowEngine({
806
- tools: options.tools,
807
- model: options.model,
808
- sseManager
880
+ if (!token) {
881
+ return new Response("Unauthorized", {
882
+ status: 401,
883
+ headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
809
884
  });
810
- let wf;
811
- if (body.goal && options.model) {
812
- wf = await engine.generateWorkflow(body.goal);
813
- } else if (body.workflow) {
814
- wf = body.workflow;
815
- } else if (body.nodes) {
816
- wf = { nodes: body.nodes };
885
+ }
886
+ if (options.proxy) {
887
+ const proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
888
+ const proxyHeaders = {};
889
+ if (from === "header" && header) {
890
+ proxyHeaders[headerName] = header;
817
891
  } else {
818
- return Response.json(
819
- { error: 'Provide "goal" (with model) or "workflow"/"nodes"' },
820
- { status: 400 }
821
- );
892
+ proxyUrl.searchParams.set("access_token", token);
822
893
  }
823
- if (options.stream && sseManager) {
824
- const workflowId = crypto.randomUUID();
825
- engine.runAsync(workflowId, wf);
826
- return Response.json({ workflowId, eventsUrl: `${path}/${workflowId}/events` });
894
+ for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
895
+ const v = req.headers.get(name);
896
+ if (v) proxyHeaders[name] = v;
827
897
  }
828
- const result = await engine.execute(wf);
829
- return Response.json({ workflow: wf, result });
830
- });
831
- return this;
832
- }
833
- handler() {
834
- return (req, ctx) => {
835
- const url = new URL(req.url);
836
- return this.handle(req, ctx, this.splitPath(url.pathname), Object.fromEntries(url.searchParams));
837
- };
838
- }
839
- websocketHandler() {
840
- const wss = new WebSocketServer({ noServer: true });
841
- const wsRoot = this.wsRoot;
842
- const router = this;
843
- return (req, socket, head) => {
844
- const url = new URL(req.url ?? "/", "http://localhost");
845
- const segments = url.pathname.split("/").filter(Boolean);
846
- const query = Object.fromEntries(url.searchParams);
847
- const match = router.matchWsTrie(wsRoot, segments);
848
- if (!match) {
849
- socket.destroy();
850
- return;
898
+ const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
899
+ if (proxyRes.status >= 400) {
900
+ return new Response(await proxyRes.text() || "Forbidden", { status: proxyRes.status });
851
901
  }
852
- const webReq = new Request(url.href, {
853
- method: req.method ?? "GET",
854
- headers: Object.fromEntries(
855
- Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : v ?? ""])
856
- )
857
- });
858
- const ctx = { params: match.params, query };
859
- if (match.middlewares.length === 0) {
860
- upgradeSocket(wss, req, socket, head, match.handler, ctx);
861
- return;
902
+ let userData = void 0;
903
+ if (proxyRes.status === 200) {
904
+ const ct = proxyRes.headers.get("content-type");
905
+ if (ct?.includes("application/json")) {
906
+ try {
907
+ userData = await proxyRes.json();
908
+ } catch {
909
+ }
910
+ }
862
911
  }
863
- let index = 0;
864
- const dispatch = async (innerReq, ctx2) => {
865
- if (index < match.middlewares.length) {
866
- const mw = match.middlewares[index++];
867
- return mw(innerReq, ctx2, dispatch);
912
+ ctx.user = userData;
913
+ return next(req, ctx);
914
+ }
915
+ if (options.token) {
916
+ if (token !== options.token) {
917
+ return new Response("Forbidden", { status: 403 });
918
+ }
919
+ return next(req, ctx);
920
+ }
921
+ if (options.verify) {
922
+ const result = await options.verify(token, req);
923
+ if (!result) {
924
+ return new Response("Forbidden", { status: 403 });
925
+ }
926
+ if (typeof result === "object" && result !== null) {
927
+ ctx.user = result;
928
+ }
929
+ return next(req, ctx);
930
+ }
931
+ return next(req, ctx);
932
+ };
933
+ }
934
+
935
+ // static.ts
936
+ import { createHash as createHash2 } from "node:crypto";
937
+ import { open } from "node:fs/promises";
938
+ import { extname, resolve as resolve2, normalize, sep as sep2 } from "node:path";
939
+ function serveStatic(root, options) {
940
+ const rootDir = resolve2(root);
941
+ const opts = options ?? {};
942
+ return async (req, ctx) => {
943
+ const relativePath = ctx.params["*"] ?? new URL(req.url).pathname.slice(1);
944
+ const decoded = decodeURIComponent(relativePath);
945
+ if (decoded.includes("..") || decoded.includes("\0")) {
946
+ return new Response("Forbidden", { status: 403 });
947
+ }
948
+ let filePath = normalize(resolve2(rootDir, decoded));
949
+ if (!filePath.startsWith(rootDir + sep2) && filePath !== rootDir) {
950
+ return new Response("Forbidden", { status: 403 });
951
+ }
952
+ let fileHandle;
953
+ try {
954
+ fileHandle = await open(filePath, "r");
955
+ const stat = await fileHandle.stat();
956
+ if (stat.isDirectory()) {
957
+ await fileHandle.close();
958
+ const indexFile = opts.index ?? "index.html";
959
+ filePath = resolve2(filePath, indexFile);
960
+ if (!filePath.startsWith(rootDir + sep2)) {
961
+ return new Response("Forbidden", { status: 403 });
868
962
  }
869
- return await new Promise((resolve3) => {
870
- upgradeSocket(wss, req, socket, head, match.handler, ctx2);
871
- resolve3(new Response(null, { status: 101 }));
872
- });
873
- };
874
- Promise.resolve(dispatch(webReq, ctx)).then((result) => {
875
- if (result.status !== 101) {
876
- sendHttpResponseOnSocket(socket, result);
963
+ fileHandle = await open(filePath, "r");
964
+ const dirStat = await fileHandle.stat();
965
+ if (!dirStat.isFile()) {
966
+ await fileHandle.close();
967
+ return new Response("Not Found", { status: 404 });
877
968
  }
878
- }).catch(() => {
879
- socket.destroy();
969
+ }
970
+ const mimeType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
971
+ const etag = `"${createHash2("md5").update(`${stat.size}-${stat.mtimeMs}`).digest("hex")}"`;
972
+ const ifNoneMatch = req.headers.get("if-none-match");
973
+ if (ifNoneMatch === etag) {
974
+ await fileHandle.close();
975
+ return new Response(null, { status: 304 });
976
+ }
977
+ const ifModifiedSince = req.headers.get("if-modified-since");
978
+ if (ifModifiedSince && stat.mtimeMs <= new Date(ifModifiedSince).getTime()) {
979
+ await fileHandle.close();
980
+ return new Response(null, { status: 304 });
981
+ }
982
+ const headers = {
983
+ "Content-Type": mimeType,
984
+ "Content-Length": String(stat.size),
985
+ "ETag": etag,
986
+ "Last-Modified": stat.mtime.toUTCString(),
987
+ "Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
988
+ };
989
+ const stream = fileHandle.readableWebStream();
990
+ return new Response(stream, { headers });
991
+ } catch (err) {
992
+ if (fileHandle) await fileHandle.close().catch(() => {
880
993
  });
881
- };
882
- }
883
- splitPath(path) {
884
- return path.split("/").filter(Boolean);
885
- }
886
- matchTrie(method, segments) {
887
- let node = this.root;
888
- const params = {};
889
- const pathMws = [...this.root.pathMws];
890
- let wildcardHandler = null;
891
- let wildcardMws = [];
892
- let wildcardIdx = -1;
893
- for (let i = 0; i < segments.length; i++) {
894
- pathMws.push(...node.pathMws);
895
- if (node.wildcard) {
896
- const h = node.handlers.get("*") || node.handlers.get(method);
897
- if (h) {
898
- wildcardHandler = h;
899
- wildcardMws = node.middlewares.get(method) || node.middlewares.get("*") || [];
900
- wildcardIdx = i;
901
- }
994
+ if (err?.code === "ENOENT") {
995
+ return new Response("Not Found", { status: 404 });
902
996
  }
903
- const segment = segments[i];
904
- if (!segment) break;
905
- const next = matchTrieNode(node, segment, params);
906
- if (!next) {
907
- if (node.subRouter) {
908
- return {
909
- pathMws,
910
- params,
911
- middlewares: [],
912
- subRouter: { router: node.subRouter, remainingIdx: i }
913
- };
914
- }
915
- if (wildcardHandler) {
916
- params["*"] = segments.slice(wildcardIdx).join("/");
917
- return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
918
- }
919
- return null;
997
+ return new Response("Internal Server Error", { status: 500 });
998
+ }
999
+ };
1000
+ }
1001
+ var MIME_TYPES = {
1002
+ ".html": "text/html; charset=utf-8",
1003
+ ".htm": "text/html; charset=utf-8",
1004
+ ".css": "text/css; charset=utf-8",
1005
+ ".js": "application/javascript; charset=utf-8",
1006
+ ".mjs": "application/javascript; charset=utf-8",
1007
+ ".json": "application/json",
1008
+ ".png": "image/png",
1009
+ ".jpg": "image/jpeg",
1010
+ ".jpeg": "image/jpeg",
1011
+ ".gif": "image/gif",
1012
+ ".svg": "image/svg+xml",
1013
+ ".ico": "image/x-icon",
1014
+ ".webp": "image/webp",
1015
+ ".avif": "image/avif",
1016
+ ".woff": "font/woff",
1017
+ ".woff2": "font/woff2",
1018
+ ".ttf": "font/ttf",
1019
+ ".otf": "font/otf",
1020
+ ".eot": "application/vnd.ms-fontobject",
1021
+ ".txt": "text/plain; charset=utf-8",
1022
+ ".xml": "application/xml",
1023
+ ".pdf": "application/pdf",
1024
+ ".zip": "application/zip",
1025
+ ".wasm": "application/wasm",
1026
+ ".map": "application/json"
1027
+ };
1028
+
1029
+ // validate.ts
1030
+ function validate(schemas) {
1031
+ return async (req, ctx, next) => {
1032
+ const parsed = {};
1033
+ const issues = [];
1034
+ if (schemas.params) {
1035
+ const result = schemas.params.safeParse(ctx.params);
1036
+ if (result.success) {
1037
+ parsed.params = result.data;
1038
+ } else {
1039
+ issues.push(...result.error.issues.map((i) => ({
1040
+ path: ["params", ...i.path.map(String)],
1041
+ message: i.message
1042
+ })));
920
1043
  }
921
- node = next;
922
- }
923
- if (node.subRouter) {
924
- return {
925
- pathMws,
926
- params,
927
- middlewares: [],
928
- subRouter: { router: node.subRouter, remainingIdx: segments.length }
929
- };
930
- }
931
- pathMws.push(...node.pathMws);
932
- const handler = node.handlers.get(method) || node.handlers.get("*");
933
- if (handler) {
934
- if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
935
- return {
936
- handler,
937
- middlewares: node.middlewares.get(method) || node.middlewares.get("*") || [],
938
- pathMws,
939
- params
940
- };
941
- }
942
- if (wildcardHandler) {
943
- params["*"] = segments.slice(wildcardIdx).join("/");
944
- return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
945
- }
946
- return null;
947
- }
948
- matchWsTrie(root, segments) {
949
- let node = root;
950
- const params = {};
951
- for (const segment of segments) {
952
- const next = matchWsNode(node, segment, params);
953
- if (!next) return null;
954
- node = next;
955
1044
  }
956
- return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
957
- }
958
- async handle(req, ctx, segments, query) {
959
- const match = this.matchTrie(req.method, segments);
960
- if (match?.subRouter) {
961
- const { router: sub, remainingIdx } = match.subRouter;
962
- const remainingSegments = segments.slice(remainingIdx);
963
- const delegate = (req2, ctx2) => sub.handle(req2, ctx2, remainingSegments, query);
964
- const allMws = this.globalMws.length + match.pathMws.length === 0 ? [] : [...this.globalMws, ...match.pathMws];
965
- try {
966
- return await this.runChain(allMws, delegate, req, { ...ctx, params: { ...ctx.params, ...match.params } });
967
- } catch (e) {
968
- return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
1045
+ if (schemas.query) {
1046
+ const result = schemas.query.safeParse(ctx.query);
1047
+ if (result.success) {
1048
+ parsed.query = result.data;
1049
+ } else {
1050
+ issues.push(...result.error.issues.map((i) => ({
1051
+ path: ["query", ...i.path.map(String)],
1052
+ message: i.message
1053
+ })));
969
1054
  }
970
1055
  }
971
- if (match?.handler) {
972
- const { handler, middlewares: routeMws, pathMws, params } = match;
973
- const allMws = this.globalMws.length + pathMws.length + routeMws.length === 0 ? [] : [...this.globalMws, ...pathMws, ...routeMws];
974
- const ctxWithMatch = { ...ctx, params: { ...ctx.params, ...params } };
975
- try {
976
- return await this.runChain(allMws, handler, req, ctxWithMatch);
977
- } catch (e) {
978
- return this.errorHandler ? this.errorHandler(e, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
1056
+ if (schemas.headers) {
1057
+ const rawHeaders = {};
1058
+ req.headers.forEach((v, k) => {
1059
+ rawHeaders[k] = v;
1060
+ });
1061
+ const result = schemas.headers.safeParse(rawHeaders);
1062
+ if (result.success) {
1063
+ parsed.headers = result.data;
1064
+ } else {
1065
+ issues.push(...result.error.issues.map((i) => ({
1066
+ path: ["headers", ...i.path.map(String)],
1067
+ message: i.message
1068
+ })));
979
1069
  }
980
1070
  }
981
- if (this.globalMws.length > 0) {
982
- try {
983
- const delegate = () => new Response("Not Found", { status: 404 });
984
- return await this.runChain(this.globalMws, delegate, req, ctx);
985
- } catch (e) {
986
- return this.errorHandler ? this.errorHandler(e, req, ctx) : new Response("Internal Server Error", { status: 500 });
1071
+ if (schemas.body) {
1072
+ if (req.body === null) {
1073
+ issues.push({ path: ["body"], message: "Request body is required" });
1074
+ } else {
1075
+ const bodyText = await req.text();
1076
+ if (!bodyText && req.method !== "GET" && req.method !== "HEAD") {
1077
+ issues.push({ path: ["body"], message: "Request body is required" });
1078
+ } else {
1079
+ let bodyValue;
1080
+ try {
1081
+ bodyValue = JSON.parse(bodyText);
1082
+ } catch {
1083
+ bodyValue = bodyText;
1084
+ }
1085
+ const result = schemas.body.safeParse(bodyValue);
1086
+ if (result.success) {
1087
+ parsed.body = result.data;
1088
+ } else {
1089
+ issues.push(...result.error.issues.map((i) => ({
1090
+ path: ["body", ...i.path.map(String)],
1091
+ message: i.message
1092
+ })));
1093
+ }
1094
+ }
987
1095
  }
988
1096
  }
989
- return new Response("Not Found", { status: 404 });
990
- }
991
- async runChain(middlewares, finalHandler, req, ctx) {
992
- let index = 0;
993
- const dispatch = async (req2, ctx2) => {
994
- if (index < middlewares.length) {
995
- const mw = middlewares[index++];
996
- return mw ? await mw(req2, ctx2, dispatch) : new Response("Middleware error", { status: 500 });
997
- }
998
- return await finalHandler(req2, ctx2);
999
- };
1000
- return dispatch(req, ctx);
1001
- }
1002
- };
1003
- function upgradeSocket(wss, req, socket, head, handler, ctx) {
1004
- wss.handleUpgrade(req, socket, head, (ws) => {
1005
- if (handler.open) {
1006
- handler.open(ws, ctx);
1097
+ if (issues.length > 0) {
1098
+ return Response.json({ error: "Validation failed", issues }, { status: 400 });
1007
1099
  }
1008
- ws.on("message", (data) => {
1009
- handler.message?.(ws, ctx, data);
1010
- });
1011
- ws.on("close", () => {
1012
- handler.close?.(ws, ctx);
1013
- });
1014
- ws.on("error", (err) => {
1015
- handler.error?.(ws, ctx, err);
1016
- });
1017
- });
1100
+ ctx.parsed = parsed;
1101
+ return next(req, ctx);
1102
+ };
1018
1103
  }
1019
- function sendHttpResponseOnSocket(socket, response) {
1020
- const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
1021
- const headerLines = [statusLine];
1022
- response.headers.forEach((value, key) => {
1023
- headerLines.push(`${key}: ${value}`);
1104
+
1105
+ // cookie.ts
1106
+ function getCookies(req) {
1107
+ const header = req.headers.get("cookie");
1108
+ if (!header) return {};
1109
+ const cookies = {};
1110
+ for (const pair of header.split(";")) {
1111
+ const idx = pair.indexOf("=");
1112
+ if (idx === -1) continue;
1113
+ const name = pair.slice(0, idx).trim();
1114
+ const value = pair.slice(idx + 1).trim();
1115
+ if (name) {
1116
+ cookies[name] = decodeURIComponent(value);
1117
+ }
1118
+ }
1119
+ return cookies;
1120
+ }
1121
+ function serializeCookie(name, value, options) {
1122
+ const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
1123
+ if (options?.maxAge != null) parts.push(`Max-Age=${options.maxAge}`);
1124
+ if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
1125
+ if (options?.domain) parts.push(`Domain=${options.domain}`);
1126
+ if (options?.path) parts.push(`Path=${options.path}`);
1127
+ if (options?.httpOnly) parts.push("HttpOnly");
1128
+ if (options?.secure) parts.push("Secure");
1129
+ if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`);
1130
+ return parts.join("; ");
1131
+ }
1132
+ function setCookie(res, name, value, options) {
1133
+ const headers = new Headers(res.headers);
1134
+ headers.append("Set-Cookie", serializeCookie(name, value, options));
1135
+ return new Response(res.body, {
1136
+ status: res.status,
1137
+ statusText: res.statusText,
1138
+ headers
1024
1139
  });
1025
- headerLines.push("Connection: close");
1026
- headerLines.push("");
1027
- const headerStr = headerLines.join("\r\n");
1028
- response.arrayBuffer().then((buf) => {
1029
- const body = Buffer.from(buf);
1030
- socket.write(headerStr + "\r\n" + body.toString());
1031
- socket.end();
1032
- }).catch(() => {
1033
- socket.write(headerStr + "\r\n");
1034
- socket.end();
1140
+ }
1141
+ function deleteCookie(res, name, options) {
1142
+ const headers = new Headers(res.headers);
1143
+ headers.append("Set-Cookie", serializeCookie(name, "", { ...options, maxAge: 0 }));
1144
+ return new Response(res.body, {
1145
+ status: res.status,
1146
+ statusText: res.statusText,
1147
+ headers
1035
1148
  });
1036
1149
  }
1037
- function parseGraphQLParamsFromGet(url) {
1038
- const query = url.searchParams.get("query");
1039
- if (!query) return null;
1040
- const variablesStr = url.searchParams.get("variables");
1041
- let variables = {};
1042
- if (variablesStr) {
1043
- try {
1044
- variables = JSON.parse(variablesStr);
1045
- } catch {
1046
- return null;
1150
+
1151
+ // upload.ts
1152
+ import { writeFile, mkdir } from "node:fs/promises";
1153
+ import { randomUUID } from "node:crypto";
1154
+ import { join as join2 } from "node:path";
1155
+ function upload(options) {
1156
+ const saveDir = options?.dir;
1157
+ return async (req, ctx, next) => {
1158
+ const ct = req.headers.get("content-type") ?? "";
1159
+ if (!ct.includes("multipart/form-data")) {
1160
+ return next(req, ctx);
1161
+ }
1162
+ const match = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
1163
+ if (!match) {
1164
+ return Response.json({ error: "Missing boundary" }, { status: 400 });
1165
+ }
1166
+ const boundary = match[1] ?? match[2];
1167
+ const body = await req.text();
1168
+ const rawParts = body.split(`--${boundary}`).filter((p) => p && !p.startsWith("--") && !p.startsWith("\r\n--"));
1169
+ const files = {};
1170
+ const fields = {};
1171
+ for (const raw of rawParts) {
1172
+ const trimmed = raw.replace(/^\r?\n/, "");
1173
+ const lines = trimmed.split(/\r?\n/);
1174
+ let i = 0;
1175
+ const headers = {};
1176
+ while (i < lines.length && lines[i].length > 0) {
1177
+ const sep3 = lines[i].indexOf(": ");
1178
+ if (sep3 !== -1) headers[lines[i].slice(0, sep3).toLowerCase()] = lines[i].slice(sep3 + 2);
1179
+ i++;
1180
+ }
1181
+ i++;
1182
+ const bodyValue = lines.slice(i).join("\r\n");
1183
+ const disposition = headers["content-disposition"] ?? "";
1184
+ const nameMatch = disposition.match(/name="([^"]*)"/);
1185
+ if (!nameMatch) continue;
1186
+ const name = nameMatch[1];
1187
+ const filenameMatch = disposition.match(/filename="([^"]*)"/);
1188
+ const filename = filenameMatch?.[1];
1189
+ if (filename) {
1190
+ const buf = Buffer.from(bodyValue.replace(/\r?\n$/, ""), "binary");
1191
+ if (options?.allowedTypes) {
1192
+ const mime = headers["content-type"] ?? "application/octet-stream";
1193
+ if (!options.allowedTypes.includes(mime)) {
1194
+ return Response.json({ error: `File type not allowed: ${mime}` }, { status: 415 });
1195
+ }
1196
+ }
1197
+ if (options?.maxFileSize && buf.byteLength > options.maxFileSize) {
1198
+ return Response.json({ error: `File too large: ${filename}` }, { status: 413 });
1199
+ }
1200
+ const uf = {
1201
+ name: filename,
1202
+ type: headers["content-type"] ?? "application/octet-stream",
1203
+ size: buf.byteLength,
1204
+ buffer: saveDir ? void 0 : buf
1205
+ };
1206
+ if (saveDir) {
1207
+ const filePath = join2(saveDir, `${randomUUID()}-${filename}`);
1208
+ await mkdir(saveDir, { recursive: true });
1209
+ await writeFile(filePath, buf);
1210
+ uf.path = filePath;
1211
+ }
1212
+ if (files[name]) {
1213
+ const existing = files[name];
1214
+ files[name] = Array.isArray(existing) ? [...existing, uf] : [existing, uf];
1215
+ } else {
1216
+ files[name] = uf;
1217
+ }
1218
+ } else {
1219
+ fields[name] = bodyValue.replace(/\r?\n$/, "");
1220
+ }
1047
1221
  }
1048
- }
1049
- return {
1050
- query,
1051
- variables,
1052
- operationName: url.searchParams.get("operationName") || void 0
1222
+ ctx.parsed = { ...ctx.parsed, files, fields };
1223
+ return next(req, ctx);
1053
1224
  };
1054
1225
  }
1055
- async function parseGraphQLParamsFromPost(req) {
1056
- try {
1057
- const body = await req.json();
1058
- if (!body.query) return null;
1059
- return {
1060
- query: body.query,
1061
- variables: body.variables || {},
1062
- operationName: body.operationName
1063
- };
1064
- } catch {
1065
- return null;
1066
- }
1067
- }
1068
- async function executeGraphQLQuery(schema, params, options, req, ctx) {
1069
- const contextValue = options.context ? await options.context(req, ctx) : ctx;
1070
- const result = await graphql({
1071
- schema,
1072
- source: params.query,
1073
- rootValue: options.rootValue,
1074
- contextValue,
1075
- variableValues: params.variables,
1076
- operationName: params.operationName
1226
+
1227
+ // rate-limit.ts
1228
+ function rateLimit(options) {
1229
+ const max = options?.max ?? 100;
1230
+ const window = options?.window ?? 6e4;
1231
+ const getKey = options?.key ?? ((req) => {
1232
+ const forwarded = req.headers.get("x-forwarded-for");
1233
+ if (forwarded) return forwarded.split(",")[0].trim();
1234
+ return new URL(req.url).hostname;
1077
1235
  });
1078
- return Response.json(result, { status: result.errors ? 400 : 200 });
1079
- }
1080
- function getGraphiQLHtml(endpoint) {
1081
- return `<!doctype html>
1082
- <html lang="en">
1083
- <head>
1084
- <meta charset="UTF-8" />
1085
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1086
- <title>GraphiQL</title>
1087
- <style>
1088
- body { margin: 0; }
1089
- #graphiql { height: 100dvh; }
1090
- </style>
1091
- <link rel="stylesheet" href="https://esm.sh/graphiql@5.2.2/dist/style.css" />
1092
- <script type="importmap">
1093
- {
1094
- "imports": {
1095
- "react": "https://esm.sh/react@19.2.5",
1096
- "react/": "https://esm.sh/react@19.2.5/",
1097
- "react-dom": "https://esm.sh/react-dom@19.2.5",
1098
- "react-dom/": "https://esm.sh/react-dom@19.2.5/",
1099
- "graphiql": "https://esm.sh/graphiql@5.2.2?standalone&external=react,react-dom,@graphiql/react,graphql",
1100
- "graphiql/": "https://esm.sh/graphiql@5.2.2/",
1101
- "@graphiql/react": "https://esm.sh/@graphiql/react@0.37.3?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
1102
- "@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql",
1103
- "graphql": "https://esm.sh/graphql@16.13.2",
1104
- "@emotion/is-prop-valid": "data:text/javascript,"
1236
+ const message = options?.message ?? "Too Many Requests";
1237
+ const hits = /* @__PURE__ */ new Map();
1238
+ const interval = setInterval(() => {
1239
+ const now = Date.now();
1240
+ for (const [key, entry] of hits) {
1241
+ if (entry.reset < now) hits.delete(key);
1242
+ }
1243
+ }, window);
1244
+ if (interval.unref) interval.unref();
1245
+ return async (req, ctx, next) => {
1246
+ const key = getKey(req);
1247
+ const now = Date.now();
1248
+ const entry = hits.get(key);
1249
+ if (!entry || entry.reset < now) {
1250
+ hits.set(key, { count: 1, reset: now + window });
1251
+ const res2 = await next(req, ctx);
1252
+ const headers2 = new Headers(res2.headers);
1253
+ headers2.set("X-RateLimit-Limit", String(max));
1254
+ headers2.set("X-RateLimit-Remaining", String(max - 1));
1255
+ headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window) / 1e3)));
1256
+ return new Response(res2.body, { status: res2.status, statusText: res2.statusText, headers: headers2 });
1257
+ }
1258
+ entry.count++;
1259
+ const remaining = Math.max(0, max - entry.count);
1260
+ if (entry.count > max) {
1261
+ return new Response(message, {
1262
+ status: 429,
1263
+ headers: {
1264
+ "Retry-After": String(Math.ceil((entry.reset - now) / 1e3)),
1265
+ "X-RateLimit-Limit": String(max),
1266
+ "X-RateLimit-Remaining": "0",
1267
+ "X-RateLimit-Reset": String(Math.ceil(entry.reset / 1e3))
1105
1268
  }
1106
- }
1107
- </script>
1108
- <script type="module">
1109
- import React from 'react';
1110
- import ReactDOM from 'react-dom/client';
1111
- import { GraphiQL } from 'graphiql';
1112
- import { createGraphiQLFetcher } from '@graphiql/toolkit';
1113
- import 'graphiql/setup-workers/esm.sh';
1114
-
1115
- const fetcher = createGraphiQLFetcher({ url: "${endpoint}" });
1116
-
1117
- function App() {
1118
- return React.createElement(GraphiQL, { fetcher });
1119
- }
1120
-
1121
- const container = document.getElementById('graphiql');
1122
- const root = ReactDOM.createRoot(container);
1123
- root.render(React.createElement(App));
1124
- </script>
1125
- </head>
1126
- <body>
1127
- <div id="graphiql">Loading\u2026</div>
1128
- </body>
1129
- </html>`;
1269
+ });
1270
+ }
1271
+ const res = await next(req, ctx);
1272
+ const headers = new Headers(res.headers);
1273
+ headers.set("X-RateLimit-Limit", String(max));
1274
+ headers.set("X-RateLimit-Remaining", String(remaining));
1275
+ headers.set("X-RateLimit-Reset", String(Math.ceil(entry.reset / 1e3)));
1276
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
1277
+ };
1130
1278
  }
1131
1279
 
1132
- // tsx.ts
1133
- import { createElement, createContext, useContext } from "react";
1134
- import { renderToReadableStream } from "react-dom/server";
1135
- import * as esbuild from "esbuild";
1136
- import { readdirSync, statSync, existsSync, mkdirSync } from "node:fs";
1137
- import { join, relative, resolve, sep, dirname } from "node:path";
1138
- import { pathToFileURL } from "node:url";
1139
- import { createHash } from "node:crypto";
1140
- var TsxContext = createContext({ params: {}, query: {} });
1141
- function useTsx() {
1142
- return useContext(TsxContext);
1143
- }
1144
- function id(s) {
1145
- return createHash("md5").update(s).digest("hex").slice(0, 8);
1146
- }
1147
- function concatUint8(chunks) {
1148
- const len = chunks.reduce((a, c) => a + c.length, 0);
1149
- const out = new Uint8Array(len);
1150
- let off = 0;
1151
- for (const c of chunks) {
1152
- out.set(c, off);
1153
- off += c.length;
1154
- }
1155
- return out;
1156
- }
1157
- async function readStream(stream) {
1158
- const chunks = [];
1159
- const reader = stream.getReader();
1160
- while (true) {
1161
- const { done, value } = await reader.read();
1162
- if (done) break;
1163
- chunks.push(value);
1164
- }
1165
- return new TextDecoder().decode(concatUint8(chunks));
1166
- }
1167
- function scanPages(dir) {
1168
- const pages = [];
1169
- function walk(current) {
1170
- let entries;
1171
- try {
1172
- entries = readdirSync(current);
1173
- } catch {
1174
- return;
1280
+ // compress.ts
1281
+ import { gzipSync, brotliCompressSync, constants } from "node:zlib";
1282
+ function compress(options) {
1283
+ const level = options?.level ?? 6;
1284
+ const threshold = options?.threshold ?? 1024;
1285
+ return async (req, ctx, next) => {
1286
+ const accept = req.headers.get("accept-encoding") ?? "";
1287
+ const useBrotli = accept.includes("br");
1288
+ const useGzip = !useBrotli && accept.includes("gzip");
1289
+ const useDeflate = !useBrotli && !useGzip && accept.includes("deflate");
1290
+ if (!useBrotli && !useGzip && !useDeflate) {
1291
+ return next(req, ctx);
1175
1292
  }
1176
- const dirs = [];
1177
- for (const name of entries) {
1178
- const full = join(current, name);
1179
- const st = statSync(full);
1180
- if (st.isDirectory()) {
1181
- if (!name.startsWith(".")) dirs.push(full);
1182
- }
1293
+ const res = await next(req, ctx);
1294
+ if (res.status === 304 || res.status === 204 || res.status < 200 || res.status >= 300) {
1295
+ return res;
1183
1296
  }
1184
- const pagePath = join(current, "page.tsx");
1185
- const tsPagePath = join(current, "page.ts");
1186
- let entryPath = "";
1187
- if (existsSync(pagePath)) {
1188
- entryPath = pagePath;
1189
- } else if (existsSync(tsPagePath)) {
1190
- entryPath = tsPagePath;
1297
+ const ce = res.headers.get("content-encoding");
1298
+ if (ce) return res;
1299
+ const ct = res.headers.get("content-type") ?? "";
1300
+ if (!ct || ct.startsWith("audio/") || ct.startsWith("video/") || ct.startsWith("image/") || ct === "application/zip") {
1301
+ return res;
1191
1302
  }
1192
- if (entryPath) {
1193
- let relPath = relative(dir, entryPath).replace(sep, "/");
1194
- relPath = relPath.replace(/\/page\.tsx?$/, "");
1195
- relPath = relPath.replace(/^page\.tsx?$/, "");
1196
- const route = filePathToRoute(relPath);
1197
- const layouts = resolveLayouts(current, dir);
1198
- const loadPath = existsSync(join(current, "load.ts")) ? join(current, "load.ts") : void 0;
1199
- const rPath = existsSync(join(current, "route.ts")) ? join(current, "route.ts") : void 0;
1200
- pages.push({
1201
- route,
1202
- entryPath,
1203
- loadPath,
1204
- layouts,
1205
- routePath: rPath
1303
+ const body = await res.bytes();
1304
+ if (body.byteLength < threshold) return res;
1305
+ let compressed;
1306
+ let encoding;
1307
+ if (useBrotli) {
1308
+ compressed = brotliCompressSync(body, {
1309
+ params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) }
1206
1310
  });
1207
- } else {
1208
- const rPath = join(current, "route.ts");
1209
- if (existsSync(rPath)) {
1210
- let relPath = relative(dir, rPath).replace(sep, "/");
1211
- relPath = relPath.replace(/\/route\.tsx?$/, "");
1212
- const route = filePathToRoute(relPath);
1213
- pages.push({
1214
- route,
1215
- entryPath: "",
1216
- layouts: [],
1217
- routePath: rPath,
1218
- routeOnly: true
1219
- });
1220
- }
1311
+ encoding = "br";
1312
+ } else if (useGzip) {
1313
+ compressed = gzipSync(body, { level: Math.min(level, 9) });
1314
+ encoding = "gzip";
1315
+ } else {
1316
+ compressed = gzipSync(body, { level: Math.min(level, 9) });
1317
+ encoding = "deflate";
1221
1318
  }
1222
- for (const d of dirs) walk(d);
1223
- }
1224
- walk(dir);
1225
- return pages;
1226
- }
1227
- function filePathToRoute(relPath) {
1228
- let route = relPath.replace(/\\/g, "/");
1229
- route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
1230
- route = route.replace(/\[(\w+)\]/g, ":$1");
1231
- return route.startsWith("/") ? route : "/" + route;
1319
+ const headers = new Headers(res.headers);
1320
+ headers.set("Content-Encoding", encoding);
1321
+ headers.set("Content-Length", String(compressed.byteLength));
1322
+ headers.delete("Content-Range");
1323
+ headers.set("Vary", "Accept-Encoding");
1324
+ return new Response(compressed, {
1325
+ status: res.status,
1326
+ statusText: res.statusText,
1327
+ headers
1328
+ });
1329
+ };
1232
1330
  }
1233
- function resolveLayouts(dir, pagesDir) {
1234
- const layouts = [];
1235
- let current = dir;
1236
- while (current.startsWith(pagesDir)) {
1237
- const p = join(current, "layout.tsx");
1238
- if (existsSync(p)) {
1239
- layouts.push(p);
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;
1240
1345
  }
1241
- const parent = dirname(current);
1242
- if (parent === current) break;
1243
- current = parent;
1244
1346
  }
1245
- return layouts.reverse();
1347
+ return { query, variables, operationName: url.searchParams.get("operationName") || void 0 };
1246
1348
  }
1247
- async function compileAll(files, outDir, platform) {
1248
- const entryPoints = {};
1249
- for (const f of files) {
1250
- entryPoints[id(f)] = f;
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;
1251
1356
  }
1252
- const isBrowser = platform === "browser";
1253
- await esbuild.build({
1254
- entryPoints,
1255
- outdir: outDir,
1256
- format: "esm",
1257
- platform: "node",
1258
- jsx: "automatic",
1259
- jsxImportSource: "react",
1260
- bundle: true,
1261
- external: isBrowser ? void 0 : [
1262
- "react",
1263
- "react-dom",
1264
- "esbuild",
1265
- "graphql",
1266
- "ws",
1267
- "zod",
1268
- "@graphql-tools/schema",
1269
- "ai"
1270
- ],
1271
- write: true,
1272
- allowOverwrite: true
1273
- });
1274
1357
  }
1275
- function compiledUrl(filePath, outDir) {
1276
- const hash = id(join(outDir, id(filePath)));
1277
- const p = join(outDir, id(filePath) + ".js");
1278
- return pathToFileURL(p).href;
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;
1279
1363
  }
1280
- var clientBundleCache = /* @__PURE__ */ new Map();
1281
- var clientRouteLog = /* @__PURE__ */ new WeakMap();
1282
- async function getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router) {
1283
- const key = id(entryPath);
1284
- const url = `/__wfw/client/${key}.js`;
1285
- if (!clientRouteLog.get(router)?.has(url)) {
1286
- let buf = clientBundleCache.get(key);
1287
- if (!buf) {
1288
- try {
1289
- const nested = layoutPaths.slice(1);
1290
- const layoutsImport = nested.map(
1291
- (p, i) => `import L${i} from${JSON.stringify(p)};`
1292
- ).join("");
1293
- const layoutsWrap = nested.map((_, i) => {
1294
- const idx = nested.length - 1 - i;
1295
- return `el=createElement(L${idx},null,el);`;
1296
- }).join("");
1297
- const code = [
1298
- `import{hydrateRoot}from'react-dom/client';`,
1299
- `import{createElement}from'react';`,
1300
- `import P from${JSON.stringify(entryPath)};`,
1301
- layoutsImport,
1302
- `const p=window.__WEIFUWU_PROPS;`,
1303
- `let el=createElement(P,p);`,
1304
- layoutsWrap,
1305
- `hydrateRoot(document.getElementById('__weifuwu_root'),el);`
1306
- ].join("");
1307
- const result = await esbuild.build({
1308
- stdin: { contents: code, loader: "tsx", resolveDir: pagesDir },
1309
- bundle: true,
1310
- format: "esm",
1311
- jsx: "automatic",
1312
- jsxImportSource: "react",
1313
- write: false,
1314
- minify: true
1315
- });
1316
- buf = result.outputFiles[0].contents;
1317
- clientBundleCache.set(key, buf);
1318
- } catch (err) {
1319
- console.error("hydration bundle failed:", err);
1320
- return null;
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
+ }
1321
1399
  }
1322
- }
1323
- router.get(url, () => new Response(buf, {
1324
- headers: { "content-type": "application/javascript; charset=utf-8" }
1325
- }));
1326
- const set = clientRouteLog.get(router) ?? /* @__PURE__ */ new Set();
1327
- set.add(url);
1328
- clientRouteLog.set(router, set);
1329
- }
1330
- return { url };
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>`;
1331
1423
  }
1332
- function makeSsrHandler(Component, loadFn, layouts, entryPath, layoutPaths, pagesDir, router) {
1333
- return async (req, ctx) => {
1334
- const loadProps = loadFn ? await loadFn({ params: ctx.params, query: ctx.query }) : {};
1335
- const allProps = { ...loadProps, params: ctx.params, query: ctx.query };
1336
- let element = createElement(Component, allProps);
1337
- for (let i = layouts.length - 1; i >= 0; i--) {
1338
- const isRoot = i === 0;
1339
- element = createElement(
1340
- layouts[i],
1341
- isRoot ? { children: element, req, ctx } : { children: element }
1342
- );
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
+ });
1343
1435
  }
1344
- element = createElement(TsxContext.Provider, {
1345
- value: { params: ctx.params, query: ctx.query, user: ctx.user, parsed: ctx.parsed }
1346
- }, element);
1347
- const stream = await renderToReadableStream(element);
1348
- const body = await readStream(stream);
1349
- const scripts = [];
1350
- scripts.push(`<script>window.__WEIFUWU_PROPS=${JSON.stringify(allProps)}</script>`);
1351
- const bundle = await getOrBuildClientBundle(entryPath, layoutPaths, pagesDir, router);
1352
- if (bundle) {
1353
- scripts.push(`<script type="module" src="${bundle.url}"></script>`);
1436
+ const params = parseParamsFromGet(url);
1437
+ if (!params) {
1438
+ return Response.json({ errors: [{ message: "Missing query" }] }, { status: 400 });
1354
1439
  }
1355
- const html = `<!DOCTYPE html>
1356
- ${body}
1357
- ${scripts.join("\n")}`;
1358
- return new Response(html, {
1359
- headers: { "content-type": "text/html; charset=utf-8" }
1360
- });
1361
- };
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;
1362
1452
  }
1363
- async function tsx(options) {
1364
- const pagesDir = resolve(options.dir);
1365
- const outDir = join(pagesDir, "..", ".weifuwu", "ssr");
1366
- const pages = scanPages(pagesDir);
1367
- if (pages.length === 0) return new Router();
1368
- const allFiles = /* @__PURE__ */ new Set();
1369
- for (const p of pages) {
1370
- if (p.entryPath) allFiles.add(p.entryPath);
1371
- if (p.loadPath) allFiles.add(p.loadPath);
1372
- for (const lp of p.layouts) allFiles.add(lp);
1373
- if (p.routePath) allFiles.add(p.routePath);
1374
- }
1375
- const nfPath = join(pagesDir, "not-found.tsx");
1376
- const hasNotFound = existsSync(nfPath);
1377
- if (hasNotFound) {
1378
- allFiles.add(nfPath);
1379
- const rootLayouts = resolveLayouts(pagesDir, pagesDir);
1380
- for (const lp of rootLayouts) allFiles.add(lp);
1381
- }
1382
- mkdirSync(outDir, { recursive: true });
1383
- await compileAll([...allFiles], outDir, "node");
1384
- const router = new Router();
1385
- for (const p of pages) {
1386
- if (p.routeOnly && p.routePath) {
1387
- const rUrl = compiledUrl(p.routePath, outDir);
1388
- const modR = await import(rUrl);
1389
- const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
1390
- for (const method of methods) {
1391
- if (modR[method]) {
1392
- router.route(method, p.route, modR[method]);
1393
- }
1394
- }
1395
- continue;
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;
1396
1485
  }
1397
- const url = compiledUrl(p.entryPath, outDir);
1398
- const mod = await import(url);
1399
- const Component = mod.default;
1400
- let loadFn;
1401
- if (p.loadPath) {
1402
- const loadUrl = compiledUrl(p.loadPath, outDir);
1403
- const modLoad = await import(loadUrl);
1404
- loadFn = modLoad.default;
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);
1405
1495
  }
1406
- const layoutComponents = [];
1407
- for (const lp of p.layouts) {
1408
- const lUrl = compiledUrl(lp, outDir);
1409
- const modL = await import(lUrl);
1410
- layoutComponents.push(modL.default);
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`);
1411
1501
  }
1412
- const handler = makeSsrHandler(
1413
- Component,
1414
- loadFn,
1415
- layoutComponents,
1416
- p.entryPath,
1417
- p.layouts,
1418
- pagesDir,
1419
- router
1420
- );
1421
- router.get(p.route, handler);
1422
- if (p.routePath) {
1423
- const rUrl = compiledUrl(p.routePath, outDir);
1424
- const modR = await import(rUrl);
1425
- const methods = ["POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
1426
- for (const method of methods) {
1427
- if (modR[method]) {
1428
- router.route(method, p.route, modR[method]);
1429
- }
1430
- }
1502
+ if (propPath.startsWith("output")) {
1503
+ return getByPath(output, propPath.slice(7).split(".").filter(Boolean));
1431
1504
  }
1505
+ return getByPath(output, propPath.split("."));
1432
1506
  }
1433
- if (hasNotFound) {
1434
- const nfUrl = compiledUrl(nfPath, outDir);
1435
- const modNf = await import(nfUrl);
1436
- const NfComponent = modNf.default;
1437
- const nfLayouts = [];
1438
- const rootLayouts = resolveLayouts(pagesDir, pagesDir);
1439
- for (const lp of rootLayouts) {
1440
- const lUrl = compiledUrl(lp, outDir);
1441
- const modL = await import(lUrl);
1442
- nfLayouts.push(modL.default);
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`);
1443
1511
  }
1444
- const handler = async (req, ctx) => {
1445
- let element = createElement(NfComponent, { params: ctx.params, query: ctx.query });
1446
- for (let i = nfLayouts.length - 1; i >= 0; i--) {
1447
- element = createElement(nfLayouts[i], { children: element });
1448
- }
1449
- element = createElement(TsxContext.Provider, {
1450
- value: { params: ctx.params, query: ctx.query, user: ctx.user, parsed: ctx.parsed }
1451
- }, element);
1452
- const stream = await renderToReadableStream(element);
1453
- const body = await readStream(stream);
1454
- const html = `<!DOCTYPE html>
1455
- ${body}`;
1456
- return new Response(html, {
1457
- status: 404,
1458
- headers: { "content-type": "text/html; charset=utf-8" }
1459
- });
1460
- };
1461
- router.all("/*", handler);
1512
+ return ctx.variables.get(name);
1462
1513
  }
1463
- return router;
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;
1464
1524
  }
1465
-
1466
- // middleware.ts
1467
- function logger(options) {
1468
- return async (req, ctx, next) => {
1469
- const start = Date.now();
1470
- const url = new URL(req.url);
1471
- const res = await next(req, ctx);
1472
- const ms = Date.now() - start;
1473
- if (options?.format === "combined") {
1474
- console.log(`${req.method} ${url.pathname}${url.search} ${res.status} ${ms}ms`);
1475
- } else {
1476
- console.log(`${req.method} ${url.pathname} ${res.status} ${ms}ms`);
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);
1477
1536
  }
1478
- return res;
1479
- };
1537
+ return result;
1538
+ }
1539
+ return v;
1480
1540
  }
1481
- function cors(options) {
1482
- const opts = {
1483
- origin: "*",
1484
- methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
1485
- allowedHeaders: ["Content-Type", "Authorization"],
1486
- ...options
1487
- };
1488
- function resolveOrigin(requestOrigin) {
1489
- if (typeof opts.origin === "string") return opts.origin === "*" ? "*" : opts.origin;
1490
- if (Array.isArray(opts.origin)) {
1491
- return opts.origin.includes(requestOrigin) ? requestOrigin : "";
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);
1492
1569
  }
1493
- const result = opts.origin(requestOrigin);
1494
- if (typeof result === "boolean") return result ? requestOrigin : "";
1495
- if (typeof result === "string") return result;
1496
- return "";
1497
1570
  }
1498
- function setCORSHeaders(res, acao) {
1499
- if (!acao) return res;
1500
- const headers = new Headers(res.headers);
1501
- headers.set("Access-Control-Allow-Origin", acao);
1502
- if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
1503
- if (opts.exposedHeaders?.length) headers.set("Access-Control-Expose-Headers", opts.exposedHeaders.join(", "));
1504
- headers.set("Vary", "Origin");
1505
- return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
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);
1506
1594
  }
1507
- return (req, ctx, next) => {
1508
- const requestOrigin = req.headers.get("origin") ?? "";
1509
- const acao = resolveOrigin(requestOrigin);
1510
- if (req.method === "OPTIONS" && acao) {
1511
- const headers = new Headers();
1512
- headers.set("Access-Control-Allow-Origin", acao);
1513
- headers.set("Access-Control-Allow-Methods", opts.methods.join(", "));
1514
- headers.set("Access-Control-Allow-Headers", opts.allowedHeaders.join(", "));
1515
- if (opts.credentials) headers.set("Access-Control-Allow-Credentials", "true");
1516
- if (opts.maxAge != null) headers.set("Access-Control-Max-Age", String(opts.maxAge));
1517
- headers.set("Vary", "Origin");
1518
- return new Response(null, { status: 204, headers });
1519
- }
1520
- if (!acao) return next(req, ctx);
1521
- return Promise.resolve(next(req, ctx)).then((res) => setCORSHeaders(res, acao));
1522
- };
1595
+ ctx.variables.set(name, resolved);
1596
+ return resolved;
1523
1597
  }
1524
- function auth(options) {
1525
- return async (req, ctx, next) => {
1526
- const headerName = options.header ?? "Authorization";
1527
- let from = "header";
1528
- let header = req.headers.get(headerName);
1529
- let token = "";
1530
- if (header) {
1531
- token = header;
1532
- if (headerName.toLowerCase() === "authorization") {
1533
- const parts = header.split(" ");
1534
- if (parts[0]?.toLowerCase() === "bearer") {
1535
- token = parts.slice(1).join(" ");
1536
- }
1537
- }
1538
- } else {
1539
- const url = new URL(req.url);
1540
- const qsToken = url.searchParams.get("access_token");
1541
- if (qsToken) {
1542
- token = qsToken;
1543
- from = "query";
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);
1544
1614
  }
1615
+ return lastOutput;
1545
1616
  }
1546
- if (!token) {
1547
- return new Response("Unauthorized", {
1548
- status: 401,
1549
- headers: headerName.toLowerCase() === "authorization" ? { "WWW-Authenticate": "Bearer" } : void 0
1550
- });
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})`);
1551
1631
  }
1552
- if (options.proxy) {
1553
- const proxyUrl = typeof options.proxy === "string" ? new URL(options.proxy) : options.proxy;
1554
- const proxyHeaders = {};
1555
- if (from === "header" && header) {
1556
- proxyHeaders[headerName] = header;
1557
- } else {
1558
- proxyUrl.searchParams.set("access_token", token);
1559
- }
1560
- for (const name of ["x-forwarded-for", "x-real-ip", "user-agent", "content-type"]) {
1561
- const v = req.headers.get(name);
1562
- if (v) proxyHeaders[name] = v;
1563
- }
1564
- const proxyRes = await fetch(proxyUrl.href, { headers: proxyHeaders });
1565
- if (proxyRes.status >= 400) {
1566
- return new Response(await proxyRes.text() || "Forbidden", { status: proxyRes.status });
1567
- }
1568
- let userData = void 0;
1569
- if (proxyRes.status === 200) {
1570
- const ct = proxyRes.headers.get("content-type");
1571
- if (ct?.includes("application/json")) {
1572
- try {
1573
- userData = await proxyRes.json();
1574
- } catch {
1575
- }
1576
- }
1577
- }
1578
- ctx.user = userData;
1579
- return next(req, ctx);
1632
+ const condition = Boolean(evaluateExpression(conditionExpr, ctx));
1633
+ if (!condition) break;
1634
+ for (const bodyNode of node.body ?? []) {
1635
+ lastOutput = await executeNode(bodyNode, ctx);
1580
1636
  }
1581
- if (options.token) {
1582
- if (token !== options.token) {
1583
- return new Response("Forbidden", { status: 403 });
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
+ }
1584
1654
  }
1585
- return next(req, ctx);
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);
1586
1666
  }
1587
- if (options.verify) {
1588
- const result = await options.verify(token, req);
1589
- if (!result) {
1590
- return new Response("Forbidden", { status: 403 });
1591
- }
1592
- if (typeof result === "object" && result !== null) {
1593
- ctx.user = result;
1594
- }
1595
- return next(req, ctx);
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);
1596
1687
  }
1597
- return next(req, ctx);
1598
- };
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);
1599
1716
  }
1600
1717
 
1601
- // static.ts
1602
- import { createHash as createHash2 } from "node:crypto";
1603
- import { open } from "node:fs/promises";
1604
- import { extname, resolve as resolve2, normalize, sep as sep2 } from "node:path";
1605
- function serveStatic(root, options) {
1606
- const rootDir = resolve2(root);
1607
- const opts = options ?? {};
1608
- return async (req, ctx) => {
1609
- const relativePath = ctx.params["*"] ?? new URL(req.url).pathname.slice(1);
1610
- const decoded = decodeURIComponent(relativePath);
1611
- if (decoded.includes("..") || decoded.includes("\0")) {
1612
- return new Response("Forbidden", { status: 403 });
1613
- }
1614
- let filePath = normalize(resolve2(rootDir, decoded));
1615
- if (!filePath.startsWith(rootDir + sep2) && filePath !== rootDir) {
1616
- return new Response("Forbidden", { status: 403 });
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
+ ]
1617
1755
  }
1618
- let fileHandle;
1619
- try {
1620
- fileHandle = await open(filePath, "r");
1621
- const stat = await fileHandle.stat();
1622
- if (stat.isDirectory()) {
1623
- await fileHandle.close();
1624
- const indexFile = opts.index ?? "index.html";
1625
- filePath = resolve2(filePath, indexFile);
1626
- if (!filePath.startsWith(rootDir + sep2)) {
1627
- return new Response("Forbidden", { status: 403 });
1628
- }
1629
- fileHandle = await open(filePath, "r");
1630
- const dirStat = await fileHandle.stat();
1631
- if (!dirStat.isFile()) {
1632
- await fileHandle.close();
1633
- return new Response("Not Found", { status: 404 });
1634
- }
1635
- }
1636
- const mimeType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
1637
- const etag = `"${createHash2("md5").update(`${stat.size}-${stat.mtimeMs}`).digest("hex")}"`;
1638
- const ifNoneMatch = req.headers.get("if-none-match");
1639
- if (ifNoneMatch === etag) {
1640
- await fileHandle.close();
1641
- return new Response(null, { status: 304 });
1642
- }
1643
- const ifModifiedSince = req.headers.get("if-modified-since");
1644
- if (ifModifiedSince && stat.mtimeMs <= new Date(ifModifiedSince).getTime()) {
1645
- await fileHandle.close();
1646
- return new Response(null, { status: 304 });
1647
- }
1648
- const headers = {
1649
- "Content-Type": mimeType,
1650
- "Content-Length": String(stat.size),
1651
- "ETag": etag,
1652
- "Last-Modified": stat.mtime.toUTCString(),
1653
- "Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
1654
- };
1655
- const stream = fileHandle.readableWebStream();
1656
- return new Response(stream, { headers });
1657
- } catch (err) {
1658
- if (fileHandle) await fileHandle.close().catch(() => {
1659
- });
1660
- if (err?.code === "ENOENT") {
1661
- return new Response("Not Found", { status: 404 });
1662
- }
1663
- return new Response("Internal Server Error", { status: 500 });
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");
1664
1793
  }
1665
- };
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
+ }
1666
1801
  }
1667
- var MIME_TYPES = {
1668
- ".html": "text/html; charset=utf-8",
1669
- ".htm": "text/html; charset=utf-8",
1670
- ".css": "text/css; charset=utf-8",
1671
- ".js": "application/javascript; charset=utf-8",
1672
- ".mjs": "application/javascript; charset=utf-8",
1673
- ".json": "application/json",
1674
- ".png": "image/png",
1675
- ".jpg": "image/jpeg",
1676
- ".jpeg": "image/jpeg",
1677
- ".gif": "image/gif",
1678
- ".svg": "image/svg+xml",
1679
- ".ico": "image/x-icon",
1680
- ".webp": "image/webp",
1681
- ".avif": "image/avif",
1682
- ".woff": "font/woff",
1683
- ".woff2": "font/woff2",
1684
- ".ttf": "font/ttf",
1685
- ".otf": "font/otf",
1686
- ".eot": "application/vnd.ms-fontobject",
1687
- ".txt": "text/plain; charset=utf-8",
1688
- ".xml": "application/xml",
1689
- ".pdf": "application/pdf",
1690
- ".zip": "application/zip",
1691
- ".wasm": "application/wasm",
1692
- ".map": "application/json"
1693
- };
1694
1802
 
1695
- // validate.ts
1696
- function validate(schemas) {
1697
- return async (req, ctx, next) => {
1698
- const parsed = {};
1699
- const issues = [];
1700
- if (schemas.params) {
1701
- const result = schemas.params.safeParse(ctx.params);
1702
- if (result.success) {
1703
- parsed.params = result.data;
1704
- } else {
1705
- issues.push(...result.error.issues.map((i) => ({
1706
- path: ["params", ...i.path.map(String)],
1707
- message: i.message
1708
- })));
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})`);
1709
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 } });
1710
1835
  }
1711
- if (schemas.query) {
1712
- const result = schemas.query.safeParse(ctx.query);
1713
- if (result.success) {
1714
- parsed.query = result.data;
1715
- } else {
1716
- issues.push(...result.error.issues.map((i) => ({
1717
- path: ["query", ...i.path.map(String)],
1718
- message: i.message
1719
- })));
1720
- }
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);
1721
1861
  }
1722
- if (schemas.headers) {
1723
- const rawHeaders = {};
1724
- req.headers.forEach((v, k) => {
1725
- rawHeaders[k] = v;
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
1726
1872
  });
1727
- const result = schemas.headers.safeParse(rawHeaders);
1728
- if (result.success) {
1729
- parsed.headers = result.data;
1730
- } else {
1731
- issues.push(...result.error.issues.map((i) => ({
1732
- path: ["headers", ...i.path.map(String)],
1733
- message: i.message
1734
- })));
1735
- }
1873
+ return { text: result.text };
1874
+ });
1875
+ }
1876
+ return {
1877
+ execute,
1878
+ runAsync,
1879
+ generateWorkflow: generateWorkflow2,
1880
+ getState(workflowId) {
1881
+ return states.get(workflowId);
1736
1882
  }
1737
- if (schemas.body) {
1738
- if (req.body === null) {
1739
- issues.push({ path: ["body"], message: "Request body is required" });
1740
- } else {
1741
- const bodyText = await req.text();
1742
- if (!bodyText && req.method !== "GET" && req.method !== "HEAD") {
1743
- issues.push({ path: ["body"], message: "Request body is required" });
1744
- } else {
1745
- let bodyValue;
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) {
1746
1902
  try {
1747
- bodyValue = JSON.parse(bodyText);
1903
+ controller.enqueue(encoder.encode(event));
1748
1904
  } catch {
1749
- bodyValue = bodyText;
1750
- }
1751
- const result = schemas.body.safeParse(bodyValue);
1752
- if (result.success) {
1753
- parsed.body = result.data;
1754
- } else {
1755
- issues.push(...result.error.issues.map((i) => ({
1756
- path: ["body", ...i.path.map(String)],
1757
- message: i.message
1758
- })));
1905
+ break;
1759
1906
  }
1760
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"
1761
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
+ );
1762
1982
  }
1763
- if (issues.length > 0) {
1764
- return Response.json({ error: "Validation failed", issues }, { status: 400 });
1983
+ if (options.stream && sseManager) {
1984
+ const workflowId = crypto.randomUUID();
1985
+ engine.runAsync(workflowId, wf);
1986
+ return Response.json({ workflowId, eventsUrl: `/${workflowId}/events` });
1765
1987
  }
1766
- ctx.parsed = parsed;
1767
- return next(req, ctx);
1768
- };
1988
+ const result = await engine.execute(wf);
1989
+ return Response.json({ workflow: wf, result });
1990
+ });
1991
+ return r;
1769
1992
  }
1770
1993
 
1771
- // cookie.ts
1772
- function getCookies(req) {
1773
- const header = req.headers.get("cookie");
1774
- if (!header) return {};
1775
- const cookies = {};
1776
- for (const pair of header.split(";")) {
1777
- const idx = pair.indexOf("=");
1778
- if (idx === -1) continue;
1779
- const name = pair.slice(0, idx).trim();
1780
- const value = pair.slice(idx + 1).trim();
1781
- if (name) {
1782
- cookies[name] = decodeURIComponent(value);
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;
1783
2006
  }
1784
2007
  }
1785
- return cookies;
2008
+ return inner;
1786
2009
  }
1787
- function serializeCookie(name, value, options) {
1788
- const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
1789
- if (options?.maxAge != null) parts.push(`Max-Age=${options.maxAge}`);
1790
- if (options?.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
1791
- if (options?.domain) parts.push(`Domain=${options.domain}`);
1792
- if (options?.path) parts.push(`Path=${options.path}`);
1793
- if (options?.httpOnly) parts.push("HttpOnly");
1794
- if (options?.secure) parts.push("Secure");
1795
- if (options?.sameSite) parts.push(`SameSite=${options.sameSite}`);
1796
- return parts.join("; ");
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;
1797
2026
  }
1798
- function setCookie(res, name, value, options) {
1799
- const headers = new Headers(res.headers);
1800
- headers.append("Set-Cookie", serializeCookie(name, value, options));
1801
- return new Response(res.body, {
1802
- status: res.status,
1803
- statusText: res.statusText,
1804
- headers
1805
- });
2027
+ function hasUUIDCheck(field) {
2028
+ const checks = field._def?.checks ?? [];
2029
+ return checks.some((c) => c.type === "string" && c.def?.format === "uuid");
1806
2030
  }
1807
- function deleteCookie(res, name, options) {
1808
- const headers = new Headers(res.headers);
1809
- headers.append("Set-Cookie", serializeCookie(name, "", { ...options, maxAge: 0 }));
1810
- return new Response(res.body, {
1811
- status: res.status,
1812
- statusText: res.statusText,
1813
- headers
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 };
1814
2073
  });
1815
2074
  }
1816
-
1817
- // upload.ts
1818
- import { writeFile, mkdir } from "node:fs/promises";
1819
- import { randomUUID } from "node:crypto";
1820
- import { join as join2 } from "node:path";
1821
- function upload(options) {
1822
- const saveDir = options?.dir;
1823
- return async (req, ctx, next) => {
1824
- const ct = req.headers.get("content-type") ?? "";
1825
- if (!ct.includes("multipart/form-data")) {
1826
- return next(req, ctx);
1827
- }
1828
- const match = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
1829
- if (!match) {
1830
- return Response.json({ error: "Missing boundary" }, { status: 400 });
1831
- }
1832
- const boundary = match[1] ?? match[2];
1833
- const body = await req.text();
1834
- const rawParts = body.split(`--${boundary}`).filter((p) => p && !p.startsWith("--") && !p.startsWith("\r\n--"));
1835
- const files = {};
1836
- const fields = {};
1837
- for (const raw of rawParts) {
1838
- const trimmed = raw.replace(/^\r?\n/, "");
1839
- const lines = trimmed.split(/\r?\n/);
1840
- let i = 0;
1841
- const headers = {};
1842
- while (i < lines.length && lines[i].length > 0) {
1843
- const sep3 = lines[i].indexOf(": ");
1844
- if (sep3 !== -1) headers[lines[i].slice(0, sep3).toLowerCase()] = lines[i].slice(sep3 + 2);
1845
- i++;
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);
1846
2091
  }
1847
- i++;
1848
- const bodyValue = lines.slice(i).join("\r\n");
1849
- const disposition = headers["content-disposition"] ?? "";
1850
- const nameMatch = disposition.match(/name="([^"]*)"/);
1851
- if (!nameMatch) continue;
1852
- const name = nameMatch[1];
1853
- const filenameMatch = disposition.match(/filename="([^"]*)"/);
1854
- const filename = filenameMatch?.[1];
1855
- if (filename) {
1856
- const buf = Buffer.from(bodyValue.replace(/\r?\n$/, ""), "binary");
1857
- if (options?.allowedTypes) {
1858
- const mime = headers["content-type"] ?? "application/octet-stream";
1859
- if (!options.allowedTypes.includes(mime)) {
1860
- return Response.json({ error: `File type not allowed: ${mime}` }, { status: 415 });
1861
- }
1862
- }
1863
- if (options?.maxFileSize && buf.byteLength > options.maxFileSize) {
1864
- return Response.json({ error: `File too large: ${filename}` }, { status: 413 });
1865
- }
1866
- const uf = {
1867
- name: filename,
1868
- type: headers["content-type"] ?? "application/octet-stream",
1869
- size: buf.byteLength,
1870
- buffer: saveDir ? void 0 : buf
1871
- };
1872
- if (saveDir) {
1873
- const filePath = join2(saveDir, `${randomUUID()}-${filename}`);
1874
- await mkdir(saveDir, { recursive: true });
1875
- await writeFile(filePath, buf);
1876
- uf.path = filePath;
1877
- }
1878
- if (files[name]) {
1879
- const existing = files[name];
1880
- files[name] = Array.isArray(existing) ? [...existing, uf] : [existing, uf];
1881
- } else {
1882
- files[name] = uf;
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()}`);
1883
2099
  }
1884
- } else {
1885
- fields[name] = bodyValue.replace(/\r?\n$/, "");
1886
2100
  }
1887
2101
  }
1888
- ctx.parsed = { ...ctx.parsed, files, fields };
1889
- return next(req, ctx);
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) };
1890
2110
  };
1891
2111
  }
1892
-
1893
- // rate-limit.ts
1894
- function rateLimit(options) {
1895
- const max = options?.max ?? 100;
1896
- const window = options?.window ?? 6e4;
1897
- const getKey = options?.key ?? ((req) => {
1898
- const forwarded = req.headers.get("x-forwarded-for");
1899
- if (forwarded) return forwarded.split(",")[0].trim();
1900
- return new URL(req.url).hostname;
1901
- });
1902
- const message = options?.message ?? "Too Many Requests";
1903
- const hits = /* @__PURE__ */ new Map();
1904
- const interval = setInterval(() => {
1905
- const now = Date.now();
1906
- for (const [key, entry] of hits) {
1907
- if (entry.reset < now) hits.delete(key);
1908
- }
1909
- }, window);
1910
- if (interval.unref) interval.unref();
1911
- return async (req, ctx, next) => {
1912
- const key = getKey(req);
1913
- const now = Date.now();
1914
- const entry = hits.get(key);
1915
- if (!entry || entry.reset < now) {
1916
- hits.set(key, { count: 1, reset: now + window });
1917
- const res2 = await next(req, ctx);
1918
- const headers2 = new Headers(res2.headers);
1919
- headers2.set("X-RateLimit-Limit", String(max));
1920
- headers2.set("X-RateLimit-Remaining", String(max - 1));
1921
- headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window) / 1e3)));
1922
- return new Response(res2.body, { status: res2.status, statusText: res2.statusText, headers: headers2 });
1923
- }
1924
- entry.count++;
1925
- const remaining = Math.max(0, max - entry.count);
1926
- if (entry.count > max) {
1927
- return new Response(message, {
1928
- status: 429,
1929
- headers: {
1930
- "Retry-After": String(Math.ceil((entry.reset - now) / 1e3)),
1931
- "X-RateLimit-Limit": String(max),
1932
- "X-RateLimit-Remaining": "0",
1933
- "X-RateLimit-Reset": String(Math.ceil(entry.reset / 1e3))
1934
- }
1935
- });
1936
- }
1937
- const res = await next(req, ctx);
1938
- const headers = new Headers(res.headers);
1939
- headers.set("X-RateLimit-Limit", String(max));
1940
- headers.set("X-RateLimit-Remaining", String(remaining));
1941
- headers.set("X-RateLimit-Reset", String(Math.ceil(entry.reset / 1e3)));
1942
- return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
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
+ };
1943
2157
  };
1944
2158
  }
1945
2159
 
1946
- // compress.ts
1947
- import { gzipSync, brotliCompressSync, constants } from "node:zlib";
1948
- function compress(options) {
1949
- const level = options?.level ?? 6;
1950
- const threshold = options?.threshold ?? 1024;
1951
- return async (req, ctx, next) => {
1952
- const accept = req.headers.get("accept-encoding") ?? "";
1953
- const useBrotli = accept.includes("br");
1954
- const useGzip = !useBrotli && accept.includes("gzip");
1955
- const useDeflate = !useBrotli && !useGzip && accept.includes("deflate");
1956
- if (!useBrotli && !useGzip && !useDeflate) {
1957
- return next(req, ctx);
1958
- }
1959
- const res = await next(req, ctx);
1960
- if (res.status === 304 || res.status === 204 || res.status < 200 || res.status >= 300) {
1961
- return res;
1962
- }
1963
- const ce = res.headers.get("content-encoding");
1964
- if (ce) return res;
1965
- const ct = res.headers.get("content-type") ?? "";
1966
- if (!ct || ct.startsWith("audio/") || ct.startsWith("video/") || ct.startsWith("image/") || ct === "application/zip") {
1967
- return res;
1968
- }
1969
- const body = await res.bytes();
1970
- if (body.byteLength < threshold) return res;
1971
- let compressed;
1972
- let encoding;
1973
- if (useBrotli) {
1974
- compressed = brotliCompressSync(body, {
1975
- params: { [constants.BROTLI_PARAM_QUALITY]: Math.min(level, 11) }
1976
- });
1977
- encoding = "br";
1978
- } else if (useGzip) {
1979
- compressed = gzipSync(body, { level: Math.min(level, 9) });
1980
- encoding = "gzip";
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));
1981
2185
  } else {
1982
- compressed = gzipSync(body, { level: Math.min(level, 9) });
1983
- encoding = "deflate";
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
+ }
1984
2192
  }
1985
- const headers = new Headers(res.headers);
1986
- headers.set("Content-Encoding", encoding);
1987
- headers.set("Content-Length", String(compressed.byteLength));
1988
- headers.delete("Content-Range");
1989
- headers.set("Vary", "Accept-Encoding");
1990
- return new Response(compressed, {
1991
- status: res.status,
1992
- statusText: res.statusText,
1993
- headers
1994
- });
1995
- };
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;
1996
2221
  }
1997
2222
  export {
1998
2223
  Router,
1999
2224
  TsxContext,
2225
+ ai,
2000
2226
  auth,
2001
2227
  compress,
2002
2228
  cors,
@@ -2005,7 +2231,9 @@ export {
2005
2231
  deleteCookie,
2006
2232
  generateWorkflow,
2007
2233
  getCookies,
2234
+ graphql,
2008
2235
  logger,
2236
+ postgres,
2009
2237
  rateLimit,
2010
2238
  serve,
2011
2239
  serveStatic,
@@ -2014,5 +2242,6 @@ export {
2014
2242
  tsx,
2015
2243
  upload,
2016
2244
  useTsx,
2017
- validate
2245
+ validate,
2246
+ workflow
2018
2247
  };