/** * Workflow channel kinds. Every state field declared on a workflow's * `stateSchema` is bound to a channel that decides the merge strategy * applied when multiple writers update the same field within a single * execution step. * * The names are **Graphorin's own design** and must not be aliased to * terms from other workflow libraries. A dedicated lint rule lands later * in the release line to enforce this. * * @stable */ export type ChannelKind = | 'latest-value' | 'any-value' | 'reducer' | 'list-aggregate' | 'stream' | 'barrier' | 'ephemeral'; /** * Discriminated union of every channel descriptor. * * Channels are a *description* of the merge strategy, not a runtime * value: the engine reads the `kind` field plus optional auxiliary * fields (`reduce`, `from`, `unique`) to decide how to combine writes. * * @stable */ export type Channel = | LatestValue | AnyValue | Reducer | ListAggregate | Stream | Barrier | Ephemeral; /** * Overwrite-on-write. Multiple writes within the same execution step * raise `MultiWriteError` (use `AnyValue` if collisions are acceptable). * * @stable */ export interface LatestValue { readonly kind: 'latest-value'; readonly default?: T; } /** * Overwrite-on-write - collisions are silently allowed (last-write-wins * semantics within a step). * * @stable */ export interface AnyValue { readonly kind: 'any-value'; readonly default?: T; } /** * Fold writes via a user-provided `reduce` function. The reducer is * invoked left-to-right over the writes collected within an execution * step. * * @stable */ export interface Reducer { readonly kind: 'reducer'; readonly default?: T; readonly reduce: (prev: T, next: T) => T; } /** * Specialization of `Reducer` that appends each write to a list. * * @stable */ export interface ListAggregate { readonly kind: 'list-aggregate'; readonly default?: ReadonlyArray; } /** * Append-only queue. Used for dynamic task creation via `Dispatch(...)` * and for application-defined event streams. * * @stable */ export interface Stream { readonly kind: 'stream'; readonly unique?: boolean; readonly default?: ReadonlyArray; } /** * Barrier - completes when every writer in `from` has produced a value. * * @stable */ export interface Barrier { readonly kind: 'barrier'; readonly from: ReadonlyArray; readonly default?: T; } /** * Value scoped to a single execution step - discarded when the step * ends. * * @stable */ export interface Ephemeral { readonly kind: 'ephemeral'; readonly default?: T; } /** * Construct a `LatestValue` channel. * * @stable */ export function latestValue(opts?: { readonly default?: T }): LatestValue { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'latest-value', default: opts.default } : { kind: 'latest-value' }; } /** * Construct an `AnyValue` channel. * * @stable */ export function anyValue(opts?: { readonly default?: T }): AnyValue { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'any-value', default: opts.default } : { kind: 'any-value' }; } /** * Construct a `Reducer` channel. * * @stable */ export function reducer( reduce: (prev: T, next: T) => T, opts?: { readonly default?: T }, ): Reducer { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'reducer', reduce, default: opts.default } : { kind: 'reducer', reduce }; } /** * Construct a `ListAggregate` channel. * * @stable */ export function listAggregate(opts?: { readonly default?: ReadonlyArray }): ListAggregate { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'list-aggregate', default: opts.default } : { kind: 'list-aggregate' }; } /** * Construct a `Stream` channel. * * @stable */ export function stream(opts?: { readonly unique?: boolean; readonly default?: ReadonlyArray; }): Stream { const out: { kind: 'stream'; unique?: boolean; default?: ReadonlyArray } = { kind: 'stream', }; if (opts?.unique !== undefined) out.unique = opts.unique; if (opts?.default !== undefined) out.default = opts.default; return out; } /** * Construct a `Barrier` channel. * * @stable */ export function barrier( from: ReadonlyArray, opts?: { readonly default?: T }, ): Barrier { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'barrier', from, default: opts.default } : { kind: 'barrier', from }; } /** * Construct an `Ephemeral` channel. * * @stable */ export function ephemeral(opts?: { readonly default?: T }): Ephemeral { return opts !== undefined && 'default' in opts && opts.default !== undefined ? { kind: 'ephemeral', default: opts.default } : { kind: 'ephemeral' }; }