@db-lyon/flowkit 0.11.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +386 -375
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/flow/runner.d.ts +23 -2
  4. package/dist/flow/runner.d.ts.map +1 -1
  5. package/dist/flow/runner.js +89 -29
  6. package/dist/flow/runner.js.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/references.d.ts +41 -0
  12. package/dist/references.d.ts.map +1 -0
  13. package/dist/references.js +110 -0
  14. package/dist/references.js.map +1 -0
  15. package/dist/task/agent-task.d.ts.map +1 -1
  16. package/dist/task/agent-task.js +1 -7
  17. package/dist/task/agent-task.js.map +1 -1
  18. package/dist/task/base-task.d.ts +28 -3
  19. package/dist/task/base-task.d.ts.map +1 -1
  20. package/dist/task/base-task.js +13 -4
  21. package/dist/task/base-task.js.map +1 -1
  22. package/dist/task/shell-task.d.ts +2 -0
  23. package/dist/task/shell-task.d.ts.map +1 -1
  24. package/dist/task/shell-task.js +186 -40
  25. package/dist/task/shell-task.js.map +1 -1
  26. package/dist/task/shell-termination.d.ts +34 -0
  27. package/dist/task/shell-termination.d.ts.map +1 -0
  28. package/dist/task/shell-termination.js +179 -0
  29. package/dist/task/shell-termination.js.map +1 -0
  30. package/dist/task/task-resolution.d.ts +33 -0
  31. package/dist/task/task-resolution.d.ts.map +1 -0
  32. package/dist/task/task-resolution.js +34 -0
  33. package/dist/task/task-resolution.js.map +1 -0
  34. package/docs/ai-agents.md +368 -368
  35. package/docs/api-reference.md +458 -430
  36. package/docs/configuration.md +347 -343
  37. package/docs/custom-tasks.md +258 -203
  38. package/package.json +53 -52
@@ -1,203 +1,258 @@
1
- # Custom tasks
2
-
3
- Tasks are the building blocks of flowkit. Each task is a class that extends `BaseTask` and implements an `execute()` method.
4
-
5
- ## Anatomy of a task
6
-
7
- ```typescript
8
- import { BaseTask, type TaskResult } from '@db-lyon/flowkit';
9
-
10
- interface MyOptions {
11
- url: string;
12
- retries?: number;
13
- }
14
-
15
- export default class FetchData extends BaseTask<MyOptions> {
16
- get taskName() {
17
- return 'fetch_data';
18
- }
19
-
20
- protected validate() {
21
- if (!this.options.url) {
22
- throw new Error('url option is required');
23
- }
24
- }
25
-
26
- async execute(): Promise<TaskResult> {
27
- const { url, retries = 3 } = this.options;
28
-
29
- const response = await fetch(url);
30
- if (!response.ok) {
31
- return {
32
- success: false,
33
- error: new Error(`HTTP ${response.status}`),
34
- };
35
- }
36
-
37
- const data = await response.json();
38
- return {
39
- success: true,
40
- data: { body: data, status: response.status },
41
- };
42
- }
43
- }
44
- ```
45
-
46
- ### Required members
47
-
48
- | Member | Description |
49
- |--------|-------------|
50
- | `get taskName()` | A human-readable name used in logging |
51
- | `execute()` | Async method that performs the work and returns a `TaskResult` |
52
-
53
- ### Optional members
54
-
55
- | Member | Description |
56
- |--------|-------------|
57
- | `validate()` | Called before `execute()`. Throw to abort with a validation error. |
58
-
59
- ### Available on `this`
60
-
61
- | Property | Description |
62
- |----------|-------------|
63
- | `this.options` | The merged options (task defaults + step overrides), typed as `TOptions` |
64
- | `this.ctx` | The `TaskContext` passed to the flow runner — use it to share state |
65
- | `this.logger` | A child logger scoped to this task instance |
66
-
67
- ## The task lifecycle
68
-
69
- When `task.run()` is called (by the flow runner):
70
-
71
- 1. `validate()` runs — throw here to reject bad options
72
- 2. `execute()` runs — return a `TaskResult`
73
- 3. The result gets a `duration` field added automatically
74
- 4. If `validate()` or `execute()` throws, the error is caught and returned as `{ success: false, error }`
75
-
76
- You never call `run()` yourself in normal usage — the flow runner handles it.
77
-
78
- ## TaskResult
79
-
80
- ```typescript
81
- interface TaskResult {
82
- success: boolean;
83
- data?: Record<string, unknown>; // arbitrary output data
84
- error?: Error; // populated on failure
85
- duration?: number; // milliseconds, set by run()
86
- }
87
- ```
88
-
89
- Return `{ success: true }` for success and `{ success: false, error }` for expected failures. Unexpected exceptions are caught automatically.
90
-
91
- ## TaskContext
92
-
93
- The context object is shared across all tasks in a flow run. Use it to pass shared state like database connections, API clients, or configuration:
94
-
95
- ```typescript
96
- const runner = new FlowRunner({
97
- // ...
98
- context: {
99
- logger: myLogger,
100
- db: databaseConnection,
101
- apiKey: process.env.API_KEY,
102
- },
103
- });
104
- ```
105
-
106
- Inside a task:
107
-
108
- ```typescript
109
- async execute(): Promise<TaskResult> {
110
- const db = this.ctx.db as Database;
111
- // ...
112
- }
113
- ```
114
-
115
- ## Registering tasks
116
-
117
- ### By name
118
-
119
- ```typescript
120
- const registry = new TaskRegistry();
121
- registry.register('fetch_data', FetchData as any);
122
- ```
123
-
124
- The YAML can then reference it directly:
125
-
126
- ```yaml
127
- tasks:
128
- fetch_data:
129
- class_path: fetch_data
130
- ```
131
-
132
- ### By class path
133
-
134
- ```typescript
135
- registry.registerClassPath('my.tasks.FetchData', FetchData as any);
136
- ```
137
-
138
- ### Bulk registration
139
-
140
- ```typescript
141
- registry.registerAll({
142
- fetch_data: FetchData as any,
143
- transform: TransformData as any,
144
- upload: Upload as any,
145
- });
146
- ```
147
-
148
- ### Dynamic resolution
149
-
150
- If a `class_path` isn't found in the registry, flowkit converts dots to path separators and looks for a file on disk:
151
-
152
- | class_path | Files checked |
153
- |------------|---------------|
154
- | `tasks.FetchData` | `tasks/FetchData.ts`, `tasks/FetchData.js`, `tasks/FetchData/index.ts`, `tasks/FetchData/index.js` |
155
- | `lib.etl.Extract` | `lib/etl/Extract.ts`, `lib/etl/Extract.js`, ... |
156
-
157
- The module must have either a `default` export or a named export matching the last segment of the path (e.g., `FetchData`). The export must extend `BaseTask`.
158
-
159
- ## Built-in: ShellTask
160
-
161
- `ShellTask` executes shell commands via `execSync`. Register it under any name you like:
162
-
163
- ```typescript
164
- import { ShellTask } from '@db-lyon/flowkit';
165
-
166
- registry.register('shell', ShellTask as any);
167
- ```
168
-
169
- Then use it in YAML:
170
-
171
- ```yaml
172
- tasks:
173
- lint:
174
- class_path: shell
175
- description: Run the linter
176
- options:
177
- command: npm run lint
178
-
179
- build:
180
- class_path: shell
181
- description: Build the project
182
- options:
183
- command: npm run build
184
- cwd: /path/to/project
185
- timeout: 120000
186
- ```
187
-
188
- ### ShellTask options
189
-
190
- | Option | Type | Default | Description |
191
- |--------|------|---------|-------------|
192
- | `command` | `string` | (required) | The shell command to execute |
193
- | `cwd` | `string` | `undefined` | Working directory |
194
- | `timeout` | `number` | `300000` (5 min) | Timeout in milliseconds |
195
-
196
- On success, `result.data.output` contains the trimmed stdout. On failure, `result.data` includes `exitCode`, `stderr`, and `stdout`.
197
-
198
- ## Listing registered tasks
199
-
200
- ```typescript
201
- const names = registry.listRegistered();
202
- // ['fetch_data', 'shell', 'my.tasks.Transform', ...]
203
- ```
1
+ # Custom tasks
2
+
3
+ Tasks are the building blocks of flowkit. Each task is a class that extends `BaseTask` and implements an `execute()` method.
4
+
5
+ ## Anatomy of a task
6
+
7
+ ```typescript
8
+ import { BaseTask, type TaskResult } from '@db-lyon/flowkit';
9
+
10
+ interface MyOptions {
11
+ url: string;
12
+ retries?: number;
13
+ }
14
+
15
+ export default class FetchData extends BaseTask<MyOptions> {
16
+ get taskName() {
17
+ return 'fetch_data';
18
+ }
19
+
20
+ protected validate() {
21
+ if (!this.options.url) {
22
+ throw new Error('url option is required');
23
+ }
24
+ }
25
+
26
+ async execute(): Promise<TaskResult> {
27
+ const { url, retries = 3 } = this.options;
28
+
29
+ const response = await fetch(url);
30
+ if (!response.ok) {
31
+ return {
32
+ success: false,
33
+ error: new Error(`HTTP ${response.status}`),
34
+ };
35
+ }
36
+
37
+ const data = await response.json();
38
+ return {
39
+ success: true,
40
+ data: { body: data, status: response.status },
41
+ };
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### Required members
47
+
48
+ | Member | Description |
49
+ |--------|-------------|
50
+ | `get taskName()` | A human-readable name used in logging |
51
+ | `execute()` | Async method that performs the work and returns a `TaskResult` |
52
+
53
+ ### Optional members
54
+
55
+ | Member | Description |
56
+ |--------|-------------|
57
+ | `validate()` | Called before `execute()`. Throw to abort with a validation error. |
58
+
59
+ ### Available on `this`
60
+
61
+ | Property | Description |
62
+ |----------|-------------|
63
+ | `this.options` | The merged options (task defaults + step overrides), typed as `TOptions` |
64
+ | `this.ctx` | The `TaskContext` passed to the flow runner — read-only, see below |
65
+ | `this.logger` | A child logger scoped to this task instance |
66
+ | `this.resolve(name, options?)` | Build another task by configured name or class path, unexecuted |
67
+ | `this.call(name, options?)` | `resolve()` plus `run()`, returning its `TaskResult` |
68
+
69
+ ## Calling other tasks
70
+
71
+ `this.call(name)` resolves `name` the same way a flow step does. A configured task name is looked up in the `tasks:` config and dispatched through its `class_path`, inheriting its configured `options` as defaults; anything you pass as `options` merges over them and wins. A name with no configured entry resolves as a class path directly, so a bare `vendor.tasks.Thing` still works.
72
+
73
+ ```yaml
74
+ tasks:
75
+ soql_query:
76
+ class_path: caseops.tasks.SoqlQuery
77
+ options:
78
+ org: ${org.username}
79
+ ```
80
+
81
+ ```typescript
82
+ // Runs caseops.tasks.SoqlQuery with { org: 'admin@example.com', query: 'SELECT ...' }
83
+ const result = await this.call('soql_query', { query: 'SELECT Id FROM Case' });
84
+ ```
85
+
86
+ The `${ns.path}` references in those configured defaults are interpolated for you, against the same scope the calling task itself runs under. The `options` you pass are your own runtime data and are **never** interpolated, so a `${...}` you computed reaches the task verbatim rather than being reinterpreted as configuration.
87
+
88
+ Calling a task requires a registry on the context, which `FlowRunner` supplies. A task constructed by hand without one throws.
89
+
90
+ ## The task lifecycle
91
+
92
+ When `task.run()` is called (by the flow runner):
93
+
94
+ 1. `validate()` runs — throw here to reject bad options
95
+ 2. `execute()` runs — return a `TaskResult`
96
+ 3. The result gets a `duration` field added automatically
97
+ 4. If `validate()` or `execute()` throws, the error is caught and returned as `{ success: false, error }`
98
+
99
+ You never call `run()` yourself in normal usage — the flow runner handles it.
100
+
101
+ ## TaskResult
102
+
103
+ ```typescript
104
+ interface TaskResult {
105
+ success: boolean;
106
+ data?: Record<string, unknown>; // arbitrary output data
107
+ error?: Error; // populated on failure
108
+ duration?: number; // milliseconds, set by run()
109
+ }
110
+ ```
111
+
112
+ Return `{ success: true }` for success and `{ success: false, error }` for expected failures. Unexpected exceptions are caught automatically.
113
+
114
+ ## TaskContext
115
+
116
+ The context carries host-supplied state to every task in a flow run — database connections, API clients, configuration:
117
+
118
+ ```typescript
119
+ const runner = new FlowRunner({
120
+ // ...
121
+ context: {
122
+ logger: myLogger,
123
+ db: databaseConnection,
124
+ apiKey: process.env.API_KEY,
125
+ },
126
+ });
127
+ ```
128
+
129
+ Inside a task:
130
+
131
+ ```typescript
132
+ async execute(): Promise<TaskResult> {
133
+ const db = this.ctx.db as Database;
134
+ // ...
135
+ }
136
+ ```
137
+
138
+ Treat the context as read-only. Each task is handed its own derived context, so assigning a key inside a task (`this.ctx.cached = x`) does not reach the next step, another task, or a sub-agent. To share mutable state, put a mutable object on the context up front and write into that:
139
+
140
+ ```typescript
141
+ context: { cache: new Map() } // this.ctx.cache.set(...) is visible everywhere
142
+ ```
143
+
144
+ ## Registering tasks
145
+
146
+ ### By name
147
+
148
+ ```typescript
149
+ const registry = new TaskRegistry();
150
+ registry.register('fetch_data', FetchData as any);
151
+ ```
152
+
153
+ The YAML can then reference it directly:
154
+
155
+ ```yaml
156
+ tasks:
157
+ fetch_data:
158
+ class_path: fetch_data
159
+ ```
160
+
161
+ ### By class path
162
+
163
+ ```typescript
164
+ registry.registerClassPath('my.tasks.FetchData', FetchData as any);
165
+ ```
166
+
167
+ ### Bulk registration
168
+
169
+ ```typescript
170
+ registry.registerAll({
171
+ fetch_data: FetchData as any,
172
+ transform: TransformData as any,
173
+ upload: Upload as any,
174
+ });
175
+ ```
176
+
177
+ ### Dynamic resolution
178
+
179
+ If a `class_path` isn't found in the registry, flowkit converts dots to path separators and looks for a file on disk:
180
+
181
+ | class_path | Files checked |
182
+ |------------|---------------|
183
+ | `tasks.FetchData` | `tasks/FetchData.ts`, `tasks/FetchData.js`, `tasks/FetchData/index.ts`, `tasks/FetchData/index.js` |
184
+ | `lib.etl.Extract` | `lib/etl/Extract.ts`, `lib/etl/Extract.js`, ... |
185
+
186
+ The module must have either a `default` export or a named export matching the last segment of the path (e.g., `FetchData`). The export must extend `BaseTask`.
187
+
188
+ ## Built-in: ShellTask
189
+
190
+ `ShellTask` executes shell commands through the platform shell and streams output.
191
+ Register it under any name you like:
192
+
193
+ ```typescript
194
+ import { ShellTask } from '@db-lyon/flowkit';
195
+
196
+ registry.register('shell', ShellTask as any);
197
+ ```
198
+
199
+ Then use it in YAML:
200
+
201
+ ```yaml
202
+ tasks:
203
+ lint:
204
+ class_path: shell
205
+ description: Run the linter
206
+ options:
207
+ command: npm run lint
208
+
209
+ build:
210
+ class_path: shell
211
+ description: Build the project
212
+ options:
213
+ command: npm run build
214
+ cwd: /path/to/project
215
+ timeout: 120000
216
+ ```
217
+
218
+ ### ShellTask options
219
+
220
+ | Option | Type | Default | Description |
221
+ |--------|------|---------|-------------|
222
+ | `command` | `string` | (required) | The shell command to execute |
223
+ | `cwd` | `string` | `undefined` | Working directory |
224
+ | `timeout` | `number` | `300000` (5 min) | Timeout in milliseconds |
225
+ | `signal` | `AbortSignal` | `undefined` | Cancels one programmatic invocation; cannot be specified in YAML |
226
+
227
+ On success, `result.data.output` contains the trimmed stdout. On failure, `result.data` includes `exitCode`, `stderr`, and `stdout`.
228
+
229
+ Existing YAML consumers need no change. Programmatic callers that need
230
+ cancellation pass an invocation-specific `AbortSignal` in the task options.
231
+ After cancellation, Flowkit waits for the shell to close, with a bounded
232
+ fallback if the operating system does not report closure (one second on POSIX,
233
+ three seconds on Windows). That fallback does not guarantee all descendants
234
+ have exited. At the deadline Flowkit requests force termination and releases
235
+ its Node handles for the root shell and any Windows `taskkill` helper before
236
+ returning. On POSIX,
237
+ signal-bearing invocations use a dedicated process group: Flowkit requests
238
+ `SIGTERM`, waits 250ms for cooperative cleanup, then escalates the group to
239
+ `SIGKILL`. Terminal Ctrl+C is not delivered to that separate group, so use the
240
+ supplied `AbortSignal` for cancellation.
241
+
242
+ On Windows, Flowkit asks `taskkill /T /F` to terminate the shell tree and does
243
+ not kill the shell while that traversal is in progress. If `taskkill` fails or
244
+ the three-second deadline expires, Flowkit requests force termination and
245
+ releases its Node handles for the root and helper. Windows and POSIX descendants that escape the managed process
246
+ tree or process group may still survive; Node does not provide a portable
247
+ guarantee of complete descendant termination.
248
+
249
+ Trailing stdout and stderr fragments are captured once, including when no
250
+ `signal` is supplied. This corrects the duplicate final-partial-line output in
251
+ earlier releases.
252
+
253
+ ## Listing registered tasks
254
+
255
+ ```typescript
256
+ const names = registry.listRegistered();
257
+ // ['fetch_data', 'shell', 'my.tasks.Transform', ...]
258
+ ```
package/package.json CHANGED
@@ -1,52 +1,53 @@
1
- {
2
- "name": "@db-lyon/flowkit",
3
- "version": "0.11.2",
4
- "description": "YAML-configured task and flow orchestration engine",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": "./dist/index.js",
10
- "./config": "./dist/config/index.js",
11
- "./task": "./dist/task/index.js",
12
- "./flow": "./dist/flow/index.js",
13
- "./dag": "./dist/dag/index.js"
14
- },
15
- "files": [
16
- "/dist",
17
- "/docs"
18
- ],
19
- "scripts": {
20
- "build": "tsc -b",
21
- "prepublishOnly": "tsc -b",
22
- "test": "vitest run",
23
- "test:watch": "vitest"
24
- },
25
- "keywords": [
26
- "task",
27
- "flow",
28
- "orchestration",
29
- "yaml",
30
- "pipeline",
31
- "engine"
32
- ],
33
- "author": "David Lyon",
34
- "license": "MIT",
35
- "repository": {
36
- "type": "git",
37
- "url": "https://github.com/db-lyon/flowkit.git"
38
- },
39
- "dependencies": {
40
- "js-yaml": "^4",
41
- "zod": "^3"
42
- },
43
- "devDependencies": {
44
- "@types/js-yaml": "^4",
45
- "@types/node": "^20",
46
- "typescript": "^5",
47
- "vitest": "^2"
48
- },
49
- "engines": {
50
- "node": ">=20"
51
- }
52
- }
1
+ {
2
+ "name": "@db-lyon/flowkit",
3
+ "version": "0.13.0",
4
+ "description": "YAML-configured task and flow orchestration engine",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./config": "./dist/config/index.js",
11
+ "./task": "./dist/task/index.js",
12
+ "./flow": "./dist/flow/index.js",
13
+ "./dag": "./dist/dag/index.js"
14
+ },
15
+ "files": [
16
+ "/dist",
17
+ "/docs"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -b",
21
+ "prepublishOnly": "tsc -b",
22
+ "test": "vitest run",
23
+ "test:package-api": "npm run build && tsc -p test/tsconfig.public-api.json",
24
+ "test:watch": "vitest"
25
+ },
26
+ "keywords": [
27
+ "task",
28
+ "flow",
29
+ "orchestration",
30
+ "yaml",
31
+ "pipeline",
32
+ "engine"
33
+ ],
34
+ "author": "David Lyon",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/db-lyon/flowkit.git"
39
+ },
40
+ "dependencies": {
41
+ "js-yaml": "^4",
42
+ "zod": "^3"
43
+ },
44
+ "devDependencies": {
45
+ "@types/js-yaml": "^4",
46
+ "@types/node": "^20",
47
+ "typescript": "^5",
48
+ "vitest": "^2"
49
+ },
50
+ "engines": {
51
+ "node": ">=20"
52
+ }
53
+ }