@voyantjs/workflows 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/trigger.ts ADDED
@@ -0,0 +1,146 @@
1
+ // Triggering workflows from server code, plus event filter declarations.
2
+ // Authoritative contract in docs/sdk-surface.md §6.
3
+
4
+ import type {
5
+ Duration,
6
+ EnvironmentName,
7
+ RunStatus,
8
+ } from "./types.js";
9
+ import type { WorkflowHandle, EnvironmentContext } from "./workflow.js";
10
+
11
+ // ---- workflows.* ----
12
+
13
+ export interface WorkflowsClient {
14
+ trigger<TIn, TOut>(
15
+ workflow: WorkflowHandle<TIn, TOut> | string,
16
+ input: TIn,
17
+ opts?: TriggerOptions,
18
+ ): Promise<Run<TOut>>;
19
+
20
+ signal(runId: string, name: string, payload: unknown, opts?: { nonce?: string }): Promise<void>;
21
+ completeToken(tokenId: string, payload: unknown): Promise<void>;
22
+
23
+ cancel(runId: string, opts?: { compensate?: boolean; reason?: string }): Promise<void>;
24
+ retry(runId: string, opts: { mode: "re-trigger" | "resume" }): Promise<Run>;
25
+ replay(runId: string, opts?: { fromStepId?: string; input?: unknown }): Promise<Run>;
26
+
27
+ get(runId: string): Promise<RunDetail>;
28
+ list(opts?: ListRunsOptions): Promise<{ runs: RunSummary[]; nextCursor?: string }>;
29
+
30
+ mintAccessToken(opts: MintAccessTokenOptions): Promise<PublicAccessToken>;
31
+ }
32
+
33
+ export interface TriggerOptions {
34
+ idempotencyKey?: string;
35
+ delay?: Duration | Date;
36
+ debounce?: { key: string; delay: Duration; mode?: "leading" | "trailing" };
37
+ ttl?: Duration;
38
+ tags?: string[];
39
+ priority?: number;
40
+ concurrencyKey?: string;
41
+ lockToVersion?: string;
42
+ environment?: EnvironmentName;
43
+ issuePublicAccessToken?: boolean;
44
+ }
45
+
46
+ export interface Run<TOut = unknown> {
47
+ id: string;
48
+ workflowId: string;
49
+ status: RunStatus;
50
+ startedAt: number;
51
+ accessToken?: string;
52
+ /** Phantom; used only for TypeScript inference. */
53
+ readonly __output?: TOut;
54
+ }
55
+
56
+ export interface RunSummary {
57
+ id: string;
58
+ workflowId: string;
59
+ status: RunStatus;
60
+ startedAt: number;
61
+ completedAt?: number;
62
+ tags: string[];
63
+ environment: EnvironmentName;
64
+ }
65
+
66
+ export interface RunDetail<TOut = unknown> extends RunSummary {
67
+ version: string;
68
+ input: unknown;
69
+ output?: TOut;
70
+ error?: unknown;
71
+ durationMs?: number;
72
+ // ... full shape in docs/runtime-protocol.md §4.2
73
+ }
74
+
75
+ export interface ListRunsOptions {
76
+ workflowId?: string;
77
+ status?: RunStatus | RunStatus[];
78
+ environment?: EnvironmentName;
79
+ tag?: string;
80
+ since?: Date | number;
81
+ until?: Date | number;
82
+ cursor?: string;
83
+ limit?: number;
84
+ }
85
+
86
+ export interface MintAccessTokenOptions {
87
+ target:
88
+ | { kind: "run"; runId: string }
89
+ | { kind: "workflow"; workflowId: string }
90
+ | { kind: "tag"; tag: string };
91
+ scope: ("read" | "trigger" | "cancel")[];
92
+ ttl?: Duration;
93
+ }
94
+
95
+ export interface PublicAccessToken {
96
+ token: string;
97
+ exp: number;
98
+ }
99
+
100
+ /**
101
+ * Top-level server SDK client. Resolves against the configured
102
+ * Voyant Cloud API key and account. The runtime implementation is
103
+ * installed by the cloud client package; imported alone, every
104
+ * method throws with guidance.
105
+ */
106
+ export const workflows: WorkflowsClient = new Proxy({} as WorkflowsClient, {
107
+ get(_, method: string) {
108
+ return () => {
109
+ throw new Error(
110
+ `@voyantjs/workflows: workflows.${method}() requires the Voyant Cloud client. ` +
111
+ `Install + configure it via @voyantjs/client, or see docs/sdk-surface.md §6.`,
112
+ );
113
+ };
114
+ },
115
+ });
116
+
117
+ // ---- trigger.on ----
118
+
119
+ export interface EventFilterHandle {
120
+ readonly id: string;
121
+ readonly event: string;
122
+ }
123
+
124
+ export interface EventFilterDeclaration<T> {
125
+ target: WorkflowHandle<T, unknown>;
126
+ match?: (
127
+ payload: T,
128
+ ctx: { environment: EnvironmentContext; project: { id: string } },
129
+ ) => boolean;
130
+ scope?: string;
131
+ input?: (payload: T) => unknown;
132
+ }
133
+
134
+ export interface TriggerApi {
135
+ on<T = unknown>(event: string, filter: EventFilterDeclaration<T>): EventFilterHandle;
136
+ }
137
+
138
+ export const trigger: TriggerApi = {
139
+ on<T>(_event: string, _filter: EventFilterDeclaration<T>): EventFilterHandle {
140
+ throw new Error(
141
+ "@voyantjs/workflows: trigger.on() must be collected by `voyant workflows build` and " +
142
+ "registered with the orchestrator at deploy time; it has no runtime behavior when " +
143
+ "called directly. See docs/sdk-surface.md §6.2.",
144
+ );
145
+ },
146
+ };
package/src/types.ts ADDED
@@ -0,0 +1,81 @@
1
+ // Core type aliases used across the SDK.
2
+ // Authoritative definitions in docs/sdk-surface.md §0 and §2.
3
+
4
+ export type Duration =
5
+ | number
6
+ | `${number}${"ms" | "s" | "m" | "h" | "d" | "w"}`;
7
+
8
+ /**
9
+ * Cloudflare Container instance types — the set Voyant Cloud honors
10
+ * for `runtime: "node"` steps. Match the sizes published at
11
+ * https://developers.cloudflare.com/containers/ (as of
12
+ * compat-date 2026-04-01).
13
+ *
14
+ * | name | vCPU | memory | disk |
15
+ * | ----------- | ----- | ------- | ----- |
16
+ * | lite | 1/16 | 256 MiB | 2 GB |
17
+ * | basic | 1/4 | 1 GiB | 4 GB |
18
+ * | standard-1 | 1/2 | 4 GiB | 8 GB |
19
+ * | standard-2 | 1 | 6 GiB | 12 GB |
20
+ * | standard-3 | 2 | 8 GiB | 16 GB |
21
+ * | standard-4 | 4 | 12 GiB | 20 GB |
22
+ *
23
+ * The open `(string & {})` escape hatch accepts CF custom instance
24
+ * types (up to 4 vCPU / 12 GiB / 20 GB, min 3 GiB RAM per vCPU) —
25
+ * rendered as `"custom-<vcpu>-<ramGiB>"` by convention.
26
+ */
27
+ export type MachineType =
28
+ | "lite"
29
+ | "basic"
30
+ | "standard-1"
31
+ | "standard-2"
32
+ | "standard-3"
33
+ | "standard-4"
34
+ | (string & {});
35
+
36
+ export type EnvironmentName = "production" | "preview" | "development";
37
+
38
+ export type RunStatus =
39
+ | "pending"
40
+ | "running"
41
+ | "waiting"
42
+ | "completed"
43
+ | "failed"
44
+ | "cancelled"
45
+ | "cancelled_by_dev_reload"
46
+ | "cancelled_by_version_sunset"
47
+ | "compensated"
48
+ | "compensation_failed"
49
+ | "timed_out";
50
+
51
+ export type ExecutionStatus =
52
+ | "CREATED"
53
+ | "QUEUED"
54
+ | "EXECUTING"
55
+ | "EXECUTING_WITH_WAITPOINTS"
56
+ | "SUSPENDED"
57
+ | "PENDING_CANCEL"
58
+ | "FINISHED";
59
+
60
+ export type WaitpointKind = "DATETIME" | "EVENT" | "SIGNAL" | "RUN" | "MANUAL";
61
+
62
+ export interface RetryPolicy {
63
+ max?: number;
64
+ backoff?: "exponential" | "linear" | "fixed";
65
+ initial?: Duration;
66
+ maxDelay?: Duration;
67
+ }
68
+
69
+ export interface RateLimitSpec {
70
+ key: string | ((input: unknown, ctx: { run: { id: string }; project: { id: string } }) => string);
71
+ limit: number | ((input: unknown) => number);
72
+ units?: number | ((input: unknown) => number);
73
+ window: Duration;
74
+ onLimit?: "queue" | "fail";
75
+ }
76
+
77
+ export type RunTrigger =
78
+ | { kind: "api"; actor?: string; accessTokenId?: string }
79
+ | { kind: "schedule"; scheduleId: string }
80
+ | { kind: "event"; eventId: string; eventType: string; filterId: string }
81
+ | { kind: "parent"; parentRunId: string; parentStepId: string };
@@ -0,0 +1,306 @@
1
+ // Workflow declaration and the `ctx` object.
2
+ // Authoritative contract in docs/sdk-surface.md §2–§3.
3
+
4
+ import type {
5
+ Duration,
6
+ EnvironmentName,
7
+ MachineType,
8
+ RetryPolicy,
9
+ RateLimitSpec,
10
+ RunStatus,
11
+ RunTrigger,
12
+ } from "./types.js";
13
+ import type { Condition } from "./conditions.js";
14
+
15
+ // ---- Workflow ----
16
+
17
+ export interface WorkflowHandle<TInput = unknown, TOutput = unknown> {
18
+ readonly id: string;
19
+ /** Phantom; used only for TypeScript inference of `workflows.trigger(...)`. */
20
+ readonly __input?: TInput;
21
+ readonly __output?: TOutput;
22
+ }
23
+
24
+ export interface WorkflowConfig<TInput, TOutput> {
25
+ id: string;
26
+ input?: unknown;
27
+ output?: unknown;
28
+ description?: string;
29
+ schedule?: ScheduleDeclaration | ScheduleDeclaration[];
30
+ concurrency?: ConcurrencyPolicy<TInput>;
31
+ retry?: RetryPolicy;
32
+ timeout?: Duration;
33
+ defaultRuntime?: "edge" | "node";
34
+ tags?: string[];
35
+ run: (input: TInput, ctx: WorkflowContext<TInput>) => Promise<TOutput>;
36
+ }
37
+
38
+ /**
39
+ * Internal registered form of a workflow. The executor takes this
40
+ * plus a request and drives the body.
41
+ */
42
+ export interface WorkflowDefinition<TInput = unknown, TOutput = unknown> extends WorkflowHandle<TInput, TOutput> {
43
+ readonly config: WorkflowConfig<TInput, TOutput>;
44
+ }
45
+
46
+ export type ScheduleDeclaration = (
47
+ | { cron: string }
48
+ | { every: Duration }
49
+ | { at: string | Date }
50
+ ) & {
51
+ timezone?: string;
52
+ input?: unknown | (() => unknown | Promise<unknown>);
53
+ enabled?: boolean;
54
+ overlap?: "skip" | "queue" | "allow";
55
+ environments?: EnvironmentName[];
56
+ name?: string;
57
+ };
58
+
59
+ export interface ConcurrencyPolicy<TInput> {
60
+ key?: string | ((input: TInput) => string);
61
+ limit?: number;
62
+ strategy?: "queue" | "cancel-in-progress" | "cancel-newest" | "round-robin";
63
+ }
64
+
65
+ /**
66
+ * Process-local registry. Backed by globalThis so bundles that inline
67
+ * their own copy of @voyantjs/workflows still share the registry with
68
+ * the loader's copy (voyant build relies on this to extract the
69
+ * manifest from a user bundle at load-time). Module-local `const`
70
+ * would create a private map per bundle copy.
71
+ */
72
+ const REGISTRY_KEY = "__voyantWorkflowRegistry" as const;
73
+ const globalRef = globalThis as unknown as Record<
74
+ typeof REGISTRY_KEY,
75
+ Map<string, WorkflowDefinition> | undefined
76
+ >;
77
+ const REGISTRY: Map<string, WorkflowDefinition> =
78
+ globalRef[REGISTRY_KEY] ??= new Map<string, WorkflowDefinition>();
79
+
80
+ /** Declare a workflow. See docs/sdk-surface.md §2.1. */
81
+ export function workflow<TInput = unknown, TOutput = unknown>(
82
+ config: WorkflowConfig<TInput, TOutput>,
83
+ ): WorkflowDefinition<TInput, TOutput> {
84
+ if (REGISTRY.has(config.id)) {
85
+ throw new Error(`workflow id "${config.id}" is already registered`);
86
+ }
87
+ const def: WorkflowDefinition<TInput, TOutput> = {
88
+ id: config.id,
89
+ config,
90
+ };
91
+ REGISTRY.set(config.id, def as WorkflowDefinition);
92
+ return def;
93
+ }
94
+
95
+ /** Internal: look up a registered workflow by id. */
96
+ export function getWorkflow(id: string): WorkflowDefinition | undefined {
97
+ return REGISTRY.get(id);
98
+ }
99
+
100
+ /**
101
+ * Internal: enumerate every registered workflow. Used by the CLI to list
102
+ * workflows discovered from a loaded entry file. Not part of the stable
103
+ * public API — implementation detail of the SDK/CLI pair.
104
+ */
105
+ export function __listRegisteredWorkflows(): WorkflowDefinition[] {
106
+ return [...REGISTRY.values()];
107
+ }
108
+
109
+ /**
110
+ * Internal: clear the workflow registry. Called by the CLI between
111
+ * builds / hot-reloads to drop stale workflows before re-importing
112
+ * the tenant bundle, and by test suites in beforeEach to isolate
113
+ * runs. Not part of the stable public API.
114
+ */
115
+ export function __resetRegistry(): void {
116
+ REGISTRY.clear();
117
+ }
118
+
119
+ // ---- Context ----
120
+
121
+ export interface RunContext {
122
+ id: string;
123
+ number: number;
124
+ attempt: number;
125
+ triggeredBy: RunTrigger;
126
+ tags: readonly string[];
127
+ startedAt: number;
128
+ }
129
+
130
+ export interface EnvironmentContext {
131
+ name: EnvironmentName;
132
+ git?: {
133
+ commit: string;
134
+ branch: string;
135
+ pr?: { number: number; url: string };
136
+ };
137
+ }
138
+
139
+ export interface WorkflowContext<_TInput = unknown> {
140
+ readonly run: RunContext;
141
+ readonly workflow: { id: string; version: string };
142
+ readonly environment: EnvironmentContext;
143
+ readonly project: { id: string; slug: string };
144
+ readonly organization: { id: string; slug: string };
145
+ readonly invocationCount: number;
146
+ readonly signal: AbortSignal;
147
+
148
+ step: StepApi;
149
+ sleep: (duration: Duration) => Promise<void>;
150
+ waitForEvent: WaitForEventApi;
151
+ waitForSignal: WaitForSignalApi;
152
+ waitForToken: WaitForTokenApi;
153
+ invoke: InvokeApi;
154
+ parallel: ParallelApi;
155
+ stream: StreamApi;
156
+ group: GroupApi;
157
+ compensate: () => Promise<never>;
158
+ metadata: MetadataApi;
159
+
160
+ now: () => number;
161
+ random: () => number;
162
+ randomUUID: () => string;
163
+
164
+ setRetry: (policy: RetryPolicy) => void;
165
+ }
166
+
167
+ // ---- Step ----
168
+
169
+ export interface StepApi {
170
+ <T>(id: string, fn: StepFn<T>): Promise<T>;
171
+ <T>(id: string, opts: StepOptions<T>, fn: StepFn<T>): Promise<T>;
172
+ }
173
+
174
+ export type StepFn<T> = (stepCtx: StepContext) => Promise<T>;
175
+
176
+ export interface StepContext {
177
+ signal: AbortSignal;
178
+ attempt: number;
179
+ log: (level: "info" | "warn" | "error", msg: string, data?: object) => void;
180
+ }
181
+
182
+ export interface StepOptions<T = unknown> {
183
+ runtime?: "edge" | "node";
184
+ machine?: MachineType;
185
+ timeout?: Duration;
186
+ retry?: RetryPolicy | { max: 0 };
187
+ idempotencyKey?: string;
188
+ compensate?: (output: T) => Promise<void>;
189
+ rateLimit?: RateLimitSpec;
190
+ waitFor?: Condition;
191
+ cancelIf?: Condition;
192
+ skipIf?: Condition;
193
+ }
194
+
195
+ // ---- Waits ----
196
+
197
+ export interface Waitable<T> extends PromiseLike<T | null> {
198
+ [Symbol.asyncIterator](): AsyncIterableIterator<T>;
199
+ close(): void;
200
+ }
201
+
202
+ export interface WaitForEventApi {
203
+ <T = unknown>(eventType: string, opts?: WaitForEventOptions<T>): Waitable<T>;
204
+ }
205
+
206
+ export interface WaitForEventOptions<T> {
207
+ match?: Partial<T> | ((payload: T) => boolean);
208
+ timeout?: Duration;
209
+ lookback?: Duration;
210
+ bufferSize?: number;
211
+ onTimeout?: "null" | "throw";
212
+ }
213
+
214
+ export interface WaitForSignalApi {
215
+ <T = unknown>(name: string, opts?: WaitForSignalOptions<T>): Waitable<T>;
216
+ }
217
+ export interface WaitForSignalOptions<T> extends WaitForEventOptions<T> {}
218
+
219
+ export interface WaitForTokenApi {
220
+ <T = unknown>(opts?: WaitForTokenOptions<T>): Promise<TokenWait<T>>;
221
+ }
222
+ export interface WaitForTokenOptions<_T> {
223
+ tokenId?: string;
224
+ timeout?: Duration;
225
+ onTimeout?: "null" | "throw";
226
+ schema?: unknown;
227
+ }
228
+ export interface TokenWait<T> {
229
+ tokenId: string;
230
+ url: string;
231
+ wait: () => Promise<T | null>;
232
+ }
233
+
234
+ // ---- Invoke / parallel ----
235
+
236
+ export interface InvokeApi {
237
+ <TIn, TOut>(
238
+ workflow: WorkflowHandle<TIn, TOut>,
239
+ input: TIn,
240
+ opts?: InvokeOptions,
241
+ ): Promise<TOut>;
242
+ }
243
+
244
+ export interface InvokeOptions {
245
+ idempotencyKey?: string;
246
+ tags?: string[];
247
+ lockToVersion?: string;
248
+ detach?: boolean;
249
+ }
250
+
251
+ export interface ParallelApi {
252
+ <T, R>(
253
+ items: readonly T[],
254
+ fn: (item: T, index: number) => Promise<R>,
255
+ opts?: ParallelOptions,
256
+ ): Promise<R[]>;
257
+ }
258
+
259
+ export interface ParallelOptions {
260
+ concurrency?: number;
261
+ settle?: boolean;
262
+ }
263
+
264
+ // ---- Streams ----
265
+
266
+ export interface StreamApi {
267
+ text(streamId: string, source: AsyncIterable<string>): Promise<void>;
268
+ json<T>(streamId: string, source: AsyncIterable<T>): Promise<void>;
269
+ bytes(streamId: string, source: AsyncIterable<Uint8Array>): Promise<void>;
270
+ <T>(streamId: string, fn: () => AsyncGenerator<T>): Promise<void>;
271
+ }
272
+
273
+ // ---- Groups (scoped compensation) ----
274
+
275
+ export interface GroupApi {
276
+ <T>(name: string, fn: (scope: GroupScope) => Promise<T>): Promise<T>;
277
+ }
278
+ export interface GroupScope {
279
+ step: StepApi;
280
+ compensate: () => Promise<never>;
281
+ }
282
+
283
+ // ---- Metadata ----
284
+
285
+ export type MetadataValue =
286
+ | string
287
+ | number
288
+ | boolean
289
+ | null
290
+ | MetadataValue[]
291
+ | { [key: string]: MetadataValue };
292
+
293
+ export interface MetadataMutatorSubset {
294
+ set(key: string, value: MetadataValue): void;
295
+ increment(key: string, by?: number): void;
296
+ append<T>(key: string, value: T): void;
297
+ remove(key: string): void;
298
+ }
299
+
300
+ export interface MetadataApi extends MetadataMutatorSubset {
301
+ flush(): Promise<void>;
302
+ parent?: MetadataMutatorSubset;
303
+ root?: MetadataMutatorSubset;
304
+ }
305
+
306
+ export type { RunStatus };