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