@kaleido-io/workflow-engine-sdk 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +486 -362
  2. package/bin/init.js +14 -15
  3. package/dist/src/client/client.d.ts +22 -0
  4. package/dist/src/client/client.d.ts.map +1 -1
  5. package/dist/src/client/client.js +1 -0
  6. package/dist/src/client/client.js.map +1 -1
  7. package/dist/src/client/client_factory.d.ts +14 -0
  8. package/dist/src/client/client_factory.d.ts.map +1 -0
  9. package/dist/src/client/client_factory.js +64 -0
  10. package/dist/src/client/client_factory.js.map +1 -0
  11. package/dist/src/client/rest-client.d.ts.map +1 -1
  12. package/dist/src/client/rest-client.js +1 -1
  13. package/dist/src/client/rest-client.js.map +1 -1
  14. package/dist/src/config/config.d.ts +81 -1
  15. package/dist/src/config/config.d.ts.map +1 -1
  16. package/dist/src/config/config.js +269 -24
  17. package/dist/src/config/config.js.map +1 -1
  18. package/dist/src/config/config_helpers.d.ts +44 -0
  19. package/dist/src/config/config_helpers.d.ts.map +1 -0
  20. package/dist/src/config/config_helpers.js +68 -0
  21. package/dist/src/config/config_helpers.js.map +1 -0
  22. package/dist/src/helpers/stage_director.js +2 -2
  23. package/dist/src/i18n/errors.d.ts +5 -0
  24. package/dist/src/i18n/errors.d.ts.map +1 -1
  25. package/dist/src/i18n/errors.js +8 -3
  26. package/dist/src/i18n/errors.js.map +1 -1
  27. package/dist/src/index.d.ts +3 -2
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +5 -1
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/runtime/handler_runtime.d.ts +22 -0
  32. package/dist/src/runtime/handler_runtime.d.ts.map +1 -1
  33. package/dist/src/runtime/handler_runtime.js +43 -3
  34. package/dist/src/runtime/handler_runtime.js.map +1 -1
  35. package/dist/src/utils/patch.d.ts +1 -1
  36. package/dist-esm/src/client/client.js +2 -1
  37. package/dist-esm/src/client/client.js.map +1 -1
  38. package/dist-esm/src/client/client_factory.js +60 -0
  39. package/dist-esm/src/client/client_factory.js.map +1 -0
  40. package/dist-esm/src/client/rest-client.js +1 -1
  41. package/dist-esm/src/client/rest-client.js.map +1 -1
  42. package/dist-esm/src/config/config.js +235 -25
  43. package/dist-esm/src/config/config.js.map +1 -1
  44. package/dist-esm/src/config/config_helpers.js +61 -0
  45. package/dist-esm/src/config/config_helpers.js.map +1 -0
  46. package/dist-esm/src/helpers/stage_director.js +2 -2
  47. package/dist-esm/src/i18n/errors.js +8 -3
  48. package/dist-esm/src/i18n/errors.js.map +1 -1
  49. package/dist-esm/src/index.js +2 -1
  50. package/dist-esm/src/index.js.map +1 -1
  51. package/dist-esm/src/runtime/handler_runtime.js +38 -2
  52. package/dist-esm/src/runtime/handler_runtime.js.map +1 -1
  53. package/dist-esm/src/utils/patch.js +2 -2
  54. package/package.json +1 -1
  55. package/template/README.md +5 -8
  56. package/template/config/config.yaml +12 -0
  57. package/template/config/wfe-config.yaml +12 -0
  58. package/template/package-lock.json +1908 -0
  59. package/template/package.json +4 -2
  60. package/template/src/connect.ts +35 -37
  61. package/template/src/provider.ts +4 -4
  62. package/template/src/samples/event-source/echo-handler.ts +2 -2
  63. package/template/src/samples/event-source/event-processor.ts +1 -1
  64. package/template/src/samples/event-source/event-source.ts +1 -1
  65. package/template/src/samples/event-source/stream.ts +11 -11
  66. package/template/src/samples/hello/flow.ts +36 -36
  67. package/template/src/samples/hello/handlers.ts +2 -2
  68. package/template/src/samples/hello/transaction.ts +4 -4
  69. package/template/src/samples/http-invoke/flow.ts +29 -29
  70. package/template/src/samples/http-invoke/handlers.ts +88 -39
  71. package/template/src/samples/http-invoke/transaction.ts +3 -3
  72. package/template/src/samples/snap/event-source.ts +1 -1
  73. package/template/src/samples/snap/flow.ts +40 -40
  74. package/template/src/samples/snap/snap-handler.ts +2 -2
  75. package/template/src/samples/snap/transaction.ts +6 -6
  76. package/template/.env.sample +0 -14
package/README.md CHANGED
@@ -23,43 +23,48 @@ This will create a new project in a directory named for project-name, and in a f
23
23
  ### Integrating into an existing project
24
24
 
25
25
  ```typescript
26
- import {
27
- WorkflowEngineClient,
26
+ import {
27
+ WorkflowEngineClient,
28
28
  ConfigLoader,
29
29
  WorkflowEngineConfig,
30
30
  newDirectedTransactionHandler,
31
31
  InvocationMode,
32
- EvalResult
33
- } from '@kaleido-io/workflow-engine-sdk';
34
- import * as fs from 'fs';
35
- import * as yaml from 'js-yaml';
32
+ EvalResult,
33
+ } from "@kaleido-io/workflow-engine-sdk";
34
+ import * as fs from "fs";
35
+ import * as yaml from "js-yaml";
36
36
 
37
37
  // 1. Load configuration (your application handles file loading)
38
- const configFile = fs.readFileSync('./config.yaml', 'utf8');
39
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
38
+ const configFile = fs.readFileSync("./config.yaml", "utf8");
39
+ const config: WorkflowEngineConfig = yaml.load(
40
+ configFile,
41
+ ) as WorkflowEngineConfig;
40
42
 
41
43
  // 2. Use SDK's ConfigLoader to create client config with your provider name
42
44
  // The SDK handles authentication header setup and URL conversion automatically
43
- const clientConfig = ConfigLoader.createClientConfig(config, 'my-service');
45
+ const clientConfig = ConfigLoader.createClientConfig(config, "my-service");
44
46
 
45
47
  // 3. Create client
46
48
  const client = new WorkflowEngineClient(clientConfig);
47
49
 
48
50
  // 4. Create and register transaction handler
49
51
  const actionMap = new Map([
50
- ['myAction', {
51
- invocationMode: InvocationMode.PARALLEL,
52
- handler: async (transaction, input) => {
53
- return {
54
- result: EvalResult.COMPLETE,
55
- output: { success: true }
56
- };
57
- }
58
- }]
52
+ [
53
+ "myAction",
54
+ {
55
+ invocationMode: InvocationMode.PARALLEL,
56
+ handler: async (transaction, input) => {
57
+ return {
58
+ result: EvalResult.COMPLETE,
59
+ output: { success: true },
60
+ };
61
+ },
62
+ },
63
+ ],
59
64
  ]);
60
65
 
61
- const handler = newDirectedTransactionHandler('my-handler', actionMap);
62
- client.registerTransactionHandler('my-handler', handler);
66
+ const handler = newDirectedTransactionHandler("my-handler", actionMap);
67
+ client.registerTransactionHandler("my-handler", handler);
63
68
 
64
69
  // 5. Connect
65
70
  await client.connect();
@@ -70,6 +75,7 @@ await client.connect();
70
75
  ### WorkflowEngineClient
71
76
 
72
77
  The main entry point that manages:
78
+
73
79
  - Handler registration (transaction handlers and event sources)
74
80
  - WebSocket connection lifecycle
75
81
  - Automatic reconnection and re-registration
@@ -77,24 +83,24 @@ The main entry point that manages:
77
83
 
78
84
  ```typescript
79
85
  const client = new WorkflowEngineClient({
80
- url: 'ws://localhost:5503/ws',
81
- providerName: 'my-service',
82
- authToken: 'your-token',
83
- authHeaderName: 'X-Kld-Authz', // Optional, defaults to X-Kld-Authz
84
- reconnectDelay: 2000, // Optional, ms between reconnect attempts
85
- maxAttempts: undefined // Optional, undefined = infinite retries (recommended)
86
+ url: "ws://localhost:5503/ws",
87
+ providerName: "my-service",
88
+ authToken: "your-token",
89
+ authHeaderName: "X-Kld-Authz", // Optional, defaults to X-Kld-Authz
90
+ reconnectDelay: 2000, // Optional, ms between reconnect attempts
91
+ maxAttempts: undefined, // Optional, undefined = infinite retries (recommended)
86
92
  });
87
93
 
88
94
  // Register handlers
89
- client.registerTransactionHandler('handler-name', transactionHandler);
90
- client.registerEventSource('source-name', eventSource);
95
+ client.registerTransactionHandler("handler-name", transactionHandler);
96
+ client.registerEventSource("source-name", eventSource);
91
97
 
92
98
  // Connect
93
99
  await client.connect();
94
100
 
95
101
  // Check connection status
96
102
  if (client.isConnected()) {
97
- console.log('Connected!');
103
+ console.log("Connected!");
98
104
  }
99
105
 
100
106
  // Disconnect
@@ -103,57 +109,75 @@ client.disconnect();
103
109
 
104
110
  ### Configuration file format
105
111
 
106
- If you choose to use YAML files, create a configuration file like this:
112
+ The SDK only accepts the root key **`workflow-engine`** in config files (not `workflowEngine`). Use one of two modes:
113
+
114
+ - **Outbound:** Provide `url` and `auth`. The app (SDK client) connects to the workflow engine at that URL.
115
+ - **Inbound:** Provide `server` with `address` and `port`. The app creates a WebSocket server on that address/port; the workflow engine connects to the app. Auth is not used. Optional `server.tls` (`enabled`, `caFile`, `certFile`, `keyFile`, `clientAuth`) enables TLS for the server.
116
+
117
+ Delay fields (`retryDelay`, etc.) use time strings: `ms`, `s`, `m`, `h` (e.g. `"2s"`, `"30s"`, `"100ms"`, `"1m"`).
118
+
119
+ **Example — outbound (local dev) with basic auth:**
107
120
 
108
121
  ```yaml
109
- # Basic authentication (username/password)
110
- workflowEngine:
122
+ workflow-engine:
123
+ providerName: my-service
111
124
  url: http://localhost:5503
112
125
  auth:
113
126
  type: basic
114
127
  username: my-user
115
128
  password: my-password
116
129
  # maxRetries: undefined = infinite reconnection (recommended)
117
- # maxRetries: 5 # Optional: limit reconnection attempts
130
+ # retryDelay: "2s" (time string: ms, s, m, h)
118
131
  retryDelay: 2s
119
- timeout: 30s
120
- batchSize: 10
121
- batchTimeout: 500ms
122
- pollDuration: 2s
123
132
  ```
124
133
 
125
- **Or use token authentication:**
134
+ **Example outbound with token auth:**
126
135
 
127
136
  ```yaml
128
- # Token authentication (API key, JWT, etc.)
129
- workflowEngine:
137
+ workflow-engine:
138
+ providerName: my-service
130
139
  url: http://localhost:5503
131
140
  auth:
132
141
  type: token
133
142
  token: dev-token-123
134
- header: X-Kld-Authz # Optional, defaults to Authorization
135
- scheme: "" # Optional, e.g. "Bearer" for "Bearer <token>"
136
- # maxRetries: undefined = infinite reconnection (recommended for long-running services)
143
+ header: X-Kld-Authz # optional, defaults to Authorization
144
+ scheme: "" # optional, e.g. "Bearer" for "Bearer <token>"
137
145
  retryDelay: 2s
138
146
  ```
139
147
 
148
+ **Example — inbound (app creates WebSocket server):**
149
+
150
+ ```yaml
151
+ workflow-engine:
152
+ providerName: my-service
153
+ providerMetadata: {}
154
+ server:
155
+ address: "0.0.0.0"
156
+ port: 6000
157
+ heartbeatInterval: "30s"
158
+ requestsPerSecond: 100
159
+ burst: 200
160
+ ```
161
+
140
162
  Load and use configuration:
141
163
 
142
164
  ```typescript
143
- import {
144
- ConfigLoader,
145
- WorkflowEngineConfig
146
- } from '@kaleido-io/workflow-engine-sdk';
147
- import * as fs from 'fs';
148
- import * as yaml from 'js-yaml';
165
+ import {
166
+ ConfigLoader,
167
+ WorkflowEngineConfig,
168
+ } from "@kaleido-io/workflow-engine-sdk";
169
+ import * as fs from "fs";
170
+ import * as yaml from "js-yaml";
149
171
 
150
172
  // Your application loads configuration (the SDK doesn't load files)
151
- const configFile = fs.readFileSync('./config.yaml', 'utf8');
152
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
173
+ const configFile = fs.readFileSync("./config.yaml", "utf8");
174
+ const config: WorkflowEngineConfig = yaml.load(
175
+ configFile,
176
+ ) as WorkflowEngineConfig;
153
177
 
154
178
  // Use SDK's ConfigLoader to create client config with provider name (REQUIRED)
155
179
  // Note: SDK automatically converts http:// to ws:// and adds /ws path
156
- const clientConfig = ConfigLoader.createClientConfig(config, 'my-service');
180
+ const clientConfig = ConfigLoader.createClientConfig(config, "my-service");
157
181
 
158
182
  // Optionally log summary (without sensitive data)
159
183
  ConfigLoader.logConfigSummary(config);
@@ -163,25 +187,69 @@ const client = new WorkflowEngineClient(clientConfig);
163
187
  ```
164
188
 
165
189
  **URL Handling:**
190
+
166
191
  - Config file uses HTTP URL: `http://localhost:5503` or `https://example.com`
167
192
  - SDK automatically converts to WebSocket: `ws://localhost:5503/ws` or `wss://example.com/ws`
168
193
  - `/ws` path is automatically added if not present
169
194
 
195
+ ### Use configuration files setup
196
+
197
+ Two config files are required for this setup:
198
+
199
+ 1. **WFE config (SDK contract)** — Workflow engine connection and identity. Path from `WFE_CONFIG_FILE` or pass `configFile` to `NewWorkflowEngineClient`. Root key in YAML must be **`workflow-engine`**. **Outbound:** `providerName`, `url`, and `auth`. **Inbound:** `providerName` and `server` (address, port); the app creates a WebSocket server and the engine connects to it. Optional `server.tls` for TLS. Delay fields use time strings (e.g. `retryDelay: "2s"`).
200
+ 2. **Provider config (application-owned)** — App-specific settings. Path from `CONFIG_FILE` or `-f`; your app loads and uses it (e.g. to build handlers). The SDK does not read or define its schema.
201
+
202
+ Example run:
203
+
204
+ ```bash
205
+ CONFIG_FILE=./config.yaml WFE_CONFIG_FILE=./wfe-config.yaml node connect.js
206
+ ```
207
+
208
+ ### New workflow engine client using config file
209
+
210
+ To initialize a Workflow Engine Client with using single function, use `NewWorkflowEngineClient` and `HandlerSetFor`:
211
+
212
+ ```typescript
213
+ import {
214
+ NewWorkflowEngineClient,
215
+ HandlerSetFor,
216
+ WFE_CONFIG_FILE,
217
+ } from "@kaleido-io/workflow-engine-sdk";
218
+
219
+ // Build your handlers (e.g. from provider config in app code)
220
+ const handler = newEventProcessorFromConfig(providerConfig);
221
+ const txHandler = newDirectedTransactionHandler("my-handler", actionMap);
222
+
223
+ // Create and start runtime (loads WFE config from file, registers handlers, connects)
224
+ const runtime = await NewWorkflowEngineClient(
225
+ HandlerSetFor(handler, txHandler),
226
+ process.env[WFE_CONFIG_FILE] ?? "./wfe-config.yaml",
227
+ );
228
+ // runtime is already connected
229
+ // On shutdown:
230
+ runtime.disconnect();
231
+ ```
232
+
233
+ - **HandlerSetFor(...handlers)**: Builds a handler set from one or more transaction handlers, event sources, or event processors.
234
+ - **NewWorkflowEngineClient(handlerSet, configFile?)**: Loads WFE config from file (when `configFile` or `WFE_CONFIG_FILE` env is set), creates the client, registers all handlers, connects, and returns the client. Uses `ConfigLoader.loadClientConfigFromFile` under the hood.
235
+
236
+ To load client config from a WFE config file without using `NewWorkflowEngineClient` (e.g. for custom startup), use `ConfigLoader.loadClientConfigFromFile(configFilePath?)`. If `configFilePath` is omitted, `process.env[WFE_CONFIG_FILE]` is used. The file must use the root key **`workflow-engine`** only. **Outbound:** include `providerName`, `url`, and `auth`. **Inbound:** include `providerName` and `server` (address, port); the app will create a WebSocket server and auth is not used.
237
+
170
238
  ### Configuration Schema
171
239
 
172
240
  ```typescript
173
241
  interface WorkflowEngineConfig {
174
242
  workflowEngine: {
175
- mode?: HandlerRuntimeMode; // Defaults to outbound
176
- port?: number; // port used for the web socket server in inbound mode
177
- url?: string; // Workflow engine URL
178
- auth?: AuthConfig; // Authentication (see below)
179
- timeout?: string; // Request timeout (e.g. "30s")
180
- maxRetries?: number; // Max reconnection attempts (undefined = infinite)
181
- retryDelay?: string; // Delay between retries (e.g. "2s")
182
- batchSize?: number; // Batch size for handlers
183
- batchTimeout?: string; // Batch timeout (e.g. "500ms")
184
- pollDuration?: string; // Event source poll duration
243
+ mode?: HandlerRuntimeMode; // Defaults to outbound
244
+ port?: number; // port used for the web socket server in inbound mode
245
+ url?: string; // Workflow engine URL
246
+ auth?: AuthConfig; // Authentication (see below)
247
+ timeout?: string; // Request timeout (e.g. "30s")
248
+ maxRetries?: number; // Max reconnection attempts (undefined = infinite)
249
+ retryDelay?: string; // Delay between retries (e.g. "2s")
250
+ batchSize?: number; // Batch size for handlers
251
+ batchTimeout?: string; // Batch timeout (e.g. "500ms")
252
+ pollDuration?: string; // Event source poll duration
185
253
  };
186
254
  }
187
255
 
@@ -189,24 +257,26 @@ interface WorkflowEngineConfig {
189
257
  type AuthConfig = BasicAuth | TokenAuth;
190
258
 
191
259
  interface BasicAuth {
192
- type: 'basic'; // Must be 'basic'
193
- username: string; // Username
194
- password: string; // Password
260
+ type: "basic"; // Must be 'basic'
261
+ username: string; // Username
262
+ password: string; // Password
195
263
  }
196
264
 
197
265
  interface TokenAuth {
198
- type: 'token'; // Must be 'token'
199
- token: string; // API token
200
- header?: string; // Header name (default: 'Authorization')
201
- scheme?: string; // Scheme (e.g. 'Bearer', default: '')
266
+ type: "token"; // Must be 'token'
267
+ token: string; // API token
268
+ header?: string; // Header name (default: 'Authorization')
269
+ scheme?: string; // Scheme (e.g. 'Bearer', default: '')
202
270
  }
203
271
  ```
204
272
 
205
- ### Configuration examples
273
+ ### Configuration examples (config file: root key `workflow-engine` only)
274
+
275
+ **Outbound — basic auth:**
206
276
 
207
- **Outbound, basic auth:**
208
277
  ```yaml
209
- workflowEngine:
278
+ workflow-engine:
279
+ providerName: my-service
210
280
  url: http://localhost:5503
211
281
  auth:
212
282
  type: basic
@@ -214,45 +284,60 @@ workflowEngine:
214
284
  password: secret123
215
285
  ```
216
286
 
217
- **Outbound, token auth (raw token):**
287
+ **Outbound token auth (raw token):**
288
+
218
289
  ```yaml
219
- workflowEngine:
290
+ workflow-engine:
291
+ providerName: my-service
220
292
  url: http://localhost:5503
221
293
  auth:
222
294
  type: token
223
295
  token: dev-token-123
224
296
  header: X-Kld-Authz
225
- scheme: "" # Empty string = raw token
297
+ scheme: "" # Empty string = raw token
226
298
  ```
227
299
 
228
- **Outbound, token auth (bearer token):**
300
+ **Outbound token auth (bearer):**
301
+
229
302
  ```yaml
230
- workflowEngine:
303
+ workflow-engine:
304
+ providerName: my-service
231
305
  url: http://localhost:5503
232
306
  auth:
233
307
  type: token
234
308
  token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
235
- scheme: Bearer # Sends "Bearer <token>"
309
+ scheme: Bearer
236
310
  ```
237
311
 
238
- **Inbound:**
312
+ **Hosted (server; auth not needed):**
239
313
 
240
- The client will wait for an inbound connection from the workflow engine
241
314
  ```yaml
242
- workflowEngine:
243
- mode: inbound
244
- port: 12345
315
+ workflow-engine:
316
+ providerName: my-service
317
+ providerMetadata: {}
318
+ server:
319
+ address: "0.0.0.0"
320
+ port: 6000
321
+ heartbeatInterval: "30s"
322
+ requestsPerSecond: 100
323
+ burst: 200
245
324
  ```
246
325
 
247
326
  **With environment variable overrides:**
327
+
248
328
  ```typescript
249
- import * as fs from 'fs';
250
- import * as yaml from 'js-yaml';
251
- import { ConfigLoader, WorkflowEngineConfig } from '@kaleido-io/workflow-engine-sdk';
329
+ import * as fs from "fs";
330
+ import * as yaml from "js-yaml";
331
+ import {
332
+ ConfigLoader,
333
+ WorkflowEngineConfig,
334
+ } from "@kaleido-io/workflow-engine-sdk";
252
335
 
253
336
  // Your application loads and merges config with env vars
254
- const configFile = fs.readFileSync('./config.yaml', 'utf8');
255
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
337
+ const configFile = fs.readFileSync("./config.yaml", "utf8");
338
+ const config: WorkflowEngineConfig = yaml.load(
339
+ configFile,
340
+ ) as WorkflowEngineConfig;
256
341
 
257
342
  // Override URL from environment
258
343
  if (process.env.WORKFLOW_ENGINE_URL) {
@@ -260,13 +345,15 @@ if (process.env.WORKFLOW_ENGINE_URL) {
260
345
  }
261
346
 
262
347
  // Override token from environment
263
- if (process.env.WORKFLOW_ENGINE_TOKEN &&
264
- config.workflowEngine.auth.type === 'token') {
348
+ if (
349
+ process.env.WORKFLOW_ENGINE_TOKEN &&
350
+ config.workflowEngine.auth.type === "token"
351
+ ) {
265
352
  config.workflowEngine.auth.token = process.env.WORKFLOW_ENGINE_TOKEN;
266
353
  }
267
354
 
268
355
  // SDK transforms config into client config
269
- const clientConfig = ConfigLoader.createClientConfig(config, 'my-service');
356
+ const clientConfig = ConfigLoader.createClientConfig(config, "my-service");
270
357
  ```
271
358
 
272
359
  ## Transaction handlers
@@ -276,12 +363,12 @@ const clientConfig = ConfigLoader.createClientConfig(config, 'my-service');
276
363
  The recommended approach for building transaction handlers:
277
364
 
278
365
  ```typescript
279
- import {
366
+ import {
280
367
  newDirectedTransactionHandler,
281
368
  InvocationMode,
282
369
  EvalResult,
283
- Patch
284
- } from '@kaleido-io/workflow-engine-sdk';
370
+ Patch,
371
+ } from "@kaleido-io/workflow-engine-sdk";
285
372
 
286
373
  // Define your input type
287
374
  interface MyInput {
@@ -291,53 +378,58 @@ interface MyInput {
291
378
 
292
379
  // Create action map
293
380
  const actionMap = new Map([
294
- ['processData', {
295
- invocationMode: InvocationMode.PARALLEL,
296
- handler: async (transaction, input: MyInput) => {
297
- // Process the data
298
- const result = processData(input.data);
299
-
300
- return {
301
- result: EvalResult.COMPLETE,
302
- output: { processed: result },
303
- extraUpdates: [
304
- Patch.add('/processedData', result)
305
- ]
306
- };
307
- }
308
- }],
309
-
310
- ['batchProcess', {
311
- invocationMode: InvocationMode.BATCH,
312
- batchHandler: async (transactions) => {
313
- // Process all transactions together
314
- const results = await processBatch(transactions.map(r => r.value));
315
-
316
- return results.map(result => ({
317
- result: EvalResult.COMPLETE,
318
- output: result
319
- }));
320
- }
321
- }]
381
+ [
382
+ "processData",
383
+ {
384
+ invocationMode: InvocationMode.PARALLEL,
385
+ handler: async (transaction, input: MyInput) => {
386
+ // Process the data
387
+ const result = processData(input.data);
388
+
389
+ return {
390
+ result: EvalResult.COMPLETE,
391
+ output: { processed: result },
392
+ extraUpdates: [Patch.add("/processedData", result)],
393
+ };
394
+ },
395
+ },
396
+ ],
397
+
398
+ [
399
+ "batchProcess",
400
+ {
401
+ invocationMode: InvocationMode.BATCH,
402
+ batchHandler: async (transactions) => {
403
+ // Process all transactions together
404
+ const results = await processBatch(transactions.map((r) => r.value));
405
+
406
+ return results.map((result) => ({
407
+ result: EvalResult.COMPLETE,
408
+ output: result,
409
+ }));
410
+ },
411
+ },
412
+ ],
322
413
  ]);
323
414
 
324
415
  // Create handler
325
- const handler = newDirectedTransactionHandler('my-handler', actionMap)
416
+ const handler = newDirectedTransactionHandler("my-handler", actionMap)
326
417
  .withInitFn(async (engAPI) => {
327
418
  // Initialize resources
328
- console.log('Handler initialized');
419
+ console.log("Handler initialized");
329
420
  })
330
421
  .withCloseFn(() => {
331
422
  // Cleanup resources
332
- console.log('Handler closed');
423
+ console.log("Handler closed");
333
424
  });
334
425
 
335
- client.registerTransactionHandler('my-handler', handler);
426
+ client.registerTransactionHandler("my-handler", handler);
336
427
  ```
337
428
 
338
429
  ### Invocation modes
339
430
 
340
431
  **PARALLEL**: Each transaction processed independently in parallel
432
+
341
433
  ```typescript
342
434
  {
343
435
  invocationMode: InvocationMode.PARALLEL,
@@ -349,6 +441,7 @@ client.registerTransactionHandler('my-handler', handler);
349
441
  ```
350
442
 
351
443
  **BATCH**: All transactions in batch processed together
444
+
352
445
  ```typescript
353
446
  {
354
447
  invocationMode: InvocationMode.BATCH,
@@ -375,16 +468,16 @@ Return appropriate result based on outcome:
375
468
  Use JSON Patch operations to update workflow state:
376
469
 
377
470
  ```typescript
378
- import { Patch } from '@kaleido-io/workflow-engine-sdk';
471
+ import { Patch } from "@kaleido-io/workflow-engine-sdk";
379
472
 
380
473
  return {
381
474
  result: EvalResult.COMPLETE,
382
475
  stateUpdates: [
383
- Patch.add('/newField', 'value'),
384
- Patch.replace('/existingField', 'newValue'),
385
- Patch.remove('/oldField'),
386
- Patch.add('/array/-', 'append to array')
387
- ]
476
+ Patch.add("/newField", "value"),
477
+ Patch.replace("/existingField", "newValue"),
478
+ Patch.remove("/oldField"),
479
+ Patch.add("/array/-", "append to array"),
480
+ ],
388
481
  };
389
482
  ```
390
483
 
@@ -395,8 +488,8 @@ Override the default next stage:
395
488
  ```typescript
396
489
  return {
397
490
  result: EvalResult.COMPLETE,
398
- customStage: 'custom-next-stage', // Override default nextStage
399
- output: { data: 'result' }
491
+ customStage: "custom-next-stage", // Override default nextStage
492
+ output: { data: "result" },
400
493
  };
401
494
  ```
402
495
 
@@ -408,9 +501,9 @@ Emit events to trigger other workflows:
408
501
  return {
409
502
  result: EvalResult.COMPLETE,
410
503
  triggers: [
411
- { topic: 'user.created' },
412
- { topic: 'notification.send', ephemeral: true }
413
- ]
504
+ { topic: "user.created" },
505
+ { topic: "notification.send", ephemeral: true },
506
+ ],
414
507
  };
415
508
  ```
416
509
 
@@ -421,9 +514,7 @@ Emit events directly from handlers:
421
514
  ```typescript
422
515
  return {
423
516
  result: EvalResult.COMPLETE,
424
- events: [
425
- { topic: 'something-happened', data: {} }
426
- ]
517
+ events: [{ topic: "something-happened", data: {} }],
427
518
  };
428
519
  ```
429
520
 
@@ -434,7 +525,7 @@ Event sources poll external systems and emit events to the workflow engine.
434
525
  ### Creating an Event Source
435
526
 
436
527
  ```typescript
437
- import { newEventSource } from '@kaleido-io/workflow-engine-sdk';
528
+ import { newEventSource } from "@kaleido-io/workflow-engine-sdk";
438
529
 
439
530
  // Define your types
440
531
  interface MyCheckpoint {
@@ -453,54 +544,54 @@ interface MyEventData {
453
544
 
454
545
  // Create event source
455
546
  const eventSource = newEventSource<MyCheckpoint, MyConfig, MyEventData>(
456
- 'my-event-source',
547
+ "my-event-source",
457
548
  async (config, checkpointIn) => {
458
549
  // Poll for events
459
550
  const events = await fetchNewEvents(
460
551
  config.config.topic,
461
- checkpointIn?.lastId || 0
552
+ checkpointIn?.lastId || 0,
462
553
  );
463
-
554
+
464
555
  // Return checkpoint and events
465
556
  return {
466
- checkpointOut: {
467
- lastId: events[events.length - 1]?.id || checkpointIn?.lastId || 0
557
+ checkpointOut: {
558
+ lastId: events[events.length - 1]?.id || checkpointIn?.lastId || 0,
468
559
  },
469
- events: events.map(e => ({
560
+ events: events.map((e) => ({
470
561
  idempotencyKey: `event-${e.id}`,
471
562
  topic: config.config.topic,
472
- data: e
473
- }))
563
+ data: e,
564
+ })),
474
565
  };
475
- }
566
+ },
476
567
  )
477
- .withInitialCheckpoint(async (config) => {
478
- // Build initial checkpoint
479
- return { lastId: 0 };
480
- })
481
- .withConfigParser(async (info, configData) => {
482
- // Parse and validate config
483
- const config = configData as MyConfig;
484
- if (!config.topic) {
485
- throw new Error('topic is required');
486
- }
487
- return config;
488
- })
489
- .withDeleteFn(async (info) => {
490
- // Cleanup on deletion
491
- console.log(`Deleting event source: ${info.streamName}`);
492
- })
493
- .withInitFn(async (engAPI) => {
494
- // Initialize resources
495
- console.log('Event source initialized');
496
- })
497
- .withCloseFn(() => {
498
- // Cleanup resources
499
- console.log('Event source closed');
500
- });
568
+ .withInitialCheckpoint(async (config) => {
569
+ // Build initial checkpoint
570
+ return { lastId: 0 };
571
+ })
572
+ .withConfigParser(async (info, configData) => {
573
+ // Parse and validate config
574
+ const config = configData as MyConfig;
575
+ if (!config.topic) {
576
+ throw new Error("topic is required");
577
+ }
578
+ return config;
579
+ })
580
+ .withDeleteFn(async (info) => {
581
+ // Cleanup on deletion
582
+ console.log(`Deleting event source: ${info.streamName}`);
583
+ })
584
+ .withInitFn(async (engAPI) => {
585
+ // Initialize resources
586
+ console.log("Event source initialized");
587
+ })
588
+ .withCloseFn(() => {
589
+ // Cleanup resources
590
+ console.log("Event source closed");
591
+ });
501
592
 
502
593
  // Register event source
503
- client.registerEventSource('my-event-source', eventSource);
594
+ client.registerEventSource("my-event-source", eventSource);
504
595
  ```
505
596
 
506
597
  ### Event source lifecycle
@@ -534,52 +625,52 @@ const stellarBlocks = newEventSource<
534
625
  StellarBlockCheckpoint,
535
626
  StellarBlockConfig,
536
627
  MinimalLedger
537
- >(
538
- 'stellarBlocks',
539
- async (config, checkpointIn) => {
540
- const startLedger = checkpointIn ? checkpointIn.lastLedger + 1 : await getLatestLedger();
541
- const batchSize = config.config.batchSize || 10;
542
-
543
- const events = [];
544
- let newCheckpoint = startLedger - 1;
545
-
546
- for (let i = 0; i < batchSize; i++) {
547
- try {
548
- const ledger = await fetchLedger(startLedger + i);
549
- events.push({
550
- idempotencyKey: ledger.hash,
551
- topic: config.config.topic,
552
- data: {
553
- sequence: ledger.sequence,
554
- hash: ledger.hash,
555
- closedAt: ledger.closed_at
556
- }
557
- });
558
- newCheckpoint = ledger.sequence;
559
- } catch (error) {
560
- break; // Ledger not yet available
561
- }
628
+ >("stellarBlocks", async (config, checkpointIn) => {
629
+ const startLedger = checkpointIn
630
+ ? checkpointIn.lastLedger + 1
631
+ : await getLatestLedger();
632
+ const batchSize = config.config.batchSize || 10;
633
+
634
+ const events = [];
635
+ let newCheckpoint = startLedger - 1;
636
+
637
+ for (let i = 0; i < batchSize; i++) {
638
+ try {
639
+ const ledger = await fetchLedger(startLedger + i);
640
+ events.push({
641
+ idempotencyKey: ledger.hash,
642
+ topic: config.config.topic,
643
+ data: {
644
+ sequence: ledger.sequence,
645
+ hash: ledger.hash,
646
+ closedAt: ledger.closed_at,
647
+ },
648
+ });
649
+ newCheckpoint = ledger.sequence;
650
+ } catch (error) {
651
+ break; // Ledger not yet available
562
652
  }
563
-
564
- return {
565
- checkpointOut: { lastLedger: newCheckpoint },
566
- events
567
- };
568
653
  }
569
- )
570
- .withInitialCheckpoint(async (config) => {
571
- const ledgerNum = config.fromLedger === 'latest'
572
- ? await getLatestLedger()
573
- : parseInt(config.fromLedger || '0', 10);
574
- return { lastLedger: ledgerNum };
654
+
655
+ return {
656
+ checkpointOut: { lastLedger: newCheckpoint },
657
+ events,
658
+ };
575
659
  })
576
- .withConfigParser(async (info, configData) => {
577
- const config = configData as StellarBlockConfig;
578
- if (!config.topic) {
579
- throw new Error('topic is required');
580
- }
581
- return config;
582
- });
660
+ .withInitialCheckpoint(async (config) => {
661
+ const ledgerNum =
662
+ config.fromLedger === "latest"
663
+ ? await getLatestLedger()
664
+ : parseInt(config.fromLedger || "0", 10);
665
+ return { lastLedger: ledgerNum };
666
+ })
667
+ .withConfigParser(async (info, configData) => {
668
+ const config = configData as StellarBlockConfig;
669
+ if (!config.topic) {
670
+ throw new Error("topic is required");
671
+ }
672
+ return config;
673
+ });
583
674
  ```
584
675
 
585
676
  ### Creating event streams
@@ -612,20 +703,17 @@ The `EngineAPI` interface allows handlers to make synchronous API calls back to
612
703
  ```typescript
613
704
  async function myHandler(transaction, input, engAPI: EngineAPI) {
614
705
  // Submit transactions to the engine
615
- const results = await engAPI.submitAsyncTransactions(
616
- transaction.authRef,
617
- [
618
- {
619
- workflowId: 'flw:abc123',
620
- operation: 'process',
621
- input: { data: 'value' }
622
- }
623
- ]
624
- );
625
-
706
+ const results = await engAPI.submitAsyncTransactions(transaction.authRef, [
707
+ {
708
+ workflowId: "flw:abc123",
709
+ operation: "process",
710
+ input: { data: "value" },
711
+ },
712
+ ]);
713
+
626
714
  return {
627
715
  result: EvalResult.COMPLETE,
628
- output: { submittedTxs: results }
716
+ output: { submittedTxs: results },
629
717
  };
630
718
  }
631
719
  ```
@@ -635,7 +723,10 @@ async function myHandler(transaction, input, engAPI: EngineAPI) {
635
723
  For workflows with action-based routing and automatic stage transitions:
636
724
 
637
725
  ```typescript
638
- import { BasicStageDirector, WithStageDirector } from '@kaleido-io/workflow-engine-sdk';
726
+ import {
727
+ BasicStageDirector,
728
+ WithStageDirector,
729
+ } from "@kaleido-io/workflow-engine-sdk";
639
730
 
640
731
  interface MyInput extends WithStageDirector {
641
732
  data: string;
@@ -647,10 +738,10 @@ class MyInputImpl implements MyInput {
647
738
 
648
739
  constructor(input: any) {
649
740
  this.stageDirector = new BasicStageDirector(
650
- input.action, // Action to execute
651
- input.outputPath, // Where to store output
652
- input.nextStage, // Stage on success
653
- input.failureStage // Stage on failure
741
+ input.action, // Action to execute
742
+ input.outputPath, // Where to store output
743
+ input.nextStage, // Stage on success
744
+ input.failureStage, // Stage on failure
654
745
  );
655
746
  this.data = input.data;
656
747
  }
@@ -663,16 +754,19 @@ class MyInputImpl implements MyInput {
663
754
  // The SDK automatically wraps plain JSON objects from the engine
664
755
  // with a getStageDirector() method, so you can also use plain objects:
665
756
  const actionMap = new Map([
666
- ['myAction', {
667
- invocationMode: InvocationMode.PARALLEL,
668
- handler: async (transaction, input: any) => {
669
- // input.action, input.outputPath, input.nextStage are available
670
- return {
671
- result: EvalResult.COMPLETE,
672
- output: { processed: input.data }
673
- };
674
- }
675
- }]
757
+ [
758
+ "myAction",
759
+ {
760
+ invocationMode: InvocationMode.PARALLEL,
761
+ handler: async (transaction, input: any) => {
762
+ // input.action, input.outputPath, input.nextStage are available
763
+ return {
764
+ result: EvalResult.COMPLETE,
765
+ output: { processed: input.data },
766
+ };
767
+ },
768
+ },
769
+ ],
676
770
  ]);
677
771
  ```
678
772
 
@@ -688,27 +782,28 @@ handler: async (transaction, input) => {
688
782
  const result = await riskyOperation(input);
689
783
  return {
690
784
  result: EvalResult.COMPLETE,
691
- output: result
785
+ output: result,
692
786
  };
693
787
  } catch (error) {
694
788
  if (isTransient(error)) {
695
789
  return {
696
790
  result: EvalResult.TRANSIENT_ERROR,
697
- error: error as Error
791
+ error: error as Error,
698
792
  };
699
793
  } else {
700
794
  return {
701
795
  result: EvalResult.HARD_FAILURE,
702
- error: error as Error
796
+ error: error as Error,
703
797
  };
704
798
  }
705
799
  }
706
- }
800
+ };
707
801
  ```
708
802
 
709
803
  ### Connection errors
710
804
 
711
805
  The client automatically handles:
806
+
712
807
  - WebSocket disconnections
713
808
  - Automatic reconnection with exponential backoff
714
809
  - Handler re-registration on reconnect
@@ -720,7 +815,7 @@ Monitor connection events:
720
815
  // The SDK logs connection events automatically
721
816
  // Check connection status programmatically:
722
817
  if (!client.isConnected()) {
723
- console.warn('Client disconnected, will auto-reconnect');
818
+ console.warn("Client disconnected, will auto-reconnect");
724
819
  }
725
820
  ```
726
821
 
@@ -729,14 +824,14 @@ if (!client.isConnected()) {
729
824
  The SDK uses a structured logger:
730
825
 
731
826
  ```typescript
732
- import { newLogger } from '@kaleido-io/workflow-engine-sdk';
827
+ import { newLogger } from "@kaleido-io/workflow-engine-sdk";
733
828
 
734
- const log = newLogger('my-component');
829
+ const log = newLogger("my-component");
735
830
 
736
- log.debug('Debug message', { metadata: 'value' });
737
- log.info('Info message', { userId: 123 });
738
- log.warn('Warning message', { reason: 'low memory' });
739
- log.error('Error message', { error: err.message });
831
+ log.debug("Debug message", { metadata: "value" });
832
+ log.info("Info message", { userId: 123 });
833
+ log.warn("Warning message", { reason: "low memory" });
834
+ log.error("Error message", { error: err.message });
740
835
  ```
741
836
 
742
837
  ## Testing
@@ -746,18 +841,18 @@ log.error('Error message', { error: err.message });
746
841
  Mock the EngineAPI and test handlers in isolation:
747
842
 
748
843
  ```typescript
749
- import { jest } from '@jest/globals';
844
+ import { jest } from "@jest/globals";
750
845
 
751
- describe('MyHandler', () => {
752
- it('should process data correctly', async () => {
846
+ describe("MyHandler", () => {
847
+ it("should process data correctly", async () => {
753
848
  const mockEngAPI = {
754
- submitAsyncTransactions: jest.fn().mockResolvedValue([])
849
+ submitAsyncTransactions: jest.fn().mockResolvedValue([]),
755
850
  };
756
851
 
757
852
  const transaction = {
758
- transactionId: 'ftx:test123',
759
- workflowId: 'flw:test',
760
- input: { action: 'process', data: 'test' }
853
+ transactionId: "ftx:test123",
854
+ workflowId: "flw:test",
855
+ input: { action: "process", data: "test" },
761
856
  };
762
857
 
763
858
  const result = await myHandler(transaction, transaction.input, mockEngAPI);
@@ -773,37 +868,42 @@ describe('MyHandler', () => {
773
868
  Test with a running workflow engine:
774
869
 
775
870
  ```typescript
776
- import {
777
- WorkflowEngineClient,
871
+ import {
872
+ WorkflowEngineClient,
778
873
  ConfigLoader,
779
- WorkflowEngineConfig
780
- } from '@kaleido-io/workflow-engine-sdk';
781
- import * as fs from 'fs';
782
- import * as yaml from 'js-yaml';
874
+ WorkflowEngineConfig,
875
+ } from "@kaleido-io/workflow-engine-sdk";
876
+ import * as fs from "fs";
877
+ import * as yaml from "js-yaml";
783
878
 
784
879
  // Helper to load test config (your test infrastructure)
785
880
  function loadTestConfig(): WorkflowEngineConfig {
786
- const configFile = fs.readFileSync('./test-config.yaml', 'utf8');
787
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
788
-
881
+ const configFile = fs.readFileSync("./test-config.yaml", "utf8");
882
+ const config: WorkflowEngineConfig = yaml.load(
883
+ configFile,
884
+ ) as WorkflowEngineConfig;
885
+
789
886
  // Override with environment variables if present
790
887
  if (process.env.WORKFLOW_ENGINE_URL) {
791
888
  config.workflowEngine.url = process.env.WORKFLOW_ENGINE_URL;
792
889
  }
793
-
890
+
794
891
  return config;
795
892
  }
796
893
 
797
- describe('Component Test', () => {
894
+ describe("Component Test", () => {
798
895
  let client: WorkflowEngineClient;
799
896
  const testConfig = loadTestConfig();
800
897
 
801
898
  beforeAll(async () => {
802
899
  // Use SDK's ConfigLoader to transform config
803
- const clientConfig = ConfigLoader.createClientConfig(testConfig, 'test-provider');
900
+ const clientConfig = ConfigLoader.createClientConfig(
901
+ testConfig,
902
+ "test-provider",
903
+ );
804
904
  client = new WorkflowEngineClient(clientConfig);
805
905
 
806
- client.registerTransactionHandler('my-handler', handler);
906
+ client.registerTransactionHandler("my-handler", handler);
807
907
  await client.connect();
808
908
  });
809
909
 
@@ -811,25 +911,31 @@ describe('Component Test', () => {
811
911
  client.disconnect();
812
912
  });
813
913
 
814
- it('should process workflow end-to-end', async () => {
914
+ it("should process workflow end-to-end", async () => {
815
915
  // For REST API calls, extract auth headers from SDK config
816
916
  function getAuthHeaders(): Record<string, string> {
817
- const clientConfig = ConfigLoader.createClientConfig(testConfig, 'test-client');
917
+ const clientConfig = ConfigLoader.createClientConfig(
918
+ testConfig,
919
+ "test-client",
920
+ );
818
921
  return clientConfig.options?.headers || {};
819
922
  }
820
923
 
821
924
  const authHeaders = getAuthHeaders();
822
-
925
+
823
926
  // Create workflow
824
- const workflowResponse = await fetch('http://localhost:5503/api/v1/workflows', {
825
- method: 'POST',
826
- headers: {
827
- 'Content-Type': 'application/x-yaml',
828
- ...authHeaders // SDK handles auth automatically
927
+ const workflowResponse = await fetch(
928
+ "http://localhost:5503/api/v1/workflows",
929
+ {
930
+ method: "POST",
931
+ headers: {
932
+ "Content-Type": "application/x-yaml",
933
+ ...authHeaders, // SDK handles auth automatically
934
+ },
935
+ body: workflowYAML,
829
936
  },
830
- body: workflowYAML
831
- });
832
-
937
+ );
938
+
833
939
  // Wait for completion and verify results
834
940
  });
835
941
  });
@@ -847,10 +953,10 @@ import {
847
953
  InvocationMode,
848
954
  EvalResult,
849
955
  Patch,
850
- ConfigLoader
851
- } from '@kaleido-io/workflow-engine-sdk';
852
- import * as fs from 'fs';
853
- import * as yaml from 'js-yaml';
956
+ ConfigLoader,
957
+ } from "@kaleido-io/workflow-engine-sdk";
958
+ import * as fs from "fs";
959
+ import * as yaml from "js-yaml";
854
960
 
855
961
  interface ProcessInput {
856
962
  action: string;
@@ -860,67 +966,79 @@ interface ProcessInput {
860
966
 
861
967
  async function main() {
862
968
  // Load config (your application handles file loading)
863
- const configFile = fs.readFileSync('./config.yaml', 'utf8');
864
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
865
-
969
+ const configFile = fs.readFileSync("./config.yaml", "utf8");
970
+ const config: WorkflowEngineConfig = yaml.load(
971
+ configFile,
972
+ ) as WorkflowEngineConfig;
973
+
866
974
  // SDK transforms config
867
- const clientConfig = ConfigLoader.createClientConfig(config, 'payment-service');
975
+ const clientConfig = ConfigLoader.createClientConfig(
976
+ config,
977
+ "payment-service",
978
+ );
868
979
 
869
980
  // Create client
870
981
  const client = new WorkflowEngineClient(clientConfig);
871
982
 
872
983
  // Define actions
873
984
  const actionMap = new Map([
874
- ['validatePayment', {
875
- invocationMode: InvocationMode.PARALLEL,
876
- handler: async (transaction, input: ProcessInput) => {
877
- if (input.amount <= 0) {
985
+ [
986
+ "validatePayment",
987
+ {
988
+ invocationMode: InvocationMode.PARALLEL,
989
+ handler: async (transaction, input: ProcessInput) => {
990
+ if (input.amount <= 0) {
991
+ return {
992
+ result: EvalResult.HARD_FAILURE,
993
+ error: new Error("Invalid amount"),
994
+ };
995
+ }
996
+
878
997
  return {
879
- result: EvalResult.HARD_FAILURE,
880
- error: new Error('Invalid amount')
998
+ result: EvalResult.COMPLETE,
999
+ output: { validated: true },
1000
+ extraUpdates: [
1001
+ Patch.add("/validation", { valid: true, timestamp: new Date() }),
1002
+ ],
881
1003
  };
882
- }
883
-
884
- return {
885
- result: EvalResult.COMPLETE,
886
- output: { validated: true },
887
- extraUpdates: [
888
- Patch.add('/validation', { valid: true, timestamp: new Date() })
889
- ]
890
- };
891
- }
892
- }],
1004
+ },
1005
+ },
1006
+ ],
893
1007
 
894
- ['processPayment', {
895
- invocationMode: InvocationMode.PARALLEL,
896
- handler: async (transaction, input: ProcessInput) => {
897
- const paymentResult = await processPayment(input.userId, input.amount);
898
-
899
- return {
900
- result: EvalResult.COMPLETE,
901
- output: paymentResult,
902
- triggers: [
903
- { topic: 'payment.completed' }
904
- ]
905
- };
906
- }
907
- }]
1008
+ [
1009
+ "processPayment",
1010
+ {
1011
+ invocationMode: InvocationMode.PARALLEL,
1012
+ handler: async (transaction, input: ProcessInput) => {
1013
+ const paymentResult = await processPayment(
1014
+ input.userId,
1015
+ input.amount,
1016
+ );
1017
+
1018
+ return {
1019
+ result: EvalResult.COMPLETE,
1020
+ output: paymentResult,
1021
+ triggers: [{ topic: "payment.completed" }],
1022
+ };
1023
+ },
1024
+ },
1025
+ ],
908
1026
  ]);
909
1027
 
910
1028
  // Create handler
911
- const handler = newDirectedTransactionHandler('payment-handler', actionMap)
1029
+ const handler = newDirectedTransactionHandler("payment-handler", actionMap)
912
1030
  .withInitFn(async (engAPI) => {
913
- console.log('Payment handler initialized');
1031
+ console.log("Payment handler initialized");
914
1032
  })
915
1033
  .withCloseFn(() => {
916
- console.log('Payment handler closed');
1034
+ console.log("Payment handler closed");
917
1035
  });
918
1036
 
919
1037
  // Register and connect
920
- client.registerTransactionHandler('payment-handler', handler);
1038
+ client.registerTransactionHandler("payment-handler", handler);
921
1039
  await client.connect();
922
1040
 
923
- console.log('Payment service ready');
1041
+ console.log("Payment service ready");
924
1042
  }
925
1043
 
926
1044
  main().catch(console.error);
@@ -973,13 +1091,13 @@ Workflow Engine
973
1091
 
974
1092
  ```typescript
975
1093
  const client = new WorkflowEngineClient({
976
- url: 'ws://localhost:5503/ws',
977
- providerName: 'my-service',
1094
+ url: "ws://localhost:5503/ws",
1095
+ providerName: "my-service",
978
1096
  options: {
979
1097
  headers: {
980
- 'Authorization': `Bearer ${process.env.AUTH_TOKEN}`
981
- }
982
- }
1098
+ Authorization: `Bearer ${process.env.AUTH_TOKEN}`,
1099
+ },
1100
+ },
983
1101
  });
984
1102
  ```
985
1103
 
@@ -987,10 +1105,10 @@ const client = new WorkflowEngineClient({
987
1105
 
988
1106
  ```typescript
989
1107
  // Register multiple handlers
990
- client.registerTransactionHandler('handler1', handler1);
991
- client.registerTransactionHandler('handler2', handler2);
992
- client.registerEventSource('source1', source1);
993
- client.registerEventSource('source2', source2);
1108
+ client.registerTransactionHandler("handler1", handler1);
1109
+ client.registerTransactionHandler("handler2", handler2);
1110
+ client.registerEventSource("source1", source1);
1111
+ client.registerEventSource("source2", source2);
994
1112
 
995
1113
  // All handlers use the same WebSocket connection
996
1114
  await client.connect();
@@ -999,30 +1117,35 @@ await client.connect();
999
1117
  ### Configuration validation
1000
1118
 
1001
1119
  ```typescript
1002
- import { ConfigLoader, WorkflowEngineConfig } from '@kaleido-io/workflow-engine-sdk';
1003
- import * as fs from 'fs';
1004
- import * as yaml from 'js-yaml';
1120
+ import {
1121
+ ConfigLoader,
1122
+ WorkflowEngineConfig,
1123
+ } from "@kaleido-io/workflow-engine-sdk";
1124
+ import * as fs from "fs";
1125
+ import * as yaml from "js-yaml";
1005
1126
 
1006
1127
  try {
1007
1128
  // Your application loads config
1008
- const configFile = fs.readFileSync('./config.yaml', 'utf8');
1009
- const config: WorkflowEngineConfig = yaml.load(configFile) as WorkflowEngineConfig;
1010
-
1129
+ const configFile = fs.readFileSync("./config.yaml", "utf8");
1130
+ const config: WorkflowEngineConfig = yaml.load(
1131
+ configFile,
1132
+ ) as WorkflowEngineConfig;
1133
+
1011
1134
  // Validate required fields
1012
1135
  if (!config.workflowEngine) {
1013
- throw new Error('Missing workflowEngine configuration');
1136
+ throw new Error("Missing workflowEngine configuration");
1014
1137
  }
1015
1138
  if (!config.workflowEngine.url) {
1016
- throw new Error('Missing workflowEngine.url');
1139
+ throw new Error("Missing workflowEngine.url");
1017
1140
  }
1018
1141
  if (!config.workflowEngine.auth) {
1019
- throw new Error('Missing workflowEngine.auth');
1142
+ throw new Error("Missing workflowEngine.auth");
1020
1143
  }
1021
-
1144
+
1022
1145
  // SDK logs summary (without sensitive data)
1023
1146
  ConfigLoader.logConfigSummary(config);
1024
1147
  } catch (error) {
1025
- console.error('Invalid configuration:', error.message);
1148
+ console.error("Invalid configuration:", error.message);
1026
1149
  process.exit(1);
1027
1150
  }
1028
1151
  ```
@@ -1048,7 +1171,7 @@ try {
1048
1171
 
1049
1172
  ```typescript
1050
1173
  // Register BEFORE submitting workflows
1051
- client.registerTransactionHandler('my-handler', handler);
1174
+ client.registerTransactionHandler("my-handler", handler);
1052
1175
  await client.connect();
1053
1176
  // Now workflows can use this handler
1054
1177
  ```
@@ -1061,18 +1184,19 @@ await client.connect();
1061
1184
 
1062
1185
  ```typescript
1063
1186
  // Verify URL format (should include ws:// or wss://)
1064
- url: 'ws://localhost:5503/ws' // ✓ Correct
1065
- url: 'localhost:5503' // ✗ Wrong
1187
+ url: "ws://localhost:5503/ws"; // ✓ Correct
1188
+ url: "localhost:5503"; // ✗ Wrong
1066
1189
 
1067
1190
  // Check authentication
1068
- authToken: process.env.AUTH_TOKEN // Ensure token is valid
1191
+ authToken: process.env.AUTH_TOKEN; // Ensure token is valid
1069
1192
  ```
1070
1193
 
1071
1194
  ### Event source not polling
1072
1195
 
1073
1196
  **Problem**: Event stream created but no events emitted
1074
1197
 
1075
- **Solution**:
1198
+ **Solution**:
1199
+
1076
1200
  1. Check stream is started: `"started": true`
1077
1201
  2. Verify handler name matches: `listenerHandler: 'my-event-source'`
1078
1202
  3. Check provider name matches: `listenerHandlerProvider: 'my-service'`
@@ -1112,10 +1236,10 @@ See the TypeScript type definitions for complete API documentation:
1112
1236
 
1113
1237
  The `ConfigLoader` class provides utilities for transforming configuration:
1114
1238
 
1115
- - `createClientConfig(config, providerName)` - Transforms `WorkflowEngineConfig` into `WorkflowEngineClientConfig`
1116
- - Converts HTTP URLs to WebSocket URLs
1117
- - Sets up authentication headers based on auth type
1118
- - Handles retry and timeout settings
1119
- - `logConfigSummary(config)` - Logs configuration summary (without sensitive data)
1239
+ - `loadClientConfigFromFile(configFilePath?)` - Loads WFE config from a YAML file. Only the root key **`workflow-engine`** is supported. **Outbound:** `url` + `auth`. **Inbound:** `server` (address, port); app creates WebSocket server, optional `server.tls`. Uses `process.env[WFE_CONFIG_FILE]` when path is omitted.
1240
+ - `createClientConfig(config, providerName)` - Transforms `WorkflowEngineConfig` into `WorkflowEngineClientConfig` (converts HTTP to WebSocket URL, sets auth headers, parses time strings for delays).
1241
+ - `logConfigSummary(config)` - Logs configuration summary (without sensitive data).
1242
+
1243
+ Time strings for delay fields (e.g. `retryDelay`) are parsed by `parseTimeStringToMs`: units `ms`, `s`, `m`, `h` (e.g. `"2s"`, `"30s"`, `"100ms"`, `"1m"`).
1120
1244
 
1121
- **Note:** The SDK does not load configuration from files. Your application should load configuration and pass it to these utilities.
1245
+ **Note:** For file-based config, use `loadClientConfigFromFile` so the SDK handles the `workflow-engine` file format. Your application can still load YAML and call `createClientConfig` when using the in-memory `WorkflowEngineConfig` shape (e.g. with a `workflowEngine` property).