@classytic/streamline 1.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.
@@ -0,0 +1,1589 @@
1
+ import { _ as WorkflowHandlers, a as SchedulingInfo, c as StepError, d as StepState, f as StepStatus, g as WorkflowEventPayload, h as WorkflowDefinition, i as RunStatus, l as StepHandler, m as WaitingFor, n as InferHandlersContext, o as Step, p as TypedHandlers, r as RecurrencePattern, s as StepContext, t as InferContext, u as StepIds, v as WorkflowRun } from "./types-DG85_LzF.mjs";
2
+ import { n as WorkflowEventName, r as globalEventBus, t as WorkflowEventBus } from "./events-C0sZINZq.mjs";
3
+ import { FilterQuery, Plugin, Repository } from "@classytic/mongokit";
4
+ import mongoose from "mongoose";
5
+
6
+ //#region src/plugins/tenant-filter.plugin.d.ts
7
+ /**
8
+ * Tenant filter plugin options
9
+ */
10
+ interface TenantFilterOptions {
11
+ /**
12
+ * Field path for tenant ID in documents
13
+ * Supports nested fields using dot notation
14
+ *
15
+ * @example 'context.tenantId' → filters { 'context.tenantId': 'tenant-123' }
16
+ * @example 'meta.orgId' → filters { 'meta.orgId': 'org-456' }
17
+ * @default 'context.tenantId'
18
+ */
19
+ tenantField?: string;
20
+ /**
21
+ * Static tenant ID for single-tenant deployments
22
+ * If set, all queries use this tenant ID (no need to pass tenantId param)
23
+ *
24
+ * @default undefined (multi-tenant mode - tenantId required per query)
25
+ */
26
+ staticTenantId?: string;
27
+ /**
28
+ * Strict mode - throws error if tenantId is missing and not bypassed
29
+ * If false, missing tenantId is silently ignored (not recommended for production)
30
+ *
31
+ * @default true
32
+ */
33
+ strict?: boolean;
34
+ /**
35
+ * Enable bypass capability - allows queries to bypass tenant filter
36
+ * If false, ALL queries must include tenant filter (maximum security)
37
+ *
38
+ * @default true (allows bypassTenant: true for admin operations)
39
+ */
40
+ allowBypass?: boolean;
41
+ }
42
+ /**
43
+ * Tenant filter plugin factory
44
+ *
45
+ * Creates a plugin that automatically injects tenant filters into all queries.
46
+ * Prevents cross-tenant data leaks by enforcing tenant isolation at repository level.
47
+ *
48
+ * @param options - Plugin configuration options
49
+ * @returns MongoKit plugin instance
50
+ *
51
+ * @example Multi-Tenant with Strict Mode
52
+ * ```typescript
53
+ * const plugin = tenantFilterPlugin({
54
+ * tenantField: 'context.tenantId',
55
+ * strict: true,
56
+ * allowBypass: false // No bypasses allowed
57
+ * });
58
+ * ```
59
+ *
60
+ * @example Single-Tenant with Static ID
61
+ * ```typescript
62
+ * const plugin = tenantFilterPlugin({
63
+ * tenantField: 'context.tenantId',
64
+ * staticTenantId: process.env.ORGANIZATION_ID
65
+ * });
66
+ * ```
67
+ */
68
+ declare function tenantFilterPlugin(options?: TenantFilterOptions): Plugin;
69
+ /**
70
+ * Helper to create single-tenant repository (syntactic sugar)
71
+ *
72
+ * @param tenantId - Static tenant ID for all operations
73
+ * @param tenantField - Field path for tenant ID (default: 'context.tenantId')
74
+ * @returns Plugin configuration for single-tenant mode
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * import { singleTenantPlugin } from './plugins/tenant-filter.plugin';
79
+ *
80
+ * const repo = new Repository(Model, [
81
+ * singleTenantPlugin('my-organization-id')
82
+ * ]);
83
+ * ```
84
+ */
85
+ declare function singleTenantPlugin(tenantId: string, tenantField?: string): Plugin;
86
+ //#endregion
87
+ //#region src/storage/run.repository.d.ts
88
+ type LeanWorkflowRun<TContext = unknown> = WorkflowRun<TContext>;
89
+ interface AtomicUpdateOptions {
90
+ tenantId?: string;
91
+ bypassTenant?: boolean;
92
+ }
93
+ interface PaginatedResult<T> {
94
+ docs: T[];
95
+ page?: number;
96
+ limit?: number;
97
+ total?: number;
98
+ hasMore?: boolean;
99
+ next?: string;
100
+ }
101
+ interface WorkflowRepositoryConfig {
102
+ multiTenant?: TenantFilterOptions;
103
+ }
104
+ /**
105
+ * Repository interface for workflow run storage operations.
106
+ * Provides CRUD, queries, and atomic updates for workflow runs.
107
+ */
108
+ interface WorkflowRunRepository {
109
+ /** Create a new workflow run */
110
+ create<TContext = unknown>(run: WorkflowRun<TContext>): Promise<WorkflowRun<TContext>>;
111
+ /** Get a workflow run by ID */
112
+ getById<TContext = unknown>(id: string): Promise<WorkflowRun<TContext> | null>;
113
+ /** Get all workflow runs with filtering and pagination */
114
+ getAll: Repository<WorkflowRun>['getAll'];
115
+ /** Update a workflow run */
116
+ update<TContext = unknown>(id: string, data: Partial<WorkflowRun<TContext>>, options?: AtomicUpdateOptions): Promise<WorkflowRun<TContext>>;
117
+ /** Delete a workflow run */
118
+ delete: Repository<WorkflowRun>['delete'];
119
+ /** Atomic update with filter (for concurrent workflow claiming) */
120
+ updateOne(filter: Record<string, unknown>, update: Record<string, unknown>, options?: AtomicUpdateOptions): Promise<{
121
+ modifiedCount: number;
122
+ }>;
123
+ /** Get all active (running/waiting) workflow runs */
124
+ getActiveRuns(): Promise<LeanWorkflowRun[]>;
125
+ /** Get workflow runs by workflow ID */
126
+ getRunsByWorkflow(workflowId: string, limit?: number): Promise<LeanWorkflowRun[]>;
127
+ /** Get all waiting workflow runs */
128
+ getWaitingRuns(): Promise<LeanWorkflowRun[]>;
129
+ /** Get all running workflow runs */
130
+ getRunningRuns(): Promise<LeanWorkflowRun[]>;
131
+ /** Get workflows ready to resume (resumeAt <= now) */
132
+ getReadyToResume(now: Date, limit?: number): Promise<LeanWorkflowRun[]>;
133
+ /** Get workflows ready for retry (retryAt <= now) */
134
+ getReadyForRetry(now: Date, limit?: number): Promise<LeanWorkflowRun[]>;
135
+ /** Get stale running workflows (no heartbeat in threshold) */
136
+ getStaleRunningWorkflows(staleThresholdMs: number, limit?: number): Promise<LeanWorkflowRun[]>;
137
+ /** Get scheduled workflows ready to execute */
138
+ getScheduledWorkflowsReadyToExecute(now: Date, options?: {
139
+ page?: number;
140
+ limit?: number;
141
+ cursor?: string | null;
142
+ tenantId?: string;
143
+ }): Promise<PaginatedResult<LeanWorkflowRun>>;
144
+ /** Access to underlying MongoKit repository */
145
+ readonly base: Repository<WorkflowRun>;
146
+ /** Access to repository hooks */
147
+ readonly _hooks: Map<string, unknown[]>;
148
+ }
149
+ /**
150
+ * Create a workflow repository instance.
151
+ *
152
+ * @example
153
+ * // Single-tenant:
154
+ * const repo = createWorkflowRepository();
155
+ *
156
+ * // Multi-tenant:
157
+ * const repo = createWorkflowRepository({
158
+ * multiTenant: { tenantField: 'context.tenantId', strict: true }
159
+ * });
160
+ */
161
+ declare function createWorkflowRepository(config?: WorkflowRepositoryConfig): WorkflowRunRepository;
162
+ declare const workflowRunRepository: WorkflowRunRepository;
163
+ //#endregion
164
+ //#region src/execution/smart-scheduler.d.ts
165
+ interface SchedulerStats {
166
+ totalPolls: number;
167
+ successfulPolls: number;
168
+ failedPolls: number;
169
+ lastPollAt?: Date;
170
+ avgPollDuration: number;
171
+ activeWorkflows: number;
172
+ resumedWorkflows: number;
173
+ missedResumes: number;
174
+ isPolling: boolean;
175
+ pollInterval: number;
176
+ uptime: number;
177
+ }
178
+ declare class SchedulerMetrics {
179
+ private stats;
180
+ private pollDurations;
181
+ private startTime?;
182
+ private readonly maxDurationsToTrack;
183
+ start(pollInterval: number): void;
184
+ stop(): void;
185
+ recordPoll(duration: number, success: boolean, workflowsFound: number): void;
186
+ recordResume(success: boolean): void;
187
+ getStats(): SchedulerStats;
188
+ isHealthy(): boolean;
189
+ }
190
+ interface SmartSchedulerConfig {
191
+ /** Base poll interval (when active) */
192
+ basePollInterval: number;
193
+ /** Max poll interval (when load is low) */
194
+ maxPollInterval: number;
195
+ /** Min poll interval (when load is high) */
196
+ minPollInterval: number;
197
+ /** Max workflows to process per poll cycle (for efficiency at scale) */
198
+ maxWorkflowsPerPoll: number;
199
+ /** How long to wait before stopping (no workflows) */
200
+ idleTimeout: number;
201
+ /** Circuit breaker: max consecutive failures */
202
+ maxConsecutiveFailures: number;
203
+ /** Enable adaptive polling */
204
+ adaptivePolling: boolean;
205
+ /** Stale workflow check interval (runs even when scheduler is idle) */
206
+ staleCheckInterval: number;
207
+ /** Threshold for stale workflow detection */
208
+ staleThreshold: number;
209
+ }
210
+ declare class SmartScheduler {
211
+ private repository;
212
+ private resumeCallback;
213
+ private config;
214
+ private eventBus?;
215
+ private timers;
216
+ private pollInterval?;
217
+ private idleTimer?;
218
+ private staleCheckTimer?;
219
+ private isPolling;
220
+ private isStaleCheckActive;
221
+ private currentInterval;
222
+ private consecutiveFailures;
223
+ private lastWorkflowCount;
224
+ private metrics;
225
+ private staleRecoveryCallback?;
226
+ private retryCallback?;
227
+ constructor(repository: WorkflowRunRepository, resumeCallback: (runId: string) => Promise<void>, config?: SmartSchedulerConfig, eventBus?: WorkflowEventBus | undefined);
228
+ private emitError;
229
+ /**
230
+ * Set callback for recovering stale 'running' workflows
231
+ * Separate from resume because stale recovery requires different atomic claim logic
232
+ */
233
+ setStaleRecoveryCallback(callback: (runId: string, thresholdMs: number) => Promise<unknown>): void;
234
+ /**
235
+ * Set callback for retrying failed steps
236
+ * Separate from resume because retries need execute() (re-run step), not resumeStep() (mark done with payload)
237
+ */
238
+ setRetryCallback(callback: (runId: string) => Promise<unknown>): void;
239
+ /**
240
+ * Intelligent start: Only starts if workflows exist
241
+ * Checks for both waiting workflows AND running workflows that might need stale recovery
242
+ */
243
+ startIfNeeded(): Promise<boolean>;
244
+ /**
245
+ * Force start polling immediately
246
+ */
247
+ start(): void;
248
+ /**
249
+ * Stop polling
250
+ */
251
+ stop(): void;
252
+ /**
253
+ * Schedule a workflow to resume at specific time
254
+ * Handles both short delays (setTimeout) and long delays (MongoDB polling)
255
+ */
256
+ scheduleResume(runId: string, resumeAt: Date): void;
257
+ /**
258
+ * Cancel scheduled resume
259
+ */
260
+ cancelSchedule(runId: string): void;
261
+ /**
262
+ * Get scheduler statistics
263
+ */
264
+ getStats(): ReturnType<SchedulerMetrics['getStats']>;
265
+ /**
266
+ * Health check
267
+ */
268
+ isHealthy(): boolean;
269
+ /**
270
+ * Get current polling interval
271
+ */
272
+ getCurrentInterval(): number;
273
+ private startPolling;
274
+ private stopPolling;
275
+ /**
276
+ * Start background stale workflow check
277
+ * Runs independently of main polling loop to ensure crashed workflows are always recovered
278
+ */
279
+ private startStaleCheck;
280
+ /**
281
+ * Stop background stale workflow check
282
+ * Guarantees no further checks will be scheduled, even if one is currently running
283
+ */
284
+ private stopStaleCheck;
285
+ /**
286
+ * Background check for stale workflows (runs even when scheduler is idle)
287
+ * If stale workflows found, start the main polling loop
288
+ */
289
+ private checkForStaleWorkflows;
290
+ private schedulePoll;
291
+ private poll;
292
+ private resumeWorkflow;
293
+ private hasWaitingWorkflows;
294
+ /**
295
+ * Check if there are any active workflows that need scheduler attention
296
+ * Checks for waiting workflows (resume/retry), scheduled workflows, OR stale running workflows
297
+ * Note: Healthy running workflows don't need the scheduler
298
+ */
299
+ private hasActiveWorkflows;
300
+ private adjustInterval;
301
+ private resetIdleTimer;
302
+ }
303
+ //#endregion
304
+ //#region src/storage/cache.d.ts
305
+ /** Cache health status for monitoring and alerting */
306
+ type CacheHealthStatus = 'healthy' | 'warning' | 'critical';
307
+ /**
308
+ * O(1) LRU cache using Map's insertion-order iteration
309
+ *
310
+ * Map maintains insertion order, so we use delete+set to move items to the end.
311
+ * - set/get/delete: O(1)
312
+ * - eviction: O(1) - just delete first key
313
+ * - Only caches active workflows (running/waiting)
314
+ */
315
+ declare class WorkflowCache {
316
+ private readonly maxSize;
317
+ private cache;
318
+ constructor(maxSize?: number);
319
+ set<TContext>(run: WorkflowRun<TContext>): void;
320
+ get<TContext = unknown>(runId: string): WorkflowRun<TContext> | null;
321
+ delete(runId: string): void;
322
+ clear(): void;
323
+ getActive<TContext = unknown>(): WorkflowRun<TContext>[];
324
+ size(): number;
325
+ getStats(): {
326
+ size: number;
327
+ maxSize: number;
328
+ utilizationPercent: number;
329
+ };
330
+ /**
331
+ * Check if cache is approaching capacity.
332
+ * Useful for monitoring and proactive scaling.
333
+ *
334
+ * @param threshold - Utilization ratio (0-1), default 0.8 (80%)
335
+ */
336
+ isNearCapacity(threshold?: number): boolean;
337
+ /**
338
+ * Get cache health status for monitoring dashboards.
339
+ * Returns status and human-readable message.
340
+ */
341
+ getHealth(): {
342
+ status: CacheHealthStatus;
343
+ message: string;
344
+ utilizationPercent: number;
345
+ };
346
+ private isActive;
347
+ }
348
+ //#endregion
349
+ //#region src/core/container.d.ts
350
+ /**
351
+ * Container holding all shared dependencies for a workflow engine instance.
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * // Use default container (creates new instances)
356
+ * const container = createContainer();
357
+ *
358
+ * // Use in workflow
359
+ * const workflow = createWorkflow('my-workflow', {
360
+ * steps: { ... },
361
+ * container
362
+ * });
363
+ * ```
364
+ */
365
+ interface StreamlineContainer {
366
+ /** MongoDB repository for workflow runs */
367
+ repository: WorkflowRunRepository;
368
+ /** Event bus for workflow lifecycle events */
369
+ eventBus: WorkflowEventBus;
370
+ /** In-memory cache for active workflows */
371
+ cache: WorkflowCache;
372
+ }
373
+ /**
374
+ * Options for creating a container
375
+ */
376
+ interface ContainerOptions {
377
+ /**
378
+ * Custom repository instance or configuration
379
+ * - If WorkflowRunRepository: uses the provided instance
380
+ * - If WorkflowRepositoryConfig: creates a new repository with the config
381
+ * - If undefined: uses the default singleton repository
382
+ */
383
+ repository?: WorkflowRunRepository | WorkflowRepositoryConfig;
384
+ /**
385
+ * Custom event bus instance
386
+ * - If WorkflowEventBus: uses the provided instance
387
+ * - If 'global': uses the globalEventBus (for telemetry integration)
388
+ * - If undefined: creates a new isolated event bus
389
+ */
390
+ eventBus?: WorkflowEventBus | 'global';
391
+ /**
392
+ * Custom cache instance
393
+ * If undefined: creates a new isolated cache
394
+ */
395
+ cache?: WorkflowCache;
396
+ }
397
+ /**
398
+ * Create a new container with configurable dependencies.
399
+ *
400
+ * @param options - Optional configuration for container dependencies
401
+ * @returns A container with the specified or default dependencies
402
+ *
403
+ * @example Default container (isolated instances)
404
+ * ```typescript
405
+ * const container = createContainer();
406
+ * ```
407
+ *
408
+ * @example Multi-tenant container
409
+ * ```typescript
410
+ * const container = createContainer({
411
+ * repository: { multiTenant: { tenantField: 'meta.tenantId', strict: true } }
412
+ * });
413
+ * ```
414
+ *
415
+ * @example Container with global event bus (for telemetry)
416
+ * ```typescript
417
+ * const container = createContainer({ eventBus: 'global' });
418
+ * ```
419
+ *
420
+ * @example Fully custom container
421
+ * ```typescript
422
+ * const container = createContainer({
423
+ * repository: myCustomRepo,
424
+ * eventBus: myCustomEventBus,
425
+ * cache: myCustomCache
426
+ * });
427
+ * ```
428
+ */
429
+ declare function createContainer(options?: ContainerOptions): StreamlineContainer;
430
+ /**
431
+ * Type guard to check if an object is a valid StreamlineContainer
432
+ */
433
+ declare function isStreamlineContainer(obj: unknown): obj is StreamlineContainer;
434
+ //#endregion
435
+ //#region src/execution/engine.d.ts
436
+ /**
437
+ * Registry mapping runId to the engine managing that run.
438
+ * Enables resumeHook() to find the correct engine for resuming.
439
+ */
440
+ declare class HookRegistry {
441
+ private engines;
442
+ private cleanupInterval;
443
+ register(runId: string, engine: WorkflowEngine<unknown>): void;
444
+ unregister(runId: string): void;
445
+ getEngine(runId: string): WorkflowEngine<unknown> | undefined;
446
+ private cleanup;
447
+ shutdown(): void;
448
+ }
449
+ /** Global hook registry instance */
450
+ declare const hookRegistry: HookRegistry;
451
+ interface WorkflowEngineOptions {
452
+ /** Auto-execute workflow after start (default: true) */
453
+ autoExecute?: boolean;
454
+ /** Custom scheduler configuration */
455
+ scheduler?: Partial<SmartSchedulerConfig>;
456
+ }
457
+ /**
458
+ * Core workflow execution engine.
459
+ *
460
+ * Manages workflow lifecycle: start, execute, pause, resume, cancel.
461
+ * Handles waiting states, retries, and crash recovery automatically.
462
+ *
463
+ * @typeParam TContext - Type of workflow context
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * const engine = new WorkflowEngine(definition, handlers, container);
468
+ * const run = await engine.start({ orderId: '123' });
469
+ * ```
470
+ */
471
+ declare class WorkflowEngine<TContext = Record<string, unknown>> {
472
+ readonly handlers: WorkflowHandlers<TContext>;
473
+ private executor;
474
+ private scheduler;
475
+ private registry;
476
+ private options;
477
+ private eventListeners;
478
+ /** Exposed for hook registry and external access */
479
+ readonly container: StreamlineContainer;
480
+ constructor(definition: WorkflowDefinition<TContext>, handlers: WorkflowHandlers<TContext>, container: StreamlineContainer, options?: WorkflowEngineOptions);
481
+ /** Get the workflow definition */
482
+ get definition(): WorkflowDefinition<TContext>;
483
+ /**
484
+ * Start a new workflow run.
485
+ *
486
+ * @param input - Input data for the workflow
487
+ * @param meta - Optional metadata (userId, tags, etc.)
488
+ * @returns The created workflow run
489
+ */
490
+ start(input: unknown, meta?: Record<string, unknown>): Promise<WorkflowRun<TContext>>;
491
+ /**
492
+ * Get a workflow run by ID.
493
+ * Returns from cache if available, otherwise fetches from database.
494
+ *
495
+ * @param runId - Workflow run ID
496
+ * @returns The workflow run or null if not found
497
+ */
498
+ get(runId: string): Promise<WorkflowRun<TContext> | null>;
499
+ /**
500
+ * Execute a workflow run to completion.
501
+ *
502
+ * Runs steps sequentially until:
503
+ * - All steps complete (status: 'done')
504
+ * - A step fails after retries (status: 'failed')
505
+ * - A step waits for external input (status: 'waiting')
506
+ * - The workflow is cancelled (status: 'cancelled')
507
+ *
508
+ * @param runId - Workflow run ID to execute
509
+ * @returns The updated workflow run
510
+ */
511
+ execute(runId: string): Promise<WorkflowRun<TContext>>;
512
+ private getOrThrow;
513
+ private shouldContinueExecution;
514
+ private executeNextStep;
515
+ private checkNoProgress;
516
+ /**
517
+ * Handle different waiting states (event, retry, timer, human input)
518
+ * @returns true if execution should continue, false to break
519
+ */
520
+ private handleWaitingState;
521
+ private findCurrentStep;
522
+ /**
523
+ * Handle data corruption scenario (missing step state)
524
+ */
525
+ private failCorruption;
526
+ /**
527
+ * Register event listener for event-based waits
528
+ * IMPORTANT: Paused workflows will NOT be resumed by events until explicitly resumed by user
529
+ */
530
+ private handleEventWait;
531
+ /**
532
+ * Handle retry backoff wait with inline execution for short delays
533
+ * @returns true if workflow should continue execution immediately
534
+ */
535
+ private handleRetryWait;
536
+ /**
537
+ * Handle timer-based wait (sleep) with inline execution for short delays
538
+ * @returns true if workflow should continue execution immediately
539
+ */
540
+ private handleTimerWait;
541
+ /**
542
+ * Resume a paused or waiting workflow.
543
+ *
544
+ * For waiting workflows:
545
+ * - If waiting for human input: payload is passed as the step output
546
+ * - If waiting for timer/retry: continues execution from current step
547
+ *
548
+ * @param runId - Workflow run ID to resume
549
+ * @param payload - Data to pass to the waiting step (becomes step output)
550
+ * @returns The updated workflow run
551
+ * @throws {InvalidStateError} If workflow is not in waiting/running state
552
+ */
553
+ resume(runId: string, payload?: unknown): Promise<WorkflowRun<TContext>>;
554
+ private resumeWaitingWorkflow;
555
+ /**
556
+ * Recover a stale 'running' workflow (crashed mid-execution)
557
+ * Uses atomic claim to prevent multiple servers from recovering the same workflow
558
+ */
559
+ recoverStale(runId: string, staleThresholdMs: number): Promise<WorkflowRun<TContext> | null>;
560
+ /**
561
+ * Execute a retry for a workflow that failed and is waiting for backoff timer
562
+ * Uses atomic claim to prevent multiple servers from retrying the same workflow
563
+ */
564
+ executeRetry(runId: string): Promise<WorkflowRun<TContext> | null>;
565
+ /**
566
+ * Rewind a workflow to a previous step.
567
+ * Resets all steps from target step onwards to pending state.
568
+ *
569
+ * @param runId - Workflow run ID
570
+ * @param stepId - Step ID to rewind to
571
+ * @returns The rewound workflow run
572
+ */
573
+ rewindTo(runId: string, stepId: string): Promise<WorkflowRun<TContext>>;
574
+ /**
575
+ * Cancel a running workflow.
576
+ * Cleans up all resources and marks workflow as cancelled.
577
+ *
578
+ * @param runId - Workflow run ID to cancel
579
+ * @returns The cancelled workflow run
580
+ */
581
+ cancel(runId: string): Promise<WorkflowRun<TContext>>;
582
+ /**
583
+ * Pause a workflow run.
584
+ *
585
+ * Sets the `paused` flag to prevent the scheduler from processing this workflow.
586
+ * Paused workflows can be resumed later with `resume()`.
587
+ *
588
+ * @param runId - Workflow run ID to pause
589
+ * @returns The paused workflow run
590
+ */
591
+ pause(runId: string): Promise<WorkflowRun<TContext>>;
592
+ /**
593
+ * Configure engine options (scheduler settings, etc.)
594
+ */
595
+ configure(options: {
596
+ scheduler?: Partial<SmartSchedulerConfig>;
597
+ }): void;
598
+ shutdown(): void;
599
+ /**
600
+ * Get scheduler statistics for monitoring
601
+ */
602
+ getSchedulerStats(): ReturnType<SmartScheduler['getStats']>;
603
+ /**
604
+ * Check if scheduler is healthy
605
+ */
606
+ isSchedulerHealthy(): boolean;
607
+ }
608
+ //#endregion
609
+ //#region src/workflow/define.d.ts
610
+ interface WorkflowConfig<TContext, TInput = unknown> {
611
+ steps: Record<string, StepHandler<unknown, TContext>>;
612
+ context?: (input: TInput) => TContext;
613
+ version?: string;
614
+ defaults?: {
615
+ retries?: number;
616
+ timeout?: number;
617
+ };
618
+ autoExecute?: boolean;
619
+ /** Optional custom container for dependency injection */
620
+ container?: StreamlineContainer;
621
+ }
622
+ /** Options for waitFor method */
623
+ interface WaitForOptions {
624
+ /** Poll interval in ms (default: 1000) */
625
+ pollInterval?: number;
626
+ /** Maximum time to wait in ms (default: no timeout) */
627
+ timeout?: number;
628
+ }
629
+ interface Workflow<TContext, TInput = unknown> {
630
+ start: (input: TInput, meta?: Record<string, unknown>) => Promise<WorkflowRun<TContext>>;
631
+ get: (runId: string) => Promise<WorkflowRun<TContext> | null>;
632
+ execute: (runId: string) => Promise<WorkflowRun<TContext>>;
633
+ resume: (runId: string, payload?: unknown) => Promise<WorkflowRun<TContext>>;
634
+ cancel: (runId: string) => Promise<WorkflowRun<TContext>>;
635
+ pause: (runId: string) => Promise<WorkflowRun<TContext>>;
636
+ rewindTo: (runId: string, stepId: string) => Promise<WorkflowRun<TContext>>;
637
+ /**
638
+ * Wait for a workflow to complete (reach a terminal state).
639
+ * Polls the workflow status until it's done, failed, or cancelled.
640
+ *
641
+ * @param runId - Workflow run ID to wait for
642
+ * @param options - Poll interval and timeout settings
643
+ * @returns The completed workflow run
644
+ * @throws {Error} If timeout is exceeded or workflow not found
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const run = await workflow.start({ data: 'test' });
649
+ * const completed = await workflow.waitFor(run._id);
650
+ * console.log(completed.status); // 'done' | 'failed' | 'cancelled'
651
+ * ```
652
+ */
653
+ waitFor: (runId: string, options?: WaitForOptions) => Promise<WorkflowRun<TContext>>;
654
+ shutdown: () => void;
655
+ definition: WorkflowDefinition<TContext>;
656
+ engine: WorkflowEngine<TContext>;
657
+ /** The container used by this workflow (for testing or custom integrations) */
658
+ container: StreamlineContainer;
659
+ }
660
+ /**
661
+ * Create a workflow with inline step handlers
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * const orderProcess = createWorkflow('order-process', {
666
+ * steps: {
667
+ * validate: async (ctx) => validateOrder(ctx.input),
668
+ * charge: async (ctx) => chargeCard(ctx.getOutput('validate')),
669
+ * fulfill: async (ctx) => shipOrder(ctx.getOutput('charge')),
670
+ * notify: async (ctx) => sendConfirmation(ctx.context.email),
671
+ * },
672
+ * context: (input) => ({ orderId: input.id, email: input.email }),
673
+ * });
674
+ *
675
+ * await orderProcess.start({ id: '123', email: 'user@example.com' });
676
+ * ```
677
+ *
678
+ * @example Custom container for testing
679
+ * ```typescript
680
+ * const container = createContainer();
681
+ * const workflow = createWorkflow('test-workflow', {
682
+ * steps: { ... },
683
+ * container
684
+ * });
685
+ * ```
686
+ */
687
+ declare function createWorkflow<TContext = Record<string, unknown>, TInput = unknown>(id: string, config: WorkflowConfig<TContext, TInput>): Workflow<TContext, TInput>;
688
+ //#endregion
689
+ //#region src/core/status.d.ts
690
+ declare const STEP_STATUS_VALUES: StepStatus[];
691
+ declare const RUN_STATUS_VALUES: RunStatus[];
692
+ declare function isStepStatus(value: unknown): value is StepStatus;
693
+ declare function isRunStatus(value: unknown): value is RunStatus;
694
+ declare function deriveRunStatus<TContext>(run: WorkflowRun<TContext>): RunStatus;
695
+ declare function isValidStepTransition(from: StepStatus, to: StepStatus): boolean;
696
+ declare function isValidRunTransition(from: RunStatus, to: RunStatus): boolean;
697
+ /**
698
+ * Check if a workflow status represents a terminal (final) state
699
+ */
700
+ declare function isTerminalState(status: RunStatus): boolean;
701
+ //#endregion
702
+ //#region src/features/hooks.d.ts
703
+ interface HookOptions {
704
+ /** Custom token (default: auto-generated with crypto-random suffix) */
705
+ token?: string;
706
+ }
707
+ interface HookResult {
708
+ /** Token to use for resuming (includes secure random component) */
709
+ token: string;
710
+ /** URL path for webhook (if using webhook manager) */
711
+ path: string;
712
+ }
713
+ /**
714
+ * Create a hook that pauses workflow until external input.
715
+ * The token includes a crypto-random suffix for security.
716
+ *
717
+ * IMPORTANT: Pass the returned token to ctx.wait() to enable token validation:
718
+ * ```typescript
719
+ * const hook = createHook(ctx, 'approval');
720
+ * return ctx.wait(hook.token, { hookToken: hook.token }); // Token stored for validation
721
+ * ```
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * // In step handler
726
+ * async function waitForApproval(ctx) {
727
+ * const hook = createHook(ctx, 'waiting-for-approval');
728
+ * console.log('Resume with token:', hook.token);
729
+ * return ctx.wait(hook.token, { hookToken: hook.token }); // Token validated on resume
730
+ * }
731
+ *
732
+ * // From API route
733
+ * await resumeHook('token-123', { approved: true });
734
+ * ```
735
+ */
736
+ declare function createHook(ctx: StepContext, reason: string, options?: HookOptions): HookResult;
737
+ /**
738
+ * Resume a paused workflow by hook token.
739
+ *
740
+ * Security: If the workflow was paused with a hookToken in waitingFor.data,
741
+ * this function validates the token before resuming.
742
+ *
743
+ * Multi-worker support: Falls back to DB lookup if engine not in local registry.
744
+ *
745
+ * @example
746
+ * ```typescript
747
+ * // API route handler
748
+ * app.post('/hooks/:token', async (req, res) => {
749
+ * const result = await resumeHook(req.params.token, req.body);
750
+ * res.json({ success: true, runId: result.runId, status: result.run.status });
751
+ * });
752
+ * ```
753
+ */
754
+ declare function resumeHook(token: string, payload: unknown): Promise<{
755
+ runId: string;
756
+ run: WorkflowRun;
757
+ }>;
758
+ /**
759
+ * Generate a deterministic token for idempotent hooks
760
+ *
761
+ * @example
762
+ * ```typescript
763
+ * // Slack bot - same channel always gets same token
764
+ * const token = hookToken('slack', channelId);
765
+ * const hook = createHook(ctx, 'slack-message', { token });
766
+ * ```
767
+ */
768
+ declare function hookToken(...parts: string[]): string;
769
+ //#endregion
770
+ //#region src/execution/context.d.ts
771
+ declare class WaitSignal extends Error {
772
+ type: 'human' | 'webhook' | 'timer' | 'event';
773
+ reason: string;
774
+ data?: unknown | undefined;
775
+ constructor(type: 'human' | 'webhook' | 'timer' | 'event', reason: string, data?: unknown | undefined);
776
+ }
777
+ //#endregion
778
+ //#region src/storage/run.model.d.ts
779
+ /**
780
+ * MULTI-TENANCY & SCHEDULED WORKFLOWS - COMPOSITE INDEXES
781
+ *
782
+ * For multi-tenant scheduled workflows, add composite indexes with tenantId FIRST.
783
+ * MongoDB can only use indexes if the query matches the prefix pattern.
784
+ *
785
+ * Example: Multi-tenant scheduled workflow polling
786
+ * ```typescript
787
+ * import { WorkflowRunModel } from '@classytic/streamline';
788
+ *
789
+ * // Add composite index: tenantId first, then scheduling fields
790
+ * WorkflowRunModel.collection.createIndex({
791
+ * 'context.tenantId': 1,
792
+ * status: 1,
793
+ * 'scheduling.executionTime': 1,
794
+ * paused: 1
795
+ * });
796
+ *
797
+ * // This query will use the index efficiently:
798
+ * const scheduledWorkflows = await WorkflowRunModel.find({
799
+ * 'context.tenantId': 'tenant123',
800
+ * status: 'draft',
801
+ * 'scheduling.executionTime': { $lte: new Date() },
802
+ * paused: { $ne: true }
803
+ * }).sort({ 'scheduling.executionTime': 1 }).limit(100);
804
+ * ```
805
+ *
806
+ * Example: List workflows by tenant and time
807
+ * ```typescript
808
+ * WorkflowRunModel.collection.createIndex({
809
+ * 'context.tenantId': 1,
810
+ * workflowId: 1,
811
+ * createdAt: -1
812
+ * });
813
+ * ```
814
+ *
815
+ * IMPORTANT: Put tenantId FIRST in all multi-tenant indexes for query efficiency.
816
+ */
817
+ /**
818
+ * MULTI-TENANCY & CUSTOM INDEXES
819
+ *
820
+ * The engine is unopinionated about multi-tenancy. Add indexes for YOUR needs:
821
+ *
822
+ * Option 1: Add tenantId to metadata and index it
823
+ * ```typescript
824
+ * import { WorkflowRunModel } from '@classytic/streamline';
825
+ *
826
+ * // Add custom index for tenant-scoped queries
827
+ * WorkflowRunModel.collection.createIndex({ 'meta.tenantId': 1, status: 1 });
828
+ * WorkflowRunModel.collection.createIndex({ 'meta.orgId': 1, createdAt: -1 });
829
+ * ```
830
+ *
831
+ * Option 2: Extend the schema (before first use)
832
+ * ```typescript
833
+ * import { WorkflowRunModel } from '@classytic/streamline';
834
+ *
835
+ * WorkflowRunModel.schema.add({
836
+ * tenantId: { type: String, index: true }
837
+ * });
838
+ * WorkflowRunModel.schema.index({ tenantId: 1, status: 1 });
839
+ * ```
840
+ *
841
+ * Then query: engine.get(runId) and filter by tenantId in your app layer
842
+ */
843
+ /**
844
+ * Export WorkflowRunModel with hot-reload safety
845
+ *
846
+ * The pattern checks if the model already exists before creating a new one.
847
+ * This prevents "OverwriteModelError" in development with hot module replacement.
848
+ */
849
+ declare let WorkflowRunModel: mongoose.Model<WorkflowRun>;
850
+ //#endregion
851
+ //#region src/storage/query-builder.d.ts
852
+ declare const RUN_STATUS: {
853
+ readonly DRAFT: "draft";
854
+ readonly RUNNING: "running";
855
+ readonly WAITING: "waiting";
856
+ readonly DONE: "done";
857
+ readonly FAILED: "failed";
858
+ readonly CANCELLED: "cancelled";
859
+ };
860
+ declare const STEP_STATUS: {
861
+ readonly PENDING: "pending";
862
+ readonly RUNNING: "running";
863
+ readonly WAITING: "waiting";
864
+ readonly DONE: "done";
865
+ readonly FAILED: "failed";
866
+ readonly SKIPPED: "skipped";
867
+ };
868
+ declare class WorkflowQueryBuilder {
869
+ private query;
870
+ static create(): WorkflowQueryBuilder;
871
+ withStatus(status: RunStatus | RunStatus[]): this;
872
+ notPaused(): this;
873
+ isPaused(): this;
874
+ withWorkflowId(workflowId: string): this;
875
+ withRunId(runId: string): this;
876
+ withUserId(userId: string): this;
877
+ withTags(tags: string | string[]): this;
878
+ withStepReady(stepStatus: StepStatus, field: string, beforeTime: Date): this;
879
+ withRetryReady(now?: Date): this;
880
+ withTimerReady(now?: Date): this;
881
+ withStaleHeartbeat(thresholdMs: number): this;
882
+ withScheduledBefore(time: Date): this;
883
+ withScheduledAfter(time: Date): this;
884
+ createdBefore(date: Date): this;
885
+ createdAfter(date: Date): this;
886
+ where(conditions: Record<string, unknown>): this;
887
+ build(): FilterQuery;
888
+ }
889
+ declare const CommonQueries: {
890
+ active: () => FilterQuery;
891
+ readyForRetry: (now?: Date) => FilterQuery;
892
+ readyToResume: (now?: Date) => FilterQuery;
893
+ staleRunning: (thresholdMs: number) => FilterQuery;
894
+ scheduledReady: (now?: Date) => FilterQuery;
895
+ byUser: (userId: string, status?: RunStatus | RunStatus[]) => FilterQuery;
896
+ failed: () => FilterQuery;
897
+ completed: () => FilterQuery;
898
+ };
899
+ //#endregion
900
+ //#region src/storage/definition.model.d.ts
901
+ interface WorkflowDefinitionDoc {
902
+ _id: string;
903
+ workflowId: string;
904
+ name: string;
905
+ description?: string;
906
+ version: string;
907
+ versionMajor: number;
908
+ versionMinor: number;
909
+ versionPatch: number;
910
+ steps: Array<{
911
+ id: string;
912
+ name: string;
913
+ retries?: number;
914
+ timeout?: number;
915
+ condition?: string;
916
+ }>;
917
+ defaults?: {
918
+ retries?: number;
919
+ timeout?: number;
920
+ };
921
+ createdBy?: string;
922
+ updatedBy?: string;
923
+ createdAt: Date;
924
+ updatedAt: Date;
925
+ isActive: boolean;
926
+ tags?: string[];
927
+ metadata?: Record<string, unknown>;
928
+ }
929
+ /**
930
+ * MULTI-TENANCY & CUSTOM INDEXES
931
+ *
932
+ * This model is intentionally unopinionated about multi-tenancy.
933
+ * Different apps have different needs (tenantId, orgId, workspaceId, etc.)
934
+ *
935
+ * To add custom indexes for your app:
936
+ *
937
+ * import { WorkflowDefinitionModel } from '@classytic/streamline';
938
+ *
939
+ * // Add your custom indexes
940
+ * WorkflowDefinitionModel.collection.createIndex({ tenantId: 1, isActive: 1 });
941
+ * WorkflowDefinitionModel.collection.createIndex({ orgId: 1, createdAt: -1 });
942
+ *
943
+ * OR extend the schema:
944
+ *
945
+ * import { WorkflowDefinitionModel } from '@classytic/streamline';
946
+ * WorkflowDefinitionModel.schema.add({ tenantId: String });
947
+ * WorkflowDefinitionModel.schema.index({ tenantId: 1, isActive: 1 });
948
+ */
949
+ /**
950
+ * Export WorkflowDefinitionModel with hot-reload safety
951
+ *
952
+ * The pattern checks if the model already exists before creating a new one.
953
+ * This prevents "OverwriteModelError" in development with hot module replacement.
954
+ */
955
+ declare let WorkflowDefinitionModel: mongoose.Model<WorkflowDefinitionDoc>;
956
+ /**
957
+ * Repository for WorkflowDefinition
958
+ * Optional: Use if you want to store workflows in MongoDB
959
+ */
960
+ declare const workflowDefinitionRepository: {
961
+ /**
962
+ * Create a new workflow definition (or version)
963
+ */
964
+ create(definition: Partial<WorkflowDefinitionDoc>): Promise<WorkflowDefinitionDoc>;
965
+ /**
966
+ * Get latest active version of a workflow
967
+ */
968
+ getLatestVersion(workflowId: string): Promise<WorkflowDefinitionDoc | null>;
969
+ /**
970
+ * Get specific version of a workflow
971
+ */
972
+ getByVersion(workflowId: string, version: string): Promise<WorkflowDefinitionDoc | null>;
973
+ /**
974
+ * Get all active workflow definitions (latest versions only)
975
+ */
976
+ getActiveDefinitions(): Promise<WorkflowDefinitionDoc[]>;
977
+ /**
978
+ * Get all versions of a specific workflow
979
+ */
980
+ getVersionHistory(workflowId: string): Promise<WorkflowDefinitionDoc[]>;
981
+ /**
982
+ * Update a specific workflow version (rare - usually create new version)
983
+ */
984
+ update(workflowId: string, version: string, updates: Partial<WorkflowDefinitionDoc>): Promise<WorkflowDefinitionDoc | null>;
985
+ /**
986
+ * Deactivate a specific version (or all versions if version not provided)
987
+ */
988
+ deactivate(workflowId: string, version?: string): Promise<void>;
989
+ /**
990
+ * Deactivate all old versions (keep only latest)
991
+ */
992
+ deactivateOldVersions(workflowId: string, keepVersion: string): Promise<void>;
993
+ };
994
+ //#endregion
995
+ //#region src/utils/visualization.d.ts
996
+ interface StepTimeline {
997
+ id: string;
998
+ status: StepState['status'];
999
+ duration: number | null;
1000
+ startedAt?: Date;
1001
+ endedAt?: Date;
1002
+ }
1003
+ interface WorkflowProgress {
1004
+ completed: number;
1005
+ total: number;
1006
+ percentage: number;
1007
+ }
1008
+ interface StepUIState extends StepState {
1009
+ isCurrentStep: boolean;
1010
+ isPastStep: boolean;
1011
+ isFutureStep: boolean;
1012
+ canRewindTo: boolean;
1013
+ }
1014
+ declare function getStepTimeline(run: WorkflowRun): StepTimeline[];
1015
+ declare function getWorkflowProgress(run: WorkflowRun): WorkflowProgress;
1016
+ declare function getStepUIStates(run: WorkflowRun): StepUIState[];
1017
+ declare function getWaitingInfo(run: WorkflowRun): {
1018
+ stepId: string;
1019
+ type: "human" | "webhook" | "timer" | "event";
1020
+ reason: string;
1021
+ resumeAt: Date | undefined;
1022
+ eventName: string | undefined;
1023
+ data: unknown;
1024
+ } | null;
1025
+ declare function canRewindTo(run: WorkflowRun, stepId: string): boolean;
1026
+ declare function getExecutionPath(run: WorkflowRun): string[];
1027
+ //#endregion
1028
+ //#region src/config/constants.d.ts
1029
+ /**
1030
+ * Computed values derived from base constants.
1031
+ * Useful for monitoring, alerting, and capacity planning.
1032
+ */
1033
+ declare const COMPUTED: {
1034
+ /** Cache utilization threshold for warnings (80% of max) */readonly CACHE_WARNING_THRESHOLD: number; /** Cache utilization threshold for critical alerts (95% of max) */
1035
+ readonly CACHE_CRITICAL_THRESHOLD: number; /** JavaScript setTimeout max safe delay (2^31-1 ms = ~24.8 days) */
1036
+ readonly MAX_TIMEOUT_SAFE_MS: 2147483647; /** Retry delay sequence preview (for documentation/debugging) */
1037
+ readonly RETRY_DELAY_SEQUENCE_MS: number[];
1038
+ };
1039
+ //#endregion
1040
+ //#region src/scheduling/scheduling.service.d.ts
1041
+ /**
1042
+ * Paginated result type for scheduled workflows
1043
+ */
1044
+ interface ScheduledWorkflowsResult<TContext = unknown> {
1045
+ docs: WorkflowRun<TContext>[];
1046
+ page?: number;
1047
+ limit?: number;
1048
+ total?: number;
1049
+ hasMore?: boolean;
1050
+ next?: string;
1051
+ }
1052
+ /**
1053
+ * Options for scheduling a workflow
1054
+ */
1055
+ interface ScheduleWorkflowOptions {
1056
+ /**
1057
+ * Local date/time to execute (in user's timezone, NOT UTC)
1058
+ *
1059
+ * Format: ISO string without timezone - "YYYY-MM-DDTHH:mm:ss"
1060
+ *
1061
+ * @example
1062
+ * ```typescript
1063
+ * scheduledFor: '2024-03-10T09:00:00' // 9:00 AM local time
1064
+ * ```
1065
+ *
1066
+ * This represents the LOCAL time in the target timezone.
1067
+ * The timezone parameter determines which timezone this represents.
1068
+ * Accepts both string and Date (Date will be converted to ISO string using local components).
1069
+ */
1070
+ scheduledFor: string | Date;
1071
+ /** IANA timezone name (e.g., "America/New_York", "Europe/London") */
1072
+ timezone: string;
1073
+ /** Input data for workflow execution */
1074
+ input: unknown;
1075
+ /** Optional recurrence pattern for repeating workflows */
1076
+ recurrence?: RecurrencePattern;
1077
+ /** Tenant ID for multi-tenant deployments */
1078
+ tenantId?: string;
1079
+ /** User ID who scheduled the workflow */
1080
+ userId?: string;
1081
+ /** Tags for categorization/filtering */
1082
+ tags?: string[];
1083
+ /** Additional metadata */
1084
+ meta?: Record<string, unknown>;
1085
+ }
1086
+ /**
1087
+ * Options for querying scheduled workflows
1088
+ */
1089
+ interface GetScheduledWorkflowsOptions {
1090
+ /** Page number for offset pagination */
1091
+ page?: number;
1092
+ /** Number of results per page */
1093
+ limit?: number;
1094
+ /** Cursor for keyset pagination (more efficient at scale) */
1095
+ cursor?: string | null;
1096
+ /** Filter by tenant ID */
1097
+ tenantId?: string;
1098
+ /** Filter by specific time range */
1099
+ executionTimeRange?: {
1100
+ from: Date;
1101
+ to: Date;
1102
+ };
1103
+ /** Filter by recurrence pattern */
1104
+ recurring?: boolean;
1105
+ }
1106
+ /**
1107
+ * Scheduling Service configuration
1108
+ */
1109
+ interface SchedulingServiceConfig {
1110
+ /** Multi-tenant configuration (optional) - shorthand for container.repository config */
1111
+ multiTenant?: TenantFilterOptions;
1112
+ /** Auto-execute workflows when ready (default: true) */
1113
+ autoExecute?: boolean;
1114
+ /**
1115
+ * Custom container or container options
1116
+ * If provided, multiTenant option is ignored (use container.repository instead)
1117
+ */
1118
+ container?: StreamlineContainer | ContainerOptions;
1119
+ }
1120
+ /**
1121
+ * SchedulingService - High-level API for timezone-aware workflow scheduling
1122
+ *
1123
+ * Combines WorkflowEngine, TimezoneHandler, and Repository for easy scheduling.
1124
+ * Handles all the complexity of timezone conversion, DST transitions, and scheduling.
1125
+ *
1126
+ * IMPORTANT: Uses a unified container for both scheduling and execution to ensure
1127
+ * consistent multi-tenant isolation and proper hook registration.
1128
+ *
1129
+ * @typeParam TContext - Workflow context type
1130
+ */
1131
+ declare class SchedulingService<TContext = Record<string, unknown>> {
1132
+ private readonly engine;
1133
+ private readonly workflow;
1134
+ private readonly timezoneHandler;
1135
+ /** Exposed for testing and advanced use cases */
1136
+ readonly container: StreamlineContainer;
1137
+ constructor(workflow: WorkflowDefinition<TContext>, handlers: WorkflowHandlers<TContext>, config?: SchedulingServiceConfig);
1138
+ /** Get the repository (uses container's repository) */
1139
+ private get repository();
1140
+ /**
1141
+ * Convert Date to ISO string format (YYYY-MM-DDTHH:mm:ss)
1142
+ * Uses local components to preserve the "naive" datetime interpretation
1143
+ */
1144
+ private convertDateToISOString;
1145
+ /**
1146
+ * Schedule a workflow for future execution at a specific timezone
1147
+ *
1148
+ * @param options - Scheduling options with timezone information
1149
+ * @returns Created workflow run with scheduling metadata
1150
+ * @throws {Error} If timezone is invalid or scheduling fails
1151
+ *
1152
+ * @example
1153
+ * ```typescript
1154
+ * const run = await service.schedule({
1155
+ * scheduledFor: '2024-06-15T14:30:00',
1156
+ * timezone: 'Europe/London',
1157
+ * input: { userId: '123', action: 'send-reminder' }
1158
+ * });
1159
+ *
1160
+ * // Check if DST transition affected scheduling
1161
+ * if (run.scheduling?.isDSTTransition) {
1162
+ * console.warn('DST Note:', run.scheduling.dstNote);
1163
+ * }
1164
+ * ```
1165
+ */
1166
+ schedule(options: ScheduleWorkflowOptions): Promise<WorkflowRun<TContext>>;
1167
+ /**
1168
+ * Reschedule an existing workflow to a new time
1169
+ *
1170
+ * @param runId - Workflow run ID to reschedule
1171
+ * @param newScheduledFor - New local date/time as ISO string (format: "YYYY-MM-DDTHH:mm:ss")
1172
+ * @param newTimezone - Optional new timezone (if changing timezone)
1173
+ * @returns Updated workflow run
1174
+ * @throws {Error} If workflow not found or already executed
1175
+ *
1176
+ * @example Reschedule to different time (same timezone)
1177
+ * ```typescript
1178
+ * await service.reschedule(runId, '2024-06-16T15:00:00');
1179
+ * ```
1180
+ *
1181
+ * @example Reschedule to different time AND timezone
1182
+ * ```typescript
1183
+ * await service.reschedule(
1184
+ * runId,
1185
+ * '2024-06-16T10:00:00',
1186
+ * 'America/New_York'
1187
+ * );
1188
+ * ```
1189
+ */
1190
+ reschedule(runId: string, newScheduledFor: string | Date, newTimezone?: string): Promise<WorkflowRun<TContext>>;
1191
+ /**
1192
+ * Cancel a scheduled workflow
1193
+ *
1194
+ * @param runId - Workflow run ID to cancel
1195
+ * @returns Cancelled workflow run
1196
+ * @throws {Error} If workflow not found or already executed
1197
+ *
1198
+ * @example
1199
+ * ```typescript
1200
+ * await service.cancelScheduled(runId);
1201
+ * ```
1202
+ */
1203
+ cancelScheduled(runId: string): Promise<WorkflowRun<TContext>>;
1204
+ /**
1205
+ * Get scheduled workflows (with pagination)
1206
+ *
1207
+ * Supports both offset (page-based) and keyset (cursor-based) pagination.
1208
+ * Use keyset pagination for large datasets (>10k workflows) for better performance.
1209
+ *
1210
+ * @param options - Query and pagination options
1211
+ * @returns Paginated results with scheduled workflows
1212
+ *
1213
+ * @example Offset Pagination (Simple)
1214
+ * ```typescript
1215
+ * const result = await service.getScheduled({
1216
+ * page: 1,
1217
+ * limit: 50,
1218
+ * tenantId: 'client-123'
1219
+ * });
1220
+ *
1221
+ * console.log(result.data); // Array of workflows
1222
+ * console.log(result.hasNextPage); // true if more pages exist
1223
+ * ```
1224
+ *
1225
+ * @example Keyset Pagination (Efficient for large datasets)
1226
+ * ```typescript
1227
+ * // First page
1228
+ * const result = await service.getScheduled({
1229
+ * cursor: null,
1230
+ * limit: 1000
1231
+ * });
1232
+ *
1233
+ * // Next page
1234
+ * const nextResult = await service.getScheduled({
1235
+ * cursor: result.nextCursor,
1236
+ * limit: 1000
1237
+ * });
1238
+ * ```
1239
+ *
1240
+ * @example Filter by execution time range
1241
+ * ```typescript
1242
+ * const result = await service.getScheduled({
1243
+ * executionTimeRange: {
1244
+ * from: new Date('2024-06-01'),
1245
+ * to: new Date('2024-06-30')
1246
+ * },
1247
+ * limit: 100
1248
+ * });
1249
+ * ```
1250
+ */
1251
+ getScheduled(options?: GetScheduledWorkflowsOptions): Promise<ScheduledWorkflowsResult<TContext>>;
1252
+ /**
1253
+ * Get workflow run by ID
1254
+ *
1255
+ * @param runId - Workflow run ID
1256
+ * @returns Workflow run or null if not found
1257
+ */
1258
+ get(runId: string): Promise<WorkflowRun<TContext> | null>;
1259
+ /**
1260
+ * Execute a scheduled workflow immediately (bypass schedule)
1261
+ *
1262
+ * @param runId - Workflow run ID to execute
1263
+ * @returns Executed workflow run
1264
+ * @throws {Error} If workflow not found or not in draft status
1265
+ *
1266
+ * @example
1267
+ * ```typescript
1268
+ * // Execute a scheduled workflow now instead of waiting for execution time
1269
+ * const run = await service.executeNow(runId);
1270
+ * ```
1271
+ */
1272
+ executeNow(runId: string): Promise<WorkflowRun<TContext>>;
1273
+ }
1274
+ //#endregion
1275
+ //#region src/scheduling/timezone-handler.d.ts
1276
+ /**
1277
+ * Result of timezone calculation including DST transition metadata
1278
+ */
1279
+ interface TimezoneCalculationResult {
1280
+ /** UTC execution time for scheduler to use */
1281
+ executionTime: Date;
1282
+ /** Local time in user's timezone (for display) */
1283
+ localTimeDisplay: string;
1284
+ /** Whether this time falls during a DST transition */
1285
+ isDSTTransition: boolean;
1286
+ /** Human-readable note about DST adjustment (if any) */
1287
+ dstNote?: string;
1288
+ }
1289
+ /**
1290
+ * TimezoneHandler - Industry-standard timezone conversion with DST edge case handling
1291
+ *
1292
+ * Handles two critical DST edge cases:
1293
+ * 1. Spring Forward (non-existent time): When clocks jump forward, e.g., 2:30 AM doesn't exist
1294
+ * 2. Fall Back (ambiguous time): When clocks fall back, e.g., 1:30 AM occurs twice
1295
+ *
1296
+ * Design Philosophy:
1297
+ * - Store both intent (timezone + local time) AND execution time (UTC)
1298
+ * - Gracefully handle invalid times by adjusting forward
1299
+ * - Warn users about ambiguous times during fall back
1300
+ * - Use IANA timezone database (not abbreviations like "EST")
1301
+ *
1302
+ * @example
1303
+ * ```typescript
1304
+ * const handler = new TimezoneHandler();
1305
+ *
1306
+ * // Schedule for 9:00 AM New York time
1307
+ * const result = handler.calculateExecutionTime(
1308
+ * '2024-03-10T09:00:00',
1309
+ * 'America/New_York'
1310
+ * );
1311
+ *
1312
+ * console.log(result.executionTime); // UTC time for scheduler
1313
+ * console.log(result.isDSTTransition); // false (9 AM is safe)
1314
+ * ```
1315
+ */
1316
+ declare class TimezoneHandler {
1317
+ /**
1318
+ * Calculate UTC execution time from user's local timezone intent
1319
+ *
1320
+ * @param scheduledFor - Local date/time as ISO string WITHOUT timezone (naive datetime)
1321
+ * Format: "YYYY-MM-DDTHH:mm:ss" (e.g., "2024-03-10T09:00:00")
1322
+ *
1323
+ * This represents the LOCAL time in the target timezone.
1324
+ * Do NOT include timezone offset (Z, +00:00, etc.)
1325
+ *
1326
+ * @param timezone - IANA timezone name (e.g., "America/New_York", "Europe/London")
1327
+ * @returns Calculation result with execution time and DST metadata
1328
+ *
1329
+ * @throws {Error} If timezone is invalid or scheduledFor format is invalid
1330
+ *
1331
+ * @example Basic Usage
1332
+ * ```typescript
1333
+ * // Schedule for 9:00 AM New York time
1334
+ * const result = handler.calculateExecutionTime(
1335
+ * '2024-03-10T09:00:00',
1336
+ * 'America/New_York'
1337
+ * );
1338
+ * console.log(result.executionTime); // UTC Date object for scheduler
1339
+ * console.log(result.localTimeDisplay); // "2024-03-10 09:00:00 EDT"
1340
+ * ```
1341
+ *
1342
+ * @example Spring Forward Edge Case (non-existent time)
1343
+ * ```typescript
1344
+ * const result = handler.calculateExecutionTime(
1345
+ * '2024-03-10T02:30:00', // 2:30 AM doesn't exist (DST springs forward)
1346
+ * 'America/New_York'
1347
+ * );
1348
+ * // Result: Adjusted to 3:30 AM, isDSTTransition=true, dstNote explains adjustment
1349
+ * ```
1350
+ *
1351
+ * @example Fall Back Edge Case (ambiguous time)
1352
+ * ```typescript
1353
+ * const result = handler.calculateExecutionTime(
1354
+ * '2024-11-03T01:30:00', // 1:30 AM occurs twice (DST falls back)
1355
+ * 'America/New_York'
1356
+ * );
1357
+ * // Result: Uses first occurrence (DST), isDSTTransition=true, dstNote warns of ambiguity
1358
+ * ```
1359
+ */
1360
+ calculateExecutionTime(scheduledFor: Date | string, timezone: string): TimezoneCalculationResult;
1361
+ /**
1362
+ * Validate if a timezone is recognized by IANA database
1363
+ *
1364
+ * @param timezone - Timezone string to validate
1365
+ * @returns true if valid, false otherwise
1366
+ *
1367
+ * @example
1368
+ * ```typescript
1369
+ * handler.isValidTimezone('America/New_York'); // true
1370
+ * handler.isValidTimezone('EST'); // false (use IANA names)
1371
+ * handler.isValidTimezone('Invalid/Zone'); // false
1372
+ * ```
1373
+ */
1374
+ isValidTimezone(timezone: string): boolean;
1375
+ /**
1376
+ * Get current offset for a timezone (useful for debugging)
1377
+ *
1378
+ * @param timezone - IANA timezone name
1379
+ * @returns Offset in minutes from UTC (e.g., -300 for EST)
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * handler.getCurrentOffset('America/New_York'); // -300 (EST) or -240 (EDT)
1384
+ * ```
1385
+ */
1386
+ getCurrentOffset(timezone: string): number;
1387
+ /**
1388
+ * Check if a timezone is currently in DST
1389
+ *
1390
+ * @param timezone - IANA timezone name
1391
+ * @returns true if currently observing DST, false otherwise
1392
+ *
1393
+ * @example
1394
+ * ```typescript
1395
+ * handler.isInDST('America/New_York'); // true in summer, false in winter
1396
+ * ```
1397
+ */
1398
+ isInDST(timezone: string): boolean;
1399
+ }
1400
+ /**
1401
+ * Singleton instance for convenience
1402
+ * Use this for most cases unless you need custom configuration
1403
+ */
1404
+ declare const timezoneHandler: TimezoneHandler;
1405
+ //#endregion
1406
+ //#region src/features/parallel.d.ts
1407
+ /**
1408
+ * Parallel Execution Utilities
1409
+ *
1410
+ * Provides utilities for executing multiple async operations in parallel
1411
+ * with support for different modes, concurrency limits, and timeouts.
1412
+ *
1413
+ * @example
1414
+ * ```typescript
1415
+ * // Execute all tasks in parallel
1416
+ * const results = await executeParallel([
1417
+ * () => fetchUser(1),
1418
+ * () => fetchUser(2),
1419
+ * () => fetchUser(3),
1420
+ * ]);
1421
+ *
1422
+ * // With concurrency limit
1423
+ * const results = await executeParallel(tasks, { concurrency: 2 });
1424
+ *
1425
+ * // With allSettled mode (never throws)
1426
+ * const results = await executeParallel(tasks, { mode: 'allSettled' });
1427
+ * ```
1428
+ */
1429
+ interface ExecuteParallelOptions {
1430
+ /**
1431
+ * Execution mode:
1432
+ * - 'all': Wait for all tasks to complete (throws if any fails)
1433
+ * - 'race': Complete when first task completes
1434
+ * - 'any': Complete when first task succeeds
1435
+ * - 'allSettled': Wait for all tasks, return success/failure for each
1436
+ *
1437
+ * @default 'all'
1438
+ */
1439
+ mode?: 'all' | 'race' | 'any' | 'allSettled';
1440
+ /**
1441
+ * Maximum number of tasks to run concurrently
1442
+ * @default Infinity
1443
+ */
1444
+ concurrency?: number;
1445
+ /**
1446
+ * Maximum time in milliseconds for each task
1447
+ * @default undefined (no timeout)
1448
+ */
1449
+ timeout?: number;
1450
+ }
1451
+ /**
1452
+ * Execute multiple async tasks in parallel with configurable mode,
1453
+ * concurrency limits, and timeouts.
1454
+ *
1455
+ * @param tasks - Array of async task functions to execute
1456
+ * @param options - Execution options (mode, concurrency, timeout)
1457
+ * @returns Array of results (exact type depends on mode)
1458
+ *
1459
+ * @example Basic parallel execution
1460
+ * ```typescript
1461
+ * const results = await executeParallel([
1462
+ * () => fetch('/api/users'),
1463
+ * () => fetch('/api/posts'),
1464
+ * () => fetch('/api/comments'),
1465
+ * ]);
1466
+ * ```
1467
+ *
1468
+ * @example With concurrency limit
1469
+ * ```typescript
1470
+ * // Only 2 requests at a time
1471
+ * const results = await executeParallel(urlTasks, { concurrency: 2 });
1472
+ * ```
1473
+ *
1474
+ * @example With allSettled mode (never throws)
1475
+ * ```typescript
1476
+ * const results = await executeParallel(tasks, { mode: 'allSettled' });
1477
+ * for (const result of results) {
1478
+ * if (result.success) {
1479
+ * console.log('Success:', result.value);
1480
+ * } else {
1481
+ * console.log('Failed:', result.reason);
1482
+ * }
1483
+ * }
1484
+ * ```
1485
+ */
1486
+ declare function executeParallel<T>(tasks: Array<() => Promise<T>>, options?: ExecuteParallelOptions): Promise<T[] | Array<{
1487
+ success: boolean;
1488
+ value?: T;
1489
+ reason?: unknown;
1490
+ }>>;
1491
+ //#endregion
1492
+ //#region src/features/conditional.d.ts
1493
+ interface ConditionalStep extends Step {
1494
+ condition?: (context: unknown, run: WorkflowRun<unknown>) => boolean | Promise<boolean>;
1495
+ skipIf?: (context: unknown) => boolean | Promise<boolean>;
1496
+ runIf?: (context: unknown) => boolean | Promise<boolean>;
1497
+ }
1498
+ declare function isConditionalStep(step: Step): step is ConditionalStep;
1499
+ declare function shouldSkipStep<TContext>(step: ConditionalStep, context: TContext, run: WorkflowRun<TContext>): Promise<boolean>;
1500
+ declare function createCondition<TContext>(predicate: (context: TContext) => boolean): (context: TContext) => boolean;
1501
+ declare const conditions: {
1502
+ hasValue: <TContext>(key: keyof TContext) => (context: TContext) => boolean;
1503
+ equals: <TContext>(key: keyof TContext, value: TContext[keyof TContext]) => (context: TContext) => boolean;
1504
+ notEquals: <TContext>(key: keyof TContext, value: TContext[keyof TContext]) => (context: TContext) => boolean;
1505
+ greaterThan: <TContext>(key: keyof TContext, value: number) => (context: TContext) => boolean;
1506
+ lessThan: <TContext>(key: keyof TContext, value: number) => (context: TContext) => boolean;
1507
+ in: <TContext>(key: keyof TContext, values: readonly TContext[keyof TContext][]) => (context: TContext) => boolean;
1508
+ and: <TContext>(...predicates: Array<(context: TContext) => boolean>) => (context: TContext) => boolean;
1509
+ or: <TContext>(...predicates: Array<(context: TContext) => boolean>) => (context: TContext) => boolean;
1510
+ not: <TContext>(predicate: (context: TContext) => boolean) => (context: TContext) => boolean;
1511
+ custom: <TContext>(predicate: (context: TContext) => boolean) => (context: TContext) => boolean;
1512
+ };
1513
+ //#endregion
1514
+ //#region src/utils/errors.d.ts
1515
+ /**
1516
+ * Custom error classes with rich context for better debugging
1517
+ */
1518
+ /**
1519
+ * Standardized error codes for programmatic error handling.
1520
+ * Use these codes to handle specific error conditions in your application.
1521
+ *
1522
+ * @example
1523
+ * ```typescript
1524
+ * try {
1525
+ * await workflow.resume(runId);
1526
+ * } catch (err) {
1527
+ * if (err.code === ErrorCode.WORKFLOW_NOT_FOUND) {
1528
+ * // Handle missing workflow
1529
+ * }
1530
+ * }
1531
+ * ```
1532
+ */
1533
+ declare const ErrorCode: {
1534
+ readonly WORKFLOW_NOT_FOUND: "WORKFLOW_NOT_FOUND";
1535
+ readonly WORKFLOW_ALREADY_COMPLETED: "WORKFLOW_ALREADY_COMPLETED";
1536
+ readonly WORKFLOW_CANCELLED: "WORKFLOW_CANCELLED";
1537
+ readonly STEP_NOT_FOUND: "STEP_NOT_FOUND";
1538
+ readonly STEP_TIMEOUT: "STEP_TIMEOUT";
1539
+ readonly STEP_FAILED: "STEP_FAILED";
1540
+ readonly INVALID_STATE: "INVALID_STATE";
1541
+ readonly INVALID_TRANSITION: "INVALID_TRANSITION";
1542
+ readonly DATA_CORRUPTION: "DATA_CORRUPTION";
1543
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
1544
+ readonly MAX_RETRIES_EXCEEDED: "MAX_RETRIES_EXCEEDED";
1545
+ readonly EXECUTION_ABORTED: "EXECUTION_ABORTED";
1546
+ };
1547
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
1548
+ declare class WorkflowError extends Error {
1549
+ readonly context: {
1550
+ runId?: string;
1551
+ workflowId?: string;
1552
+ stepId?: string;
1553
+ [key: string]: unknown;
1554
+ };
1555
+ readonly code: ErrorCode;
1556
+ constructor(message: string, code: ErrorCode, context: {
1557
+ runId?: string;
1558
+ workflowId?: string;
1559
+ stepId?: string;
1560
+ [key: string]: unknown;
1561
+ });
1562
+ toString(): string;
1563
+ }
1564
+ declare class StepNotFoundError extends WorkflowError {
1565
+ constructor(stepId: string, workflowId: string, availableSteps: string[]);
1566
+ }
1567
+ declare class WorkflowNotFoundError extends WorkflowError {
1568
+ constructor(runId: string);
1569
+ }
1570
+ declare class InvalidStateError extends WorkflowError {
1571
+ constructor(action: string, currentState: string, expectedStates: string[], context: {
1572
+ runId?: string;
1573
+ stepId?: string;
1574
+ });
1575
+ }
1576
+ declare class StepTimeoutError extends WorkflowError {
1577
+ constructor(stepId: string, timeoutMs: number, runId?: string);
1578
+ }
1579
+ declare class DataCorruptionError extends WorkflowError {
1580
+ constructor(reason: string, context: {
1581
+ runId: string;
1582
+ [key: string]: unknown;
1583
+ });
1584
+ }
1585
+ declare class MaxRetriesExceededError extends WorkflowError {
1586
+ constructor(stepId: string, attempts: number, runId?: string);
1587
+ }
1588
+ //#endregion
1589
+ export { COMPUTED, type CacheHealthStatus, CommonQueries, type ConditionalStep, type ContainerOptions, DataCorruptionError, ErrorCode, type ErrorCode as ErrorCodeType, type ExecuteParallelOptions, type GetScheduledWorkflowsOptions, InferContext, InferHandlersContext, InvalidStateError, MaxRetriesExceededError, RUN_STATUS, RUN_STATUS_VALUES, RecurrencePattern, RunStatus, STEP_STATUS, STEP_STATUS_VALUES, type ScheduleWorkflowOptions, type SchedulerStats, SchedulingInfo, SchedulingService, type SchedulingServiceConfig, type SmartSchedulerConfig, Step, type StepContext, StepError, StepHandler, StepIds, StepNotFoundError, StepState, StepStatus, type StepTimeline, StepTimeoutError, type StepUIState, type StreamlineContainer, type TenantFilterOptions, type TimezoneCalculationResult, TimezoneHandler, TypedHandlers, WaitSignal, WaitingFor, WorkflowCache, WorkflowDefinition, type WorkflowDefinitionDoc, WorkflowDefinitionModel, WorkflowEngine, type WorkflowEngineOptions, WorkflowError, WorkflowEventBus, type WorkflowEventName, WorkflowEventPayload, WorkflowHandlers, WorkflowNotFoundError, type WorkflowProgress, WorkflowQueryBuilder, type WorkflowRepositoryConfig, type WorkflowRun, WorkflowRunModel, type WorkflowRunRepository, canRewindTo, conditions, createCondition, createContainer, createHook, createWorkflow, createWorkflowRepository, deriveRunStatus, executeParallel, getExecutionPath, getStepTimeline, getStepUIStates, getWaitingInfo, getWorkflowProgress, globalEventBus, hookRegistry, hookToken, isConditionalStep, isRunStatus, isStepStatus, isStreamlineContainer, isTerminalState, isValidRunTransition, isValidStepTransition, resumeHook, shouldSkipStep, singleTenantPlugin, tenantFilterPlugin, timezoneHandler, workflowDefinitionRepository, workflowRunRepository };