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