@fkws/klonk 0.0.6 → 0.0.7
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/README.md +9 -0
- package/dist/index.cjs +5 -10
- package/dist/index.d.ts +193 -0
- package/dist/index.js +5 -10
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
# Klonk
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+
[](https://codecov.io/gh/klar-web-services/klonk)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
2
11
|
*A code-first, type-safe automation engine for TypeScript.*
|
|
3
12
|
|
|
4
13
|
## Introduction
|
package/dist/index.cjs
CHANGED
|
@@ -451,25 +451,20 @@ class Machine {
|
|
|
451
451
|
}
|
|
452
452
|
retries++;
|
|
453
453
|
}
|
|
454
|
-
if (!next) {
|
|
455
|
-
logger?.info({ phase: "end" }, "No next state after retries, run complete.");
|
|
456
|
-
return stateData;
|
|
457
|
-
}
|
|
458
454
|
}
|
|
459
|
-
|
|
455
|
+
const resolvedNext = next;
|
|
456
|
+
if (resolvedNext === this.initialState) {
|
|
460
457
|
logger?.info({ phase: "end" }, "Next state is initial state, run complete.");
|
|
461
458
|
return stateData;
|
|
462
459
|
}
|
|
463
|
-
logger?.info({ phase: "progress", from: current.ident, to:
|
|
464
|
-
current =
|
|
460
|
+
logger?.info({ phase: "progress", from: current.ident, to: resolvedNext.ident }, "Transitioning state.");
|
|
461
|
+
current = resolvedNext;
|
|
465
462
|
await current.playlist.run(stateData);
|
|
466
|
-
|
|
467
|
-
visitedIdents.add(current.ident);
|
|
463
|
+
visitedIdents.add(current.ident);
|
|
468
464
|
if (visitedIdents.size >= allStates.length) {
|
|
469
465
|
logger?.info({ phase: "end" }, "All reachable states visited, run complete.");
|
|
470
466
|
return stateData;
|
|
471
467
|
}
|
|
472
468
|
}
|
|
473
|
-
return stateData;
|
|
474
469
|
}
|
|
475
470
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple discriminated-union result type used by Tasks.
|
|
3
|
+
* Prefer returning a `Railroad` from `Task.run` instead of throwing exceptions.
|
|
4
|
+
*
|
|
5
|
+
* Example:
|
|
6
|
+
* - Success: `{ success: true, data: value }`
|
|
7
|
+
* - Failure: `{ success: false, error }`
|
|
8
|
+
*
|
|
9
|
+
* @template OutputType - The success payload type.
|
|
10
|
+
* @template ErrorType - Optional error payload type (default `Error`).
|
|
11
|
+
*/
|
|
1
12
|
type Railroad<
|
|
2
13
|
OutputType,
|
|
3
14
|
ErrorType = Error
|
|
@@ -8,20 +19,55 @@ type Railroad<
|
|
|
8
19
|
readonly success: false
|
|
9
20
|
readonly error: ErrorType
|
|
10
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Base class for all executable units in Klonk.
|
|
24
|
+
* Implement `validateInput` for runtime checks and `run` for the actual work.
|
|
25
|
+
*
|
|
26
|
+
* The type parameters power Klonk's autocomplete across Playlists and Workflows:
|
|
27
|
+
* - `InputType` is inferred at call sites when you build a Playlist.
|
|
28
|
+
* - `OutputType` becomes available to subsequent tasks under this task's `ident`.
|
|
29
|
+
* - `IdentType` should be a string literal to provide strongly-typed keys in outputs.
|
|
30
|
+
*
|
|
31
|
+
* See README Code Examples for end-to-end usage in Playlists and Machines.
|
|
32
|
+
*
|
|
33
|
+
* @template InputType - Runtime input shape expected by the task.
|
|
34
|
+
* @template OutputType - Result shape produced by the task.
|
|
35
|
+
* @template IdentType - Unique task identifier (use a string literal).
|
|
36
|
+
*/
|
|
11
37
|
declare abstract class Task<
|
|
12
38
|
InputType,
|
|
13
39
|
OutputType,
|
|
14
40
|
IdentType extends string
|
|
15
41
|
> {
|
|
16
42
|
ident: IdentType;
|
|
43
|
+
/**
|
|
44
|
+
* Unique identifier for the task. Also used as the key in Playlist outputs.
|
|
45
|
+
*/
|
|
17
46
|
constructor(ident: IdentType);
|
|
47
|
+
/**
|
|
48
|
+
* Execute the task logic.
|
|
49
|
+
* Return a `Railroad` to encode success or failure without throwing.
|
|
50
|
+
*
|
|
51
|
+
* @param input - The input object provided by the Playlist builder.
|
|
52
|
+
* @returns A `Railroad` containing output data on success, or an error on failure.
|
|
53
|
+
*/
|
|
18
54
|
abstract run(input: InputType): Promise<Railroad<OutputType>>;
|
|
19
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Function used to build the input for a Task from the Playlist context.
|
|
58
|
+
*
|
|
59
|
+
* @template SourceType - The source object passed to `run` (e.g., trigger event or machine state).
|
|
60
|
+
* @template AllOutputTypes - Accumulated outputs from previously executed tasks.
|
|
61
|
+
* @template TaskInputType - Concrete input type required by the target Task.
|
|
62
|
+
*/
|
|
20
63
|
type InputBuilder<
|
|
21
64
|
SourceType,
|
|
22
65
|
AllOutputTypes,
|
|
23
66
|
TaskInputType
|
|
24
67
|
> = (source: SourceType, outputs: AllOutputTypes) => TaskInputType;
|
|
68
|
+
/**
|
|
69
|
+
* @internal Internal assembly type that couples a task with its input builder.
|
|
70
|
+
*/
|
|
25
71
|
interface Machine<
|
|
26
72
|
SourceType,
|
|
27
73
|
AllOutputTypes,
|
|
@@ -32,14 +78,56 @@ interface Machine<
|
|
|
32
78
|
task: Task<TaskInputType, TaskOutputType, IdentType>;
|
|
33
79
|
builder: InputBuilder<SourceType, AllOutputTypes, TaskInputType>;
|
|
34
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Prevent TypeScript from inferring `T` from a builder argument so that the
|
|
83
|
+
* task definition remains the source of truth. Useful for preserving safety
|
|
84
|
+
* when chaining `.addTask` calls.
|
|
85
|
+
*/
|
|
35
86
|
type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
87
|
+
/**
|
|
88
|
+
* An ordered sequence of Tasks executed with strong type inference.
|
|
89
|
+
*
|
|
90
|
+
* As tasks are added via `.addTask`, their outputs are merged into the
|
|
91
|
+
* accumulated `AllOutputTypes` map using the task's `ident` as a key. The
|
|
92
|
+
* next task's input builder receives both the original `source` and the
|
|
93
|
+
* strongly-typed `outputs` from all previous tasks.
|
|
94
|
+
*
|
|
95
|
+
* Typical sources:
|
|
96
|
+
* - Workflow: the trigger event object.
|
|
97
|
+
* - Machine: the current mutable state object.
|
|
98
|
+
*
|
|
99
|
+
* See README Code Examples for how to chain tasks and consume outputs.
|
|
100
|
+
*
|
|
101
|
+
* @template AllOutputTypes - Map of task idents to their `Railroad<Output>` results.
|
|
102
|
+
* @template SourceType - The source object provided to `run`.
|
|
103
|
+
*/
|
|
36
104
|
declare class Playlist<
|
|
37
105
|
AllOutputTypes extends Record<string, any>,
|
|
38
106
|
SourceType = unknown
|
|
39
107
|
> {
|
|
108
|
+
/**
|
|
109
|
+
* Internal list of task + builder pairs in the order they will run.
|
|
110
|
+
*/
|
|
40
111
|
machines: Machine<any, any, any, any, string>[];
|
|
112
|
+
/**
|
|
113
|
+
* Optional finalizer invoked after all tasks complete (successfully or not).
|
|
114
|
+
*/
|
|
41
115
|
finalizer?: (source: SourceType, outputs: Record<string, any>) => void | Promise<void>;
|
|
42
116
|
constructor(machines?: Machine<any, any, any, any, string>[], finalizer?: (source: SourceType, outputs: Record<string, any>) => void | Promise<void>);
|
|
117
|
+
/**
|
|
118
|
+
* Append a task to the end of the playlist.
|
|
119
|
+
*
|
|
120
|
+
* The task's `ident` is used as a key in the aggregated `outputs` object made
|
|
121
|
+
* available to subsequent builders. The value under that key is the task's
|
|
122
|
+
* `Railroad<Output>` result, enabling type-safe success/error handling.
|
|
123
|
+
*
|
|
124
|
+
* @template TaskInputType - Input required by the task.
|
|
125
|
+
* @template TaskOutputType - Output produced by the task.
|
|
126
|
+
* @template IdentType - The task's ident (string literal recommended).
|
|
127
|
+
* @param task - The task instance to run at this step.
|
|
128
|
+
* @param builder - Function that builds the task input from `source` and prior `outputs`.
|
|
129
|
+
* @returns A new Playlist with the output map extended to include this task's result.
|
|
130
|
+
*/
|
|
43
131
|
addTask<
|
|
44
132
|
TaskInputType,
|
|
45
133
|
TaskOutputType,
|
|
@@ -47,9 +135,35 @@ declare class Playlist<
|
|
|
47
135
|
>(task: Task<TaskInputType, TaskOutputType, IdentType> & {
|
|
48
136
|
ident: IdentType
|
|
49
137
|
}, builder: (source: SourceType, outputs: AllOutputTypes) => NoInfer<TaskInputType>): Playlist<AllOutputTypes & { [K in IdentType] : Railroad<TaskOutputType> }, SourceType>;
|
|
138
|
+
/**
|
|
139
|
+
* Register a callback to run after the playlist finishes. Use this hook to
|
|
140
|
+
* react to the last task or to adjust machine state before a transition.
|
|
141
|
+
*
|
|
142
|
+
* Note: The callback receives the strongly-typed `outputs` object.
|
|
143
|
+
*
|
|
144
|
+
* @param finalizer - Callback executed once after all tasks complete.
|
|
145
|
+
* @returns This playlist for chaining.
|
|
146
|
+
*/
|
|
50
147
|
finally(finalizer: (source: SourceType, outputs: AllOutputTypes) => void | Promise<void>): this;
|
|
148
|
+
/**
|
|
149
|
+
* Execute all tasks in order, building each task's input via its builder
|
|
150
|
+
* and storing each result under the task's ident in the outputs map.
|
|
151
|
+
* If a task's `validateInput` returns false, execution stops with an error.
|
|
152
|
+
*
|
|
153
|
+
* @param source - The source object for this run (e.g., trigger event or machine state).
|
|
154
|
+
* @returns The aggregated, strongly-typed outputs map.
|
|
155
|
+
*/
|
|
51
156
|
run(source: SourceType): Promise<AllOutputTypes>;
|
|
52
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Event object produced by a `Trigger` and consumed by a `Workflow`.
|
|
160
|
+
*
|
|
161
|
+
* - `triggerIdent` lets a workflow that has multiple triggers disambiguate the source.
|
|
162
|
+
* - `data` is the trigger-specific payload (e.g., webhook body, file metadata, etc.).
|
|
163
|
+
*
|
|
164
|
+
* @template IdentType - Trigger identifier (use a string literal).
|
|
165
|
+
* @template T - Payload type emitted by this trigger.
|
|
166
|
+
*/
|
|
53
167
|
type TriggerEvent<
|
|
54
168
|
IdentType extends string,
|
|
55
169
|
T
|
|
@@ -57,6 +171,9 @@ type TriggerEvent<
|
|
|
57
171
|
triggerIdent: IdentType
|
|
58
172
|
data: T
|
|
59
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* @internal Small FIFO queue used by Triggers to buffer events.
|
|
176
|
+
*/
|
|
60
177
|
declare class EventQueue<TEventType> {
|
|
61
178
|
size: number;
|
|
62
179
|
queue: TEventType[];
|
|
@@ -71,11 +188,46 @@ declare abstract class Trigger<
|
|
|
71
188
|
> {
|
|
72
189
|
readonly ident: IdentType;
|
|
73
190
|
protected readonly queue: EventQueue<TriggerEvent<IdentType, TData>>;
|
|
191
|
+
/**
|
|
192
|
+
* Base class for event sources that feed Workflows.
|
|
193
|
+
* Implementations should acquire resources in `start` and release them in `stop`.
|
|
194
|
+
* Use `pushEvent` to enqueue new events; Workflows poll with `poll`.
|
|
195
|
+
*
|
|
196
|
+
* @param ident - Unique identifier for this trigger (use a string literal).
|
|
197
|
+
* @param queueSize - Max events buffered (oldest are dropped when at capacity). Default: 50.
|
|
198
|
+
*/
|
|
74
199
|
constructor(ident: IdentType, queueSize?: number);
|
|
200
|
+
/**
|
|
201
|
+
* Stop the trigger and release any resources.
|
|
202
|
+
*/
|
|
75
203
|
abstract stop(): Promise<void>;
|
|
204
|
+
/**
|
|
205
|
+
* Enqueue an event for consumption by a Workflow.
|
|
206
|
+
* Protected to avoid manual emission from outside trigger implementations.
|
|
207
|
+
*
|
|
208
|
+
* @param data - Event payload emitted by this trigger.
|
|
209
|
+
*/
|
|
76
210
|
protected pushEvent(data: TData): void;
|
|
211
|
+
/**
|
|
212
|
+
* Retrieve the next event in the buffer (or null if none available).
|
|
213
|
+
* Workflows call this in their polling loop.
|
|
214
|
+
*/
|
|
77
215
|
poll(): TriggerEvent<IdentType, TData> | null;
|
|
78
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Connects one or more `Trigger`s to a `Playlist`.
|
|
219
|
+
* When a trigger emits an event, the playlist runs with that event as `source`.
|
|
220
|
+
*
|
|
221
|
+
* - Add triggers with `addTrigger`.
|
|
222
|
+
* - Configure the playlist using `setPlaylist(p => p.addTask(...))`.
|
|
223
|
+
* - Start polling with `start`, optionally receiving a callback when a run completes.
|
|
224
|
+
*
|
|
225
|
+
* See README Code Examples for building a full workflow.
|
|
226
|
+
*
|
|
227
|
+
* @template AllTriggerEvents - Union of all trigger event shapes in this workflow.
|
|
228
|
+
* @template TAllOutputs - Aggregated outputs map shape produced by the playlist.
|
|
229
|
+
* @template TPlaylist - Concrete playlist type or `null` before configuration.
|
|
230
|
+
*/
|
|
79
231
|
declare class Workflow<
|
|
80
232
|
AllTriggerEvents extends TriggerEvent<string, any>,
|
|
81
233
|
TAllOutputs extends Record<string, any>,
|
|
@@ -84,24 +236,65 @@ declare class Workflow<
|
|
|
84
236
|
playlist: TPlaylist;
|
|
85
237
|
triggers: Trigger<string, any>[];
|
|
86
238
|
constructor(triggers: Trigger<string, any>[], playlist: TPlaylist);
|
|
239
|
+
/**
|
|
240
|
+
* Register a new trigger to feed events into the workflow.
|
|
241
|
+
* The resulting workflow type widens its `AllTriggerEvents` union accordingly.
|
|
242
|
+
*
|
|
243
|
+
* @template TIdent - Trigger ident (string literal recommended).
|
|
244
|
+
* @template TData - Payload type emitted by the trigger.
|
|
245
|
+
* @param trigger - The trigger instance to add.
|
|
246
|
+
* @returns A new Workflow instance with updated event type.
|
|
247
|
+
*/
|
|
87
248
|
addTrigger<
|
|
88
249
|
const TIdent extends string,
|
|
89
250
|
TData
|
|
90
251
|
>(trigger: Trigger<TIdent, TData>): Workflow<AllTriggerEvents | TriggerEvent<TIdent, TData>, TAllOutputs, Playlist<TAllOutputs, AllTriggerEvents | TriggerEvent<TIdent, TData>> | null>;
|
|
252
|
+
/**
|
|
253
|
+
* Configure the playlist by providing a builder that starts from an empty
|
|
254
|
+
* `Playlist<{}, AllTriggerEvents>` and returns your fully configured playlist.
|
|
255
|
+
*
|
|
256
|
+
* This method ensures type inference flows from your tasks and idents into
|
|
257
|
+
* the resulting `TBuilderOutputs` map used by the workflow's callback.
|
|
258
|
+
*
|
|
259
|
+
* @template TBuilderOutputs - Aggregated outputs map (deduced from your tasks).
|
|
260
|
+
* @template TFinalPlaylist - Concrete playlist type returned by the builder.
|
|
261
|
+
* @param builder - Receives an empty playlist and must return a configured one.
|
|
262
|
+
* @returns A new Workflow with the concrete playlist and output types.
|
|
263
|
+
*/
|
|
91
264
|
setPlaylist<
|
|
92
265
|
TBuilderOutputs extends Record<string, any>,
|
|
93
266
|
TFinalPlaylist extends Playlist<TBuilderOutputs, AllTriggerEvents>
|
|
94
267
|
>(builder: (p: Playlist<{}, AllTriggerEvents>) => TFinalPlaylist): Workflow<AllTriggerEvents, TBuilderOutputs, TFinalPlaylist>;
|
|
268
|
+
/**
|
|
269
|
+
* Begin polling triggers and run the playlist whenever an event is available.
|
|
270
|
+
* The loop uses `setTimeout` with the given `interval` and returns immediately.
|
|
271
|
+
*
|
|
272
|
+
* @param interval - Polling interval in milliseconds (default 5000ms).
|
|
273
|
+
* @param callback - Optional callback executed after each successful playlist run.
|
|
274
|
+
* @throws If called before a playlist is configured.
|
|
275
|
+
*/
|
|
95
276
|
start({ interval, callback }?: {
|
|
96
277
|
interval?: number
|
|
97
278
|
callback?: (source: AllTriggerEvents, outputs: TAllOutputs) => any
|
|
98
279
|
}): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* Create a new, empty workflow. Add triggers and set a playlist before starting.
|
|
282
|
+
*/
|
|
99
283
|
static create(): Workflow<never, {}, null>;
|
|
100
284
|
}
|
|
101
285
|
import { Logger } from "pino";
|
|
286
|
+
/**
|
|
287
|
+
* A weighted, conditional edge to a target state.
|
|
288
|
+
* Transitions are evaluated in descending `weight` order, then by insertion order.
|
|
289
|
+
*
|
|
290
|
+
* @template TStateData - Mutable state shared across the machine.
|
|
291
|
+
*/
|
|
102
292
|
type Transition<TStateData> = {
|
|
293
|
+
/** Target state node, resolved during `finalize`. */
|
|
103
294
|
to: StateNode<TStateData> | null
|
|
295
|
+
/** Async predicate that decides whether to take this transition. */
|
|
104
296
|
condition: (stateData: TStateData) => Promise<boolean>
|
|
297
|
+
/** Higher values are tried first; defaults to 0. */
|
|
105
298
|
weight?: number
|
|
106
299
|
};
|
|
107
300
|
/**
|
package/dist/index.js
CHANGED
|
@@ -397,26 +397,21 @@ class Machine {
|
|
|
397
397
|
}
|
|
398
398
|
retries++;
|
|
399
399
|
}
|
|
400
|
-
if (!next) {
|
|
401
|
-
logger?.info({ phase: "end" }, "No next state after retries, run complete.");
|
|
402
|
-
return stateData;
|
|
403
|
-
}
|
|
404
400
|
}
|
|
405
|
-
|
|
401
|
+
const resolvedNext = next;
|
|
402
|
+
if (resolvedNext === this.initialState) {
|
|
406
403
|
logger?.info({ phase: "end" }, "Next state is initial state, run complete.");
|
|
407
404
|
return stateData;
|
|
408
405
|
}
|
|
409
|
-
logger?.info({ phase: "progress", from: current.ident, to:
|
|
410
|
-
current =
|
|
406
|
+
logger?.info({ phase: "progress", from: current.ident, to: resolvedNext.ident }, "Transitioning state.");
|
|
407
|
+
current = resolvedNext;
|
|
411
408
|
await current.playlist.run(stateData);
|
|
412
|
-
|
|
413
|
-
visitedIdents.add(current.ident);
|
|
409
|
+
visitedIdents.add(current.ident);
|
|
414
410
|
if (visitedIdents.size >= allStates.length) {
|
|
415
411
|
logger?.info({ phase: "end" }, "All reachable states visited, run complete.");
|
|
416
412
|
return stateData;
|
|
417
413
|
}
|
|
418
414
|
}
|
|
419
|
-
return stateData;
|
|
420
415
|
}
|
|
421
416
|
}
|
|
422
417
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fkws/klonk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A lightweight, extensible workflow automation engine for Node.js and Bun",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"start": "bun index.ts",
|
|
26
26
|
"build": "bunx rimraf dist && bunup",
|
|
27
27
|
"prepublishOnly": "bun run build",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
28
31
|
"release": "bun scripts/release.js",
|
|
29
32
|
"release:patch": "bun scripts/release.js patch",
|
|
30
33
|
"release:minor": "bun scripts/release.js minor",
|
|
@@ -41,7 +44,9 @@
|
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@types/bun": "latest",
|
|
43
46
|
"@types/node": "latest",
|
|
44
|
-
"
|
|
47
|
+
"@vitest/coverage-v8": "^4.0.3",
|
|
48
|
+
"bunup": "^0.6.2",
|
|
49
|
+
"vitest": "^4.0.3"
|
|
45
50
|
},
|
|
46
51
|
"peerDependencies": {
|
|
47
52
|
"typescript": "^5"
|