@context-action/core 0.0.4 → 0.1.0
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 +675 -492
- package/dist/index.d.cts +147 -589
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +147 -589
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +675 -493
- package/dist/index.js.map +1 -1
- package/package.json +1 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createLogger, getDebugFromEnv, getLoggerNameFromEnv } from "@context-action/logger";
|
|
2
|
-
|
|
3
1
|
//#region rolldown:runtime
|
|
4
2
|
var __create = Object.create;
|
|
5
3
|
var __defProp = Object.defineProperty;
|
|
@@ -29,111 +27,87 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
27
|
//#region src/execution-modes.ts
|
|
30
28
|
/**
|
|
31
29
|
* 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
30
|
*/
|
|
54
|
-
async function executeSequential(context, createController
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
}
|
|
31
|
+
async function executeSequential(context, createController) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < context.handlers.length) {
|
|
34
|
+
if (context.aborted || context.terminated) break;
|
|
78
35
|
const registration = context.handlers[i];
|
|
79
36
|
context.currentIndex = i;
|
|
37
|
+
/** Check condition if provided */
|
|
80
38
|
if (registration.config.condition && !registration.config.condition()) {
|
|
81
|
-
|
|
39
|
+
i++;
|
|
82
40
|
continue;
|
|
83
41
|
}
|
|
42
|
+
/** Check validation if provided */
|
|
84
43
|
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
85
|
-
|
|
44
|
+
i++;
|
|
86
45
|
continue;
|
|
87
46
|
}
|
|
88
47
|
const controller = createController(registration, i);
|
|
89
48
|
try {
|
|
90
|
-
|
|
91
|
-
handlerId: registration.id,
|
|
92
|
-
priority: registration.config.priority
|
|
93
|
-
});
|
|
49
|
+
if (context.aborted) break;
|
|
94
50
|
const result = registration.handler(context.payload, controller);
|
|
51
|
+
/** Wait for async handlers if they're blocking */
|
|
95
52
|
if (registration.config.blocking && result instanceof Promise) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
53
|
+
const handlerResult = await result;
|
|
54
|
+
/** Collect result if handler returned something and wasn't terminated */
|
|
55
|
+
if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
|
|
56
|
+
} else if (result !== void 0 && !context.terminated)
|
|
57
|
+
/** Collect synchronous result */
|
|
58
|
+
if (result instanceof Promise) result.then((asyncResult) => {
|
|
59
|
+
if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
|
|
60
|
+
}).catch(() => {});
|
|
61
|
+
else context.results.push(result);
|
|
62
|
+
/** Check if pipeline was terminated by controller.return() */
|
|
63
|
+
if (context.terminated) break;
|
|
64
|
+
/** Handle jump to priority AFTER handler execution */
|
|
65
|
+
if (context.jumpToPriority !== void 0) {
|
|
66
|
+
const jumpIndex = context.handlers.findIndex((handler) => handler.config.priority === context.jumpToPriority);
|
|
67
|
+
if (jumpIndex !== -1) {
|
|
68
|
+
i = jumpIndex;
|
|
69
|
+
context.jumpToPriority = void 0;
|
|
70
|
+
continue;
|
|
71
|
+
} else {
|
|
72
|
+
context.jumpToPriority = void 0;
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
} else i++;
|
|
100
76
|
} catch (error) {
|
|
101
|
-
logger.error(`Handler '${registration.id}' threw an error`, error);
|
|
102
77
|
if (registration.config.blocking) throw error;
|
|
78
|
+
i++;
|
|
103
79
|
}
|
|
104
80
|
}
|
|
105
81
|
}
|
|
106
82
|
/**
|
|
107
83
|
* Execute handlers in parallel mode (all at once)
|
|
108
|
-
* @internal
|
|
109
84
|
*/
|
|
110
|
-
async function executeParallel(context, createController
|
|
111
|
-
|
|
85
|
+
async function executeParallel(context, createController) {
|
|
86
|
+
/** Filter handlers that should run */
|
|
112
87
|
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
118
|
-
logger.debug(`Skipping handler '${registration.id}' - validation failed`);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
88
|
+
/** Check condition */
|
|
89
|
+
if (registration.config.condition && !registration.config.condition()) return false;
|
|
90
|
+
/** Check validation */
|
|
91
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) return false;
|
|
121
92
|
return true;
|
|
122
93
|
});
|
|
123
|
-
|
|
94
|
+
/** Create promises for all handlers */
|
|
124
95
|
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
125
96
|
const controller = createController(registration, _index);
|
|
126
97
|
try {
|
|
127
|
-
logger.trace(`Starting parallel handler '${registration.id}'`);
|
|
128
98
|
const result = registration.handler(context.payload, controller);
|
|
129
|
-
|
|
130
|
-
|
|
99
|
+
let handlerResult;
|
|
100
|
+
if (result instanceof Promise) handlerResult = await result;
|
|
101
|
+
else handlerResult = result;
|
|
102
|
+
/** Collect result if handler returned something and pipeline wasn't terminated */
|
|
103
|
+
if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
|
|
131
104
|
return {
|
|
132
105
|
success: true,
|
|
133
|
-
handlerId: registration.id
|
|
106
|
+
handlerId: registration.id,
|
|
107
|
+
result: handlerResult,
|
|
108
|
+
terminated: context.terminated
|
|
134
109
|
};
|
|
135
110
|
} catch (error) {
|
|
136
|
-
logger.error(`Parallel handler '${registration.id}' failed`, error);
|
|
137
111
|
if (registration.config.blocking) throw error;
|
|
138
112
|
return {
|
|
139
113
|
success: false,
|
|
@@ -142,7 +116,9 @@ async function executeParallel(context, createController, logger) {
|
|
|
142
116
|
};
|
|
143
117
|
}
|
|
144
118
|
});
|
|
119
|
+
/** Wait for all handlers to complete */
|
|
145
120
|
const results = await Promise.allSettled(handlerPromises);
|
|
121
|
+
/** Check for any rejected blocking handlers */
|
|
146
122
|
const failures = results.filter((result, index) => {
|
|
147
123
|
if (result.status === "rejected") {
|
|
148
124
|
const registration = runnableHandlers[index];
|
|
@@ -154,47 +130,43 @@ async function executeParallel(context, createController, logger) {
|
|
|
154
130
|
const firstFailure = failures[0];
|
|
155
131
|
throw firstFailure.reason;
|
|
156
132
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
133
|
+
/** Check if any handler terminated the pipeline */
|
|
134
|
+
const terminatedResults = results.filter((result) => result.status === "fulfilled" && result.value.terminated);
|
|
135
|
+
if (terminatedResults.length > 0) {
|
|
136
|
+
context.terminated = true;
|
|
137
|
+
const firstTerminated = terminatedResults[0];
|
|
138
|
+
context.terminationResult = firstTerminated.value.result;
|
|
139
|
+
}
|
|
161
140
|
}
|
|
162
141
|
/**
|
|
163
142
|
* Execute handlers in race mode (first to complete wins)
|
|
164
|
-
* @internal
|
|
165
143
|
*/
|
|
166
|
-
async function executeRace(context, createController
|
|
167
|
-
|
|
144
|
+
async function executeRace(context, createController) {
|
|
145
|
+
/** Filter handlers that should run */
|
|
168
146
|
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
174
|
-
logger.debug(`Skipping handler '${registration.id}' - validation failed`);
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
147
|
+
/** Check condition */
|
|
148
|
+
if (registration.config.condition && !registration.config.condition()) return false;
|
|
149
|
+
/** Check validation */
|
|
150
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) return false;
|
|
177
151
|
return true;
|
|
178
152
|
});
|
|
179
|
-
if (runnableHandlers.length === 0)
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
logger.trace(`Racing ${runnableHandlers.length} handlers`);
|
|
153
|
+
if (runnableHandlers.length === 0) return;
|
|
154
|
+
/** Create promises for all handlers */
|
|
184
155
|
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
185
156
|
const controller = createController(registration, _index);
|
|
186
157
|
try {
|
|
187
|
-
logger.trace(`Starting race handler '${registration.id}'`);
|
|
188
158
|
const result = registration.handler(context.payload, controller);
|
|
189
|
-
|
|
190
|
-
|
|
159
|
+
let handlerResult;
|
|
160
|
+
if (result instanceof Promise) handlerResult = await result;
|
|
161
|
+
else handlerResult = result;
|
|
191
162
|
return {
|
|
192
163
|
success: true,
|
|
193
164
|
handlerId: registration.id,
|
|
194
|
-
registration
|
|
165
|
+
registration,
|
|
166
|
+
result: handlerResult,
|
|
167
|
+
terminated: context.terminated
|
|
195
168
|
};
|
|
196
169
|
} catch (error) {
|
|
197
|
-
logger.error(`Race handler '${registration.id}' failed`, error);
|
|
198
170
|
return {
|
|
199
171
|
success: false,
|
|
200
172
|
handlerId: registration.id,
|
|
@@ -203,22 +175,22 @@ async function executeRace(context, createController, logger) {
|
|
|
203
175
|
};
|
|
204
176
|
}
|
|
205
177
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
178
|
+
/** Race all handlers */
|
|
179
|
+
const winner = await Promise.race(handlerPromises);
|
|
180
|
+
/** If the winner failed and was blocking, throw the error */
|
|
181
|
+
if (!winner.success && winner.registration?.config.blocking) throw winner.error;
|
|
182
|
+
/** Collect result from the winning handler */
|
|
183
|
+
if (winner.success && winner.result !== void 0) context.results.push(winner.result);
|
|
184
|
+
/** Check if the winning handler terminated the pipeline */
|
|
185
|
+
if (winner.success && winner.terminated) {
|
|
186
|
+
context.terminated = true;
|
|
187
|
+
context.terminationResult = winner.result;
|
|
216
188
|
}
|
|
217
189
|
}
|
|
218
190
|
|
|
219
191
|
//#endregion
|
|
220
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
221
|
-
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
192
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
193
|
+
var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/typeof.js": ((exports, module) => {
|
|
222
194
|
function _typeof$2(o) {
|
|
223
195
|
"@babel/helpers - typeof";
|
|
224
196
|
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
@@ -228,11 +200,11 @@ var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime
|
|
|
228
200
|
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
|
|
229
201
|
}
|
|
230
202
|
module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
231
|
-
} });
|
|
203
|
+
}) });
|
|
232
204
|
|
|
233
205
|
//#endregion
|
|
234
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
235
|
-
var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
206
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
|
|
207
|
+
var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js": ((exports, module) => {
|
|
236
208
|
var _typeof$1 = require_typeof()["default"];
|
|
237
209
|
function toPrimitive$1(t, r) {
|
|
238
210
|
if ("object" != _typeof$1(t) || !t) return t;
|
|
@@ -245,11 +217,11 @@ var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+ru
|
|
|
245
217
|
return ("string" === r ? String : Number)(t);
|
|
246
218
|
}
|
|
247
219
|
module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
248
|
-
} });
|
|
220
|
+
}) });
|
|
249
221
|
|
|
250
222
|
//#endregion
|
|
251
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
252
|
-
var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
223
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
|
|
224
|
+
var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js": ((exports, module) => {
|
|
253
225
|
var _typeof = require_typeof()["default"];
|
|
254
226
|
var toPrimitive = require_toPrimitive();
|
|
255
227
|
function toPropertyKey$1(t) {
|
|
@@ -257,11 +229,11 @@ var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
|
|
|
257
229
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
258
230
|
}
|
|
259
231
|
module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
260
|
-
} });
|
|
232
|
+
}) });
|
|
261
233
|
|
|
262
234
|
//#endregion
|
|
263
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
264
|
-
var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
235
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
|
|
236
|
+
var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js": ((exports, module) => {
|
|
265
237
|
var toPropertyKey = require_toPropertyKey();
|
|
266
238
|
function _defineProperty$2(e, r, t) {
|
|
267
239
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
@@ -272,29 +244,23 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
|
|
|
272
244
|
}) : e[r] = t, e;
|
|
273
245
|
}
|
|
274
246
|
module.exports = _defineProperty$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
275
|
-
} });
|
|
247
|
+
}) });
|
|
276
248
|
|
|
277
249
|
//#endregion
|
|
278
250
|
//#region src/action-guard.ts
|
|
279
|
-
var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
|
|
251
|
+
var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
280
252
|
/**
|
|
281
253
|
* Action Guard system for managing action execution timing
|
|
282
254
|
* @implements action-guard
|
|
283
255
|
* @implements performance-optimization
|
|
284
256
|
* @implements user-experience-optimization
|
|
257
|
+
* @implements class-naming
|
|
285
258
|
* @memberof core-concepts
|
|
286
259
|
* @internal
|
|
287
260
|
* @since 1.0.0
|
|
288
261
|
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
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
|
|
262
|
+
* Manages action execution timing through debouncing and throttling
|
|
263
|
+
* @implements performance-optimization
|
|
298
264
|
*
|
|
299
265
|
* @example
|
|
300
266
|
* ```typescript
|
|
@@ -312,10 +278,8 @@ var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
|
|
|
312
278
|
* ```
|
|
313
279
|
*/
|
|
314
280
|
var ActionGuard = class {
|
|
315
|
-
constructor(
|
|
281
|
+
constructor() {
|
|
316
282
|
(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
283
|
}
|
|
320
284
|
/**
|
|
321
285
|
* Check if action should be debounced
|
|
@@ -324,27 +288,29 @@ var ActionGuard = class {
|
|
|
324
288
|
* @returns Promise that resolves when debounce period is complete
|
|
325
289
|
*/
|
|
326
290
|
async debounce(actionKey, debounceMs) {
|
|
327
|
-
|
|
291
|
+
/** Get or create guard state for this action */
|
|
328
292
|
let state = this.guards.get(actionKey);
|
|
329
293
|
if (!state) {
|
|
294
|
+
/** Initialize new guard state with default values */
|
|
330
295
|
state = {
|
|
331
296
|
lastExecuted: 0,
|
|
332
297
|
isThrottled: false
|
|
333
298
|
};
|
|
334
299
|
this.guards.set(actionKey, state);
|
|
335
300
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
301
|
+
/** Clear any existing debounce timer to restart the delay period */
|
|
302
|
+
/** This implements the "debounce" behavior where rapid calls reset the timer */
|
|
303
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
304
|
+
/** Create new debounce promise that resolves after the delay period */
|
|
305
|
+
/** The promise will only resolve if no new debounce requests arrive */
|
|
340
306
|
return new Promise((resolve) => {
|
|
341
307
|
state.debounceTimer = setTimeout(() => {
|
|
342
|
-
|
|
308
|
+
/** Clean up timer reference to prevent memory leaks */
|
|
343
309
|
state.debounceTimer = void 0;
|
|
310
|
+
/** Update last execution timestamp for throttling calculations */
|
|
344
311
|
state.lastExecuted = Date.now();
|
|
345
312
|
resolve(true);
|
|
346
313
|
}, debounceMs);
|
|
347
|
-
this.logger.trace(`Set debounce timer for '${actionKey}'`, { delay: debounceMs });
|
|
348
314
|
});
|
|
349
315
|
}
|
|
350
316
|
/**
|
|
@@ -354,9 +320,10 @@ var ActionGuard = class {
|
|
|
354
320
|
* @returns True if action should proceed, false if throttled
|
|
355
321
|
*/
|
|
356
322
|
throttle(actionKey, throttleMs) {
|
|
357
|
-
|
|
323
|
+
/** Get or create guard state for this action */
|
|
358
324
|
let state = this.guards.get(actionKey);
|
|
359
325
|
if (!state) {
|
|
326
|
+
/** Initialize new guard state with default values */
|
|
360
327
|
state = {
|
|
361
328
|
lastExecuted: 0,
|
|
362
329
|
isThrottled: false
|
|
@@ -365,30 +332,27 @@ var ActionGuard = class {
|
|
|
365
332
|
}
|
|
366
333
|
const now = Date.now();
|
|
367
334
|
const timeSinceLastExecution = now - state.lastExecuted;
|
|
335
|
+
/** Check if enough time has passed since last execution */
|
|
336
|
+
/** If throttle period has elapsed, allow immediate execution */
|
|
368
337
|
if (timeSinceLastExecution >= throttleMs) {
|
|
338
|
+
/** Update execution timestamp and clear throttled state */
|
|
369
339
|
state.lastExecuted = now;
|
|
370
340
|
state.isThrottled = false;
|
|
371
|
-
this.logger.trace(`Throttle passed for '${actionKey}'`, {
|
|
372
|
-
timeSinceLastExecution,
|
|
373
|
-
throttleMs
|
|
374
|
-
});
|
|
375
341
|
return true;
|
|
376
342
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
343
|
+
/** If already in throttled state, don't create duplicate timers */
|
|
344
|
+
/** This prevents timer accumulation and unnecessary processing */
|
|
345
|
+
if (state.isThrottled) return false;
|
|
346
|
+
/** Set throttle timer to automatically clear the throttled state */
|
|
347
|
+
/** Calculate remaining time until throttle period expires */
|
|
381
348
|
state.isThrottled = true;
|
|
382
349
|
const remainingTime = throttleMs - timeSinceLastExecution;
|
|
350
|
+
/** Create timer to reset throttled state when period expires */
|
|
383
351
|
state.throttleTimer = setTimeout(() => {
|
|
352
|
+
/** Clear throttled state and timer reference */
|
|
384
353
|
state.isThrottled = false;
|
|
385
354
|
state.throttleTimer = void 0;
|
|
386
|
-
this.logger.trace(`Throttle period ended for '${actionKey}'`);
|
|
387
355
|
}, remainingTime);
|
|
388
|
-
this.logger.trace(`Action '${actionKey}' throttled`, {
|
|
389
|
-
timeSinceLastExecution,
|
|
390
|
-
remainingTime
|
|
391
|
-
});
|
|
392
356
|
return false;
|
|
393
357
|
}
|
|
394
358
|
/**
|
|
@@ -396,26 +360,30 @@ var ActionGuard = class {
|
|
|
396
360
|
* @param actionKey - Action key to clear
|
|
397
361
|
*/
|
|
398
362
|
clearGuards(actionKey) {
|
|
399
|
-
this.logger.trace(`Clearing guards for '${actionKey}'`);
|
|
400
363
|
const state = this.guards.get(actionKey);
|
|
401
364
|
if (state) {
|
|
365
|
+
/** Clear debounce timer if active to prevent memory leaks */
|
|
402
366
|
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
367
|
+
/** Clear throttle timer if active to prevent memory leaks */
|
|
403
368
|
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
369
|
+
/** Remove guard state from memory */
|
|
404
370
|
this.guards.delete(actionKey);
|
|
405
|
-
this.logger.debug(`Cleared guards for '${actionKey}'`);
|
|
406
371
|
}
|
|
407
372
|
}
|
|
408
373
|
/**
|
|
409
374
|
* Clear all guards
|
|
410
375
|
*/
|
|
411
376
|
clearAll() {
|
|
412
|
-
|
|
377
|
+
/** Iterate through all guard states and clear their timers */
|
|
378
|
+
/** This prevents memory leaks when clearing the entire guard system */
|
|
413
379
|
for (const [, state] of this.guards) {
|
|
380
|
+
/** Clear any active debounce timers */
|
|
414
381
|
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
382
|
+
/** Clear any active throttle timers */
|
|
415
383
|
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
416
384
|
}
|
|
385
|
+
/** Remove all guard states from memory */
|
|
417
386
|
this.guards.clear();
|
|
418
|
-
this.logger.debug("Cleared all action guards");
|
|
419
387
|
}
|
|
420
388
|
/**
|
|
421
389
|
* Get current guard state for debugging
|
|
@@ -434,222 +402,58 @@ var ActionGuard = class {
|
|
|
434
402
|
|
|
435
403
|
//#endregion
|
|
436
404
|
//#region src/ActionRegister.ts
|
|
437
|
-
var import_defineProperty = __toESM(require_defineProperty(), 1);
|
|
438
|
-
/**
|
|
439
|
-
* Simple event emitter implementation for ActionRegister events
|
|
440
|
-
* @internal
|
|
441
|
-
*/
|
|
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
|
-
});
|
|
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
|
-
};
|
|
405
|
+
var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
473
406
|
/**
|
|
474
|
-
*
|
|
475
|
-
* @implements action-pipeline-system
|
|
476
|
-
* @implements actionregister
|
|
477
|
-
* @memberof core-concepts
|
|
407
|
+
* 중앙화된 액션 등록 및 디스패치 시스템으로, 타입 안전한 액션 파이프라인 관리를 제공하는 핵심 클래스입니다.
|
|
478
408
|
*
|
|
479
|
-
*
|
|
480
|
-
* @
|
|
409
|
+
* @implements {ActionRegister}
|
|
410
|
+
* @implements {Action Pipeline System}
|
|
411
|
+
* @memberof core-concepts
|
|
481
412
|
*
|
|
482
413
|
* @example
|
|
483
414
|
* ```typescript
|
|
484
415
|
* interface AppActions extends ActionPayloadMap {
|
|
485
|
-
* increment: void;
|
|
486
|
-
* setCount: number;
|
|
487
416
|
* updateUser: { id: string; name: string };
|
|
417
|
+
* calculateTotal: void;
|
|
488
418
|
* }
|
|
489
|
-
*
|
|
490
|
-
* const
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
419
|
+
*
|
|
420
|
+
* const register = new ActionRegister<AppActions>({
|
|
421
|
+
* name: 'AppRegister',
|
|
422
|
+
* logLevel: LogLevel.DEBUG
|
|
423
|
+
* });
|
|
424
|
+
*
|
|
425
|
+
* // 핸들러 등록
|
|
426
|
+
* register.register('updateUser', ({ id, name }, controller) => {
|
|
427
|
+
* userStore.setValue({ id, name });
|
|
495
428
|
* controller.next();
|
|
496
429
|
* }, { priority: 10 });
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* controller.next();
|
|
501
|
-
* });
|
|
502
|
-
*
|
|
503
|
-
* // Dispatch actions with type safety
|
|
504
|
-
* await actionRegister.dispatch('increment');
|
|
505
|
-
* await actionRegister.dispatch('setCount', 42);
|
|
430
|
+
*
|
|
431
|
+
* // 액션 디스패치
|
|
432
|
+
* await register.dispatch('updateUser', { id: '1', name: 'John' });
|
|
506
433
|
* ```
|
|
507
434
|
*/
|
|
508
435
|
var ActionRegister = class {
|
|
509
436
|
constructor(config = {}) {
|
|
510
437
|
(0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
|
|
511
438
|
(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
439
|
(0, import_defineProperty.default)(this, "actionGuard", void 0);
|
|
516
440
|
(0, import_defineProperty.default)(this, "executionMode", "sequential");
|
|
517
441
|
(0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
|
|
518
|
-
(0, import_defineProperty.default)(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
|
442
|
+
(0, import_defineProperty.default)(this, "name", void 0);
|
|
443
|
+
(0, import_defineProperty.default)(this, "registryConfig", void 0);
|
|
444
|
+
(0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
|
|
445
|
+
this.name = config.name || "ActionRegister";
|
|
446
|
+
this.registryConfig = config.registry;
|
|
447
|
+
this.actionGuard = new ActionGuard();
|
|
448
|
+
if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
|
|
449
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 ActionRegister created: ${this.name}`, {
|
|
450
|
+
defaultExecutionMode: this.executionMode,
|
|
451
|
+
maxHandlers: this.registryConfig.maxHandlers,
|
|
452
|
+
autoCleanup: this.registryConfig.autoCleanup ?? true
|
|
615
453
|
});
|
|
616
|
-
this.logger.trace(`${this.config.name} constructor completed`);
|
|
617
454
|
}
|
|
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
|
-
*/
|
|
649
455
|
register(action, handler, config = {}) {
|
|
650
|
-
|
|
651
|
-
const handlerId = config.id || `handler_${++this.handlerCounter}`;
|
|
652
|
-
this.logger.trace(`Generated handler ID: ${handlerId}`);
|
|
456
|
+
const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
|
|
653
457
|
const registration = {
|
|
654
458
|
handler,
|
|
655
459
|
config: {
|
|
@@ -658,227 +462,605 @@ var ActionRegister = class {
|
|
|
658
462
|
blocking: config.blocking ?? false,
|
|
659
463
|
once: config.once ?? false,
|
|
660
464
|
condition: config.condition || (() => true),
|
|
661
|
-
debounce: config.debounce,
|
|
662
|
-
throttle: config.throttle,
|
|
663
|
-
validation: config.validation,
|
|
664
|
-
middleware: config.middleware ?? false
|
|
465
|
+
debounce: config.debounce ?? void 0,
|
|
466
|
+
throttle: config.throttle ?? void 0,
|
|
467
|
+
validation: config.validation ?? void 0,
|
|
468
|
+
middleware: config.middleware ?? false,
|
|
469
|
+
tags: config.tags ?? [],
|
|
470
|
+
category: config.category ?? void 0,
|
|
471
|
+
description: config.description ?? void 0,
|
|
472
|
+
version: config.version ?? void 0,
|
|
473
|
+
returnType: config.returnType ?? "value",
|
|
474
|
+
timeout: config.timeout ?? void 0,
|
|
475
|
+
retries: config.retries ?? 0,
|
|
476
|
+
dependencies: config.dependencies ?? [],
|
|
477
|
+
conflicts: config.conflicts ?? [],
|
|
478
|
+
environment: config.environment ?? void 0,
|
|
479
|
+
feature: config.feature ?? void 0,
|
|
480
|
+
metrics: config.metrics ?? {
|
|
481
|
+
collectTiming: false,
|
|
482
|
+
collectErrors: false,
|
|
483
|
+
customMetrics: {}
|
|
484
|
+
},
|
|
485
|
+
metadata: config.metadata ?? {}
|
|
665
486
|
},
|
|
666
487
|
id: handlerId
|
|
667
488
|
};
|
|
668
|
-
this.
|
|
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
|
-
}
|
|
489
|
+
if (!this.pipelines.has(action)) this.pipelines.set(action, []);
|
|
677
490
|
const pipeline = this.pipelines.get(action);
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
this.logger.trace(`Duplicate handler registration aborted`);
|
|
682
|
-
return () => {};
|
|
683
|
-
}
|
|
491
|
+
const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
|
|
492
|
+
if (existingIndex !== -1) return () => {};
|
|
493
|
+
if (this.registryConfig?.maxHandlers && pipeline.length >= this.registryConfig.maxHandlers) throw new Error(`Maximum number of handlers (${this.registryConfig.maxHandlers}) reached for action '${String(action)}' in registry '${this.name}'`);
|
|
684
494
|
pipeline.push(registration);
|
|
685
|
-
this.logger.trace(`Added handler to pipeline, current length: ${pipeline.length}`);
|
|
686
495
|
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
687
|
-
this.
|
|
688
|
-
id: reg.id,
|
|
689
|
-
priority: reg.config.priority
|
|
690
|
-
})) });
|
|
691
|
-
this.logger.debug(`Registered handler for action '${String(action)}'`, {
|
|
496
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
|
|
692
497
|
handlerId,
|
|
693
|
-
priority:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
action,
|
|
699
|
-
handlerId,
|
|
700
|
-
config: registration.config
|
|
498
|
+
priority: config.priority,
|
|
499
|
+
tags: config.tags,
|
|
500
|
+
category: config.category,
|
|
501
|
+
totalHandlers: pipeline.length,
|
|
502
|
+
registry: this.name
|
|
701
503
|
});
|
|
702
504
|
return () => {
|
|
703
|
-
|
|
704
|
-
const index = pipeline.findIndex((reg) => reg.id === handlerId);
|
|
505
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
705
506
|
if (index !== -1) {
|
|
706
507
|
pipeline.splice(index, 1);
|
|
707
|
-
this.
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
handlerId
|
|
508
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
|
|
509
|
+
handlerId,
|
|
510
|
+
remainingHandlers: pipeline.length,
|
|
511
|
+
registry: this.name
|
|
712
512
|
});
|
|
713
|
-
}
|
|
513
|
+
}
|
|
714
514
|
};
|
|
715
515
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
516
|
+
async dispatch(action, payload, options) {
|
|
517
|
+
let autoAbortController;
|
|
518
|
+
let effectiveSignal = options?.signal;
|
|
519
|
+
if (options?.autoAbort?.enabled) {
|
|
520
|
+
autoAbortController = new AbortController();
|
|
521
|
+
effectiveSignal = autoAbortController.signal;
|
|
522
|
+
if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
|
|
523
|
+
if (options?.signal) {
|
|
524
|
+
const originalSignal = options.signal;
|
|
525
|
+
if (originalSignal.aborted) autoAbortController.abort();
|
|
526
|
+
else {
|
|
527
|
+
const abortHandler$1 = () => autoAbortController.abort();
|
|
528
|
+
originalSignal.addEventListener("abort", abortHandler$1, { once: true });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (effectiveSignal?.aborted) return;
|
|
533
|
+
const pipeline = this.pipelines.get(action);
|
|
534
|
+
if (!pipeline || pipeline.length === 0) return;
|
|
535
|
+
const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
|
|
536
|
+
const actionKey = String(action);
|
|
537
|
+
let throttleMs;
|
|
538
|
+
let debounceMs;
|
|
539
|
+
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
540
|
+
else if (filteredHandlers.length > 0) {
|
|
541
|
+
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
542
|
+
throttleMs = handler.config.throttle;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
547
|
+
else if (filteredHandlers.length > 0) {
|
|
548
|
+
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
549
|
+
debounceMs = handler.config.debounce;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (debounceMs !== void 0) {
|
|
554
|
+
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
555
|
+
if (!shouldProceed) return;
|
|
556
|
+
}
|
|
557
|
+
if (throttleMs !== void 0) {
|
|
558
|
+
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
559
|
+
if (!shouldProceed) return;
|
|
560
|
+
}
|
|
561
|
+
const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
|
|
562
|
+
const context = {
|
|
563
|
+
action: String(action),
|
|
564
|
+
payload,
|
|
565
|
+
handlers: filteredHandlers,
|
|
566
|
+
aborted: false,
|
|
567
|
+
abortReason: void 0,
|
|
568
|
+
currentIndex: 0,
|
|
569
|
+
jumpToPriority: void 0,
|
|
570
|
+
executionMode: currentExecutionMode,
|
|
571
|
+
results: [],
|
|
572
|
+
terminated: false,
|
|
573
|
+
terminationResult: void 0
|
|
574
|
+
};
|
|
575
|
+
const startTime = Date.now();
|
|
576
|
+
let executionSuccess = true;
|
|
577
|
+
const abortHandler = effectiveSignal ? () => {
|
|
578
|
+
context.aborted = true;
|
|
579
|
+
context.abortReason = "Action dispatch aborted by signal";
|
|
580
|
+
} : void 0;
|
|
581
|
+
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
582
|
+
try {
|
|
583
|
+
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
executionSuccess = false;
|
|
586
|
+
throw error;
|
|
587
|
+
} finally {
|
|
588
|
+
if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
|
|
589
|
+
const duration = Date.now() - startTime;
|
|
590
|
+
this.updateExecutionStats(action, executionSuccess, duration);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async dispatchWithResult(action, payload, options) {
|
|
594
|
+
const startTime = Date.now();
|
|
595
|
+
let autoAbortController;
|
|
596
|
+
let effectiveSignal = options?.signal;
|
|
597
|
+
if (options?.autoAbort?.enabled) {
|
|
598
|
+
autoAbortController = new AbortController();
|
|
599
|
+
effectiveSignal = autoAbortController.signal;
|
|
600
|
+
if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
|
|
601
|
+
if (options?.signal) {
|
|
602
|
+
const originalSignal = options.signal;
|
|
603
|
+
if (originalSignal.aborted) autoAbortController.abort();
|
|
604
|
+
else {
|
|
605
|
+
const abortHandler$1 = () => autoAbortController.abort();
|
|
606
|
+
originalSignal.addEventListener("abort", abortHandler$1, { once: true });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (effectiveSignal?.aborted) return {
|
|
611
|
+
success: false,
|
|
612
|
+
aborted: true,
|
|
613
|
+
abortReason: "Action dispatch aborted by signal",
|
|
614
|
+
terminated: false,
|
|
615
|
+
result: void 0,
|
|
616
|
+
results: [],
|
|
617
|
+
execution: {
|
|
618
|
+
duration: 0,
|
|
619
|
+
handlersExecuted: 0,
|
|
620
|
+
handlersSkipped: 0,
|
|
621
|
+
handlersFailed: 0,
|
|
622
|
+
startTime,
|
|
623
|
+
endTime: startTime
|
|
624
|
+
},
|
|
625
|
+
handlers: [],
|
|
626
|
+
errors: []
|
|
627
|
+
};
|
|
628
|
+
const pipeline = this.pipelines.get(action);
|
|
629
|
+
if (!pipeline || pipeline.length === 0) return {
|
|
630
|
+
success: true,
|
|
631
|
+
aborted: false,
|
|
632
|
+
terminated: false,
|
|
633
|
+
result: void 0,
|
|
634
|
+
results: [],
|
|
635
|
+
execution: {
|
|
636
|
+
duration: 0,
|
|
637
|
+
handlersExecuted: 0,
|
|
638
|
+
handlersSkipped: 0,
|
|
639
|
+
handlersFailed: 0,
|
|
640
|
+
startTime,
|
|
641
|
+
endTime: startTime
|
|
642
|
+
},
|
|
643
|
+
handlers: [],
|
|
644
|
+
errors: []
|
|
645
|
+
};
|
|
646
|
+
const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
|
|
647
|
+
const actionKey = String(action);
|
|
648
|
+
let throttleMs;
|
|
649
|
+
let debounceMs;
|
|
650
|
+
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
651
|
+
else if (filteredHandlers.length > 0) {
|
|
652
|
+
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
653
|
+
throttleMs = handler.config.throttle;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
658
|
+
else if (filteredHandlers.length > 0) {
|
|
659
|
+
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
660
|
+
debounceMs = handler.config.debounce;
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (debounceMs !== void 0) {
|
|
665
|
+
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
666
|
+
if (!shouldProceed) return {
|
|
667
|
+
success: false,
|
|
668
|
+
aborted: true,
|
|
669
|
+
abortReason: "Debounced execution",
|
|
670
|
+
terminated: false,
|
|
671
|
+
result: void 0,
|
|
672
|
+
results: [],
|
|
673
|
+
execution: {
|
|
674
|
+
duration: Date.now() - startTime,
|
|
675
|
+
handlersExecuted: 0,
|
|
676
|
+
handlersSkipped: pipeline.length,
|
|
677
|
+
handlersFailed: 0,
|
|
678
|
+
startTime,
|
|
679
|
+
endTime: Date.now()
|
|
680
|
+
},
|
|
681
|
+
handlers: [],
|
|
682
|
+
errors: []
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
if (throttleMs !== void 0) {
|
|
686
|
+
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
687
|
+
if (!shouldProceed) return {
|
|
688
|
+
success: false,
|
|
689
|
+
aborted: true,
|
|
690
|
+
abortReason: "Throttled execution",
|
|
691
|
+
terminated: false,
|
|
692
|
+
result: void 0,
|
|
693
|
+
results: [],
|
|
694
|
+
execution: {
|
|
695
|
+
duration: Date.now() - startTime,
|
|
696
|
+
handlersExecuted: 0,
|
|
697
|
+
handlersSkipped: pipeline.length,
|
|
698
|
+
handlersFailed: 0,
|
|
699
|
+
startTime,
|
|
700
|
+
endTime: Date.now()
|
|
701
|
+
},
|
|
702
|
+
handlers: [],
|
|
703
|
+
errors: []
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
|
|
707
|
+
const context = {
|
|
708
|
+
action: String(action),
|
|
709
|
+
payload,
|
|
710
|
+
handlers: filteredHandlers,
|
|
711
|
+
aborted: false,
|
|
712
|
+
abortReason: void 0,
|
|
713
|
+
currentIndex: 0,
|
|
714
|
+
jumpToPriority: void 0,
|
|
715
|
+
executionMode: currentExecutionMode,
|
|
716
|
+
results: [],
|
|
717
|
+
terminated: false,
|
|
718
|
+
terminationResult: void 0
|
|
719
|
+
};
|
|
720
|
+
let executionError;
|
|
721
|
+
const handlerResults = [];
|
|
722
|
+
const errors = [];
|
|
723
|
+
const abortHandler = effectiveSignal ? () => {
|
|
724
|
+
context.aborted = true;
|
|
725
|
+
context.abortReason = "Action dispatch aborted by signal";
|
|
726
|
+
} : void 0;
|
|
727
|
+
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
728
|
+
try {
|
|
729
|
+
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
730
|
+
} catch (error) {
|
|
731
|
+
executionError = error instanceof Error ? error : new Error(String(error));
|
|
732
|
+
errors.push({
|
|
733
|
+
handlerId: "pipeline",
|
|
734
|
+
error: executionError,
|
|
735
|
+
timestamp: Date.now()
|
|
736
|
+
});
|
|
737
|
+
} finally {
|
|
738
|
+
if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
|
|
739
|
+
}
|
|
740
|
+
const endTime = Date.now();
|
|
741
|
+
const executionSuccess = !executionError && !context.aborted;
|
|
742
|
+
this.updateExecutionStats(action, executionSuccess, endTime - startTime);
|
|
743
|
+
const processedResult = this.processResults(context, options?.result);
|
|
744
|
+
const executionResult = {
|
|
745
|
+
success: !executionError && !context.aborted,
|
|
746
|
+
aborted: context.aborted,
|
|
747
|
+
abortReason: context.abortReason,
|
|
748
|
+
terminated: context.terminated,
|
|
749
|
+
result: processedResult,
|
|
750
|
+
results: context.results,
|
|
751
|
+
execution: {
|
|
752
|
+
duration: endTime - startTime,
|
|
753
|
+
handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
|
|
754
|
+
handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
|
|
755
|
+
handlersFailed: errors.length,
|
|
756
|
+
startTime,
|
|
757
|
+
endTime
|
|
758
|
+
},
|
|
759
|
+
handlers: handlerResults,
|
|
760
|
+
errors
|
|
761
|
+
};
|
|
762
|
+
/** Clean up one-time handlers after execution */
|
|
763
|
+
this.cleanupOneTimeHandlers(action, context.handlers);
|
|
764
|
+
return executionResult;
|
|
765
|
+
}
|
|
766
|
+
filterHandlers(handlers, filterOptions) {
|
|
767
|
+
if (!filterOptions) return handlers;
|
|
768
|
+
return handlers.filter((registration) => {
|
|
769
|
+
const config = registration.config;
|
|
770
|
+
if (filterOptions.tags && filterOptions.tags.length > 0) {
|
|
771
|
+
const hasMatchingTag = filterOptions.tags.some((tag) => config.tags.includes(tag));
|
|
772
|
+
if (!hasMatchingTag) return false;
|
|
773
|
+
}
|
|
774
|
+
if (filterOptions.category && config.category !== filterOptions.category) return false;
|
|
775
|
+
if (filterOptions.handlerIds && filterOptions.handlerIds.length > 0) {
|
|
776
|
+
if (!filterOptions.handlerIds.includes(config.id)) return false;
|
|
777
|
+
}
|
|
778
|
+
if (filterOptions.environment && config.environment !== filterOptions.environment) return false;
|
|
779
|
+
if (filterOptions.feature && config.feature !== filterOptions.feature) return false;
|
|
780
|
+
if (filterOptions.excludeTags && filterOptions.excludeTags.length > 0) {
|
|
781
|
+
const hasExcludedTag = filterOptions.excludeTags.some((tag) => config.tags.includes(tag));
|
|
782
|
+
if (hasExcludedTag) return false;
|
|
783
|
+
}
|
|
784
|
+
if (filterOptions.excludeCategory && config.category === filterOptions.excludeCategory) return false;
|
|
785
|
+
if (filterOptions.excludeHandlerIds && filterOptions.excludeHandlerIds.length > 0) {
|
|
786
|
+
if (filterOptions.excludeHandlerIds.includes(config.id)) return false;
|
|
787
|
+
}
|
|
788
|
+
if (filterOptions.custom && !filterOptions.custom(config)) return false;
|
|
789
|
+
return true;
|
|
726
790
|
});
|
|
727
|
-
|
|
791
|
+
}
|
|
792
|
+
processResults(context, resultOptions) {
|
|
793
|
+
if (!resultOptions || !resultOptions.collect) return void 0;
|
|
794
|
+
const results = context.results;
|
|
795
|
+
if (context.terminated && context.terminationResult !== void 0) return context.terminationResult;
|
|
796
|
+
const limitedResults = resultOptions.maxResults ? results.slice(0, resultOptions.maxResults) : results;
|
|
797
|
+
if (limitedResults.length === 0) return void 0;
|
|
798
|
+
switch (resultOptions.strategy) {
|
|
799
|
+
case "first": return limitedResults[0];
|
|
800
|
+
case "last": return limitedResults[limitedResults.length - 1];
|
|
801
|
+
case "all": return limitedResults;
|
|
802
|
+
case "merge":
|
|
803
|
+
if (resultOptions.merger) return resultOptions.merger(limitedResults);
|
|
804
|
+
return limitedResults[limitedResults.length - 1];
|
|
805
|
+
case "custom":
|
|
806
|
+
if (resultOptions.merger) return resultOptions.merger(limitedResults);
|
|
807
|
+
throw new Error("Custom result strategy requires a merger function");
|
|
808
|
+
default: return limitedResults;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async executePipeline(context, autoAbortController, autoAbortOptions) {
|
|
812
|
+
const createController = (_registration, _index) => {
|
|
728
813
|
return {
|
|
729
814
|
next: () => {},
|
|
730
815
|
abort: (reason) => {
|
|
731
|
-
this.logger.trace(`Handler '${registration.id}' is aborting pipeline`, { reason });
|
|
732
816
|
context.aborted = true;
|
|
733
817
|
context.abortReason = reason;
|
|
734
|
-
|
|
818
|
+
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
735
819
|
},
|
|
736
820
|
modifyPayload: (modifier) => {
|
|
737
|
-
this.logger.trace(`Handler '${registration.id}' is modifying payload`);
|
|
738
|
-
const oldPayload = context.payload;
|
|
739
821
|
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
822
|
},
|
|
746
823
|
getPayload: () => context.payload,
|
|
747
824
|
jumpToPriority: (priority) => {
|
|
748
|
-
this.logger.trace(`Handler '${registration.id}' jumping to priority ${priority}`);
|
|
749
825
|
context.jumpToPriority = priority;
|
|
826
|
+
},
|
|
827
|
+
return: (result) => {
|
|
828
|
+
context.terminated = true;
|
|
829
|
+
context.terminationResult = result;
|
|
830
|
+
},
|
|
831
|
+
setResult: (result) => {
|
|
832
|
+
context.results.push(result);
|
|
833
|
+
},
|
|
834
|
+
getResults: () => {
|
|
835
|
+
return [...context.results];
|
|
836
|
+
},
|
|
837
|
+
mergeResult: (merger) => {
|
|
838
|
+
const currentResult = context.results[context.results.length - 1];
|
|
839
|
+
const previousResults = context.results.slice(0, -1);
|
|
840
|
+
const mergedResult = merger(previousResults, currentResult);
|
|
841
|
+
context.results[context.results.length - 1] = mergedResult;
|
|
750
842
|
}
|
|
751
843
|
};
|
|
752
844
|
};
|
|
753
845
|
switch (context.executionMode) {
|
|
754
846
|
case "sequential":
|
|
755
|
-
await executeSequential(context, createController
|
|
847
|
+
await executeSequential(context, createController);
|
|
756
848
|
break;
|
|
757
849
|
case "parallel":
|
|
758
|
-
await executeParallel(context, createController
|
|
850
|
+
await executeParallel(context, createController);
|
|
759
851
|
break;
|
|
760
852
|
case "race":
|
|
761
|
-
await executeRace(context, createController
|
|
853
|
+
await executeRace(context, createController);
|
|
762
854
|
break;
|
|
763
855
|
default: throw new Error(`Unknown execution mode: ${context.executionMode}`);
|
|
764
856
|
}
|
|
765
857
|
this.cleanupOneTimeHandlers(context.action, context.handlers);
|
|
766
858
|
}
|
|
767
|
-
/**
|
|
768
|
-
* Clean up one-time handlers after pipeline execution
|
|
769
|
-
* @internal
|
|
770
|
-
*/
|
|
771
859
|
cleanupOneTimeHandlers(action, executedHandlers) {
|
|
772
860
|
const pipeline = this.pipelines.get(action);
|
|
773
861
|
if (!pipeline) return;
|
|
774
862
|
const oneTimeHandlers = executedHandlers.filter((reg) => reg.config.once);
|
|
775
863
|
if (oneTimeHandlers.length === 0) return;
|
|
776
|
-
this.logger.trace(`Cleaning up ${oneTimeHandlers.length} one-time handlers`);
|
|
777
864
|
oneTimeHandlers.forEach((registration) => {
|
|
778
865
|
const index = pipeline.findIndex((reg) => reg.id === registration.id);
|
|
779
866
|
if (index !== -1) {
|
|
780
867
|
pipeline.splice(index, 1);
|
|
781
|
-
this.
|
|
868
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 One-time handler removed: ${String(action)}`, {
|
|
869
|
+
handlerId: registration.id,
|
|
870
|
+
remainingHandlers: pipeline.length,
|
|
871
|
+
registry: this.name
|
|
872
|
+
});
|
|
782
873
|
}
|
|
783
874
|
});
|
|
784
|
-
this.logger.trace(`Pipeline now has ${pipeline.length} handlers`);
|
|
785
875
|
}
|
|
786
876
|
/**
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
* @
|
|
877
|
+
* Update execution statistics for an action
|
|
878
|
+
*
|
|
879
|
+
* @param action Action name
|
|
880
|
+
* @param success Whether execution was successful
|
|
881
|
+
* @param duration Execution duration in milliseconds
|
|
790
882
|
*/
|
|
883
|
+
updateExecutionStats(action, success, duration) {
|
|
884
|
+
if (!this.executionStats.has(action)) this.executionStats.set(action, {
|
|
885
|
+
totalExecutions: 0,
|
|
886
|
+
totalDuration: 0,
|
|
887
|
+
successCount: 0,
|
|
888
|
+
errorCount: 0
|
|
889
|
+
});
|
|
890
|
+
const stats = this.executionStats.get(action);
|
|
891
|
+
stats.totalExecutions++;
|
|
892
|
+
stats.totalDuration += duration;
|
|
893
|
+
if (success) stats.successCount++;
|
|
894
|
+
else stats.errorCount++;
|
|
895
|
+
}
|
|
791
896
|
getHandlerCount(action) {
|
|
792
897
|
const pipeline = this.pipelines.get(action);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
898
|
+
return pipeline ? pipeline.length : 0;
|
|
899
|
+
}
|
|
900
|
+
hasHandlers(action) {
|
|
901
|
+
return this.getHandlerCount(action) > 0;
|
|
902
|
+
}
|
|
903
|
+
getRegisteredActions() {
|
|
904
|
+
return Array.from(this.pipelines.keys());
|
|
905
|
+
}
|
|
906
|
+
clearAction(action) {
|
|
907
|
+
this.pipelines.delete(action);
|
|
908
|
+
}
|
|
909
|
+
clearAll() {
|
|
910
|
+
this.pipelines.clear();
|
|
911
|
+
}
|
|
912
|
+
getName() {
|
|
913
|
+
return this.name;
|
|
796
914
|
}
|
|
797
915
|
/**
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
* @returns
|
|
916
|
+
* Get comprehensive registry information (similar to DeclarativeStoreRegistry pattern)
|
|
917
|
+
*
|
|
918
|
+
* @returns Registry information including actions, handlers, and execution modes
|
|
801
919
|
*/
|
|
802
|
-
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
920
|
+
getRegistryInfo() {
|
|
921
|
+
const totalHandlers = Array.from(this.pipelines.values()).reduce((total, pipeline) => total + pipeline.length, 0);
|
|
922
|
+
return {
|
|
923
|
+
name: this.name,
|
|
924
|
+
totalActions: this.pipelines.size,
|
|
925
|
+
totalHandlers,
|
|
926
|
+
registeredActions: Array.from(this.pipelines.keys()),
|
|
927
|
+
actionExecutionModes: new Map(this.actionExecutionModes),
|
|
928
|
+
defaultExecutionMode: this.executionMode
|
|
929
|
+
};
|
|
806
930
|
}
|
|
807
931
|
/**
|
|
808
|
-
* Get
|
|
809
|
-
*
|
|
932
|
+
* Get detailed statistics for a specific action
|
|
933
|
+
*
|
|
934
|
+
* @param action Action name to get statistics for
|
|
935
|
+
* @returns Detailed handler statistics
|
|
810
936
|
*/
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
937
|
+
getActionStats(action) {
|
|
938
|
+
const pipeline = this.pipelines.get(action);
|
|
939
|
+
if (!pipeline) return null;
|
|
940
|
+
const priorityMap = /* @__PURE__ */ new Map();
|
|
941
|
+
pipeline.forEach((handler) => {
|
|
942
|
+
if (!priorityMap.has(handler.config.priority)) priorityMap.set(handler.config.priority, []);
|
|
943
|
+
priorityMap.get(handler.config.priority).push(handler);
|
|
816
944
|
});
|
|
817
|
-
|
|
945
|
+
const handlersByPriority = Array.from(priorityMap.entries()).sort(([a], [b]) => b - a).map(([priority, handlers]) => ({
|
|
946
|
+
priority,
|
|
947
|
+
handlers: handlers.map((h) => ({
|
|
948
|
+
id: h.config.id,
|
|
949
|
+
tags: h.config.tags,
|
|
950
|
+
category: h.config.category,
|
|
951
|
+
description: h.config.description,
|
|
952
|
+
version: h.config.version
|
|
953
|
+
}))
|
|
954
|
+
}));
|
|
955
|
+
const stats = this.executionStats.get(action);
|
|
956
|
+
const executionStats = stats ? {
|
|
957
|
+
totalExecutions: stats.totalExecutions,
|
|
958
|
+
averageDuration: stats.totalExecutions > 0 ? stats.totalDuration / stats.totalExecutions : 0,
|
|
959
|
+
successRate: stats.totalExecutions > 0 ? stats.successCount / stats.totalExecutions * 100 : 0,
|
|
960
|
+
errorCount: stats.errorCount
|
|
961
|
+
} : void 0;
|
|
962
|
+
return {
|
|
963
|
+
action,
|
|
964
|
+
handlerCount: pipeline.length,
|
|
965
|
+
handlersByPriority,
|
|
966
|
+
executionStats
|
|
967
|
+
};
|
|
818
968
|
}
|
|
819
969
|
/**
|
|
820
|
-
*
|
|
821
|
-
*
|
|
970
|
+
* Get statistics for all registered actions
|
|
971
|
+
*
|
|
972
|
+
* @returns Array of statistics for all actions
|
|
822
973
|
*/
|
|
823
|
-
|
|
824
|
-
this.
|
|
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`);
|
|
974
|
+
getAllActionStats() {
|
|
975
|
+
return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
|
|
832
976
|
}
|
|
833
977
|
/**
|
|
834
|
-
*
|
|
978
|
+
* Get handlers by tag across all actions
|
|
979
|
+
*
|
|
980
|
+
* @param tag Tag to filter handlers by
|
|
981
|
+
* @returns Map of actions to handlers with the specified tag
|
|
835
982
|
*/
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
983
|
+
getHandlersByTag(tag) {
|
|
984
|
+
const result = /* @__PURE__ */ new Map();
|
|
985
|
+
for (const [action, pipeline] of this.pipelines.entries()) {
|
|
986
|
+
const matchingHandlers = pipeline.filter((handler) => handler.config.tags.includes(tag));
|
|
987
|
+
if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
|
|
988
|
+
}
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Get handlers by category across all actions
|
|
993
|
+
*
|
|
994
|
+
* @param category Category to filter handlers by
|
|
995
|
+
* @returns Map of actions to handlers with the specified category
|
|
996
|
+
*/
|
|
997
|
+
getHandlersByCategory(category) {
|
|
998
|
+
const result = /* @__PURE__ */ new Map();
|
|
999
|
+
for (const [action, pipeline] of this.pipelines.entries()) {
|
|
1000
|
+
const matchingHandlers = pipeline.filter((handler) => handler.config.category === category);
|
|
1001
|
+
if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
|
|
1002
|
+
}
|
|
1003
|
+
return result;
|
|
851
1004
|
}
|
|
852
1005
|
/**
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* @param
|
|
856
|
-
* @
|
|
1006
|
+
* Set execution mode for a specific action
|
|
1007
|
+
*
|
|
1008
|
+
* @param action Action name
|
|
1009
|
+
* @param mode Execution mode to set
|
|
857
1010
|
*/
|
|
858
|
-
|
|
859
|
-
|
|
1011
|
+
setActionExecutionMode(action, mode) {
|
|
1012
|
+
this.actionExecutionModes.set(action, mode);
|
|
1013
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode set for action '${String(action)}': ${mode}`);
|
|
860
1014
|
}
|
|
861
1015
|
/**
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
* @param
|
|
1016
|
+
* Get execution mode for a specific action
|
|
1017
|
+
*
|
|
1018
|
+
* @param action Action name
|
|
1019
|
+
* @returns Execution mode for the action, or default if not set
|
|
865
1020
|
*/
|
|
866
|
-
|
|
867
|
-
this.
|
|
1021
|
+
getActionExecutionMode(action) {
|
|
1022
|
+
return this.actionExecutionModes.get(action) || this.executionMode;
|
|
868
1023
|
}
|
|
869
1024
|
/**
|
|
870
|
-
*
|
|
871
|
-
*
|
|
1025
|
+
* Remove execution mode override for a specific action
|
|
1026
|
+
*
|
|
1027
|
+
* @param action Action name
|
|
872
1028
|
*/
|
|
873
|
-
|
|
874
|
-
|
|
1029
|
+
removeActionExecutionMode(action) {
|
|
1030
|
+
this.actionExecutionModes.delete(action);
|
|
1031
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
|
|
875
1032
|
}
|
|
876
1033
|
/**
|
|
877
|
-
*
|
|
878
|
-
|
|
1034
|
+
* Clear execution statistics for all actions
|
|
1035
|
+
*/
|
|
1036
|
+
clearExecutionStats() {
|
|
1037
|
+
this.executionStats.clear();
|
|
1038
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for registry: ${this.name}`);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Clear execution statistics for a specific action
|
|
1042
|
+
*
|
|
1043
|
+
* @param action Action name
|
|
1044
|
+
*/
|
|
1045
|
+
clearActionExecutionStats(action) {
|
|
1046
|
+
this.executionStats.delete(action);
|
|
1047
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for action: ${String(action)}`);
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get registry configuration (for debugging and inspection)
|
|
1051
|
+
*
|
|
1052
|
+
* @returns Current registry configuration
|
|
1053
|
+
*/
|
|
1054
|
+
getRegistryConfig() {
|
|
1055
|
+
return this.registryConfig;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Check if registry has debug mode enabled
|
|
1059
|
+
*
|
|
1060
|
+
* @returns Whether debug mode is enabled
|
|
879
1061
|
*/
|
|
880
|
-
|
|
881
|
-
return this.
|
|
1062
|
+
isDebugEnabled() {
|
|
1063
|
+
return Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
|
|
882
1064
|
}
|
|
883
1065
|
};
|
|
884
1066
|
|