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