@quazardous/quarkernel 1.0.11 → 2.2.2

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/fsm.cjs ADDED
@@ -0,0 +1,1436 @@
1
+ 'use strict';
2
+
3
+ // src/kernel-event.ts
4
+ var KernelEvent = class {
5
+ /**
6
+ * Event name/type
7
+ */
8
+ name;
9
+ /**
10
+ * Event payload (typed, immutable)
11
+ */
12
+ data;
13
+ /**
14
+ * Shared mutable context for passing data between listeners
15
+ */
16
+ context;
17
+ /**
18
+ * Event creation timestamp (milliseconds since epoch)
19
+ */
20
+ timestamp;
21
+ /**
22
+ * Internal flag: stop propagation to remaining listeners
23
+ * @private
24
+ */
25
+ _propagationStopped = false;
26
+ constructor(name, data, context = {}) {
27
+ this.name = name;
28
+ this.data = data;
29
+ this.context = context;
30
+ this.timestamp = Date.now();
31
+ }
32
+ /**
33
+ * Stop propagation to remaining listeners in the chain
34
+ * Similar to DOM Event.stopPropagation()
35
+ *
36
+ * After calling this, no more listeners will execute.
37
+ */
38
+ stopPropagation = () => {
39
+ this._propagationStopped = true;
40
+ };
41
+ /**
42
+ * Check if propagation was stopped
43
+ * @internal
44
+ */
45
+ get isPropagationStopped() {
46
+ return this._propagationStopped;
47
+ }
48
+ };
49
+
50
+ // src/listener-context.ts
51
+ var ListenerContext = class {
52
+ /** Unique identifier for this listener instance */
53
+ id;
54
+ /** Event name this listener is registered for */
55
+ eventName;
56
+ /** Listener priority (higher = earlier execution) */
57
+ priority;
58
+ /** Listener dependencies (IDs of listeners that must execute first) */
59
+ dependencies;
60
+ /** AbortSignal for cancellation (if provided during registration) */
61
+ signal;
62
+ /** Reference to the kernel instance for emit/off operations */
63
+ kernel;
64
+ /** Reference to the listener function for removal */
65
+ listenerFn;
66
+ /** Current event being processed (set during emit) */
67
+ currentEvent;
68
+ /**
69
+ * Creates a new ListenerContext.
70
+ * @internal Use Kernel.on() to register listeners, which creates contexts automatically.
71
+ */
72
+ constructor(id, eventName, priority, dependencies, kernel, listenerFn, signal) {
73
+ this.id = id;
74
+ this.eventName = eventName;
75
+ this.priority = priority;
76
+ this.dependencies = dependencies;
77
+ this.kernel = kernel;
78
+ this.listenerFn = listenerFn;
79
+ this.signal = signal;
80
+ }
81
+ /**
82
+ * Sets the current event being processed.
83
+ * @internal Called by Kernel during emit()
84
+ */
85
+ setCurrentEvent = (event) => {
86
+ this.currentEvent = event;
87
+ };
88
+ /**
89
+ * Clears the current event after processing.
90
+ * @internal Called by Kernel after listener execution
91
+ */
92
+ clearCurrentEvent = () => {
93
+ this.currentEvent = void 0;
94
+ };
95
+ /**
96
+ * Removes this listener from the kernel.
97
+ * Alias for kernel.off() with this listener's reference.
98
+ */
99
+ cancel = () => {
100
+ this.kernel.off(this.eventName, this.listenerFn);
101
+ };
102
+ /**
103
+ * Alias for cancel() to match common naming patterns.
104
+ */
105
+ off = () => {
106
+ this.cancel();
107
+ };
108
+ /**
109
+ * Emits an event from within this listener.
110
+ * Delegates to kernel.emit().
111
+ */
112
+ emit = async (eventName, data) => {
113
+ return this.kernel.emit(eventName, data);
114
+ };
115
+ /**
116
+ * Stops propagation of the current event to remaining listeners.
117
+ * Requires an event to be currently processing.
118
+ */
119
+ stopPropagation = () => {
120
+ if (!this.currentEvent) {
121
+ throw new Error("stopPropagation() can only be called during event processing");
122
+ }
123
+ this.currentEvent.stopPropagation();
124
+ };
125
+ };
126
+
127
+ // src/wildcard.ts
128
+ var patternCache = /* @__PURE__ */ new Map();
129
+ var MAX_CACHE_SIZE = 100;
130
+ var patternToRegex = (pattern, delimiter = ":") => {
131
+ const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
132
+ if (pattern === "*") {
133
+ return new RegExp(`^[^${escapedDelimiter}]+$`);
134
+ }
135
+ if (pattern === "**") {
136
+ return new RegExp("^.*$");
137
+ }
138
+ const regexPattern = pattern.replace(/\*\*/g, "___DOUBLE_WILDCARD___").replace(/\*/g, `[^${escapedDelimiter}]+`).replace(/___DOUBLE_WILDCARD___/g, ".*");
139
+ return new RegExp(`^${regexPattern}$`);
140
+ };
141
+ var hasWildcard = (pattern) => {
142
+ return pattern.includes("*");
143
+ };
144
+ var getPatternRegex = (pattern, delimiter = ":") => {
145
+ const cacheKey = `${pattern}::${delimiter}`;
146
+ let regex = patternCache.get(cacheKey);
147
+ if (!regex) {
148
+ regex = patternToRegex(pattern, delimiter);
149
+ if (patternCache.size >= MAX_CACHE_SIZE) {
150
+ const firstKey = patternCache.keys().next().value;
151
+ if (firstKey !== void 0) {
152
+ patternCache.delete(firstKey);
153
+ }
154
+ }
155
+ patternCache.set(cacheKey, regex);
156
+ }
157
+ return regex;
158
+ };
159
+ var matchesPattern = (eventName, pattern, delimiter = ":") => {
160
+ if (!hasWildcard(pattern)) {
161
+ return eventName === pattern;
162
+ }
163
+ const regex = getPatternRegex(pattern, delimiter);
164
+ return regex.test(eventName);
165
+ };
166
+ var findMatchingPatterns = (eventName, patterns, delimiter = ":") => {
167
+ return patterns.filter((pattern) => matchesPattern(eventName, pattern, delimiter));
168
+ };
169
+
170
+ // src/toposort.ts
171
+ var CyclicDependencyError = class extends Error {
172
+ constructor(cycle) {
173
+ super(`Cyclic dependency detected: ${cycle.join(" -> ")}`);
174
+ this.cycle = cycle;
175
+ this.name = "CyclicDependencyError";
176
+ }
177
+ };
178
+ var toposort = (nodes) => {
179
+ const graph = /* @__PURE__ */ new Map();
180
+ const inDegree = /* @__PURE__ */ new Map();
181
+ const allNodes = /* @__PURE__ */ new Set();
182
+ nodes.forEach((node) => {
183
+ allNodes.add(node.id);
184
+ if (!graph.has(node.id)) {
185
+ graph.set(node.id, []);
186
+ }
187
+ if (!inDegree.has(node.id)) {
188
+ inDegree.set(node.id, 0);
189
+ }
190
+ });
191
+ nodes.forEach((node) => {
192
+ node.after.forEach((dep) => {
193
+ if (!allNodes.has(dep)) {
194
+ allNodes.add(dep);
195
+ graph.set(dep, []);
196
+ inDegree.set(dep, 0);
197
+ }
198
+ graph.get(dep).push(node.id);
199
+ inDegree.set(node.id, (inDegree.get(node.id) || 0) + 1);
200
+ });
201
+ });
202
+ const queue = [];
203
+ const result = [];
204
+ allNodes.forEach((id) => {
205
+ if (inDegree.get(id) === 0) {
206
+ queue.push(id);
207
+ }
208
+ });
209
+ while (queue.length > 0) {
210
+ const current = queue.shift();
211
+ result.push(current);
212
+ const neighbors = graph.get(current) || [];
213
+ neighbors.forEach((neighbor) => {
214
+ const newDegree = inDegree.get(neighbor) - 1;
215
+ inDegree.set(neighbor, newDegree);
216
+ if (newDegree === 0) {
217
+ queue.push(neighbor);
218
+ }
219
+ });
220
+ }
221
+ if (result.length !== allNodes.size) {
222
+ const remaining = Array.from(allNodes).filter((id) => !result.includes(id));
223
+ const cycle = detectCycle(remaining, graph);
224
+ throw new CyclicDependencyError(cycle);
225
+ }
226
+ return result.filter((id) => nodes.some((n) => n.id === id));
227
+ };
228
+ var detectCycle = (remaining, graph) => {
229
+ const visited = /* @__PURE__ */ new Set();
230
+ const recStack = /* @__PURE__ */ new Set();
231
+ const path = [];
232
+ const dfs = (node) => {
233
+ visited.add(node);
234
+ recStack.add(node);
235
+ path.push(node);
236
+ const neighbors = graph.get(node) || [];
237
+ for (const neighbor of neighbors) {
238
+ if (!remaining.includes(neighbor)) continue;
239
+ if (!visited.has(neighbor)) {
240
+ if (dfs(neighbor)) return true;
241
+ } else if (recStack.has(neighbor)) {
242
+ return true;
243
+ }
244
+ }
245
+ recStack.delete(node);
246
+ path.pop();
247
+ return false;
248
+ };
249
+ for (const node of remaining) {
250
+ if (!visited.has(node)) {
251
+ if (dfs(node)) {
252
+ return [...path];
253
+ }
254
+ }
255
+ }
256
+ return remaining;
257
+ };
258
+
259
+ // src/composition/mergers/namespaced.ts
260
+ var createNamespacedMerger = () => ({
261
+ merge: (contexts, _sources) => {
262
+ const result = {};
263
+ for (const [eventName, context] of contexts) {
264
+ for (const [key, value] of Object.entries(context)) {
265
+ result[`${eventName}:${key}`] = value;
266
+ }
267
+ }
268
+ return result;
269
+ },
270
+ mergeWithConflicts: (contexts, _sources) => {
271
+ const result = {};
272
+ for (const [eventName, context] of contexts) {
273
+ for (const [key, value] of Object.entries(context)) {
274
+ result[`${eventName}:${key}`] = value;
275
+ }
276
+ }
277
+ return {
278
+ context: result,
279
+ conflicts: []
280
+ // No conflicts possible with namespacing
281
+ };
282
+ }
283
+ });
284
+
285
+ // src/composition/composition.ts
286
+ var COMPOSED_EVENT = "__qk:composed__";
287
+ var Composition = class {
288
+ kernel;
289
+ subscriptions = [];
290
+ buffers = /* @__PURE__ */ new Map();
291
+ merger;
292
+ bufferLimit;
293
+ reset;
294
+ eventTTL;
295
+ eventTTLs;
296
+ sourceEvents = /* @__PURE__ */ new Set();
297
+ firedSinceLastComposite = /* @__PURE__ */ new Set();
298
+ lastConflicts = [];
299
+ expirationTimers = /* @__PURE__ */ new Map();
300
+ /**
301
+ * Create a new Composition
302
+ *
303
+ * @param kernels - Array of [kernel, eventName] tuples to subscribe to
304
+ * @param options - Composition options
305
+ */
306
+ constructor(kernels, options = {}) {
307
+ if (kernels.length === 0) {
308
+ throw new Error("Composition requires at least one kernel");
309
+ }
310
+ this.merger = options.merger ?? createNamespacedMerger();
311
+ this.bufferLimit = options.bufferLimit ?? 100;
312
+ this.reset = options.reset ?? true;
313
+ this.eventTTL = options.eventTTL ?? 0;
314
+ this.eventTTLs = options.eventTTLs ?? {};
315
+ this.kernel = new Kernel({
316
+ debug: false,
317
+ errorBoundary: true,
318
+ onContextConflict: options.onConflict ? (key, values) => {
319
+ options.onConflict({
320
+ key,
321
+ sources: [],
322
+ values
323
+ });
324
+ } : void 0
325
+ });
326
+ for (const [kernel, eventName] of kernels) {
327
+ this.subscribeToKernel(kernel, eventName);
328
+ this.sourceEvents.add(eventName);
329
+ }
330
+ }
331
+ /**
332
+ * Subscribe to events from a source kernel
333
+ */
334
+ subscribeToKernel(kernel, eventName) {
335
+ this.buffers.set(eventName, []);
336
+ const unbind = kernel.on(
337
+ eventName,
338
+ async (event) => {
339
+ await this.handleSourceEvent(eventName, event);
340
+ },
341
+ { priority: -Infinity }
342
+ );
343
+ this.subscriptions.push({
344
+ kernel,
345
+ eventName,
346
+ unbind
347
+ });
348
+ }
349
+ /**
350
+ * Get the effective TTL for a specific event
351
+ * Priority: per-event TTL > global TTL > 0 (permanent)
352
+ */
353
+ getEffectiveTTL(eventName) {
354
+ const perEventTTL = this.eventTTLs[eventName];
355
+ if (perEventTTL !== void 0) {
356
+ return perEventTTL;
357
+ }
358
+ return this.eventTTL > 0 ? this.eventTTL : "permanent";
359
+ }
360
+ /**
361
+ * Handle an event from a source kernel
362
+ */
363
+ async handleSourceEvent(eventName, event) {
364
+ const buffer = this.buffers.get(eventName);
365
+ if (!buffer) return;
366
+ const effectiveTTL = this.getEffectiveTTL(eventName);
367
+ const eventId = `${eventName}:${event.timestamp}:${Math.random().toString(36).slice(2, 8)}`;
368
+ buffer.push({
369
+ name: event.name,
370
+ data: event.data,
371
+ context: { ...event.context },
372
+ timestamp: event.timestamp
373
+ });
374
+ if (buffer.length > this.bufferLimit) {
375
+ buffer.shift();
376
+ }
377
+ this.firedSinceLastComposite.add(eventName);
378
+ const compositionCompleted = await this.checkAndEmitComposite();
379
+ if (effectiveTTL === "instant" && !compositionCompleted) {
380
+ buffer.pop();
381
+ this.firedSinceLastComposite.delete(eventName);
382
+ return;
383
+ }
384
+ if (typeof effectiveTTL === "number" && effectiveTTL > 0) {
385
+ const timer = setTimeout(() => {
386
+ this.expireEvent(eventName, event.timestamp);
387
+ this.expirationTimers.delete(eventId);
388
+ }, effectiveTTL);
389
+ this.expirationTimers.set(eventId, timer);
390
+ }
391
+ }
392
+ /**
393
+ * Expire an event from the buffer based on timestamp
394
+ */
395
+ expireEvent(eventName, timestamp) {
396
+ const buffer = this.buffers.get(eventName);
397
+ if (!buffer) return;
398
+ const filtered = buffer.filter((e) => e.timestamp > timestamp);
399
+ this.buffers.set(eventName, filtered);
400
+ if (filtered.length === 0) {
401
+ this.firedSinceLastComposite.delete(eventName);
402
+ }
403
+ }
404
+ /**
405
+ * Check if all source events have fired and emit composite event
406
+ * @returns true if composite was emitted, false otherwise
407
+ */
408
+ async checkAndEmitComposite() {
409
+ for (const eventName of this.sourceEvents) {
410
+ const buffer = this.buffers.get(eventName);
411
+ if (!buffer || buffer.length === 0) {
412
+ return false;
413
+ }
414
+ }
415
+ for (const eventName of this.sourceEvents) {
416
+ if (!this.firedSinceLastComposite.has(eventName)) {
417
+ return false;
418
+ }
419
+ }
420
+ if (this.kernel.listenerCount(COMPOSED_EVENT) === 0) {
421
+ return false;
422
+ }
423
+ const contexts = /* @__PURE__ */ new Map();
424
+ const sources = Array.from(this.sourceEvents);
425
+ for (const eventName of sources) {
426
+ const buffer = this.buffers.get(eventName);
427
+ if (!buffer || buffer.length === 0) continue;
428
+ const latestEvent = buffer[buffer.length - 1];
429
+ const eventData = latestEvent.data && typeof latestEvent.data === "object" ? latestEvent.data : {};
430
+ const combined = { ...eventData, ...latestEvent.context };
431
+ contexts.set(eventName, combined);
432
+ }
433
+ const mergeResult = this.merger.mergeWithConflicts(contexts, sources);
434
+ this.lastConflicts = mergeResult.conflicts;
435
+ await this.kernel.emit(COMPOSED_EVENT, {
436
+ sources,
437
+ contexts: Object.fromEntries(contexts),
438
+ merged: mergeResult.context
439
+ });
440
+ this.firedSinceLastComposite.clear();
441
+ if (this.reset) {
442
+ for (const eventName of this.sourceEvents) {
443
+ const buffer = this.buffers.get(eventName);
444
+ if (buffer && buffer.length > 0) {
445
+ const latest = buffer[buffer.length - 1];
446
+ this.buffers.set(eventName, [latest]);
447
+ }
448
+ }
449
+ }
450
+ return true;
451
+ }
452
+ /**
453
+ * Register a listener for when all source events have fired
454
+ * This is the primary way to react to composition completion
455
+ *
456
+ * @param listener - Function called with merged context when composition completes
457
+ * @param options - Listener options (priority, id, etc.)
458
+ * @returns Unbind function to remove the listener
459
+ */
460
+ onComposed(listener, options) {
461
+ return this.kernel.on(COMPOSED_EVENT, listener, options);
462
+ }
463
+ /**
464
+ * Remove a listener for composed events
465
+ */
466
+ offComposed(listener) {
467
+ this.kernel.off(COMPOSED_EVENT, listener);
468
+ }
469
+ /**
470
+ * Get number of composed event listeners
471
+ */
472
+ composedListenerCount() {
473
+ return this.kernel.listenerCount(COMPOSED_EVENT);
474
+ }
475
+ /**
476
+ * Register a listener for events on internal kernel
477
+ * Note: Use onComposed() to listen for composition completion
478
+ */
479
+ on(eventName, listener, options) {
480
+ return this.kernel.on(eventName, listener, options);
481
+ }
482
+ /**
483
+ * Remove a listener
484
+ * Delegates to internal kernel
485
+ */
486
+ off(eventName, listener) {
487
+ this.kernel.off(eventName, listener);
488
+ }
489
+ /**
490
+ * Emit an event through the composition
491
+ * Note: Reserved internal events (prefixed with __qk:) cannot be emitted
492
+ */
493
+ async emit(eventName, data) {
494
+ if (String(eventName).startsWith("__qk:")) {
495
+ throw new Error(`Cannot emit reserved event: ${String(eventName)}`);
496
+ }
497
+ return this.kernel.emit(eventName, data);
498
+ }
499
+ /**
500
+ * Get merged context from latest buffered events
501
+ * Does not emit - just returns the merged context
502
+ */
503
+ getContext() {
504
+ for (const eventName of this.sourceEvents) {
505
+ const buffer = this.buffers.get(eventName);
506
+ if (!buffer || buffer.length === 0) {
507
+ return null;
508
+ }
509
+ }
510
+ const contexts = /* @__PURE__ */ new Map();
511
+ const sources = Array.from(this.sourceEvents);
512
+ for (const eventName of sources) {
513
+ const buffer = this.buffers.get(eventName);
514
+ if (!buffer || buffer.length === 0) continue;
515
+ const latestEvent = buffer[buffer.length - 1];
516
+ const eventData = latestEvent.data && typeof latestEvent.data === "object" ? latestEvent.data : {};
517
+ const combined = { ...eventData, ...latestEvent.context };
518
+ contexts.set(eventName, combined);
519
+ }
520
+ const mergeResult = this.merger.mergeWithConflicts(contexts, sources);
521
+ this.lastConflicts = mergeResult.conflicts;
522
+ return mergeResult.context;
523
+ }
524
+ /**
525
+ * Get number of listeners for an event
526
+ */
527
+ listenerCount(eventName) {
528
+ return this.kernel.listenerCount(eventName);
529
+ }
530
+ /**
531
+ * Get all event names with registered listeners
532
+ */
533
+ eventNames() {
534
+ return this.kernel.eventNames();
535
+ }
536
+ /**
537
+ * Remove all listeners
538
+ */
539
+ offAll(eventName) {
540
+ this.kernel.offAll(eventName);
541
+ }
542
+ /**
543
+ * Enable/disable debug mode
544
+ */
545
+ debug(enabled) {
546
+ this.kernel.debug(enabled);
547
+ }
548
+ /**
549
+ * Get buffer for a specific source event (for debugging)
550
+ */
551
+ getBuffer(eventName) {
552
+ return this.buffers.get(eventName);
553
+ }
554
+ /**
555
+ * Clear all buffers
556
+ */
557
+ clearBuffers() {
558
+ for (const eventName of this.sourceEvents) {
559
+ this.buffers.set(eventName, []);
560
+ }
561
+ this.firedSinceLastComposite.clear();
562
+ }
563
+ /**
564
+ * Get conflicts detected during the last merge operation
565
+ *
566
+ * Returns an array of ConflictInfo objects describing which source events
567
+ * provided conflicting values for the same context keys.
568
+ *
569
+ * @returns Array of conflicts from the last merge, or empty array if no conflicts
570
+ */
571
+ getConflicts() {
572
+ return this.lastConflicts;
573
+ }
574
+ /**
575
+ * Cleanup all subscriptions and listeners
576
+ */
577
+ dispose() {
578
+ for (const sub of this.subscriptions) {
579
+ sub.unbind();
580
+ }
581
+ this.subscriptions = [];
582
+ for (const timer of this.expirationTimers.values()) {
583
+ clearTimeout(timer);
584
+ }
585
+ this.expirationTimers.clear();
586
+ this.kernel.offAll();
587
+ this.buffers.clear();
588
+ this.sourceEvents.clear();
589
+ this.firedSinceLastComposite.clear();
590
+ this.lastConflicts = [];
591
+ }
592
+ /**
593
+ * Get the configured global event TTL in milliseconds
594
+ * @returns The TTL value, or 0 if no TTL is configured
595
+ */
596
+ getEventTTL() {
597
+ return this.eventTTL;
598
+ }
599
+ /**
600
+ * Set the global event TTL in milliseconds
601
+ * @param ttl - TTL in milliseconds (0 = permanent)
602
+ */
603
+ setEventTTL(ttl) {
604
+ this.eventTTL = ttl;
605
+ }
606
+ /**
607
+ * Get per-event TTL configuration
608
+ * @returns The eventTTLs configuration object
609
+ */
610
+ getEventTTLs() {
611
+ return this.eventTTLs;
612
+ }
613
+ /**
614
+ * Set TTL for a specific event
615
+ * @param eventName - The event name to configure
616
+ * @param ttl - TTL value: number (ms), 'permanent', or 'instant'
617
+ */
618
+ setEventTTLFor(eventName, ttl) {
619
+ this.eventTTLs[eventName] = ttl;
620
+ }
621
+ /**
622
+ * Remove per-event TTL configuration (falls back to global TTL)
623
+ * @param eventName - The event name to reset
624
+ */
625
+ clearEventTTLFor(eventName) {
626
+ delete this.eventTTLs[eventName];
627
+ }
628
+ };
629
+
630
+ // src/kernel.ts
631
+ var Kernel = class _Kernel {
632
+ listeners = /* @__PURE__ */ new Map();
633
+ options;
634
+ listenerIdCounter = 0;
635
+ executionErrors = [];
636
+ constructor(options = {}) {
637
+ this.options = {
638
+ delimiter: options.delimiter ?? ":",
639
+ wildcard: options.wildcard ?? true,
640
+ maxListeners: options.maxListeners ?? Infinity,
641
+ debug: options.debug ?? false,
642
+ errorBoundary: options.errorBoundary ?? true,
643
+ onError: options.onError ?? ((error) => {
644
+ console.error("Kernel error:", error);
645
+ }),
646
+ contextMerger: options.contextMerger ?? void 0,
647
+ onContextConflict: options.onContextConflict ?? void 0
648
+ };
649
+ if (this.options.debug) {
650
+ console.debug("[QuarKernel] Kernel initialized", {
651
+ delimiter: this.options.delimiter,
652
+ wildcard: this.options.wildcard,
653
+ maxListeners: this.options.maxListeners,
654
+ errorBoundary: this.options.errorBoundary
655
+ });
656
+ }
657
+ }
658
+ /**
659
+ * Register an event listener
660
+ * Returns an unbind function for cleanup
661
+ */
662
+ on(eventName, listener, options = {}) {
663
+ const event = String(eventName);
664
+ if (options.signal?.aborted) {
665
+ if (this.options.debug) {
666
+ console.debug("[QuarKernel] Listener not added (signal already aborted)", {
667
+ event
668
+ });
669
+ }
670
+ return () => {
671
+ };
672
+ }
673
+ const priority = options.priority ?? 0;
674
+ const id = options.id ?? `listener_${++this.listenerIdCounter}`;
675
+ const after = Array.isArray(options.after) ? options.after : options.after ? [options.after] : [];
676
+ let abortListener;
677
+ if (options.signal) {
678
+ abortListener = () => this.off(event, listener);
679
+ }
680
+ const entry = {
681
+ id,
682
+ callback: listener,
683
+ after,
684
+ priority,
685
+ once: options.once ?? false,
686
+ original: listener,
687
+ signal: options.signal,
688
+ abortListener
689
+ };
690
+ const entries = this.listeners.get(event) ?? [];
691
+ entries.push(entry);
692
+ entries.sort((a, b) => b.priority - a.priority);
693
+ this.listeners.set(event, entries);
694
+ if (this.options.debug) {
695
+ console.debug("[QuarKernel] Listener added", {
696
+ event,
697
+ listenerId: id,
698
+ priority,
699
+ after,
700
+ once: entry.once,
701
+ totalListeners: entries.length
702
+ });
703
+ }
704
+ if (this.options.maxListeners > 0 && entries.length > this.options.maxListeners) {
705
+ console.warn(
706
+ `MaxListenersExceeded: Event "${event}" has ${entries.length} listeners (limit: ${this.options.maxListeners})`
707
+ );
708
+ }
709
+ if (options.signal && abortListener) {
710
+ options.signal.addEventListener("abort", abortListener, { once: true });
711
+ }
712
+ return () => this.off(event, listener);
713
+ }
714
+ /**
715
+ * Remove an event listener
716
+ * If no listener provided, removes all listeners for the event
717
+ */
718
+ off(eventName, listener) {
719
+ const entries = this.listeners.get(eventName);
720
+ if (!entries) {
721
+ return;
722
+ }
723
+ if (!listener) {
724
+ if (this.options.debug) {
725
+ console.debug("[QuarKernel] All listeners removed", {
726
+ event: eventName,
727
+ count: entries.length
728
+ });
729
+ }
730
+ for (const entry of entries) {
731
+ if (entry.signal && entry.abortListener) {
732
+ entry.signal.removeEventListener("abort", entry.abortListener);
733
+ }
734
+ }
735
+ this.listeners.delete(eventName);
736
+ return;
737
+ }
738
+ const entryToRemove = entries.find((entry) => entry.original === listener);
739
+ if (entryToRemove?.signal && entryToRemove.abortListener) {
740
+ entryToRemove.signal.removeEventListener("abort", entryToRemove.abortListener);
741
+ }
742
+ const filtered = entries.filter((entry) => entry.original !== listener);
743
+ const removed = entries.length - filtered.length;
744
+ if (this.options.debug && removed > 0) {
745
+ console.debug("[QuarKernel] Listener removed", {
746
+ event: eventName,
747
+ removed,
748
+ remaining: filtered.length
749
+ });
750
+ }
751
+ if (filtered.length === 0) {
752
+ this.listeners.delete(eventName);
753
+ } else {
754
+ this.listeners.set(eventName, filtered);
755
+ }
756
+ }
757
+ /**
758
+ * Emit an event
759
+ * Executes all registered listeners in parallel (by default)
760
+ * Returns a Promise that resolves when all listeners complete
761
+ * Throws AggregateError if any listeners failed
762
+ */
763
+ async emit(eventName, data) {
764
+ const event = String(eventName);
765
+ const allPatterns = Array.from(this.listeners.keys());
766
+ const matchingPatterns = this.options.wildcard ? findMatchingPatterns(event, allPatterns, this.options.delimiter) : allPatterns.filter((p) => p === event);
767
+ const allEntries = [];
768
+ for (const pattern of matchingPatterns) {
769
+ const entries = this.listeners.get(pattern);
770
+ if (entries) {
771
+ allEntries.push(...entries);
772
+ }
773
+ }
774
+ if (allEntries.length === 0) {
775
+ if (this.options.debug) {
776
+ console.debug("[QuarKernel] Event emitted (no listeners)", { event });
777
+ }
778
+ return;
779
+ }
780
+ if (this.options.debug) {
781
+ console.debug("[QuarKernel] Event emitted", {
782
+ event,
783
+ listenerCount: allEntries.length,
784
+ data: data !== void 0 ? JSON.stringify(data).substring(0, 100) : void 0
785
+ });
786
+ }
787
+ this.executionErrors = [];
788
+ const kernelEvent = new KernelEvent(
789
+ event,
790
+ data,
791
+ {}
792
+ );
793
+ const sortedEntries = this.sortListenersByDependencies(allEntries);
794
+ const promises = sortedEntries.map(
795
+ (entry) => this.executeListener(entry, kernelEvent, event)
796
+ );
797
+ const results = await Promise.allSettled(promises);
798
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
799
+ if (!this.options.errorBoundary) {
800
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
801
+ if (errors.length > 0) {
802
+ throw new AggregateError(errors, `${errors.length} listener(s) failed for event "${event}"`);
803
+ }
804
+ }
805
+ if (this.options.debug) {
806
+ console.debug("[QuarKernel] Event completed", {
807
+ event
808
+ });
809
+ }
810
+ }
811
+ /**
812
+ * Emit an event with serial execution
813
+ * Executes listeners sequentially (one after another) instead of in parallel
814
+ * Respects the same dependency and priority ordering as emit()
815
+ * Stops on first error if errorBoundary is false, otherwise continues and collects errors
816
+ */
817
+ async emitSerial(eventName, data) {
818
+ const event = String(eventName);
819
+ const allPatterns = Array.from(this.listeners.keys());
820
+ const matchingPatterns = this.options.wildcard ? findMatchingPatterns(event, allPatterns, this.options.delimiter) : allPatterns.filter((p) => p === event);
821
+ const allEntries = [];
822
+ for (const pattern of matchingPatterns) {
823
+ const entries = this.listeners.get(pattern);
824
+ if (entries) {
825
+ allEntries.push(...entries);
826
+ }
827
+ }
828
+ if (allEntries.length === 0) {
829
+ if (this.options.debug) {
830
+ console.debug("[QuarKernel] Event emitted serially (no listeners)", { event });
831
+ }
832
+ return;
833
+ }
834
+ if (this.options.debug) {
835
+ console.debug("[QuarKernel] Event emitted serially", {
836
+ event,
837
+ listenerCount: allEntries.length,
838
+ data: data !== void 0 ? JSON.stringify(data).substring(0, 100) : void 0
839
+ });
840
+ }
841
+ this.executionErrors = [];
842
+ const kernelEvent = new KernelEvent(
843
+ event,
844
+ data,
845
+ {}
846
+ );
847
+ const sortedEntries = this.sortListenersByDependencies(allEntries);
848
+ const errors = [];
849
+ for (const entry of sortedEntries) {
850
+ try {
851
+ await this.executeListener(entry, kernelEvent, event);
852
+ } catch (error) {
853
+ if (!this.options.errorBoundary) {
854
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
855
+ throw error;
856
+ }
857
+ errors.push(error);
858
+ }
859
+ }
860
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
861
+ if (!this.options.errorBoundary && errors.length > 0) {
862
+ throw new AggregateError(errors, `${errors.length} listener(s) failed for event "${event}"`);
863
+ }
864
+ if (this.options.debug) {
865
+ console.debug("[QuarKernel] Event completed serially", {
866
+ event
867
+ });
868
+ }
869
+ }
870
+ /**
871
+ * Sort listeners by dependencies and priority
872
+ * Uses topological sort for dependency resolution
873
+ */
874
+ sortListenersByDependencies(entries) {
875
+ const hasDependencies = entries.some((e) => e.after.length > 0);
876
+ if (!hasDependencies) {
877
+ return [...entries].sort((a, b) => b.priority - a.priority);
878
+ }
879
+ const listenerIds = new Set(entries.map((e) => e.id));
880
+ for (const entry of entries) {
881
+ for (const dep of entry.after) {
882
+ if (!listenerIds.has(dep)) {
883
+ throw new Error(`Listener "${entry.id}" depends on missing listener "${dep}"`);
884
+ }
885
+ }
886
+ }
887
+ const nodes = entries.map((e) => ({
888
+ id: e.id,
889
+ after: e.after
890
+ }));
891
+ toposort(nodes);
892
+ const levelMap = /* @__PURE__ */ new Map();
893
+ const assignLevel = (id, visited = /* @__PURE__ */ new Set()) => {
894
+ if (levelMap.has(id)) {
895
+ return levelMap.get(id);
896
+ }
897
+ if (visited.has(id)) {
898
+ return 0;
899
+ }
900
+ visited.add(id);
901
+ const entry = entries.find((e) => e.id === id);
902
+ if (!entry || entry.after.length === 0) {
903
+ levelMap.set(id, 0);
904
+ return 0;
905
+ }
906
+ const maxDepLevel = Math.max(...entry.after.map((dep) => assignLevel(dep, visited)));
907
+ const level = maxDepLevel + 1;
908
+ levelMap.set(id, level);
909
+ return level;
910
+ };
911
+ entries.forEach((e) => assignLevel(e.id));
912
+ return [...entries].sort((a, b) => {
913
+ const levelA = levelMap.get(a.id) ?? 0;
914
+ const levelB = levelMap.get(b.id) ?? 0;
915
+ if (levelA !== levelB) {
916
+ return levelA - levelB;
917
+ }
918
+ return b.priority - a.priority;
919
+ });
920
+ }
921
+ /**
922
+ * Execute a single listener with error handling
923
+ */
924
+ async executeListener(entry, event, eventName) {
925
+ if (event.isPropagationStopped) {
926
+ if (this.options.debug) {
927
+ console.debug("[QuarKernel] Listener skipped (propagation stopped)", {
928
+ listenerId: entry.id
929
+ });
930
+ }
931
+ return;
932
+ }
933
+ const startTime = Date.now();
934
+ if (this.options.debug) {
935
+ console.debug("[QuarKernel] Listener executing", {
936
+ listenerId: entry.id,
937
+ event: eventName,
938
+ priority: entry.priority
939
+ });
940
+ }
941
+ try {
942
+ const ctx = new ListenerContext(
943
+ entry.id,
944
+ eventName,
945
+ entry.priority,
946
+ entry.after,
947
+ this,
948
+ entry.original,
949
+ entry.signal
950
+ );
951
+ ctx.setCurrentEvent(event);
952
+ try {
953
+ await entry.callback(event, ctx);
954
+ if (this.options.debug) {
955
+ const duration = Date.now() - startTime;
956
+ console.debug("[QuarKernel] Listener completed", {
957
+ listenerId: entry.id,
958
+ duration: `${duration}ms`
959
+ });
960
+ }
961
+ } finally {
962
+ ctx.clearCurrentEvent();
963
+ }
964
+ } catch (error) {
965
+ const executionError = {
966
+ listenerId: entry.id,
967
+ error,
968
+ timestamp: Date.now(),
969
+ eventName
970
+ };
971
+ this.executionErrors.push(executionError);
972
+ if (this.options.debug) {
973
+ console.debug("[QuarKernel] Listener error", {
974
+ listenerId: entry.id,
975
+ error: error.message
976
+ });
977
+ }
978
+ if (this.options.errorBoundary) {
979
+ this.options.onError(error, event);
980
+ } else {
981
+ throw error;
982
+ }
983
+ }
984
+ }
985
+ /**
986
+ * Remove listeners marked with once: true or whose predicate returns true after execution
987
+ *
988
+ * This method is called AFTER all listeners have executed for an event.
989
+ * The predicate functions receive the event object with the final state after all listeners ran.
990
+ *
991
+ * Behavior:
992
+ * - If once: true, the listener is always removed after execution
993
+ * - If once is a predicate function, it's evaluated with the post-execution event state
994
+ * - Predicates can examine event.context to make decisions based on listener modifications
995
+ * - Listeners are removed even if they threw errors (when errorBoundary: true)
996
+ *
997
+ * @param eventName - The event name being processed
998
+ * @param entries - Listeners that executed (or were scheduled to execute)
999
+ * @param event - The event object with final state after all listeners executed
1000
+ */
1001
+ removeOnceListeners(eventName, entries, event) {
1002
+ const listenersToRemove = entries.filter((entry) => {
1003
+ if (!entry.once) {
1004
+ return false;
1005
+ }
1006
+ if (entry.once === true) {
1007
+ return true;
1008
+ }
1009
+ if (typeof entry.once === "function") {
1010
+ return entry.once(event);
1011
+ }
1012
+ return false;
1013
+ });
1014
+ if (listenersToRemove.length === 0) {
1015
+ return;
1016
+ }
1017
+ if (this.options.debug) {
1018
+ console.debug("[QuarKernel] Removing once listeners", {
1019
+ event: eventName,
1020
+ count: listenersToRemove.length
1021
+ });
1022
+ }
1023
+ for (const entry of listenersToRemove) {
1024
+ for (const [pattern, entries2] of this.listeners.entries()) {
1025
+ if (entries2.includes(entry)) {
1026
+ this.off(pattern, entry.original);
1027
+ break;
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+ /**
1033
+ * Get number of listeners for an event
1034
+ */
1035
+ listenerCount(eventName) {
1036
+ if (!eventName) {
1037
+ let total = 0;
1038
+ for (const entries2 of this.listeners.values()) {
1039
+ total += entries2.length;
1040
+ }
1041
+ return total;
1042
+ }
1043
+ const event = String(eventName);
1044
+ const entries = this.listeners.get(event);
1045
+ return entries?.length ?? 0;
1046
+ }
1047
+ /**
1048
+ * Get all event names with registered listeners
1049
+ */
1050
+ eventNames() {
1051
+ return Array.from(this.listeners.keys());
1052
+ }
1053
+ /**
1054
+ * Remove all listeners for all events (or specific event)
1055
+ */
1056
+ offAll(eventName) {
1057
+ if (!eventName) {
1058
+ for (const entries2 of this.listeners.values()) {
1059
+ for (const entry of entries2) {
1060
+ if (entry.signal && entry.abortListener) {
1061
+ entry.signal.removeEventListener("abort", entry.abortListener);
1062
+ }
1063
+ }
1064
+ }
1065
+ this.listeners.clear();
1066
+ return;
1067
+ }
1068
+ const event = String(eventName);
1069
+ const entries = this.listeners.get(event);
1070
+ if (entries) {
1071
+ for (const entry of entries) {
1072
+ if (entry.signal && entry.abortListener) {
1073
+ entry.signal.removeEventListener("abort", entry.abortListener);
1074
+ }
1075
+ }
1076
+ }
1077
+ this.listeners.delete(event);
1078
+ }
1079
+ /**
1080
+ * Enable/disable debug mode
1081
+ * In T115, this is a placeholder - full debug implementation in T129
1082
+ */
1083
+ debug(enabled) {
1084
+ this.options.debug = enabled;
1085
+ if (enabled) {
1086
+ console.debug("[QuarKernel] Debug mode enabled");
1087
+ } else {
1088
+ console.debug("[QuarKernel] Debug mode disabled");
1089
+ }
1090
+ }
1091
+ /**
1092
+ * Get collected execution errors from the last emit
1093
+ * Useful for error aggregation and reporting
1094
+ */
1095
+ getExecutionErrors() {
1096
+ return this.executionErrors;
1097
+ }
1098
+ /**
1099
+ * Clear collected execution errors
1100
+ */
1101
+ clearExecutionErrors() {
1102
+ this.executionErrors = [];
1103
+ }
1104
+ /**
1105
+ * Create a composition from multiple kernels
1106
+ *
1107
+ * @param kernels - Rest parameters of [kernel, eventName] tuples
1108
+ * @param options - Optional composition options (if last argument is not a tuple)
1109
+ * @returns Composition instance that merges events from all kernels
1110
+ *
1111
+ * @example
1112
+ * ```ts
1113
+ * const userKernel = createKernel();
1114
+ * const profileKernel = createKernel();
1115
+ *
1116
+ * const composition = Kernel.compose(
1117
+ * [userKernel, 'user:loaded'],
1118
+ * [profileKernel, 'profile:loaded'],
1119
+ * { merger: createNamespacedMerger() }
1120
+ * );
1121
+ *
1122
+ * composition.onComposed((event) => {
1123
+ * console.log('All sources ready:', event.data.merged);
1124
+ * });
1125
+ * ```
1126
+ */
1127
+ static compose(...args) {
1128
+ const kernels = [];
1129
+ let options;
1130
+ for (const arg of args) {
1131
+ if (Array.isArray(arg) && arg.length === 2 && arg[0] instanceof _Kernel) {
1132
+ kernels.push(arg);
1133
+ } else if (typeof arg === "object" && !Array.isArray(arg)) {
1134
+ options = arg;
1135
+ }
1136
+ }
1137
+ return new Composition(kernels, options);
1138
+ }
1139
+ };
1140
+
1141
+ // src/fsm/machine.ts
1142
+ function useMachine(kernel, config) {
1143
+ const {
1144
+ prefix,
1145
+ initial,
1146
+ states,
1147
+ allowForce = true,
1148
+ snapshot,
1149
+ trackHistory = false,
1150
+ maxHistory = 100
1151
+ } = config;
1152
+ let currentState = snapshot?.state ?? initial;
1153
+ let context = snapshot?.context ?? config.context ?? {};
1154
+ let history = snapshot?.history ? [...snapshot.history] : [];
1155
+ if (!states[currentState]) {
1156
+ throw new Error(`Invalid initial state "${currentState}" - not defined in states`);
1157
+ }
1158
+ const cleanupFns = [];
1159
+ const emitFSM = async (eventType, data) => {
1160
+ const eventData = {
1161
+ machine: prefix,
1162
+ ...data
1163
+ };
1164
+ await kernel.emit(`${prefix}:${eventType}`, eventData);
1165
+ };
1166
+ const getTransition = (event) => {
1167
+ const stateNode = states[currentState];
1168
+ if (!stateNode?.on) return null;
1169
+ const transition = stateNode.on[event];
1170
+ if (!transition) return null;
1171
+ if (typeof transition === "string") {
1172
+ return { target: transition };
1173
+ }
1174
+ return transition;
1175
+ };
1176
+ const doTransition = async (event, targetState, payload, forced = false) => {
1177
+ const fromState = currentState;
1178
+ const fromNode = states[fromState];
1179
+ const toNode = states[targetState];
1180
+ if (!toNode) {
1181
+ throw new Error(`Invalid target state "${targetState}" - not defined in states`);
1182
+ }
1183
+ if (fromNode?.exit) {
1184
+ await fromNode.exit(context, event, payload);
1185
+ }
1186
+ await emitFSM(`exit:${fromState}`, {
1187
+ state: fromState,
1188
+ from: fromState,
1189
+ to: targetState,
1190
+ event,
1191
+ payload,
1192
+ forced
1193
+ });
1194
+ currentState = targetState;
1195
+ if (trackHistory) {
1196
+ history.push({
1197
+ from: fromState,
1198
+ to: targetState,
1199
+ event,
1200
+ timestamp: Date.now()
1201
+ });
1202
+ if (history.length > maxHistory) {
1203
+ history = history.slice(-maxHistory);
1204
+ }
1205
+ }
1206
+ await emitFSM("transition", {
1207
+ state: targetState,
1208
+ from: fromState,
1209
+ to: targetState,
1210
+ event,
1211
+ payload,
1212
+ forced
1213
+ });
1214
+ await emitFSM(`transition:${event}`, {
1215
+ state: targetState,
1216
+ from: fromState,
1217
+ to: targetState,
1218
+ event,
1219
+ payload,
1220
+ forced
1221
+ });
1222
+ await emitFSM(`enter:${targetState}`, {
1223
+ state: targetState,
1224
+ from: fromState,
1225
+ to: targetState,
1226
+ event,
1227
+ payload,
1228
+ forced
1229
+ });
1230
+ if (toNode.entry) {
1231
+ await toNode.entry(context, event, payload);
1232
+ }
1233
+ };
1234
+ const machine = {
1235
+ prefix,
1236
+ getState() {
1237
+ return currentState;
1238
+ },
1239
+ getContext() {
1240
+ return context;
1241
+ },
1242
+ setContext(updater) {
1243
+ if (typeof updater === "function") {
1244
+ context = updater(context);
1245
+ } else {
1246
+ context = { ...context, ...updater };
1247
+ }
1248
+ },
1249
+ async send(event, payload, options = {}) {
1250
+ const { force = false, target, guard: inlineGuard, fallback } = options;
1251
+ if (force && allowForce) {
1252
+ const targetState = target ?? initial;
1253
+ await doTransition(event, targetState, payload, true);
1254
+ return true;
1255
+ }
1256
+ const transition = getTransition(event);
1257
+ if (!transition) {
1258
+ if (force && !allowForce) {
1259
+ throw new Error(`Force transitions not allowed on machine "${prefix}"`);
1260
+ }
1261
+ return false;
1262
+ }
1263
+ const guardFn = inlineGuard ?? transition.guard;
1264
+ if (guardFn && !guardFn(context, event, payload)) {
1265
+ await emitFSM("guard:rejected", {
1266
+ state: currentState,
1267
+ event,
1268
+ payload
1269
+ });
1270
+ if (fallback) {
1271
+ if (!states[fallback]) {
1272
+ throw new Error(`Invalid fallback state "${fallback}" - not defined in states`);
1273
+ }
1274
+ await doTransition(event, fallback, payload, false);
1275
+ return true;
1276
+ }
1277
+ return false;
1278
+ }
1279
+ if (transition.actions) {
1280
+ const actions = Array.isArray(transition.actions) ? transition.actions : [transition.actions];
1281
+ for (const action of actions) {
1282
+ await action(context, event, payload);
1283
+ }
1284
+ }
1285
+ await doTransition(event, transition.target, payload, false);
1286
+ return true;
1287
+ },
1288
+ can(event) {
1289
+ return getTransition(event) !== null;
1290
+ },
1291
+ transitions() {
1292
+ const stateNode = states[currentState];
1293
+ if (!stateNode?.on) return [];
1294
+ return Object.keys(stateNode.on);
1295
+ },
1296
+ toJSON() {
1297
+ return {
1298
+ state: currentState,
1299
+ context: structuredClone(context),
1300
+ history: trackHistory ? [...history] : void 0
1301
+ };
1302
+ },
1303
+ restore(snapshot2) {
1304
+ if (!states[snapshot2.state]) {
1305
+ throw new Error(`Invalid snapshot state "${snapshot2.state}" - not defined in states`);
1306
+ }
1307
+ currentState = snapshot2.state;
1308
+ context = snapshot2.context;
1309
+ if (snapshot2.history) {
1310
+ history = [...snapshot2.history];
1311
+ }
1312
+ },
1313
+ destroy() {
1314
+ for (const cleanup of cleanupFns) {
1315
+ cleanup();
1316
+ }
1317
+ cleanupFns.length = 0;
1318
+ }
1319
+ };
1320
+ if (!snapshot) {
1321
+ setTimeout(async () => {
1322
+ const initialNode = states[initial];
1323
+ await emitFSM(`enter:${initial}`, {
1324
+ state: initial
1325
+ });
1326
+ if (initialNode?.entry) {
1327
+ await initialNode.entry(context, "__INIT__", void 0);
1328
+ }
1329
+ }, 0);
1330
+ }
1331
+ return machine;
1332
+ }
1333
+ function defineMachine(config) {
1334
+ return config;
1335
+ }
1336
+
1337
+ // src/fsm/create-machine.ts
1338
+ function createMachine(config) {
1339
+ const {
1340
+ id,
1341
+ initial,
1342
+ context: initialContext,
1343
+ states,
1344
+ on: eventHandlers = {},
1345
+ helpers: customHelpers = {}
1346
+ } = config;
1347
+ const kernel = new Kernel();
1348
+ const activeTimers = /* @__PURE__ */ new Map();
1349
+ const machineConfig = {
1350
+ prefix: id,
1351
+ initial,
1352
+ context: initialContext,
1353
+ states: {},
1354
+ trackHistory: true
1355
+ };
1356
+ for (const [stateName, stateDef] of Object.entries(states)) {
1357
+ machineConfig.states[stateName] = {
1358
+ on: stateDef.on ? { ...stateDef.on } : void 0
1359
+ };
1360
+ }
1361
+ const baseMachine = useMachine(kernel, machineConfig);
1362
+ const createHelpers = () => ({
1363
+ // Built-ins
1364
+ set: (partial) => baseMachine.setContext(partial),
1365
+ send: (event, payload) => baseMachine.send(event, payload),
1366
+ log: console.log,
1367
+ // Custom helpers (can override built-ins)
1368
+ ...customHelpers
1369
+ });
1370
+ kernel.on(`${id}:enter:*`, async (e) => {
1371
+ const stateName = e.data?.state;
1372
+ if (!stateName) return;
1373
+ activeTimers.forEach((timer, key) => {
1374
+ if (!key.startsWith(stateName + ":")) {
1375
+ clearTimeout(timer);
1376
+ activeTimers.delete(key);
1377
+ }
1378
+ });
1379
+ const stateConfig = states[stateName];
1380
+ if (!stateConfig) return;
1381
+ if (stateConfig.entry) {
1382
+ await stateConfig.entry(baseMachine.getContext(), createHelpers());
1383
+ }
1384
+ if (stateConfig.after) {
1385
+ const timerId = setTimeout(() => {
1386
+ baseMachine.send(stateConfig.after.send);
1387
+ activeTimers.delete(stateName + ":timer");
1388
+ }, stateConfig.after.delay);
1389
+ activeTimers.set(stateName + ":timer", timerId);
1390
+ }
1391
+ });
1392
+ kernel.on(`${id}:exit:*`, async (e) => {
1393
+ const stateName = e.data?.state;
1394
+ if (!stateName) return;
1395
+ const timerId = activeTimers.get(stateName + ":timer");
1396
+ if (timerId) {
1397
+ clearTimeout(timerId);
1398
+ activeTimers.delete(stateName + ":timer");
1399
+ }
1400
+ const stateConfig = states[stateName];
1401
+ if (!stateConfig) return;
1402
+ if (stateConfig.exit) {
1403
+ await stateConfig.exit(baseMachine.getContext(), createHelpers());
1404
+ }
1405
+ });
1406
+ kernel.on(`${id}:transition`, async (e) => {
1407
+ const event = e.data?.event;
1408
+ if (!event) return;
1409
+ const handler = eventHandlers[event];
1410
+ if (handler) {
1411
+ await handler(baseMachine.getContext(), createHelpers());
1412
+ }
1413
+ });
1414
+ const machine = {
1415
+ ...baseMachine,
1416
+ id,
1417
+ get state() {
1418
+ return baseMachine.getState();
1419
+ },
1420
+ get context() {
1421
+ return baseMachine.getContext();
1422
+ },
1423
+ destroy() {
1424
+ activeTimers.forEach((timer) => clearTimeout(timer));
1425
+ activeTimers.clear();
1426
+ baseMachine.destroy();
1427
+ }
1428
+ };
1429
+ return machine;
1430
+ }
1431
+
1432
+ exports.createMachine = createMachine;
1433
+ exports.defineMachine = defineMachine;
1434
+ exports.useMachine = useMachine;
1435
+ //# sourceMappingURL=fsm.cjs.map
1436
+ //# sourceMappingURL=fsm.cjs.map