# Timing Nodes

Nodes that pause the workflow for a duration or until a point in time, resuming automatically. These require the `flowdrop_interrupt` module and a pipeline-backed orchestrator (e.g. **StateGraph**) — under the stateless synchronous orchestrator there is no pipeline to resume, so timing nodes cannot be used there.

**How resume works:** a timing node persists a pending *Timer* interrupt carrying the scheduled resume time and pauses the pipeline. On each cron run, due timers are resolved automatically and the pipeline continues. Precision is therefore cron-bound: a "5 minute" delay resumes on the first cron run at least 5 minutes after the pause. Sites that need tighter timing should run cron every minute.

**Skipping the wait:** a pending timer appears in the interrupt inbox as a "Waiting until …" card with a **Resume now** button. Resolving it there (or through the resume API) skips the remaining wait immediately — the node's outputs report who skipped and by how much.

The maximum accepted delay is governed by the `timer_max_delay` setting in `flowdrop_interrupt.settings` (default: 1 year; `0` for unlimited).

!!! tip "Sequencing downstream nodes"
    To run a node *after* the wait without passing data, connect the timing node's **trigger** output to the downstream node's **trigger** input. Wiring a data output (e.g. `resumed_at`) into a trigger input is interpreted as a gateway branch condition and the downstream node will be skipped.

---

## Delay

Pauses the workflow for a relative duration ("wait 2 hours").

**Category:** Timing

### Configuration

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| **value** | Number | Yes | `5` | How long to wait |
| **unit** | String | No | `minutes` | One of `seconds`, `minutes`, `hours`, `days` |

A zero delay passes straight through without pausing.

### Output Ports

| Port | Type | Description |
|------|------|-------------|
| **scheduled_at** | Number | Unix timestamp the workflow was scheduled to resume at |
| **resumed_at** | Number | Unix timestamp the workflow actually resumed at |
| **drift_seconds** | Number | Seconds between scheduled and actual resume (negative when skipped early) |
| **resolved_by_timer** | Boolean | `true` when the timer fired; `false` when a user skipped the wait |
| **user_id** | Number | ID of the user who skipped the wait (empty when the timer fired) |

---

## Schedule

Pauses the workflow until a specific date and time ("resume at 2026-06-10 09:00").

**Category:** Timing

### Configuration

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| **when** | String | Yes | When to resume: ISO 8601 datetime (`2026-06-10T09:00:00+02:00`), unix timestamp, or a natural phrase like `tomorrow 09:00`. Numeric values below `1000000000` (bare years, IDs) are rejected rather than misread as 1970-era timestamps |

The **when** input is connectable — an upstream node (e.g. a content entity's `publish_at` field) can feed it. A time already in the past passes straight through without pausing.

!!! warning "Timezones"
    A datetime **without an explicit offset** (`2026-06-10 09:00`) is resolved in the timezone of the user who launched the run, since Drupal sets the PHP timezone per user. For deterministic fire times, use ISO 8601 with an offset (`2026-06-10T09:00:00+02:00`) or a unix timestamp.

### Output Ports

Same as [Delay](#delay).

### Example: scheduled publishing

```
Load Entity → Schedule (when ← publish_at field) → Publish Entity
```

The workflow pauses after loading the entity, resumes on the first cron run after the scheduled publish time, and then publishes.

---

## Rate Limit

Limits how often execution passes through the node. Runs over the limit pause and retry automatically until a slot frees up.

Unlike Delay and Schedule, the limit is **shared across runs**: the counter is backed by Drupal's flood service, so all pipelines passing through the same bucket collectively stay under the limit. Use it to be polite to external APIs ("at most 100 calls per hour to this vendor") or to pace bulk operations.

**Category:** Timing

### Configuration

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| **limit** | Number | Yes | `10` | Maximum passes per window |
| **window** | Number | Yes | `3600` | Window length in seconds |
| **identifier** | String | No | — | Bucket key. Empty = a private bucket per node instance. Set the same key on several nodes (or connect a per-user/per-domain value) to share or partition the limit |

### Output Ports

The same audit trail as [Delay](#delay), plus:

| Port | Type | Description |
|------|------|-------------|
| **throttled** | Boolean | `true` when the run had to wait for a slot, `false` when it passed straight through |

### Behaviour notes

- A blocked run pauses on a timer paced at `window ÷ limit` seconds. On resume it **re-checks** the bucket — other runs may have taken the freed slot — and re-arms a fresh timer if still over the limit.
- Resolving the timer early from the inbox does **not** bypass the limit; it only triggers an early re-check.
- The limit is **best-effort, not a hard guarantee**: the check and the count are not atomic, so runs racing the same bucket in the same instant can briefly exceed it. Right for being polite to external APIs; wrong for billing enforcement.
- Long or non-ASCII identifiers are hashed internally (same input → same bucket), so connecting URLs or emails is safe.
- Precision is cron-bound: suitable for per-minute/per-hour limits, not per-second throttling. For sub-minute rates, throttle inside the node making the calls.
