Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 78 additions & 40 deletions apps/web/content/docs/modules/functions.mdx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
---
title: Functions
description: Admin-defined server-side JavaScript with grpcSdk access.
agent_summary: "Admin-trusted JS — webhooks, event bus handlers, admin routes; grpcSdk access; not a substitute for database custom endpoints."
agent_summary: "Admin-trusted JS — request/webhook/socket/middleware/event/cron types; VM timeout + require allowlist; routes on Client API via router; CRUD on Admin API."
---

The **functions** module runs **admin-defined JavaScript** in-process with full `grpcSdk` access. Treat function code like server configuration — admin-trusted, not user-supplied.
import FunctionsDeepDive from "@/mdx/deep-dives/functions-deep-dive.mdx";

The **functions** module runs **admin-defined JavaScript** in-process with full `grpcSdk` access. Operators upload function definitions through the Admin API; HTTP and socket handlers register on the **Client API** (via router), while function CRUD and execution history live on the **Admin API**. Treat function code like server configuration — admin-trusted, not user-supplied.

## Use cases

<ModuleUseCases
items={[
{
title: "Webhooks",
outcome: "Receive Stripe/GitHub callbacks and call grpcSdk.database or grpcSdk.email",
outcome: "Receive Stripe/GitHub callbacks at /hook/{name} and call grpcSdk.database or grpcSdk.email",
},
{
title: "Custom Client API routes",
outcome: "Authenticated request handlers at /{name} with declared params and returns",
},
{
title: "Event automation",
outcome: "React to bus events — send notifications when records change",
outcome: "React to Redis bus events — send notifications when records change",
},
{
title: "Scheduled jobs",
outcome: "Cron functions run on a BullMQ schedule (Redis-backed)",
},
{
title: "Admin-only routes",
outcome: "Custom HTTP handlers registered on Admin API surface",
title: "Socket handlers",
outcome: "Custom Socket.io event handlers on a dedicated namespace path",
},
{
title: "Cross-module orchestration",
Expand All @@ -33,72 +43,100 @@ The **functions** module runs **admin-defined JavaScript** in-process with full

<ModuleCapabilities
items={[
"In-process JS execution",
"Six function types (request, webhook, middleware, socket, event, cron)",
"Client API route registration via router",
"node:vm sandbox (timeout + require allowlist)",
"grpcSdk access",
"Webhook handlers",
"Event bus subscribers",
"Admin HTTP routes",
"Database & comms integration",
"BullMQ cron scheduler",
"Execution logging & metrics",
"GitOps export/import",
]}
/>

## Example: Webhook handler concept
## Example: Webhook handler

<ModuleExample
steps={[
"Admin defines a Function that exposes POST /hook/stripe (Admin API)",
"Function validates webhook signature in JS",
"On payment_intent.succeeded, grpcSdk.database finds Order and updates status",
"grpcSdk.email sends receipt using a provisioned template",
"Optional: grpcSdk.chat sends system message to order room",
"Admin uploads a webhook function via POST /functions/upload (Admin API)",
"Router registers POST /hook/stripeWebhook on CLIENT_BASE_URL",
"External provider POSTs to the hook URL",
"Function validates signature in JS, calls grpcSdk.database to update Order",
"grpcSdk.email sends receipt; res({ ok: true }) returns the response shape",
]}
>
<APIExample
title="Conceptual handler (admin-defined JS — not Client API)"
curl={`# Functions run on Admin API surface, configured via admin panel or MCP
# Example: webhook receives POST, calls grpcSdk internally
curl -X POST http://localhost:3030/function/stripeWebhook \\
title="Invoke webhook (Client API)"
curl={`curl -X POST http://localhost:3000/hook/stripeWebhook \\
-H "Content-Type: application/json" \\
-d '{"type":"payment_intent.succeeded","data":{...}}'`}
/>
<APIExample
title="Upload function (Admin API)"
curl={`curl -X POST http://localhost:3030/functions/upload \\
-H "masterkey: YOUR_MASTERKEY" \\
-H "Content-Type: application/json" \\
-d '{
"name": "stripeWebhook",
"functionType": "webhook",
"functionCode": "const sig = req.headers[\\"stripe-signature\\"]; /* verify + handle */ res({ received: true });",
"inputs": { "method": "POST" },
"returns": { "received": "Boolean" }
}'`}
/>
</ModuleExample>

## How it works

<ModuleDeepDive>
### Trust boundary
<FunctionsDeepDive />
</ModuleDeepDive>

Functions code is **admin-trusted** — equivalent to a custom module deployed by operators. It is not exposed to end users as arbitrary script upload. For user-facing filtered queries, use [database custom endpoints](/docs/modules/database) at `/database/function/{name}` on the Client API instead.
## Configure

### grpcSdk access
| Key | Default | Meaning |
|-----|---------|---------|
| `active` | `true` | Module serves and registers routes when router is up |

Inside a function, `grpcSdk` provides typed clients for database, authentication, storage, chat, communications, and other registered modules — same as custom ManagedModule services.
Define functions via Admin panel or Admin API when the functions module is enabled. Each document stores `name`, `functionType`, `functionCode`, `inputs`, optional `returns`, and `timeout`.

### When to use Functions vs custom modules
On create/update/delete the module publishes to the `functions` bus channel and **refreshes router registrations**.

| Need | Use |
|------|-----|
| Filtered Client API query | Database custom endpoint |
| Long-running domain service | Custom ManagedModule |
| Webhook, cron, one-off admin automation | Functions |
| System chat message from backend | grpcSdk.chat from Function or custom module |
</ModuleDeepDive>
## Client API

## Configure
Functions register routes on the router (not Admin API):

Define functions via Admin panel or MCP when the functions module is enabled. Each function specifies trigger type (HTTP route, bus event), source code, and enabled flag.
| Type | Example path | Notes |
|------|--------------|-------|
| `request` | `PUT /myHandler` | When `inputs.method` is `PUT` → UPDATE action |
| `webhook` | `POST /hook/stripeWebhook` | Typically unauthenticated |
| `socket` | Namespace `/{name}` | Event from `inputs.event` |

## Client API
App runtime code calls these URLs on `CLIENT_BASE_URL` like any other Client API route. **`request` functions with `inputs.auth: true` require a bearer token.**

Functions do **not** replace `/database/function/{name}` — those are database custom endpoints.

## Admin API

Function management on `ADMIN_BASE_URL/functions/...`:

Functions do **not** replace Client API routes for app runtime. App code calls `/database/function/{name}` for provisioned queries — those are database custom endpoints, not this module.
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/functions/upload` | Create function |
| GET | `/functions/` | List (`search`, `skip`, `limit`, `sort`) |
| GET | `/functions/:id` | Get function |
| PATCH | `/functions/:id` | Update code, inputs, returns, timeout |
| DELETE | `/functions/:id` | Delete function |
| DELETE | `/functions/` | Bulk delete (`ids[]` query) |
| GET | `/functions/list/executions` | All execution logs |
| GET | `/functions/executions/:functionId` | Per-function executions (`success` filter) |

## MCP

Enable functions module in deployment. Admin tools manage function CRUD alongside other admin routes.
Enable with `?modules=functions` in your MCP server URL. Admin tools manage function CRUD; uploaded functions appear on the Client API after router refresh.

<NextSteps steps={[
{ title: "Router (Client API gateway)", href: "/docs/modules/router" },
{ title: "Database custom endpoints", href: "/docs/modules/database" },
{ title: "Communications (email from webhooks)", href: "/docs/modules/communications" },
{ title: "Admin API reference", href: "/docs/reference/admin-api" },
{ title: "Module authoring (custom modules)", href: "/docs/learn/architecture" },
{ title: "Client vs Admin API", href: "/docs/learn/client-vs-admin-api" },
]} />
110 changes: 68 additions & 42 deletions apps/web/content/docs/modules/router.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
---
title: Router
description: Client API gateway — REST, GraphQL, and WebSockets.
agent_summary: "Hermes gateway on CLIENT_HTTP_PORT 3000 and CLIENT_SOCKET_PORT 3001; aggregates module client routes; Admin API on core :3030."
agent_summary: "Hermes gateway on CLIENT_HTTP_PORT 3000 and CLIENT_SOCKET_PORT 3001; proxies module client routes over gRPC; Admin API on core :3030; transports rest/graphql/sockets only."
---

The **router** module (Hermes) exposes the **Client API** by aggregating routes registered by other modules. It is the single entry point applications connect to.
import RouterDeepDive from "@/mdx/deep-dives/router-deep-dive.mdx";

The **router** module (Hermes) is the **Client API gateway**. Every module that serves application traffic registers routes with router over gRPC; Hermes terminates HTTP, GraphQL, and Socket.io on the client ports and **proxies** each request to the owning module handler. The **Admin API** lives on **core** (`:3030`) — router does not expose admin routes on the client port.

## Use cases

Expand All @@ -16,7 +18,7 @@ The **router** module (Hermes) exposes the **Client API** by aggregating routes
},
{
title: "Realtime apps",
outcome: "REST on :3000, Socket.io on :3001 with shared auth",
outcome: "REST on :3000, Socket.io on :3001 with shared auth middleware",
},
{
title: "GraphQL queries",
Expand All @@ -26,19 +28,26 @@ The **router** module (Hermes) exposes the **Client API** by aggregating routes
title: "Client credentials",
outcome: "Router validates client id/secret context for auth flows",
},
{
title: "Public hook URLs",
outcome: "hostUrl config builds correct /hook/... links in emails and deep links",
},
]}
/>

## Capabilities

<ModuleCapabilities
items={[
"REST aggregation",
"REST aggregation (Hermes)",
"GraphQL",
"WebSockets / Socket.io",
"Module route mounting",
"Client credential context",
"CORS & middleware",
"gRPC route proxy to modules",
"Global middleware (CORS, rate limit, captcha, client validation)",
"Per-route middleware patching (Admin API)",
"Security client registry",
"HA route recovery via Redis bus",
"Swagger / API reference",
]}
/>

Expand All @@ -47,69 +56,86 @@ The **router** module (Hermes) exposes the **Client API** by aggregating routes
<ModuleExample
steps={[
"Application calls CLIENT_BASE_URL (default http://localhost:3000)",
"Router forwards /authentication/* to authentication module routes",
"Router forwards /database/* to database module routes",
"Router forwards /storage/*, /chat/*, /authorization/* to respective modules",
"Router matches path and verb, runs global + route middleware",
"Router forwards the request to the module gRPC handler that registered the route",
"Module handler returns; router serializes the response to the client",
"Socket.io listens on CLIENT_SOCKET_PORT (default 3001) with path /realtime",
"Admin API (:3030) lives on core — not router",
"Admin API (:3030) on core — operators inspect routes and patch middleware there",
]}
/>

## How it works

<ModuleDeepDive>
### Port layout

| Port | Env var | Protocol |
|------|---------|----------|
| 3000 | `CLIENT_HTTP_PORT` | REST, GraphQL |
| 3001 | `CLIENT_SOCKET_PORT` | WebSockets / Socket.io |
| 3030 | Admin | Admin API (core module) |

### Request flow
<RouterDeepDive />
</ModuleDeepDive>

```
App → Router (Hermes) → Module client routes
→ authMiddleware, client credentials
→ database, storage, chat, …
```
## Configure

Each module registers its Client API routes with Hermes at startup. The database module is required by router and most feature modules.
Router requires the [database](/docs/modules/database) module. Enable feature modules in deployment; their Client routes appear automatically when they register with router.

### What router is not
Patch via MCP `patch_config_router` (`?modules=router`):

Router does **not** replace [database custom endpoints](/docs/modules/database) — filtered queries still need provisioned `/database/function/{name}` endpoints. Router also does not expose Admin API routes; use `:3030` or MCP for provisioning.
</ModuleDeepDive>

## Configure
| Key | Default | Meaning |
|-----|---------|---------|
| `hostUrl` | `http://localhost:3000` | Public Client API base for generated links |
| `transports.rest` | `true` | Enable REST |
| `transports.graphql` | `true` | Enable GraphQL |
| `transports.sockets` | `true` | Enable Socket.io |
| `cors.enabled` | `true` | CORS middleware |
| `cors.origin` | `*` | Allowed origin(s), comma-separated |
| `security.clientValidation` | `false` | Require registered client credentials on auth routes |
| `captcha.enabled` | `false` | Captcha on applicable routes |
| `rateLimit.maxRequests` | `50` | Max requests per interval per user |
| `rateLimit.resetInterval` | `1` | Reset interval in seconds |

Router is a core dependency — enable other modules in deployment; their Client routes appear automatically. Environment variables:
Environment variables:

| Variable | Default | Meaning |
|----------|---------|---------|
| `CLIENT_HTTP_PORT` | 3000 | REST/GraphQL port |
| `CLIENT_SOCKET_PORT` | 3001 | WebSocket port |
| `CLIENT_HTTP_PORT` | `3000` | REST/GraphQL port |
| `CLIENT_SOCKET_PORT` | `3001` | WebSocket port |

See [Environment variables](/docs/reference/env-vars) and [Architecture](/docs/learn/architecture).

## Client API

All module Client paths are relative to `CLIENT_BASE_URL`:

- `/authentication/*`
- `/database/*`
- `/storage/*`
- `/chat/*`
- `/authorization/*`
- `/graphql`
| Prefix | Module |
|--------|--------|
| `/authentication/*` | Authentication |
| `/database/*` | Database |
| `/storage/*` | Storage |
| `/chat/*` | Chat |
| `/authorization/*` | Authorization |
| `/hook/*` | Functions webhooks (and module hooks) |
| `/{functionName}` | Functions `request` type |
| `/graphql` | GraphQL (when enabled) |

## Admin API

Operator routes registered by the router module on `ADMIN_BASE_URL`:

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/routes` | List all registered client routes by module |
| GET | `/router/middlewares` | List available middleware handlers |
| GET | `/router/route-middlewares` | Query `path` + `action` → middleware chain |
| PATCH | `/router/patch-middleware` | Patch middleware on a client route |
| POST | `/security/client` | Create security client (platform + secret) |
| GET | `/security/client` | List security clients |
| PATCH | `/security/client/:id` | Update security client |
| DELETE | `/security/client/:id` | Delete security client |

## MCP

Router has no separate MCP module — configure via core and enable feature modules (`?modules=database,authentication,…`).
Enable router in deployment for `patch_config_router` and route inspection tools. Feature modules are enabled separately (`?modules=database,authentication,…`).

<NextSteps steps={[
{ title: "Architecture", href: "/docs/learn/architecture" },
{ title: "Client vs Admin API", href: "/docs/learn/client-vs-admin-api" },
{ title: "Database module", href: "/docs/modules/database" },
{ title: "Chat (sockets)", href: "/docs/modules/chat" },
{ title: "Functions (hooks on router)", href: "/docs/modules/functions" },
{ title: "Deployment overview", href: "/docs/deployment" },
]} />
Loading
Loading