@voyantjs/workflows 0.0.0 → 0.6.8

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.
Files changed (61) hide show
  1. package/dist/auth/index.d.ts +26 -0
  2. package/dist/auth/index.d.ts.map +1 -0
  3. package/dist/auth/index.js +137 -0
  4. package/dist/conditions.d.ts +29 -0
  5. package/dist/conditions.d.ts.map +1 -0
  6. package/dist/conditions.js +5 -0
  7. package/dist/handler/index.d.ts +104 -0
  8. package/dist/handler/index.d.ts.map +1 -0
  9. package/dist/handler/index.js +238 -0
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +10 -0
  13. package/dist/protocol/index.d.ts +187 -0
  14. package/dist/protocol/index.d.ts.map +1 -0
  15. package/dist/protocol/index.js +7 -0
  16. package/dist/rate-limit/index.d.ts +40 -0
  17. package/dist/rate-limit/index.d.ts.map +1 -0
  18. package/dist/rate-limit/index.js +139 -0
  19. package/dist/runtime/ctx.d.ts +102 -0
  20. package/dist/runtime/ctx.d.ts.map +1 -0
  21. package/dist/runtime/ctx.js +607 -0
  22. package/dist/runtime/determinism.d.ts +19 -0
  23. package/dist/runtime/determinism.d.ts.map +1 -0
  24. package/dist/runtime/determinism.js +61 -0
  25. package/dist/runtime/errors.d.ts +21 -0
  26. package/dist/runtime/errors.d.ts.map +1 -0
  27. package/dist/runtime/errors.js +45 -0
  28. package/dist/runtime/executor.d.ts +159 -0
  29. package/dist/runtime/executor.d.ts.map +1 -0
  30. package/dist/runtime/executor.js +225 -0
  31. package/dist/runtime/journal.d.ts +55 -0
  32. package/dist/runtime/journal.d.ts.map +1 -0
  33. package/dist/runtime/journal.js +28 -0
  34. package/dist/testing/index.d.ts +117 -0
  35. package/dist/testing/index.d.ts.map +1 -0
  36. package/dist/testing/index.js +595 -0
  37. package/dist/trigger.d.ts +122 -0
  38. package/dist/trigger.d.ts.map +1 -0
  39. package/dist/trigger.js +23 -0
  40. package/dist/types.d.ts +63 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +3 -0
  43. package/dist/workflow.d.ts +212 -0
  44. package/dist/workflow.d.ts.map +1 -0
  45. package/dist/workflow.js +46 -0
  46. package/package.json +30 -30
  47. package/src/auth/index.ts +46 -52
  48. package/src/conditions.ts +13 -13
  49. package/src/handler/index.ts +110 -106
  50. package/src/index.ts +7 -7
  51. package/src/protocol/index.ts +137 -71
  52. package/src/rate-limit/index.ts +77 -78
  53. package/src/runtime/ctx.ts +354 -342
  54. package/src/runtime/determinism.ts +27 -27
  55. package/src/runtime/errors.ts +17 -17
  56. package/src/runtime/executor.ts +179 -172
  57. package/src/runtime/journal.ts +25 -25
  58. package/src/testing/index.ts +268 -202
  59. package/src/trigger.ts +64 -71
  60. package/src/types.ts +16 -18
  61. package/src/workflow.ts +154 -152
@@ -5,18 +5,18 @@
5
5
  // (test harness, adapters, dashboards) can build and inspect wire
6
6
  // payloads without reaching into runtime internals.
7
7
 
8
- export type ProtocolVersion = 1;
9
- export const PROTOCOL_VERSION: ProtocolVersion = 1;
8
+ export type ProtocolVersion = 1
9
+ export const PROTOCOL_VERSION: ProtocolVersion = 1
10
10
 
11
11
  // Journal types: shape of the tenant-side view of a run's state.
12
12
  // Re-exported so orchestrators and tools can build/inspect journals
13
13
  // without reaching into the runtime subpath.
14
14
  export type {
15
+ CompensationJournalEntry,
15
16
  JournalSlice,
16
17
  StepJournalEntry,
17
18
  WaitpointResolutionEntry,
18
- CompensationJournalEntry,
19
- } from "../runtime/journal.js";
19
+ } from "../runtime/journal.js"
20
20
 
21
21
  export type ExecutionStatus =
22
22
  | "CREATED"
@@ -25,109 +25,175 @@ export type ExecutionStatus =
25
25
  | "EXECUTING_WITH_WAITPOINTS"
26
26
  | "SUSPENDED"
27
27
  | "PENDING_CANCEL"
28
- | "FINISHED";
28
+ | "FINISHED"
29
29
 
30
- export type WaitpointKind = "DATETIME" | "EVENT" | "SIGNAL" | "RUN" | "MANUAL";
30
+ export type WaitpointKind = "DATETIME" | "EVENT" | "SIGNAL" | "RUN" | "MANUAL"
31
31
 
32
32
  export interface SerializedError {
33
- category: "USER_ERROR" | "RUNTIME_ERROR";
34
- code: string;
35
- message: string;
36
- name?: string;
37
- stack?: string;
38
- cause?: SerializedError;
39
- data?: Record<string, unknown>;
33
+ category: "USER_ERROR" | "RUNTIME_ERROR"
34
+ code: string
35
+ message: string
36
+ name?: string
37
+ stack?: string
38
+ cause?: SerializedError
39
+ data?: Record<string, unknown>
40
40
  }
41
41
 
42
- export type PayloadLocation = "INLINE" | "EXTERNAL";
42
+ export type PayloadLocation = "INLINE" | "EXTERNAL"
43
43
 
44
44
  export interface WorkflowManifest {
45
- schemaVersion: 1;
46
- projectId: string;
47
- versionId: string;
48
- builtAt: number;
49
- builderVersion: string;
50
- capabilities: string[];
51
- workflows: WorkflowManifestEntry[];
52
- eventFilters: EventFilterManifestEntry[];
53
- bindings: Record<string, { type: "d1" | "r2" | "kv" | "queue"; name: string }>;
54
- environments: Record<string, { customDomain?: string }>;
45
+ schemaVersion: 1
46
+ projectId: string
47
+ versionId: string
48
+ builtAt: number
49
+ builderVersion: string
50
+ capabilities: string[]
51
+ workflows: WorkflowManifestEntry[]
52
+ eventFilters: EventFilterManifestEntry[]
53
+ bindings: Record<string, { type: "d1" | "r2" | "kv" | "queue"; name: string }>
54
+ environments: Record<string, { customDomain?: string }>
55
55
  }
56
56
 
57
57
  export interface WorkflowManifestEntry {
58
- id: string;
59
- version: string;
60
- inputSchema?: unknown;
61
- outputSchema?: unknown;
62
- steps: ManifestStep[];
63
- schedules: ManifestSchedule[];
64
- defaultRuntime: "edge" | "node";
65
- hasCompensation: boolean;
66
- sourceLocation: { file: string; line: number };
58
+ id: string
59
+ version: string
60
+ inputSchema?: unknown
61
+ outputSchema?: unknown
62
+ steps: ManifestStep[]
63
+ schedules: ManifestSchedule[]
64
+ defaultRuntime: "edge" | "node"
65
+ hasCompensation: boolean
66
+ sourceLocation: { file: string; line: number }
67
67
  }
68
68
 
69
69
  export interface ManifestStep {
70
- id: string;
71
- runtime: "edge" | "node";
72
- hasCompensation: boolean;
73
- sourceLocation: { file: string; line: number };
70
+ id: string
71
+ runtime: "edge" | "node"
72
+ hasCompensation: boolean
73
+ sourceLocation: { file: string; line: number }
74
74
  }
75
75
 
76
76
  export interface ManifestSchedule {
77
- cron?: string;
78
- every?: string;
79
- at?: string;
80
- timezone?: string;
81
- environments?: ("production" | "preview" | "development")[];
82
- name?: string;
77
+ cron?: string
78
+ every?: string
79
+ at?: string
80
+ timezone?: string
81
+ environments?: ("production" | "preview" | "development")[]
82
+ name?: string
83
83
  }
84
84
 
85
85
  export interface EventFilterManifestEntry {
86
- id: string;
87
- eventType: string;
88
- scope?: string;
89
- matchExpression?: string;
90
- payloadHash: string;
91
- targetWorkflowId: string;
86
+ id: string
87
+ eventType: string
88
+ scope?: string
89
+ matchExpression?: string
90
+ payloadHash: string
91
+ targetWorkflowId: string
92
92
  }
93
93
 
94
94
  // WebSocket stream events — full union in docs/runtime-protocol.md §6.2.
95
95
  export type StreamEvent =
96
- | { kind: "step.started"; eventId: string; at: number; stepId: string; runtime: "edge" | "node"; machine?: string }
97
- | { kind: "step.ok"; eventId: string; at: number; stepId: string; attempt: number; durationMs: number; output?: unknown }
98
- | { kind: "step.err"; eventId: string; at: number; stepId: string; attempt: number; error: SerializedError }
96
+ | {
97
+ kind: "step.started"
98
+ eventId: string
99
+ at: number
100
+ stepId: string
101
+ runtime: "edge" | "node"
102
+ machine?: string
103
+ }
104
+ | {
105
+ kind: "step.ok"
106
+ eventId: string
107
+ at: number
108
+ stepId: string
109
+ attempt: number
110
+ durationMs: number
111
+ output?: unknown
112
+ }
113
+ | {
114
+ kind: "step.err"
115
+ eventId: string
116
+ at: number
117
+ stepId: string
118
+ attempt: number
119
+ error: SerializedError
120
+ }
99
121
  | { kind: "step.skipped"; eventId: string; at: number; stepId: string; reason: string }
100
- | { kind: "step.compensated"; eventId: string; at: number; stepId: string; status: "ok" | "err"; error?: SerializedError }
101
- | { kind: "waitpoint.registered"; eventId: string; at: number; waitpointId: string; waitpointKind: WaitpointKind; meta: Record<string, unknown> }
102
- | { kind: "waitpoint.resolved"; eventId: string; at: number; waitpointId: string; payload?: unknown; source: "live" | "inbox" | "replay" }
122
+ | {
123
+ kind: "step.compensated"
124
+ eventId: string
125
+ at: number
126
+ stepId: string
127
+ status: "ok" | "err"
128
+ error?: SerializedError
129
+ }
130
+ | {
131
+ kind: "waitpoint.registered"
132
+ eventId: string
133
+ at: number
134
+ waitpointId: string
135
+ waitpointKind: WaitpointKind
136
+ meta: Record<string, unknown>
137
+ }
138
+ | {
139
+ kind: "waitpoint.resolved"
140
+ eventId: string
141
+ at: number
142
+ waitpointId: string
143
+ payload?: unknown
144
+ source: "live" | "inbox" | "replay"
145
+ }
103
146
  | { kind: "metadata.changed"; eventId: string; at: number; metadata: Record<string, unknown> }
104
- | { kind: "stream.chunk"; eventId: string; at: number; streamId: string; chunk: unknown; encoding: "json" | "text" | "base64"; final: boolean }
105
- | { kind: "log"; eventId: string; at: number; level: "info" | "warn" | "error"; message: string; stepId?: string; data?: object }
147
+ | {
148
+ kind: "stream.chunk"
149
+ eventId: string
150
+ at: number
151
+ streamId: string
152
+ chunk: unknown
153
+ encoding: "json" | "text" | "base64"
154
+ final: boolean
155
+ }
156
+ | {
157
+ kind: "log"
158
+ eventId: string
159
+ at: number
160
+ level: "info" | "warn" | "error"
161
+ message: string
162
+ stepId?: string
163
+ data?: object
164
+ }
106
165
  | { kind: "version.rebased"; eventId: string; at: number; fromVersion: string; toVersion: string }
107
166
  | { kind: "run.cancelled"; eventId: string; at: number; reason?: string }
108
- | { kind: "run.finished"; eventId: string; at: number; status: string; output?: unknown; error?: SerializedError };
167
+ | {
168
+ kind: "run.finished"
169
+ eventId: string
170
+ at: number
171
+ status: string
172
+ output?: unknown
173
+ error?: SerializedError
174
+ }
109
175
 
110
176
  // Shared envelope for journal events written by the orchestrator,
111
177
  // the tenant worker, or a node-runtime container. Concrete `kind`
112
178
  // discriminants are owned by the emitting layer.
113
179
  export interface JournalEventEnvelope<TKind extends string = string, TData = unknown> {
114
- eventId: string;
115
- runId: string;
116
- createdAt: number;
117
- kind: TKind;
118
- data: TData;
119
- snapshotId?: string;
120
- writtenBy: "orchestrator" | "tenant" | "node";
180
+ eventId: string
181
+ runId: string
182
+ createdAt: number
183
+ kind: TKind
184
+ data: TData
185
+ snapshotId?: string
186
+ writtenBy: "orchestrator" | "tenant" | "node"
121
187
  }
122
188
 
123
189
  export interface PublicAccessTokenClaims {
124
- sub: "pat";
125
- tenantId: string;
126
- environment: "production" | "preview" | "development";
127
- scope: ("read" | "trigger" | "cancel")[];
190
+ sub: "pat"
191
+ tenantId: string
192
+ environment: "production" | "preview" | "development"
193
+ scope: ("read" | "trigger" | "cancel")[]
128
194
  target:
129
195
  | { kind: "run"; runId: string }
130
196
  | { kind: "workflow"; workflowId: string }
131
- | { kind: "tag"; tag: string };
132
- exp: number;
197
+ | { kind: "tag"; tag: string }
198
+ exp: number
133
199
  }
@@ -14,50 +14,50 @@
14
14
  // should swap in a Durable-Object or Redis-backed implementation that
15
15
  // shares state across isolates.
16
16
 
17
- import type { Duration } from "../types.js";
17
+ import type { Duration } from "../types.js"
18
18
 
19
19
  export interface AcquireArgs {
20
20
  /** Bucket key — usually a tenant id, a url host, a user id, etc. */
21
- key: string;
21
+ key: string
22
22
  /** Maximum units the bucket can hold. */
23
- limit: number;
23
+ limit: number
24
24
  /** Units the current call consumes. */
25
- units: number;
25
+ units: number
26
26
  /** Refill window in ms. `limit` tokens per `windowMs`. */
27
- windowMs: number;
27
+ windowMs: number
28
28
  /** `queue` → wait until capacity; `fail` → throw immediately. */
29
- onLimit: "queue" | "fail";
29
+ onLimit: "queue" | "fail"
30
30
  /** Forwarded from the run; limiter observes aborts during queue waits. */
31
- signal?: AbortSignal;
31
+ signal?: AbortSignal
32
32
  }
33
33
 
34
34
  export interface RateLimiter {
35
- acquire(args: AcquireArgs): Promise<void>;
35
+ acquire(args: AcquireArgs): Promise<void>
36
36
  }
37
37
 
38
38
  /** Error thrown when `onLimit === "fail"` and the bucket is empty. */
39
39
  export class RateLimitExceededError extends Error {
40
- readonly code = "RATE_LIMITED";
41
- readonly retryAfterMs: number;
40
+ readonly code = "RATE_LIMITED"
41
+ readonly retryAfterMs: number
42
42
  constructor(key: string, retryAfterMs: number) {
43
- super(`rate limit exceeded for key "${key}" (retry after ${retryAfterMs}ms)`);
44
- this.name = "RateLimitExceededError";
45
- this.retryAfterMs = retryAfterMs;
43
+ super(`rate limit exceeded for key "${key}" (retry after ${retryAfterMs}ms)`)
44
+ this.name = "RateLimitExceededError"
45
+ this.retryAfterMs = retryAfterMs
46
46
  }
47
47
  }
48
48
 
49
49
  interface Bucket {
50
- tokens: number;
51
- capacity: number;
52
- refillPerMs: number;
53
- lastRefillAt: number;
50
+ tokens: number
51
+ capacity: number
52
+ refillPerMs: number
53
+ lastRefillAt: number
54
54
  }
55
55
 
56
56
  export interface InMemoryLimiterOptions {
57
57
  /** Injectable clock, ms since epoch. Defaults to Date.now. */
58
- now?: () => number;
58
+ now?: () => number
59
59
  /** Injectable delay; defaults to setTimeout. Tests override this. */
60
- delay?: (ms: number, signal?: AbortSignal) => Promise<void>;
60
+ delay?: (ms: number, signal?: AbortSignal) => Promise<void>
61
61
  }
62
62
 
63
63
  /**
@@ -66,117 +66,116 @@ export interface InMemoryLimiterOptions {
66
66
  * `limit` / `windowMs` of the first `acquire` call and are updated on
67
67
  * subsequent calls that change them.
68
68
  */
69
- export function createInMemoryRateLimiter(
70
- opts: InMemoryLimiterOptions = {},
71
- ): RateLimiter {
72
- const now = opts.now ?? (() => Date.now());
73
- const delay = opts.delay ?? defaultDelay;
74
- const buckets = new Map<string, Bucket>();
69
+ export function createInMemoryRateLimiter(opts: InMemoryLimiterOptions = {}): RateLimiter {
70
+ const now = opts.now ?? (() => Date.now())
71
+ const delay = opts.delay ?? defaultDelay
72
+ const buckets = new Map<string, Bucket>()
75
73
 
76
74
  return {
77
75
  async acquire(args) {
78
- if (args.units <= 0) return;
76
+ if (args.units <= 0) return
79
77
  if (args.limit <= 0) {
80
- throw new Error(
81
- `rate-limit: "limit" must be > 0 (got ${args.limit}) for key "${args.key}"`,
82
- );
78
+ throw new Error(`rate-limit: "limit" must be > 0 (got ${args.limit}) for key "${args.key}"`)
83
79
  }
84
80
  if (args.windowMs <= 0) {
85
81
  throw new Error(
86
82
  `rate-limit: "windowMs" must be > 0 (got ${args.windowMs}) for key "${args.key}"`,
87
- );
83
+ )
88
84
  }
89
85
  if (args.units > args.limit) {
90
86
  // The step will never be admissible — short-circuit regardless
91
87
  // of onLimit to avoid hanging indefinitely under queue mode.
92
88
  throw new Error(
93
89
  `rate-limit: units (${args.units}) > limit (${args.limit}) for key "${args.key}" — step can never be admitted`,
94
- );
90
+ )
95
91
  }
96
92
 
97
93
  while (true) {
98
- const bucket = refill(buckets, args, now());
94
+ const bucket = refill(buckets, args, now())
99
95
  if (bucket.tokens >= args.units) {
100
- bucket.tokens -= args.units;
101
- return;
96
+ bucket.tokens -= args.units
97
+ return
102
98
  }
103
- const missing = args.units - bucket.tokens;
104
- const waitMs = Math.ceil(missing / bucket.refillPerMs);
99
+ const missing = args.units - bucket.tokens
100
+ const waitMs = Math.ceil(missing / bucket.refillPerMs)
105
101
  if (args.onLimit === "fail") {
106
- throw new RateLimitExceededError(args.key, waitMs);
102
+ throw new RateLimitExceededError(args.key, waitMs)
107
103
  }
108
- await delay(waitMs, args.signal);
104
+ await delay(waitMs, args.signal)
109
105
  if (args.signal?.aborted) {
110
- throw args.signal.reason ?? new Error("rate-limit: aborted while queued");
106
+ throw args.signal.reason ?? new Error("rate-limit: aborted while queued")
111
107
  }
112
108
  }
113
109
  },
114
- };
110
+ }
115
111
  }
116
112
 
117
- function refill(
118
- buckets: Map<string, Bucket>,
119
- args: AcquireArgs,
120
- nowMs: number,
121
- ): Bucket {
122
- const refillPerMs = args.limit / args.windowMs;
123
- let b = buckets.get(args.key);
113
+ function refill(buckets: Map<string, Bucket>, args: AcquireArgs, nowMs: number): Bucket {
114
+ const refillPerMs = args.limit / args.windowMs
115
+ let b = buckets.get(args.key)
124
116
  if (!b) {
125
117
  b = {
126
118
  tokens: args.limit,
127
119
  capacity: args.limit,
128
120
  refillPerMs,
129
121
  lastRefillAt: nowMs,
130
- };
131
- buckets.set(args.key, b);
132
- return b;
122
+ }
123
+ buckets.set(args.key, b)
124
+ return b
133
125
  }
134
126
  // Re-parameterize if the caller changed limit / windowMs. Clamp
135
127
  // tokens to the new capacity so a shrink doesn't leave stale excess.
136
128
  if (b.capacity !== args.limit || b.refillPerMs !== refillPerMs) {
137
- b.capacity = args.limit;
138
- b.refillPerMs = refillPerMs;
139
- if (b.tokens > b.capacity) b.tokens = b.capacity;
129
+ b.capacity = args.limit
130
+ b.refillPerMs = refillPerMs
131
+ if (b.tokens > b.capacity) b.tokens = b.capacity
140
132
  }
141
- const elapsed = Math.max(0, nowMs - b.lastRefillAt);
133
+ const elapsed = Math.max(0, nowMs - b.lastRefillAt)
142
134
  if (elapsed > 0) {
143
- b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs);
144
- b.lastRefillAt = nowMs;
135
+ b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs)
136
+ b.lastRefillAt = nowMs
145
137
  }
146
- return b;
138
+ return b
147
139
  }
148
140
 
149
141
  function defaultDelay(ms: number, signal?: AbortSignal): Promise<void> {
150
142
  return new Promise((resolve, reject) => {
151
143
  if (signal?.aborted) {
152
- reject(signal.reason ?? new Error("aborted"));
153
- return;
144
+ reject(signal.reason ?? new Error("aborted"))
145
+ return
154
146
  }
155
147
  const timer = setTimeout(() => {
156
- signal?.removeEventListener("abort", onAbort);
157
- resolve();
158
- }, ms);
148
+ signal?.removeEventListener("abort", onAbort)
149
+ resolve()
150
+ }, ms)
159
151
  const onAbort = () => {
160
- clearTimeout(timer);
161
- reject(signal?.reason ?? new Error("aborted"));
162
- };
163
- signal?.addEventListener("abort", onAbort, { once: true });
164
- });
152
+ clearTimeout(timer)
153
+ reject(signal?.reason ?? new Error("aborted"))
154
+ }
155
+ signal?.addEventListener("abort", onAbort, { once: true })
156
+ })
165
157
  }
166
158
 
167
159
  /** Normalize a Duration to milliseconds. Same units as `toMs` in ctx.ts. */
168
160
  export function durationToMs(d: Duration): number {
169
- if (typeof d === "number") return d;
170
- const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(d);
171
- if (!m) throw new Error(`rate-limit: invalid duration "${d}"`);
172
- const n = Number(m[1]);
161
+ if (typeof d === "number") return d
162
+ const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(d)
163
+ if (!m) throw new Error(`rate-limit: invalid duration "${d}"`)
164
+ const n = Number(m[1])
173
165
  switch (m[2]) {
174
- case "ms": return n;
175
- case "s": return n * 1_000;
176
- case "m": return n * 60_000;
177
- case "h": return n * 3_600_000;
178
- case "d": return n * 86_400_000;
179
- case "w": return n * 604_800_000;
180
- default: throw new Error(`rate-limit: invalid duration "${d}"`);
166
+ case "ms":
167
+ return n
168
+ case "s":
169
+ return n * 1_000
170
+ case "m":
171
+ return n * 60_000
172
+ case "h":
173
+ return n * 3_600_000
174
+ case "d":
175
+ return n * 86_400_000
176
+ case "w":
177
+ return n * 604_800_000
178
+ default:
179
+ throw new Error(`rate-limit: invalid duration "${d}"`)
181
180
  }
182
181
  }