import { ConditionQueueConfig } from '../../../types/hmsh_escalations'; /** * Pauses the workflow until a signal with the given `signalId` is received. * The workflow suspends durably — it survives process restarts and will * resume exactly once when the matching `signal()` call delivers data. * * `condition` is the **receive** side of the signal coordination pair. * The **send** side is `signal()`, which can be called from another * workflow, a hook function, or externally via `client.workflow.signal()`. * * On replay, `condition` returns the previously stored signal payload * immediately — no actual suspension occurs. * * ## Basic usage * * ```typescript * import { Durable } from '@hotmeshio/hotmesh'; * * export async function approvalWorkflow(orderId: string): Promise { * const { submitForReview } = Durable.workflow.proxyActivities(); * await submitForReview(orderId); * * // Pause until a human approves or rejects * const decision = await Durable.workflow.condition<{ approved: boolean }>('approval'); * return decision.approved; * } * * // From an API handler or another workflow: * await client.workflow.signal('approval', { approved: true }); * ``` * * ## With timeout * * Pass a duration string as the second argument to set a deadline. * `condition` returns `false` if the timeout fires before a signal arrives. * * ```typescript * const decision = await Durable.workflow.condition<{ approved: boolean }>( * 'approval', * '72h', // give reviewers 72 hours; returns false on timeout * ); * if (decision === false) return 'auto-rejected-timeout'; * return decision.approved ? 'approved' : 'rejected'; * ``` * * ## With escalation queue config * * Pass a {@link ConditionQueueConfig} as the second argument to surface the * pause as a claimable row in `public.hmsh_escalations`. The INSERT — every * field of the config, including `metadata` facets — is committed atomically * with the workflow checkpoint: one write, one commit, crash-safe. From the * row's first visible moment it carries its complete routing context and * metadata, so claim-by-metadata routing and version-pinned facets (e.g. a * `schema_version` the resolver UI renders) can trust every row they read. * * ```typescript * const decision = await Durable.workflow.condition<{ approved: boolean }>( * 'manager-approval', * { * role: 'manager', * type: 'order-approval', * subtype: 'regional', * priority: 2, * description: 'Approve or reject the regional order', * metadata: { orderId, region }, * envelope: { instructions: 'Review the attached order' }, * timeout: '72h', // SLA: resume with false + expire the row if unresolved * }, * ); * if (decision === false) return 'auto-rejected-sla'; // row is now status='expired' * * // Elsewhere: list, claim, then resolve (resumes the workflow) * const [item] = await client.escalations.list({ role: 'manager', status: 'pending' }); * await client.escalations.claim({ id: item.id, assignee: 'alice@company.com' }); * await client.escalations.resolve({ id: item.id, resolverPayload: { approved: true } }); * ``` * * ## Assign at creation * * Set `assignee` in the config to write `assigned_to` in the same atomic * commit — the row is born routed to that user (e.g. hand the follow-on step * to `$resolution.resolvedBy` of a prior escalation) and is resolvable by * them immediately. Add `durationMinutes` to arm the claim TTL window at * creation, locking the row to the assignee exactly as `claim()` would. * * ## Batch accumulation: one wait, N contributions * * Set `batch` in the config to declare the wait as an accumulator. Each * declared item key is filled exactly once via * `client.escalations.resolveBatchItem()`; interim fills are cheap row * updates (`accepted`, row stays pending), and the LAST fill resolves the * row and resumes the wait with the full collection — one atomic statement. * Type the generic as the collection: * * ```typescript * const parts = await Durable.workflow.condition< * Record<'cut' | 'weld' | 'paint', StationResult> * >(signalId, { * role: 'assembly', * batch: ['cut', 'weld', 'paint'], * metadata: { orderId }, * timeout: '24h', * }); * if (parts === false) return 'sla-expired'; // partial items audit on the expired row * if (parts === null) return 'cancelled'; * parts.weld; // typed item payload * ``` * * The row's `metadata.batch_pending` / `batch_count` facets track progress * (`@>`-queryable); payloads accumulate in `envelope.batch_items`. A plain * `resolve()` on a batch row remains an admin override that resolves the * whole row with the payload given. * * ## Placement: call escalation-bearing waits from main workflow code * * The resolve/signal delivery pipeline routes to the main flow's waiter. * Inside a hook function (`execHook`), an escalation-bearing wait writes its * row and honors `timeout` (the row expires and the hook resumes with * `false`), while resolution delivery targets the main flow — so structure * SLA-gated human waits in the workflow body and let hook functions report * back via `signal()`. * * ## Early signals are buffered * * A signal delivered before its `condition()` registers — a fast signaler, * or a payload deposited before the workflow starts — is buffered as a * pending signal and delivered when the wait registers. The buffer holds a * signal for 10 minutes by default; pass `expire` to `signal()` (e.g. * `'1h'`, `'30d'`) to hold it longer when signaling early on purpose. * * ## Fan-in: wait for multiple signals in parallel * * ```typescript * const [name, score] = await Promise.all([ * Durable.workflow.condition('name-signal'), * Durable.workflow.condition('score-signal'), * ]); * ``` * * Harvest fan-out scales the same way: open N waits with `Promise.all` and * signal all of them at once. Buffering covers every signal that races * ahead of registration, so size fan-out by the pending-signal TTL — how * long a racing signal may wait for its condition to register — with no * separate bound on the number of concurrent waits. * * ## Paired with hook: spawn work, wait for its signal * * ```typescript * const signalId = `result-${Durable.workflow.random()}`; * await Durable.workflow.hook({ * taskQueue: 'processors', * workflowName: 'processItem', * args: [input, signalId], * }); * return await Durable.workflow.condition(signalId); * ``` * * @template T - The type of data expected in the signal payload. * @param signalId - A unique signal identifier shared by the sender and receiver. * @param timeoutOrConfig - Optional timeout string (e.g. `'30s'`, `'24h'`) OR a * {@link ConditionQueueConfig} that writes one row to `public.hmsh_escalations` * atomically at suspension time. For an escalation-bearing wait with an SLA, * set the config's `timeout` field — the wait arms the same resume timer as * the string form, and when the timer wins the escalation row transitions * `pending → expired` so a late resolve fails as already-expired. (`expiresAt` * is display metadata on the row only; it arms nothing.) * @returns The signal payload, `false` when a timeout (string form or * `config.timeout`) expired first, or `null` if the escalation was cancelled * via `client.escalations.cancel()`. */ export declare function condition(signalId: string, timeoutOrConfig?: string | ConditionQueueConfig): Promise;