The whole surface,part by part
DjiniousWorkflow is one Bun API over SurrealDB with a React client on top: an engine, a node catalog, a place for the graphs to live, and three ways in that do not involve a browser. This page walks all of it — what builds, what fires, what executes, what a graph reaches, and what it leaves behind.
Authoring
The graph is the document
A workflow is a set of tasks, each calling one function from the catalog, with the edges living in the nodes' own inputs and outputs. That makes a graph one self-contained document you can export, diff in a pull request, and import somewhere else — not a database of rows that only mean something inside one installation.
330 nodes, 19 tools
Flow control (23), data shaping (15), dataframe verbs (20), transforms (19), sandboxed JavaScript (2), HTTP (6), AI (4), entity CRUD (6), artifacts (4), run widgets (10), pages (7), notifications (2) — and the connectors: SQL (40), document and key-value stores (25), analytics and search (22), brokers (11), files and object storage (30), push and mail (37), SaaS APIs (47).
Parameters that know what they are
A parameter declares its type, its help text, whether it is required, and when it is even relevant — an HTTP node hides the body on a GET. A credential parameter offers the credentials of that kind and nothing else. Every parameter also accepts an expression, so a value can come from the payload, the run context or the loop counter.
Validation while you build
The header says whether the graph is runnable and, when it is not, which node and which edge is the problem. A draft is allowed to be incomplete — you save as you build — but a run refuses an invalid definition rather than discovering it three nodes in.


Triggering
Cron schedules
A schedule is a row with a cron expression, an optional timezone and a next-run time. Occurrences are claimed atomically, so with four replicas running exactly one of them fires each occurrence — the ordinary case, not a race to be avoided.
Inbound webhooks
A trigger mints its own token and answers at
/api/wf/hooks/<token>. The body becomes the run's input, and the trigger can name the task to enter, so one graph can serve several sources without a router node in front of it.Run from anywhere
POST /api/workflows/:id/runstarts a run; scoped API tokens (djwf_…) mean a script or a CI job can do it without a user session. A graph can also start another — fire-and-forget, or call-and-wait for its result.Human-driven, deliberately
Some runs should start when a person presses a button, and a run can also park mid-flight on a
ui:triggernode and continue when somebody presses one on the canvas. The wait is a row like any other, so the press can land on any replica.


Execution
A stateless engine
Every piece of run state — the queue of what to do next, a join's partial arrivals, a suspended approval, a repeating timer — is a row in the database. A process contributes a worker loop and nothing else, so scaling out is running more replicas and a node that dies has its work picked up by the others.
Fan-out and gather
For eachturns an array into one event per item, andCollectaccumulates them back. Each arrival is its own row rather than an append to a shared array, which is what keeps a forty-way fan-out from contending with itself, and what lets a gather survive the worker that started it.Retries, and then a branch
A node can retry with exponential backoff. When the attempts are spent the payload leaves by the error branch — carrying the failure message *and* the payload that failed — so the graph handles it instead of the run dying. With no error branch wired, the workflow's own setting decides between failing the run and letting the other branches finish.
Long waits cost nothing
An approval, an inbound signal, a delay of six hours, an interval that fires every ten minutes: none of them occupies a worker. They are rows with a time or a status on them, which is why a run can sit on a decision for days across restarts.


What a graph reaches
Databases and stores
PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, Timescale, QuestDB, Supabase, Redshift, Yugabyte; MongoDB, CouchDB, Redis and friends; ClickHouse, Elasticsearch, OpenSearch, Meilisearch, Typesense, Qdrant, InfluxDB, Prometheus, BigQuery. Query, select, insert, upsert, update, delete, transaction — plus the introspection nodes that list and describe tables.
Files, objects and brokers
CSV, XML, YAML, JSONL, Excel and ZIP as pure format nodes with no network at all; S3, FTP and SFTP as location nodes that move bytes; Kafka, RabbitMQ, MQTT and NATS for the queues. Consume nodes are bounded and say so.
Credentials, sealed
A connector node names a credential; the credential holds the value, sealed with AES-256-GCM, and no endpoint returns it. The same graph runs against staging and production by pointing at a different credential of the same name.
A node the catalog does not have
Build it in the app: an HTTP call, a GraphQL query, a SQL statement, a script, or another graph wrapped as a single step. It lands in the same catalog as the built-ins, so the palette, the reference page, the MCP tool list and the assistant all pick it up immediately — with no fork and no deploy.


What it leaves behind
Pages
A
views:*node writes a table, a chart, a metric, some markdown or a map into a saved page. Panels are addressed by key, so republishing replaces the data wherever the page has been arranged to put it and keeps the title and size a person chose. A workflow is the only writer of panel data — the REST surface enforces it.Records
Entity types are defined at runtime — fields, a status machine, an icon — and a graph writes records into them. Every record carries the id of the run that produced it, so a figure on a page traces back to the execution that computed it.
Files and resources
A project holds artifacts: data, documents, images, video and links. Where the bytes go is the platform's decision, not the caller's — small payloads stay in the database, real files go to the S3-compatible object store, and a URL is stored as a reference rather than a copy.
Out of the building
In-app notifications, outbound webhooks, email over SMTP, and the push and messaging connectors for everything else. A graph that finishes quietly is a graph nobody trusts twice.


Where the work lives
A project is a workspace
Graphs live in a project with the resources they share, and a graph reaches its project's resources by name — so the same graph cloned into another project reads that project's copy of 'the source list'. Every workflow belongs to exactly one project; there is no unfiled state to tidy up later.
Artifacts hold what a run made
Data, documents, images, video and links. Where the bytes go is the platform's decision rather than the caller's: a value stays in the database, a real file goes to the object store, a URL is stored as a reference. The row records which, so nothing is ambiguous about what you are holding.
An ontology you define at runtime
Entity types are configured, not coded: fields with types, a status machine with its transitions, an icon and a colour. A graph writes records into them, and the record carries the run that wrote it — which is how a figure on a dashboard is traced back to an execution.
Import, export, diff
A workflow exports as one JSON document and imports into another deployment. That is what makes a graph reviewable in a pull request, and what makes 'promote this from staging' a file rather than a migration.


Being driven
The REST API is the product
Everything the UI does is an API call, and the UI is one client among several. Scoped tokens (
djwf_…) let a script, a CI job or another service author, run and inspect a workflow without a user session.MCP, in the same process
An MCP server answers at
POST /mcp: list the catalog, create a workflow, run it, read its logs, resolve an approval, query entities. An agent with that endpoint can build and operate automation without a browser — and the tools it sees are generated from the same catalog the palette reads.Provider Interface v1
The platform contract other Djinious apps and
djinious-atomsdrive this one through: a versioned operation registry, a/capabilitiesdocument, and an OpenAPI snapshot committed to the repo with a unit test that fails on drift — because a consumer's CI is written against that file.Roles that mean something
Admin, user and viewer. A viewer sees workflows, runs and logs and can change none of them — including that a custom node exists without seeing its implementation, and never a credential's value. The checks live next to the route handlers, not only in the UI.


A script and a cron entry, and what changes when the engine holds the state
Most of what this product runs was a script before it was a graph, and for plenty of jobs a script is still the right answer. These are the axes on which the two differ — not a scorecard.
| Axis of comparison | A script and a cron entry | DjiniousWorkflow |
|---|---|---|
| Where the state is | In the process, until it exits. A crash halfway through is a state you reconstruct from the logs, if the logs happen to say enough. | In the database, per node. A crash halfway through is a lease that expires and another worker picking the event up. |
| What failure does | Whatever the try/except says, if there is one. The common case is the whole job stopping on the first thing that did not answer. | Retries with backoff, then the payload leaves by the error branch and the graph handles it. With no branch wired, the run fails and names the node. |
| Waiting for a person | A flag in a table, a second script to poll it, and a third thing to notice when nobody answers. | A node. The run suspends in the database and continues when the decision is recorded, with the approver on the row. |
| Doing many at once | As much as the process can hold, on the machine it happens to be on. | One event per item on a shared queue. More throughput is more replicas; nothing is pinned to a node. |
| What ran, three weeks later | The lines the author remembered to log, if the retention kept them. | The run: the graph as it was, every node's result, the log, and the records and files it wrote — each stamped with the run id. |
| Changing it | A pull request, a review and a deploy — for the tolerance as well as for the logic. | The canvas, and a pull request when you want one: a graph is a document you can export, diff and import. |
| Who may change it | Whoever can deploy. | Whoever has the role. A viewer can read every run and every log and change nothing, which is a different person from the one who can deploy. |
One database, and what is in it
The unusual thing about this engine is not what it is built on but how little lives outside it. Everything below is a table you can query, which is what makes the operational questions answerable.
- SurrealDB owns
- Users, roles and tokens; projects, workflows and their versions; the run ledger, per-task state and logs; the event queue, gather arrivals, waits and timers; schedules and triggers; credentials (sealed); custom nodes; the mirrored node catalog; entities and the ontology that shapes them; views and their panels.
- Object storage
- Artifact bytes above the inline ceiling go to an S3-compatible bucket (RustFS in the dev stack). It is optional: with no endpoint configured everything stays in the database and the platform is fully functional — it simply cannot hold large files comfortably.
- The worker
- A dispatcher loop per replica. It claims events with a lease, executes them, records the result and enqueues the consequences. It holds no run state of its own, which is the property everything else on the reliability page depends on.
- Sandboxed code
- A JavaScript node runs in a Bun worker with a timeout, not in the server's own context. Local file nodes are confined to a configured root and are disabled — not unrestricted — when that root is unset.
- Identity
- JWT (HMAC-SHA256) for people, Argon2id for their passwords, and scoped
djwf_tokens for everything else.
Walk the surface with us
A demo goes through this page in the running product: build a graph, fire it, watch it execute, and open the page it published — inside the hour, on your own example.