| title | Io Node Config |
|---|---|
| description | Io Node Config protocol schemas |
{/*
@module automation/io-node-config
Config contracts for the flat IO builtins — notify and http (#4045).
Each schema here was derived by reading what the executor actually does with
node.config (service-automation/builtin/notify-node.ts, http-nodes.ts),
not by transcribing the hand-written configSchema literal on the node's
descriptor. That independence is the point: the two artifacts are reconciled
bidirectionally by io-node-form-zod-ledger.test.ts in service-automation,
and a Zod copied from the form would make that reconciliation a tautology —
it would pass by construction and prove nothing (#4045).
Like LoopConfigSchema / ParallelConfigSchema / TryCatchConfigSchema,
these are live execute-time contracts: each executor parse()s its
config against its schema before running (service-automation's
parse-config.ts), so type and required violations refuse the node as a
guard (not routable via fault edges). notify parses the RAW stored
config — its slots are string-typed, so \{token\} templates pass and the
post-interpolation guards still own "resolved to nothing". http parses
the INTERPOLATED config, because that is the shape its executor reads —
a \{token\} in a typed slot (timeoutMs, durable) resolves to its real
type first. Unknown keys are the registration layer's job: registerFlow()
rejects keys the descriptor configSchema does not declare (the tightened
#4059 check), while the parse here strips them.
connector_action has no schema here on purpose: its config contract is
empty. The executor reads only the declared FlowNodeSchema.connectorConfig
sibling block — see the descriptor note in
service-automation/builtin/connector-nodes.ts.
import { HttpConfig, NotifyConfig } from '@objectstack/spec/automation';
import type { HttpConfig, NotifyConfig } from '@objectstack/spec/automation';
// Validate data
const result = HttpConfig.parse(data);| Property | Type | Required | Description |
|---|---|---|---|
| url | string |
✅ | Target URL |
| method | string |
optional | HTTP method (default GET; POST when durable) |
| headers | Record<string, string> |
optional | Request headers |
| body | any |
optional | Request body (JSON-serialised) |
| durable | boolean |
optional | Fire-and-forget via the durable outbox (retry/dead-letter) instead of inline request/response |
| timeoutMs | number |
optional | Per-request timeout (ms) |
| signingSecret | string |
optional | HMAC-SHA256 secret → X-Objectstack-Signature |
| Property | Type | Required | Description |
|---|---|---|---|
| recipients | string | string[] |
✅ | Recipient user id(s) / audience selector(s); {token} templates resolve per run |
| title | string |
✅ | Notification title |
| message | string |
optional | Notification body |
| channels | string | string[] |
optional | Channels to fan out to (default: inbox) |
| topic | string |
optional | Event topic (default: "notify") |
| severity | string |
optional | info | warning | critical |
| sourceObject | string |
optional | Object name of the record the notification links to (writes sys_notification.source_object). Requires sourceId. |
| sourceId | string |
optional | Record id the notification links to (writes sys_notification.source_id). Requires sourceObject. |
| actorId | string |
optional | User id that caused the event (writes sys_notification.actor_id) |
| actionUrl | string |
optional | Explicit click-through URL; overrides the link synthesized from sourceObject/sourceId |
| payload | Record<string, any> |
optional | Extra template inputs merged into the notification payload |