@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.js
CHANGED
|
@@ -23,6 +23,171 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
enumerable: true
|
|
24
24
|
}) : target, mod));
|
|
25
25
|
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/execution-modes.ts
|
|
28
|
+
/**
|
|
29
|
+
* Execute handlers in sequential mode (one after another)
|
|
30
|
+
*/
|
|
31
|
+
async function executeSequential(context, createController) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < context.handlers.length) {
|
|
34
|
+
if (context.aborted || context.terminated) break;
|
|
35
|
+
const registration = context.handlers[i];
|
|
36
|
+
context.currentIndex = i;
|
|
37
|
+
/** Check condition if provided */
|
|
38
|
+
if (registration.config.condition && !registration.config.condition()) {
|
|
39
|
+
i++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
/** Check validation if provided */
|
|
43
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
44
|
+
i++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const controller = createController(registration, i);
|
|
48
|
+
try {
|
|
49
|
+
if (context.aborted) break;
|
|
50
|
+
const result = registration.handler(context.payload, controller);
|
|
51
|
+
/** Wait for async handlers if they're blocking */
|
|
52
|
+
if (registration.config.blocking && result instanceof Promise) {
|
|
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++;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (registration.config.blocking) throw error;
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute handlers in parallel mode (all at once)
|
|
84
|
+
*/
|
|
85
|
+
async function executeParallel(context, createController) {
|
|
86
|
+
/** Filter handlers that should run */
|
|
87
|
+
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
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;
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
/** Create promises for all handlers */
|
|
95
|
+
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
96
|
+
const controller = createController(registration, _index);
|
|
97
|
+
try {
|
|
98
|
+
const result = registration.handler(context.payload, controller);
|
|
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);
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
handlerId: registration.id,
|
|
107
|
+
result: handlerResult,
|
|
108
|
+
terminated: context.terminated
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (registration.config.blocking) throw error;
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
handlerId: registration.id,
|
|
115
|
+
error
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
/** Wait for all handlers to complete */
|
|
120
|
+
const results = await Promise.allSettled(handlerPromises);
|
|
121
|
+
/** Check for any rejected blocking handlers */
|
|
122
|
+
const failures = results.filter((result, index) => {
|
|
123
|
+
if (result.status === "rejected") {
|
|
124
|
+
const registration = runnableHandlers[index];
|
|
125
|
+
return registration.config.blocking;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
});
|
|
129
|
+
if (failures.length > 0) {
|
|
130
|
+
const firstFailure = failures[0];
|
|
131
|
+
throw firstFailure.reason;
|
|
132
|
+
}
|
|
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
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute handlers in race mode (first to complete wins)
|
|
143
|
+
*/
|
|
144
|
+
async function executeRace(context, createController) {
|
|
145
|
+
/** Filter handlers that should run */
|
|
146
|
+
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
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;
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
if (runnableHandlers.length === 0) return;
|
|
154
|
+
/** Create promises for all handlers */
|
|
155
|
+
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
156
|
+
const controller = createController(registration, _index);
|
|
157
|
+
try {
|
|
158
|
+
const result = registration.handler(context.payload, controller);
|
|
159
|
+
let handlerResult;
|
|
160
|
+
if (result instanceof Promise) handlerResult = await result;
|
|
161
|
+
else handlerResult = result;
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
handlerId: registration.id,
|
|
165
|
+
registration,
|
|
166
|
+
result: handlerResult,
|
|
167
|
+
terminated: context.terminated
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
handlerId: registration.id,
|
|
173
|
+
error,
|
|
174
|
+
registration
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
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;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
26
191
|
//#endregion
|
|
27
192
|
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
28
193
|
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
|
|
@@ -82,173 +247,156 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
|
|
|
82
247
|
} });
|
|
83
248
|
|
|
84
249
|
//#endregion
|
|
85
|
-
//#region src/
|
|
250
|
+
//#region src/action-guard.ts
|
|
86
251
|
var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
|
|
87
252
|
/**
|
|
88
|
-
*
|
|
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
|
-
debug(message, ...args) {
|
|
116
|
-
if (this.shouldLog(LogLevel.DEBUG)) console.debug(this.formatMessage("debug", message), ...args);
|
|
117
|
-
}
|
|
118
|
-
info(message, ...args) {
|
|
119
|
-
if (this.shouldLog(LogLevel.INFO)) console.info(this.formatMessage("info", message), ...args);
|
|
120
|
-
}
|
|
121
|
-
warn(message, ...args) {
|
|
122
|
-
if (this.shouldLog(LogLevel.WARN)) console.warn(this.formatMessage("warn", message), ...args);
|
|
123
|
-
}
|
|
124
|
-
error(message, ...args) {
|
|
125
|
-
if (this.shouldLog(LogLevel.ERROR)) console.error(this.formatMessage("error", message), ...args);
|
|
126
|
-
}
|
|
127
|
-
fatal(message, ...args) {
|
|
128
|
-
if (this.shouldLog(LogLevel.FATAL)) console.error(this.formatMessage("fatal", message), ...args);
|
|
129
|
-
}
|
|
130
|
-
setLevel(level) {
|
|
131
|
-
this.level = level;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* Parse log level from string
|
|
136
|
-
*/
|
|
137
|
-
function parseLogLevel(level) {
|
|
138
|
-
const upperLevel = level.toUpperCase();
|
|
139
|
-
switch (upperLevel) {
|
|
140
|
-
case "TRACE": return LogLevel.TRACE;
|
|
141
|
-
case "DEBUG": return LogLevel.DEBUG;
|
|
142
|
-
case "INFO": return LogLevel.INFO;
|
|
143
|
-
case "WARN": return LogLevel.WARN;
|
|
144
|
-
case "ERROR": return LogLevel.ERROR;
|
|
145
|
-
case "FATAL": return LogLevel.FATAL;
|
|
146
|
-
default: return LogLevel.ERROR;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Get log level from environment variable or default to ERROR
|
|
151
|
-
*/
|
|
152
|
-
function getLogLevelFromEnv() {
|
|
153
|
-
if (typeof process !== "undefined" && process.env) {
|
|
154
|
-
const envLevel = process.env.LOG_LEVEL || process.env.ACTION_LOG_LEVEL;
|
|
155
|
-
if (envLevel) return parseLogLevel(envLevel);
|
|
156
|
-
}
|
|
157
|
-
return LogLevel.TRACE;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Extract trace ID from payload if it exists
|
|
161
|
-
*/
|
|
162
|
-
function extractTraceIdFromPayload(payload) {
|
|
163
|
-
if (payload && typeof payload === "object") return payload._traceId || payload.traceId || payload.trace_id;
|
|
164
|
-
return void 0;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Extract session ID from payload if it exists
|
|
168
|
-
*/
|
|
169
|
-
function extractSessionIdFromPayload(payload) {
|
|
170
|
-
if (payload && typeof payload === "object") return payload._sessionId || payload.sessionId || payload.session_id;
|
|
171
|
-
return void 0;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Create OTEL context from payload
|
|
175
|
-
*/
|
|
176
|
-
function createOtelContextFromPayload(payload) {
|
|
177
|
-
return {
|
|
178
|
-
traceId: extractTraceIdFromPayload(payload),
|
|
179
|
-
sessionId: extractSessionIdFromPayload(payload),
|
|
180
|
-
metadata: payload
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* OpenTelemetry-aware console logger implementation
|
|
253
|
+
* Action Guard system for managing action execution timing
|
|
254
|
+
* @implements action-guard
|
|
255
|
+
* @implements performance-optimization
|
|
256
|
+
* @implements user-experience-optimization
|
|
257
|
+
* @implements class-naming
|
|
258
|
+
* @memberof core-concepts
|
|
259
|
+
* @internal
|
|
260
|
+
* @since 1.0.0
|
|
261
|
+
*
|
|
262
|
+
* Manages action execution timing through debouncing and throttling
|
|
263
|
+
* @implements performance-optimization
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const guard = new ActionGuard(logger);
|
|
268
|
+
*
|
|
269
|
+
* // Debounce search input (wait 300ms after typing stops)
|
|
270
|
+
* if (await guard.debounce('search', 300)) {
|
|
271
|
+
* executeSearch();
|
|
272
|
+
* }
|
|
273
|
+
*
|
|
274
|
+
* // Throttle scroll handler (max once per 100ms)
|
|
275
|
+
* if (guard.throttle('scroll', 100)) {
|
|
276
|
+
* updateScrollPosition();
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
185
279
|
*/
|
|
186
|
-
var
|
|
187
|
-
constructor(
|
|
188
|
-
|
|
189
|
-
(0, import_defineProperty$1.default)(this, "context", {});
|
|
190
|
-
}
|
|
191
|
-
setContext(context) {
|
|
192
|
-
this.context = {
|
|
193
|
-
...this.context,
|
|
194
|
-
...context
|
|
195
|
-
};
|
|
280
|
+
var ActionGuard = class {
|
|
281
|
+
constructor() {
|
|
282
|
+
(0, import_defineProperty$1.default)(this, "guards", /* @__PURE__ */ new Map());
|
|
196
283
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const formattedMessage = this.formatWithContext(levelName, message);
|
|
214
|
-
switch (level) {
|
|
215
|
-
case LogLevel.TRACE:
|
|
216
|
-
if (this.shouldLog(LogLevel.TRACE)) console.trace(formattedMessage, ...args);
|
|
217
|
-
break;
|
|
218
|
-
case LogLevel.DEBUG:
|
|
219
|
-
if (this.shouldLog(LogLevel.DEBUG)) console.debug(formattedMessage, ...args);
|
|
220
|
-
break;
|
|
221
|
-
case LogLevel.INFO:
|
|
222
|
-
if (this.shouldLog(LogLevel.INFO)) console.info(formattedMessage, ...args);
|
|
223
|
-
break;
|
|
224
|
-
case LogLevel.WARN:
|
|
225
|
-
if (this.shouldLog(LogLevel.WARN)) console.warn(formattedMessage, ...args);
|
|
226
|
-
break;
|
|
227
|
-
case LogLevel.ERROR:
|
|
228
|
-
if (this.shouldLog(LogLevel.ERROR)) console.error(formattedMessage, ...args);
|
|
229
|
-
break;
|
|
230
|
-
case LogLevel.FATAL:
|
|
231
|
-
if (this.shouldLog(LogLevel.FATAL)) console.error(formattedMessage, ...args);
|
|
232
|
-
break;
|
|
284
|
+
/**
|
|
285
|
+
* Check if action should be debounced
|
|
286
|
+
* @param actionKey - Unique key for the action
|
|
287
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
288
|
+
* @returns Promise that resolves when debounce period is complete
|
|
289
|
+
*/
|
|
290
|
+
async debounce(actionKey, debounceMs) {
|
|
291
|
+
/** Get or create guard state for this action */
|
|
292
|
+
let state = this.guards.get(actionKey);
|
|
293
|
+
if (!state) {
|
|
294
|
+
/** Initialize new guard state with default values */
|
|
295
|
+
state = {
|
|
296
|
+
lastExecuted: 0,
|
|
297
|
+
isThrottled: false
|
|
298
|
+
};
|
|
299
|
+
this.guards.set(actionKey, state);
|
|
233
300
|
}
|
|
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 */
|
|
306
|
+
return new Promise((resolve) => {
|
|
307
|
+
state.debounceTimer = setTimeout(() => {
|
|
308
|
+
/** Clean up timer reference to prevent memory leaks */
|
|
309
|
+
state.debounceTimer = void 0;
|
|
310
|
+
/** Update last execution timestamp for throttling calculations */
|
|
311
|
+
state.lastExecuted = Date.now();
|
|
312
|
+
resolve(true);
|
|
313
|
+
}, debounceMs);
|
|
314
|
+
});
|
|
234
315
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Check if action should be throttled
|
|
318
|
+
* @param actionKey - Unique key for the action
|
|
319
|
+
* @param throttleMs - Throttle delay in milliseconds
|
|
320
|
+
* @returns True if action should proceed, false if throttled
|
|
321
|
+
*/
|
|
322
|
+
throttle(actionKey, throttleMs) {
|
|
323
|
+
/** Get or create guard state for this action */
|
|
324
|
+
let state = this.guards.get(actionKey);
|
|
325
|
+
if (!state) {
|
|
326
|
+
/** Initialize new guard state with default values */
|
|
327
|
+
state = {
|
|
328
|
+
lastExecuted: 0,
|
|
329
|
+
isThrottled: false
|
|
330
|
+
};
|
|
331
|
+
this.guards.set(actionKey, state);
|
|
332
|
+
}
|
|
333
|
+
const now = Date.now();
|
|
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 */
|
|
337
|
+
if (timeSinceLastExecution >= throttleMs) {
|
|
338
|
+
/** Update execution timestamp and clear throttled state */
|
|
339
|
+
state.lastExecuted = now;
|
|
340
|
+
state.isThrottled = false;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
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 */
|
|
348
|
+
state.isThrottled = true;
|
|
349
|
+
const remainingTime = throttleMs - timeSinceLastExecution;
|
|
350
|
+
/** Create timer to reset throttled state when period expires */
|
|
351
|
+
state.throttleTimer = setTimeout(() => {
|
|
352
|
+
/** Clear throttled state and timer reference */
|
|
353
|
+
state.isThrottled = false;
|
|
354
|
+
state.throttleTimer = void 0;
|
|
355
|
+
}, remainingTime);
|
|
356
|
+
return false;
|
|
240
357
|
}
|
|
241
|
-
|
|
242
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Clear all guards for an action
|
|
360
|
+
* @param actionKey - Action key to clear
|
|
361
|
+
*/
|
|
362
|
+
clearGuards(actionKey) {
|
|
363
|
+
const state = this.guards.get(actionKey);
|
|
364
|
+
if (state) {
|
|
365
|
+
/** Clear debounce timer if active to prevent memory leaks */
|
|
366
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
367
|
+
/** Clear throttle timer if active to prevent memory leaks */
|
|
368
|
+
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
369
|
+
/** Remove guard state from memory */
|
|
370
|
+
this.guards.delete(actionKey);
|
|
371
|
+
}
|
|
243
372
|
}
|
|
244
|
-
|
|
245
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Clear all guards
|
|
375
|
+
*/
|
|
376
|
+
clearAll() {
|
|
377
|
+
/** Iterate through all guard states and clear their timers */
|
|
378
|
+
/** This prevents memory leaks when clearing the entire guard system */
|
|
379
|
+
for (const [, state] of this.guards) {
|
|
380
|
+
/** Clear any active debounce timers */
|
|
381
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
382
|
+
/** Clear any active throttle timers */
|
|
383
|
+
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
384
|
+
}
|
|
385
|
+
/** Remove all guard states from memory */
|
|
386
|
+
this.guards.clear();
|
|
246
387
|
}
|
|
247
|
-
|
|
248
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Get current guard state for debugging
|
|
390
|
+
* @param actionKey - Action key to inspect
|
|
391
|
+
*/
|
|
392
|
+
getGuardState(actionKey) {
|
|
393
|
+
return this.guards.get(actionKey);
|
|
249
394
|
}
|
|
250
|
-
|
|
251
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Get all active guards for debugging
|
|
397
|
+
*/
|
|
398
|
+
getAllGuardStates() {
|
|
399
|
+
return new Map(this.guards);
|
|
252
400
|
}
|
|
253
401
|
};
|
|
254
402
|
|
|
@@ -256,163 +404,666 @@ var OtelConsoleLogger = class extends ConsoleLogger {
|
|
|
256
404
|
//#region src/ActionRegister.ts
|
|
257
405
|
var import_defineProperty = __toESM(require_defineProperty(), 1);
|
|
258
406
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
407
|
+
* 중앙화된 액션 등록 및 디스패치 시스템으로, 타입 안전한 액션 파이프라인 관리를 제공하는 핵심 클래스입니다.
|
|
408
|
+
*
|
|
409
|
+
* @implements {ActionRegister}
|
|
410
|
+
* @implements {Action Pipeline System}
|
|
411
|
+
* @memberof core-concepts
|
|
412
|
+
*
|
|
261
413
|
* @example
|
|
262
414
|
* ```typescript
|
|
263
415
|
* interface AppActions extends ActionPayloadMap {
|
|
264
|
-
*
|
|
265
|
-
*
|
|
416
|
+
* updateUser: { id: string; name: string };
|
|
417
|
+
* calculateTotal: void;
|
|
266
418
|
* }
|
|
267
419
|
*
|
|
268
|
-
* const
|
|
420
|
+
* const register = new ActionRegister<AppActions>({
|
|
421
|
+
* name: 'AppRegister',
|
|
422
|
+
* logLevel: LogLevel.DEBUG
|
|
423
|
+
* });
|
|
269
424
|
*
|
|
270
|
-
* //
|
|
271
|
-
*
|
|
272
|
-
*
|
|
425
|
+
* // 핸들러 등록
|
|
426
|
+
* register.register('updateUser', ({ id, name }, controller) => {
|
|
427
|
+
* userStore.setValue({ id, name });
|
|
428
|
+
* controller.next();
|
|
429
|
+
* }, { priority: 10 });
|
|
273
430
|
*
|
|
274
|
-
* //
|
|
275
|
-
* await
|
|
276
|
-
* await actionRegister.dispatch('setCount', 42);
|
|
431
|
+
* // 액션 디스패치
|
|
432
|
+
* await register.dispatch('updateUser', { id: '1', name: 'John' });
|
|
277
433
|
* ```
|
|
278
434
|
*/
|
|
279
435
|
var ActionRegister = class {
|
|
280
|
-
constructor(config) {
|
|
436
|
+
constructor(config = {}) {
|
|
281
437
|
(0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
|
|
282
|
-
(0, import_defineProperty.default)(this, "atomSetters", /* @__PURE__ */ new Map());
|
|
283
438
|
(0, import_defineProperty.default)(this, "handlerCounter", 0);
|
|
284
|
-
(0, import_defineProperty.default)(this, "
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
439
|
+
(0, import_defineProperty.default)(this, "actionGuard", void 0);
|
|
440
|
+
(0, import_defineProperty.default)(this, "executionMode", "sequential");
|
|
441
|
+
(0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
|
|
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
|
|
295
453
|
});
|
|
296
454
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Register a handler for an action in the pipeline
|
|
299
|
-
* @param action - The action name to handle
|
|
300
|
-
* @param handler - The handler function to execute
|
|
301
|
-
* @param config - Optional configuration for the handler
|
|
302
|
-
* @returns Unregister function to remove the handler
|
|
303
|
-
* @example
|
|
304
|
-
* ```typescript
|
|
305
|
-
* const unregister = actionRegister.register('increment', () => {
|
|
306
|
-
* console.log('Incremented!');
|
|
307
|
-
* }, { priority: 10 });
|
|
308
|
-
*
|
|
309
|
-
* // Later, remove the handler
|
|
310
|
-
* unregister();
|
|
311
|
-
* ```
|
|
312
|
-
*/
|
|
313
455
|
register(action, handler, config = {}) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.logger.debug(`Created new pipeline for action: ${String(action)}`);
|
|
317
|
-
}
|
|
318
|
-
const pipeline = this.pipelines.get(action);
|
|
319
|
-
const handlerId = config.id || `handler_${++this.handlerCounter}`;
|
|
320
|
-
if (pipeline.has(handlerId)) {
|
|
321
|
-
this.logger.warn(`Handler with id ${handlerId} already exists for action: ${String(action)}`);
|
|
322
|
-
return () => {};
|
|
323
|
-
}
|
|
324
|
-
pipeline.set(handlerId, {
|
|
456
|
+
const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
|
|
457
|
+
const registration = {
|
|
325
458
|
handler,
|
|
326
|
-
config
|
|
327
|
-
|
|
328
|
-
|
|
459
|
+
config: {
|
|
460
|
+
priority: config.priority ?? 0,
|
|
461
|
+
id: handlerId,
|
|
462
|
+
blocking: config.blocking ?? false,
|
|
463
|
+
once: config.once ?? false,
|
|
464
|
+
condition: config.condition || (() => true),
|
|
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 ?? {}
|
|
486
|
+
},
|
|
487
|
+
id: handlerId
|
|
488
|
+
};
|
|
489
|
+
if (!this.pipelines.has(action)) this.pipelines.set(action, []);
|
|
490
|
+
const pipeline = this.pipelines.get(action);
|
|
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}'`);
|
|
494
|
+
pipeline.push(registration);
|
|
495
|
+
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
496
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
|
|
329
497
|
handlerId,
|
|
330
|
-
priority: config.priority
|
|
331
|
-
|
|
498
|
+
priority: config.priority,
|
|
499
|
+
tags: config.tags,
|
|
500
|
+
category: config.category,
|
|
501
|
+
totalHandlers: pipeline.length,
|
|
502
|
+
registry: this.name
|
|
332
503
|
});
|
|
333
|
-
this.sortPipeline(action);
|
|
334
504
|
return () => {
|
|
335
|
-
pipeline.
|
|
336
|
-
|
|
505
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
506
|
+
if (index !== -1) {
|
|
507
|
+
pipeline.splice(index, 1);
|
|
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
|
|
512
|
+
});
|
|
513
|
+
}
|
|
337
514
|
};
|
|
338
515
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
}
|
|
342
592
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
+
};
|
|
348
628
|
const pipeline = this.pipelines.get(action);
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
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
|
+
}
|
|
352
656
|
}
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
}
|
|
357
663
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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()
|
|
367
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;
|
|
790
|
+
});
|
|
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) => {
|
|
813
|
+
return {
|
|
814
|
+
next: () => {},
|
|
368
815
|
abort: (reason) => {
|
|
369
|
-
|
|
370
|
-
|
|
816
|
+
context.aborted = true;
|
|
817
|
+
context.abortReason = reason;
|
|
818
|
+
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
371
819
|
},
|
|
372
820
|
modifyPayload: (modifier) => {
|
|
373
|
-
|
|
374
|
-
|
|
821
|
+
context.payload = modifier(context.payload);
|
|
822
|
+
},
|
|
823
|
+
getPayload: () => context.payload,
|
|
824
|
+
jumpToPriority: (priority) => {
|
|
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;
|
|
375
842
|
}
|
|
376
843
|
};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
844
|
+
};
|
|
845
|
+
switch (context.executionMode) {
|
|
846
|
+
case "sequential":
|
|
847
|
+
await executeSequential(context, createController);
|
|
848
|
+
break;
|
|
849
|
+
case "parallel":
|
|
850
|
+
await executeParallel(context, createController);
|
|
851
|
+
break;
|
|
852
|
+
case "race":
|
|
853
|
+
await executeRace(context, createController);
|
|
854
|
+
break;
|
|
855
|
+
default: throw new Error(`Unknown execution mode: ${context.executionMode}`);
|
|
384
856
|
}
|
|
385
|
-
this.
|
|
857
|
+
this.cleanupOneTimeHandlers(context.action, context.handlers);
|
|
386
858
|
}
|
|
387
|
-
|
|
859
|
+
cleanupOneTimeHandlers(action, executedHandlers) {
|
|
388
860
|
const pipeline = this.pipelines.get(action);
|
|
389
861
|
if (!pipeline) return;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
862
|
+
const oneTimeHandlers = executedHandlers.filter((reg) => reg.config.once);
|
|
863
|
+
if (oneTimeHandlers.length === 0) return;
|
|
864
|
+
oneTimeHandlers.forEach((registration) => {
|
|
865
|
+
const index = pipeline.findIndex((reg) => reg.id === registration.id);
|
|
866
|
+
if (index !== -1) {
|
|
867
|
+
pipeline.splice(index, 1);
|
|
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
|
+
});
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
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
|
|
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
|
|
394
889
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
890
|
+
const stats = this.executionStats.get(action);
|
|
891
|
+
stats.totalExecutions++;
|
|
892
|
+
stats.totalDuration += duration;
|
|
893
|
+
if (success) stats.successCount++;
|
|
894
|
+
else stats.errorCount++;
|
|
895
|
+
}
|
|
896
|
+
getHandlerCount(action) {
|
|
897
|
+
const pipeline = this.pipelines.get(action);
|
|
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;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Get comprehensive registry information (similar to DeclarativeStoreRegistry pattern)
|
|
917
|
+
*
|
|
918
|
+
* @returns Registry information including actions, handlers, and execution modes
|
|
919
|
+
*/
|
|
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
|
+
};
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Get detailed statistics for a specific action
|
|
933
|
+
*
|
|
934
|
+
* @param action Action name to get statistics for
|
|
935
|
+
* @returns Detailed handler statistics
|
|
936
|
+
*/
|
|
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);
|
|
400
944
|
});
|
|
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
|
+
};
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Get statistics for all registered actions
|
|
971
|
+
*
|
|
972
|
+
* @returns Array of statistics for all actions
|
|
973
|
+
*/
|
|
974
|
+
getAllActionStats() {
|
|
975
|
+
return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
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
|
|
982
|
+
*/
|
|
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;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Set execution mode for a specific action
|
|
1007
|
+
*
|
|
1008
|
+
* @param action Action name
|
|
1009
|
+
* @param mode Execution mode to set
|
|
1010
|
+
*/
|
|
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}`);
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
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
|
|
1020
|
+
*/
|
|
1021
|
+
getActionExecutionMode(action) {
|
|
1022
|
+
return this.actionExecutionModes.get(action) || this.executionMode;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Remove execution mode override for a specific action
|
|
1026
|
+
*
|
|
1027
|
+
* @param action Action name
|
|
1028
|
+
*/
|
|
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}`);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
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
|
|
1061
|
+
*/
|
|
1062
|
+
isDebugEnabled() {
|
|
1063
|
+
return Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
|
|
401
1064
|
}
|
|
402
1065
|
};
|
|
403
1066
|
|
|
404
1067
|
//#endregion
|
|
405
|
-
|
|
406
|
-
function createAction(type, payload) {
|
|
407
|
-
return {
|
|
408
|
-
type,
|
|
409
|
-
payload
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
function isAction(action, type) {
|
|
413
|
-
return action?.type === type;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
//#endregion
|
|
417
|
-
export { ActionRegister, ConsoleLogger, LogLevel, OtelConsoleLogger, createAction, createOtelContextFromPayload, extractSessionIdFromPayload, extractTraceIdFromPayload, getLogLevelFromEnv, isAction, parseLogLevel };
|
|
1068
|
+
export { ActionGuard, ActionRegister, executeParallel, executeRace, executeSequential };
|
|
418
1069
|
//# sourceMappingURL=index.js.map
|