HTTP server for Generata workflow handlers. Auto-discovers user-authored handler scripts under serve/, mounts each at POST /<route>, and runs them in-process with Bearer auth, async run lifecycle (202 + status URL), and disk-persisted run state.
pnpm add @generata/serve- Drop a handler script under
serve/in your project:
// serve/review.ts
import type { Handler } from "@generata/serve";
import { reviewWorkflow } from "../workflows/review.ts";
const handler: Handler = async ({ body, runAsync }) => {
return runAsync(reviewWorkflow, { pr: String(body.pr_number) });
};
export default handler;- Set the auth token and start the server:
export GENERATA_SERVE_TOKEN=$(openssl rand -hex 32)
pnpm generata-serve --port 3000- Fire a request:
curl -X POST http://127.0.0.1:3000/review \
-H "Authorization: Bearer $GENERATA_SERVE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pr_number": 123}'
# → 202 with { "runId": "..." } and Location: /runs/<id>
curl http://127.0.0.1:3000/runs/<id> \
-H "Authorization: Bearer $GENERATA_SERVE_TOKEN"
# → { "runId": "...", "status": "pending"|"completed"|"failed", ... }Sync (200 response, blocks until done):
import type { Handler } from "@generata/serve";
const handler: Handler = async ({ body, runWorkflow }) => {
const result = await runWorkflow(myWorkflow, { input: String(body.input) });
return { ok: true, summary: result.steps.summary?.output };
};
export default handler;Async (202 response, run continues in background):
import type { Handler } from "@generata/serve";
const handler: Handler = async ({ body, runAsync }) => {
return runAsync(myWorkflow, { input: String(body.input) });
};
export default handler;runAsync takes (workflow, args, options?) matching runWorkflow's 3-arg signature.
generata-serve [options]
--port <number> Listen port (default 3000)
--host <string> Listen host (default 127.0.0.1)
--serve-dir <path> Override serveDir from config
--token-env <name> Env var name for the auth token (default GENERATA_SERVE_TOKEN)
--shutdown-timeout <s> Drain timeout on SIGTERM (default 30)
--help Show help
generata.config.ts:
import { defineConfig } from "@generata/core";
export default defineConfig({
serve: {
serveDir: "serve",
port: 3000,
host: "127.0.0.1",
tokenEnv: "GENERATA_SERVE_TOKEN",
bodyLimitBytes: 1024 * 1024,
shutdownTimeoutSec: 30,
runStoreDir: ".generata/runs",
},
});CLI flags override config; config overrides built-in defaults.
Built-in HMAC verification (GitHub, Slack, Stripe, etc.) is intentionally out of scope for v1. Two recommended patterns:
Reverse-proxy (Caddy example for GitHub):
example.com {
reverse_proxy /webhook 127.0.0.1:3000 {
header_up Authorization "Bearer {env.GENERATA_SERVE_TOKEN}"
}
}(Pair with a Caddy plugin or sidecar that verifies X-Hub-Signature-256 before forwarding.)
In-handler verification: do it inside the handler itself by reading the relevant header from body (after JSON-parsing) plus a known secret. Note that body is parsed from the request - if you need byte-level signature verification you'll want a reverse proxy in front.
- v1 is single-process and in-memory + disk-persisted. No multi-tenant isolation, no rate limiting.
- Handlers run in the daemon's Node process; one bad handler can affect concurrent requests in the same process.
- Token rotation requires daemon restart.
- Run state is never auto-evicted (infinite TTL by design - delete files under
.generata/runs/to reclaim disk).