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