@context-action/core 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { createLogger, getDebugFromEnv, getLoggerNameFromEnv } from "@context-action/logger";
2
+
1
3
  //#region rolldown:runtime
2
4
  var __create = Object.create;
3
5
  var __defProp = Object.defineProperty;
@@ -23,6 +25,197 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
25
  enumerable: true
24
26
  }) : target, mod));
25
27
 
28
+ //#endregion
29
+ //#region src/execution-modes.ts
30
+ /**
31
+ * Execute handlers in sequential mode (one after another)
32
+ * @implements execution-modes
33
+ * @implements sequential-execution
34
+ * @memberof core-concepts
35
+ * @internal
36
+ * @since 1.0.0
37
+ *
38
+ * Executes action handlers sequentially in priority order, supporting flow control,
39
+ * conditional execution, and priority jumping within the pipeline.
40
+ *
41
+ * @template T - The type of the payload being processed
42
+ * @param context - Pipeline execution context with handlers and state
43
+ * @param createController - Factory function for creating pipeline controllers
44
+ * @param logger - Logger instance for tracing execution
45
+ * @returns Promise that resolves when all handlers complete or pipeline aborts
46
+ *
47
+ * Features:
48
+ * - Priority-based execution order (higher priority first)
49
+ * - Support for priority jumping within execution
50
+ * - Conditional handler execution (condition/validation checks)
51
+ * - Blocking/non-blocking handler support
52
+ * - Comprehensive error handling and recovery
53
+ */
54
+ async function executeSequential(context, createController, logger) {
55
+ logger.trace("Executing in sequential mode", { handlerCount: context.handlers.length });
56
+ for (let i = 0; i < context.handlers.length; i++) {
57
+ if (context.aborted) {
58
+ logger.trace("Sequential execution aborted", {
59
+ atIndex: i,
60
+ reason: context.abortReason
61
+ });
62
+ break;
63
+ }
64
+ if (context.jumpToPriority !== void 0) {
65
+ const jumpIndex = context.handlers.findIndex((handler) => handler.config.priority === context.jumpToPriority);
66
+ if (jumpIndex !== -1 && jumpIndex !== i) {
67
+ logger.trace("Jumping to priority", {
68
+ fromIndex: i,
69
+ toIndex: jumpIndex,
70
+ priority: context.jumpToPriority
71
+ });
72
+ i = jumpIndex - 1;
73
+ context.jumpToPriority = void 0;
74
+ continue;
75
+ }
76
+ context.jumpToPriority = void 0;
77
+ }
78
+ const registration = context.handlers[i];
79
+ context.currentIndex = i;
80
+ if (registration.config.condition && !registration.config.condition()) {
81
+ logger.debug(`Skipping handler '${registration.id}' - condition not met`);
82
+ continue;
83
+ }
84
+ if (registration.config.validation && !registration.config.validation(context.payload)) {
85
+ logger.debug(`Skipping handler '${registration.id}' - validation failed`);
86
+ continue;
87
+ }
88
+ const controller = createController(registration, i);
89
+ try {
90
+ logger.trace(`Executing handler ${i + 1}/${context.handlers.length}`, {
91
+ handlerId: registration.id,
92
+ priority: registration.config.priority
93
+ });
94
+ const result = registration.handler(context.payload, controller);
95
+ if (registration.config.blocking && result instanceof Promise) {
96
+ logger.trace(`Waiting for blocking handler '${registration.id}'`);
97
+ await result;
98
+ }
99
+ logger.trace(`Handler '${registration.id}' completed`);
100
+ } catch (error) {
101
+ logger.error(`Handler '${registration.id}' threw an error`, error);
102
+ if (registration.config.blocking) throw error;
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Execute handlers in parallel mode (all at once)
108
+ * @internal
109
+ */
110
+ async function executeParallel(context, createController, logger) {
111
+ logger.trace("Executing in parallel mode", { handlerCount: context.handlers.length });
112
+ const runnableHandlers = context.handlers.filter((registration, _index) => {
113
+ if (registration.config.condition && !registration.config.condition()) {
114
+ logger.debug(`Skipping handler '${registration.id}' - condition not met`);
115
+ return false;
116
+ }
117
+ if (registration.config.validation && !registration.config.validation(context.payload)) {
118
+ logger.debug(`Skipping handler '${registration.id}' - validation failed`);
119
+ return false;
120
+ }
121
+ return true;
122
+ });
123
+ logger.trace(`Running ${runnableHandlers.length} handlers in parallel`);
124
+ const handlerPromises = runnableHandlers.map(async (registration, _index) => {
125
+ const controller = createController(registration, _index);
126
+ try {
127
+ logger.trace(`Starting parallel handler '${registration.id}'`);
128
+ const result = registration.handler(context.payload, controller);
129
+ if (result instanceof Promise) await result;
130
+ logger.trace(`Parallel handler '${registration.id}' completed`);
131
+ return {
132
+ success: true,
133
+ handlerId: registration.id
134
+ };
135
+ } catch (error) {
136
+ logger.error(`Parallel handler '${registration.id}' failed`, error);
137
+ if (registration.config.blocking) throw error;
138
+ return {
139
+ success: false,
140
+ handlerId: registration.id,
141
+ error
142
+ };
143
+ }
144
+ });
145
+ const results = await Promise.allSettled(handlerPromises);
146
+ const failures = results.filter((result, index) => {
147
+ if (result.status === "rejected") {
148
+ const registration = runnableHandlers[index];
149
+ return registration.config.blocking;
150
+ }
151
+ return false;
152
+ });
153
+ if (failures.length > 0) {
154
+ const firstFailure = failures[0];
155
+ throw firstFailure.reason;
156
+ }
157
+ logger.trace("Parallel execution completed", {
158
+ successful: results.filter((r) => r.status === "fulfilled").length,
159
+ failed: results.filter((r) => r.status === "rejected").length
160
+ });
161
+ }
162
+ /**
163
+ * Execute handlers in race mode (first to complete wins)
164
+ * @internal
165
+ */
166
+ async function executeRace(context, createController, logger) {
167
+ logger.trace("Executing in race mode", { handlerCount: context.handlers.length });
168
+ const runnableHandlers = context.handlers.filter((registration, _index) => {
169
+ if (registration.config.condition && !registration.config.condition()) {
170
+ logger.debug(`Skipping handler '${registration.id}' - condition not met`);
171
+ return false;
172
+ }
173
+ if (registration.config.validation && !registration.config.validation(context.payload)) {
174
+ logger.debug(`Skipping handler '${registration.id}' - validation failed`);
175
+ return false;
176
+ }
177
+ return true;
178
+ });
179
+ if (runnableHandlers.length === 0) {
180
+ logger.trace("No runnable handlers for race mode");
181
+ return;
182
+ }
183
+ logger.trace(`Racing ${runnableHandlers.length} handlers`);
184
+ const handlerPromises = runnableHandlers.map(async (registration, _index) => {
185
+ const controller = createController(registration, _index);
186
+ try {
187
+ logger.trace(`Starting race handler '${registration.id}'`);
188
+ const result = registration.handler(context.payload, controller);
189
+ if (result instanceof Promise) await result;
190
+ logger.trace(`Race handler '${registration.id}' completed`);
191
+ return {
192
+ success: true,
193
+ handlerId: registration.id,
194
+ registration
195
+ };
196
+ } catch (error) {
197
+ logger.error(`Race handler '${registration.id}' failed`, error);
198
+ return {
199
+ success: false,
200
+ handlerId: registration.id,
201
+ error,
202
+ registration
203
+ };
204
+ }
205
+ });
206
+ try {
207
+ const winner = await Promise.race(handlerPromises);
208
+ logger.debug("Race completed", {
209
+ winner: winner.handlerId,
210
+ success: winner.success
211
+ });
212
+ if (!winner.success && winner.registration?.config.blocking) throw winner.error;
213
+ } catch (error) {
214
+ logger.error("Race execution failed", error);
215
+ throw error;
216
+ }
217
+ }
218
+
26
219
  //#endregion
27
220
  //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js
28
221
  var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
@@ -70,7 +263,7 @@ var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
70
263
  //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
71
264
  var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
72
265
  var toPropertyKey = require_toPropertyKey();
73
- function _defineProperty$1(e, r, t) {
266
+ function _defineProperty$2(e, r, t) {
74
267
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
75
268
  value: t,
76
269
  enumerable: !0,
@@ -78,94 +271,617 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
78
271
  writable: !0
79
272
  }) : e[r] = t, e;
80
273
  }
81
- module.exports = _defineProperty$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
274
+ module.exports = _defineProperty$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
82
275
  } });
83
276
 
277
+ //#endregion
278
+ //#region src/action-guard.ts
279
+ var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
280
+ /**
281
+ * Action Guard system for managing action execution timing
282
+ * @implements action-guard
283
+ * @implements performance-optimization
284
+ * @implements user-experience-optimization
285
+ * @memberof core-concepts
286
+ * @internal
287
+ * @since 1.0.0
288
+ *
289
+ * Provides debouncing, throttling, and blocking mechanisms for action execution
290
+ * to optimize performance and enhance user experience. Manages timing state
291
+ * per action to prevent unnecessary or excessive action invocations.
292
+ *
293
+ * Key Features:
294
+ * - Debouncing: Delay execution until activity stops
295
+ * - Throttling: Limit execution frequency to intervals
296
+ * - Per-action state management with automatic cleanup
297
+ * - Memory leak prevention through proper timer management
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * const guard = new ActionGuard(logger);
302
+ *
303
+ * // Debounce search input (wait 300ms after typing stops)
304
+ * if (await guard.debounce('search', 300)) {
305
+ * executeSearch();
306
+ * }
307
+ *
308
+ * // Throttle scroll handler (max once per 100ms)
309
+ * if (guard.throttle('scroll', 100)) {
310
+ * updateScrollPosition();
311
+ * }
312
+ * ```
313
+ */
314
+ var ActionGuard = class {
315
+ constructor(logger) {
316
+ (0, import_defineProperty$1.default)(this, "guards", /* @__PURE__ */ new Map());
317
+ (0, import_defineProperty$1.default)(this, "logger", void 0);
318
+ this.logger = logger;
319
+ }
320
+ /**
321
+ * Check if action should be debounced
322
+ * @param actionKey - Unique key for the action
323
+ * @param debounceMs - Debounce delay in milliseconds
324
+ * @returns Promise that resolves when debounce period is complete
325
+ */
326
+ async debounce(actionKey, debounceMs) {
327
+ this.logger.trace(`Checking debounce for '${actionKey}'`, { debounceMs });
328
+ let state = this.guards.get(actionKey);
329
+ if (!state) {
330
+ state = {
331
+ lastExecuted: 0,
332
+ isThrottled: false
333
+ };
334
+ this.guards.set(actionKey, state);
335
+ }
336
+ if (state.debounceTimer) {
337
+ clearTimeout(state.debounceTimer);
338
+ this.logger.trace(`Cleared existing debounce timer for '${actionKey}'`);
339
+ }
340
+ return new Promise((resolve) => {
341
+ state.debounceTimer = setTimeout(() => {
342
+ this.logger.trace(`Debounce completed for '${actionKey}'`);
343
+ state.debounceTimer = void 0;
344
+ state.lastExecuted = Date.now();
345
+ resolve(true);
346
+ }, debounceMs);
347
+ this.logger.trace(`Set debounce timer for '${actionKey}'`, { delay: debounceMs });
348
+ });
349
+ }
350
+ /**
351
+ * Check if action should be throttled
352
+ * @param actionKey - Unique key for the action
353
+ * @param throttleMs - Throttle delay in milliseconds
354
+ * @returns True if action should proceed, false if throttled
355
+ */
356
+ throttle(actionKey, throttleMs) {
357
+ this.logger.trace(`Checking throttle for '${actionKey}'`, { throttleMs });
358
+ let state = this.guards.get(actionKey);
359
+ if (!state) {
360
+ state = {
361
+ lastExecuted: 0,
362
+ isThrottled: false
363
+ };
364
+ this.guards.set(actionKey, state);
365
+ }
366
+ const now = Date.now();
367
+ const timeSinceLastExecution = now - state.lastExecuted;
368
+ if (timeSinceLastExecution >= throttleMs) {
369
+ state.lastExecuted = now;
370
+ state.isThrottled = false;
371
+ this.logger.trace(`Throttle passed for '${actionKey}'`, {
372
+ timeSinceLastExecution,
373
+ throttleMs
374
+ });
375
+ return true;
376
+ }
377
+ if (state.isThrottled) {
378
+ this.logger.trace(`Action '${actionKey}' is already throttled`);
379
+ return false;
380
+ }
381
+ state.isThrottled = true;
382
+ const remainingTime = throttleMs - timeSinceLastExecution;
383
+ state.throttleTimer = setTimeout(() => {
384
+ state.isThrottled = false;
385
+ state.throttleTimer = void 0;
386
+ this.logger.trace(`Throttle period ended for '${actionKey}'`);
387
+ }, remainingTime);
388
+ this.logger.trace(`Action '${actionKey}' throttled`, {
389
+ timeSinceLastExecution,
390
+ remainingTime
391
+ });
392
+ return false;
393
+ }
394
+ /**
395
+ * Clear all guards for an action
396
+ * @param actionKey - Action key to clear
397
+ */
398
+ clearGuards(actionKey) {
399
+ this.logger.trace(`Clearing guards for '${actionKey}'`);
400
+ const state = this.guards.get(actionKey);
401
+ if (state) {
402
+ if (state.debounceTimer) clearTimeout(state.debounceTimer);
403
+ if (state.throttleTimer) clearTimeout(state.throttleTimer);
404
+ this.guards.delete(actionKey);
405
+ this.logger.debug(`Cleared guards for '${actionKey}'`);
406
+ }
407
+ }
408
+ /**
409
+ * Clear all guards
410
+ */
411
+ clearAll() {
412
+ this.logger.trace("Clearing all action guards");
413
+ for (const [, state] of this.guards) {
414
+ if (state.debounceTimer) clearTimeout(state.debounceTimer);
415
+ if (state.throttleTimer) clearTimeout(state.throttleTimer);
416
+ }
417
+ this.guards.clear();
418
+ this.logger.debug("Cleared all action guards");
419
+ }
420
+ /**
421
+ * Get current guard state for debugging
422
+ * @param actionKey - Action key to inspect
423
+ */
424
+ getGuardState(actionKey) {
425
+ return this.guards.get(actionKey);
426
+ }
427
+ /**
428
+ * Get all active guards for debugging
429
+ */
430
+ getAllGuardStates() {
431
+ return new Map(this.guards);
432
+ }
433
+ };
434
+
84
435
  //#endregion
85
436
  //#region src/ActionRegister.ts
86
437
  var import_defineProperty = __toESM(require_defineProperty(), 1);
87
- var ActionRegister = class {
438
+ /**
439
+ * Simple event emitter implementation for ActionRegister events
440
+ * @internal
441
+ */
442
+ var SimpleEventEmitter = class {
88
443
  constructor() {
444
+ (0, import_defineProperty.default)(this, "listeners", /* @__PURE__ */ new Map());
445
+ }
446
+ on(event, handler) {
447
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
448
+ this.listeners.get(event).add(handler);
449
+ return () => this.off(event, handler);
450
+ }
451
+ emit(event, data) {
452
+ const eventListeners = this.listeners.get(event);
453
+ if (eventListeners) eventListeners.forEach((handler) => {
454
+ try {
455
+ handler(data);
456
+ } catch (error) {
457
+ console.error(`Error in event handler for ${String(event)}:`, error);
458
+ }
459
+ });
460
+ }
461
+ off(event, handler) {
462
+ const eventListeners = this.listeners.get(event);
463
+ if (eventListeners) {
464
+ eventListeners.delete(handler);
465
+ if (eventListeners.size === 0) this.listeners.delete(event);
466
+ }
467
+ }
468
+ removeAllListeners(event) {
469
+ if (event) this.listeners.delete(event);
470
+ else this.listeners.clear();
471
+ }
472
+ };
473
+ /**
474
+ * Central action registration and dispatch system
475
+ * @implements action-pipeline-system
476
+ * @implements actionregister
477
+ * @memberof core-concepts
478
+ *
479
+ * Core action pipeline management system with type-safe action dispatch
480
+ * @template T - Action payload map defining available actions and their payload types
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * interface AppActions extends ActionPayloadMap {
485
+ * increment: void;
486
+ * setCount: number;
487
+ * updateUser: { id: string; name: string };
488
+ * }
489
+ *
490
+ * const actionRegister = new ActionRegister<AppActions>();
491
+ *
492
+ * // Register handlers with priority and configuration
493
+ * actionRegister.register('increment', (_, controller) => {
494
+ * console.log('Incremented');
495
+ * controller.next();
496
+ * }, { priority: 10 });
497
+ *
498
+ * actionRegister.register('setCount', (count, controller) => {
499
+ * console.log(`Count: ${count}`);
500
+ * controller.next();
501
+ * });
502
+ *
503
+ * // Dispatch actions with type safety
504
+ * await actionRegister.dispatch('increment');
505
+ * await actionRegister.dispatch('setCount', 42);
506
+ * ```
507
+ */
508
+ var ActionRegister = class {
509
+ constructor(config = {}) {
89
510
  (0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
90
- (0, import_defineProperty.default)(this, "atomSetters", /* @__PURE__ */ new Map());
511
+ (0, import_defineProperty.default)(this, "handlerCounter", 0);
512
+ (0, import_defineProperty.default)(this, "logger", void 0);
513
+ (0, import_defineProperty.default)(this, "events", new SimpleEventEmitter());
514
+ (0, import_defineProperty.default)(this, "config", void 0);
515
+ (0, import_defineProperty.default)(this, "actionGuard", void 0);
516
+ (0, import_defineProperty.default)(this, "executionMode", "sequential");
517
+ (0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
518
+ (0, import_defineProperty.default)(
519
+ this,
520
+ /**
521
+ * Dispatch action through pipeline
522
+ * @implements action-dispatcher
523
+ *
524
+ * Dispatch an action through the pipeline
525
+ * Overloaded to provide type safety for actions with and without payloads
526
+ */
527
+ "dispatch",
528
+ async (action, payload) => {
529
+ const startTime = Date.now();
530
+ this.logger.trace(`Starting dispatch for action '${String(action)}'`, {
531
+ action,
532
+ payload,
533
+ startTime
534
+ });
535
+ this.events.emit("action:start", {
536
+ action,
537
+ payload
538
+ });
539
+ this.logger.trace(`Emitted 'action:start' event`);
540
+ this.logger.debug(`Dispatching action '${String(action)}'`, { payload });
541
+ const pipeline = this.pipelines.get(action);
542
+ if (!pipeline || pipeline.length === 0) {
543
+ this.logger.warn(`No handlers registered for action '${String(action)}'`);
544
+ this.logger.trace(`Dispatch completed early - no handlers`);
545
+ return;
546
+ }
547
+ this.logger.trace(`Found ${pipeline.length} handlers for action '${String(action)}'`, { handlerIds: pipeline.map((reg) => reg.id) });
548
+ const currentExecutionMode = this.actionExecutionModes.get(action) || this.executionMode;
549
+ const context = {
550
+ action: String(action),
551
+ payload,
552
+ handlers: [...pipeline],
553
+ aborted: false,
554
+ abortReason: void 0,
555
+ currentIndex: 0,
556
+ jumpToPriority: void 0,
557
+ executionMode: currentExecutionMode
558
+ };
559
+ try {
560
+ await this.executePipeline(context);
561
+ const metrics = {
562
+ action: String(action),
563
+ executionTime: Date.now() - startTime,
564
+ handlerCount: context.handlers.length,
565
+ success: !context.aborted,
566
+ timestamp: Date.now()
567
+ };
568
+ if (context.aborted) {
569
+ metrics.error = context.abortReason;
570
+ this.events.emit("action:abort", {
571
+ action,
572
+ payload,
573
+ reason: context.abortReason
574
+ });
575
+ } else this.events.emit("action:complete", {
576
+ action,
577
+ payload,
578
+ metrics
579
+ });
580
+ this.logger.debug(`Completed action '${String(action)}'`, metrics);
581
+ } catch (error) {
582
+ const metrics = {
583
+ action: String(action),
584
+ executionTime: Date.now() - startTime,
585
+ handlerCount: context.handlers.length,
586
+ success: false,
587
+ error: error.message || "Unknown error",
588
+ timestamp: Date.now()
589
+ };
590
+ this.logger.error(`Error executing action '${String(action)}'`, metrics);
591
+ this.events.emit("action:error", {
592
+ action,
593
+ payload,
594
+ error: error instanceof Error ? error : new Error(String(error))
595
+ });
596
+ throw error;
597
+ }
598
+ }
599
+ );
600
+ this.config = {
601
+ logger: config.logger || createLogger(config.logLevel),
602
+ logLevel: config.logLevel ?? 3,
603
+ name: config.name || getLoggerNameFromEnv(),
604
+ debug: config.debug ?? getDebugFromEnv(),
605
+ defaultExecutionMode: config.defaultExecutionMode ?? "sequential"
606
+ };
607
+ this.logger = this.config.logger;
608
+ this.actionGuard = new ActionGuard(this.logger);
609
+ this.executionMode = this.config.defaultExecutionMode;
610
+ this.logger.trace(`${this.config.name} constructor called`, { config });
611
+ if (this.config.debug) this.logger.info(`${this.config.name} initialized`, {
612
+ logLevel: this.config.logLevel,
613
+ debug: this.config.debug,
614
+ defaultExecutionMode: this.executionMode
615
+ });
616
+ this.logger.trace(`${this.config.name} constructor completed`);
91
617
  }
618
+ /**
619
+ * Register action handler with pipeline
620
+ * @implements action-handler
621
+ *
622
+ * Register a handler for an action in the pipeline
623
+ * @param action - The action name to handle
624
+ * @param handler - The handler function to execute
625
+ * @param config - Optional configuration for the handler
626
+ * @returns Unregister function to remove the handler
627
+ *
628
+ * @example
629
+ * ```typescript
630
+ * const unregister = actionRegister.register('updateUser',
631
+ * async (payload, controller) => {
632
+ * // Validate payload
633
+ * if (!payload.id) {
634
+ * controller.abort('User ID is required');
635
+ * return;
636
+ * }
637
+ *
638
+ * // Process update
639
+ * await updateUserInStore(payload);
640
+ * controller.next();
641
+ * },
642
+ * { priority: 10, blocking: true }
643
+ * );
644
+ *
645
+ * // Later, remove the handler
646
+ * unregister();
647
+ * ```
648
+ */
92
649
  register(action, handler, config = {}) {
93
- if (!this.pipelines.has(action)) this.pipelines.set(action, /* @__PURE__ */ new Map());
650
+ this.logger.trace(`Registering handler for action '${String(action)}'`, { config });
651
+ const handlerId = config.id || `handler_${++this.handlerCounter}`;
652
+ this.logger.trace(`Generated handler ID: ${handlerId}`);
653
+ const registration = {
654
+ handler,
655
+ config: {
656
+ priority: config.priority ?? 0,
657
+ id: handlerId,
658
+ blocking: config.blocking ?? false,
659
+ once: config.once ?? false,
660
+ condition: config.condition || (() => true),
661
+ debounce: config.debounce,
662
+ throttle: config.throttle,
663
+ validation: config.validation,
664
+ middleware: config.middleware ?? false
665
+ },
666
+ id: handlerId
667
+ };
668
+ this.logger.trace(`Created handler registration`, { registration: {
669
+ id: handlerId,
670
+ config: registration.config
671
+ } });
672
+ if (!this.pipelines.has(action)) {
673
+ this.logger.trace(`Creating new pipeline for action: ${String(action)}`);
674
+ this.pipelines.set(action, []);
675
+ this.logger.debug(`Created pipeline for action: ${String(action)}`);
676
+ }
94
677
  const pipeline = this.pipelines.get(action);
95
- const handlerId = config.id || `handler_${Date.now()}_${Math.random()}`;
96
- if (pipeline.has(handlerId)) {
97
- console.warn(`Handler with id ${handlerId} already exists`);
678
+ this.logger.trace(`Current pipeline for '${String(action)}' has ${pipeline.length} handlers`);
679
+ if (pipeline.some((reg) => reg.id === handlerId)) {
680
+ this.logger.warn(`Handler with ID '${handlerId}' already exists for action '${String(action)}'`);
681
+ this.logger.trace(`Duplicate handler registration aborted`);
98
682
  return () => {};
99
683
  }
100
- pipeline.set(handlerId, {
101
- handler,
102
- config
684
+ pipeline.push(registration);
685
+ this.logger.trace(`Added handler to pipeline, current length: ${pipeline.length}`);
686
+ pipeline.sort((a, b) => b.config.priority - a.config.priority);
687
+ this.logger.trace(`Pipeline sorted by priority`, { priorities: pipeline.map((reg) => ({
688
+ id: reg.id,
689
+ priority: reg.config.priority
690
+ })) });
691
+ this.logger.debug(`Registered handler for action '${String(action)}'`, {
692
+ handlerId,
693
+ priority: registration.config.priority,
694
+ blocking: registration.config.blocking,
695
+ once: registration.config.once
696
+ });
697
+ this.events.emit("handler:register", {
698
+ action,
699
+ handlerId,
700
+ config: registration.config
103
701
  });
104
- this.sortPipeline(action);
105
702
  return () => {
106
- pipeline.delete(handlerId);
703
+ this.logger.trace(`Unregistering handler '${handlerId}' from action '${String(action)}'`);
704
+ const index = pipeline.findIndex((reg) => reg.id === handlerId);
705
+ if (index !== -1) {
706
+ pipeline.splice(index, 1);
707
+ this.logger.debug(`Unregistered handler '${handlerId}' from action '${String(action)}'`);
708
+ this.logger.trace(`Pipeline now has ${pipeline.length} handlers`);
709
+ this.events.emit("handler:unregister", {
710
+ action,
711
+ handlerId
712
+ });
713
+ } else this.logger.trace(`Handler '${handlerId}' not found in pipeline for unregistration`);
107
714
  };
108
715
  }
109
- registerAtomSetter(name, setter) {
110
- this.atomSetters.set(name, setter);
111
- }
112
- async dispatch(action, payload) {
113
- const pipeline = this.pipelines.get(action);
114
- if (!pipeline || pipeline.size === 0) {
115
- console.warn(`No handlers registered for action: ${String(action)}`);
116
- return;
117
- }
118
- let modifiedPayload = payload;
119
- const handlers = Array.from(pipeline.values());
120
- for (const { handler, config } of handlers) {
121
- let shouldContinue = true;
122
- const controller = {
123
- next: () => {
124
- shouldContinue = true;
125
- },
716
+ /**
717
+ * Execute the pipeline with proper flow control
718
+ * @internal
719
+ */
720
+ async executePipeline(context) {
721
+ this.logger.trace(`Starting pipeline execution`, {
722
+ action: context.action,
723
+ handlerCount: context.handlers.length,
724
+ executionMode: context.executionMode,
725
+ payload: context.payload
726
+ });
727
+ const createController = (registration, _index) => {
728
+ return {
729
+ next: () => {},
126
730
  abort: (reason) => {
127
- shouldContinue = false;
128
- console.log(`Pipeline aborted: ${reason}`);
731
+ this.logger.trace(`Handler '${registration.id}' is aborting pipeline`, { reason });
732
+ context.aborted = true;
733
+ context.abortReason = reason;
734
+ this.logger.warn(`Pipeline aborted by handler '${registration.id}'`, { reason });
129
735
  },
130
736
  modifyPayload: (modifier) => {
131
- modifiedPayload = modifier(modifiedPayload);
737
+ this.logger.trace(`Handler '${registration.id}' is modifying payload`);
738
+ const oldPayload = context.payload;
739
+ context.payload = modifier(context.payload);
740
+ this.logger.debug(`Payload modified by handler '${registration.id}'`);
741
+ this.logger.trace(`Payload change`, {
742
+ oldPayload,
743
+ newPayload: context.payload
744
+ });
745
+ },
746
+ getPayload: () => context.payload,
747
+ jumpToPriority: (priority) => {
748
+ this.logger.trace(`Handler '${registration.id}' jumping to priority ${priority}`);
749
+ context.jumpToPriority = priority;
132
750
  }
133
751
  };
134
- try {
135
- if (config.blocking) await handler(modifiedPayload, controller);
136
- else handler(modifiedPayload, controller);
137
- if (!shouldContinue) break;
138
- } catch (error) {
139
- console.error(`Error in pipeline handler:`, error);
140
- if (config.blocking) throw error;
141
- }
752
+ };
753
+ switch (context.executionMode) {
754
+ case "sequential":
755
+ await executeSequential(context, createController, this.logger);
756
+ break;
757
+ case "parallel":
758
+ await executeParallel(context, createController, this.logger);
759
+ break;
760
+ case "race":
761
+ await executeRace(context, createController, this.logger);
762
+ break;
763
+ default: throw new Error(`Unknown execution mode: ${context.executionMode}`);
142
764
  }
765
+ this.cleanupOneTimeHandlers(context.action, context.handlers);
143
766
  }
144
- sortPipeline(action) {
767
+ /**
768
+ * Clean up one-time handlers after pipeline execution
769
+ * @internal
770
+ */
771
+ cleanupOneTimeHandlers(action, executedHandlers) {
145
772
  const pipeline = this.pipelines.get(action);
146
773
  if (!pipeline) return;
147
- const sorted = Array.from(pipeline.entries()).sort(([, a], [, b]) => {
148
- const priorityA = a.config.priority ?? 0;
149
- const priorityB = b.config.priority ?? 0;
150
- return priorityB - priorityA;
774
+ const oneTimeHandlers = executedHandlers.filter((reg) => reg.config.once);
775
+ if (oneTimeHandlers.length === 0) return;
776
+ this.logger.trace(`Cleaning up ${oneTimeHandlers.length} one-time handlers`);
777
+ oneTimeHandlers.forEach((registration) => {
778
+ const index = pipeline.findIndex((reg) => reg.id === registration.id);
779
+ if (index !== -1) {
780
+ pipeline.splice(index, 1);
781
+ this.logger.debug(`Removed one-time handler '${registration.id}'`);
782
+ }
151
783
  });
152
- pipeline.clear();
153
- sorted.forEach(([id, data]) => pipeline.set(id, data));
784
+ this.logger.trace(`Pipeline now has ${pipeline.length} handlers`);
785
+ }
786
+ /**
787
+ * Get the number of handlers registered for an action
788
+ * @param action - The action to check
789
+ * @returns Number of handlers registered
790
+ */
791
+ getHandlerCount(action) {
792
+ const pipeline = this.pipelines.get(action);
793
+ const count = pipeline ? pipeline.length : 0;
794
+ this.logger.trace(`Handler count for '${String(action)}': ${count}`);
795
+ return count;
796
+ }
797
+ /**
798
+ * Check if any handlers are registered for an action
799
+ * @param action - The action to check
800
+ * @returns True if handlers are registered
801
+ */
802
+ hasHandlers(action) {
803
+ const hasHandlers = this.getHandlerCount(action) > 0;
804
+ this.logger.trace(`Has handlers for '${String(action)}': ${hasHandlers}`);
805
+ return hasHandlers;
806
+ }
807
+ /**
808
+ * Get all registered action names
809
+ * @returns Array of action names
810
+ */
811
+ getRegisteredActions() {
812
+ const actions = Array.from(this.pipelines.keys());
813
+ this.logger.trace(`Registered actions`, {
814
+ actions,
815
+ count: actions.length
816
+ });
817
+ return actions;
818
+ }
819
+ /**
820
+ * Clear all handlers for a specific action
821
+ * @param action - The action to clear
822
+ */
823
+ clearAction(action) {
824
+ this.logger.trace(`Clearing handlers for action '${String(action)}'`);
825
+ const pipeline = this.pipelines.get(action);
826
+ if (pipeline) {
827
+ const handlerCount = pipeline.length;
828
+ this.pipelines.delete(action);
829
+ this.logger.debug(`Cleared ${handlerCount} handlers for action '${String(action)}'`);
830
+ this.logger.trace(`Action '${String(action)}' pipeline removed`);
831
+ } else this.logger.trace(`No pipeline found for action '${String(action)}' to clear`);
832
+ }
833
+ /**
834
+ * Clear all handlers for all actions
835
+ */
836
+ clearAll() {
837
+ this.logger.trace(`Clearing all handlers and pipelines`);
838
+ const actionCount = this.pipelines.size;
839
+ const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
840
+ this.logger.trace(`Before clear`, {
841
+ actionCount,
842
+ totalHandlers
843
+ });
844
+ this.pipelines.clear();
845
+ this.events.removeAllListeners();
846
+ this.logger.info(`Cleared all handlers`, {
847
+ actionCount,
848
+ totalHandlers
849
+ });
850
+ this.logger.trace(`All pipelines and event listeners cleared`);
851
+ }
852
+ /**
853
+ * Add event listener for ActionRegister events
854
+ * @param event - Event name to listen for
855
+ * @param handler - Event handler function
856
+ * @returns Unregister function to remove the listener
857
+ */
858
+ on(event, handler) {
859
+ return this.events.on(event, handler);
860
+ }
861
+ /**
862
+ * Remove event listener
863
+ * @param event - Event name
864
+ * @param handler - Event handler to remove
865
+ */
866
+ off(event, handler) {
867
+ this.events.off(event, handler);
868
+ }
869
+ /**
870
+ * Get current configuration
871
+ * @returns Current ActionRegister configuration
872
+ */
873
+ getConfig() {
874
+ return { ...this.config };
875
+ }
876
+ /**
877
+ * Get logger instance
878
+ * @returns Current logger instance
879
+ */
880
+ getLogger() {
881
+ return this.logger;
154
882
  }
155
883
  };
156
884
 
157
885
  //#endregion
158
- //#region src/types.ts
159
- function createAction(type, payload) {
160
- return {
161
- type,
162
- payload
163
- };
164
- }
165
- function isAction(action, type) {
166
- return action?.type === type;
167
- }
168
-
169
- //#endregion
170
- export { ActionRegister, createAction, isAction };
886
+ export { ActionGuard, ActionRegister, executeParallel, executeRace, executeSequential };
171
887
  //# sourceMappingURL=index.js.map