@context-action/core 0.0.3 → 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.cjs CHANGED
@@ -24,7 +24,199 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  }) : target, mod));
25
25
 
26
26
  //#endregion
27
+ const __context_action_logger = __toESM(require("@context-action/logger"));
27
28
 
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
+
219
+ //#endregion
28
220
  //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js
29
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) {
30
222
  function _typeof$2(o) {
@@ -83,346 +275,616 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
83
275
  } });
84
276
 
85
277
  //#endregion
86
- //#region src/logger.ts
278
+ //#region src/action-guard.ts
87
279
  var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
88
280
  /**
89
- * Log levels in order of severity (lowest to highest)
90
- */
91
- let LogLevel = /* @__PURE__ */ function(LogLevel$1) {
92
- LogLevel$1[LogLevel$1["TRACE"] = 0] = "TRACE";
93
- LogLevel$1[LogLevel$1["DEBUG"] = 1] = "DEBUG";
94
- LogLevel$1[LogLevel$1["INFO"] = 2] = "INFO";
95
- LogLevel$1[LogLevel$1["WARN"] = 3] = "WARN";
96
- LogLevel$1[LogLevel$1["ERROR"] = 4] = "ERROR";
97
- LogLevel$1[LogLevel$1["FATAL"] = 5] = "FATAL";
98
- return LogLevel$1;
99
- }({});
100
- /**
101
- * Default console logger implementation
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
+ * ```
102
313
  */
103
- var ConsoleLogger = class {
104
- constructor(level = LogLevel.ERROR) {
105
- this.level = level;
106
- }
107
- shouldLog(level) {
108
- return level >= this.level;
109
- }
110
- formatMessage(level, message) {
111
- return `[${level.toUpperCase()}] ${message}`;
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;
112
319
  }
113
- trace(message, ...args) {
114
- if (this.shouldLog(LogLevel.TRACE)) console.trace(this.formatMessage("trace", message), ...args);
115
- }
116
- debug(message, ...args) {
117
- if (this.shouldLog(LogLevel.DEBUG)) console.debug(this.formatMessage("debug", message), ...args);
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
+ });
118
349
  }
119
- info(message, ...args) {
120
- if (this.shouldLog(LogLevel.INFO)) console.info(this.formatMessage("info", message), ...args);
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;
121
393
  }
122
- warn(message, ...args) {
123
- if (this.shouldLog(LogLevel.WARN)) console.warn(this.formatMessage("warn", message), ...args);
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
+ }
124
407
  }
125
- error(message, ...args) {
126
- if (this.shouldLog(LogLevel.ERROR)) console.error(this.formatMessage("error", message), ...args);
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");
127
419
  }
128
- fatal(message, ...args) {
129
- if (this.shouldLog(LogLevel.FATAL)) console.error(this.formatMessage("fatal", message), ...args);
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);
130
426
  }
131
- setLevel(level) {
132
- this.level = level;
427
+ /**
428
+ * Get all active guards for debugging
429
+ */
430
+ getAllGuardStates() {
431
+ return new Map(this.guards);
133
432
  }
134
433
  };
434
+
435
+ //#endregion
436
+ //#region src/ActionRegister.ts
437
+ var import_defineProperty = __toESM(require_defineProperty(), 1);
135
438
  /**
136
- * Parse log level from string
137
- */
138
- function parseLogLevel(level) {
139
- const upperLevel = level.toUpperCase();
140
- switch (upperLevel) {
141
- case "TRACE": return LogLevel.TRACE;
142
- case "DEBUG": return LogLevel.DEBUG;
143
- case "INFO": return LogLevel.INFO;
144
- case "WARN": return LogLevel.WARN;
145
- case "ERROR": return LogLevel.ERROR;
146
- case "FATAL": return LogLevel.FATAL;
147
- default: return LogLevel.ERROR;
148
- }
149
- }
150
- /**
151
- * Get log level from environment variable or default to ERROR
152
- */
153
- function getLogLevelFromEnv() {
154
- if (typeof process !== "undefined" && process.env) {
155
- const envLevel = process.env.LOG_LEVEL || process.env.ACTION_LOG_LEVEL;
156
- if (envLevel) return parseLogLevel(envLevel);
157
- }
158
- return LogLevel.TRACE;
159
- }
160
- /**
161
- * Extract trace ID from payload if it exists
162
- */
163
- function extractTraceIdFromPayload(payload) {
164
- if (payload && typeof payload === "object") return payload._traceId || payload.traceId || payload.trace_id;
165
- return void 0;
166
- }
167
- /**
168
- * Extract session ID from payload if it exists
169
- */
170
- function extractSessionIdFromPayload(payload) {
171
- if (payload && typeof payload === "object") return payload._sessionId || payload.sessionId || payload.session_id;
172
- return void 0;
173
- }
174
- /**
175
- * Create OTEL context from payload
176
- */
177
- function createOtelContextFromPayload(payload) {
178
- return {
179
- traceId: extractTraceIdFromPayload(payload),
180
- sessionId: extractSessionIdFromPayload(payload),
181
- metadata: payload
182
- };
183
- }
184
- /**
185
- * OpenTelemetry-aware console logger implementation
439
+ * Simple event emitter implementation for ActionRegister events
440
+ * @internal
186
441
  */
187
- var OtelConsoleLogger = class extends ConsoleLogger {
188
- constructor(level = LogLevel.ERROR) {
189
- super(level);
190
- (0, import_defineProperty$1.default)(this, "context", {});
191
- }
192
- setContext(context) {
193
- this.context = {
194
- ...this.context,
195
- ...context
196
- };
442
+ var SimpleEventEmitter = class {
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
+ });
197
460
  }
198
- getContext() {
199
- return { ...this.context };
200
- }
201
- clearContext() {
202
- this.context = {};
203
- }
204
- formatWithContext(level, message) {
205
- const contextParts = [];
206
- if (this.context.sessionId) contextParts.push(`session=${this.context.sessionId}`);
207
- if (this.context.traceId) contextParts.push(`trace=${this.context.traceId}`);
208
- if (this.context.spanId) contextParts.push(`span=${this.context.spanId}`);
209
- const contextStr = contextParts.length > 0 ? ` [${contextParts.join(", ")}]` : "";
210
- return `[${level.toUpperCase()}]${contextStr} ${message}`;
211
- }
212
- logWithContext(level, message, ...args) {
213
- const levelName = LogLevel[level].toLowerCase();
214
- const formattedMessage = this.formatWithContext(levelName, message);
215
- switch (level) {
216
- case LogLevel.TRACE:
217
- if (this.shouldLog(LogLevel.TRACE)) console.trace(formattedMessage, ...args);
218
- break;
219
- case LogLevel.DEBUG:
220
- if (this.shouldLog(LogLevel.DEBUG)) console.debug(formattedMessage, ...args);
221
- break;
222
- case LogLevel.INFO:
223
- if (this.shouldLog(LogLevel.INFO)) console.info(formattedMessage, ...args);
224
- break;
225
- case LogLevel.WARN:
226
- if (this.shouldLog(LogLevel.WARN)) console.warn(formattedMessage, ...args);
227
- break;
228
- case LogLevel.ERROR:
229
- if (this.shouldLog(LogLevel.ERROR)) console.error(formattedMessage, ...args);
230
- break;
231
- case LogLevel.FATAL:
232
- if (this.shouldLog(LogLevel.FATAL)) console.error(formattedMessage, ...args);
233
- break;
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);
234
466
  }
235
467
  }
236
- trace(message, ...args) {
237
- this.logWithContext(LogLevel.TRACE, message, ...args);
238
- }
239
- debug(message, ...args) {
240
- this.logWithContext(LogLevel.DEBUG, message, ...args);
241
- }
242
- info(message, ...args) {
243
- this.logWithContext(LogLevel.INFO, message, ...args);
244
- }
245
- warn(message, ...args) {
246
- this.logWithContext(LogLevel.WARN, message, ...args);
247
- }
248
- error(message, ...args) {
249
- this.logWithContext(LogLevel.ERROR, message, ...args);
250
- }
251
- fatal(message, ...args) {
252
- this.logWithContext(LogLevel.FATAL, message, ...args);
468
+ removeAllListeners(event) {
469
+ if (event) this.listeners.delete(event);
470
+ else this.listeners.clear();
253
471
  }
254
472
  };
255
-
256
- //#endregion
257
- //#region src/ActionRegister.ts
258
- var import_defineProperty = __toESM(require_defineProperty(), 1);
259
473
  /**
260
- * Core action pipeline management system
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
261
480
  * @template T - Action payload map defining available actions and their payload types
481
+ *
262
482
  * @example
263
483
  * ```typescript
264
484
  * interface AppActions extends ActionPayloadMap {
265
485
  * increment: void;
266
486
  * setCount: number;
487
+ * updateUser: { id: string; name: string };
267
488
  * }
268
- *
489
+ *
269
490
  * const actionRegister = new ActionRegister<AppActions>();
270
- *
271
- * // Register handlers
272
- * actionRegister.register('increment', () => console.log('Incremented'));
273
- * actionRegister.register('setCount', (count) => console.log(`Count: ${count}`));
274
- *
275
- * // Dispatch actions
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
276
504
  * await actionRegister.dispatch('increment');
277
505
  * await actionRegister.dispatch('setCount', 42);
278
506
  * ```
279
507
  */
280
508
  var ActionRegister = class {
281
- constructor(config) {
509
+ constructor(config = {}) {
282
510
  (0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
283
- (0, import_defineProperty.default)(this, "atomSetters", /* @__PURE__ */ new Map());
284
511
  (0, import_defineProperty.default)(this, "handlerCounter", 0);
285
512
  (0, import_defineProperty.default)(this, "logger", void 0);
286
- const envLogLevel = getLogLevelFromEnv();
287
- const configLogLevel = config?.logLevel ?? envLogLevel;
288
- if (config?.logger) this.logger = config.logger;
289
- else if (config?.useOtel) this.logger = new OtelConsoleLogger(configLogLevel);
290
- else this.logger = new ConsoleLogger(configLogLevel);
291
- if (config?.otelContext && this.logger instanceof OtelConsoleLogger) this.logger.setContext(config.otelContext);
292
- this.logger.debug("ActionRegister initialized", {
293
- logLevel: configLogLevel,
294
- useOtel: config?.useOtel ?? false,
295
- hasOtelContext: !!config?.otelContext
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 || (0, __context_action_logger.createLogger)(config.logLevel),
602
+ logLevel: config.logLevel ?? 3,
603
+ name: config.name || (0, __context_action_logger.getLoggerNameFromEnv)(),
604
+ debug: config.debug ?? (0, __context_action_logger.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
296
615
  });
616
+ this.logger.trace(`${this.config.name} constructor completed`);
297
617
  }
298
618
  /**
619
+ * Register action handler with pipeline
620
+ * @implements action-handler
621
+ *
299
622
  * Register a handler for an action in the pipeline
300
623
  * @param action - The action name to handle
301
624
  * @param handler - The handler function to execute
302
625
  * @param config - Optional configuration for the handler
303
626
  * @returns Unregister function to remove the handler
627
+ *
304
628
  * @example
305
629
  * ```typescript
306
- * const unregister = actionRegister.register('increment', () => {
307
- * console.log('Incremented!');
308
- * }, { priority: 10 });
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
+ * );
309
644
  *
310
645
  * // Later, remove the handler
311
646
  * unregister();
312
647
  * ```
313
648
  */
314
649
  register(action, handler, config = {}) {
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
+ } });
315
672
  if (!this.pipelines.has(action)) {
316
- this.pipelines.set(action, /* @__PURE__ */ new Map());
317
- this.logger.debug(`Created new pipeline for action: ${String(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)}`);
318
676
  }
319
677
  const pipeline = this.pipelines.get(action);
320
- const handlerId = config.id || `handler_${++this.handlerCounter}`;
321
- if (pipeline.has(handlerId)) {
322
- this.logger.warn(`Handler with id ${handlerId} already exists for action: ${String(action)}`);
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`);
323
682
  return () => {};
324
683
  }
325
- pipeline.set(handlerId, {
326
- handler,
327
- 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
328
696
  });
329
- this.logger.debug(`Registered handler for action: ${String(action)}`, {
697
+ this.events.emit("handler:register", {
698
+ action,
330
699
  handlerId,
331
- priority: config.priority ?? 0,
332
- blocking: config.blocking ?? false
700
+ config: registration.config
333
701
  });
334
- this.sortPipeline(action);
335
702
  return () => {
336
- pipeline.delete(handlerId);
337
- this.logger.debug(`Unregistered handler: ${handlerId} for action: ${String(action)}`);
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`);
338
714
  };
339
715
  }
340
- registerAtomSetter(name, setter) {
341
- this.atomSetters.set(name, setter);
342
- this.logger.debug(`Registered atom setter: ${name}`);
343
- }
344
716
  /**
345
- * Internal dispatch implementation
717
+ * Execute the pipeline with proper flow control
346
718
  * @internal
347
719
  */
348
- async dispatch(action, payload) {
349
- const pipeline = this.pipelines.get(action);
350
- if (this.logger instanceof OtelConsoleLogger && payload) {
351
- const otelContext = createOtelContextFromPayload(payload);
352
- if (otelContext.traceId || otelContext.sessionId) this.logger.setContext(otelContext);
353
- }
354
- this.logger.debug(`Dispatching action: ${String(action)}`, { payload });
355
- if (!pipeline || pipeline.size === 0) {
356
- this.logger.warn(`No handlers registered for action: ${String(action)}`);
357
- return;
358
- }
359
- let modifiedPayload = payload;
360
- const handlers = Array.from(pipeline.values());
361
- let shouldContinue = true;
362
- this.logger.trace(`Executing pipeline for action: ${String(action)}`, { handlerCount: handlers.length });
363
- for (const { handler, config } of handlers) {
364
- if (!shouldContinue) break;
365
- const controller = {
366
- next: () => {
367
- shouldContinue = true;
368
- },
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: () => {},
369
730
  abort: (reason) => {
370
- shouldContinue = false;
371
- this.logger.warn(`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 });
372
735
  },
373
736
  modifyPayload: (modifier) => {
374
- modifiedPayload = modifier(modifiedPayload);
375
- this.logger.trace(`Payload modified for action: ${String(action)}`);
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;
376
750
  }
377
751
  };
378
- try {
379
- if (config.blocking) await handler(modifiedPayload, controller);
380
- else handler(modifiedPayload, controller);
381
- } catch (error) {
382
- this.logger.error(`Error in pipeline handler for action: ${String(action)}`, error);
383
- if (config.blocking) throw error;
384
- }
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}`);
385
764
  }
386
- this.logger.debug(`Completed dispatching action: ${String(action)}`);
765
+ this.cleanupOneTimeHandlers(context.action, context.handlers);
387
766
  }
388
- sortPipeline(action) {
767
+ /**
768
+ * Clean up one-time handlers after pipeline execution
769
+ * @internal
770
+ */
771
+ cleanupOneTimeHandlers(action, executedHandlers) {
389
772
  const pipeline = this.pipelines.get(action);
390
773
  if (!pipeline) return;
391
- const sorted = Array.from(pipeline.entries()).sort(([, a], [, b]) => {
392
- const priorityA = a.config.priority ?? 0;
393
- const priorityB = b.config.priority ?? 0;
394
- 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
+ }
783
+ });
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
395
843
  });
396
- pipeline.clear();
397
- sorted.forEach(([id, data]) => pipeline.set(id, data));
398
- this.logger.trace(`Sorted pipeline for action: ${String(action)}`, {
399
- handlerCount: sorted.length,
400
- priorities: sorted.map(([, data]) => data.config.priority ?? 0)
844
+ this.pipelines.clear();
845
+ this.events.removeAllListeners();
846
+ this.logger.info(`Cleared all handlers`, {
847
+ actionCount,
848
+ totalHandlers
401
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;
402
882
  }
403
883
  };
404
884
 
405
885
  //#endregion
406
- //#region src/types.ts
407
- function createAction(type, payload) {
408
- return {
409
- type,
410
- payload
411
- };
412
- }
413
- function isAction(action, type) {
414
- return action?.type === type;
415
- }
416
-
417
- //#endregion
886
+ exports.ActionGuard = ActionGuard;
418
887
  exports.ActionRegister = ActionRegister;
419
- exports.ConsoleLogger = ConsoleLogger;
420
- exports.LogLevel = LogLevel;
421
- exports.OtelConsoleLogger = OtelConsoleLogger;
422
- exports.createAction = createAction;
423
- exports.createOtelContextFromPayload = createOtelContextFromPayload;
424
- exports.extractSessionIdFromPayload = extractSessionIdFromPayload;
425
- exports.extractTraceIdFromPayload = extractTraceIdFromPayload;
426
- exports.getLogLevelFromEnv = getLogLevelFromEnv;
427
- exports.isAction = isAction;
428
- exports.parseLogLevel = parseLogLevel;
888
+ exports.executeParallel = executeParallel;
889
+ exports.executeRace = executeRace;
890
+ exports.executeSequential = executeSequential;