Siftline
Packages

@siftline/actions

Adapters that carry a Decision to a webhook or to Slack.

npm install @siftline/actions

ESM only. Node 22.14 or newer. Depends on @siftline/core for the Decision and Recipe types.

defineActions(definitions) declares your Actions, keyed by Action id. Each value is { kind, config }, with the config typed by that kind's Adapter. Every config is parsed against its kind's configSchema at once, and the ids stay literal keys in the result. ActionId<typeof actions> reads those ids back as a union, for typing Rules: Rule<Q, ActionId<typeof actions>> rejects an Action id you did not define. dispatch(decision, actions, recipe, { fetch?, signal? }) sends a routed Decision to the Action it selected and returns { action, request, response }, or null when the Decision went to Review or selected no Action.

Underneath, building a request and sending it are two steps. An Adapter's build(decision, config, recipe) turns a Decision into an ActionRequest: method, URL, headers, body and the idempotency key. perform(request, { fetch?, signal? }) sends it. dispatch only composes the two. Preview is build without perform, and nothing in a request depends on a clock or randomness, so the same Decision, config and Recipe always build the same bytes. The guide shows both requests.

Adapters

ExportkindBody
webhookwebhookThe Decision line, no envelope. parseDecision reads it.
slackIncomingWebhookslack_incoming_webhookOne mrkdwn message with each answer and its confidence.

adapters maps each kind to its adapter, and every adapter carries a configSchema for a config that arrives as JSON. The webhook config is { url, secret?, headers? }; with a secret the request carries X-Siftline-Signature: sha256=<HMAC-SHA256 of the body>. The Slack config is { url }.

Every request carries Idempotency-Key: <decision.id>:<decision.action> and a User-Agent naming the package version. Slack ignores the key, so a redelivery past your own guard posts twice.

Sending

perform sends once, with no retries and no timeout of its own, and keeps the first 4096 bytes of the response body, cut on a code-point boundary. It returns { status, body, truncated }. dispatch sends through perform and adds no retry or timeout either. fetch defaults to the global one, read on each call. The option is typed narrower than the global fetch, so the global one and a stub both fit without a cast. signal goes to fetch unchanged.

Errors

ClasscodeThrown when
ActionBuildErroraction_buildAn unknown kind or an invalid config in defineActions, an Action id dispatch was not given, or build on a Decision that selected no Action.
ActionFailedErroraction_failedA non-2xx status or a fetch that threw. Carries status and retryable, true for 408, 429, 5xx and network failures.

Both extend core's SiftlineError.

On this page