@tasker-systems/tasker 0.1.0-alpha.1
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/dist/events/index.d.ts +3 -0
- package/dist/events/index.js +1642 -0
- package/dist/events/index.js.map +1 -0
- package/dist/ffi/index.d.ts +308 -0
- package/dist/ffi/index.js +1945 -0
- package/dist/ffi/index.js.map +1 -0
- package/dist/index-B3BcknlZ.d.ts +1349 -0
- package/dist/index.d.ts +4013 -0
- package/dist/index.js +7870 -0
- package/dist/index.js.map +1 -0
- package/dist/koffi-3HFAASOB.node +0 -0
- package/dist/koffi-AHHUCM3C.node +0 -0
- package/dist/koffi-AVDVVSXH.node +0 -0
- package/dist/koffi-BMO5K7B3.node +0 -0
- package/dist/koffi-G4D35B2D.node +0 -0
- package/dist/koffi-GG4SDSYA.node +0 -0
- package/dist/koffi-GOENU54R.node +0 -0
- package/dist/koffi-IDX6JEDH.node +0 -0
- package/dist/koffi-KFZAXWPQ.node +0 -0
- package/dist/koffi-LOH6WKRQ.node +0 -0
- package/dist/koffi-LUY2JHJP.node +0 -0
- package/dist/koffi-OMHWL3D6.node +0 -0
- package/dist/koffi-QKY2KSXW.node +0 -0
- package/dist/koffi-ROB3FRHA.node +0 -0
- package/dist/koffi-SE4ZI36U.node +0 -0
- package/dist/koffi-X3YT67KE.node +0 -0
- package/dist/koffi-X7JMBSZH.node +0 -0
- package/dist/koffi-YNQDUF3Q.node +0 -0
- package/dist/runtime-interface-CE4viUt7.d.ts +694 -0
- package/package.json +77 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4013 @@
|
|
|
1
|
+
import { T as TaskerRuntime, F as FfiDomainEvent, H as HandlerDefinitionDto } from './runtime-interface-CE4viUt7.js';
|
|
2
|
+
export { B as BaseTaskerRuntime, C as ClientHealthResponse, c as ClientPaginationInfo, d as ClientResult, e as ClientStepAuditResponse, f as ClientStepReadiness, g as ClientStepResponse, h as ClientTaskListResponse, i as ClientTaskRequest, j as ClientTaskResponse, D as DependencyResult, a as FfiBootstrapConfig, b as FfiBootstrapResult, k as FfiDispatchMetrics, L as FfiLogFields, l as FfiStepEvent, q as FfiStopResult, W as FfiWorkerStatus, m as HandlerDefinition, O as OrchestrationMetadata, R as RetryConfiguration, S as StepDefinition, n as StepExecutionError, o as StepExecutionMetadata, p as StepExecutionResult, r as Task, s as WorkflowStep } from './runtime-interface-CE4viUt7.js';
|
|
3
|
+
import { S as StepHandlerResult, a as StepHandler, B as BatchWorkerConfig, b as StepContext, E as ExecutableHandler, c as StepHandlerClass, T as TaskerEventEmitter, d as EventPoller, e as StepExecutionSubscriber, f as EventSystem } from './index-B3BcknlZ.js';
|
|
4
|
+
export { y as ErrorCallback, I as ErrorType, n as EventName, o as EventNames, z as EventPollerConfig, F as EventSystemConfig, G as EventSystemStats, A as MetricsCallback, p as MetricsEventName, q as MetricsEventNames, M as MetricsPayload, P as PollerCyclePayload, r as PollerEventName, s as PollerEventNames, C as PollerState, g as StepCompletionSentPayload, L as StepContextParams, D as StepEventCallback, t as StepEventName, u as StepEventNames, h as StepExecutionCompletedPayload, i as StepExecutionFailedPayload, j as StepExecutionReceivedPayload, k as StepExecutionStartedPayload, H as StepExecutionSubscriberConfig, N as StepHandlerResultParams, l as TaskerEventMap, W as WorkerErrorPayload, v as WorkerEventName, w as WorkerEventNames, m as WorkerEventPayload, x as createEventPoller, J as isStandardErrorType, K as isTypicallyRetryable } from './index-B3BcknlZ.js';
|
|
5
|
+
import { FfiLayerConfig } from './ffi/index.js';
|
|
6
|
+
export { BunRuntime, DenoRuntime, FfiLayer, NodeRuntime, RuntimeInfo, RuntimeType, detectRuntime, getLibraryPath, getRuntimeInfo, isBun, isDeno, isNode } from './ffi/index.js';
|
|
7
|
+
import { Logger } from 'pino';
|
|
8
|
+
import 'eventemitter3';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bootstrap configuration and result types.
|
|
12
|
+
*
|
|
13
|
+
* These types extend the FFI types with TypeScript-friendly interfaces
|
|
14
|
+
* for worker lifecycle management.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for worker bootstrap.
|
|
19
|
+
*
|
|
20
|
+
* Matches Python's BootstrapConfig and Ruby's bootstrap options.
|
|
21
|
+
*/
|
|
22
|
+
interface BootstrapConfig {
|
|
23
|
+
/** Optional worker ID. Auto-generated if not provided. */
|
|
24
|
+
workerId?: string;
|
|
25
|
+
/** Task namespace this worker handles (default: "default"). */
|
|
26
|
+
namespace?: string;
|
|
27
|
+
/** Path to custom configuration file (TOML). */
|
|
28
|
+
configPath?: string;
|
|
29
|
+
/** Log level: trace, debug, info, warn, error (default: "info"). */
|
|
30
|
+
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
31
|
+
/** Database URL override. */
|
|
32
|
+
databaseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Result from worker bootstrap.
|
|
36
|
+
*
|
|
37
|
+
* Contains information about the bootstrapped worker instance.
|
|
38
|
+
*/
|
|
39
|
+
interface BootstrapResult {
|
|
40
|
+
/** Whether bootstrap was successful. */
|
|
41
|
+
success: boolean;
|
|
42
|
+
/** Current status (started, already_running, error). */
|
|
43
|
+
status: 'started' | 'already_running' | 'error';
|
|
44
|
+
/** Human-readable status message. */
|
|
45
|
+
message: string;
|
|
46
|
+
/** Unique identifier for this worker instance. */
|
|
47
|
+
workerId?: string;
|
|
48
|
+
/** Error message if bootstrap failed. */
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Current worker status.
|
|
53
|
+
*
|
|
54
|
+
* Contains detailed information about the worker's state and resources.
|
|
55
|
+
*/
|
|
56
|
+
interface WorkerStatus {
|
|
57
|
+
/** Whether the status query succeeded. */
|
|
58
|
+
success: boolean;
|
|
59
|
+
/** Whether the worker is currently running. */
|
|
60
|
+
running: boolean;
|
|
61
|
+
/** Current status string. */
|
|
62
|
+
status?: string;
|
|
63
|
+
/** Worker ID if running. */
|
|
64
|
+
workerId?: string;
|
|
65
|
+
/** Current environment (test, development, production). */
|
|
66
|
+
environment?: string;
|
|
67
|
+
/** Internal worker core status. */
|
|
68
|
+
workerCoreStatus?: string;
|
|
69
|
+
/** Whether the web API is enabled. */
|
|
70
|
+
webApiEnabled?: boolean;
|
|
71
|
+
/** List of task namespaces this worker handles. */
|
|
72
|
+
supportedNamespaces?: string[];
|
|
73
|
+
/** Total database connection pool size. */
|
|
74
|
+
databasePoolSize?: number;
|
|
75
|
+
/** Number of idle database connections. */
|
|
76
|
+
databasePoolIdle?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Result from stopping the worker.
|
|
80
|
+
*/
|
|
81
|
+
interface StopResult {
|
|
82
|
+
/** Whether the stop was successful. */
|
|
83
|
+
success: boolean;
|
|
84
|
+
/** Current status (stopped, not_running, error). */
|
|
85
|
+
status: 'stopped' | 'not_running' | 'error';
|
|
86
|
+
/** Human-readable status message. */
|
|
87
|
+
message: string;
|
|
88
|
+
/** Worker ID that was stopped. */
|
|
89
|
+
workerId?: string;
|
|
90
|
+
/** Error message if stop failed. */
|
|
91
|
+
error?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Bootstrap API for TypeScript workers.
|
|
96
|
+
*
|
|
97
|
+
* High-level TypeScript API for worker lifecycle management.
|
|
98
|
+
* Wraps FFI calls with type-safe interfaces and error handling.
|
|
99
|
+
*
|
|
100
|
+
* Matches Python's bootstrap.py and Ruby's bootstrap.rb (TAS-92 aligned).
|
|
101
|
+
*
|
|
102
|
+
* All functions require an explicit runtime parameter. Use FfiLayer to load
|
|
103
|
+
* the runtime before calling these functions.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Initialize the worker system.
|
|
108
|
+
*
|
|
109
|
+
* This function bootstraps the full tasker-worker system, including:
|
|
110
|
+
* - Creating a Tokio runtime for async operations
|
|
111
|
+
* - Connecting to the database
|
|
112
|
+
* - Setting up the FFI dispatch channel for step events
|
|
113
|
+
* - Subscribing to domain events
|
|
114
|
+
*
|
|
115
|
+
* @param config - Optional bootstrap configuration
|
|
116
|
+
* @param runtime - The loaded FFI runtime (required)
|
|
117
|
+
* @returns BootstrapResult with worker details and status
|
|
118
|
+
* @throws Error if bootstrap fails critically
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const ffiLayer = new FfiLayer();
|
|
123
|
+
* await ffiLayer.load();
|
|
124
|
+
* const result = await bootstrapWorker({ namespace: 'payments' }, ffiLayer.getRuntime());
|
|
125
|
+
* console.log(`Worker ${result.workerId} started`);
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function bootstrapWorker(config: BootstrapConfig | undefined, runtime: TaskerRuntime): Promise<BootstrapResult>;
|
|
129
|
+
/**
|
|
130
|
+
* Stop the worker system gracefully.
|
|
131
|
+
*
|
|
132
|
+
* This function stops the worker system and releases all resources.
|
|
133
|
+
* Safe to call even if the worker is not running.
|
|
134
|
+
*
|
|
135
|
+
* @param runtime - The loaded FFI runtime (optional - returns success if not loaded)
|
|
136
|
+
* @returns StopResult indicating the outcome
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const result = stopWorker(runtime);
|
|
141
|
+
* if (result.success) {
|
|
142
|
+
* console.log('Worker stopped successfully');
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function stopWorker(runtime?: TaskerRuntime): StopResult;
|
|
147
|
+
/**
|
|
148
|
+
* Get the current worker system status.
|
|
149
|
+
*
|
|
150
|
+
* Returns detailed information about the worker's current state,
|
|
151
|
+
* including resource usage and operational status.
|
|
152
|
+
*
|
|
153
|
+
* @param runtime - The loaded FFI runtime (optional - returns stopped if not loaded)
|
|
154
|
+
* @returns WorkerStatus with current state and metrics
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const status = getWorkerStatus(runtime);
|
|
159
|
+
* if (status.running) {
|
|
160
|
+
* console.log(`Pool size: ${status.databasePoolSize}`);
|
|
161
|
+
* } else {
|
|
162
|
+
* console.log(`Worker not running`);
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function getWorkerStatus(runtime?: TaskerRuntime): WorkerStatus;
|
|
167
|
+
/**
|
|
168
|
+
* Initiate graceful shutdown of the worker system.
|
|
169
|
+
*
|
|
170
|
+
* This function begins the graceful shutdown process, allowing
|
|
171
|
+
* in-flight operations to complete before fully stopping.
|
|
172
|
+
* Call stopWorker() after this to fully stop the worker.
|
|
173
|
+
*
|
|
174
|
+
* @param runtime - The loaded FFI runtime (optional - returns success if not loaded)
|
|
175
|
+
* @returns StopResult indicating the transition status
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* // Start graceful shutdown
|
|
180
|
+
* transitionToGracefulShutdown(runtime);
|
|
181
|
+
*
|
|
182
|
+
* // Wait for in-flight operations...
|
|
183
|
+
* await new Promise(resolve => setTimeout(resolve, 5000));
|
|
184
|
+
*
|
|
185
|
+
* // Fully stop
|
|
186
|
+
* stopWorker(runtime);
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function transitionToGracefulShutdown(runtime?: TaskerRuntime): StopResult;
|
|
190
|
+
/**
|
|
191
|
+
* Check if the worker system is currently running.
|
|
192
|
+
*
|
|
193
|
+
* Lightweight check that doesn't query the full status.
|
|
194
|
+
*
|
|
195
|
+
* @param runtime - The loaded FFI runtime (optional - returns false if not loaded)
|
|
196
|
+
* @returns True if the worker is running
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* if (!isWorkerRunning(runtime)) {
|
|
201
|
+
* await bootstrapWorker(config, runtime);
|
|
202
|
+
* }
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
declare function isWorkerRunning(runtime?: TaskerRuntime): boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Get version information for the worker system.
|
|
208
|
+
*
|
|
209
|
+
* @param runtime - The loaded FFI runtime (optional)
|
|
210
|
+
* @returns Version string from the Rust library
|
|
211
|
+
*/
|
|
212
|
+
declare function getVersion(runtime?: TaskerRuntime): string;
|
|
213
|
+
/**
|
|
214
|
+
* Get detailed Rust library version.
|
|
215
|
+
*
|
|
216
|
+
* @param runtime - The loaded FFI runtime (optional)
|
|
217
|
+
* @returns Detailed version information
|
|
218
|
+
*/
|
|
219
|
+
declare function getRustVersion(runtime?: TaskerRuntime): string;
|
|
220
|
+
/**
|
|
221
|
+
* Perform a health check on the FFI module.
|
|
222
|
+
*
|
|
223
|
+
* @param runtime - The loaded FFI runtime (optional - returns false if not loaded)
|
|
224
|
+
* @returns True if the FFI module is functional
|
|
225
|
+
*/
|
|
226
|
+
declare function healthCheck(runtime?: TaskerRuntime): boolean;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* API mixin for HTTP functionality.
|
|
230
|
+
*
|
|
231
|
+
* TAS-112: Composition Pattern - API Mixin
|
|
232
|
+
*
|
|
233
|
+
* This module provides the APIMixin class for step handlers that need HTTP
|
|
234
|
+
* functionality. Use via interface implementation with method binding.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* class FetchUserHandler extends StepHandler implements APICapable {
|
|
239
|
+
* static handlerName = 'fetch_user';
|
|
240
|
+
* static baseUrl = 'https://api.example.com';
|
|
241
|
+
*
|
|
242
|
+
* // Bind APIMixin methods
|
|
243
|
+
* get = APIMixin.prototype.get.bind(this);
|
|
244
|
+
* apiSuccess = APIMixin.prototype.apiSuccess.bind(this);
|
|
245
|
+
* apiFailure = APIMixin.prototype.apiFailure.bind(this);
|
|
246
|
+
* // ... other required methods
|
|
247
|
+
*
|
|
248
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
249
|
+
* const response = await this.get('/users');
|
|
250
|
+
* if (response.ok) {
|
|
251
|
+
* return this.apiSuccess(response);
|
|
252
|
+
* }
|
|
253
|
+
* return this.apiFailure(response);
|
|
254
|
+
* }
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @module handler/mixins/api
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Response wrapper for API calls.
|
|
263
|
+
*
|
|
264
|
+
* Provides convenient access to response data and error classification.
|
|
265
|
+
*/
|
|
266
|
+
declare class ApiResponse {
|
|
267
|
+
readonly statusCode: number;
|
|
268
|
+
readonly headers: Record<string, string>;
|
|
269
|
+
readonly body: unknown;
|
|
270
|
+
readonly rawResponse: Response;
|
|
271
|
+
constructor(response: Response, body?: unknown);
|
|
272
|
+
/**
|
|
273
|
+
* Check if the response indicates success (2xx status).
|
|
274
|
+
*/
|
|
275
|
+
get ok(): boolean;
|
|
276
|
+
/**
|
|
277
|
+
* Check if the response indicates a client error (4xx status).
|
|
278
|
+
*/
|
|
279
|
+
get isClientError(): boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Check if the response indicates a server error (5xx status).
|
|
282
|
+
*/
|
|
283
|
+
get isServerError(): boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Check if the error should be retried.
|
|
286
|
+
*/
|
|
287
|
+
get isRetryable(): boolean;
|
|
288
|
+
/**
|
|
289
|
+
* Get the Retry-After header value in seconds, if present.
|
|
290
|
+
*/
|
|
291
|
+
get retryAfter(): number | null;
|
|
292
|
+
/**
|
|
293
|
+
* Convert the response to a dictionary for result output.
|
|
294
|
+
*/
|
|
295
|
+
toDict(): Record<string, unknown>;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Interface for API-capable handlers.
|
|
299
|
+
*
|
|
300
|
+
* Implement this interface and bind APIMixin methods to get HTTP functionality.
|
|
301
|
+
*/
|
|
302
|
+
interface APICapable {
|
|
303
|
+
/** Base URL for API calls */
|
|
304
|
+
baseUrl: string;
|
|
305
|
+
/** Default request timeout in milliseconds */
|
|
306
|
+
timeout: number;
|
|
307
|
+
/** Default headers to include in all requests */
|
|
308
|
+
defaultHeaders: Record<string, string>;
|
|
309
|
+
get(path: string, params?: Record<string, unknown>, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
310
|
+
post(path: string, options?: {
|
|
311
|
+
body?: unknown;
|
|
312
|
+
json?: boolean;
|
|
313
|
+
headers?: Record<string, string>;
|
|
314
|
+
}): Promise<ApiResponse>;
|
|
315
|
+
put(path: string, options?: {
|
|
316
|
+
body?: unknown;
|
|
317
|
+
json?: boolean;
|
|
318
|
+
headers?: Record<string, string>;
|
|
319
|
+
}): Promise<ApiResponse>;
|
|
320
|
+
patch(path: string, options?: {
|
|
321
|
+
body?: unknown;
|
|
322
|
+
json?: boolean;
|
|
323
|
+
headers?: Record<string, string>;
|
|
324
|
+
}): Promise<ApiResponse>;
|
|
325
|
+
delete(path: string, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
326
|
+
request(method: string, path: string, options?: RequestInit): Promise<ApiResponse>;
|
|
327
|
+
apiSuccess(response: ApiResponse, result?: Record<string, unknown>, includeResponse?: boolean): StepHandlerResult;
|
|
328
|
+
apiFailure(response: ApiResponse, message?: string): StepHandlerResult;
|
|
329
|
+
connectionError(error: Error, context?: string): StepHandlerResult;
|
|
330
|
+
timeoutError(error: Error, context?: string): StepHandlerResult;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Implementation of API methods.
|
|
334
|
+
*
|
|
335
|
+
* TAS-112: Use via interface implementation with method binding.
|
|
336
|
+
*
|
|
337
|
+
* Provides HTTP client functionality with automatic error classification,
|
|
338
|
+
* retry handling, and convenient methods for common HTTP operations.
|
|
339
|
+
*/
|
|
340
|
+
declare class APIMixin implements APICapable {
|
|
341
|
+
static baseUrl: string;
|
|
342
|
+
static defaultTimeout: number;
|
|
343
|
+
static defaultHeaders: Record<string, string>;
|
|
344
|
+
get baseUrl(): string;
|
|
345
|
+
get timeout(): number;
|
|
346
|
+
get defaultHeaders(): Record<string, string>;
|
|
347
|
+
/**
|
|
348
|
+
* Make a GET request.
|
|
349
|
+
*/
|
|
350
|
+
get(path: string, params?: Record<string, unknown>, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
351
|
+
/**
|
|
352
|
+
* Make a POST request.
|
|
353
|
+
*/
|
|
354
|
+
post(path: string, options?: {
|
|
355
|
+
body?: unknown;
|
|
356
|
+
json?: boolean;
|
|
357
|
+
headers?: Record<string, string>;
|
|
358
|
+
}): Promise<ApiResponse>;
|
|
359
|
+
/**
|
|
360
|
+
* Make a PUT request.
|
|
361
|
+
*/
|
|
362
|
+
put(path: string, options?: {
|
|
363
|
+
body?: unknown;
|
|
364
|
+
json?: boolean;
|
|
365
|
+
headers?: Record<string, string>;
|
|
366
|
+
}): Promise<ApiResponse>;
|
|
367
|
+
/**
|
|
368
|
+
* Make a PATCH request.
|
|
369
|
+
*/
|
|
370
|
+
patch(path: string, options?: {
|
|
371
|
+
body?: unknown;
|
|
372
|
+
json?: boolean;
|
|
373
|
+
headers?: Record<string, string>;
|
|
374
|
+
}): Promise<ApiResponse>;
|
|
375
|
+
/**
|
|
376
|
+
* Make a DELETE request.
|
|
377
|
+
*/
|
|
378
|
+
delete(path: string, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
379
|
+
/**
|
|
380
|
+
* Make an arbitrary HTTP request.
|
|
381
|
+
*/
|
|
382
|
+
request(method: string, path: string, options?: RequestInit): Promise<ApiResponse>;
|
|
383
|
+
/**
|
|
384
|
+
* Create a success result from an API response.
|
|
385
|
+
*/
|
|
386
|
+
apiSuccess(response: ApiResponse, result?: Record<string, unknown>, includeResponse?: boolean): StepHandlerResult;
|
|
387
|
+
/**
|
|
388
|
+
* Create a failure result from an API response.
|
|
389
|
+
*/
|
|
390
|
+
apiFailure(response: ApiResponse, message?: string): StepHandlerResult;
|
|
391
|
+
/**
|
|
392
|
+
* Create a failure result from a connection error.
|
|
393
|
+
*/
|
|
394
|
+
connectionError(error: Error, context?: string): StepHandlerResult;
|
|
395
|
+
/**
|
|
396
|
+
* Create a failure result from a timeout error.
|
|
397
|
+
*/
|
|
398
|
+
timeoutError(error: Error, context?: string): StepHandlerResult;
|
|
399
|
+
private fetch;
|
|
400
|
+
private buildUrl;
|
|
401
|
+
private mergeHeaders;
|
|
402
|
+
private prepareBody;
|
|
403
|
+
private classifyError;
|
|
404
|
+
private formatErrorMessage;
|
|
405
|
+
private extractBodyErrorMessage;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Helper function to apply API methods to a handler instance.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```typescript
|
|
412
|
+
* class MyApiHandler extends StepHandler {
|
|
413
|
+
* constructor() {
|
|
414
|
+
* super();
|
|
415
|
+
* applyAPI(this);
|
|
416
|
+
* }
|
|
417
|
+
* }
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare function applyAPI<T extends object>(target: T): T & APICapable;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* API handler for HTTP interactions.
|
|
424
|
+
*
|
|
425
|
+
* TAS-112: Composition Pattern (DEPRECATED CLASS)
|
|
426
|
+
*
|
|
427
|
+
* This module provides the ApiHandler class for backward compatibility.
|
|
428
|
+
* For new code, use the mixin pattern:
|
|
429
|
+
*
|
|
430
|
+
* @example Using APIMixin
|
|
431
|
+
* ```typescript
|
|
432
|
+
* import { StepHandler } from './base';
|
|
433
|
+
* import { APIMixin, APICapable, applyAPI } from './mixins/api';
|
|
434
|
+
*
|
|
435
|
+
* class FetchUserHandler extends StepHandler implements APICapable {
|
|
436
|
+
* static handlerName = 'fetch_user';
|
|
437
|
+
* static baseUrl = 'https://api.example.com';
|
|
438
|
+
*
|
|
439
|
+
* constructor() {
|
|
440
|
+
* super();
|
|
441
|
+
* applyAPI(this);
|
|
442
|
+
* }
|
|
443
|
+
*
|
|
444
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
445
|
+
* const response = await this.get('/users');
|
|
446
|
+
* if (response.ok) {
|
|
447
|
+
* return this.apiSuccess(response);
|
|
448
|
+
* }
|
|
449
|
+
* return this.apiFailure(response);
|
|
450
|
+
* }
|
|
451
|
+
* }
|
|
452
|
+
* ```
|
|
453
|
+
*
|
|
454
|
+
* @module handler/api
|
|
455
|
+
*/
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Base class for HTTP API step handlers.
|
|
459
|
+
*
|
|
460
|
+
* TAS-112: This class is provided for backward compatibility.
|
|
461
|
+
* For new code, prefer using APIMixin directly with applyAPI().
|
|
462
|
+
*
|
|
463
|
+
* Provides HTTP client functionality with automatic error classification,
|
|
464
|
+
* retry handling, and convenient methods for common HTTP operations.
|
|
465
|
+
*
|
|
466
|
+
* Uses native fetch API (available in Bun and Node.js 18+).
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```typescript
|
|
470
|
+
* class PaymentApiHandler extends ApiHandler {
|
|
471
|
+
* static handlerName = 'process_payment';
|
|
472
|
+
* static baseUrl = 'https://payments.example.com/api/v1';
|
|
473
|
+
* static defaultHeaders = { 'X-API-Key': 'secret' };
|
|
474
|
+
*
|
|
475
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
476
|
+
* const paymentData = context.inputData['payment'];
|
|
477
|
+
* const response = await this.post('/payments', { body: paymentData });
|
|
478
|
+
* if (response.ok) {
|
|
479
|
+
* return this.apiSuccess(response);
|
|
480
|
+
* }
|
|
481
|
+
* return this.apiFailure(response);
|
|
482
|
+
* }
|
|
483
|
+
* }
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
declare abstract class ApiHandler extends StepHandler {
|
|
487
|
+
/** Base URL for API calls. Override in subclasses. */
|
|
488
|
+
static baseUrl: string;
|
|
489
|
+
/** Default request timeout in milliseconds. */
|
|
490
|
+
static defaultTimeout: number;
|
|
491
|
+
/** Default headers to include in all requests. */
|
|
492
|
+
static defaultHeaders: Record<string, string>;
|
|
493
|
+
private _apiMixin;
|
|
494
|
+
private getApiMixin;
|
|
495
|
+
get capabilities(): string[];
|
|
496
|
+
/**
|
|
497
|
+
* Get the base URL for this handler.
|
|
498
|
+
*/
|
|
499
|
+
get baseUrl(): string;
|
|
500
|
+
/**
|
|
501
|
+
* Get the default timeout for this handler.
|
|
502
|
+
*/
|
|
503
|
+
get timeout(): number;
|
|
504
|
+
/**
|
|
505
|
+
* Get the default headers for this handler.
|
|
506
|
+
*/
|
|
507
|
+
get defaultHeaders(): Record<string, string>;
|
|
508
|
+
protected get(path: string, params?: Record<string, unknown>, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
509
|
+
protected post(path: string, options?: {
|
|
510
|
+
body?: unknown;
|
|
511
|
+
json?: boolean;
|
|
512
|
+
headers?: Record<string, string>;
|
|
513
|
+
}): Promise<ApiResponse>;
|
|
514
|
+
protected put(path: string, options?: {
|
|
515
|
+
body?: unknown;
|
|
516
|
+
json?: boolean;
|
|
517
|
+
headers?: Record<string, string>;
|
|
518
|
+
}): Promise<ApiResponse>;
|
|
519
|
+
protected patch(path: string, options?: {
|
|
520
|
+
body?: unknown;
|
|
521
|
+
json?: boolean;
|
|
522
|
+
headers?: Record<string, string>;
|
|
523
|
+
}): Promise<ApiResponse>;
|
|
524
|
+
protected delete(path: string, headers?: Record<string, string>): Promise<ApiResponse>;
|
|
525
|
+
protected request(method: string, path: string, options?: RequestInit): Promise<ApiResponse>;
|
|
526
|
+
protected apiSuccess(response: ApiResponse, result?: Record<string, unknown>, includeResponse?: boolean): StepHandlerResult;
|
|
527
|
+
protected apiFailure(response: ApiResponse, message?: string): StepHandlerResult;
|
|
528
|
+
protected connectionError(error: Error, context?: string): StepHandlerResult;
|
|
529
|
+
protected timeoutError(error: Error, context?: string): StepHandlerResult;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Batch processing types for cursor-based batch operations.
|
|
534
|
+
*
|
|
535
|
+
* These types support the analyzer/worker pattern for processing
|
|
536
|
+
* large datasets in parallel batches.
|
|
537
|
+
*
|
|
538
|
+
* Matches Python's batch processing types and Ruby's batch types (TAS-92 aligned).
|
|
539
|
+
*
|
|
540
|
+
* ## FFI Boundary Types (TAS-112/TAS-123)
|
|
541
|
+
*
|
|
542
|
+
* This module includes types that cross the Rust ↔ TypeScript FFI boundary:
|
|
543
|
+
*
|
|
544
|
+
* - `RustCursorConfig` - Cursor configuration with flexible cursor types
|
|
545
|
+
* - `BatchProcessingOutcome` - Discriminated union for batch processing decisions
|
|
546
|
+
* - `RustBatchWorkerInputs` - Worker initialization inputs from Rust orchestration
|
|
547
|
+
* - `BatchMetadata` - Batch processing metadata from template configuration
|
|
548
|
+
*
|
|
549
|
+
* These types are serialized by Rust and deserialized by TypeScript workers.
|
|
550
|
+
* They must remain compatible with Rust's serde serialization format.
|
|
551
|
+
*
|
|
552
|
+
* @module types/batch
|
|
553
|
+
*/
|
|
554
|
+
/**
|
|
555
|
+
* Configuration for cursor-based batch processing.
|
|
556
|
+
*
|
|
557
|
+
* Defines a range of items to process in a batch worker step.
|
|
558
|
+
*/
|
|
559
|
+
interface CursorConfig {
|
|
560
|
+
/** Starting cursor position (inclusive) */
|
|
561
|
+
startCursor: number;
|
|
562
|
+
/** Ending cursor position (exclusive) */
|
|
563
|
+
endCursor: number;
|
|
564
|
+
/** Step size for iteration (usually 1) */
|
|
565
|
+
stepSize: number;
|
|
566
|
+
/** Additional metadata for this cursor range */
|
|
567
|
+
metadata: Record<string, unknown>;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Outcome from a batch analyzer handler.
|
|
571
|
+
*
|
|
572
|
+
* Batch analyzers return this to define the cursor ranges that will
|
|
573
|
+
* spawn parallel batch worker steps.
|
|
574
|
+
*/
|
|
575
|
+
interface BatchAnalyzerOutcome {
|
|
576
|
+
/** List of cursor configurations for batch workers */
|
|
577
|
+
cursorConfigs: CursorConfig[];
|
|
578
|
+
/** Total number of items to process (for progress tracking) */
|
|
579
|
+
totalItems: number | null;
|
|
580
|
+
/** Metadata to pass to all batch workers */
|
|
581
|
+
batchMetadata: Record<string, unknown>;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Context for a batch worker step.
|
|
585
|
+
*
|
|
586
|
+
* Provides information about the specific batch this worker should process,
|
|
587
|
+
* including checkpoint data from previous yields (TAS-125).
|
|
588
|
+
*/
|
|
589
|
+
interface BatchWorkerContext {
|
|
590
|
+
/** Unique identifier for this batch */
|
|
591
|
+
batchId: string;
|
|
592
|
+
/** Cursor configuration for this batch */
|
|
593
|
+
cursorConfig: CursorConfig;
|
|
594
|
+
/** Index of this batch (0-based) */
|
|
595
|
+
batchIndex: number;
|
|
596
|
+
/** Total number of batches */
|
|
597
|
+
totalBatches: number;
|
|
598
|
+
/** Metadata from the analyzer */
|
|
599
|
+
batchMetadata: Record<string, unknown>;
|
|
600
|
+
/** TAS-125: Checkpoint data from previous yields */
|
|
601
|
+
checkpoint: Record<string, unknown>;
|
|
602
|
+
/** Convenience accessor for start cursor */
|
|
603
|
+
readonly startCursor: number;
|
|
604
|
+
/** Convenience accessor for end cursor */
|
|
605
|
+
readonly endCursor: number;
|
|
606
|
+
/** Convenience accessor for step size */
|
|
607
|
+
readonly stepSize: number;
|
|
608
|
+
/** TAS-125: Get checkpoint cursor from previous yield */
|
|
609
|
+
readonly checkpointCursor: number | string | Record<string, unknown> | undefined;
|
|
610
|
+
/** TAS-125: Get accumulated results from previous checkpoint yield */
|
|
611
|
+
readonly accumulatedResults: Record<string, unknown> | undefined;
|
|
612
|
+
/** TAS-125: Get items processed count from checkpoint */
|
|
613
|
+
readonly checkpointItemsProcessed: number;
|
|
614
|
+
/** TAS-125: Check if checkpoint exists */
|
|
615
|
+
hasCheckpoint(): boolean;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Outcome from a batch worker step.
|
|
619
|
+
*
|
|
620
|
+
* Batch workers return this to report progress and results.
|
|
621
|
+
*/
|
|
622
|
+
interface BatchWorkerOutcome {
|
|
623
|
+
/** Total items processed in this batch */
|
|
624
|
+
itemsProcessed: number;
|
|
625
|
+
/** Items that succeeded */
|
|
626
|
+
itemsSucceeded: number;
|
|
627
|
+
/** Items that failed */
|
|
628
|
+
itemsFailed: number;
|
|
629
|
+
/** Items that were skipped */
|
|
630
|
+
itemsSkipped: number;
|
|
631
|
+
/** Individual item results */
|
|
632
|
+
results: Array<Record<string, unknown>>;
|
|
633
|
+
/** Individual item errors */
|
|
634
|
+
errors: Array<Record<string, unknown>>;
|
|
635
|
+
/** Last cursor position processed */
|
|
636
|
+
lastCursor: number | null;
|
|
637
|
+
/** Additional batch metadata */
|
|
638
|
+
batchMetadata: Record<string, unknown>;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Create a BatchWorkerContext from raw batch data.
|
|
642
|
+
*
|
|
643
|
+
* Handles both formats:
|
|
644
|
+
* - Nested: { batch_id, cursor_config: { start_cursor, end_cursor, ... } }
|
|
645
|
+
* - Flat: { batch_id, start_cursor, end_cursor, ... }
|
|
646
|
+
*
|
|
647
|
+
* TAS-125: Also extracts checkpoint data if present.
|
|
648
|
+
*
|
|
649
|
+
* @internal
|
|
650
|
+
*/
|
|
651
|
+
declare function createBatchWorkerContext(batchData: Record<string, unknown>, checkpoint?: Record<string, unknown>): BatchWorkerContext;
|
|
652
|
+
/**
|
|
653
|
+
* Cursor configuration for a single batch's position and range.
|
|
654
|
+
*
|
|
655
|
+
* Matches Rust's `CursorConfig` in `tasker-shared/src/messaging/execution_types.rs`.
|
|
656
|
+
*
|
|
657
|
+
* ## Flexible Cursor Types
|
|
658
|
+
*
|
|
659
|
+
* Unlike the simpler `CursorConfig` interface (which uses `number`),
|
|
660
|
+
* this type supports flexible cursor values that can be:
|
|
661
|
+
* - Integer for record IDs: `123`
|
|
662
|
+
* - String for timestamps: `"2025-11-01T00:00:00Z"`
|
|
663
|
+
* - Object for composite keys: `{"page": 1, "offset": 0}`
|
|
664
|
+
*
|
|
665
|
+
* This enables cursor-based pagination across diverse data sources.
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* ```typescript
|
|
669
|
+
* // Integer cursors (most common)
|
|
670
|
+
* const intCursor: RustCursorConfig = {
|
|
671
|
+
* batch_id: "batch_001",
|
|
672
|
+
* start_cursor: 0,
|
|
673
|
+
* end_cursor: 1000,
|
|
674
|
+
* batch_size: 1000,
|
|
675
|
+
* };
|
|
676
|
+
*
|
|
677
|
+
* // Timestamp cursors
|
|
678
|
+
* const timestampCursor: RustCursorConfig = {
|
|
679
|
+
* batch_id: "batch_001",
|
|
680
|
+
* start_cursor: "2025-01-01T00:00:00Z",
|
|
681
|
+
* end_cursor: "2025-01-02T00:00:00Z",
|
|
682
|
+
* batch_size: 86400, // seconds in a day
|
|
683
|
+
* };
|
|
684
|
+
*
|
|
685
|
+
* // Composite cursors
|
|
686
|
+
* const compositeCursor: RustCursorConfig = {
|
|
687
|
+
* batch_id: "batch_001",
|
|
688
|
+
* start_cursor: { page: 1, offset: 0 },
|
|
689
|
+
* end_cursor: { page: 10, offset: 0 },
|
|
690
|
+
* batch_size: 1000,
|
|
691
|
+
* };
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
interface RustCursorConfig {
|
|
695
|
+
/** Batch identifier (e.g., "batch_001", "batch_002") */
|
|
696
|
+
batch_id: string;
|
|
697
|
+
/**
|
|
698
|
+
* Starting position for this batch (inclusive).
|
|
699
|
+
*
|
|
700
|
+
* Type depends on cursor strategy:
|
|
701
|
+
* - `number` for record IDs
|
|
702
|
+
* - `string` for timestamps or UUIDs
|
|
703
|
+
* - `object` for composite keys
|
|
704
|
+
*/
|
|
705
|
+
start_cursor: unknown;
|
|
706
|
+
/**
|
|
707
|
+
* Ending position for this batch (exclusive).
|
|
708
|
+
*
|
|
709
|
+
* Workers process items from `start_cursor` (inclusive)
|
|
710
|
+
* up to but not including `end_cursor`.
|
|
711
|
+
*/
|
|
712
|
+
end_cursor: unknown;
|
|
713
|
+
/** Number of items in this batch (for progress reporting) */
|
|
714
|
+
batch_size: number;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Failure strategy for batch processing.
|
|
718
|
+
*
|
|
719
|
+
* Matches Rust's `FailureStrategy` enum in `task_template.rs`.
|
|
720
|
+
*/
|
|
721
|
+
type FailureStrategy = 'continue_on_failure' | 'fail_fast' | 'isolate';
|
|
722
|
+
/**
|
|
723
|
+
* Batch processing metadata from template configuration.
|
|
724
|
+
*
|
|
725
|
+
* Matches Rust's `BatchMetadata` in `tasker-shared/src/models/core/batch_worker.rs`.
|
|
726
|
+
*
|
|
727
|
+
* This structure extracts relevant template configuration that workers
|
|
728
|
+
* need during execution. Workers don't need parallelism settings or
|
|
729
|
+
* batch size calculation logic - just execution parameters.
|
|
730
|
+
*/
|
|
731
|
+
interface BatchMetadata {
|
|
732
|
+
/**
|
|
733
|
+
* Database field name used for cursor-based pagination.
|
|
734
|
+
*
|
|
735
|
+
* Workers use this to construct queries like:
|
|
736
|
+
* `WHERE cursor_field > start_cursor AND cursor_field <= end_cursor`
|
|
737
|
+
*
|
|
738
|
+
* Common values: "id", "created_at", "sequence_number"
|
|
739
|
+
*/
|
|
740
|
+
cursor_field: string;
|
|
741
|
+
/**
|
|
742
|
+
* How this worker should handle failures during batch processing.
|
|
743
|
+
*
|
|
744
|
+
* - `continue_on_failure`: Log errors, continue processing remaining items
|
|
745
|
+
* - `fail_fast`: Stop immediately on first error
|
|
746
|
+
* - `isolate`: Mark batch for manual investigation
|
|
747
|
+
*/
|
|
748
|
+
failure_strategy: FailureStrategy;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Initialization inputs for batch worker instances.
|
|
752
|
+
*
|
|
753
|
+
* Matches Rust's `BatchWorkerInputs` in `tasker-shared/src/models/core/batch_worker.rs`.
|
|
754
|
+
*
|
|
755
|
+
* This structure is serialized to JSONB by Rust orchestration and stored
|
|
756
|
+
* in `workflow_steps.inputs` for dynamically created batch workers.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* // In a batch worker handler
|
|
761
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
762
|
+
* const inputs = context.stepInputs as RustBatchWorkerInputs;
|
|
763
|
+
*
|
|
764
|
+
* // Check for no-op placeholder first
|
|
765
|
+
* if (inputs.is_no_op) {
|
|
766
|
+
* return this.success({
|
|
767
|
+
* batch_id: inputs.cursor.batch_id,
|
|
768
|
+
* no_op: true,
|
|
769
|
+
* message: 'No batches to process',
|
|
770
|
+
* });
|
|
771
|
+
* }
|
|
772
|
+
*
|
|
773
|
+
* // Process the batch using cursor bounds
|
|
774
|
+
* const { start_cursor, end_cursor } = inputs.cursor;
|
|
775
|
+
* // ... process items in range
|
|
776
|
+
* }
|
|
777
|
+
* ```
|
|
778
|
+
*/
|
|
779
|
+
interface RustBatchWorkerInputs {
|
|
780
|
+
/**
|
|
781
|
+
* Cursor configuration defining this worker's processing range.
|
|
782
|
+
*
|
|
783
|
+
* Created by the batchable handler after analyzing dataset size.
|
|
784
|
+
*/
|
|
785
|
+
cursor: RustCursorConfig;
|
|
786
|
+
/**
|
|
787
|
+
* Batch processing metadata from template configuration.
|
|
788
|
+
*
|
|
789
|
+
* Provides checkpointing frequency, cursor field, and failure strategy.
|
|
790
|
+
*/
|
|
791
|
+
batch_metadata: BatchMetadata;
|
|
792
|
+
/**
|
|
793
|
+
* Explicit flag indicating if this is a no-op/placeholder worker.
|
|
794
|
+
*
|
|
795
|
+
* Set by orchestration based on BatchProcessingOutcome type:
|
|
796
|
+
* - `true` for NoBatches outcome (placeholder worker)
|
|
797
|
+
* - `false` for CreateBatches outcome (real worker with data)
|
|
798
|
+
*
|
|
799
|
+
* Workers should check this flag FIRST before any processing logic.
|
|
800
|
+
* If `true`, immediately return success without processing.
|
|
801
|
+
*/
|
|
802
|
+
is_no_op: boolean;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* No batches needed - process as single step or skip.
|
|
806
|
+
*
|
|
807
|
+
* Returned when:
|
|
808
|
+
* - Dataset is too small to warrant batching
|
|
809
|
+
* - Data doesn't meet batching criteria
|
|
810
|
+
* - Batch processing not applicable for this execution
|
|
811
|
+
*
|
|
812
|
+
* Serialization format: `{ "type": "no_batches" }`
|
|
813
|
+
*/
|
|
814
|
+
interface NoBatchesOutcome {
|
|
815
|
+
type: 'no_batches';
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Create batch worker steps from template.
|
|
819
|
+
*
|
|
820
|
+
* The orchestration system will:
|
|
821
|
+
* 1. Instantiate N workers from the template step
|
|
822
|
+
* 2. Assign each worker a unique cursor config
|
|
823
|
+
* 3. Create DAG edges from batchable step to workers
|
|
824
|
+
* 4. Enqueue workers for parallel execution
|
|
825
|
+
*
|
|
826
|
+
* Serialization format:
|
|
827
|
+
* ```json
|
|
828
|
+
* {
|
|
829
|
+
* "type": "create_batches",
|
|
830
|
+
* "worker_template_name": "batch_worker_template",
|
|
831
|
+
* "worker_count": 5,
|
|
832
|
+
* "cursor_configs": [...],
|
|
833
|
+
* "total_items": 5000
|
|
834
|
+
* }
|
|
835
|
+
* ```
|
|
836
|
+
*/
|
|
837
|
+
interface CreateBatchesOutcome {
|
|
838
|
+
type: 'create_batches';
|
|
839
|
+
/**
|
|
840
|
+
* Template step name to use for creating workers.
|
|
841
|
+
*
|
|
842
|
+
* Must match a step definition in the template with `type: batch_worker`.
|
|
843
|
+
* The system creates multiple instances with generated names like:
|
|
844
|
+
* - `{template_name}_001`
|
|
845
|
+
* - `{template_name}_002`
|
|
846
|
+
*/
|
|
847
|
+
worker_template_name: string;
|
|
848
|
+
/**
|
|
849
|
+
* Number of worker instances to create.
|
|
850
|
+
*
|
|
851
|
+
* Typically calculated based on dataset size / batch_size.
|
|
852
|
+
*/
|
|
853
|
+
worker_count: number;
|
|
854
|
+
/**
|
|
855
|
+
* Initial cursor positions for each batch.
|
|
856
|
+
*
|
|
857
|
+
* Each worker receives one cursor config that defines its
|
|
858
|
+
* processing boundaries. Length must equal `worker_count`.
|
|
859
|
+
*/
|
|
860
|
+
cursor_configs: RustCursorConfig[];
|
|
861
|
+
/**
|
|
862
|
+
* Total items to process across all batches.
|
|
863
|
+
*
|
|
864
|
+
* Used for progress tracking and observability.
|
|
865
|
+
*/
|
|
866
|
+
total_items: number;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Outcome of a batchable step that determines batch worker creation.
|
|
870
|
+
*
|
|
871
|
+
* Matches Rust's `BatchProcessingOutcome` enum in
|
|
872
|
+
* `tasker-shared/src/messaging/execution_types.rs`.
|
|
873
|
+
*
|
|
874
|
+
* This discriminated union enables type-safe pattern matching:
|
|
875
|
+
*
|
|
876
|
+
* @example
|
|
877
|
+
* ```typescript
|
|
878
|
+
* function handleOutcome(outcome: BatchProcessingOutcome): void {
|
|
879
|
+
* switch (outcome.type) {
|
|
880
|
+
* case 'no_batches':
|
|
881
|
+
* console.log('No batches needed');
|
|
882
|
+
* break;
|
|
883
|
+
* case 'create_batches':
|
|
884
|
+
* console.log(`Creating ${outcome.worker_count} workers`);
|
|
885
|
+
* console.log(`Total items: ${outcome.total_items}`);
|
|
886
|
+
* break;
|
|
887
|
+
* default: {
|
|
888
|
+
* const _exhaustive: never = outcome;
|
|
889
|
+
* throw new Error(`Unhandled outcome type: ${_exhaustive}`);
|
|
890
|
+
* }
|
|
891
|
+
* }
|
|
892
|
+
* }
|
|
893
|
+
* ```
|
|
894
|
+
*/
|
|
895
|
+
type BatchProcessingOutcome = NoBatchesOutcome | CreateBatchesOutcome;
|
|
896
|
+
/**
|
|
897
|
+
* Create a NoBatches outcome.
|
|
898
|
+
*
|
|
899
|
+
* Use when batching is not needed or applicable.
|
|
900
|
+
*
|
|
901
|
+
* @returns A NoBatchesOutcome object
|
|
902
|
+
*/
|
|
903
|
+
declare function noBatches(): NoBatchesOutcome;
|
|
904
|
+
/**
|
|
905
|
+
* Create a CreateBatches outcome with specified configuration.
|
|
906
|
+
*
|
|
907
|
+
* @param workerTemplateName - Name of the template step to instantiate
|
|
908
|
+
* @param workerCount - Number of workers to create
|
|
909
|
+
* @param cursorConfigs - Cursor configuration for each worker
|
|
910
|
+
* @param totalItems - Total number of items to process
|
|
911
|
+
* @returns A CreateBatchesOutcome object
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```typescript
|
|
915
|
+
* const outcome = createBatches(
|
|
916
|
+
* 'process_csv_batch',
|
|
917
|
+
* 3,
|
|
918
|
+
* [
|
|
919
|
+
* { batch_id: '001', start_cursor: 0, end_cursor: 1000, batch_size: 1000 },
|
|
920
|
+
* { batch_id: '002', start_cursor: 1000, end_cursor: 2000, batch_size: 1000 },
|
|
921
|
+
* { batch_id: '003', start_cursor: 2000, end_cursor: 3000, batch_size: 1000 },
|
|
922
|
+
* ],
|
|
923
|
+
* 3000
|
|
924
|
+
* );
|
|
925
|
+
* ```
|
|
926
|
+
*/
|
|
927
|
+
declare function createBatches(workerTemplateName: string, workerCount: number, cursorConfigs: RustCursorConfig[], totalItems: number): CreateBatchesOutcome;
|
|
928
|
+
/**
|
|
929
|
+
* Type guard to check if an outcome is NoBatches.
|
|
930
|
+
*/
|
|
931
|
+
declare function isNoBatches(outcome: BatchProcessingOutcome): outcome is NoBatchesOutcome;
|
|
932
|
+
/**
|
|
933
|
+
* Type guard to check if an outcome is CreateBatches.
|
|
934
|
+
*/
|
|
935
|
+
declare function isCreateBatches(outcome: BatchProcessingOutcome): outcome is CreateBatchesOutcome;
|
|
936
|
+
/**
|
|
937
|
+
* Result from aggregating multiple batch worker results.
|
|
938
|
+
*
|
|
939
|
+
* Cross-language standard: matches Python's aggregate_worker_results output
|
|
940
|
+
* and Ruby's aggregate_batch_worker_results.
|
|
941
|
+
*
|
|
942
|
+
* TAS-112: Standardized aggregation result structure.
|
|
943
|
+
*/
|
|
944
|
+
interface BatchAggregationResult {
|
|
945
|
+
/** Total items processed across all batches */
|
|
946
|
+
total_processed: number;
|
|
947
|
+
/** Total items that succeeded */
|
|
948
|
+
total_succeeded: number;
|
|
949
|
+
/** Total items that failed */
|
|
950
|
+
total_failed: number;
|
|
951
|
+
/** Total items that were skipped */
|
|
952
|
+
total_skipped: number;
|
|
953
|
+
/** Number of batch workers that ran */
|
|
954
|
+
batch_count: number;
|
|
955
|
+
/** Success rate (0.0 to 1.0) */
|
|
956
|
+
success_rate: number;
|
|
957
|
+
/** Collected errors from all batches (limited) */
|
|
958
|
+
errors: Array<Record<string, unknown>>;
|
|
959
|
+
/** Total error count (may exceed errors array length) */
|
|
960
|
+
error_count: number;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Aggregate results from multiple batch workers.
|
|
964
|
+
*
|
|
965
|
+
* Cross-language standard: matches Python's `Batchable.aggregate_worker_results`
|
|
966
|
+
* and Ruby's `aggregate_batch_worker_results`.
|
|
967
|
+
*
|
|
968
|
+
* @param workerResults - Array of results from batch worker steps
|
|
969
|
+
* @param maxErrors - Maximum number of errors to collect (default: 100)
|
|
970
|
+
* @returns Aggregated summary of all batch processing
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```typescript
|
|
974
|
+
* // In an aggregator handler
|
|
975
|
+
* const workerResults = Object.values(context.previousResults)
|
|
976
|
+
* .filter(r => r?.batch_worker);
|
|
977
|
+
* const summary = aggregateBatchResults(workerResults);
|
|
978
|
+
* return this.success(summary);
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
declare function aggregateBatchResults(workerResults: Array<Record<string, unknown> | null | undefined>, maxErrors?: number): BatchAggregationResult;
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Represents the aggregation scenario for batch processing convergence steps.
|
|
985
|
+
*
|
|
986
|
+
* Cross-language standard: matches Ruby's BatchAggregationScenario,
|
|
987
|
+
* Python's BatchAggregationScenario, and Rust's BatchAggregationScenario enum.
|
|
988
|
+
*
|
|
989
|
+
* There are two scenarios:
|
|
990
|
+
* - **NoBatches**: The batchable step returned `noBatches()`, no workers were created.
|
|
991
|
+
* The convergence step should read results directly from the batchable step.
|
|
992
|
+
* - **WithBatches**: Workers were created and processed batches. The convergence
|
|
993
|
+
* step should aggregate results from all batch workers.
|
|
994
|
+
*/
|
|
995
|
+
interface BatchAggregationScenario {
|
|
996
|
+
/** True if this is a NoBatches scenario. */
|
|
997
|
+
isNoBatches: boolean;
|
|
998
|
+
/** Result from the batchable step (always present). */
|
|
999
|
+
batchableResult: Record<string, unknown>;
|
|
1000
|
+
/** Dict of worker_name -> result (empty for NoBatches). */
|
|
1001
|
+
batchResults: Record<string, Record<string, unknown>>;
|
|
1002
|
+
/** Number of batch workers (0 for NoBatches). */
|
|
1003
|
+
workerCount: number;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Mixin interface for batch processing capabilities.
|
|
1007
|
+
*
|
|
1008
|
+
* TypeScript implementation using interface + method binding pattern
|
|
1009
|
+
* (since TS doesn't have true mixins like Python/Ruby).
|
|
1010
|
+
*
|
|
1011
|
+
* Matches Python's Batchable mixin and Ruby's Batchable module (TAS-92 aligned).
|
|
1012
|
+
*/
|
|
1013
|
+
interface Batchable {
|
|
1014
|
+
createCursorConfig(start: number, end: number, stepSize?: number, metadata?: Record<string, unknown>): CursorConfig;
|
|
1015
|
+
createCursorRanges(totalItems: number, batchSize: number, stepSize?: number, maxBatches?: number): CursorConfig[];
|
|
1016
|
+
/**
|
|
1017
|
+
* Create cursor configurations for a specific number of workers.
|
|
1018
|
+
*
|
|
1019
|
+
* Ruby-style method that divides items into worker_count roughly equal ranges.
|
|
1020
|
+
* Use this when you know the desired number of workers rather than batch size.
|
|
1021
|
+
*
|
|
1022
|
+
* Cross-language standard: matches Ruby's create_cursor_configs(total_items, worker_count).
|
|
1023
|
+
*/
|
|
1024
|
+
createCursorConfigs(totalItems: number, workerCount: number): BatchWorkerConfig[];
|
|
1025
|
+
createBatchOutcome(totalItems: number, batchSize: number, stepSize?: number, batchMetadata?: Record<string, unknown>): BatchAnalyzerOutcome;
|
|
1026
|
+
createWorkerOutcome(itemsProcessed: number, itemsSucceeded?: number, itemsFailed?: number, itemsSkipped?: number, results?: Array<Record<string, unknown>>, errors?: Array<Record<string, unknown>>, lastCursor?: number | null, batchMetadata?: Record<string, unknown>): BatchWorkerOutcome;
|
|
1027
|
+
getBatchContext(context: StepContext): BatchWorkerContext | null;
|
|
1028
|
+
/**
|
|
1029
|
+
* Get Rust batch worker inputs from step context.
|
|
1030
|
+
*
|
|
1031
|
+
* Returns the BatchWorkerInputs structure from workflow_step.inputs,
|
|
1032
|
+
* which contains cursor config, batch metadata, and no-op flag.
|
|
1033
|
+
*
|
|
1034
|
+
* Cross-language standard: matches Ruby's get_batch_context pattern.
|
|
1035
|
+
*/
|
|
1036
|
+
getBatchWorkerInputs(context: StepContext): Partial<RustBatchWorkerInputs> | null;
|
|
1037
|
+
/**
|
|
1038
|
+
* Handle no-op placeholder worker scenario.
|
|
1039
|
+
*
|
|
1040
|
+
* Returns a success result if the worker is a no-op placeholder,
|
|
1041
|
+
* otherwise returns null to allow normal processing to continue.
|
|
1042
|
+
*
|
|
1043
|
+
* Cross-language standard: matches Ruby's handle_no_op_worker.
|
|
1044
|
+
*/
|
|
1045
|
+
handleNoOpWorker(context: StepContext): StepHandlerResult | null;
|
|
1046
|
+
batchAnalyzerSuccess(outcome: BatchAnalyzerOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1047
|
+
batchWorkerSuccess(outcome: BatchWorkerOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1048
|
+
/**
|
|
1049
|
+
* TAS-125: Yield checkpoint for batch processing.
|
|
1050
|
+
*
|
|
1051
|
+
* Use this method when your handler needs to persist progress and be
|
|
1052
|
+
* re-dispatched for continued processing. Unlike batchWorkerSuccess,
|
|
1053
|
+
* this does NOT complete the step.
|
|
1054
|
+
*
|
|
1055
|
+
* @param cursor - Position to resume from (number, string, or object)
|
|
1056
|
+
* @param itemsProcessed - Total items processed so far (cumulative)
|
|
1057
|
+
* @param accumulatedResults - Partial aggregations to carry forward
|
|
1058
|
+
* @returns A StepHandlerResult with checkpoint_yield type
|
|
1059
|
+
*/
|
|
1060
|
+
checkpointYield(cursor: number | string | Record<string, unknown>, itemsProcessed: number, accumulatedResults?: Record<string, unknown>): StepHandlerResult;
|
|
1061
|
+
/**
|
|
1062
|
+
* Detect batch aggregation scenario from dependency results.
|
|
1063
|
+
*
|
|
1064
|
+
* Cross-language standard: matches Ruby's detect_aggregation_scenario,
|
|
1065
|
+
* Python's detect_aggregation_scenario, and Rust's BatchAggregationScenario::detect.
|
|
1066
|
+
*/
|
|
1067
|
+
detectAggregationScenario(dependencyResults: Record<string, unknown>, batchableStepName: string, batchWorkerPrefix: string): BatchAggregationScenario;
|
|
1068
|
+
/**
|
|
1069
|
+
* Create a success result for NoBatches aggregation scenario.
|
|
1070
|
+
*
|
|
1071
|
+
* Cross-language standard: matches Ruby's no_batches_aggregation_result
|
|
1072
|
+
* and Python's no_batches_aggregation_result.
|
|
1073
|
+
*/
|
|
1074
|
+
noBatchesAggregationResult(zeroMetrics?: Record<string, unknown>): StepHandlerResult;
|
|
1075
|
+
/**
|
|
1076
|
+
* Aggregate batch worker results handling both scenarios.
|
|
1077
|
+
*
|
|
1078
|
+
* Cross-language standard: matches Ruby's aggregate_batch_worker_results
|
|
1079
|
+
* and Python's aggregate_batch_worker_results.
|
|
1080
|
+
*/
|
|
1081
|
+
aggregateBatchWorkerResults(scenario: BatchAggregationScenario, zeroMetrics?: Record<string, unknown>, aggregationFn?: (batchResults: Record<string, Record<string, unknown>>) => Record<string, unknown>): StepHandlerResult;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Implementation of Batchable methods.
|
|
1085
|
+
*
|
|
1086
|
+
* Use this class to add batch processing capabilities to your handlers.
|
|
1087
|
+
* The methods can be bound to handler instances or used as a mixin.
|
|
1088
|
+
*
|
|
1089
|
+
* @example Analyzer using method binding
|
|
1090
|
+
* ```typescript
|
|
1091
|
+
* class ProductAnalyzer extends StepHandler implements Batchable {
|
|
1092
|
+
* // Bind Batchable methods to this instance
|
|
1093
|
+
* createBatchOutcome = BatchableMixin.prototype.createBatchOutcome.bind(this);
|
|
1094
|
+
* batchAnalyzerSuccess = BatchableMixin.prototype.batchAnalyzerSuccess.bind(this);
|
|
1095
|
+
* // ... other required Batchable methods
|
|
1096
|
+
*
|
|
1097
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1098
|
+
* const total = context.inputData['product_count'] as number;
|
|
1099
|
+
* const outcome = this.createBatchOutcome(total, 100);
|
|
1100
|
+
* return this.batchAnalyzerSuccess(outcome);
|
|
1101
|
+
* }
|
|
1102
|
+
* }
|
|
1103
|
+
* ```
|
|
1104
|
+
*
|
|
1105
|
+
* @example Worker using method binding
|
|
1106
|
+
* ```typescript
|
|
1107
|
+
* class ProductWorker extends StepHandler implements Batchable {
|
|
1108
|
+
* getBatchContext = BatchableMixin.prototype.getBatchContext.bind(this);
|
|
1109
|
+
* createWorkerOutcome = BatchableMixin.prototype.createWorkerOutcome.bind(this);
|
|
1110
|
+
* batchWorkerSuccess = BatchableMixin.prototype.batchWorkerSuccess.bind(this);
|
|
1111
|
+
* // ... other required Batchable methods
|
|
1112
|
+
*
|
|
1113
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1114
|
+
* const batchCtx = this.getBatchContext(context);
|
|
1115
|
+
* if (!batchCtx) {
|
|
1116
|
+
* return this.failure('No batch context found');
|
|
1117
|
+
* }
|
|
1118
|
+
*
|
|
1119
|
+
* const results = [];
|
|
1120
|
+
* for (let i = batchCtx.startCursor; i < batchCtx.endCursor; i++) {
|
|
1121
|
+
* results.push(await this.processItem(i));
|
|
1122
|
+
* }
|
|
1123
|
+
*
|
|
1124
|
+
* const outcome = this.createWorkerOutcome(results.length, results.length);
|
|
1125
|
+
* return this.batchWorkerSuccess(outcome);
|
|
1126
|
+
* }
|
|
1127
|
+
* }
|
|
1128
|
+
* ```
|
|
1129
|
+
*/
|
|
1130
|
+
declare class BatchableMixin implements Batchable {
|
|
1131
|
+
/**
|
|
1132
|
+
* Create a cursor configuration for a batch range.
|
|
1133
|
+
*
|
|
1134
|
+
* @param start - Starting cursor position (inclusive)
|
|
1135
|
+
* @param end - Ending cursor position (exclusive)
|
|
1136
|
+
* @param stepSize - Step size for iteration (default: 1)
|
|
1137
|
+
* @param metadata - Additional metadata for this cursor range
|
|
1138
|
+
* @returns CursorConfig for the specified range
|
|
1139
|
+
*/
|
|
1140
|
+
createCursorConfig(start: number, end: number, stepSize?: number, metadata?: Record<string, unknown>): CursorConfig;
|
|
1141
|
+
/**
|
|
1142
|
+
* Create cursor ranges for batch processing.
|
|
1143
|
+
*
|
|
1144
|
+
* Divides totalItems into batches of batchSize, optionally limiting
|
|
1145
|
+
* the number of batches.
|
|
1146
|
+
*
|
|
1147
|
+
* @param totalItems - Total number of items to process
|
|
1148
|
+
* @param batchSize - Number of items per batch
|
|
1149
|
+
* @param stepSize - Step size for iteration (default: 1)
|
|
1150
|
+
* @param maxBatches - Maximum number of batches (optional)
|
|
1151
|
+
* @returns Array of CursorConfig for each batch
|
|
1152
|
+
*/
|
|
1153
|
+
createCursorRanges(totalItems: number, batchSize: number, stepSize?: number, maxBatches?: number): CursorConfig[];
|
|
1154
|
+
/**
|
|
1155
|
+
* Create cursor configurations for a specific number of workers.
|
|
1156
|
+
*
|
|
1157
|
+
* Ruby-style method that divides items into worker_count roughly equal ranges.
|
|
1158
|
+
* Uses ceiling division to ensure all items are covered.
|
|
1159
|
+
*
|
|
1160
|
+
* ## Cursor Boundary Math
|
|
1161
|
+
*
|
|
1162
|
+
* 1. items_per_worker = ceil(total_items / worker_count)
|
|
1163
|
+
* 2. For worker i (0-indexed):
|
|
1164
|
+
* - start = i * items_per_worker
|
|
1165
|
+
* - end = min((i + 1) * items_per_worker, total_items)
|
|
1166
|
+
* - batch_size = end - start
|
|
1167
|
+
*
|
|
1168
|
+
* Example: 1000 items, 3 workers
|
|
1169
|
+
* - items_per_worker = ceil(1000/3) = 334
|
|
1170
|
+
* - Worker 0: start=0, end=334, size=334
|
|
1171
|
+
* - Worker 1: start=334, end=668, size=334
|
|
1172
|
+
* - Worker 2: start=668, end=1000, size=332
|
|
1173
|
+
*
|
|
1174
|
+
* Cross-language standard: matches Ruby's create_cursor_configs(total_items, worker_count).
|
|
1175
|
+
*
|
|
1176
|
+
* @param totalItems - Total number of items to process
|
|
1177
|
+
* @param workerCount - Number of workers to create configs for (must be > 0)
|
|
1178
|
+
* @returns Array of BatchWorkerConfig for each worker
|
|
1179
|
+
*/
|
|
1180
|
+
createCursorConfigs(totalItems: number, workerCount: number): BatchWorkerConfig[];
|
|
1181
|
+
/**
|
|
1182
|
+
* Create a batch analyzer outcome.
|
|
1183
|
+
*
|
|
1184
|
+
* Convenience method that creates cursor ranges and wraps them
|
|
1185
|
+
* in a BatchAnalyzerOutcome.
|
|
1186
|
+
*
|
|
1187
|
+
* @param totalItems - Total number of items to process
|
|
1188
|
+
* @param batchSize - Number of items per batch
|
|
1189
|
+
* @param stepSize - Step size for iteration (default: 1)
|
|
1190
|
+
* @param batchMetadata - Metadata to pass to all batch workers
|
|
1191
|
+
* @returns BatchAnalyzerOutcome ready for batchAnalyzerSuccess
|
|
1192
|
+
*/
|
|
1193
|
+
createBatchOutcome(totalItems: number, batchSize: number, stepSize?: number, batchMetadata?: Record<string, unknown>): BatchAnalyzerOutcome;
|
|
1194
|
+
/**
|
|
1195
|
+
* Create a batch worker outcome.
|
|
1196
|
+
*
|
|
1197
|
+
* @param itemsProcessed - Total items processed in this batch
|
|
1198
|
+
* @param itemsSucceeded - Items that succeeded (default: itemsProcessed)
|
|
1199
|
+
* @param itemsFailed - Items that failed (default: 0)
|
|
1200
|
+
* @param itemsSkipped - Items that were skipped (default: 0)
|
|
1201
|
+
* @param results - Individual item results
|
|
1202
|
+
* @param errors - Individual item errors
|
|
1203
|
+
* @param lastCursor - Last cursor position processed
|
|
1204
|
+
* @param batchMetadata - Additional batch metadata
|
|
1205
|
+
* @returns BatchWorkerOutcome ready for batchWorkerSuccess
|
|
1206
|
+
*/
|
|
1207
|
+
createWorkerOutcome(itemsProcessed: number, itemsSucceeded?: number, itemsFailed?: number, itemsSkipped?: number, results?: Array<Record<string, unknown>>, errors?: Array<Record<string, unknown>>, lastCursor?: number | null, batchMetadata?: Record<string, unknown>): BatchWorkerOutcome;
|
|
1208
|
+
/**
|
|
1209
|
+
* Get the batch context from a step context.
|
|
1210
|
+
*
|
|
1211
|
+
* Looks for batch context in step_config, input_data, or step_inputs.
|
|
1212
|
+
*
|
|
1213
|
+
* @param context - The step context
|
|
1214
|
+
* @returns BatchWorkerContext or null if not found
|
|
1215
|
+
*/
|
|
1216
|
+
getBatchContext(context: StepContext): BatchWorkerContext | null;
|
|
1217
|
+
/**
|
|
1218
|
+
* Get Rust batch worker inputs from step context.
|
|
1219
|
+
*
|
|
1220
|
+
* Returns the BatchWorkerInputs structure from workflow_step.inputs,
|
|
1221
|
+
* which contains cursor config, batch metadata, and no-op flag.
|
|
1222
|
+
*
|
|
1223
|
+
* Cross-language standard: matches Ruby's get_batch_context pattern
|
|
1224
|
+
* for accessing Rust-provided batch configuration.
|
|
1225
|
+
*
|
|
1226
|
+
* @param context - The step context
|
|
1227
|
+
* @returns BatchWorkerInputs or null if not found
|
|
1228
|
+
*/
|
|
1229
|
+
getBatchWorkerInputs(context: StepContext): Partial<RustBatchWorkerInputs> | null;
|
|
1230
|
+
/**
|
|
1231
|
+
* Handle no-op placeholder worker scenario.
|
|
1232
|
+
*
|
|
1233
|
+
* Returns a success result if the worker is a no-op placeholder
|
|
1234
|
+
* (created when a batchable step returns NoBatches), otherwise
|
|
1235
|
+
* returns null to allow normal processing to continue.
|
|
1236
|
+
*
|
|
1237
|
+
* Cross-language standard: matches Ruby's handle_no_op_worker.
|
|
1238
|
+
*
|
|
1239
|
+
* @param context - The step context
|
|
1240
|
+
* @returns Success result if no-op, null otherwise
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* ```typescript
|
|
1244
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1245
|
+
* const noOpResult = this.handleNoOpWorker(context);
|
|
1246
|
+
* if (noOpResult) {
|
|
1247
|
+
* return noOpResult;
|
|
1248
|
+
* }
|
|
1249
|
+
* // ... normal processing
|
|
1250
|
+
* }
|
|
1251
|
+
* ```
|
|
1252
|
+
*/
|
|
1253
|
+
handleNoOpWorker(context: StepContext): StepHandlerResult | null;
|
|
1254
|
+
/**
|
|
1255
|
+
* Create a success result for a batch analyzer.
|
|
1256
|
+
*
|
|
1257
|
+
* Formats the BatchAnalyzerOutcome in the structure expected by
|
|
1258
|
+
* the orchestration layer.
|
|
1259
|
+
*
|
|
1260
|
+
* @param outcome - The batch analyzer outcome
|
|
1261
|
+
* @param metadata - Optional additional metadata
|
|
1262
|
+
* @returns A success StepHandlerResult with the batch outcome
|
|
1263
|
+
*/
|
|
1264
|
+
batchAnalyzerSuccess(outcome: BatchAnalyzerOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1265
|
+
/**
|
|
1266
|
+
* Create a success result for a batch worker.
|
|
1267
|
+
*
|
|
1268
|
+
* Formats the BatchWorkerOutcome in the structure expected by
|
|
1269
|
+
* the orchestration layer.
|
|
1270
|
+
*
|
|
1271
|
+
* @param outcome - The batch worker outcome
|
|
1272
|
+
* @param metadata - Optional additional metadata
|
|
1273
|
+
* @returns A success StepHandlerResult with the worker outcome
|
|
1274
|
+
*/
|
|
1275
|
+
batchWorkerSuccess(outcome: BatchWorkerOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1276
|
+
/**
|
|
1277
|
+
* TAS-125: Yield checkpoint for batch processing.
|
|
1278
|
+
*
|
|
1279
|
+
* Use this method when your handler needs to persist progress and be
|
|
1280
|
+
* re-dispatched for continued processing. This is useful for:
|
|
1281
|
+
* - Processing very large datasets that exceed memory limits
|
|
1282
|
+
* - Providing progress visibility for long-running batch jobs
|
|
1283
|
+
* - Enabling graceful shutdown with resumption capability
|
|
1284
|
+
*
|
|
1285
|
+
* Unlike batchWorkerSuccess, this does NOT complete the step.
|
|
1286
|
+
* Instead, it persists the checkpoint and causes the step to be
|
|
1287
|
+
* re-dispatched with the updated checkpoint context.
|
|
1288
|
+
*
|
|
1289
|
+
* @param cursor - Position to resume from
|
|
1290
|
+
* - number: For offset-based pagination (row number)
|
|
1291
|
+
* - string: For cursor-based pagination (opaque token)
|
|
1292
|
+
* - object: For complex cursors (e.g., {page_token: "..."})
|
|
1293
|
+
* @param itemsProcessed - Total items processed so far (cumulative across all yields)
|
|
1294
|
+
* @param accumulatedResults - Partial aggregations to carry forward
|
|
1295
|
+
* @returns A StepHandlerResult with checkpoint_yield type
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* ```typescript
|
|
1299
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1300
|
+
* const batchCtx = this.getBatchContext(context);
|
|
1301
|
+
* const start = batchCtx?.checkpointCursor ?? batchCtx?.startCursor ?? 0;
|
|
1302
|
+
* const accumulated = batchCtx?.accumulatedResults ?? { total: 0 };
|
|
1303
|
+
*
|
|
1304
|
+
* // Process a chunk
|
|
1305
|
+
* const chunkSize = 1000;
|
|
1306
|
+
* for (let i = 0; i < chunkSize && start + i < batchCtx.endCursor; i++) {
|
|
1307
|
+
* accumulated.total += await processItem(start + i);
|
|
1308
|
+
* }
|
|
1309
|
+
*
|
|
1310
|
+
* const newCursor = start + chunkSize;
|
|
1311
|
+
* if (newCursor < batchCtx.endCursor) {
|
|
1312
|
+
* // More work to do - yield checkpoint
|
|
1313
|
+
* return this.checkpointYield(newCursor, newCursor, accumulated);
|
|
1314
|
+
* }
|
|
1315
|
+
*
|
|
1316
|
+
* // Done - return final success
|
|
1317
|
+
* return this.batchWorkerSuccess(
|
|
1318
|
+
* this.createWorkerOutcome(batchCtx.endCursor - batchCtx.startCursor)
|
|
1319
|
+
* );
|
|
1320
|
+
* }
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
checkpointYield(cursor: number | string | Record<string, unknown>, itemsProcessed: number, accumulatedResults?: Record<string, unknown>): StepHandlerResult;
|
|
1324
|
+
/**
|
|
1325
|
+
* Detect batch aggregation scenario from dependency results.
|
|
1326
|
+
*
|
|
1327
|
+
* Cross-language standard: matches Ruby's detect_aggregation_scenario,
|
|
1328
|
+
* Python's detect_aggregation_scenario, and Rust's BatchAggregationScenario::detect.
|
|
1329
|
+
*
|
|
1330
|
+
* @param dependencyResults - All dependency results from the step context.
|
|
1331
|
+
* @param batchableStepName - Name of the batchable step (e.g., "analyze_csv").
|
|
1332
|
+
* @param batchWorkerPrefix - Prefix for batch worker step names (e.g., "process_csv_batch_").
|
|
1333
|
+
* @returns BatchAggregationScenario indicating NoBatches or WithBatches.
|
|
1334
|
+
*
|
|
1335
|
+
* @example
|
|
1336
|
+
* ```typescript
|
|
1337
|
+
* const scenario = this.detectAggregationScenario(
|
|
1338
|
+
* context.dependencyResults,
|
|
1339
|
+
* 'analyze_csv',
|
|
1340
|
+
* 'process_csv_batch_'
|
|
1341
|
+
* );
|
|
1342
|
+
* if (scenario.isNoBatches) {
|
|
1343
|
+
* return this.noBatchesAggregationResult({ total: 0 });
|
|
1344
|
+
* }
|
|
1345
|
+
* ```
|
|
1346
|
+
*/
|
|
1347
|
+
detectAggregationScenario(dependencyResults: Record<string, unknown>, batchableStepName: string, batchWorkerPrefix: string): BatchAggregationScenario;
|
|
1348
|
+
/**
|
|
1349
|
+
* Create a success result for NoBatches aggregation scenario.
|
|
1350
|
+
*
|
|
1351
|
+
* Cross-language standard: matches Ruby's no_batches_aggregation_result
|
|
1352
|
+
* and Python's no_batches_aggregation_result.
|
|
1353
|
+
*
|
|
1354
|
+
* @param zeroMetrics - Metrics to return (typically zeros).
|
|
1355
|
+
* @returns Success result with workerCount=0 and scenario="no_batches".
|
|
1356
|
+
*
|
|
1357
|
+
* @example
|
|
1358
|
+
* ```typescript
|
|
1359
|
+
* return this.noBatchesAggregationResult({
|
|
1360
|
+
* totalProcessed: 0,
|
|
1361
|
+
* totalValue: 0.0,
|
|
1362
|
+
* });
|
|
1363
|
+
* ```
|
|
1364
|
+
*/
|
|
1365
|
+
noBatchesAggregationResult(zeroMetrics?: Record<string, unknown>): StepHandlerResult;
|
|
1366
|
+
/**
|
|
1367
|
+
* Aggregate batch worker results handling both scenarios.
|
|
1368
|
+
*
|
|
1369
|
+
* Cross-language standard: matches Ruby's aggregate_batch_worker_results
|
|
1370
|
+
* and Python's aggregate_batch_worker_results.
|
|
1371
|
+
* Handles both NoBatches and WithBatches scenarios automatically.
|
|
1372
|
+
*
|
|
1373
|
+
* For NoBatches, returns zeroMetrics with worker_count=0.
|
|
1374
|
+
* For WithBatches, calls aggregationFn with batchResults dict.
|
|
1375
|
+
*
|
|
1376
|
+
* @param scenario - BatchAggregationScenario from detectAggregationScenario().
|
|
1377
|
+
* @param zeroMetrics - Metrics to return for NoBatches scenario.
|
|
1378
|
+
* @param aggregationFn - Function to aggregate batch results. Receives dict of
|
|
1379
|
+
* worker_name -> result_dict, returns aggregated metrics dict.
|
|
1380
|
+
* @returns Success result with aggregated data and worker_count.
|
|
1381
|
+
*
|
|
1382
|
+
* @example
|
|
1383
|
+
* ```typescript
|
|
1384
|
+
* const scenario = this.detectAggregationScenario(
|
|
1385
|
+
* context.dependencyResults,
|
|
1386
|
+
* 'analyze_csv',
|
|
1387
|
+
* 'process_csv_batch_'
|
|
1388
|
+
* );
|
|
1389
|
+
*
|
|
1390
|
+
* return this.aggregateBatchWorkerResults(
|
|
1391
|
+
* scenario,
|
|
1392
|
+
* { totalProcessed: 0 },
|
|
1393
|
+
* (batchResults) => {
|
|
1394
|
+
* let total = 0;
|
|
1395
|
+
* for (const result of Object.values(batchResults)) {
|
|
1396
|
+
* total += (result.count as number) || 0;
|
|
1397
|
+
* }
|
|
1398
|
+
* return { totalProcessed: total };
|
|
1399
|
+
* }
|
|
1400
|
+
* );
|
|
1401
|
+
* ```
|
|
1402
|
+
*/
|
|
1403
|
+
aggregateBatchWorkerResults(scenario: BatchAggregationScenario, zeroMetrics?: Record<string, unknown>, aggregationFn?: (batchResults: Record<string, Record<string, unknown>>) => Record<string, unknown>): StepHandlerResult;
|
|
1404
|
+
/**
|
|
1405
|
+
* Aggregate results from multiple batch workers.
|
|
1406
|
+
*
|
|
1407
|
+
* Delegates to `aggregateBatchResults` from types/batch.ts (TAS-112/TAS-123).
|
|
1408
|
+
* Cross-language standard: matches Python's aggregate_batch_results.
|
|
1409
|
+
*
|
|
1410
|
+
* @param workerResults - Array of results from batch worker steps
|
|
1411
|
+
* @param maxErrors - Maximum number of errors to collect (default: 100)
|
|
1412
|
+
* @returns Aggregated summary of all batch processing
|
|
1413
|
+
*
|
|
1414
|
+
* @example
|
|
1415
|
+
* ```typescript
|
|
1416
|
+
* // In an aggregator handler
|
|
1417
|
+
* const workerResults = context.getAllDependencyResults('process_batch_');
|
|
1418
|
+
* const summary = BatchableMixin.aggregateWorkerResults(workerResults);
|
|
1419
|
+
* return this.success(summary);
|
|
1420
|
+
* ```
|
|
1421
|
+
*/
|
|
1422
|
+
static aggregateWorkerResults(workerResults: Array<Record<string, unknown> | null>, maxErrors?: number): BatchAggregationResult;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Helper function to apply Batchable methods to a handler class.
|
|
1426
|
+
*
|
|
1427
|
+
* This is a convenience for applying all Batchable methods at once.
|
|
1428
|
+
*
|
|
1429
|
+
* @example
|
|
1430
|
+
* ```typescript
|
|
1431
|
+
* class MyBatchHandler extends StepHandler {
|
|
1432
|
+
* constructor() {
|
|
1433
|
+
* super();
|
|
1434
|
+
* applyBatchable(this);
|
|
1435
|
+
* }
|
|
1436
|
+
* }
|
|
1437
|
+
* ```
|
|
1438
|
+
*/
|
|
1439
|
+
declare function applyBatchable<T extends object>(target: T): T & Batchable;
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Decision mixin for workflow routing.
|
|
1443
|
+
*
|
|
1444
|
+
* TAS-112: Composition Pattern - Decision Mixin
|
|
1445
|
+
*
|
|
1446
|
+
* This module provides the DecisionMixin class for step handlers that make
|
|
1447
|
+
* routing decisions. Use via interface implementation with method binding.
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* class RouteOrderHandler extends StepHandler implements DecisionCapable {
|
|
1452
|
+
* static handlerName = 'route_order';
|
|
1453
|
+
*
|
|
1454
|
+
* // Bind DecisionMixin methods
|
|
1455
|
+
* decisionSuccess = DecisionMixin.prototype.decisionSuccess.bind(this);
|
|
1456
|
+
* skipBranches = DecisionMixin.prototype.skipBranches.bind(this);
|
|
1457
|
+
* // ... other required methods
|
|
1458
|
+
*
|
|
1459
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1460
|
+
* const orderType = context.inputData['order_type'];
|
|
1461
|
+
* if (orderType === 'premium') {
|
|
1462
|
+
* return this.decisionSuccess(
|
|
1463
|
+
* ['validate_premium', 'process_premium'],
|
|
1464
|
+
* { order_type: orderType }
|
|
1465
|
+
* );
|
|
1466
|
+
* }
|
|
1467
|
+
* return this.decisionSuccess(['process_standard']);
|
|
1468
|
+
* }
|
|
1469
|
+
* }
|
|
1470
|
+
* ```
|
|
1471
|
+
*
|
|
1472
|
+
* @module handler/mixins/decision
|
|
1473
|
+
*/
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Type of decision point outcome.
|
|
1477
|
+
*/
|
|
1478
|
+
declare enum DecisionType {
|
|
1479
|
+
CREATE_STEPS = "create_steps",
|
|
1480
|
+
NO_BRANCHES = "no_branches"
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Outcome from a decision point handler.
|
|
1484
|
+
*
|
|
1485
|
+
* Decision handlers return this to indicate which branch(es) of a workflow
|
|
1486
|
+
* to execute.
|
|
1487
|
+
*/
|
|
1488
|
+
interface DecisionPointOutcome {
|
|
1489
|
+
/** Type of decision made */
|
|
1490
|
+
decisionType: DecisionType;
|
|
1491
|
+
/** Names of steps to execute next */
|
|
1492
|
+
nextStepNames: string[];
|
|
1493
|
+
/** Optional dynamically created steps */
|
|
1494
|
+
dynamicSteps?: Array<Record<string, unknown>>;
|
|
1495
|
+
/** Human-readable reason for the decision */
|
|
1496
|
+
reason?: string;
|
|
1497
|
+
/** Context data for routing */
|
|
1498
|
+
routingContext: Record<string, unknown>;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Interface for decision-capable handlers.
|
|
1502
|
+
*
|
|
1503
|
+
* Implement this interface and bind DecisionMixin methods to get decision functionality.
|
|
1504
|
+
*/
|
|
1505
|
+
interface DecisionCapable {
|
|
1506
|
+
/** Handler name (from StepHandler) */
|
|
1507
|
+
name: string;
|
|
1508
|
+
/** Handler version (from StepHandler) */
|
|
1509
|
+
version: string;
|
|
1510
|
+
decisionSuccess(steps: string[], routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1511
|
+
decisionSuccessWithOutcome(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1512
|
+
decisionNoBranches(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1513
|
+
skipBranches(reason: string, routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1514
|
+
decisionFailure(message: string, errorType?: string, retryable?: boolean, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Implementation of decision methods.
|
|
1518
|
+
*
|
|
1519
|
+
* TAS-112: Use via interface implementation with method binding.
|
|
1520
|
+
*
|
|
1521
|
+
* Decision handlers are used to make routing decisions in workflows.
|
|
1522
|
+
* They evaluate conditions and determine which steps should execute next.
|
|
1523
|
+
*/
|
|
1524
|
+
declare class DecisionMixin implements DecisionCapable {
|
|
1525
|
+
get name(): string;
|
|
1526
|
+
get version(): string;
|
|
1527
|
+
/**
|
|
1528
|
+
* Simplified decision success helper (cross-language standard API).
|
|
1529
|
+
*
|
|
1530
|
+
* Use this when routing to one or more steps based on a decision.
|
|
1531
|
+
* This is the recommended method for most decision handlers.
|
|
1532
|
+
*
|
|
1533
|
+
* @param steps - List of step names to activate
|
|
1534
|
+
* @param routingContext - Optional context for routing decisions
|
|
1535
|
+
* @param metadata - Optional additional metadata
|
|
1536
|
+
* @returns A success StepHandlerResult with the decision outcome
|
|
1537
|
+
*/
|
|
1538
|
+
decisionSuccess(steps: string[], routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1539
|
+
/**
|
|
1540
|
+
* Create a success result with a DecisionPointOutcome.
|
|
1541
|
+
*
|
|
1542
|
+
* Use this for complex decision outcomes that require dynamic steps
|
|
1543
|
+
* or advanced routing. For simple step routing, use `decisionSuccess()`.
|
|
1544
|
+
*/
|
|
1545
|
+
decisionSuccessWithOutcome(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1546
|
+
/**
|
|
1547
|
+
* Create a success result for a decision with no branches.
|
|
1548
|
+
*
|
|
1549
|
+
* Use this when the decision results in no additional steps being executed.
|
|
1550
|
+
* This is still a successful outcome - the decision was made correctly,
|
|
1551
|
+
* it just doesn't require any follow-up steps.
|
|
1552
|
+
*/
|
|
1553
|
+
decisionNoBranches(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1554
|
+
/**
|
|
1555
|
+
* Convenience method to skip all branches.
|
|
1556
|
+
*
|
|
1557
|
+
* @param reason - Human-readable reason for skipping branches
|
|
1558
|
+
* @param routingContext - Optional context data
|
|
1559
|
+
* @param metadata - Optional additional metadata
|
|
1560
|
+
* @returns A success StepHandlerResult indicating no branches
|
|
1561
|
+
*/
|
|
1562
|
+
skipBranches(reason: string, routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1563
|
+
/**
|
|
1564
|
+
* Create a failure result for a decision that could not be made.
|
|
1565
|
+
*
|
|
1566
|
+
* Use this when the handler cannot determine the appropriate routing,
|
|
1567
|
+
* typically due to invalid input data or missing required information.
|
|
1568
|
+
*
|
|
1569
|
+
* Decision failures are usually NOT retryable.
|
|
1570
|
+
*/
|
|
1571
|
+
decisionFailure(message: string, errorType?: string, retryable?: boolean, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Helper function to apply decision methods to a handler instance.
|
|
1575
|
+
*
|
|
1576
|
+
* @example
|
|
1577
|
+
* ```typescript
|
|
1578
|
+
* class MyDecisionHandler extends StepHandler {
|
|
1579
|
+
* constructor() {
|
|
1580
|
+
* super();
|
|
1581
|
+
* applyDecision(this);
|
|
1582
|
+
* }
|
|
1583
|
+
* }
|
|
1584
|
+
* ```
|
|
1585
|
+
*/
|
|
1586
|
+
declare function applyDecision<T extends {
|
|
1587
|
+
name: string;
|
|
1588
|
+
version: string;
|
|
1589
|
+
}>(target: T): T & DecisionCapable;
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Decision handler for workflow routing.
|
|
1593
|
+
*
|
|
1594
|
+
* TAS-112: Composition Pattern (DEPRECATED CLASS)
|
|
1595
|
+
*
|
|
1596
|
+
* This module provides the DecisionHandler class for backward compatibility.
|
|
1597
|
+
* For new code, use the mixin pattern:
|
|
1598
|
+
*
|
|
1599
|
+
* @example Using DecisionMixin
|
|
1600
|
+
* ```typescript
|
|
1601
|
+
* import { StepHandler } from './base';
|
|
1602
|
+
* import { DecisionMixin, DecisionCapable, applyDecision } from './mixins/decision';
|
|
1603
|
+
*
|
|
1604
|
+
* class RouteOrderHandler extends StepHandler implements DecisionCapable {
|
|
1605
|
+
* static handlerName = 'route_order';
|
|
1606
|
+
*
|
|
1607
|
+
* constructor() {
|
|
1608
|
+
* super();
|
|
1609
|
+
* applyDecision(this);
|
|
1610
|
+
* }
|
|
1611
|
+
*
|
|
1612
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1613
|
+
* const orderType = context.inputData['order_type'];
|
|
1614
|
+
* if (orderType === 'premium') {
|
|
1615
|
+
* return this.decisionSuccess(
|
|
1616
|
+
* ['validate_premium', 'process_premium'],
|
|
1617
|
+
* { order_type: orderType }
|
|
1618
|
+
* );
|
|
1619
|
+
* }
|
|
1620
|
+
* return this.decisionSuccess(['process_standard']);
|
|
1621
|
+
* }
|
|
1622
|
+
* }
|
|
1623
|
+
* ```
|
|
1624
|
+
*
|
|
1625
|
+
* @module handler/decision
|
|
1626
|
+
*/
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Base class for decision point step handlers.
|
|
1630
|
+
*
|
|
1631
|
+
* TAS-112: This class is provided for backward compatibility.
|
|
1632
|
+
* For new code, prefer using DecisionMixin directly with applyDecision().
|
|
1633
|
+
*
|
|
1634
|
+
* Decision handlers are used to make routing decisions in workflows.
|
|
1635
|
+
* They evaluate conditions and determine which steps should execute next.
|
|
1636
|
+
*
|
|
1637
|
+
* @example
|
|
1638
|
+
* ```typescript
|
|
1639
|
+
* class CustomerTierRouter extends DecisionHandler {
|
|
1640
|
+
* static handlerName = 'route_by_tier';
|
|
1641
|
+
*
|
|
1642
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
1643
|
+
* const tier = context.inputData['customer_tier'];
|
|
1644
|
+
* if (tier === 'enterprise') {
|
|
1645
|
+
* return this.decisionSuccess(
|
|
1646
|
+
* ['enterprise_validation', 'enterprise_processing'],
|
|
1647
|
+
* { tier }
|
|
1648
|
+
* );
|
|
1649
|
+
* } else if (tier === 'premium') {
|
|
1650
|
+
* return this.decisionSuccess(['premium_processing']);
|
|
1651
|
+
* } else {
|
|
1652
|
+
* return this.decisionSuccess(['standard_processing']);
|
|
1653
|
+
* }
|
|
1654
|
+
* }
|
|
1655
|
+
* }
|
|
1656
|
+
* ```
|
|
1657
|
+
*/
|
|
1658
|
+
declare abstract class DecisionHandler extends StepHandler {
|
|
1659
|
+
private readonly _decisionMixin;
|
|
1660
|
+
get capabilities(): string[];
|
|
1661
|
+
/**
|
|
1662
|
+
* Simplified decision success helper (cross-language standard API).
|
|
1663
|
+
*
|
|
1664
|
+
* Use this when routing to one or more steps based on a decision.
|
|
1665
|
+
* This is the recommended method for most decision handlers.
|
|
1666
|
+
*/
|
|
1667
|
+
protected decisionSuccess(steps: string[], routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1668
|
+
/**
|
|
1669
|
+
* Create a success result with a DecisionPointOutcome.
|
|
1670
|
+
*
|
|
1671
|
+
* Use this for complex decision outcomes that require dynamic steps
|
|
1672
|
+
* or advanced routing. For simple step routing, use `decisionSuccess()`.
|
|
1673
|
+
*/
|
|
1674
|
+
protected decisionSuccessWithOutcome(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1675
|
+
/**
|
|
1676
|
+
* Create a success result for a decision with no branches.
|
|
1677
|
+
*
|
|
1678
|
+
* Use this when the decision results in no additional steps being executed.
|
|
1679
|
+
*/
|
|
1680
|
+
protected decisionNoBranches(outcome: DecisionPointOutcome, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1681
|
+
/**
|
|
1682
|
+
* Convenience method to skip all branches.
|
|
1683
|
+
*/
|
|
1684
|
+
protected skipBranches(reason: string, routingContext?: Record<string, unknown>, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1685
|
+
/**
|
|
1686
|
+
* Create a failure result for a decision that could not be made.
|
|
1687
|
+
*/
|
|
1688
|
+
protected decisionFailure(message: string, errorType?: string, retryable?: boolean, metadata?: Record<string, unknown>): StepHandlerResult;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/**
|
|
1692
|
+
* Domain Events Module for TypeScript Workers
|
|
1693
|
+
*
|
|
1694
|
+
* Provides infrastructure for custom domain event publishers and subscribers.
|
|
1695
|
+
* Publishers transform step results into business events; subscribers handle
|
|
1696
|
+
* fast (in-process) events for internal processing.
|
|
1697
|
+
*
|
|
1698
|
+
* Architecture: Handlers DON'T publish events directly. Events are declared
|
|
1699
|
+
* in YAML templates, and Rust orchestration publishes them after step completion.
|
|
1700
|
+
* Custom publishers only transform payloads. Subscribers only receive fast events.
|
|
1701
|
+
*
|
|
1702
|
+
* @module handler/domain-events
|
|
1703
|
+
* @see docs/architecture/domain-events.md
|
|
1704
|
+
* @see TAS-122, TAS-112 Phase 7
|
|
1705
|
+
*/
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* Context passed to publishers for event transformation.
|
|
1709
|
+
*
|
|
1710
|
+
* Contains all information needed to build a business event payload.
|
|
1711
|
+
*/
|
|
1712
|
+
interface StepEventContext {
|
|
1713
|
+
/** UUID of the task */
|
|
1714
|
+
readonly taskUuid: string;
|
|
1715
|
+
/** UUID of the step */
|
|
1716
|
+
readonly stepUuid: string;
|
|
1717
|
+
/** Name of the step handler */
|
|
1718
|
+
readonly stepName: string;
|
|
1719
|
+
/** Namespace (e.g., "payments", "inventory") */
|
|
1720
|
+
readonly namespace: string;
|
|
1721
|
+
/** Correlation ID for distributed tracing */
|
|
1722
|
+
readonly correlationId: string;
|
|
1723
|
+
/** Step result data (success payload or error info) */
|
|
1724
|
+
readonly result?: Record<string, unknown>;
|
|
1725
|
+
/** Execution metadata */
|
|
1726
|
+
readonly metadata: Record<string, unknown>;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Event declaration from YAML task template.
|
|
1730
|
+
*/
|
|
1731
|
+
interface EventDeclaration {
|
|
1732
|
+
/** Event name (e.g., "payment.processed") */
|
|
1733
|
+
readonly name: string;
|
|
1734
|
+
/** Publication condition: "success" | "failure" | "always" */
|
|
1735
|
+
readonly condition?: 'success' | 'failure' | 'retryable_failure' | 'permanent_failure' | 'always';
|
|
1736
|
+
/** Delivery mode: "durable" (PGMQ) | "fast" (in-process) | "broadcast" (both) */
|
|
1737
|
+
readonly deliveryMode?: 'durable' | 'fast' | 'broadcast';
|
|
1738
|
+
/** Custom publisher name (optional) */
|
|
1739
|
+
readonly publisher?: string;
|
|
1740
|
+
/** JSON schema for payload validation (optional) */
|
|
1741
|
+
readonly schema?: Record<string, unknown>;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Step result passed to publishers.
|
|
1745
|
+
*/
|
|
1746
|
+
interface StepResult {
|
|
1747
|
+
/** Whether the step succeeded */
|
|
1748
|
+
readonly success: boolean;
|
|
1749
|
+
/** Step handler's return value */
|
|
1750
|
+
readonly result?: Record<string, unknown>;
|
|
1751
|
+
/** Execution metadata */
|
|
1752
|
+
readonly metadata?: Record<string, unknown>;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Domain event structure for subscribers.
|
|
1756
|
+
*
|
|
1757
|
+
* This is the shape of events received by BaseSubscriber.handle().
|
|
1758
|
+
*/
|
|
1759
|
+
interface DomainEvent {
|
|
1760
|
+
/** Unique event ID (UUID v7) */
|
|
1761
|
+
readonly eventId: string;
|
|
1762
|
+
/** Event name (e.g., "payment.processed") */
|
|
1763
|
+
readonly eventName: string;
|
|
1764
|
+
/** Business payload from publisher transformation */
|
|
1765
|
+
readonly payload: Record<string, unknown>;
|
|
1766
|
+
/** Event metadata */
|
|
1767
|
+
readonly metadata: DomainEventMetadata;
|
|
1768
|
+
/** Original step execution result */
|
|
1769
|
+
readonly executionResult: StepResult;
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Metadata attached to every domain event.
|
|
1773
|
+
*/
|
|
1774
|
+
interface DomainEventMetadata {
|
|
1775
|
+
/** Task UUID */
|
|
1776
|
+
readonly taskUuid: string;
|
|
1777
|
+
/** Step UUID */
|
|
1778
|
+
readonly stepUuid: string;
|
|
1779
|
+
/** Step name */
|
|
1780
|
+
readonly stepName?: string;
|
|
1781
|
+
/** Namespace */
|
|
1782
|
+
readonly namespace: string;
|
|
1783
|
+
/** Correlation ID for tracing */
|
|
1784
|
+
readonly correlationId: string;
|
|
1785
|
+
/** When the event was fired (ISO8601) */
|
|
1786
|
+
readonly firedAt: string;
|
|
1787
|
+
/** Publisher that created this event */
|
|
1788
|
+
readonly publisher?: string;
|
|
1789
|
+
/** Additional custom metadata */
|
|
1790
|
+
readonly [key: string]: unknown;
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Context passed to the publish() method.
|
|
1794
|
+
*
|
|
1795
|
+
* Cross-language standard API (TAS-96).
|
|
1796
|
+
*/
|
|
1797
|
+
interface PublishContext {
|
|
1798
|
+
/** Event name */
|
|
1799
|
+
readonly eventName: string;
|
|
1800
|
+
/** Step result */
|
|
1801
|
+
readonly stepResult: StepResult;
|
|
1802
|
+
/** Event declaration from YAML */
|
|
1803
|
+
readonly eventDeclaration?: EventDeclaration;
|
|
1804
|
+
/** Step execution context */
|
|
1805
|
+
readonly stepContext?: StepEventContext;
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Abstract base class for custom domain event publishers.
|
|
1809
|
+
*
|
|
1810
|
+
* Publishers transform step execution results into business events.
|
|
1811
|
+
* They are declared in YAML via the `publisher:` field and registered
|
|
1812
|
+
* at bootstrap time.
|
|
1813
|
+
*
|
|
1814
|
+
* NOTE: Publishers don't call publish APIs directly. They transform data
|
|
1815
|
+
* that Rust orchestration publishes.
|
|
1816
|
+
*
|
|
1817
|
+
* @example
|
|
1818
|
+
* ```typescript
|
|
1819
|
+
* class PaymentEventPublisher extends BasePublisher {
|
|
1820
|
+
* readonly publisherName = 'PaymentEventPublisher';
|
|
1821
|
+
*
|
|
1822
|
+
* transformPayload(stepResult: StepResult, eventDecl?: EventDeclaration): Record<string, unknown> {
|
|
1823
|
+
* const result = stepResult.result ?? {};
|
|
1824
|
+
* return {
|
|
1825
|
+
* transactionId: result.transaction_id,
|
|
1826
|
+
* amount: result.amount,
|
|
1827
|
+
* currency: result.currency ?? 'USD',
|
|
1828
|
+
* status: stepResult.success ? 'succeeded' : 'failed',
|
|
1829
|
+
* };
|
|
1830
|
+
* }
|
|
1831
|
+
*
|
|
1832
|
+
* shouldPublish(stepResult: StepResult): boolean {
|
|
1833
|
+
* return stepResult.success && stepResult.result?.transaction_id != null;
|
|
1834
|
+
* }
|
|
1835
|
+
* }
|
|
1836
|
+
* ```
|
|
1837
|
+
*/
|
|
1838
|
+
declare abstract class BasePublisher {
|
|
1839
|
+
protected readonly logger: Logger;
|
|
1840
|
+
/**
|
|
1841
|
+
* Publisher name for registry lookup.
|
|
1842
|
+
* Must match the `publisher:` field in YAML.
|
|
1843
|
+
*/
|
|
1844
|
+
abstract readonly publisherName: string;
|
|
1845
|
+
/**
|
|
1846
|
+
* Transform step result into business event payload.
|
|
1847
|
+
*
|
|
1848
|
+
* Override to customize the event payload for your domain.
|
|
1849
|
+
* Default returns the step result as-is.
|
|
1850
|
+
*
|
|
1851
|
+
* @param stepResult - The step execution result
|
|
1852
|
+
* @param eventDeclaration - The event declaration from YAML
|
|
1853
|
+
* @param stepContext - The step execution context
|
|
1854
|
+
* @returns Business event payload to publish
|
|
1855
|
+
*/
|
|
1856
|
+
transformPayload(stepResult: StepResult, _eventDeclaration?: EventDeclaration, _stepContext?: StepEventContext): Record<string, unknown>;
|
|
1857
|
+
/**
|
|
1858
|
+
* Determine if this event should be published.
|
|
1859
|
+
*
|
|
1860
|
+
* Override to add custom conditions beyond YAML's `condition:` field.
|
|
1861
|
+
* The YAML condition is evaluated first by Rust orchestration.
|
|
1862
|
+
*
|
|
1863
|
+
* @param stepResult - The step execution result
|
|
1864
|
+
* @param eventDeclaration - The event declaration from YAML
|
|
1865
|
+
* @param stepContext - The step execution context
|
|
1866
|
+
* @returns true if the event should be published
|
|
1867
|
+
*/
|
|
1868
|
+
shouldPublish(_stepResult: StepResult, _eventDeclaration?: EventDeclaration, _stepContext?: StepEventContext): boolean;
|
|
1869
|
+
/**
|
|
1870
|
+
* Add additional metadata to the event.
|
|
1871
|
+
*
|
|
1872
|
+
* Override to add custom metadata fields.
|
|
1873
|
+
*
|
|
1874
|
+
* @param stepResult - The step execution result
|
|
1875
|
+
* @param eventDeclaration - The event declaration from YAML
|
|
1876
|
+
* @param stepContext - The step execution context
|
|
1877
|
+
* @returns Additional metadata to merge into event metadata
|
|
1878
|
+
*/
|
|
1879
|
+
additionalMetadata(_stepResult: StepResult, _eventDeclaration?: EventDeclaration, _stepContext?: StepEventContext): Record<string, unknown>;
|
|
1880
|
+
/**
|
|
1881
|
+
* Hook called before publishing.
|
|
1882
|
+
*
|
|
1883
|
+
* Override for pre-publish validation, logging, or metrics.
|
|
1884
|
+
* Return false to prevent publishing.
|
|
1885
|
+
*
|
|
1886
|
+
* @param eventName - The event name
|
|
1887
|
+
* @param payload - The transformed payload
|
|
1888
|
+
* @param metadata - The event metadata
|
|
1889
|
+
* @returns true to continue, false to skip publishing
|
|
1890
|
+
*/
|
|
1891
|
+
beforePublish(eventName: string, _payload: Record<string, unknown>, _metadata: Record<string, unknown>): boolean;
|
|
1892
|
+
/**
|
|
1893
|
+
* Hook called after successful publishing.
|
|
1894
|
+
*
|
|
1895
|
+
* Override for post-publish logging, metrics, or cleanup.
|
|
1896
|
+
*
|
|
1897
|
+
* @param eventName - The event name
|
|
1898
|
+
* @param payload - The transformed payload
|
|
1899
|
+
* @param metadata - The event metadata
|
|
1900
|
+
*/
|
|
1901
|
+
afterPublish(eventName: string, _payload: Record<string, unknown>, _metadata: Record<string, unknown>): void;
|
|
1902
|
+
/**
|
|
1903
|
+
* Hook called if publishing fails.
|
|
1904
|
+
*
|
|
1905
|
+
* Override for error handling, logging, or fallback behavior.
|
|
1906
|
+
*
|
|
1907
|
+
* @param eventName - The event name
|
|
1908
|
+
* @param error - The error that occurred
|
|
1909
|
+
* @param payload - The transformed payload
|
|
1910
|
+
*/
|
|
1911
|
+
onPublishError(eventName: string, error: Error, _payload: Record<string, unknown>): void;
|
|
1912
|
+
/**
|
|
1913
|
+
* Cross-language standard: Publish an event with unified context.
|
|
1914
|
+
*
|
|
1915
|
+
* Coordinates the lifecycle hooks into a single publish call.
|
|
1916
|
+
*
|
|
1917
|
+
* @param ctx - Publish context containing all required data
|
|
1918
|
+
* @returns true if event was published, false if skipped
|
|
1919
|
+
*/
|
|
1920
|
+
publish(ctx: PublishContext): boolean;
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Default publisher that passes step result through unchanged.
|
|
1924
|
+
*/
|
|
1925
|
+
declare class DefaultPublisher extends BasePublisher {
|
|
1926
|
+
readonly publisherName = "default";
|
|
1927
|
+
transformPayload(stepResult: StepResult): Record<string, unknown>;
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Static interface for subscriber classes (for type checking).
|
|
1931
|
+
*/
|
|
1932
|
+
interface SubscriberClass {
|
|
1933
|
+
/** Patterns to subscribe to */
|
|
1934
|
+
subscribesTo(): string[];
|
|
1935
|
+
/** Constructor */
|
|
1936
|
+
new (): BaseSubscriber;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Abstract base class for domain event subscribers.
|
|
1940
|
+
*
|
|
1941
|
+
* Subscribers handle fast (in-process) domain events for internal processing.
|
|
1942
|
+
* They subscribe to event patterns and receive events via the InProcessEventBus.
|
|
1943
|
+
*
|
|
1944
|
+
* @example
|
|
1945
|
+
* ```typescript
|
|
1946
|
+
* class MetricsSubscriber extends BaseSubscriber {
|
|
1947
|
+
* static subscribesTo(): string[] {
|
|
1948
|
+
* return ['*']; // All events
|
|
1949
|
+
* }
|
|
1950
|
+
*
|
|
1951
|
+
* handle(event: DomainEvent): void {
|
|
1952
|
+
* metrics.increment(`domain_events.${event.eventName.replace('.', '_')}`);
|
|
1953
|
+
* }
|
|
1954
|
+
* }
|
|
1955
|
+
*
|
|
1956
|
+
* class PaymentSubscriber extends BaseSubscriber {
|
|
1957
|
+
* static subscribesTo(): string[] {
|
|
1958
|
+
* return ['payment.*']; // Only payment events
|
|
1959
|
+
* }
|
|
1960
|
+
*
|
|
1961
|
+
* handle(event: DomainEvent): void {
|
|
1962
|
+
* if (event.eventName === 'payment.processed') {
|
|
1963
|
+
* notifyAccounting(event.payload);
|
|
1964
|
+
* }
|
|
1965
|
+
* }
|
|
1966
|
+
* }
|
|
1967
|
+
* ```
|
|
1968
|
+
*/
|
|
1969
|
+
declare abstract class BaseSubscriber {
|
|
1970
|
+
protected readonly logger: Logger;
|
|
1971
|
+
private _active;
|
|
1972
|
+
private _subscriptions;
|
|
1973
|
+
/**
|
|
1974
|
+
* Event patterns to subscribe to.
|
|
1975
|
+
*
|
|
1976
|
+
* Override this static method to declare patterns.
|
|
1977
|
+
* Supports wildcards: "*" matches all, "payment.*" matches payment.processed, etc.
|
|
1978
|
+
*
|
|
1979
|
+
* @returns Array of event patterns
|
|
1980
|
+
*/
|
|
1981
|
+
static subscribesTo(): string[];
|
|
1982
|
+
/**
|
|
1983
|
+
* Check if the subscriber is active.
|
|
1984
|
+
*/
|
|
1985
|
+
get active(): boolean;
|
|
1986
|
+
/**
|
|
1987
|
+
* Get subscribed patterns.
|
|
1988
|
+
*/
|
|
1989
|
+
get subscriptions(): readonly string[];
|
|
1990
|
+
/**
|
|
1991
|
+
* Handle a domain event.
|
|
1992
|
+
*
|
|
1993
|
+
* Subclasses MUST implement this method.
|
|
1994
|
+
*
|
|
1995
|
+
* @param event - The domain event to handle
|
|
1996
|
+
*/
|
|
1997
|
+
abstract handle(event: DomainEvent): void | Promise<void>;
|
|
1998
|
+
/**
|
|
1999
|
+
* Hook called before handling an event.
|
|
2000
|
+
*
|
|
2001
|
+
* Override for pre-processing, validation, or filtering.
|
|
2002
|
+
* Return false to skip handling this event.
|
|
2003
|
+
*
|
|
2004
|
+
* @param event - The domain event
|
|
2005
|
+
* @returns true to continue handling, false to skip
|
|
2006
|
+
*/
|
|
2007
|
+
beforeHandle(_event: DomainEvent): boolean;
|
|
2008
|
+
/**
|
|
2009
|
+
* Hook called after successful handling.
|
|
2010
|
+
*
|
|
2011
|
+
* Override for post-processing, metrics, or cleanup.
|
|
2012
|
+
*
|
|
2013
|
+
* @param event - The domain event
|
|
2014
|
+
*/
|
|
2015
|
+
afterHandle(_event: DomainEvent): void;
|
|
2016
|
+
/**
|
|
2017
|
+
* Hook called if handling fails.
|
|
2018
|
+
*
|
|
2019
|
+
* Override for custom error handling, alerts, or retry logic.
|
|
2020
|
+
* Note: Domain events use fire-and-forget semantics.
|
|
2021
|
+
*
|
|
2022
|
+
* @param event - The domain event
|
|
2023
|
+
* @param error - The error that occurred
|
|
2024
|
+
*/
|
|
2025
|
+
onHandleError(event: DomainEvent, error: Error): void;
|
|
2026
|
+
/**
|
|
2027
|
+
* Check if this subscriber matches an event name.
|
|
2028
|
+
*
|
|
2029
|
+
* Supports wildcard patterns:
|
|
2030
|
+
* - "*" matches everything
|
|
2031
|
+
* - "payment.*" matches "payment.processed", "payment.failed"
|
|
2032
|
+
* - "order.created" matches exactly "order.created"
|
|
2033
|
+
*
|
|
2034
|
+
* @param eventName - The event name to check
|
|
2035
|
+
* @returns true if this subscriber should handle the event
|
|
2036
|
+
*/
|
|
2037
|
+
matches(eventName: string): boolean;
|
|
2038
|
+
/**
|
|
2039
|
+
* Start listening for events.
|
|
2040
|
+
*
|
|
2041
|
+
* Registers with the InProcessDomainEventPoller.
|
|
2042
|
+
*
|
|
2043
|
+
* @param poller - The event poller to register with
|
|
2044
|
+
*/
|
|
2045
|
+
start(poller: InProcessDomainEventPoller): void;
|
|
2046
|
+
/**
|
|
2047
|
+
* Stop listening for events.
|
|
2048
|
+
*/
|
|
2049
|
+
stop(): void;
|
|
2050
|
+
/**
|
|
2051
|
+
* Safely handle an event with error capture.
|
|
2052
|
+
*/
|
|
2053
|
+
private handleEventSafely;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Error thrown when a required publisher is not registered.
|
|
2057
|
+
*/
|
|
2058
|
+
declare class PublisherNotFoundError extends Error {
|
|
2059
|
+
readonly publisherName: string;
|
|
2060
|
+
readonly registeredPublishers: string[];
|
|
2061
|
+
constructor(name: string, registered: string[]);
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Error thrown when required publishers are missing during validation.
|
|
2065
|
+
*/
|
|
2066
|
+
declare class PublisherValidationError extends Error {
|
|
2067
|
+
readonly missingPublishers: string[];
|
|
2068
|
+
readonly registeredPublishers: string[];
|
|
2069
|
+
constructor(missing: string[], registered: string[]);
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Error thrown when modifying a frozen registry.
|
|
2073
|
+
*/
|
|
2074
|
+
declare class RegistryFrozenError extends Error {
|
|
2075
|
+
constructor();
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Error thrown when registering a duplicate publisher.
|
|
2079
|
+
*/
|
|
2080
|
+
declare class DuplicatePublisherError extends Error {
|
|
2081
|
+
readonly publisherName: string;
|
|
2082
|
+
constructor(name: string);
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Registry for custom domain event publishers.
|
|
2086
|
+
*
|
|
2087
|
+
* Maps publisher names (from YAML configuration) to their implementations.
|
|
2088
|
+
* Publishers are registered at bootstrap time and validated against task templates.
|
|
2089
|
+
*
|
|
2090
|
+
* @example
|
|
2091
|
+
* ```typescript
|
|
2092
|
+
* const registry = PublisherRegistry.instance;
|
|
2093
|
+
*
|
|
2094
|
+
* // Register custom publishers
|
|
2095
|
+
* registry.register(new PaymentEventPublisher());
|
|
2096
|
+
* registry.register(new InventoryEventPublisher());
|
|
2097
|
+
*
|
|
2098
|
+
* // Validate against templates
|
|
2099
|
+
* registry.validateRequired(['PaymentEventPublisher', 'InventoryEventPublisher']);
|
|
2100
|
+
*
|
|
2101
|
+
* // Look up publishers
|
|
2102
|
+
* const publisher = registry.get('PaymentEventPublisher');
|
|
2103
|
+
* ```
|
|
2104
|
+
*/
|
|
2105
|
+
declare class PublisherRegistry {
|
|
2106
|
+
private static _instance;
|
|
2107
|
+
private readonly publishers;
|
|
2108
|
+
private readonly defaultPublisher;
|
|
2109
|
+
private frozen;
|
|
2110
|
+
private readonly logger;
|
|
2111
|
+
private constructor();
|
|
2112
|
+
/**
|
|
2113
|
+
* Get the singleton instance.
|
|
2114
|
+
*/
|
|
2115
|
+
static get instance(): PublisherRegistry;
|
|
2116
|
+
/**
|
|
2117
|
+
* Register a custom publisher.
|
|
2118
|
+
*
|
|
2119
|
+
* @param publisher - The publisher instance to register
|
|
2120
|
+
* @throws RegistryFrozenError if registry is frozen
|
|
2121
|
+
* @throws DuplicatePublisherError if name is already registered
|
|
2122
|
+
*/
|
|
2123
|
+
register(publisher: BasePublisher): void;
|
|
2124
|
+
/**
|
|
2125
|
+
* Get a publisher by name.
|
|
2126
|
+
*
|
|
2127
|
+
* @param name - The publisher name
|
|
2128
|
+
* @returns The publisher, or undefined if not found
|
|
2129
|
+
*/
|
|
2130
|
+
get(name: string): BasePublisher | undefined;
|
|
2131
|
+
/**
|
|
2132
|
+
* Get a publisher by name, or return the default.
|
|
2133
|
+
*
|
|
2134
|
+
* @param name - The publisher name (optional)
|
|
2135
|
+
* @returns The publisher or default
|
|
2136
|
+
*/
|
|
2137
|
+
getOrDefault(name?: string): BasePublisher;
|
|
2138
|
+
/**
|
|
2139
|
+
* Get a publisher by name (strict mode).
|
|
2140
|
+
*
|
|
2141
|
+
* @param name - The publisher name
|
|
2142
|
+
* @returns The publisher
|
|
2143
|
+
* @throws PublisherNotFoundError if not registered
|
|
2144
|
+
*/
|
|
2145
|
+
getStrict(name: string): BasePublisher;
|
|
2146
|
+
/**
|
|
2147
|
+
* Check if a publisher is registered.
|
|
2148
|
+
*/
|
|
2149
|
+
isRegistered(name: string): boolean;
|
|
2150
|
+
/**
|
|
2151
|
+
* Get all registered publisher names.
|
|
2152
|
+
*/
|
|
2153
|
+
get registeredNames(): string[];
|
|
2154
|
+
/**
|
|
2155
|
+
* Get count of registered publishers.
|
|
2156
|
+
*/
|
|
2157
|
+
get count(): number;
|
|
2158
|
+
/**
|
|
2159
|
+
* Check if registry is empty.
|
|
2160
|
+
*/
|
|
2161
|
+
get isEmpty(): boolean;
|
|
2162
|
+
/**
|
|
2163
|
+
* Check if registry is frozen.
|
|
2164
|
+
*/
|
|
2165
|
+
get isFrozen(): boolean;
|
|
2166
|
+
/**
|
|
2167
|
+
* Unregister a publisher.
|
|
2168
|
+
*
|
|
2169
|
+
* @throws RegistryFrozenError if registry is frozen
|
|
2170
|
+
*/
|
|
2171
|
+
unregister(name: string): boolean;
|
|
2172
|
+
/**
|
|
2173
|
+
* Clear all registered publishers.
|
|
2174
|
+
*
|
|
2175
|
+
* @throws RegistryFrozenError if registry is frozen
|
|
2176
|
+
*/
|
|
2177
|
+
clear(): void;
|
|
2178
|
+
/**
|
|
2179
|
+
* Validate that all required publishers are registered.
|
|
2180
|
+
*
|
|
2181
|
+
* After validation, the registry is frozen.
|
|
2182
|
+
*
|
|
2183
|
+
* @param requiredPublishers - Publisher names from YAML configs
|
|
2184
|
+
* @throws PublisherValidationError if some publishers are missing
|
|
2185
|
+
*/
|
|
2186
|
+
validateRequired(requiredPublishers: string[]): void;
|
|
2187
|
+
/**
|
|
2188
|
+
* Freeze the registry to prevent further changes.
|
|
2189
|
+
*/
|
|
2190
|
+
freeze(): void;
|
|
2191
|
+
/**
|
|
2192
|
+
* Reset the registry (for testing).
|
|
2193
|
+
*/
|
|
2194
|
+
reset(): void;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Subscriber statistics.
|
|
2198
|
+
*/
|
|
2199
|
+
interface SubscriberStats {
|
|
2200
|
+
/** Whether subscribers have been started */
|
|
2201
|
+
readonly started: boolean;
|
|
2202
|
+
/** Total subscriber count */
|
|
2203
|
+
readonly subscriberCount: number;
|
|
2204
|
+
/** Number of active subscribers */
|
|
2205
|
+
readonly activeCount: number;
|
|
2206
|
+
/** Individual subscriber info */
|
|
2207
|
+
readonly subscribers: ReadonlyArray<{
|
|
2208
|
+
readonly className: string;
|
|
2209
|
+
readonly active: boolean;
|
|
2210
|
+
readonly patterns: readonly string[];
|
|
2211
|
+
}>;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Registry for domain event subscribers.
|
|
2215
|
+
*
|
|
2216
|
+
* Manages the lifecycle of subscribers. Subscribers are registered at bootstrap
|
|
2217
|
+
* time and started/stopped together with the worker.
|
|
2218
|
+
*
|
|
2219
|
+
* @example
|
|
2220
|
+
* ```typescript
|
|
2221
|
+
* const registry = SubscriberRegistry.instance;
|
|
2222
|
+
*
|
|
2223
|
+
* // Register subscriber classes (instantiation deferred)
|
|
2224
|
+
* registry.register(PaymentSubscriber);
|
|
2225
|
+
* registry.register(MetricsSubscriber);
|
|
2226
|
+
*
|
|
2227
|
+
* // Start all subscribers with the poller
|
|
2228
|
+
* registry.startAll(poller);
|
|
2229
|
+
*
|
|
2230
|
+
* // Stop all when shutting down
|
|
2231
|
+
* registry.stopAll();
|
|
2232
|
+
* ```
|
|
2233
|
+
*/
|
|
2234
|
+
declare class SubscriberRegistry {
|
|
2235
|
+
private static _instance;
|
|
2236
|
+
private readonly subscriberClasses;
|
|
2237
|
+
private readonly subscribers;
|
|
2238
|
+
private started;
|
|
2239
|
+
private readonly logger;
|
|
2240
|
+
private constructor();
|
|
2241
|
+
/**
|
|
2242
|
+
* Get the singleton instance.
|
|
2243
|
+
*/
|
|
2244
|
+
static get instance(): SubscriberRegistry;
|
|
2245
|
+
/**
|
|
2246
|
+
* Register a subscriber class.
|
|
2247
|
+
*
|
|
2248
|
+
* The class will be instantiated when startAll() is called.
|
|
2249
|
+
*
|
|
2250
|
+
* @param subscriberClass - A subclass of BaseSubscriber
|
|
2251
|
+
*/
|
|
2252
|
+
register(subscriberClass: SubscriberClass): void;
|
|
2253
|
+
/**
|
|
2254
|
+
* Register a subscriber instance directly.
|
|
2255
|
+
*
|
|
2256
|
+
* Use this when your subscriber needs custom initialization.
|
|
2257
|
+
*
|
|
2258
|
+
* @param subscriber - A subscriber instance
|
|
2259
|
+
*/
|
|
2260
|
+
registerInstance(subscriber: BaseSubscriber): void;
|
|
2261
|
+
/**
|
|
2262
|
+
* Start all registered subscribers.
|
|
2263
|
+
*
|
|
2264
|
+
* @param poller - The event poller to register subscribers with
|
|
2265
|
+
*/
|
|
2266
|
+
startAll(poller: InProcessDomainEventPoller): void;
|
|
2267
|
+
/**
|
|
2268
|
+
* Stop all subscribers.
|
|
2269
|
+
*/
|
|
2270
|
+
stopAll(): void;
|
|
2271
|
+
/**
|
|
2272
|
+
* Check if subscribers have been started.
|
|
2273
|
+
*/
|
|
2274
|
+
get isStarted(): boolean;
|
|
2275
|
+
/**
|
|
2276
|
+
* Get count of registered subscribers (classes + instances).
|
|
2277
|
+
*/
|
|
2278
|
+
get count(): number;
|
|
2279
|
+
/**
|
|
2280
|
+
* Get subscriber statistics.
|
|
2281
|
+
*/
|
|
2282
|
+
get stats(): SubscriberStats;
|
|
2283
|
+
/**
|
|
2284
|
+
* Reset the registry (for testing).
|
|
2285
|
+
*/
|
|
2286
|
+
reset(): void;
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Callback type for domain event handlers.
|
|
2290
|
+
*/
|
|
2291
|
+
type DomainEventCallback = (event: DomainEvent) => void | Promise<void>;
|
|
2292
|
+
/**
|
|
2293
|
+
* Error callback type for domain event processing.
|
|
2294
|
+
*/
|
|
2295
|
+
type DomainEventErrorCallback = (error: Error) => void;
|
|
2296
|
+
/**
|
|
2297
|
+
* Poller statistics.
|
|
2298
|
+
*/
|
|
2299
|
+
interface PollerStats {
|
|
2300
|
+
/** Total poll cycles */
|
|
2301
|
+
readonly pollCount: number;
|
|
2302
|
+
/** Total events processed */
|
|
2303
|
+
readonly eventsProcessed: number;
|
|
2304
|
+
/** Events that lagged (couldn't keep up) */
|
|
2305
|
+
readonly eventsLagged: number;
|
|
2306
|
+
/** Whether the poller is running */
|
|
2307
|
+
readonly running: boolean;
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Poller configuration.
|
|
2311
|
+
*/
|
|
2312
|
+
interface DomainEventPollerConfig {
|
|
2313
|
+
/** Polling interval in milliseconds (default: 10) */
|
|
2314
|
+
readonly pollIntervalMs?: number;
|
|
2315
|
+
/** Maximum events per poll cycle (default: 100) */
|
|
2316
|
+
readonly maxEventsPerPoll?: number;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* In-process domain event poller.
|
|
2320
|
+
*
|
|
2321
|
+
* Polls for fast domain events from the Rust broadcast channel and
|
|
2322
|
+
* dispatches them to registered subscribers.
|
|
2323
|
+
*
|
|
2324
|
+
* Threading Model:
|
|
2325
|
+
* - Main Thread: TypeScript application, event handlers
|
|
2326
|
+
* - Poll Loop: Async timer-based polling
|
|
2327
|
+
* - Rust: Rust worker runtime (separate from TypeScript)
|
|
2328
|
+
*
|
|
2329
|
+
* @example
|
|
2330
|
+
* ```typescript
|
|
2331
|
+
* const poller = new InProcessDomainEventPoller({
|
|
2332
|
+
* pollIntervalMs: 10,
|
|
2333
|
+
* maxEventsPerPoll: 100,
|
|
2334
|
+
* });
|
|
2335
|
+
*
|
|
2336
|
+
* // Subscribe to patterns
|
|
2337
|
+
* poller.subscribe('payment.*', (event) => {
|
|
2338
|
+
* console.log('Payment event:', event.eventName);
|
|
2339
|
+
* });
|
|
2340
|
+
*
|
|
2341
|
+
* // Start polling
|
|
2342
|
+
* poller.start();
|
|
2343
|
+
*
|
|
2344
|
+
* // Stop when done
|
|
2345
|
+
* poller.stop();
|
|
2346
|
+
* ```
|
|
2347
|
+
*/
|
|
2348
|
+
declare class InProcessDomainEventPoller {
|
|
2349
|
+
private readonly pollIntervalMs;
|
|
2350
|
+
private readonly maxEventsPerPoll;
|
|
2351
|
+
private running;
|
|
2352
|
+
private pollTimer;
|
|
2353
|
+
private readonly logger;
|
|
2354
|
+
private readonly subscriptions;
|
|
2355
|
+
private readonly errorCallbacks;
|
|
2356
|
+
private pollCount;
|
|
2357
|
+
private eventsProcessed;
|
|
2358
|
+
private eventsLagged;
|
|
2359
|
+
private pollFn;
|
|
2360
|
+
constructor(config?: DomainEventPollerConfig);
|
|
2361
|
+
/**
|
|
2362
|
+
* Set the FFI poll function.
|
|
2363
|
+
*
|
|
2364
|
+
* This must be called before start() to enable actual polling.
|
|
2365
|
+
* Without it, the poller runs in "dry" mode (for testing).
|
|
2366
|
+
*
|
|
2367
|
+
* @param pollFn - Function that polls for events from Rust
|
|
2368
|
+
*/
|
|
2369
|
+
setPollFunction(pollFn: () => DomainEvent | null): void;
|
|
2370
|
+
/**
|
|
2371
|
+
* Subscribe to events matching a pattern.
|
|
2372
|
+
*
|
|
2373
|
+
* @param pattern - Event pattern (e.g., "*", "payment.*")
|
|
2374
|
+
* @param callback - Function to call when events match
|
|
2375
|
+
*/
|
|
2376
|
+
subscribe(pattern: string, callback: DomainEventCallback): void;
|
|
2377
|
+
/**
|
|
2378
|
+
* Unsubscribe from a pattern.
|
|
2379
|
+
*
|
|
2380
|
+
* @param pattern - The pattern to unsubscribe from
|
|
2381
|
+
*/
|
|
2382
|
+
unsubscribe(pattern: string): void;
|
|
2383
|
+
/**
|
|
2384
|
+
* Register an error callback.
|
|
2385
|
+
*/
|
|
2386
|
+
onError(callback: DomainEventErrorCallback): void;
|
|
2387
|
+
/**
|
|
2388
|
+
* Start the polling loop.
|
|
2389
|
+
*/
|
|
2390
|
+
start(): void;
|
|
2391
|
+
/**
|
|
2392
|
+
* Stop the polling loop.
|
|
2393
|
+
*/
|
|
2394
|
+
stop(): void;
|
|
2395
|
+
/**
|
|
2396
|
+
* Check if the poller is running.
|
|
2397
|
+
*/
|
|
2398
|
+
get isRunning(): boolean;
|
|
2399
|
+
/**
|
|
2400
|
+
* Get poller statistics.
|
|
2401
|
+
*/
|
|
2402
|
+
get stats(): PollerStats;
|
|
2403
|
+
/**
|
|
2404
|
+
* Schedule the next poll cycle.
|
|
2405
|
+
*/
|
|
2406
|
+
private schedulePoll;
|
|
2407
|
+
/**
|
|
2408
|
+
* Execute a poll cycle.
|
|
2409
|
+
*/
|
|
2410
|
+
private pollCycle;
|
|
2411
|
+
/**
|
|
2412
|
+
* Dispatch an event to matching subscribers.
|
|
2413
|
+
*/
|
|
2414
|
+
private dispatchEvent;
|
|
2415
|
+
/**
|
|
2416
|
+
* Check if an event name matches a pattern.
|
|
2417
|
+
*/
|
|
2418
|
+
private matchesPattern;
|
|
2419
|
+
/**
|
|
2420
|
+
* Emit an error to registered callbacks.
|
|
2421
|
+
*/
|
|
2422
|
+
private emitError;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Create a StepEventContext from step data.
|
|
2426
|
+
*
|
|
2427
|
+
* @param data - Raw step context data
|
|
2428
|
+
* @returns StepEventContext
|
|
2429
|
+
*/
|
|
2430
|
+
declare function createStepEventContext(data: {
|
|
2431
|
+
taskUuid: string;
|
|
2432
|
+
stepUuid: string;
|
|
2433
|
+
stepName: string;
|
|
2434
|
+
namespace?: string;
|
|
2435
|
+
correlationId?: string;
|
|
2436
|
+
result?: Record<string, unknown>;
|
|
2437
|
+
metadata?: Record<string, unknown>;
|
|
2438
|
+
}): StepEventContext;
|
|
2439
|
+
/**
|
|
2440
|
+
* Create a DomainEvent from raw data.
|
|
2441
|
+
*
|
|
2442
|
+
* @param data - Raw event data
|
|
2443
|
+
* @returns DomainEvent
|
|
2444
|
+
*/
|
|
2445
|
+
declare function createDomainEvent(data: {
|
|
2446
|
+
eventId: string;
|
|
2447
|
+
eventName: string;
|
|
2448
|
+
payload: Record<string, unknown>;
|
|
2449
|
+
metadata: Record<string, unknown>;
|
|
2450
|
+
executionResult: StepResult;
|
|
2451
|
+
}): DomainEvent;
|
|
2452
|
+
/**
|
|
2453
|
+
* Transform an FfiDomainEvent to a DomainEvent.
|
|
2454
|
+
*
|
|
2455
|
+
* The FFI domain event has a simpler structure than the full DomainEvent.
|
|
2456
|
+
* This function converts between them, synthesizing missing fields.
|
|
2457
|
+
*
|
|
2458
|
+
* @param ffiEvent - Event from FFI poll_in_process_events
|
|
2459
|
+
* @returns DomainEvent suitable for subscribers
|
|
2460
|
+
*/
|
|
2461
|
+
declare function ffiEventToDomainEvent(ffiEvent: FfiDomainEvent): DomainEvent;
|
|
2462
|
+
/**
|
|
2463
|
+
* Create an FFI poll function adapter for InProcessDomainEventPoller.
|
|
2464
|
+
*
|
|
2465
|
+
* This function wraps the runtime's pollInProcessEvents() to return
|
|
2466
|
+
* DomainEvent objects that the poller expects.
|
|
2467
|
+
*
|
|
2468
|
+
* @param runtime - The FFI runtime instance
|
|
2469
|
+
* @returns A poll function that returns DomainEvent | null
|
|
2470
|
+
*
|
|
2471
|
+
* @example
|
|
2472
|
+
* ```typescript
|
|
2473
|
+
* const poller = new InProcessDomainEventPoller();
|
|
2474
|
+
* poller.setPollFunction(createFfiPollAdapter(runtime));
|
|
2475
|
+
* poller.start();
|
|
2476
|
+
* ```
|
|
2477
|
+
*/
|
|
2478
|
+
declare function createFfiPollAdapter(runtime: TaskerRuntime): () => DomainEvent | null;
|
|
2479
|
+
|
|
2480
|
+
/**
|
|
2481
|
+
* TAS-93: Handler definition type for resolver chain.
|
|
2482
|
+
*
|
|
2483
|
+
* Represents the configuration for a step handler, including:
|
|
2484
|
+
* - callable: The handler address/identifier
|
|
2485
|
+
* - method: Optional method to invoke (defaults to "call")
|
|
2486
|
+
* - resolver: Optional resolver hint to bypass chain
|
|
2487
|
+
* - initialization: Optional configuration passed to handler
|
|
2488
|
+
*
|
|
2489
|
+
* @example
|
|
2490
|
+
* ```typescript
|
|
2491
|
+
* const definition: HandlerDefinition = {
|
|
2492
|
+
* callable: 'payment_handler',
|
|
2493
|
+
* method: 'refund',
|
|
2494
|
+
* resolver: 'explicit_mapping',
|
|
2495
|
+
* initialization: { apiKey: 'secret' },
|
|
2496
|
+
* };
|
|
2497
|
+
*
|
|
2498
|
+
* // Check if method dispatch is needed
|
|
2499
|
+
* if (usesMethodDispatch(definition)) {
|
|
2500
|
+
* // Wrap handler for method dispatch
|
|
2501
|
+
* }
|
|
2502
|
+
* ```
|
|
2503
|
+
*/
|
|
2504
|
+
|
|
2505
|
+
/**
|
|
2506
|
+
* Handler definition with resolution metadata.
|
|
2507
|
+
*/
|
|
2508
|
+
interface HandlerDefinition {
|
|
2509
|
+
/** The handler address/identifier (required) */
|
|
2510
|
+
callable: string;
|
|
2511
|
+
/** Method to invoke on the handler (defaults to "call") */
|
|
2512
|
+
method?: string | null;
|
|
2513
|
+
/** Resolver hint to bypass chain resolution */
|
|
2514
|
+
resolver?: string | null;
|
|
2515
|
+
/** Initialization configuration passed to handler */
|
|
2516
|
+
initialization?: Record<string, unknown>;
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Get the effective method to invoke.
|
|
2520
|
+
*
|
|
2521
|
+
* @param definition - Handler definition
|
|
2522
|
+
* @returns The method name (defaults to "call")
|
|
2523
|
+
*/
|
|
2524
|
+
declare function effectiveMethod(definition: HandlerDefinition): string;
|
|
2525
|
+
/**
|
|
2526
|
+
* Check if method dispatch is needed.
|
|
2527
|
+
*
|
|
2528
|
+
* Returns true if a non-default method is specified.
|
|
2529
|
+
*
|
|
2530
|
+
* @param definition - Handler definition
|
|
2531
|
+
* @returns True if method dispatch wrapper is needed
|
|
2532
|
+
*/
|
|
2533
|
+
declare function usesMethodDispatch(definition: HandlerDefinition): boolean;
|
|
2534
|
+
/**
|
|
2535
|
+
* Check if a resolver hint is provided.
|
|
2536
|
+
*
|
|
2537
|
+
* @param definition - Handler definition
|
|
2538
|
+
* @returns True if resolver hint should be used
|
|
2539
|
+
*/
|
|
2540
|
+
declare function hasResolverHint(definition: HandlerDefinition): boolean;
|
|
2541
|
+
/**
|
|
2542
|
+
* Create a HandlerDefinition from a callable string.
|
|
2543
|
+
*
|
|
2544
|
+
* @param callable - Handler callable string
|
|
2545
|
+
* @returns HandlerDefinition with defaults
|
|
2546
|
+
*/
|
|
2547
|
+
declare function fromCallable(callable: string): HandlerDefinition;
|
|
2548
|
+
/**
|
|
2549
|
+
* Create a HandlerDefinition from FFI DTO.
|
|
2550
|
+
*
|
|
2551
|
+
* Maps Rust field names to TypeScript:
|
|
2552
|
+
* - Rust uses 'method' field (matches our interface)
|
|
2553
|
+
*
|
|
2554
|
+
* @param dto - FFI handler definition DTO
|
|
2555
|
+
* @returns HandlerDefinition
|
|
2556
|
+
*/
|
|
2557
|
+
declare function fromDto(dto: HandlerDefinitionDto | null | undefined): HandlerDefinition;
|
|
2558
|
+
/**
|
|
2559
|
+
* Type alias for handler specification input.
|
|
2560
|
+
*
|
|
2561
|
+
* Accepts:
|
|
2562
|
+
* - string: Simple callable name
|
|
2563
|
+
* - HandlerDefinition: Full definition with method/resolver
|
|
2564
|
+
* - HandlerDefinitionDto: FFI DTO from Rust
|
|
2565
|
+
*/
|
|
2566
|
+
type HandlerSpec = string | HandlerDefinition | HandlerDefinitionDto;
|
|
2567
|
+
/**
|
|
2568
|
+
* Normalize any handler spec to HandlerDefinition.
|
|
2569
|
+
*
|
|
2570
|
+
* @param spec - Handler specification (string, definition, or DTO)
|
|
2571
|
+
* @returns Normalized HandlerDefinition
|
|
2572
|
+
*/
|
|
2573
|
+
declare function normalizeToDefinition(spec: HandlerSpec): HandlerDefinition;
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* TAS-93: Base resolver interface for step handler resolution.
|
|
2577
|
+
*
|
|
2578
|
+
* Defines the contract that all resolvers must implement.
|
|
2579
|
+
* Resolvers are ordered by priority (lower = checked first).
|
|
2580
|
+
*
|
|
2581
|
+
* @example
|
|
2582
|
+
* ```typescript
|
|
2583
|
+
* class MyResolver implements BaseResolver {
|
|
2584
|
+
* readonly name = 'my_resolver';
|
|
2585
|
+
* readonly priority = 50;
|
|
2586
|
+
*
|
|
2587
|
+
* canResolve(definition: HandlerDefinition): boolean {
|
|
2588
|
+
* return definition.callable.startsWith('my:');
|
|
2589
|
+
* }
|
|
2590
|
+
*
|
|
2591
|
+
* async resolve(definition: HandlerDefinition): Promise<StepHandler | null> {
|
|
2592
|
+
* if (!this.canResolve(definition)) return null;
|
|
2593
|
+
* return new MyHandler();
|
|
2594
|
+
* }
|
|
2595
|
+
* }
|
|
2596
|
+
* ```
|
|
2597
|
+
*/
|
|
2598
|
+
|
|
2599
|
+
/**
|
|
2600
|
+
* Configuration passed to resolvers during resolution.
|
|
2601
|
+
*/
|
|
2602
|
+
interface ResolverConfig {
|
|
2603
|
+
/** Additional context for resolution */
|
|
2604
|
+
[key: string]: unknown;
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Base interface for step handler resolvers.
|
|
2608
|
+
*
|
|
2609
|
+
* Resolvers are responsible for finding and instantiating handlers
|
|
2610
|
+
* based on their callable format. Multiple resolvers form a chain,
|
|
2611
|
+
* ordered by priority.
|
|
2612
|
+
*/
|
|
2613
|
+
interface BaseResolver {
|
|
2614
|
+
/**
|
|
2615
|
+
* Unique name for this resolver.
|
|
2616
|
+
* Used for resolver hints and debugging.
|
|
2617
|
+
*/
|
|
2618
|
+
readonly name: string;
|
|
2619
|
+
/**
|
|
2620
|
+
* Priority in the resolver chain.
|
|
2621
|
+
* Lower values are checked first.
|
|
2622
|
+
*
|
|
2623
|
+
* Suggested ranges:
|
|
2624
|
+
* - 1-20: Explicit mappings (highest priority)
|
|
2625
|
+
* - 21-50: Custom domain resolvers
|
|
2626
|
+
* - 51-99: Pattern-based resolvers
|
|
2627
|
+
* - 100+: Inferential resolvers (lowest priority)
|
|
2628
|
+
*/
|
|
2629
|
+
readonly priority: number;
|
|
2630
|
+
/**
|
|
2631
|
+
* Check if this resolver can handle the given definition.
|
|
2632
|
+
*
|
|
2633
|
+
* This is a quick check without actually resolving.
|
|
2634
|
+
* Used to determine if resolve() should be called.
|
|
2635
|
+
*
|
|
2636
|
+
* @param definition - Handler definition to check
|
|
2637
|
+
* @param config - Optional resolver configuration
|
|
2638
|
+
* @returns True if this resolver can attempt resolution
|
|
2639
|
+
*/
|
|
2640
|
+
canResolve(definition: HandlerDefinition, config?: ResolverConfig): boolean;
|
|
2641
|
+
/**
|
|
2642
|
+
* Resolve a handler from the definition.
|
|
2643
|
+
*
|
|
2644
|
+
* Should return null if resolution fails (allows chain to continue).
|
|
2645
|
+
* Should throw only for unrecoverable errors.
|
|
2646
|
+
*
|
|
2647
|
+
* @param definition - Handler definition to resolve
|
|
2648
|
+
* @param config - Optional resolver configuration
|
|
2649
|
+
* @returns Handler instance or null if not found
|
|
2650
|
+
*/
|
|
2651
|
+
resolve(definition: HandlerDefinition, config?: ResolverConfig): Promise<StepHandler | null>;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* TAS-93: Error types for resolver chain.
|
|
2656
|
+
*/
|
|
2657
|
+
/**
|
|
2658
|
+
* Base error class for resolution errors.
|
|
2659
|
+
*/
|
|
2660
|
+
declare class ResolutionError extends Error {
|
|
2661
|
+
constructor(message: string);
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* Error thrown when a specified resolver is not found.
|
|
2665
|
+
*/
|
|
2666
|
+
declare class ResolverNotFoundError extends ResolutionError {
|
|
2667
|
+
readonly resolverName: string;
|
|
2668
|
+
constructor(resolverName: string);
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Error thrown when no resolver can handle a callable.
|
|
2672
|
+
*/
|
|
2673
|
+
declare class NoResolverMatchError extends ResolutionError {
|
|
2674
|
+
readonly callable: string;
|
|
2675
|
+
readonly triedResolvers: string[];
|
|
2676
|
+
constructor(callable: string, triedResolvers: string[]);
|
|
2677
|
+
}
|
|
2678
|
+
/**
|
|
2679
|
+
* Error thrown when method dispatch fails.
|
|
2680
|
+
*/
|
|
2681
|
+
declare class MethodDispatchError extends ResolutionError {
|
|
2682
|
+
readonly handlerName: string;
|
|
2683
|
+
readonly methodName: string;
|
|
2684
|
+
constructor(handlerName: string, methodName: string);
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
/**
|
|
2688
|
+
* TAS-93: Method dispatch wrapper for step handlers.
|
|
2689
|
+
*
|
|
2690
|
+
* Wraps a handler to redirect .call() invocations to a specified method.
|
|
2691
|
+
* This enables the `handler_method` field to work transparently.
|
|
2692
|
+
*
|
|
2693
|
+
* @example
|
|
2694
|
+
* ```typescript
|
|
2695
|
+
* class PaymentHandler extends StepHandler {
|
|
2696
|
+
* static handlerName = 'payment_handler';
|
|
2697
|
+
*
|
|
2698
|
+
* async call(context: StepContext): Promise<StepHandlerResult> {
|
|
2699
|
+
* return this.success({ action: 'default' });
|
|
2700
|
+
* }
|
|
2701
|
+
*
|
|
2702
|
+
* async refund(context: StepContext): Promise<StepHandlerResult> {
|
|
2703
|
+
* return this.success({ action: 'refund' });
|
|
2704
|
+
* }
|
|
2705
|
+
* }
|
|
2706
|
+
*
|
|
2707
|
+
* // Without wrapper: handler.call(ctx) returns { action: 'default' }
|
|
2708
|
+
* // With wrapper:
|
|
2709
|
+
* const wrapped = new MethodDispatchWrapper(handler, 'refund');
|
|
2710
|
+
* await wrapped.call(ctx); // returns { action: 'refund' }
|
|
2711
|
+
* ```
|
|
2712
|
+
*/
|
|
2713
|
+
|
|
2714
|
+
/**
|
|
2715
|
+
* Wrapper that redirects .call() to a specified method.
|
|
2716
|
+
*
|
|
2717
|
+
* Implements ExecutableHandler to be type-safe when used in place of
|
|
2718
|
+
* a regular StepHandler. This avoids unsafe type casting while providing
|
|
2719
|
+
* the same public interface.
|
|
2720
|
+
*/
|
|
2721
|
+
declare class MethodDispatchWrapper implements ExecutableHandler {
|
|
2722
|
+
/** The wrapped handler instance */
|
|
2723
|
+
readonly handler: StepHandler;
|
|
2724
|
+
/** The method to invoke instead of call() */
|
|
2725
|
+
readonly targetMethod: string;
|
|
2726
|
+
/** The bound method function */
|
|
2727
|
+
private readonly boundMethod;
|
|
2728
|
+
/**
|
|
2729
|
+
* Create a new method dispatch wrapper.
|
|
2730
|
+
*
|
|
2731
|
+
* @param handler - The handler to wrap
|
|
2732
|
+
* @param targetMethod - The method name to invoke
|
|
2733
|
+
* @throws MethodDispatchError if handler doesn't have the method
|
|
2734
|
+
*/
|
|
2735
|
+
constructor(handler: StepHandler, targetMethod: string);
|
|
2736
|
+
/**
|
|
2737
|
+
* Get the handler name.
|
|
2738
|
+
* Delegates to wrapped handler.
|
|
2739
|
+
*/
|
|
2740
|
+
get name(): string;
|
|
2741
|
+
/**
|
|
2742
|
+
* Get the handler version.
|
|
2743
|
+
* Delegates to wrapped handler.
|
|
2744
|
+
*/
|
|
2745
|
+
get version(): string;
|
|
2746
|
+
/**
|
|
2747
|
+
* Get the handler capabilities.
|
|
2748
|
+
* Delegates to wrapped handler.
|
|
2749
|
+
*/
|
|
2750
|
+
get capabilities(): string[];
|
|
2751
|
+
/**
|
|
2752
|
+
* Execute the step by calling the target method.
|
|
2753
|
+
*
|
|
2754
|
+
* @param context - Step execution context
|
|
2755
|
+
* @returns Handler result from the target method
|
|
2756
|
+
*/
|
|
2757
|
+
call(context: StepContext): Promise<StepHandlerResult>;
|
|
2758
|
+
/**
|
|
2759
|
+
* Get config schema from wrapped handler.
|
|
2760
|
+
*/
|
|
2761
|
+
configSchema(): Record<string, unknown> | null;
|
|
2762
|
+
/**
|
|
2763
|
+
* Get the unwrapped handler.
|
|
2764
|
+
*
|
|
2765
|
+
* Useful for testing and debugging.
|
|
2766
|
+
*
|
|
2767
|
+
* @returns The original handler instance
|
|
2768
|
+
*/
|
|
2769
|
+
unwrap(): StepHandler;
|
|
2770
|
+
/**
|
|
2771
|
+
* String representation for debugging.
|
|
2772
|
+
*/
|
|
2773
|
+
toString(): string;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
/**
|
|
2777
|
+
* TAS-93: Developer-friendly base class for custom resolvers.
|
|
2778
|
+
*
|
|
2779
|
+
* Provides pattern and prefix matching capabilities for implementing
|
|
2780
|
+
* custom domain-specific resolvers.
|
|
2781
|
+
*
|
|
2782
|
+
* @example
|
|
2783
|
+
* ```typescript
|
|
2784
|
+
* class PaymentResolver extends RegistryResolver {
|
|
2785
|
+
* static readonly _name = 'payment_resolver';
|
|
2786
|
+
* static readonly _priority = 20;
|
|
2787
|
+
* static readonly pattern = /^payments:(?<provider>\w+):(?<action>\w+)$/;
|
|
2788
|
+
*
|
|
2789
|
+
* async resolveHandler(
|
|
2790
|
+
* definition: HandlerDefinition,
|
|
2791
|
+
* match: RegExpMatchArray | null
|
|
2792
|
+
* ): Promise<StepHandler | null> {
|
|
2793
|
+
* if (!match?.groups) return null;
|
|
2794
|
+
*
|
|
2795
|
+
* const { provider, action } = match.groups;
|
|
2796
|
+
* return PaymentHandlers.get(provider, action);
|
|
2797
|
+
* }
|
|
2798
|
+
* }
|
|
2799
|
+
*
|
|
2800
|
+
* // Register with chain
|
|
2801
|
+
* chain.addResolver(new PaymentResolver());
|
|
2802
|
+
*
|
|
2803
|
+
* // Resolves: { callable: 'payments:stripe:refund' }
|
|
2804
|
+
* ```
|
|
2805
|
+
*/
|
|
2806
|
+
|
|
2807
|
+
/**
|
|
2808
|
+
* Static configuration for RegistryResolver subclasses.
|
|
2809
|
+
*/
|
|
2810
|
+
interface RegistryResolverStatic {
|
|
2811
|
+
/** Resolver name (required) */
|
|
2812
|
+
readonly _name: string;
|
|
2813
|
+
/** Priority in chain (default: 50) */
|
|
2814
|
+
readonly _priority?: number;
|
|
2815
|
+
/** Regex pattern to match callables (optional) */
|
|
2816
|
+
readonly pattern?: RegExp;
|
|
2817
|
+
/** String prefix to match callables (optional) */
|
|
2818
|
+
readonly prefix?: string;
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Developer-friendly base class for custom resolvers.
|
|
2822
|
+
*
|
|
2823
|
+
* Subclasses should:
|
|
2824
|
+
* 1. Set static `_name` and optionally `_priority`
|
|
2825
|
+
* 2. Set static `pattern` or `prefix` for matching
|
|
2826
|
+
* 3. Implement `resolveHandler()` for resolution logic
|
|
2827
|
+
*/
|
|
2828
|
+
declare abstract class RegistryResolver implements BaseResolver {
|
|
2829
|
+
/**
|
|
2830
|
+
* Get the resolver name from static property.
|
|
2831
|
+
*/
|
|
2832
|
+
get name(): string;
|
|
2833
|
+
/**
|
|
2834
|
+
* Get the resolver priority from static property.
|
|
2835
|
+
*/
|
|
2836
|
+
get priority(): number;
|
|
2837
|
+
/**
|
|
2838
|
+
* Check if this resolver can handle the definition.
|
|
2839
|
+
*
|
|
2840
|
+
* Uses pattern or prefix matching from static properties.
|
|
2841
|
+
*
|
|
2842
|
+
* @param definition - Handler definition
|
|
2843
|
+
* @param _config - Unused, part of interface
|
|
2844
|
+
* @returns True if callable matches
|
|
2845
|
+
*/
|
|
2846
|
+
canResolve(definition: HandlerDefinition, _config?: ResolverConfig): boolean;
|
|
2847
|
+
/**
|
|
2848
|
+
* Resolve the handler.
|
|
2849
|
+
*
|
|
2850
|
+
* Delegates to resolveHandler() with pattern match if applicable.
|
|
2851
|
+
*
|
|
2852
|
+
* @param definition - Handler definition
|
|
2853
|
+
* @param config - Resolver configuration
|
|
2854
|
+
* @returns Handler instance or null
|
|
2855
|
+
*/
|
|
2856
|
+
resolve(definition: HandlerDefinition, config?: ResolverConfig): Promise<StepHandler | null>;
|
|
2857
|
+
/**
|
|
2858
|
+
* Override this in subclasses for custom resolution logic.
|
|
2859
|
+
*
|
|
2860
|
+
* @param definition - Handler definition
|
|
2861
|
+
* @param match - Regex match result (if pattern was used)
|
|
2862
|
+
* @param config - Resolver configuration
|
|
2863
|
+
* @returns Handler instance or null
|
|
2864
|
+
*/
|
|
2865
|
+
abstract resolveHandler(definition: HandlerDefinition, match: RegExpMatchArray | null, config?: ResolverConfig): Promise<StepHandler | null>;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/**
|
|
2869
|
+
* TAS-93: Resolver chain for step handler resolution.
|
|
2870
|
+
*
|
|
2871
|
+
* Orchestrates multiple resolvers in priority order to find handlers.
|
|
2872
|
+
* Supports resolver hints to bypass the chain and method dispatch wrapping.
|
|
2873
|
+
*
|
|
2874
|
+
* @example
|
|
2875
|
+
* ```typescript
|
|
2876
|
+
* // Create chain with default resolvers
|
|
2877
|
+
* const chain = ResolverChain.default();
|
|
2878
|
+
*
|
|
2879
|
+
* // Register a handler
|
|
2880
|
+
* const explicit = chain.getResolver('explicit_mapping') as ExplicitMappingResolver;
|
|
2881
|
+
* explicit.register('my_handler', MyHandler);
|
|
2882
|
+
*
|
|
2883
|
+
* // Resolve handler
|
|
2884
|
+
* const definition: HandlerDefinition = { callable: 'my_handler' };
|
|
2885
|
+
* const handler = await chain.resolve(definition);
|
|
2886
|
+
*
|
|
2887
|
+
* // Resolve with method dispatch
|
|
2888
|
+
* const definition2: HandlerDefinition = {
|
|
2889
|
+
* callable: 'my_handler',
|
|
2890
|
+
* method: 'process',
|
|
2891
|
+
* };
|
|
2892
|
+
* const handler2 = await chain.resolve(definition2);
|
|
2893
|
+
* // handler2.call() will invoke handler.process()
|
|
2894
|
+
* ```
|
|
2895
|
+
*/
|
|
2896
|
+
|
|
2897
|
+
/**
|
|
2898
|
+
* Priority-ordered chain of resolvers for handler resolution.
|
|
2899
|
+
*/
|
|
2900
|
+
declare class ResolverChain {
|
|
2901
|
+
private resolvers;
|
|
2902
|
+
private resolversByName;
|
|
2903
|
+
/**
|
|
2904
|
+
* Create a resolver chain with default resolvers.
|
|
2905
|
+
*
|
|
2906
|
+
* Default resolvers:
|
|
2907
|
+
* - ExplicitMappingResolver (priority 10)
|
|
2908
|
+
* - ClassLookupResolver (priority 100)
|
|
2909
|
+
*
|
|
2910
|
+
* Note: Uses Promise caching to prevent race conditions when called
|
|
2911
|
+
* concurrently. Multiple callers will receive the same chain instance.
|
|
2912
|
+
*
|
|
2913
|
+
* @returns Configured resolver chain
|
|
2914
|
+
*/
|
|
2915
|
+
static default(): Promise<ResolverChain>;
|
|
2916
|
+
/**
|
|
2917
|
+
* Reset the default chain (for testing only).
|
|
2918
|
+
*
|
|
2919
|
+
* @internal
|
|
2920
|
+
*/
|
|
2921
|
+
static resetDefault(): void;
|
|
2922
|
+
/**
|
|
2923
|
+
* Internal method to create the default chain.
|
|
2924
|
+
*/
|
|
2925
|
+
private static createDefaultChain;
|
|
2926
|
+
/**
|
|
2927
|
+
* Create a resolver chain synchronously with provided resolvers.
|
|
2928
|
+
*
|
|
2929
|
+
* Use this when you want to avoid async initialization.
|
|
2930
|
+
*
|
|
2931
|
+
* @param resolvers - Resolvers to add to the chain
|
|
2932
|
+
* @returns Configured resolver chain
|
|
2933
|
+
*/
|
|
2934
|
+
static withResolvers(resolvers: BaseResolver[]): ResolverChain;
|
|
2935
|
+
/**
|
|
2936
|
+
* Add a resolver to the chain.
|
|
2937
|
+
*
|
|
2938
|
+
* Resolvers are automatically sorted by priority (lowest first).
|
|
2939
|
+
*
|
|
2940
|
+
* @param resolver - Resolver to add
|
|
2941
|
+
*/
|
|
2942
|
+
addResolver(resolver: BaseResolver): void;
|
|
2943
|
+
/**
|
|
2944
|
+
* Get a resolver by name.
|
|
2945
|
+
*
|
|
2946
|
+
* @param name - Resolver name
|
|
2947
|
+
* @returns Resolver or undefined if not found
|
|
2948
|
+
*/
|
|
2949
|
+
getResolver(name: string): BaseResolver | undefined;
|
|
2950
|
+
/**
|
|
2951
|
+
* List all resolvers with their priorities.
|
|
2952
|
+
*
|
|
2953
|
+
* @returns Array of [name, priority] tuples, sorted by priority
|
|
2954
|
+
*/
|
|
2955
|
+
listResolvers(): Array<[string, number]>;
|
|
2956
|
+
/**
|
|
2957
|
+
* Resolve a handler from a definition.
|
|
2958
|
+
*
|
|
2959
|
+
* Resolution process:
|
|
2960
|
+
* 1. If resolver hint is present, use only that resolver
|
|
2961
|
+
* 2. Otherwise, try resolvers in priority order
|
|
2962
|
+
* 3. If method dispatch is needed, wrap the handler
|
|
2963
|
+
*
|
|
2964
|
+
* @param definition - Handler definition to resolve
|
|
2965
|
+
* @param config - Optional resolver configuration
|
|
2966
|
+
* @returns ExecutableHandler instance (possibly wrapped) or null
|
|
2967
|
+
*/
|
|
2968
|
+
resolve(definition: HandlerDefinition, config?: ResolverConfig): Promise<ExecutableHandler | null>;
|
|
2969
|
+
/**
|
|
2970
|
+
* Wrap a handler for method dispatch if needed.
|
|
2971
|
+
*
|
|
2972
|
+
* @param handler - Handler to potentially wrap
|
|
2973
|
+
* @param definition - Handler definition with method info
|
|
2974
|
+
* @returns Original handler or MethodDispatchWrapper (both implement ExecutableHandler)
|
|
2975
|
+
*/
|
|
2976
|
+
wrapForMethodDispatch(handler: StepHandler, definition: HandlerDefinition): ExecutableHandler;
|
|
2977
|
+
/**
|
|
2978
|
+
* Resolve using a specific resolver (from hint).
|
|
2979
|
+
*/
|
|
2980
|
+
private resolveWithHint;
|
|
2981
|
+
/**
|
|
2982
|
+
* Resolve by trying resolvers in priority order.
|
|
2983
|
+
*/
|
|
2984
|
+
private resolveWithChain;
|
|
2985
|
+
/**
|
|
2986
|
+
* Sort resolvers by priority (ascending).
|
|
2987
|
+
*/
|
|
2988
|
+
private sortResolvers;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
/**
|
|
2992
|
+
* TAS-93: Class lookup resolver (priority 100).
|
|
2993
|
+
*
|
|
2994
|
+
* Infers handler classes from callable strings using dynamic imports.
|
|
2995
|
+
* This resolver handles module path formats.
|
|
2996
|
+
*
|
|
2997
|
+
* Supports callable formats:
|
|
2998
|
+
* - "./path/to/handler.js" - Relative module path
|
|
2999
|
+
* - "../parent/handler.js" - Parent-relative module path
|
|
3000
|
+
* - "@scope/package/handler" - Scoped package path
|
|
3001
|
+
*
|
|
3002
|
+
* NOT supported (security):
|
|
3003
|
+
* - "/absolute/path/to/handler.js" - Absolute paths are blocked to prevent
|
|
3004
|
+
* loading arbitrary code in shared hosting environments.
|
|
3005
|
+
*
|
|
3006
|
+
* The module must export a class with a static `handlerName` property
|
|
3007
|
+
* matching the expected handler interface.
|
|
3008
|
+
*
|
|
3009
|
+
* Note: This resolver is lower priority (100) because dynamic imports
|
|
3010
|
+
* are more expensive than explicit mappings.
|
|
3011
|
+
*
|
|
3012
|
+
* @example
|
|
3013
|
+
* ```typescript
|
|
3014
|
+
* const resolver = new ClassLookupResolver();
|
|
3015
|
+
*
|
|
3016
|
+
* // Resolve from module path
|
|
3017
|
+
* const definition: HandlerDefinition = {
|
|
3018
|
+
* callable: './handlers/payment-handler.js',
|
|
3019
|
+
* };
|
|
3020
|
+
* const handler = await resolver.resolve(definition);
|
|
3021
|
+
* ```
|
|
3022
|
+
*/
|
|
3023
|
+
|
|
3024
|
+
/**
|
|
3025
|
+
* Resolver that infers handlers from module paths via dynamic import.
|
|
3026
|
+
*
|
|
3027
|
+
* Priority 100 - checked last in the default chain (inferential).
|
|
3028
|
+
*/
|
|
3029
|
+
declare class ClassLookupResolver implements BaseResolver {
|
|
3030
|
+
readonly name = "class_lookup";
|
|
3031
|
+
readonly priority = 100;
|
|
3032
|
+
/**
|
|
3033
|
+
* Check if callable looks like an importable path.
|
|
3034
|
+
*
|
|
3035
|
+
* @param definition - Handler definition
|
|
3036
|
+
* @param _config - Unused, part of interface
|
|
3037
|
+
* @returns True if callable matches importable pattern
|
|
3038
|
+
*/
|
|
3039
|
+
canResolve(definition: HandlerDefinition, _config?: ResolverConfig): boolean;
|
|
3040
|
+
/**
|
|
3041
|
+
* Resolve handler by dynamically importing the module.
|
|
3042
|
+
*
|
|
3043
|
+
* Looks for:
|
|
3044
|
+
* 1. Default export that is a handler class
|
|
3045
|
+
* 2. Named exports that are handler classes
|
|
3046
|
+
*
|
|
3047
|
+
* @param definition - Handler definition with module path
|
|
3048
|
+
* @param _config - Unused, part of interface
|
|
3049
|
+
* @returns Handler instance or null if not found
|
|
3050
|
+
*/
|
|
3051
|
+
resolve(definition: HandlerDefinition, _config?: ResolverConfig): Promise<StepHandler | null>;
|
|
3052
|
+
/**
|
|
3053
|
+
* Find a handler class in a module's exports.
|
|
3054
|
+
*/
|
|
3055
|
+
private findHandlerClass;
|
|
3056
|
+
/**
|
|
3057
|
+
* Check if a value is a valid handler class.
|
|
3058
|
+
*/
|
|
3059
|
+
private isHandlerClass;
|
|
3060
|
+
/**
|
|
3061
|
+
* Instantiate a handler class.
|
|
3062
|
+
*/
|
|
3063
|
+
private instantiateHandler;
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
/**
|
|
3067
|
+
* TAS-93: Explicit mapping resolver (priority 10).
|
|
3068
|
+
*
|
|
3069
|
+
* Resolves handlers from explicitly registered mappings.
|
|
3070
|
+
* This is the highest priority resolver in the default chain.
|
|
3071
|
+
*
|
|
3072
|
+
* Supports registering:
|
|
3073
|
+
* - Handler classes (instantiated on resolve)
|
|
3074
|
+
* - Handler instances (returned directly)
|
|
3075
|
+
* - Factory functions (called with config on resolve)
|
|
3076
|
+
*
|
|
3077
|
+
* @example
|
|
3078
|
+
* ```typescript
|
|
3079
|
+
* const resolver = new ExplicitMappingResolver();
|
|
3080
|
+
*
|
|
3081
|
+
* // Register a class
|
|
3082
|
+
* resolver.register('my_handler', MyHandler);
|
|
3083
|
+
*
|
|
3084
|
+
* // Register an instance
|
|
3085
|
+
* resolver.register('shared_handler', new SharedHandler());
|
|
3086
|
+
*
|
|
3087
|
+
* // Register a factory
|
|
3088
|
+
* resolver.register('configurable_handler', (config) => new ConfigHandler(config));
|
|
3089
|
+
*
|
|
3090
|
+
* // Resolve
|
|
3091
|
+
* const definition: HandlerDefinition = { callable: 'my_handler' };
|
|
3092
|
+
* const handler = await resolver.resolve(definition);
|
|
3093
|
+
* ```
|
|
3094
|
+
*/
|
|
3095
|
+
|
|
3096
|
+
/**
|
|
3097
|
+
* Factory function for creating handlers.
|
|
3098
|
+
*/
|
|
3099
|
+
type HandlerFactory = (config: ResolverConfig) => StepHandler;
|
|
3100
|
+
/**
|
|
3101
|
+
* Entry types that can be registered.
|
|
3102
|
+
*/
|
|
3103
|
+
type HandlerEntry = StepHandlerClass | StepHandler | HandlerFactory;
|
|
3104
|
+
/**
|
|
3105
|
+
* Resolver for explicitly registered handlers.
|
|
3106
|
+
*
|
|
3107
|
+
* Priority 10 - checked first in the default chain.
|
|
3108
|
+
*/
|
|
3109
|
+
declare class ExplicitMappingResolver implements BaseResolver {
|
|
3110
|
+
readonly name: string;
|
|
3111
|
+
readonly priority = 10;
|
|
3112
|
+
private handlers;
|
|
3113
|
+
/**
|
|
3114
|
+
* Create an explicit mapping resolver.
|
|
3115
|
+
*
|
|
3116
|
+
* @param name - Resolver name (default: 'explicit_mapping')
|
|
3117
|
+
*/
|
|
3118
|
+
constructor(name?: string);
|
|
3119
|
+
/**
|
|
3120
|
+
* Check if a handler is registered for this callable.
|
|
3121
|
+
*
|
|
3122
|
+
* @param definition - Handler definition
|
|
3123
|
+
* @param _config - Unused, part of interface
|
|
3124
|
+
* @returns True if handler is registered
|
|
3125
|
+
*/
|
|
3126
|
+
canResolve(definition: HandlerDefinition, _config?: ResolverConfig): boolean;
|
|
3127
|
+
/**
|
|
3128
|
+
* Resolve and instantiate a registered handler.
|
|
3129
|
+
*
|
|
3130
|
+
* @param definition - Handler definition
|
|
3131
|
+
* @param config - Configuration passed to factories
|
|
3132
|
+
* @returns Handler instance or null if not registered
|
|
3133
|
+
*/
|
|
3134
|
+
resolve(definition: HandlerDefinition, config?: ResolverConfig): Promise<StepHandler | null>;
|
|
3135
|
+
/**
|
|
3136
|
+
* Register a handler.
|
|
3137
|
+
*
|
|
3138
|
+
* @param key - Handler identifier (matched against definition.callable)
|
|
3139
|
+
* @param handler - Handler class, instance, or factory function
|
|
3140
|
+
*/
|
|
3141
|
+
register(key: string, handler: HandlerEntry): void;
|
|
3142
|
+
/**
|
|
3143
|
+
* Unregister a handler.
|
|
3144
|
+
*
|
|
3145
|
+
* @param key - Handler identifier to remove
|
|
3146
|
+
* @returns True if handler was removed, false if not found
|
|
3147
|
+
*/
|
|
3148
|
+
unregister(key: string): boolean;
|
|
3149
|
+
/**
|
|
3150
|
+
* Get all registered callable keys.
|
|
3151
|
+
*
|
|
3152
|
+
* @returns Array of registered keys
|
|
3153
|
+
*/
|
|
3154
|
+
registeredCallables(): string[];
|
|
3155
|
+
/**
|
|
3156
|
+
* Instantiate a handler from a registered entry.
|
|
3157
|
+
*/
|
|
3158
|
+
private instantiateHandler;
|
|
3159
|
+
/**
|
|
3160
|
+
* Check if entry is a handler class.
|
|
3161
|
+
*/
|
|
3162
|
+
private isHandlerClass;
|
|
3163
|
+
/**
|
|
3164
|
+
* Instantiate a handler class.
|
|
3165
|
+
*/
|
|
3166
|
+
private instantiateClass;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
/**
|
|
3170
|
+
* TAS-93: Step handler registry with resolver chain support.
|
|
3171
|
+
*
|
|
3172
|
+
* Provides handler registration and resolution using a priority-ordered
|
|
3173
|
+
* resolver chain. Supports method dispatch and resolver hints.
|
|
3174
|
+
*
|
|
3175
|
+
* @example
|
|
3176
|
+
* ```typescript
|
|
3177
|
+
* const registry = new HandlerRegistry();
|
|
3178
|
+
* await registry.initialize();
|
|
3179
|
+
*
|
|
3180
|
+
* // Register a handler
|
|
3181
|
+
* registry.register('my_handler', MyHandler);
|
|
3182
|
+
*
|
|
3183
|
+
* // Simple resolution (string callable)
|
|
3184
|
+
* const handler = await registry.resolve('my_handler');
|
|
3185
|
+
*
|
|
3186
|
+
* // Resolution with method dispatch
|
|
3187
|
+
* const definition: HandlerDefinition = {
|
|
3188
|
+
* callable: 'my_handler',
|
|
3189
|
+
* method: 'process',
|
|
3190
|
+
* };
|
|
3191
|
+
* const handler2 = await registry.resolve(definition);
|
|
3192
|
+
* // handler2.call() invokes handler.process()
|
|
3193
|
+
*
|
|
3194
|
+
* // Resolution with resolver hint
|
|
3195
|
+
* const definition2: HandlerDefinition = {
|
|
3196
|
+
* callable: 'my_handler',
|
|
3197
|
+
* resolver: 'explicit_mapping',
|
|
3198
|
+
* };
|
|
3199
|
+
* const handler3 = await registry.resolve(definition2);
|
|
3200
|
+
* ```
|
|
3201
|
+
*/
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* Registry for step handler classes.
|
|
3205
|
+
*
|
|
3206
|
+
* TAS-93: Uses ResolverChain for flexible handler resolution
|
|
3207
|
+
* with support for method dispatch and resolver hints.
|
|
3208
|
+
*
|
|
3209
|
+
* Provides handler discovery, registration, and resolution.
|
|
3210
|
+
*/
|
|
3211
|
+
declare class HandlerRegistry {
|
|
3212
|
+
private _resolverChain;
|
|
3213
|
+
private _explicitResolver;
|
|
3214
|
+
private _initialized;
|
|
3215
|
+
/** Promise-based lock for initialization to prevent concurrent init */
|
|
3216
|
+
private _initPromise;
|
|
3217
|
+
/** Track registrations so they can be transferred to chain resolver */
|
|
3218
|
+
private _pendingRegistrations;
|
|
3219
|
+
/**
|
|
3220
|
+
* Initialize the registry with default resolvers.
|
|
3221
|
+
*
|
|
3222
|
+
* Must be called before using resolve() with resolver chain features.
|
|
3223
|
+
* For simple register/resolve with strings, lazy initialization is used.
|
|
3224
|
+
*/
|
|
3225
|
+
initialize(): Promise<void>;
|
|
3226
|
+
/**
|
|
3227
|
+
* Ensure the registry is initialized.
|
|
3228
|
+
* Uses lazy initialization with proper locking to prevent concurrent init.
|
|
3229
|
+
*/
|
|
3230
|
+
private ensureInitialized;
|
|
3231
|
+
/**
|
|
3232
|
+
* Get the explicit mapping resolver for direct registration.
|
|
3233
|
+
*/
|
|
3234
|
+
private getExplicitResolver;
|
|
3235
|
+
/**
|
|
3236
|
+
* Register a handler class.
|
|
3237
|
+
*
|
|
3238
|
+
* @param name - Handler name (must match step definition)
|
|
3239
|
+
* @param handlerClass - StepHandler subclass
|
|
3240
|
+
* @throws Error if handlerClass is not a valid StepHandler subclass
|
|
3241
|
+
*
|
|
3242
|
+
* @example
|
|
3243
|
+
* ```typescript
|
|
3244
|
+
* registry.register('my_handler', MyHandler);
|
|
3245
|
+
* ```
|
|
3246
|
+
*/
|
|
3247
|
+
register(name: string, handlerClass: StepHandlerClass): void;
|
|
3248
|
+
/**
|
|
3249
|
+
* Unregister a handler.
|
|
3250
|
+
*
|
|
3251
|
+
* @param name - Handler name to unregister
|
|
3252
|
+
* @returns True if handler was unregistered, false if not found
|
|
3253
|
+
*/
|
|
3254
|
+
unregister(name: string): boolean;
|
|
3255
|
+
/**
|
|
3256
|
+
* Resolve and instantiate a handler.
|
|
3257
|
+
*
|
|
3258
|
+
* TAS-93: Accepts flexible input types:
|
|
3259
|
+
* - string: Simple callable name
|
|
3260
|
+
* - HandlerDefinition: Full definition with method/resolver
|
|
3261
|
+
* - HandlerSpec: Union type for all formats
|
|
3262
|
+
*
|
|
3263
|
+
* @param handlerSpec - Handler specification
|
|
3264
|
+
* @returns ExecutableHandler (StepHandler or MethodDispatchWrapper) or null if not found
|
|
3265
|
+
*
|
|
3266
|
+
* @example
|
|
3267
|
+
* ```typescript
|
|
3268
|
+
* // String callable
|
|
3269
|
+
* const handler = await registry.resolve('my_handler');
|
|
3270
|
+
*
|
|
3271
|
+
* // With method dispatch
|
|
3272
|
+
* const handler2 = await registry.resolve({
|
|
3273
|
+
* callable: 'my_handler',
|
|
3274
|
+
* method: 'process',
|
|
3275
|
+
* });
|
|
3276
|
+
* ```
|
|
3277
|
+
*/
|
|
3278
|
+
resolve(handlerSpec: HandlerSpec): Promise<ExecutableHandler | null>;
|
|
3279
|
+
/**
|
|
3280
|
+
* Synchronous resolve for backward compatibility.
|
|
3281
|
+
*
|
|
3282
|
+
* Note: This only works with explicitly registered handlers.
|
|
3283
|
+
* For full resolver chain support, use the async resolve() method.
|
|
3284
|
+
*
|
|
3285
|
+
* @param name - Handler name to resolve
|
|
3286
|
+
* @returns Instantiated handler or null if not found
|
|
3287
|
+
*/
|
|
3288
|
+
resolveSync(name: string): StepHandler | null;
|
|
3289
|
+
/**
|
|
3290
|
+
* Get a handler class without instantiation.
|
|
3291
|
+
*
|
|
3292
|
+
* @param name - Handler name to look up
|
|
3293
|
+
* @returns Handler class or undefined if not found
|
|
3294
|
+
*/
|
|
3295
|
+
getHandlerClass(_name: string): StepHandlerClass | undefined;
|
|
3296
|
+
/**
|
|
3297
|
+
* Check if a handler is registered.
|
|
3298
|
+
*
|
|
3299
|
+
* @param name - Handler name to check
|
|
3300
|
+
* @returns True if handler is registered
|
|
3301
|
+
*/
|
|
3302
|
+
isRegistered(name: string): boolean;
|
|
3303
|
+
/**
|
|
3304
|
+
* List all registered handler names.
|
|
3305
|
+
*
|
|
3306
|
+
* @returns Array of registered handler names
|
|
3307
|
+
*/
|
|
3308
|
+
listHandlers(): string[];
|
|
3309
|
+
/**
|
|
3310
|
+
* Get the number of registered handlers.
|
|
3311
|
+
*/
|
|
3312
|
+
handlerCount(): number;
|
|
3313
|
+
/**
|
|
3314
|
+
* Clear all registered handlers.
|
|
3315
|
+
*/
|
|
3316
|
+
clear(): void;
|
|
3317
|
+
/**
|
|
3318
|
+
* Add a custom resolver to the chain.
|
|
3319
|
+
*
|
|
3320
|
+
* TAS-93: Allows adding custom domain-specific resolvers.
|
|
3321
|
+
*
|
|
3322
|
+
* @param resolver - Resolver to add
|
|
3323
|
+
*/
|
|
3324
|
+
addResolver(resolver: BaseResolver): Promise<void>;
|
|
3325
|
+
/**
|
|
3326
|
+
* Get a resolver by name.
|
|
3327
|
+
*
|
|
3328
|
+
* @param name - Resolver name
|
|
3329
|
+
* @returns Resolver or undefined
|
|
3330
|
+
*/
|
|
3331
|
+
getResolver(name: string): Promise<BaseResolver | undefined>;
|
|
3332
|
+
/**
|
|
3333
|
+
* List all resolvers with priorities.
|
|
3334
|
+
*
|
|
3335
|
+
* @returns Array of [name, priority] tuples
|
|
3336
|
+
*/
|
|
3337
|
+
listResolvers(): Promise<Array<[string, number]>>;
|
|
3338
|
+
/**
|
|
3339
|
+
* Get the underlying resolver chain.
|
|
3340
|
+
*
|
|
3341
|
+
* Useful for advanced configuration.
|
|
3342
|
+
*/
|
|
3343
|
+
getResolverChain(): Promise<ResolverChain | null>;
|
|
3344
|
+
/**
|
|
3345
|
+
* Get debug information about the registry.
|
|
3346
|
+
*/
|
|
3347
|
+
debugInfo(): Record<string, unknown>;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
/**
|
|
3351
|
+
* HandlerSystem - Owns handler registration and discovery.
|
|
3352
|
+
*
|
|
3353
|
+
* TAS-93: Uses HandlerRegistry with resolver chain for flexible handler resolution.
|
|
3354
|
+
*
|
|
3355
|
+
* This class encapsulates handler management:
|
|
3356
|
+
* - Owns a HandlerRegistry instance (no singleton)
|
|
3357
|
+
* - Provides handler discovery from directories
|
|
3358
|
+
* - Manages handler registration lifecycle
|
|
3359
|
+
* - Full resolver chain support via HandlerRegistry
|
|
3360
|
+
*
|
|
3361
|
+
* Design principles:
|
|
3362
|
+
* - Explicit construction: No singleton pattern
|
|
3363
|
+
* - Clear ownership: Owns the registry instance
|
|
3364
|
+
* - Encapsulated discovery: Handler loading logic contained here
|
|
3365
|
+
* - Single registry: Uses HandlerRegistry for all resolution
|
|
3366
|
+
*/
|
|
3367
|
+
|
|
3368
|
+
/**
|
|
3369
|
+
* Owns handler registration and discovery.
|
|
3370
|
+
*
|
|
3371
|
+
* TAS-93: Uses HandlerRegistry internally for unified resolution.
|
|
3372
|
+
*
|
|
3373
|
+
* Unlike using HandlerRegistry directly, this class:
|
|
3374
|
+
* - Encapsulates handler discovery from directories
|
|
3375
|
+
* - Provides convenience methods for loading handlers
|
|
3376
|
+
* - Manages the full handler lifecycle
|
|
3377
|
+
*
|
|
3378
|
+
* @example
|
|
3379
|
+
* ```typescript
|
|
3380
|
+
* const handlerSystem = new HandlerSystem();
|
|
3381
|
+
*
|
|
3382
|
+
* // Register individual handlers
|
|
3383
|
+
* handlerSystem.register('my_handler', MyHandler);
|
|
3384
|
+
*
|
|
3385
|
+
* // Or load from directory
|
|
3386
|
+
* await handlerSystem.loadFromPath('./handlers');
|
|
3387
|
+
*
|
|
3388
|
+
* // Resolve with full resolver chain support
|
|
3389
|
+
* const handler = await handlerSystem.resolve('my_handler');
|
|
3390
|
+
*
|
|
3391
|
+
* // Resolve with method dispatch
|
|
3392
|
+
* const handler2 = await handlerSystem.resolve({
|
|
3393
|
+
* callable: 'my_handler',
|
|
3394
|
+
* method: 'process',
|
|
3395
|
+
* });
|
|
3396
|
+
* ```
|
|
3397
|
+
*/
|
|
3398
|
+
declare class HandlerSystem {
|
|
3399
|
+
private readonly registry;
|
|
3400
|
+
/**
|
|
3401
|
+
* Create a new HandlerSystem.
|
|
3402
|
+
*/
|
|
3403
|
+
constructor();
|
|
3404
|
+
/**
|
|
3405
|
+
* Initialize the handler system.
|
|
3406
|
+
*
|
|
3407
|
+
* Initializes the underlying HandlerRegistry with resolver chain.
|
|
3408
|
+
* Call this before using resolve() for best performance.
|
|
3409
|
+
*/
|
|
3410
|
+
initialize(): Promise<void>;
|
|
3411
|
+
/**
|
|
3412
|
+
* Check if the system is initialized.
|
|
3413
|
+
*/
|
|
3414
|
+
get initialized(): boolean;
|
|
3415
|
+
/**
|
|
3416
|
+
* Load handlers from a directory path.
|
|
3417
|
+
*
|
|
3418
|
+
* Searches for handler modules in the directory and registers them.
|
|
3419
|
+
* Supports both index file exports and directory scanning.
|
|
3420
|
+
*
|
|
3421
|
+
* @param path - Path to directory containing handlers
|
|
3422
|
+
* @returns Number of handlers loaded
|
|
3423
|
+
*/
|
|
3424
|
+
loadFromPath(path: string): Promise<number>;
|
|
3425
|
+
/**
|
|
3426
|
+
* Load handlers from TYPESCRIPT_HANDLER_PATH environment variable.
|
|
3427
|
+
*
|
|
3428
|
+
* @returns Number of handlers loaded
|
|
3429
|
+
*/
|
|
3430
|
+
loadFromEnv(): Promise<number>;
|
|
3431
|
+
/**
|
|
3432
|
+
* Register a handler class.
|
|
3433
|
+
*
|
|
3434
|
+
* @param name - Handler name (must match step definition)
|
|
3435
|
+
* @param handlerClass - StepHandler subclass
|
|
3436
|
+
*/
|
|
3437
|
+
register(name: string, handlerClass: StepHandlerClass): void;
|
|
3438
|
+
/**
|
|
3439
|
+
* Unregister a handler.
|
|
3440
|
+
*
|
|
3441
|
+
* @param name - Handler name to unregister
|
|
3442
|
+
* @returns True if handler was unregistered
|
|
3443
|
+
*/
|
|
3444
|
+
unregister(name: string): boolean;
|
|
3445
|
+
/**
|
|
3446
|
+
* Resolve and instantiate a handler.
|
|
3447
|
+
*
|
|
3448
|
+
* TAS-93: Supports full resolver chain with method dispatch and resolver hints.
|
|
3449
|
+
*
|
|
3450
|
+
* @param handlerSpec - Handler specification (string, definition, or DTO)
|
|
3451
|
+
* @returns Instantiated handler or null if not found
|
|
3452
|
+
*
|
|
3453
|
+
* @example
|
|
3454
|
+
* ```typescript
|
|
3455
|
+
* // String callable
|
|
3456
|
+
* const handler = await handlerSystem.resolve('my_handler');
|
|
3457
|
+
*
|
|
3458
|
+
* // With method dispatch
|
|
3459
|
+
* const handler2 = await handlerSystem.resolve({
|
|
3460
|
+
* callable: 'my_handler',
|
|
3461
|
+
* method: 'process',
|
|
3462
|
+
* });
|
|
3463
|
+
*
|
|
3464
|
+
* // With resolver hint
|
|
3465
|
+
* const handler3 = await handlerSystem.resolve({
|
|
3466
|
+
* callable: 'my_handler',
|
|
3467
|
+
* resolver: 'explicit_mapping',
|
|
3468
|
+
* });
|
|
3469
|
+
* ```
|
|
3470
|
+
*/
|
|
3471
|
+
resolve(handlerSpec: HandlerSpec): Promise<ExecutableHandler | null>;
|
|
3472
|
+
/**
|
|
3473
|
+
* Check if a handler is registered.
|
|
3474
|
+
*/
|
|
3475
|
+
isRegistered(name: string): boolean;
|
|
3476
|
+
/**
|
|
3477
|
+
* List all registered handler names.
|
|
3478
|
+
*/
|
|
3479
|
+
listHandlers(): string[];
|
|
3480
|
+
/**
|
|
3481
|
+
* Get the number of registered handlers.
|
|
3482
|
+
*/
|
|
3483
|
+
handlerCount(): number;
|
|
3484
|
+
/**
|
|
3485
|
+
* Clear all registered handlers.
|
|
3486
|
+
*/
|
|
3487
|
+
clear(): void;
|
|
3488
|
+
/**
|
|
3489
|
+
* Add a custom resolver to the chain.
|
|
3490
|
+
*
|
|
3491
|
+
* TAS-93: Allows adding custom domain-specific resolvers.
|
|
3492
|
+
*
|
|
3493
|
+
* @param resolver - Resolver to add
|
|
3494
|
+
*/
|
|
3495
|
+
addResolver(resolver: BaseResolver): Promise<void>;
|
|
3496
|
+
/**
|
|
3497
|
+
* Get a resolver by name.
|
|
3498
|
+
*
|
|
3499
|
+
* @param name - Resolver name
|
|
3500
|
+
* @returns Resolver or undefined
|
|
3501
|
+
*/
|
|
3502
|
+
getResolver(name: string): Promise<BaseResolver | undefined>;
|
|
3503
|
+
/**
|
|
3504
|
+
* List all resolvers with priorities.
|
|
3505
|
+
*
|
|
3506
|
+
* @returns Array of [name, priority] tuples
|
|
3507
|
+
*/
|
|
3508
|
+
listResolvers(): Promise<Array<[string, number]>>;
|
|
3509
|
+
/**
|
|
3510
|
+
* Get the underlying resolver chain.
|
|
3511
|
+
*
|
|
3512
|
+
* Useful for advanced configuration.
|
|
3513
|
+
*/
|
|
3514
|
+
getResolverChain(): Promise<ResolverChain | null>;
|
|
3515
|
+
/**
|
|
3516
|
+
* Get the underlying HandlerRegistry.
|
|
3517
|
+
*
|
|
3518
|
+
* TAS-93: Returns the HandlerRegistry directly for use with
|
|
3519
|
+
* StepExecutionSubscriber and other components.
|
|
3520
|
+
*/
|
|
3521
|
+
getRegistry(): HandlerRegistry;
|
|
3522
|
+
/**
|
|
3523
|
+
* Get debug information about the handler system.
|
|
3524
|
+
*/
|
|
3525
|
+
debugInfo(): Record<string, unknown>;
|
|
3526
|
+
/**
|
|
3527
|
+
* Try to import an index file from the handler path.
|
|
3528
|
+
*/
|
|
3529
|
+
private tryImportIndexFile;
|
|
3530
|
+
/**
|
|
3531
|
+
* Register handlers from a module's exports.
|
|
3532
|
+
*/
|
|
3533
|
+
private registerHandlersFromModule;
|
|
3534
|
+
/**
|
|
3535
|
+
* Register handlers from ALL_EXAMPLE_HANDLERS array.
|
|
3536
|
+
*/
|
|
3537
|
+
private registerFromHandlerArray;
|
|
3538
|
+
/**
|
|
3539
|
+
* Register handlers from module exports.
|
|
3540
|
+
*/
|
|
3541
|
+
private registerFromModuleExports;
|
|
3542
|
+
/**
|
|
3543
|
+
* Check if a value is a valid handler class.
|
|
3544
|
+
*/
|
|
3545
|
+
private isValidHandlerClass;
|
|
3546
|
+
/**
|
|
3547
|
+
* Recursively scan a directory for handler files and import them.
|
|
3548
|
+
*/
|
|
3549
|
+
private scanAndImportHandlers;
|
|
3550
|
+
/**
|
|
3551
|
+
* Process a single directory entry for handler import.
|
|
3552
|
+
*/
|
|
3553
|
+
private processDirectoryEntry;
|
|
3554
|
+
/**
|
|
3555
|
+
* Check if a directory should be scanned for handlers.
|
|
3556
|
+
*/
|
|
3557
|
+
private shouldScanDirectory;
|
|
3558
|
+
/**
|
|
3559
|
+
* Check if a file might contain handlers.
|
|
3560
|
+
*/
|
|
3561
|
+
private isHandlerFile;
|
|
3562
|
+
/**
|
|
3563
|
+
* Import handlers from a single file.
|
|
3564
|
+
*/
|
|
3565
|
+
private importHandlerFile;
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
/**
|
|
3569
|
+
* Structured logging API for TypeScript workers.
|
|
3570
|
+
*
|
|
3571
|
+
* Provides unified structured logging that integrates with Rust tracing
|
|
3572
|
+
* infrastructure via FFI. All log messages are forwarded to the Rust
|
|
3573
|
+
* tracing subscriber for consistent formatting and output.
|
|
3574
|
+
*
|
|
3575
|
+
* Matches Python's logging module and Ruby's tracing module (TAS-92 aligned).
|
|
3576
|
+
*
|
|
3577
|
+
* To enable FFI logging, call setLoggingRuntime() after loading the FFI layer.
|
|
3578
|
+
* If no runtime is installed, logs fall back to console output.
|
|
3579
|
+
*/
|
|
3580
|
+
|
|
3581
|
+
/**
|
|
3582
|
+
* Structured logging fields.
|
|
3583
|
+
*
|
|
3584
|
+
* All fields are optional. Common fields include:
|
|
3585
|
+
* - component: Component/subsystem identifier (e.g., "handler", "registry")
|
|
3586
|
+
* - operation: Operation being performed (e.g., "process_payment")
|
|
3587
|
+
* - correlation_id: Distributed tracing correlation ID
|
|
3588
|
+
* - task_uuid: Task identifier
|
|
3589
|
+
* - step_uuid: Step identifier
|
|
3590
|
+
* - namespace: Task namespace
|
|
3591
|
+
* - error_message: Error message for error logs
|
|
3592
|
+
* - duration_ms: Execution duration for timed operations
|
|
3593
|
+
*/
|
|
3594
|
+
interface LogFields {
|
|
3595
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
3596
|
+
}
|
|
3597
|
+
/**
|
|
3598
|
+
* Log an ERROR level message with structured fields.
|
|
3599
|
+
*
|
|
3600
|
+
* Use this for unrecoverable failures that require intervention.
|
|
3601
|
+
*
|
|
3602
|
+
* @param message - The log message
|
|
3603
|
+
* @param fields - Optional structured fields for context
|
|
3604
|
+
*
|
|
3605
|
+
* @example
|
|
3606
|
+
* logError('Database connection failed', {
|
|
3607
|
+
* component: 'database',
|
|
3608
|
+
* operation: 'connect',
|
|
3609
|
+
* error_message: 'Connection timeout',
|
|
3610
|
+
* });
|
|
3611
|
+
*/
|
|
3612
|
+
declare function logError(message: string, fields?: LogFields): void;
|
|
3613
|
+
/**
|
|
3614
|
+
* Log a WARN level message with structured fields.
|
|
3615
|
+
*
|
|
3616
|
+
* Use this for degraded operation or retryable failures.
|
|
3617
|
+
*
|
|
3618
|
+
* @param message - The log message
|
|
3619
|
+
* @param fields - Optional structured fields for context
|
|
3620
|
+
*
|
|
3621
|
+
* @example
|
|
3622
|
+
* logWarn('Retry attempt 3 of 5', {
|
|
3623
|
+
* component: 'handler',
|
|
3624
|
+
* operation: 'retry',
|
|
3625
|
+
* attempt: 3,
|
|
3626
|
+
* });
|
|
3627
|
+
*/
|
|
3628
|
+
declare function logWarn(message: string, fields?: LogFields): void;
|
|
3629
|
+
/**
|
|
3630
|
+
* Log an INFO level message with structured fields.
|
|
3631
|
+
*
|
|
3632
|
+
* Use this for lifecycle events and state transitions.
|
|
3633
|
+
*
|
|
3634
|
+
* @param message - The log message
|
|
3635
|
+
* @param fields - Optional structured fields for context
|
|
3636
|
+
*
|
|
3637
|
+
* @example
|
|
3638
|
+
* logInfo('Task processing started', {
|
|
3639
|
+
* component: 'handler',
|
|
3640
|
+
* operation: 'process_payment',
|
|
3641
|
+
* correlation_id: 'abc-123',
|
|
3642
|
+
* task_uuid: 'task-456',
|
|
3643
|
+
* });
|
|
3644
|
+
*/
|
|
3645
|
+
declare function logInfo(message: string, fields?: LogFields): void;
|
|
3646
|
+
/**
|
|
3647
|
+
* Log a DEBUG level message with structured fields.
|
|
3648
|
+
*
|
|
3649
|
+
* Use this for detailed diagnostic information during development.
|
|
3650
|
+
*
|
|
3651
|
+
* @param message - The log message
|
|
3652
|
+
* @param fields - Optional structured fields for context
|
|
3653
|
+
*
|
|
3654
|
+
* @example
|
|
3655
|
+
* logDebug('Parsed request payload', {
|
|
3656
|
+
* component: 'handler',
|
|
3657
|
+
* payload_size: 1024,
|
|
3658
|
+
* content_type: 'application/json',
|
|
3659
|
+
* });
|
|
3660
|
+
*/
|
|
3661
|
+
declare function logDebug(message: string, fields?: LogFields): void;
|
|
3662
|
+
/**
|
|
3663
|
+
* Log a TRACE level message with structured fields.
|
|
3664
|
+
*
|
|
3665
|
+
* Use this for very verbose logging, like function entry/exit.
|
|
3666
|
+
* This level is typically disabled in production.
|
|
3667
|
+
*
|
|
3668
|
+
* @param message - The log message
|
|
3669
|
+
* @param fields - Optional structured fields for context
|
|
3670
|
+
*
|
|
3671
|
+
* @example
|
|
3672
|
+
* logTrace('Entering process_step', {
|
|
3673
|
+
* component: 'handler',
|
|
3674
|
+
* step_uuid: 'step-789',
|
|
3675
|
+
* });
|
|
3676
|
+
*/
|
|
3677
|
+
declare function logTrace(message: string, fields?: LogFields): void;
|
|
3678
|
+
/**
|
|
3679
|
+
* Create a logger with preset fields.
|
|
3680
|
+
*
|
|
3681
|
+
* Useful for creating component-specific loggers that automatically
|
|
3682
|
+
* include common fields in every log message.
|
|
3683
|
+
*
|
|
3684
|
+
* @param defaultFields - Fields to include in every log message
|
|
3685
|
+
* @returns Logger object with log methods
|
|
3686
|
+
*
|
|
3687
|
+
* @example
|
|
3688
|
+
* const logger = createLogger({ component: 'payment_handler' });
|
|
3689
|
+
* logger.info('Processing payment', { amount: 100 });
|
|
3690
|
+
* // Logs: { component: 'payment_handler', amount: 100 }
|
|
3691
|
+
*/
|
|
3692
|
+
declare function createLogger(defaultFields: LogFields): {
|
|
3693
|
+
error: (message: string, fields?: LogFields) => void;
|
|
3694
|
+
warn: (message: string, fields?: LogFields) => void;
|
|
3695
|
+
info: (message: string, fields?: LogFields) => void;
|
|
3696
|
+
debug: (message: string, fields?: LogFields) => void;
|
|
3697
|
+
trace: (message: string, fields?: LogFields) => void;
|
|
3698
|
+
};
|
|
3699
|
+
|
|
3700
|
+
/**
|
|
3701
|
+
* Shutdown controller for coordinating graceful shutdown.
|
|
3702
|
+
*
|
|
3703
|
+
* Provides a signal-based mechanism for triggering and awaiting
|
|
3704
|
+
* shutdown across async boundaries.
|
|
3705
|
+
*/
|
|
3706
|
+
/**
|
|
3707
|
+
* Shutdown signal handler type.
|
|
3708
|
+
*/
|
|
3709
|
+
type ShutdownHandler = () => void | Promise<void>;
|
|
3710
|
+
/**
|
|
3711
|
+
* Controller for coordinating graceful shutdown.
|
|
3712
|
+
*
|
|
3713
|
+
* Provides a promise-based mechanism for waiting on shutdown signals
|
|
3714
|
+
* and executing cleanup handlers in order.
|
|
3715
|
+
*
|
|
3716
|
+
* @example
|
|
3717
|
+
* ```typescript
|
|
3718
|
+
* const shutdown = new ShutdownController();
|
|
3719
|
+
*
|
|
3720
|
+
* process.on('SIGTERM', () => shutdown.trigger('SIGTERM'));
|
|
3721
|
+
* process.on('SIGINT', () => shutdown.trigger('SIGINT'));
|
|
3722
|
+
*
|
|
3723
|
+
* // Wait for shutdown signal
|
|
3724
|
+
* await shutdown.promise;
|
|
3725
|
+
*
|
|
3726
|
+
* // Or check if shutdown was requested
|
|
3727
|
+
* if (shutdown.isRequested) {
|
|
3728
|
+
* await cleanup();
|
|
3729
|
+
* }
|
|
3730
|
+
* ```
|
|
3731
|
+
*/
|
|
3732
|
+
declare class ShutdownController {
|
|
3733
|
+
private _shutdownRequested;
|
|
3734
|
+
private _resolver;
|
|
3735
|
+
private _signal;
|
|
3736
|
+
private readonly _handlers;
|
|
3737
|
+
/**
|
|
3738
|
+
* Promise that resolves when shutdown is triggered.
|
|
3739
|
+
*/
|
|
3740
|
+
readonly promise: Promise<void>;
|
|
3741
|
+
constructor();
|
|
3742
|
+
/**
|
|
3743
|
+
* Check if shutdown has been requested.
|
|
3744
|
+
*/
|
|
3745
|
+
get isRequested(): boolean;
|
|
3746
|
+
/**
|
|
3747
|
+
* Get the signal that triggered shutdown, if any.
|
|
3748
|
+
*/
|
|
3749
|
+
get signal(): string | null;
|
|
3750
|
+
/**
|
|
3751
|
+
* Register a handler to be called during shutdown.
|
|
3752
|
+
*
|
|
3753
|
+
* Handlers are called in registration order.
|
|
3754
|
+
*/
|
|
3755
|
+
onShutdown(handler: ShutdownHandler): void;
|
|
3756
|
+
/**
|
|
3757
|
+
* Trigger shutdown with the given signal.
|
|
3758
|
+
*
|
|
3759
|
+
* @param signal - The signal that triggered shutdown (e.g., 'SIGTERM', 'SIGINT')
|
|
3760
|
+
*/
|
|
3761
|
+
trigger(signal: string): void;
|
|
3762
|
+
/**
|
|
3763
|
+
* Execute all registered shutdown handlers.
|
|
3764
|
+
*
|
|
3765
|
+
* Handlers are called in registration order. Errors are logged
|
|
3766
|
+
* but do not prevent subsequent handlers from running.
|
|
3767
|
+
*/
|
|
3768
|
+
executeHandlers(): Promise<void>;
|
|
3769
|
+
/**
|
|
3770
|
+
* Reset the controller for reuse (primarily for testing).
|
|
3771
|
+
*/
|
|
3772
|
+
reset(): void;
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
/**
|
|
3776
|
+
* Server module types.
|
|
3777
|
+
*
|
|
3778
|
+
* Defines types for WorkerServer lifecycle and configuration.
|
|
3779
|
+
*/
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* Server state constants.
|
|
3783
|
+
*
|
|
3784
|
+
* Use these constants instead of raw strings for type safety and consistency.
|
|
3785
|
+
*
|
|
3786
|
+
* State transitions:
|
|
3787
|
+
* - INITIALIZED -> STARTING -> RUNNING -> SHUTTING_DOWN -> STOPPED
|
|
3788
|
+
* - Any state can transition to ERROR on fatal failures
|
|
3789
|
+
*/
|
|
3790
|
+
declare const ServerStates: {
|
|
3791
|
+
/** Initial state after construction */
|
|
3792
|
+
readonly INITIALIZED: "initialized";
|
|
3793
|
+
/** Starting up: loading FFI, registering handlers, bootstrapping */
|
|
3794
|
+
readonly STARTING: "starting";
|
|
3795
|
+
/** Running and processing events */
|
|
3796
|
+
readonly RUNNING: "running";
|
|
3797
|
+
/** Graceful shutdown in progress */
|
|
3798
|
+
readonly SHUTTING_DOWN: "shutting_down";
|
|
3799
|
+
/** Fully stopped */
|
|
3800
|
+
readonly STOPPED: "stopped";
|
|
3801
|
+
/** Error state - fatal failure occurred */
|
|
3802
|
+
readonly ERROR: "error";
|
|
3803
|
+
};
|
|
3804
|
+
/**
|
|
3805
|
+
* Server lifecycle states (union type derived from constants).
|
|
3806
|
+
*/
|
|
3807
|
+
type ServerState = (typeof ServerStates)[keyof typeof ServerStates];
|
|
3808
|
+
/**
|
|
3809
|
+
* Worker server configuration.
|
|
3810
|
+
*
|
|
3811
|
+
* All fields are optional with sensible defaults.
|
|
3812
|
+
*/
|
|
3813
|
+
interface WorkerServerConfig {
|
|
3814
|
+
/** Task namespace to handle (default: "default") */
|
|
3815
|
+
namespace?: string;
|
|
3816
|
+
/** Log level for Rust tracing (default: "info") */
|
|
3817
|
+
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
3818
|
+
/** Path to TOML configuration file */
|
|
3819
|
+
configPath?: string;
|
|
3820
|
+
/** PostgreSQL database connection URL */
|
|
3821
|
+
databaseUrl?: string;
|
|
3822
|
+
/** Path to the FFI library (overrides auto-discovery) */
|
|
3823
|
+
libraryPath?: string;
|
|
3824
|
+
/** Maximum concurrent handler executions (default: 10) */
|
|
3825
|
+
maxConcurrentHandlers?: number;
|
|
3826
|
+
/** Handler execution timeout in milliseconds (default: 300000 = 5 minutes) */
|
|
3827
|
+
handlerTimeoutMs?: number;
|
|
3828
|
+
/** Event polling interval in milliseconds (default: 10) */
|
|
3829
|
+
pollIntervalMs?: number;
|
|
3830
|
+
/** Starvation check interval in poll cycles (default: 100) */
|
|
3831
|
+
starvationCheckInterval?: number;
|
|
3832
|
+
/** Cleanup interval in poll cycles (default: 1000) */
|
|
3833
|
+
cleanupInterval?: number;
|
|
3834
|
+
/** Metrics reporting interval in poll cycles (default: 100) */
|
|
3835
|
+
metricsInterval?: number;
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Health check result.
|
|
3839
|
+
*/
|
|
3840
|
+
interface HealthCheckResult {
|
|
3841
|
+
/** Whether the worker is healthy */
|
|
3842
|
+
healthy: boolean;
|
|
3843
|
+
/** Optional status details when healthy */
|
|
3844
|
+
status?: ServerStatus;
|
|
3845
|
+
/** Error message when unhealthy */
|
|
3846
|
+
error?: string;
|
|
3847
|
+
}
|
|
3848
|
+
/**
|
|
3849
|
+
* Server status information.
|
|
3850
|
+
*/
|
|
3851
|
+
interface ServerStatus {
|
|
3852
|
+
/** Current server state */
|
|
3853
|
+
state: ServerState;
|
|
3854
|
+
/** Worker identifier */
|
|
3855
|
+
workerId: string | null;
|
|
3856
|
+
/** Whether the worker is actively running */
|
|
3857
|
+
running: boolean;
|
|
3858
|
+
/** Number of events processed */
|
|
3859
|
+
processedCount: number;
|
|
3860
|
+
/** Number of errors encountered */
|
|
3861
|
+
errorCount: number;
|
|
3862
|
+
/** Number of currently active handlers */
|
|
3863
|
+
activeHandlers: number;
|
|
3864
|
+
/** Server uptime in milliseconds */
|
|
3865
|
+
uptimeMs: number;
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3868
|
+
* Internal server components.
|
|
3869
|
+
*
|
|
3870
|
+
* Used by WorkerServer to manage lifecycle of all components.
|
|
3871
|
+
*/
|
|
3872
|
+
interface ServerComponents {
|
|
3873
|
+
/** FFI runtime instance */
|
|
3874
|
+
runtime: TaskerRuntime;
|
|
3875
|
+
/** Event emitter for step events */
|
|
3876
|
+
emitter: TaskerEventEmitter;
|
|
3877
|
+
/** Handler registry */
|
|
3878
|
+
registry: HandlerRegistry;
|
|
3879
|
+
/** Event poller for FFI events */
|
|
3880
|
+
eventPoller: EventPoller;
|
|
3881
|
+
/** Step execution subscriber */
|
|
3882
|
+
stepSubscriber: StepExecutionSubscriber;
|
|
3883
|
+
/** Worker identifier from bootstrap */
|
|
3884
|
+
workerId: string;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
/**
|
|
3888
|
+
* WorkerServer - Orchestrates the TypeScript worker lifecycle.
|
|
3889
|
+
*
|
|
3890
|
+
* Manages the complete lifecycle of a TypeScript worker:
|
|
3891
|
+
* - FFI library loading (via FfiLayer)
|
|
3892
|
+
* - Handler registration (via HandlerSystem)
|
|
3893
|
+
* - Rust worker bootstrapping
|
|
3894
|
+
* - Event processing (via EventSystem)
|
|
3895
|
+
* - Graceful shutdown
|
|
3896
|
+
*
|
|
3897
|
+
* Design principles:
|
|
3898
|
+
* - Explicit construction: No singleton pattern - caller creates and manages
|
|
3899
|
+
* - Clear ownership: Owns FfiLayer, HandlerSystem, EventSystem
|
|
3900
|
+
* - Explicit lifecycle: Clear 3-phase startup and shutdown
|
|
3901
|
+
*
|
|
3902
|
+
* @example
|
|
3903
|
+
* ```typescript
|
|
3904
|
+
* const server = new WorkerServer();
|
|
3905
|
+
*
|
|
3906
|
+
* await server.start({
|
|
3907
|
+
* namespace: 'payments',
|
|
3908
|
+
* logLevel: 'debug',
|
|
3909
|
+
* });
|
|
3910
|
+
*
|
|
3911
|
+
* // Server is now running and processing tasks
|
|
3912
|
+
*
|
|
3913
|
+
* await server.shutdown();
|
|
3914
|
+
* ```
|
|
3915
|
+
*/
|
|
3916
|
+
|
|
3917
|
+
/**
|
|
3918
|
+
* Worker server class.
|
|
3919
|
+
*
|
|
3920
|
+
* Unlike the previous singleton pattern, this class is instantiated directly
|
|
3921
|
+
* by the caller (typically bin/server.ts). This provides explicit lifecycle
|
|
3922
|
+
* control and clear dependency ownership.
|
|
3923
|
+
*/
|
|
3924
|
+
declare class WorkerServer {
|
|
3925
|
+
private readonly ffiLayer;
|
|
3926
|
+
private readonly handlerSystem;
|
|
3927
|
+
private eventSystem;
|
|
3928
|
+
private state;
|
|
3929
|
+
private config;
|
|
3930
|
+
private workerId;
|
|
3931
|
+
private startTime;
|
|
3932
|
+
private shutdownHandlers;
|
|
3933
|
+
/**
|
|
3934
|
+
* Create a new WorkerServer.
|
|
3935
|
+
*
|
|
3936
|
+
* @param ffiConfig - Optional FFI layer configuration
|
|
3937
|
+
*/
|
|
3938
|
+
constructor(ffiConfig?: FfiLayerConfig);
|
|
3939
|
+
/**
|
|
3940
|
+
* Get the current server state.
|
|
3941
|
+
*/
|
|
3942
|
+
getState(): ServerState;
|
|
3943
|
+
/**
|
|
3944
|
+
* Get the worker identifier.
|
|
3945
|
+
*/
|
|
3946
|
+
getWorkerId(): string | null;
|
|
3947
|
+
/**
|
|
3948
|
+
* Check if the server is currently running.
|
|
3949
|
+
*/
|
|
3950
|
+
isRunning(): boolean;
|
|
3951
|
+
/**
|
|
3952
|
+
* Get the handler system for external handler registration.
|
|
3953
|
+
*/
|
|
3954
|
+
getHandlerSystem(): HandlerSystem;
|
|
3955
|
+
/**
|
|
3956
|
+
* Get the event system (available after start).
|
|
3957
|
+
*/
|
|
3958
|
+
getEventSystem(): EventSystem | null;
|
|
3959
|
+
/**
|
|
3960
|
+
* Start the worker server.
|
|
3961
|
+
*
|
|
3962
|
+
* Three-phase startup:
|
|
3963
|
+
* 1. Initialize: Load FFI, load handlers
|
|
3964
|
+
* 2. Bootstrap: Initialize Rust worker
|
|
3965
|
+
* 3. Start: Begin event processing
|
|
3966
|
+
*
|
|
3967
|
+
* @param config - Optional server configuration
|
|
3968
|
+
* @returns The server instance for chaining
|
|
3969
|
+
* @throws Error if server is already running or fails to start
|
|
3970
|
+
*/
|
|
3971
|
+
start(config?: WorkerServerConfig): Promise<this>;
|
|
3972
|
+
/**
|
|
3973
|
+
* Shutdown the worker server gracefully.
|
|
3974
|
+
*
|
|
3975
|
+
* Three-phase shutdown:
|
|
3976
|
+
* 1. Stop event processing
|
|
3977
|
+
* 2. Stop Rust worker
|
|
3978
|
+
* 3. Unload FFI
|
|
3979
|
+
*/
|
|
3980
|
+
shutdown(): Promise<void>;
|
|
3981
|
+
/**
|
|
3982
|
+
* Register a handler to be called during shutdown.
|
|
3983
|
+
*
|
|
3984
|
+
* @param handler - Function to execute during shutdown
|
|
3985
|
+
*/
|
|
3986
|
+
onShutdown(handler: () => void | Promise<void>): void;
|
|
3987
|
+
/**
|
|
3988
|
+
* Perform a health check on the worker.
|
|
3989
|
+
*/
|
|
3990
|
+
healthCheck(): HealthCheckResult;
|
|
3991
|
+
/**
|
|
3992
|
+
* Get detailed server status.
|
|
3993
|
+
*/
|
|
3994
|
+
status(): ServerStatus;
|
|
3995
|
+
/**
|
|
3996
|
+
* Phase 1: Initialize FFI and handlers.
|
|
3997
|
+
*/
|
|
3998
|
+
private initializePhase;
|
|
3999
|
+
/**
|
|
4000
|
+
* Phase 2: Bootstrap Rust worker.
|
|
4001
|
+
*/
|
|
4002
|
+
private bootstrapPhase;
|
|
4003
|
+
/**
|
|
4004
|
+
* Phase 3: Start event processing.
|
|
4005
|
+
*/
|
|
4006
|
+
private startEventProcessingPhase;
|
|
4007
|
+
/**
|
|
4008
|
+
* Cleanup on error during startup.
|
|
4009
|
+
*/
|
|
4010
|
+
private cleanupOnError;
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
export { type APICapable, APIMixin, ApiHandler, ApiResponse, BasePublisher, type BaseResolver, BaseSubscriber, type BatchAggregationResult, type BatchAnalyzerOutcome, type BatchMetadata, type BatchProcessingOutcome, type BatchWorkerContext, type BatchWorkerOutcome, type Batchable, BatchableMixin, type BootstrapConfig, type BootstrapResult, ClassLookupResolver, type CreateBatchesOutcome, type CursorConfig, type DecisionCapable, DecisionHandler, DecisionMixin, type DecisionPointOutcome, DecisionType, DefaultPublisher, type DomainEvent, type DomainEventCallback, type DomainEventErrorCallback, type DomainEventMetadata, type DomainEventPollerConfig, DuplicatePublisherError, type EventDeclaration, EventPoller, EventSystem, ExecutableHandler, ExplicitMappingResolver, type FailureStrategy, FfiLayerConfig, type HandlerEntry, type HandlerFactory, HandlerRegistry, type HandlerSpec, HandlerSystem, InProcessDomainEventPoller, type LogFields, MethodDispatchError, MethodDispatchWrapper, type NoBatchesOutcome, NoResolverMatchError, type PollerStats, type PublishContext, PublisherNotFoundError, PublisherRegistry, PublisherValidationError, RegistryFrozenError, RegistryResolver, type RegistryResolverStatic, ResolutionError, ResolverChain, type ResolverConfig, ResolverNotFoundError, type RustBatchWorkerInputs, type RustCursorConfig, type ServerComponents, type HealthCheckResult as ServerHealthCheckResult, type ServerState, type ServerStatus, ShutdownController, type ShutdownHandler, StepContext, type StepEventContext, StepExecutionSubscriber, StepHandler, StepHandlerClass, StepHandlerResult, type StepResult, type StopResult, type SubscriberClass, SubscriberRegistry, type SubscriberStats, TaskerEventEmitter, TaskerRuntime, WorkerServer, type WorkerServerConfig, type WorkerStatus, aggregateBatchResults, applyAPI, applyBatchable, applyDecision, bootstrapWorker, createBatchWorkerContext, createBatches, createDomainEvent, createFfiPollAdapter, createLogger, createStepEventContext, effectiveMethod, ffiEventToDomainEvent, fromCallable, fromDto, getRustVersion, getVersion, getWorkerStatus, hasResolverHint, healthCheck, isCreateBatches, isNoBatches, isWorkerRunning, logDebug, logError, logInfo, logTrace, logWarn, noBatches, normalizeToDefinition, stopWorker, transitionToGracefulShutdown, usesMethodDispatch };
|