Architecture
Request path
The application is a Next.js App Router project. Routes are grouped by audience:
app/(marketing)— the public landing page and these documentsapp/(auth)— Clerk's sign-in, sign-up, and organization selectionapp/(dashboard)— the product, behind authentication
proxy.ts holds the Clerk middleware and decides which routes require a
session. / and /docs are deliberately absent from it.
Data
Postgres, accessed through Drizzle ORM. The schema lives in lib/db/schema/,
and migrations in lib/db/migrations/ are applied by drizzle-kit migrate.
Everything is scoped to a Clerk organization id.
lib/db/index.ts picks its driver from the connection string: Neon's HTTP
driver for a Neon host, a standard TCP pool for anything else. The client is
built on the first query rather than at import, so a build machine without a
working DATABASE_URL does not fail the build.
Running a workflow
Pressing Run saves the graph and triggers a Trigger.dev task
(features/workflows/tasks/run-workflow.ts). The task:
- Loads the saved graph.
- Drops unconnected nodes and sorts the rest topologically.
- Walks them in order, publishing each step's status to run metadata.
- Returns the steps and the session id the run used.
The canvas subscribes to that metadata, which is why steps light up live rather than at the end. Steps that need a browser share one cloud session, created lazily so a graph without them never opens one.
Collaboration
Each workflow has a Liveblocks room whose id is the workflow id. Rooms are created with the owning organization as the only group granted write access, so a workflow is private to its organization.
Adding a node
Three files under features/workflows/nodes/:
- The implementation, for example
open-url.ts. - Registration in
node-executors.ts. Thesatisfiescontract turns a missing executor into a compile error. - A manifest entry in
node-registry.ts— kind, label, icon, accent, input fields, and outputs.
The run task and the canvas step node are driven by the registry. Neither needs editing to add a node.
Billing and entitlement
getEntitlement(orgId) in features/billing/lib/entitlement.ts is the single
answer to what an organization may do. When DEPLOYMENT_MODE=self-hosted it
returns full access immediately, without reading the database or contacting the
payment provider.
Two enforcement points on the hosted deployment:
app/(dashboard)/workflows/layout.tsxredirects unentitled organizations to/upgrade.- Every mutating server action in
features/workflows/actions.tsre-checks entitlement itself, because a server action can be invoked without that layout ever rendering.
The layout is a convenience. The server actions are the boundary.
Brovio.cc