@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/index.cjs ADDED
@@ -0,0 +1,1750 @@
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/mergers/override.ts
286
+ var createOverrideMerger = () => ({
287
+ merge: (contexts, sources) => {
288
+ const result = {};
289
+ for (const eventName of sources) {
290
+ const context = contexts.get(eventName);
291
+ if (!context) continue;
292
+ for (const [key, value] of Object.entries(context)) {
293
+ result[key] = value;
294
+ }
295
+ }
296
+ return result;
297
+ },
298
+ mergeWithConflicts: (contexts, sources) => {
299
+ const result = {};
300
+ const conflicts = [];
301
+ const keySources = /* @__PURE__ */ new Map();
302
+ for (const eventName of sources) {
303
+ const context = contexts.get(eventName);
304
+ if (!context) continue;
305
+ for (const [key, value] of Object.entries(context)) {
306
+ if (!keySources.has(key)) {
307
+ keySources.set(key, []);
308
+ }
309
+ keySources.get(key).push({ source: eventName, value });
310
+ }
311
+ }
312
+ for (const [key, sourceList] of keySources) {
313
+ if (sourceList.length > 1) {
314
+ conflicts.push({
315
+ key,
316
+ sources: sourceList.map((s) => s.source),
317
+ values: sourceList.map((s) => s.value)
318
+ });
319
+ }
320
+ result[key] = sourceList[sourceList.length - 1].value;
321
+ }
322
+ return {
323
+ context: result,
324
+ conflicts
325
+ };
326
+ }
327
+ });
328
+
329
+ // src/composition/composition.ts
330
+ var COMPOSED_EVENT = "__qk:composed__";
331
+ var Composition = class {
332
+ kernel;
333
+ subscriptions = [];
334
+ buffers = /* @__PURE__ */ new Map();
335
+ merger;
336
+ bufferLimit;
337
+ reset;
338
+ eventTTL;
339
+ eventTTLs;
340
+ sourceEvents = /* @__PURE__ */ new Set();
341
+ firedSinceLastComposite = /* @__PURE__ */ new Set();
342
+ lastConflicts = [];
343
+ expirationTimers = /* @__PURE__ */ new Map();
344
+ /**
345
+ * Create a new Composition
346
+ *
347
+ * @param kernels - Array of [kernel, eventName] tuples to subscribe to
348
+ * @param options - Composition options
349
+ */
350
+ constructor(kernels, options = {}) {
351
+ if (kernels.length === 0) {
352
+ throw new Error("Composition requires at least one kernel");
353
+ }
354
+ this.merger = options.merger ?? createNamespacedMerger();
355
+ this.bufferLimit = options.bufferLimit ?? 100;
356
+ this.reset = options.reset ?? true;
357
+ this.eventTTL = options.eventTTL ?? 0;
358
+ this.eventTTLs = options.eventTTLs ?? {};
359
+ this.kernel = new Kernel({
360
+ debug: false,
361
+ errorBoundary: true,
362
+ onContextConflict: options.onConflict ? (key, values) => {
363
+ options.onConflict({
364
+ key,
365
+ sources: [],
366
+ values
367
+ });
368
+ } : void 0
369
+ });
370
+ for (const [kernel, eventName] of kernels) {
371
+ this.subscribeToKernel(kernel, eventName);
372
+ this.sourceEvents.add(eventName);
373
+ }
374
+ }
375
+ /**
376
+ * Subscribe to events from a source kernel
377
+ */
378
+ subscribeToKernel(kernel, eventName) {
379
+ this.buffers.set(eventName, []);
380
+ const unbind = kernel.on(
381
+ eventName,
382
+ async (event) => {
383
+ await this.handleSourceEvent(eventName, event);
384
+ },
385
+ { priority: -Infinity }
386
+ );
387
+ this.subscriptions.push({
388
+ kernel,
389
+ eventName,
390
+ unbind
391
+ });
392
+ }
393
+ /**
394
+ * Get the effective TTL for a specific event
395
+ * Priority: per-event TTL > global TTL > 0 (permanent)
396
+ */
397
+ getEffectiveTTL(eventName) {
398
+ const perEventTTL = this.eventTTLs[eventName];
399
+ if (perEventTTL !== void 0) {
400
+ return perEventTTL;
401
+ }
402
+ return this.eventTTL > 0 ? this.eventTTL : "permanent";
403
+ }
404
+ /**
405
+ * Handle an event from a source kernel
406
+ */
407
+ async handleSourceEvent(eventName, event) {
408
+ const buffer = this.buffers.get(eventName);
409
+ if (!buffer) return;
410
+ const effectiveTTL = this.getEffectiveTTL(eventName);
411
+ const eventId = `${eventName}:${event.timestamp}:${Math.random().toString(36).slice(2, 8)}`;
412
+ buffer.push({
413
+ name: event.name,
414
+ data: event.data,
415
+ context: { ...event.context },
416
+ timestamp: event.timestamp
417
+ });
418
+ if (buffer.length > this.bufferLimit) {
419
+ buffer.shift();
420
+ }
421
+ this.firedSinceLastComposite.add(eventName);
422
+ const compositionCompleted = await this.checkAndEmitComposite();
423
+ if (effectiveTTL === "instant" && !compositionCompleted) {
424
+ buffer.pop();
425
+ this.firedSinceLastComposite.delete(eventName);
426
+ return;
427
+ }
428
+ if (typeof effectiveTTL === "number" && effectiveTTL > 0) {
429
+ const timer = setTimeout(() => {
430
+ this.expireEvent(eventName, event.timestamp);
431
+ this.expirationTimers.delete(eventId);
432
+ }, effectiveTTL);
433
+ this.expirationTimers.set(eventId, timer);
434
+ }
435
+ }
436
+ /**
437
+ * Expire an event from the buffer based on timestamp
438
+ */
439
+ expireEvent(eventName, timestamp) {
440
+ const buffer = this.buffers.get(eventName);
441
+ if (!buffer) return;
442
+ const filtered = buffer.filter((e) => e.timestamp > timestamp);
443
+ this.buffers.set(eventName, filtered);
444
+ if (filtered.length === 0) {
445
+ this.firedSinceLastComposite.delete(eventName);
446
+ }
447
+ }
448
+ /**
449
+ * Check if all source events have fired and emit composite event
450
+ * @returns true if composite was emitted, false otherwise
451
+ */
452
+ async checkAndEmitComposite() {
453
+ for (const eventName of this.sourceEvents) {
454
+ const buffer = this.buffers.get(eventName);
455
+ if (!buffer || buffer.length === 0) {
456
+ return false;
457
+ }
458
+ }
459
+ for (const eventName of this.sourceEvents) {
460
+ if (!this.firedSinceLastComposite.has(eventName)) {
461
+ return false;
462
+ }
463
+ }
464
+ if (this.kernel.listenerCount(COMPOSED_EVENT) === 0) {
465
+ return false;
466
+ }
467
+ const contexts = /* @__PURE__ */ new Map();
468
+ const sources = Array.from(this.sourceEvents);
469
+ for (const eventName of sources) {
470
+ const buffer = this.buffers.get(eventName);
471
+ if (!buffer || buffer.length === 0) continue;
472
+ const latestEvent = buffer[buffer.length - 1];
473
+ const eventData = latestEvent.data && typeof latestEvent.data === "object" ? latestEvent.data : {};
474
+ const combined = { ...eventData, ...latestEvent.context };
475
+ contexts.set(eventName, combined);
476
+ }
477
+ const mergeResult = this.merger.mergeWithConflicts(contexts, sources);
478
+ this.lastConflicts = mergeResult.conflicts;
479
+ await this.kernel.emit(COMPOSED_EVENT, {
480
+ sources,
481
+ contexts: Object.fromEntries(contexts),
482
+ merged: mergeResult.context
483
+ });
484
+ this.firedSinceLastComposite.clear();
485
+ if (this.reset) {
486
+ for (const eventName of this.sourceEvents) {
487
+ const buffer = this.buffers.get(eventName);
488
+ if (buffer && buffer.length > 0) {
489
+ const latest = buffer[buffer.length - 1];
490
+ this.buffers.set(eventName, [latest]);
491
+ }
492
+ }
493
+ }
494
+ return true;
495
+ }
496
+ /**
497
+ * Register a listener for when all source events have fired
498
+ * This is the primary way to react to composition completion
499
+ *
500
+ * @param listener - Function called with merged context when composition completes
501
+ * @param options - Listener options (priority, id, etc.)
502
+ * @returns Unbind function to remove the listener
503
+ */
504
+ onComposed(listener, options) {
505
+ return this.kernel.on(COMPOSED_EVENT, listener, options);
506
+ }
507
+ /**
508
+ * Remove a listener for composed events
509
+ */
510
+ offComposed(listener) {
511
+ this.kernel.off(COMPOSED_EVENT, listener);
512
+ }
513
+ /**
514
+ * Get number of composed event listeners
515
+ */
516
+ composedListenerCount() {
517
+ return this.kernel.listenerCount(COMPOSED_EVENT);
518
+ }
519
+ /**
520
+ * Register a listener for events on internal kernel
521
+ * Note: Use onComposed() to listen for composition completion
522
+ */
523
+ on(eventName, listener, options) {
524
+ return this.kernel.on(eventName, listener, options);
525
+ }
526
+ /**
527
+ * Remove a listener
528
+ * Delegates to internal kernel
529
+ */
530
+ off(eventName, listener) {
531
+ this.kernel.off(eventName, listener);
532
+ }
533
+ /**
534
+ * Emit an event through the composition
535
+ * Note: Reserved internal events (prefixed with __qk:) cannot be emitted
536
+ */
537
+ async emit(eventName, data) {
538
+ if (String(eventName).startsWith("__qk:")) {
539
+ throw new Error(`Cannot emit reserved event: ${String(eventName)}`);
540
+ }
541
+ return this.kernel.emit(eventName, data);
542
+ }
543
+ /**
544
+ * Get merged context from latest buffered events
545
+ * Does not emit - just returns the merged context
546
+ */
547
+ getContext() {
548
+ for (const eventName of this.sourceEvents) {
549
+ const buffer = this.buffers.get(eventName);
550
+ if (!buffer || buffer.length === 0) {
551
+ return null;
552
+ }
553
+ }
554
+ const contexts = /* @__PURE__ */ new Map();
555
+ const sources = Array.from(this.sourceEvents);
556
+ for (const eventName of sources) {
557
+ const buffer = this.buffers.get(eventName);
558
+ if (!buffer || buffer.length === 0) continue;
559
+ const latestEvent = buffer[buffer.length - 1];
560
+ const eventData = latestEvent.data && typeof latestEvent.data === "object" ? latestEvent.data : {};
561
+ const combined = { ...eventData, ...latestEvent.context };
562
+ contexts.set(eventName, combined);
563
+ }
564
+ const mergeResult = this.merger.mergeWithConflicts(contexts, sources);
565
+ this.lastConflicts = mergeResult.conflicts;
566
+ return mergeResult.context;
567
+ }
568
+ /**
569
+ * Get number of listeners for an event
570
+ */
571
+ listenerCount(eventName) {
572
+ return this.kernel.listenerCount(eventName);
573
+ }
574
+ /**
575
+ * Get all event names with registered listeners
576
+ */
577
+ eventNames() {
578
+ return this.kernel.eventNames();
579
+ }
580
+ /**
581
+ * Remove all listeners
582
+ */
583
+ offAll(eventName) {
584
+ this.kernel.offAll(eventName);
585
+ }
586
+ /**
587
+ * Enable/disable debug mode
588
+ */
589
+ debug(enabled) {
590
+ this.kernel.debug(enabled);
591
+ }
592
+ /**
593
+ * Get buffer for a specific source event (for debugging)
594
+ */
595
+ getBuffer(eventName) {
596
+ return this.buffers.get(eventName);
597
+ }
598
+ /**
599
+ * Clear all buffers
600
+ */
601
+ clearBuffers() {
602
+ for (const eventName of this.sourceEvents) {
603
+ this.buffers.set(eventName, []);
604
+ }
605
+ this.firedSinceLastComposite.clear();
606
+ }
607
+ /**
608
+ * Get conflicts detected during the last merge operation
609
+ *
610
+ * Returns an array of ConflictInfo objects describing which source events
611
+ * provided conflicting values for the same context keys.
612
+ *
613
+ * @returns Array of conflicts from the last merge, or empty array if no conflicts
614
+ */
615
+ getConflicts() {
616
+ return this.lastConflicts;
617
+ }
618
+ /**
619
+ * Cleanup all subscriptions and listeners
620
+ */
621
+ dispose() {
622
+ for (const sub of this.subscriptions) {
623
+ sub.unbind();
624
+ }
625
+ this.subscriptions = [];
626
+ for (const timer of this.expirationTimers.values()) {
627
+ clearTimeout(timer);
628
+ }
629
+ this.expirationTimers.clear();
630
+ this.kernel.offAll();
631
+ this.buffers.clear();
632
+ this.sourceEvents.clear();
633
+ this.firedSinceLastComposite.clear();
634
+ this.lastConflicts = [];
635
+ }
636
+ /**
637
+ * Get the configured global event TTL in milliseconds
638
+ * @returns The TTL value, or 0 if no TTL is configured
639
+ */
640
+ getEventTTL() {
641
+ return this.eventTTL;
642
+ }
643
+ /**
644
+ * Set the global event TTL in milliseconds
645
+ * @param ttl - TTL in milliseconds (0 = permanent)
646
+ */
647
+ setEventTTL(ttl) {
648
+ this.eventTTL = ttl;
649
+ }
650
+ /**
651
+ * Get per-event TTL configuration
652
+ * @returns The eventTTLs configuration object
653
+ */
654
+ getEventTTLs() {
655
+ return this.eventTTLs;
656
+ }
657
+ /**
658
+ * Set TTL for a specific event
659
+ * @param eventName - The event name to configure
660
+ * @param ttl - TTL value: number (ms), 'permanent', or 'instant'
661
+ */
662
+ setEventTTLFor(eventName, ttl) {
663
+ this.eventTTLs[eventName] = ttl;
664
+ }
665
+ /**
666
+ * Remove per-event TTL configuration (falls back to global TTL)
667
+ * @param eventName - The event name to reset
668
+ */
669
+ clearEventTTLFor(eventName) {
670
+ delete this.eventTTLs[eventName];
671
+ }
672
+ };
673
+ var createComposition = (kernels, options) => {
674
+ return new Composition(kernels, options);
675
+ };
676
+
677
+ // src/kernel.ts
678
+ var Kernel = class _Kernel {
679
+ listeners = /* @__PURE__ */ new Map();
680
+ options;
681
+ listenerIdCounter = 0;
682
+ executionErrors = [];
683
+ constructor(options = {}) {
684
+ this.options = {
685
+ delimiter: options.delimiter ?? ":",
686
+ wildcard: options.wildcard ?? true,
687
+ maxListeners: options.maxListeners ?? Infinity,
688
+ debug: options.debug ?? false,
689
+ errorBoundary: options.errorBoundary ?? true,
690
+ onError: options.onError ?? ((error) => {
691
+ console.error("Kernel error:", error);
692
+ }),
693
+ contextMerger: options.contextMerger ?? void 0,
694
+ onContextConflict: options.onContextConflict ?? void 0
695
+ };
696
+ if (this.options.debug) {
697
+ console.debug("[QuarKernel] Kernel initialized", {
698
+ delimiter: this.options.delimiter,
699
+ wildcard: this.options.wildcard,
700
+ maxListeners: this.options.maxListeners,
701
+ errorBoundary: this.options.errorBoundary
702
+ });
703
+ }
704
+ }
705
+ /**
706
+ * Register an event listener
707
+ * Returns an unbind function for cleanup
708
+ */
709
+ on(eventName, listener, options = {}) {
710
+ const event = String(eventName);
711
+ if (options.signal?.aborted) {
712
+ if (this.options.debug) {
713
+ console.debug("[QuarKernel] Listener not added (signal already aborted)", {
714
+ event
715
+ });
716
+ }
717
+ return () => {
718
+ };
719
+ }
720
+ const priority = options.priority ?? 0;
721
+ const id = options.id ?? `listener_${++this.listenerIdCounter}`;
722
+ const after = Array.isArray(options.after) ? options.after : options.after ? [options.after] : [];
723
+ let abortListener;
724
+ if (options.signal) {
725
+ abortListener = () => this.off(event, listener);
726
+ }
727
+ const entry = {
728
+ id,
729
+ callback: listener,
730
+ after,
731
+ priority,
732
+ once: options.once ?? false,
733
+ original: listener,
734
+ signal: options.signal,
735
+ abortListener
736
+ };
737
+ const entries = this.listeners.get(event) ?? [];
738
+ entries.push(entry);
739
+ entries.sort((a, b) => b.priority - a.priority);
740
+ this.listeners.set(event, entries);
741
+ if (this.options.debug) {
742
+ console.debug("[QuarKernel] Listener added", {
743
+ event,
744
+ listenerId: id,
745
+ priority,
746
+ after,
747
+ once: entry.once,
748
+ totalListeners: entries.length
749
+ });
750
+ }
751
+ if (this.options.maxListeners > 0 && entries.length > this.options.maxListeners) {
752
+ console.warn(
753
+ `MaxListenersExceeded: Event "${event}" has ${entries.length} listeners (limit: ${this.options.maxListeners})`
754
+ );
755
+ }
756
+ if (options.signal && abortListener) {
757
+ options.signal.addEventListener("abort", abortListener, { once: true });
758
+ }
759
+ return () => this.off(event, listener);
760
+ }
761
+ /**
762
+ * Remove an event listener
763
+ * If no listener provided, removes all listeners for the event
764
+ */
765
+ off(eventName, listener) {
766
+ const entries = this.listeners.get(eventName);
767
+ if (!entries) {
768
+ return;
769
+ }
770
+ if (!listener) {
771
+ if (this.options.debug) {
772
+ console.debug("[QuarKernel] All listeners removed", {
773
+ event: eventName,
774
+ count: entries.length
775
+ });
776
+ }
777
+ for (const entry of entries) {
778
+ if (entry.signal && entry.abortListener) {
779
+ entry.signal.removeEventListener("abort", entry.abortListener);
780
+ }
781
+ }
782
+ this.listeners.delete(eventName);
783
+ return;
784
+ }
785
+ const entryToRemove = entries.find((entry) => entry.original === listener);
786
+ if (entryToRemove?.signal && entryToRemove.abortListener) {
787
+ entryToRemove.signal.removeEventListener("abort", entryToRemove.abortListener);
788
+ }
789
+ const filtered = entries.filter((entry) => entry.original !== listener);
790
+ const removed = entries.length - filtered.length;
791
+ if (this.options.debug && removed > 0) {
792
+ console.debug("[QuarKernel] Listener removed", {
793
+ event: eventName,
794
+ removed,
795
+ remaining: filtered.length
796
+ });
797
+ }
798
+ if (filtered.length === 0) {
799
+ this.listeners.delete(eventName);
800
+ } else {
801
+ this.listeners.set(eventName, filtered);
802
+ }
803
+ }
804
+ /**
805
+ * Emit an event
806
+ * Executes all registered listeners in parallel (by default)
807
+ * Returns a Promise that resolves when all listeners complete
808
+ * Throws AggregateError if any listeners failed
809
+ */
810
+ async emit(eventName, data) {
811
+ const event = String(eventName);
812
+ const allPatterns = Array.from(this.listeners.keys());
813
+ const matchingPatterns = this.options.wildcard ? findMatchingPatterns(event, allPatterns, this.options.delimiter) : allPatterns.filter((p) => p === event);
814
+ const allEntries = [];
815
+ for (const pattern of matchingPatterns) {
816
+ const entries = this.listeners.get(pattern);
817
+ if (entries) {
818
+ allEntries.push(...entries);
819
+ }
820
+ }
821
+ if (allEntries.length === 0) {
822
+ if (this.options.debug) {
823
+ console.debug("[QuarKernel] Event emitted (no listeners)", { event });
824
+ }
825
+ return;
826
+ }
827
+ if (this.options.debug) {
828
+ console.debug("[QuarKernel] Event emitted", {
829
+ event,
830
+ listenerCount: allEntries.length,
831
+ data: data !== void 0 ? JSON.stringify(data).substring(0, 100) : void 0
832
+ });
833
+ }
834
+ this.executionErrors = [];
835
+ const kernelEvent = new KernelEvent(
836
+ event,
837
+ data,
838
+ {}
839
+ );
840
+ const sortedEntries = this.sortListenersByDependencies(allEntries);
841
+ const promises = sortedEntries.map(
842
+ (entry) => this.executeListener(entry, kernelEvent, event)
843
+ );
844
+ const results = await Promise.allSettled(promises);
845
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
846
+ if (!this.options.errorBoundary) {
847
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
848
+ if (errors.length > 0) {
849
+ throw new AggregateError(errors, `${errors.length} listener(s) failed for event "${event}"`);
850
+ }
851
+ }
852
+ if (this.options.debug) {
853
+ console.debug("[QuarKernel] Event completed", {
854
+ event
855
+ });
856
+ }
857
+ }
858
+ /**
859
+ * Emit an event with serial execution
860
+ * Executes listeners sequentially (one after another) instead of in parallel
861
+ * Respects the same dependency and priority ordering as emit()
862
+ * Stops on first error if errorBoundary is false, otherwise continues and collects errors
863
+ */
864
+ async emitSerial(eventName, data) {
865
+ const event = String(eventName);
866
+ const allPatterns = Array.from(this.listeners.keys());
867
+ const matchingPatterns = this.options.wildcard ? findMatchingPatterns(event, allPatterns, this.options.delimiter) : allPatterns.filter((p) => p === event);
868
+ const allEntries = [];
869
+ for (const pattern of matchingPatterns) {
870
+ const entries = this.listeners.get(pattern);
871
+ if (entries) {
872
+ allEntries.push(...entries);
873
+ }
874
+ }
875
+ if (allEntries.length === 0) {
876
+ if (this.options.debug) {
877
+ console.debug("[QuarKernel] Event emitted serially (no listeners)", { event });
878
+ }
879
+ return;
880
+ }
881
+ if (this.options.debug) {
882
+ console.debug("[QuarKernel] Event emitted serially", {
883
+ event,
884
+ listenerCount: allEntries.length,
885
+ data: data !== void 0 ? JSON.stringify(data).substring(0, 100) : void 0
886
+ });
887
+ }
888
+ this.executionErrors = [];
889
+ const kernelEvent = new KernelEvent(
890
+ event,
891
+ data,
892
+ {}
893
+ );
894
+ const sortedEntries = this.sortListenersByDependencies(allEntries);
895
+ const errors = [];
896
+ for (const entry of sortedEntries) {
897
+ try {
898
+ await this.executeListener(entry, kernelEvent, event);
899
+ } catch (error) {
900
+ if (!this.options.errorBoundary) {
901
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
902
+ throw error;
903
+ }
904
+ errors.push(error);
905
+ }
906
+ }
907
+ this.removeOnceListeners(event, sortedEntries, kernelEvent);
908
+ if (!this.options.errorBoundary && errors.length > 0) {
909
+ throw new AggregateError(errors, `${errors.length} listener(s) failed for event "${event}"`);
910
+ }
911
+ if (this.options.debug) {
912
+ console.debug("[QuarKernel] Event completed serially", {
913
+ event
914
+ });
915
+ }
916
+ }
917
+ /**
918
+ * Sort listeners by dependencies and priority
919
+ * Uses topological sort for dependency resolution
920
+ */
921
+ sortListenersByDependencies(entries) {
922
+ const hasDependencies = entries.some((e) => e.after.length > 0);
923
+ if (!hasDependencies) {
924
+ return [...entries].sort((a, b) => b.priority - a.priority);
925
+ }
926
+ const listenerIds = new Set(entries.map((e) => e.id));
927
+ for (const entry of entries) {
928
+ for (const dep of entry.after) {
929
+ if (!listenerIds.has(dep)) {
930
+ throw new Error(`Listener "${entry.id}" depends on missing listener "${dep}"`);
931
+ }
932
+ }
933
+ }
934
+ const nodes = entries.map((e) => ({
935
+ id: e.id,
936
+ after: e.after
937
+ }));
938
+ toposort(nodes);
939
+ const levelMap = /* @__PURE__ */ new Map();
940
+ const assignLevel = (id, visited = /* @__PURE__ */ new Set()) => {
941
+ if (levelMap.has(id)) {
942
+ return levelMap.get(id);
943
+ }
944
+ if (visited.has(id)) {
945
+ return 0;
946
+ }
947
+ visited.add(id);
948
+ const entry = entries.find((e) => e.id === id);
949
+ if (!entry || entry.after.length === 0) {
950
+ levelMap.set(id, 0);
951
+ return 0;
952
+ }
953
+ const maxDepLevel = Math.max(...entry.after.map((dep) => assignLevel(dep, visited)));
954
+ const level = maxDepLevel + 1;
955
+ levelMap.set(id, level);
956
+ return level;
957
+ };
958
+ entries.forEach((e) => assignLevel(e.id));
959
+ return [...entries].sort((a, b) => {
960
+ const levelA = levelMap.get(a.id) ?? 0;
961
+ const levelB = levelMap.get(b.id) ?? 0;
962
+ if (levelA !== levelB) {
963
+ return levelA - levelB;
964
+ }
965
+ return b.priority - a.priority;
966
+ });
967
+ }
968
+ /**
969
+ * Execute a single listener with error handling
970
+ */
971
+ async executeListener(entry, event, eventName) {
972
+ if (event.isPropagationStopped) {
973
+ if (this.options.debug) {
974
+ console.debug("[QuarKernel] Listener skipped (propagation stopped)", {
975
+ listenerId: entry.id
976
+ });
977
+ }
978
+ return;
979
+ }
980
+ const startTime = Date.now();
981
+ if (this.options.debug) {
982
+ console.debug("[QuarKernel] Listener executing", {
983
+ listenerId: entry.id,
984
+ event: eventName,
985
+ priority: entry.priority
986
+ });
987
+ }
988
+ try {
989
+ const ctx = new ListenerContext(
990
+ entry.id,
991
+ eventName,
992
+ entry.priority,
993
+ entry.after,
994
+ this,
995
+ entry.original,
996
+ entry.signal
997
+ );
998
+ ctx.setCurrentEvent(event);
999
+ try {
1000
+ await entry.callback(event, ctx);
1001
+ if (this.options.debug) {
1002
+ const duration = Date.now() - startTime;
1003
+ console.debug("[QuarKernel] Listener completed", {
1004
+ listenerId: entry.id,
1005
+ duration: `${duration}ms`
1006
+ });
1007
+ }
1008
+ } finally {
1009
+ ctx.clearCurrentEvent();
1010
+ }
1011
+ } catch (error) {
1012
+ const executionError = {
1013
+ listenerId: entry.id,
1014
+ error,
1015
+ timestamp: Date.now(),
1016
+ eventName
1017
+ };
1018
+ this.executionErrors.push(executionError);
1019
+ if (this.options.debug) {
1020
+ console.debug("[QuarKernel] Listener error", {
1021
+ listenerId: entry.id,
1022
+ error: error.message
1023
+ });
1024
+ }
1025
+ if (this.options.errorBoundary) {
1026
+ this.options.onError(error, event);
1027
+ } else {
1028
+ throw error;
1029
+ }
1030
+ }
1031
+ }
1032
+ /**
1033
+ * Remove listeners marked with once: true or whose predicate returns true after execution
1034
+ *
1035
+ * This method is called AFTER all listeners have executed for an event.
1036
+ * The predicate functions receive the event object with the final state after all listeners ran.
1037
+ *
1038
+ * Behavior:
1039
+ * - If once: true, the listener is always removed after execution
1040
+ * - If once is a predicate function, it's evaluated with the post-execution event state
1041
+ * - Predicates can examine event.context to make decisions based on listener modifications
1042
+ * - Listeners are removed even if they threw errors (when errorBoundary: true)
1043
+ *
1044
+ * @param eventName - The event name being processed
1045
+ * @param entries - Listeners that executed (or were scheduled to execute)
1046
+ * @param event - The event object with final state after all listeners executed
1047
+ */
1048
+ removeOnceListeners(eventName, entries, event) {
1049
+ const listenersToRemove = entries.filter((entry) => {
1050
+ if (!entry.once) {
1051
+ return false;
1052
+ }
1053
+ if (entry.once === true) {
1054
+ return true;
1055
+ }
1056
+ if (typeof entry.once === "function") {
1057
+ return entry.once(event);
1058
+ }
1059
+ return false;
1060
+ });
1061
+ if (listenersToRemove.length === 0) {
1062
+ return;
1063
+ }
1064
+ if (this.options.debug) {
1065
+ console.debug("[QuarKernel] Removing once listeners", {
1066
+ event: eventName,
1067
+ count: listenersToRemove.length
1068
+ });
1069
+ }
1070
+ for (const entry of listenersToRemove) {
1071
+ for (const [pattern, entries2] of this.listeners.entries()) {
1072
+ if (entries2.includes(entry)) {
1073
+ this.off(pattern, entry.original);
1074
+ break;
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ /**
1080
+ * Get number of listeners for an event
1081
+ */
1082
+ listenerCount(eventName) {
1083
+ if (!eventName) {
1084
+ let total = 0;
1085
+ for (const entries2 of this.listeners.values()) {
1086
+ total += entries2.length;
1087
+ }
1088
+ return total;
1089
+ }
1090
+ const event = String(eventName);
1091
+ const entries = this.listeners.get(event);
1092
+ return entries?.length ?? 0;
1093
+ }
1094
+ /**
1095
+ * Get all event names with registered listeners
1096
+ */
1097
+ eventNames() {
1098
+ return Array.from(this.listeners.keys());
1099
+ }
1100
+ /**
1101
+ * Remove all listeners for all events (or specific event)
1102
+ */
1103
+ offAll(eventName) {
1104
+ if (!eventName) {
1105
+ for (const entries2 of this.listeners.values()) {
1106
+ for (const entry of entries2) {
1107
+ if (entry.signal && entry.abortListener) {
1108
+ entry.signal.removeEventListener("abort", entry.abortListener);
1109
+ }
1110
+ }
1111
+ }
1112
+ this.listeners.clear();
1113
+ return;
1114
+ }
1115
+ const event = String(eventName);
1116
+ const entries = this.listeners.get(event);
1117
+ if (entries) {
1118
+ for (const entry of entries) {
1119
+ if (entry.signal && entry.abortListener) {
1120
+ entry.signal.removeEventListener("abort", entry.abortListener);
1121
+ }
1122
+ }
1123
+ }
1124
+ this.listeners.delete(event);
1125
+ }
1126
+ /**
1127
+ * Enable/disable debug mode
1128
+ * In T115, this is a placeholder - full debug implementation in T129
1129
+ */
1130
+ debug(enabled) {
1131
+ this.options.debug = enabled;
1132
+ if (enabled) {
1133
+ console.debug("[QuarKernel] Debug mode enabled");
1134
+ } else {
1135
+ console.debug("[QuarKernel] Debug mode disabled");
1136
+ }
1137
+ }
1138
+ /**
1139
+ * Get collected execution errors from the last emit
1140
+ * Useful for error aggregation and reporting
1141
+ */
1142
+ getExecutionErrors() {
1143
+ return this.executionErrors;
1144
+ }
1145
+ /**
1146
+ * Clear collected execution errors
1147
+ */
1148
+ clearExecutionErrors() {
1149
+ this.executionErrors = [];
1150
+ }
1151
+ /**
1152
+ * Create a composition from multiple kernels
1153
+ *
1154
+ * @param kernels - Rest parameters of [kernel, eventName] tuples
1155
+ * @param options - Optional composition options (if last argument is not a tuple)
1156
+ * @returns Composition instance that merges events from all kernels
1157
+ *
1158
+ * @example
1159
+ * ```ts
1160
+ * const userKernel = createKernel();
1161
+ * const profileKernel = createKernel();
1162
+ *
1163
+ * const composition = Kernel.compose(
1164
+ * [userKernel, 'user:loaded'],
1165
+ * [profileKernel, 'profile:loaded'],
1166
+ * { merger: createNamespacedMerger() }
1167
+ * );
1168
+ *
1169
+ * composition.onComposed((event) => {
1170
+ * console.log('All sources ready:', event.data.merged);
1171
+ * });
1172
+ * ```
1173
+ */
1174
+ static compose(...args) {
1175
+ const kernels = [];
1176
+ let options;
1177
+ for (const arg of args) {
1178
+ if (Array.isArray(arg) && arg.length === 2 && arg[0] instanceof _Kernel) {
1179
+ kernels.push(arg);
1180
+ } else if (typeof arg === "object" && !Array.isArray(arg)) {
1181
+ options = arg;
1182
+ }
1183
+ }
1184
+ return new Composition(kernels, options);
1185
+ }
1186
+ };
1187
+ var createKernel = (options) => {
1188
+ return new Kernel(options);
1189
+ };
1190
+
1191
+ // src/types.ts
1192
+ function isEventName(value) {
1193
+ return typeof value === "string" && value.length > 0;
1194
+ }
1195
+ function isListenerFunction(value) {
1196
+ return typeof value === "function";
1197
+ }
1198
+ function isPredicateFunction(value) {
1199
+ return typeof value === "function";
1200
+ }
1201
+ function isContextMerger(value) {
1202
+ return value !== null && typeof value === "object" && "merge" in value && typeof value.merge === "function";
1203
+ }
1204
+ function isContextMergerFunction(value) {
1205
+ return typeof value === "function";
1206
+ }
1207
+ function isListenerOptions(value) {
1208
+ if (value === null || typeof value !== "object") {
1209
+ return false;
1210
+ }
1211
+ const opts = value;
1212
+ if ("id" in opts && typeof opts.id !== "string") return false;
1213
+ if ("after" in opts && typeof opts.after !== "string" && !Array.isArray(opts.after)) return false;
1214
+ if ("priority" in opts && typeof opts.priority !== "number") return false;
1215
+ if ("once" in opts && typeof opts.once !== "boolean" && typeof opts.once !== "function") return false;
1216
+ if ("signal" in opts && !(opts.signal instanceof AbortSignal)) return false;
1217
+ return true;
1218
+ }
1219
+ function isKernelEvent(value) {
1220
+ if (value === null || typeof value !== "object") {
1221
+ return false;
1222
+ }
1223
+ const event = value;
1224
+ return typeof event.name === "string" && "data" in event && typeof event.context === "object" && event.context !== null && typeof event.timestamp === "number" && typeof event.stopPropagation === "function" && typeof event.isPropagationStopped === "boolean";
1225
+ }
1226
+ var CircularDependencyError = class extends Error {
1227
+ constructor(cycle) {
1228
+ super(`Circular dependency detected: ${cycle.join(" -> ")}`);
1229
+ this.name = "CircularDependencyError";
1230
+ }
1231
+ };
1232
+ var MissingDependencyError = class extends Error {
1233
+ constructor(listenerId, missingDep) {
1234
+ super(`Listener "${listenerId}" depends on missing listener "${missingDep}"`);
1235
+ this.name = "MissingDependencyError";
1236
+ }
1237
+ };
1238
+ var MaxListenersExceededError = class extends Error {
1239
+ constructor(event, max) {
1240
+ super(`Max listeners (${max}) exceeded for event "${event}"`);
1241
+ this.name = "MaxListenersExceededError";
1242
+ }
1243
+ };
1244
+
1245
+ // src/fsm/machine.ts
1246
+ function useMachine(kernel, config) {
1247
+ const {
1248
+ prefix,
1249
+ initial,
1250
+ states,
1251
+ allowForce = true,
1252
+ snapshot,
1253
+ trackHistory = false,
1254
+ maxHistory = 100
1255
+ } = config;
1256
+ let currentState = snapshot?.state ?? initial;
1257
+ let context = snapshot?.context ?? config.context ?? {};
1258
+ let history = snapshot?.history ? [...snapshot.history] : [];
1259
+ if (!states[currentState]) {
1260
+ throw new Error(`Invalid initial state "${currentState}" - not defined in states`);
1261
+ }
1262
+ const cleanupFns = [];
1263
+ const emitFSM = async (eventType, data) => {
1264
+ const eventData = {
1265
+ machine: prefix,
1266
+ ...data
1267
+ };
1268
+ await kernel.emit(`${prefix}:${eventType}`, eventData);
1269
+ };
1270
+ const getTransition = (event) => {
1271
+ const stateNode = states[currentState];
1272
+ if (!stateNode?.on) return null;
1273
+ const transition = stateNode.on[event];
1274
+ if (!transition) return null;
1275
+ if (typeof transition === "string") {
1276
+ return { target: transition };
1277
+ }
1278
+ return transition;
1279
+ };
1280
+ const doTransition = async (event, targetState, payload, forced = false) => {
1281
+ const fromState = currentState;
1282
+ const fromNode = states[fromState];
1283
+ const toNode = states[targetState];
1284
+ if (!toNode) {
1285
+ throw new Error(`Invalid target state "${targetState}" - not defined in states`);
1286
+ }
1287
+ if (fromNode?.exit) {
1288
+ await fromNode.exit(context, event, payload);
1289
+ }
1290
+ await emitFSM(`exit:${fromState}`, {
1291
+ state: fromState,
1292
+ from: fromState,
1293
+ to: targetState,
1294
+ event,
1295
+ payload,
1296
+ forced
1297
+ });
1298
+ currentState = targetState;
1299
+ if (trackHistory) {
1300
+ history.push({
1301
+ from: fromState,
1302
+ to: targetState,
1303
+ event,
1304
+ timestamp: Date.now()
1305
+ });
1306
+ if (history.length > maxHistory) {
1307
+ history = history.slice(-maxHistory);
1308
+ }
1309
+ }
1310
+ await emitFSM("transition", {
1311
+ state: targetState,
1312
+ from: fromState,
1313
+ to: targetState,
1314
+ event,
1315
+ payload,
1316
+ forced
1317
+ });
1318
+ await emitFSM(`transition:${event}`, {
1319
+ state: targetState,
1320
+ from: fromState,
1321
+ to: targetState,
1322
+ event,
1323
+ payload,
1324
+ forced
1325
+ });
1326
+ await emitFSM(`enter:${targetState}`, {
1327
+ state: targetState,
1328
+ from: fromState,
1329
+ to: targetState,
1330
+ event,
1331
+ payload,
1332
+ forced
1333
+ });
1334
+ if (toNode.entry) {
1335
+ await toNode.entry(context, event, payload);
1336
+ }
1337
+ };
1338
+ const machine = {
1339
+ prefix,
1340
+ getState() {
1341
+ return currentState;
1342
+ },
1343
+ getContext() {
1344
+ return context;
1345
+ },
1346
+ setContext(updater) {
1347
+ if (typeof updater === "function") {
1348
+ context = updater(context);
1349
+ } else {
1350
+ context = { ...context, ...updater };
1351
+ }
1352
+ },
1353
+ async send(event, payload, options = {}) {
1354
+ const { force = false, target, guard: inlineGuard, fallback } = options;
1355
+ if (force && allowForce) {
1356
+ const targetState = target ?? initial;
1357
+ await doTransition(event, targetState, payload, true);
1358
+ return true;
1359
+ }
1360
+ const transition = getTransition(event);
1361
+ if (!transition) {
1362
+ if (force && !allowForce) {
1363
+ throw new Error(`Force transitions not allowed on machine "${prefix}"`);
1364
+ }
1365
+ return false;
1366
+ }
1367
+ const guardFn = inlineGuard ?? transition.guard;
1368
+ if (guardFn && !guardFn(context, event, payload)) {
1369
+ await emitFSM("guard:rejected", {
1370
+ state: currentState,
1371
+ event,
1372
+ payload
1373
+ });
1374
+ if (fallback) {
1375
+ if (!states[fallback]) {
1376
+ throw new Error(`Invalid fallback state "${fallback}" - not defined in states`);
1377
+ }
1378
+ await doTransition(event, fallback, payload, false);
1379
+ return true;
1380
+ }
1381
+ return false;
1382
+ }
1383
+ if (transition.actions) {
1384
+ const actions = Array.isArray(transition.actions) ? transition.actions : [transition.actions];
1385
+ for (const action of actions) {
1386
+ await action(context, event, payload);
1387
+ }
1388
+ }
1389
+ await doTransition(event, transition.target, payload, false);
1390
+ return true;
1391
+ },
1392
+ can(event) {
1393
+ return getTransition(event) !== null;
1394
+ },
1395
+ transitions() {
1396
+ const stateNode = states[currentState];
1397
+ if (!stateNode?.on) return [];
1398
+ return Object.keys(stateNode.on);
1399
+ },
1400
+ toJSON() {
1401
+ return {
1402
+ state: currentState,
1403
+ context: structuredClone(context),
1404
+ history: trackHistory ? [...history] : void 0
1405
+ };
1406
+ },
1407
+ restore(snapshot2) {
1408
+ if (!states[snapshot2.state]) {
1409
+ throw new Error(`Invalid snapshot state "${snapshot2.state}" - not defined in states`);
1410
+ }
1411
+ currentState = snapshot2.state;
1412
+ context = snapshot2.context;
1413
+ if (snapshot2.history) {
1414
+ history = [...snapshot2.history];
1415
+ }
1416
+ },
1417
+ destroy() {
1418
+ for (const cleanup of cleanupFns) {
1419
+ cleanup();
1420
+ }
1421
+ cleanupFns.length = 0;
1422
+ }
1423
+ };
1424
+ if (!snapshot) {
1425
+ setTimeout(async () => {
1426
+ const initialNode = states[initial];
1427
+ await emitFSM(`enter:${initial}`, {
1428
+ state: initial
1429
+ });
1430
+ if (initialNode?.entry) {
1431
+ await initialNode.entry(context, "__INIT__", void 0);
1432
+ }
1433
+ }, 0);
1434
+ }
1435
+ return machine;
1436
+ }
1437
+ function defineMachine(config) {
1438
+ return config;
1439
+ }
1440
+
1441
+ // src/fsm/create-machine.ts
1442
+ function createMachine(config) {
1443
+ const {
1444
+ id,
1445
+ initial,
1446
+ context: initialContext,
1447
+ states,
1448
+ on: eventHandlers = {},
1449
+ helpers: customHelpers = {}
1450
+ } = config;
1451
+ const kernel = new Kernel();
1452
+ const activeTimers = /* @__PURE__ */ new Map();
1453
+ const machineConfig = {
1454
+ prefix: id,
1455
+ initial,
1456
+ context: initialContext,
1457
+ states: {},
1458
+ trackHistory: true
1459
+ };
1460
+ for (const [stateName, stateDef] of Object.entries(states)) {
1461
+ machineConfig.states[stateName] = {
1462
+ on: stateDef.on ? { ...stateDef.on } : void 0
1463
+ };
1464
+ }
1465
+ const baseMachine = useMachine(kernel, machineConfig);
1466
+ const createHelpers = () => ({
1467
+ // Built-ins
1468
+ set: (partial) => baseMachine.setContext(partial),
1469
+ send: (event, payload) => baseMachine.send(event, payload),
1470
+ log: console.log,
1471
+ // Custom helpers (can override built-ins)
1472
+ ...customHelpers
1473
+ });
1474
+ kernel.on(`${id}:enter:*`, async (e) => {
1475
+ const stateName = e.data?.state;
1476
+ if (!stateName) return;
1477
+ activeTimers.forEach((timer, key) => {
1478
+ if (!key.startsWith(stateName + ":")) {
1479
+ clearTimeout(timer);
1480
+ activeTimers.delete(key);
1481
+ }
1482
+ });
1483
+ const stateConfig = states[stateName];
1484
+ if (!stateConfig) return;
1485
+ if (stateConfig.entry) {
1486
+ await stateConfig.entry(baseMachine.getContext(), createHelpers());
1487
+ }
1488
+ if (stateConfig.after) {
1489
+ const timerId = setTimeout(() => {
1490
+ baseMachine.send(stateConfig.after.send);
1491
+ activeTimers.delete(stateName + ":timer");
1492
+ }, stateConfig.after.delay);
1493
+ activeTimers.set(stateName + ":timer", timerId);
1494
+ }
1495
+ });
1496
+ kernel.on(`${id}:exit:*`, async (e) => {
1497
+ const stateName = e.data?.state;
1498
+ if (!stateName) return;
1499
+ const timerId = activeTimers.get(stateName + ":timer");
1500
+ if (timerId) {
1501
+ clearTimeout(timerId);
1502
+ activeTimers.delete(stateName + ":timer");
1503
+ }
1504
+ const stateConfig = states[stateName];
1505
+ if (!stateConfig) return;
1506
+ if (stateConfig.exit) {
1507
+ await stateConfig.exit(baseMachine.getContext(), createHelpers());
1508
+ }
1509
+ });
1510
+ kernel.on(`${id}:transition`, async (e) => {
1511
+ const event = e.data?.event;
1512
+ if (!event) return;
1513
+ const handler = eventHandlers[event];
1514
+ if (handler) {
1515
+ await handler(baseMachine.getContext(), createHelpers());
1516
+ }
1517
+ });
1518
+ const machine = {
1519
+ ...baseMachine,
1520
+ id,
1521
+ get state() {
1522
+ return baseMachine.getState();
1523
+ },
1524
+ get context() {
1525
+ return baseMachine.getContext();
1526
+ },
1527
+ destroy() {
1528
+ activeTimers.forEach((timer) => clearTimeout(timer));
1529
+ activeTimers.clear();
1530
+ baseMachine.destroy();
1531
+ }
1532
+ };
1533
+ return machine;
1534
+ }
1535
+
1536
+ // src/xstate/xstate-import.ts
1537
+ function fromXState(xstateConfig, options) {
1538
+ const { prefix, guards = {}, actions = {}, allowForce, trackHistory } = options;
1539
+ const states = {};
1540
+ for (const [stateName, xstateNode] of Object.entries(xstateConfig.states)) {
1541
+ const stateNode = {};
1542
+ if (xstateNode.on) {
1543
+ stateNode.on = {};
1544
+ for (const [event, transition] of Object.entries(xstateNode.on)) {
1545
+ if (typeof transition === "string") {
1546
+ stateNode.on[event] = transition;
1547
+ } else {
1548
+ const transitionDef = {
1549
+ target: transition.target || stateName
1550
+ // Self-transition if no target
1551
+ };
1552
+ const guardRef = transition.cond || transition.guard;
1553
+ if (guardRef) {
1554
+ const guardName = typeof guardRef === "string" ? guardRef : guardRef.type;
1555
+ const guardFn = guards[guardName];
1556
+ if (guardFn) {
1557
+ transitionDef.guard = guardFn;
1558
+ } else {
1559
+ console.warn(`Guard "${guardName}" not provided in options.guards`);
1560
+ }
1561
+ }
1562
+ if (transition.actions) {
1563
+ const actionRefs = Array.isArray(transition.actions) ? transition.actions : [transition.actions];
1564
+ const actionFns = [];
1565
+ for (const actionRef of actionRefs) {
1566
+ const actionName = typeof actionRef === "string" ? actionRef : actionRef.type;
1567
+ const actionFn = actions[actionName];
1568
+ if (actionFn) {
1569
+ actionFns.push(actionFn);
1570
+ } else {
1571
+ console.warn(`Action "${actionName}" not provided in options.actions`);
1572
+ }
1573
+ }
1574
+ if (actionFns.length > 0) {
1575
+ transitionDef.actions = actionFns.length === 1 ? actionFns[0] : actionFns;
1576
+ }
1577
+ }
1578
+ stateNode.on[event] = transitionDef;
1579
+ }
1580
+ }
1581
+ }
1582
+ if (xstateNode.entry) {
1583
+ const entryRefs = Array.isArray(xstateNode.entry) ? xstateNode.entry : [xstateNode.entry];
1584
+ const entryFns = [];
1585
+ for (const actionRef of entryRefs) {
1586
+ const actionName = typeof actionRef === "string" ? actionRef : actionRef.type;
1587
+ const actionFn = actions[actionName];
1588
+ if (actionFn) {
1589
+ entryFns.push(actionFn);
1590
+ }
1591
+ }
1592
+ if (entryFns.length > 0) {
1593
+ stateNode.entry = async (ctx, event, payload) => {
1594
+ for (const fn of entryFns) {
1595
+ await fn(ctx, event, payload);
1596
+ }
1597
+ };
1598
+ }
1599
+ }
1600
+ if (xstateNode.exit) {
1601
+ const exitRefs = Array.isArray(xstateNode.exit) ? xstateNode.exit : [xstateNode.exit];
1602
+ const exitFns = [];
1603
+ for (const actionRef of exitRefs) {
1604
+ const actionName = typeof actionRef === "string" ? actionRef : actionRef.type;
1605
+ const actionFn = actions[actionName];
1606
+ if (actionFn) {
1607
+ exitFns.push(actionFn);
1608
+ }
1609
+ }
1610
+ if (exitFns.length > 0) {
1611
+ stateNode.exit = async (ctx, event, payload) => {
1612
+ for (const fn of exitFns) {
1613
+ await fn(ctx, event, payload);
1614
+ }
1615
+ };
1616
+ }
1617
+ }
1618
+ states[stateName] = stateNode;
1619
+ }
1620
+ return {
1621
+ prefix,
1622
+ initial: xstateConfig.initial,
1623
+ context: xstateConfig.context,
1624
+ states,
1625
+ allowForce,
1626
+ trackHistory
1627
+ };
1628
+ }
1629
+ function toXStateFormat(config) {
1630
+ const states = {};
1631
+ for (const [stateName, stateNode] of Object.entries(config.states)) {
1632
+ const xstateNode = {};
1633
+ if (stateNode.on) {
1634
+ xstateNode.on = {};
1635
+ for (const [event, transition] of Object.entries(stateNode.on)) {
1636
+ if (typeof transition === "string") {
1637
+ xstateNode.on[event] = transition;
1638
+ } else {
1639
+ const xstateTransition = {
1640
+ target: transition.target
1641
+ };
1642
+ if (transition.guard) {
1643
+ xstateTransition.guard = { type: "guard" };
1644
+ }
1645
+ if (transition.actions) {
1646
+ xstateTransition.actions = { type: "action" };
1647
+ }
1648
+ xstateNode.on[event] = xstateTransition;
1649
+ }
1650
+ }
1651
+ }
1652
+ if (stateNode.entry) {
1653
+ xstateNode.entry = { type: "entry" };
1654
+ }
1655
+ if (stateNode.exit) {
1656
+ xstateNode.exit = { type: "exit" };
1657
+ }
1658
+ states[stateName] = xstateNode;
1659
+ }
1660
+ return {
1661
+ id: config.prefix,
1662
+ initial: config.initial,
1663
+ context: config.context,
1664
+ states
1665
+ };
1666
+ }
1667
+
1668
+ // src/xstate/xstate-behaviors.ts
1669
+ function formatStateCentricCode(config) {
1670
+ const { id, initial, context, states, on: globalHandlers } = config;
1671
+ const lines = [
1672
+ `// FSM Definition for "${id}"`,
1673
+ `// Helpers: ctx, set(obj), send(event), log(msg)`,
1674
+ "",
1675
+ `export default {`,
1676
+ ` id: '${id}',`,
1677
+ ` initial: '${initial}',`
1678
+ ];
1679
+ if (context && Object.keys(context).length > 0) {
1680
+ lines.push(` context: ${JSON.stringify(context)},`);
1681
+ }
1682
+ lines.push(" states: {");
1683
+ for (const [stateName, stateConfig] of Object.entries(states)) {
1684
+ lines.push(` ${stateName}: {`);
1685
+ if (stateConfig.entry) {
1686
+ lines.push(` entry: ${stateConfig.entry.toString()},`);
1687
+ }
1688
+ if (stateConfig.exit) {
1689
+ lines.push(` exit: ${stateConfig.exit.toString()},`);
1690
+ }
1691
+ if (stateConfig.after) {
1692
+ lines.push(` after: { delay: ${stateConfig.after.delay}, send: '${stateConfig.after.send}' },`);
1693
+ }
1694
+ if (stateConfig.on && Object.keys(stateConfig.on).length > 0) {
1695
+ lines.push(" on: {");
1696
+ for (const [event, target] of Object.entries(stateConfig.on)) {
1697
+ if (typeof target === "string") {
1698
+ lines.push(` ${event}: '${target}',`);
1699
+ } else {
1700
+ lines.push(` ${event}: { target: '${target.target}'${target.cond ? `, cond: '${target.cond}'` : ""} },`);
1701
+ }
1702
+ }
1703
+ lines.push(" },");
1704
+ }
1705
+ lines.push(" },");
1706
+ }
1707
+ lines.push(" },");
1708
+ if (globalHandlers && Object.keys(globalHandlers).length > 0) {
1709
+ lines.push(" on: {");
1710
+ for (const [event, fn] of Object.entries(globalHandlers)) {
1711
+ lines.push(` ${event}: ${fn.toString()},`);
1712
+ }
1713
+ lines.push(" },");
1714
+ }
1715
+ lines.push("};");
1716
+ return lines.join("\n");
1717
+ }
1718
+
1719
+ // src/index.ts
1720
+ var VERSION = "2.2.0";
1721
+
1722
+ exports.CircularDependencyError = CircularDependencyError;
1723
+ exports.Composition = Composition;
1724
+ exports.CyclicDependencyError = CyclicDependencyError;
1725
+ exports.Kernel = Kernel;
1726
+ exports.KernelEvent = KernelEvent;
1727
+ exports.ListenerContext = ListenerContext;
1728
+ exports.MaxListenersExceededError = MaxListenersExceededError;
1729
+ exports.MissingDependencyError = MissingDependencyError;
1730
+ exports.VERSION = VERSION;
1731
+ exports.createComposition = createComposition;
1732
+ exports.createKernel = createKernel;
1733
+ exports.createMachine = createMachine;
1734
+ exports.createNamespacedMerger = createNamespacedMerger;
1735
+ exports.createOverrideMerger = createOverrideMerger;
1736
+ exports.defineMachine = defineMachine;
1737
+ exports.formatStateCentricCode = formatStateCentricCode;
1738
+ exports.fromXState = fromXState;
1739
+ exports.isContextMerger = isContextMerger;
1740
+ exports.isContextMergerFunction = isContextMergerFunction;
1741
+ exports.isEventName = isEventName;
1742
+ exports.isKernelEvent = isKernelEvent;
1743
+ exports.isListenerFunction = isListenerFunction;
1744
+ exports.isListenerOptions = isListenerOptions;
1745
+ exports.isPredicateFunction = isPredicateFunction;
1746
+ exports.toXStateFormat = toXStateFormat;
1747
+ exports.toposort = toposort;
1748
+ exports.useMachine = useMachine;
1749
+ //# sourceMappingURL=index.cjs.map
1750
+ //# sourceMappingURL=index.cjs.map