UNPKG

@temporalio/workflow

Version:
133 lines (117 loc) 5.13 kB
import { assertInWorkflowContext } from './global-attributes'; import type { Activator } from './internals'; import { fillWithRandom, uuid4FromRandom } from './random-helpers'; /** * A deterministic PRNG stream scoped to the current Workflow execution. * * Workflow code already gets a deterministic default stream through `Math.random()`. * This interface exposes additional named streams that are derived from the workflow * seed without consuming that default stream. Repeated calls to * {@link getRandomStream} with the same name refer to the same logical stream state * for the current workflow execution, and that state is preserved across activations. * * The primary use case is workflow plugins and interceptors that need private, * replay-stable entropy without perturbing user workflow randomness. * * @experimental This API may be removed or changed in the future. */ export interface WorkflowRandomStream { /** * Draw the next deterministic pseudo-random number from this stream. * * This is equivalent to `Math.random()`, but isolated from the workflow's main * random stream and from other named streams. */ random(): number; /** * Generate a deterministic UUIDv4 backed by this stream. */ uuid4(): string; /** * Fill a byte array deterministically from this stream. */ fill(bytes: Uint8Array): Uint8Array; /** * Run `fn` with scoped workflow-random helpers such as `Math.random()` and `uuid4()` * routed through this stream. * * This is the scoped override API for workflow random streams. It is intended for * bounded plugin/interceptor code that needs existing calls to `Math.random()` or * `uuid4()` to use this stream without perturbing the workflow's default random * sequence or any other named stream. * * The override follows async continuations started by `fn`. Workflow interceptor * `next(...)` continuations restore the downstream workflow random scope before * entering the rest of the interceptor chain or workflow code, so a temporary * plugin scope does not leak downstream unless that downstream code explicitly * establishes its own scope. * * Prefer explicit `stream.random()` / `stream.uuid4()` calls when that is practical. * Use `stream.with(...)` when a temporary scoped override is the better fit, or * when you want to keep the stream instance in module scope and reuse it directly. */ with<T>(fn: () => T): T; } class ActivatorRandomStream implements WorkflowRandomStream { constructor( protected readonly activator: Activator, protected readonly name: string ) {} random(): number { return this.activator.getNamedRandom(this.name)(); } uuid4(): string { return uuid4FromRandom(() => this.random()); } fill(bytes: Uint8Array): Uint8Array { return fillWithRandom(() => this.random(), bytes); } with<T>(fn: () => T): T { return this.activator.withCurrentRandom(this, fn); } } class DefaultWorkflowRandomStream implements WorkflowRandomStream { random(): number { return assertInWorkflowContext('Workflow.workflowRandom may only be used from workflow context.').random(); } uuid4(): string { return uuid4FromRandom(() => this.random()); } fill(bytes: Uint8Array): Uint8Array { return fillWithRandom(() => this.random(), bytes); } with<T>(fn: () => T): T { const activator = assertInWorkflowContext('Workflow.workflowRandom may only be used from workflow context.'); return activator.withCurrentRandom(this, fn); } } /** * The default deterministic random stream for the current workflow execution. * * This exposes the same underlying sequence used by workflow-level `Math.random()` * when no named override is active. It can be useful for plugin/interceptor code * that wants an explicit handle to the main workflow random stream, including from * inside a temporary named scope established by another `WorkflowRandomStream`. * * @experimental This API may be removed or changed in the future. */ export const workflowRandom: WorkflowRandomStream = new DefaultWorkflowRandomStream(); /** * Get a named deterministic random stream for the current workflow execution. * * Named streams are derived from the workflow seed and a stable stream name, * without consuming the workflow's default `Math.random()` stream. Repeated * calls with the same `name` within a workflow execution refer to the same * logical stream state, including across activations. * * This is the preferred entry point for workflow plugins and interceptors that * need their own deterministic entropy. Use stable package- or module-style * names so the stream identity remains replay-safe, then keep the returned * `WorkflowRandomStream` around and call its methods directly. * * @experimental This API may be removed or changed in the future. */ export function getRandomStream(name: string): WorkflowRandomStream { const activator = assertInWorkflowContext('Workflow.getRandomStream(...) may only be used from workflow context.'); return new ActivatorRandomStream(activator, name); }