@fkws/klonk 0.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,475 @@
1
+ var import_node_module = require("node:module");
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
20
+ var __toCommonJS = (from) => {
21
+ var entry = __moduleCache.get(from), desc;
22
+ if (entry)
23
+ return entry;
24
+ entry = __defProp({}, "__esModule", { value: true });
25
+ if (from && typeof from === "object" || typeof from === "function")
26
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
+ get: () => from[key],
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ }));
30
+ __moduleCache.set(from, entry);
31
+ return entry;
32
+ };
33
+ var __export = (target, all) => {
34
+ for (var name in all)
35
+ __defProp(target, name, {
36
+ get: all[name],
37
+ enumerable: true,
38
+ configurable: true,
39
+ set: (newValue) => all[name] = () => newValue
40
+ });
41
+ };
42
+
43
+ // src/index.ts
44
+ var exports_src = {};
45
+ __export(exports_src, {
46
+ Workflow: () => Workflow,
47
+ Trigger: () => Trigger,
48
+ Task: () => Task,
49
+ StateNode: () => StateNode,
50
+ Playlist: () => Playlist,
51
+ Machine: () => Machine
52
+ });
53
+ module.exports = __toCommonJS(exports_src);
54
+
55
+ // src/prototypes/Playlist.ts
56
+ class Playlist {
57
+ machines;
58
+ finalizer;
59
+ constructor(machines = [], finalizer) {
60
+ this.machines = machines;
61
+ this.finalizer = finalizer;
62
+ }
63
+ addTask(task, builder) {
64
+ const machine = { task, builder };
65
+ const newMachines = [...this.machines, machine];
66
+ return new Playlist(newMachines, this.finalizer);
67
+ }
68
+ finally(finalizer) {
69
+ this.finalizer = finalizer;
70
+ return this;
71
+ }
72
+ async run(source) {
73
+ const outputs = {};
74
+ for (const machine of this.machines) {
75
+ const input = machine.builder(source, outputs);
76
+ const isValid = await machine.task.validateInput(input);
77
+ if (!isValid) {
78
+ throw new Error(`Input validation failed for task '${machine.task.ident}'`);
79
+ }
80
+ const result = await machine.task.run(input);
81
+ outputs[machine.task.ident] = result;
82
+ }
83
+ if (this.finalizer) {
84
+ await this.finalizer(source, outputs);
85
+ }
86
+ return outputs;
87
+ }
88
+ }
89
+
90
+ // src/prototypes/Workflow.ts
91
+ class Workflow {
92
+ playlist;
93
+ triggers;
94
+ constructor(triggers, playlist) {
95
+ this.triggers = triggers;
96
+ this.playlist = playlist;
97
+ }
98
+ addTrigger(trigger) {
99
+ const newTriggers = [...this.triggers, trigger];
100
+ const newPlaylist = this.playlist;
101
+ return new Workflow(newTriggers, newPlaylist);
102
+ }
103
+ setPlaylist(builder) {
104
+ const initialPlaylist = new Playlist;
105
+ const finalPlaylist = builder(initialPlaylist);
106
+ return new Workflow(this.triggers, finalPlaylist);
107
+ }
108
+ async start({ interval = 5000, callback } = {}) {
109
+ if (!this.playlist) {
110
+ throw new Error("Cannot start a workflow without a playlist.");
111
+ }
112
+ for (const trigger of this.triggers) {
113
+ await trigger.start();
114
+ }
115
+ const runTick = async () => {
116
+ for (const trigger of this.triggers) {
117
+ const event = trigger.poll();
118
+ if (event) {
119
+ try {
120
+ const outputs = await this.playlist.run(event);
121
+ if (callback) {
122
+ callback(event, outputs);
123
+ }
124
+ } catch (error) {
125
+ console.error(`[Workflow] Error during playlist execution for trigger '${event.triggerIdent}':`, error);
126
+ }
127
+ }
128
+ }
129
+ setTimeout(runTick, interval);
130
+ };
131
+ runTick();
132
+ }
133
+ static create() {
134
+ return new Workflow([], null);
135
+ }
136
+ }
137
+ // src/prototypes/Task.ts
138
+ class Task {
139
+ ident;
140
+ constructor(ident) {
141
+ this.ident = ident;
142
+ }
143
+ }
144
+ // src/prototypes/Trigger.ts
145
+ class EventQueue {
146
+ size;
147
+ queue = [];
148
+ constructor(size) {
149
+ this.size = size;
150
+ }
151
+ push(item) {
152
+ if (this.queue.length + 1 > this.size) {
153
+ this.queue.shift();
154
+ }
155
+ this.queue.push(item);
156
+ }
157
+ shift() {
158
+ return this.queue.shift();
159
+ }
160
+ get length() {
161
+ return this.queue.length;
162
+ }
163
+ }
164
+
165
+ class Trigger {
166
+ ident;
167
+ queue;
168
+ constructor(ident, queueSize = 50) {
169
+ this.ident = ident;
170
+ this.queue = new EventQueue(queueSize);
171
+ }
172
+ pushEvent(data) {
173
+ this.queue.push({
174
+ triggerIdent: this.ident,
175
+ data
176
+ });
177
+ }
178
+ poll() {
179
+ const event = this.queue.shift();
180
+ return event ?? null;
181
+ }
182
+ }
183
+ // src/prototypes/Machine.ts
184
+ var import_pino = __toESM(require("pino"));
185
+ var import_crypto = require("crypto");
186
+ var glogger = null;
187
+
188
+ class StateNode {
189
+ transitions;
190
+ playlist;
191
+ timeToNextTick;
192
+ ident;
193
+ tempTransitions;
194
+ retry;
195
+ maxRetries;
196
+ constructor(transitions, playlist) {
197
+ this.transitions = transitions;
198
+ this.playlist = playlist;
199
+ this.timeToNextTick = 1000;
200
+ this.ident = "";
201
+ this.retry = 1000;
202
+ this.maxRetries = false;
203
+ }
204
+ static create() {
205
+ return new StateNode([], new Playlist);
206
+ }
207
+ addTransition({ to, condition, weight }) {
208
+ if (!this.tempTransitions) {
209
+ this.tempTransitions = [];
210
+ }
211
+ this.tempTransitions?.push({ to, condition, weight });
212
+ return this;
213
+ }
214
+ setPlaylist(arg) {
215
+ if (typeof arg === "function") {
216
+ const initial = new Playlist;
217
+ const finalPlaylist = arg(initial);
218
+ this.playlist = finalPlaylist;
219
+ return this;
220
+ }
221
+ this.playlist = arg;
222
+ return this;
223
+ }
224
+ preventRetry() {
225
+ this.retry = false;
226
+ return this;
227
+ }
228
+ retryDelayMs(delayMs) {
229
+ this.retry = delayMs;
230
+ return this;
231
+ }
232
+ retryLimit(maxRetries) {
233
+ this.maxRetries = maxRetries;
234
+ return this;
235
+ }
236
+ setIdent(ident) {
237
+ this.ident = ident;
238
+ return this;
239
+ }
240
+ getByIdent(ident, visited = []) {
241
+ if (this.ident === ident) {
242
+ return this;
243
+ }
244
+ if (visited.includes(this.ident)) {
245
+ return null;
246
+ }
247
+ visited.push(this.ident);
248
+ if (this.transitions) {
249
+ for (const transition of this.transitions) {
250
+ const found = transition.to?.getByIdent(ident, visited);
251
+ if (found)
252
+ return found;
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+ async next(data) {
258
+ const logger = glogger?.child({ path: "StateNode.next", state: this.ident });
259
+ logger?.info({ phase: "start" }, "Evaluating next state");
260
+ const sorted = [...this.transitions || []].map((t, i) => ({ t, i })).sort((a, b) => (b.t.weight ?? 0) - (a.t.weight ?? 0) || a.i - b.i);
261
+ for (const { t } of sorted) {
262
+ try {
263
+ if (await t.condition(data)) {
264
+ logger?.info({ phase: "end", nextState: t.to?.ident }, "Condition met, transitioning");
265
+ return t.to;
266
+ }
267
+ } catch (err) {
268
+ logger?.error({ phase: "error", error: err }, "Transition condition failed");
269
+ }
270
+ }
271
+ logger?.info({ phase: "end", nextState: null }, "No condition met, no transition");
272
+ return null;
273
+ }
274
+ }
275
+
276
+ class Machine {
277
+ initialState = null;
278
+ statesToCreate = [];
279
+ currentState = null;
280
+ finalized = false;
281
+ logger;
282
+ ident;
283
+ getAllStates() {
284
+ const logger = glogger?.child({ path: "machine.getAllStates" });
285
+ logger?.info({ phase: "start" }, "Gathering all states...");
286
+ if (!this.initialState)
287
+ return [];
288
+ const visited = new Set;
289
+ const result = [];
290
+ const stack = [this.initialState];
291
+ while (stack.length > 0) {
292
+ const node = stack.pop();
293
+ if (!node.ident || visited.has(node.ident))
294
+ continue;
295
+ visited.add(node.ident);
296
+ result.push(node);
297
+ for (const tr of node.transitions || []) {
298
+ if (tr.to && tr.to.ident && !visited.has(tr.to.ident)) {
299
+ stack.push(tr.to);
300
+ }
301
+ }
302
+ }
303
+ logger?.info({ phase: "end" }, "States gathered");
304
+ return result;
305
+ }
306
+ sleep(ms) {
307
+ return new Promise((resolve) => setTimeout(resolve, ms));
308
+ }
309
+ static create() {
310
+ return new Machine;
311
+ }
312
+ finalize({
313
+ verbose,
314
+ ident
315
+ } = {}) {
316
+ const logger = glogger?.child({ path: "machine.finalize", instance: this.ident });
317
+ if (!this.initialState || this.statesToCreate.length === 0) {
318
+ logger?.error({ phase: "error" }, "Finalization failed: no initial state or states to create");
319
+ throw new Error("Cannot finalize a machine without an initial state or states to create.");
320
+ }
321
+ if (ident) {
322
+ this.ident = ident;
323
+ } else {
324
+ this.ident = import_crypto.randomUUID();
325
+ }
326
+ if (verbose)
327
+ glogger = import_pino.default();
328
+ logger?.info("Logging enabled.");
329
+ logger?.info({ phase: "start" }, `Finalizing machine ${this.ident}...`);
330
+ const registry = new Map;
331
+ logger?.info({ phase: "progress" }, `Building state registry...`);
332
+ for (const s of this.statesToCreate) {
333
+ if (!s.ident) {
334
+ logger?.error({ phase: "error" }, "Finalization failed: state missing ident");
335
+ throw new Error("State missing ident.");
336
+ }
337
+ if (registry.has(s.ident)) {
338
+ logger?.error({ phase: "error", state: s.ident }, "Finalization failed: duplicate state ident");
339
+ throw new Error(`Duplicate state ident '${s.ident}'.`);
340
+ }
341
+ registry.set(s.ident, s);
342
+ }
343
+ logger?.info({ phase: "progress", count: registry.size }, `State registry built.`);
344
+ logger?.info({ phase: "progress" }, `Resolving transitions...`);
345
+ for (const state of this.statesToCreate) {
346
+ state.transitions = [];
347
+ for (const tr of state.tempTransitions || []) {
348
+ const toNode = registry.get(tr.to);
349
+ if (!toNode) {
350
+ logger?.error({ phase: "error", from: state.ident, to: tr.to }, "Finalization failed: target state not found");
351
+ throw new Error(`State '${tr.to}' not found.`);
352
+ }
353
+ state.transitions.push({ to: toNode, condition: tr.condition, weight: tr.weight });
354
+ }
355
+ state.tempTransitions = undefined;
356
+ }
357
+ logger?.info({ phase: "progress" }, `Transitions resolved.`);
358
+ this.statesToCreate = [];
359
+ this.finalized = true;
360
+ logger?.info({ phase: "end" }, `Machine ${this.ident} finalized.`);
361
+ return this;
362
+ }
363
+ addState(state, options = {}) {
364
+ const logger = glogger?.child({ path: "machine.addState", instance: this.ident });
365
+ logger?.info({ phase: "start", state: state.ident, isInitial: !!options.initial }, "Adding state");
366
+ this.statesToCreate.push(state);
367
+ if (options.initial) {
368
+ this.initialState = state;
369
+ }
370
+ logger?.info({ phase: "end", state: state.ident }, "State added");
371
+ return this;
372
+ }
373
+ async start(stateData, options) {
374
+ const logger = glogger?.child({ path: "machine.start", instance: this.ident });
375
+ logger?.info({ phase: "start" }, "Starting machine...");
376
+ if (!this.finalized) {
377
+ logger?.error({ phase: "error" }, "Machine not finalized");
378
+ throw new Error("Cannot start a machine that is not finalized.");
379
+ }
380
+ if (!this.initialState) {
381
+ logger?.error({ phase: "error" }, "No initial state");
382
+ throw new Error("Cannot start a machine without an initial state.");
383
+ }
384
+ const interval = options?.interval ?? 1000;
385
+ logger?.info({ phase: "progress", interval }, "Machine interval set");
386
+ if (!this.currentState) {
387
+ this.currentState = this.initialState;
388
+ logger?.info({ phase: "progress", state: this.currentState.ident }, "Set initial state. Running playlist.");
389
+ await this.currentState.playlist.run(stateData);
390
+ logger?.info({ phase: "progress", state: this.currentState.ident }, "Initial playlist run complete.");
391
+ }
392
+ const tick = async () => {
393
+ const tickLogger = logger?.child({ path: "machine.tick" });
394
+ tickLogger?.info({ phase: "start", state: this.currentState.ident }, "Tick.");
395
+ const next = await this.currentState.next(stateData);
396
+ if (next) {
397
+ tickLogger?.info({ phase: "progress", from: this.currentState.ident, to: next.ident }, "Transitioning state.");
398
+ this.currentState = next;
399
+ await this.currentState.playlist.run(stateData);
400
+ tickLogger?.info({ phase: "progress", state: this.currentState.ident }, "Playlist run complete.");
401
+ } else {
402
+ tickLogger?.info({ phase: "progress", state: this.currentState.ident }, "No next state.");
403
+ }
404
+ setTimeout(tick, interval);
405
+ };
406
+ tick();
407
+ logger?.info({ phase: "end" }, "Machine started, tick loop initiated.");
408
+ }
409
+ async run(stateData) {
410
+ const logger = glogger?.child({ path: "machine.run", instance: this.ident });
411
+ logger?.info({ phase: "start" }, "Running machine...");
412
+ if (!this.finalized) {
413
+ logger?.error({ phase: "error" }, "Machine not finalized");
414
+ throw new Error("Cannot run a machine that is not finalized.");
415
+ }
416
+ if (!this.initialState) {
417
+ logger?.error({ phase: "error" }, "No initial state");
418
+ throw new Error("Cannot run a machine without an initial state.");
419
+ }
420
+ const allStates = this.getAllStates();
421
+ const visitedIdents = new Set;
422
+ let current = this.initialState;
423
+ logger?.info({ phase: "progress", state: current.ident }, "Set initial state. Running playlist.");
424
+ await current.playlist.run(stateData);
425
+ visitedIdents.add(current.ident);
426
+ while (true) {
427
+ if (!current.transitions || current.transitions.length === 0) {
428
+ logger?.info({ phase: "end", state: current.ident }, "Reached leaf node, run complete.");
429
+ return stateData;
430
+ }
431
+ let next = await current.next(stateData);
432
+ if (!next) {
433
+ const retryDelay = current.retry;
434
+ if (!retryDelay) {
435
+ logger?.info({ phase: "end", state: current.ident }, "No next state and retries disabled, run complete.");
436
+ return stateData;
437
+ }
438
+ let retries = 0;
439
+ logger?.info({ phase: "progress", state: current.ident, retryDelay }, "No next state, beginning retry logic.");
440
+ while (!next) {
441
+ if (current.maxRetries && retries >= current.maxRetries) {
442
+ logger?.warn({ phase: "end", state: current.ident, retries }, "Retry limit exhausted, run complete.");
443
+ return stateData;
444
+ }
445
+ await this.sleep(retryDelay);
446
+ logger?.info({ phase: "progress", state: current.ident, attempt: retries + 1 }, "Retrying to find next state.");
447
+ next = await current.next(stateData);
448
+ if (next) {
449
+ logger?.info({ phase: "progress", state: current.ident, nextState: next.ident }, "Retry successful.");
450
+ break;
451
+ }
452
+ retries++;
453
+ }
454
+ if (!next) {
455
+ logger?.info({ phase: "end" }, "No next state after retries, run complete.");
456
+ return stateData;
457
+ }
458
+ }
459
+ if (next === this.initialState) {
460
+ logger?.info({ phase: "end" }, "Next state is initial state, run complete.");
461
+ return stateData;
462
+ }
463
+ logger?.info({ phase: "progress", from: current.ident, to: next.ident }, "Transitioning state.");
464
+ current = next;
465
+ await current.playlist.run(stateData);
466
+ if (current.ident)
467
+ visitedIdents.add(current.ident);
468
+ if (visitedIdents.size >= allStates.length) {
469
+ logger?.info({ phase: "end" }, "All reachable states visited, run complete.");
470
+ return stateData;
471
+ }
472
+ }
473
+ return stateData;
474
+ }
475
+ }