- State & Storage
- Config Management
Config Management
Ironflow provides environment-scoped configuration management. It allows you to store, patch, and watch JSON documents representing application settings, feature flags, or system parameters.
Event-Driven by Design
Section titled “Event-Driven by Design”Config Management is built directly on top of Ironflow’s NATS JetStream architecture using system-level KV buckets (SYS_config_*), separate from user-facing KV Store buckets.
- Durable: Every change is persisted to an internal NATS KV bucket (not visible in the KV Store dashboard).
- Atomic:
patchoperations use automatic CAS (Compare-And-Swap) retries to prevent lost updates. - Observable: Every
set,patchanddeleteemits a system event that can trigger workflows or real-time dashboards. - Self-healing: Config buckets are lazily created on first use and automatically recreated if deleted.
When to Use Config Management
Section titled “When to Use Config Management”| Feature | Use For |
|---|---|
| Config Management | Application settings, Feature flags, Environment toggles. |
| KV Store | Session data, caching, high-frequency counters. |
| Secrets | API Keys, DB passwords (encrypted at rest). |
Quick Start
Section titled “Quick Start”Node.js (Server-side)
Section titled “Node.js (Server-side)”import { createClient } from "@ironflow/node";
const ironflow = createClient({ apiKey: process.env.IRONFLOW_API_KEY });const config = ironflow.config();
// Set a full documentawait config.set("app-settings", { theme: "dark", retries: 3 });
// Patch a specific field (shallow merge)await config.patch("app-settings", { retries: 5 });
// Watch for real-time changes (Node SDK also supports watch via WebSocket)const watcher = config.watch("app-settings", { onUpdate: (event) => console.log("New settings:", event.data),});// watcher.stop() to end the subscriptionconfig.patch() automatically creates the document if it does not exist — no separate “init” call is required.
Python
Section titled “Python”from contextlib import aclosingfrom ironflow import IronflowClient
client = IronflowClient("http://localhost:9123", api_key="ifkey_...")client.config_create("app-settings", {"theme": "dark", "retries": 3})
# Watch for real-time changes (needs: pip install 'ironflow-py[watch]')async with aclosing(client.watch_config("app-settings")) as events: async for ev in events: print(ev.revision, ev.data)Browser (Client-side)
Section titled “Browser (Client-side)”import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });
// Watch for real-time changesconst sub = await ironflow.configManager().watch("app-settings", { onUpdate: (cfg) => console.log("New settings:", cfg.data),});REST API Reference
Section titled “REST API Reference”| Method | Path | Description |
|---|---|---|
| GET | /api/v1/config | List all config documents. |
| GET | /api/v1/config/{name} | Get a specific document. |
| POST | /api/v1/config/{name} | Replace a document (Full Set). |
| PATCH | /api/v1/config/{name} | Shallow merge fields (creates the document if missing). |
| DELETE | /api/v1/config/{name} | Delete a document. |
| GET | /api/v1/config/{name}/watch | WebSocket stream of ConfigWatchEvents. |
Successful POST and PATCH writes also publish the system event system.config.{name}.updated, which any subscriber can consume. A successful DELETE publishes system.config.{name}.deleted, with data set to null.
For a conditional write, send If-Match: <revision> using the numeric revision returned by
GET. POST, PATCH and DELETE accept it. POST also accepts If-None-Match: * to create
only if the name is absent. A failed condition returns 412 without changing the document.
If-Match succeeds only while that revision is the live document, so a deleted config cannot be
written back with its delete revision from the watch stream. A conditional PATCH merges once
onto that revision and does not retry. A combined, repeated, empty or malformed condition returns
400. Without these headers, POST creates or replaces, PATCH merges, and DELETE deletes,
all unconditionally.
The body of POST and PATCH must be a JSON object; null returns 400. A body larger than
4 MiB returns 413. The stored document has the NATS message limit (1 MiB by default), and a
document larger than that limit also returns 413.
The Node and Go SDKs send these conditions for you:
| Node | Go | Condition |
|---|---|---|
config.create(name, data) | Config().Create(ctx, name, data) | If-None-Match: * |
config.update(name, data, revision) | Config().Update(ctx, name, data, revision) | If-Match on POST |
config.patchIf(name, data, revision) | Config().PatchIf(ctx, name, data, revision) | If-Match on PATCH |
config.deleteIf(name, revision) | Config().DeleteIf(ctx, name, revision) | If-Match on DELETE |
In Python, pass if_match= or if_none_match= to config_create, and if_match= to
config_patch or config_delete.
The dashboard uses the revision captured when editing starts. Each config keeps its own draft, so you can select another config and come back to it. If another writer saves a newer revision or deletes the config, the editor warns that saving will fail. If another writer changes or deletes the document, saving reports a conflict and keeps the draft open. Copy the draft, select Cancel, then select Edit to load the latest version. Creating a config in the dashboard also refuses to replace an existing name. The list marks each config that has an unsaved draft. Delete deletes only the revision on screen; if another writer changed the config, the delete fails and the config stays.
Error Handling
Section titled “Error Handling”Config operations throw typed errors consistent with the main Ironflow client:
import { IronflowError, UnauthenticatedError, UnauthorizedError } from "@ironflow/node";
try { const entry = await config.get("app-settings");} catch (err) { if (err instanceof UnauthenticatedError) { console.error("Invalid or missing API key"); } else if (err instanceof UnauthorizedError) { console.error("Insufficient permissions"); } else if (err instanceof IronflowError) { console.error("Config error:", err.message); }}from ironflow import IronflowError
try: entry = client.config_get("app-settings")except IronflowError as err: if err.status_code == 401: print("Invalid or missing API key") elif err.status_code == 403: print("Insufficient permissions") else: print("Config error:", err)