@context-action/core 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +379 -161
- package/dist/index.cjs +692 -725
- package/dist/index.d.cts +81 -58
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +81 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +686 -725
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.cjs
CHANGED
|
@@ -1,32 +1,24 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
13
|
-
key = keys[i];
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
15
|
-
get: ((k) => from[k]).bind(null, key),
|
|
16
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
-
value: mod,
|
|
23
|
-
enumerable: true
|
|
24
|
-
}) : target, mod));
|
|
25
|
-
|
|
26
|
-
//#endregion
|
|
27
1
|
|
|
28
2
|
//#region src/execution-modes.ts
|
|
29
3
|
/**
|
|
4
|
+
* Create standardized error handling for handlers
|
|
5
|
+
*
|
|
6
|
+
* @param error - The error that occurred
|
|
7
|
+
* @param registration - The handler registration that failed
|
|
8
|
+
* @returns Standardized HandlerError object
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
function handleExecutionError(error, registration) {
|
|
13
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
14
|
+
return {
|
|
15
|
+
handlerId: registration.id,
|
|
16
|
+
error: errorObj,
|
|
17
|
+
timestamp: Date.now(),
|
|
18
|
+
severity: registration.config.blocking ? "blocking" : "non-blocking"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
30
22
|
* Execute handlers in sequential mode (one after another)
|
|
31
23
|
*
|
|
32
24
|
* Executes action handlers one at a time in priority order (highest first).
|
|
@@ -40,64 +32,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
40
32
|
* @param context - Pipeline execution context containing handlers and state
|
|
41
33
|
* @param createController - Factory function for creating pipeline controllers
|
|
42
34
|
*
|
|
43
|
-
* @throws {Error} When a blocking handler fails
|
|
35
|
+
* @throws {Error} When a blocking handler fails
|
|
44
36
|
*
|
|
45
|
-
* @
|
|
46
|
-
* ```typescript
|
|
47
|
-
* // This is called internally by ActionRegister.dispatch()
|
|
48
|
-
* // when executionMode is 'sequential'
|
|
49
|
-
*
|
|
50
|
-
* // Handlers execute in this order (by priority):
|
|
51
|
-
* // 1. Priority 100: Validation handler
|
|
52
|
-
* // 2. Priority 50: Business logic handler
|
|
53
|
-
* // 3. Priority 10: Logging handler
|
|
54
|
-
*
|
|
55
|
-
* await executeSequential(context, (registration, index) => ({
|
|
56
|
-
* abort: (reason) => { context.aborted = true; context.abortReason = reason },
|
|
57
|
-
* modifyPayload: (modifier) => { context.payload = modifier(context.payload) },
|
|
58
|
-
* // ... other controller methods
|
|
59
|
-
* }))
|
|
60
|
-
* ```
|
|
37
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns
|
|
61
38
|
*
|
|
62
39
|
* @public
|
|
63
40
|
*/
|
|
64
41
|
async function executeSequential(context, createController) {
|
|
65
42
|
let i = 0;
|
|
66
43
|
const nonBlockingPromises = [];
|
|
44
|
+
const errors = [];
|
|
67
45
|
while (i < context.handlers.length) {
|
|
68
46
|
if (context.aborted || context.terminated) break;
|
|
69
47
|
const registration = context.handlers[i];
|
|
70
48
|
context.currentIndex = i;
|
|
71
|
-
/** Check condition if provided */
|
|
72
|
-
if (registration.config.condition && !registration.config.condition()) {
|
|
73
|
-
i++;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
/** Check validation if provided */
|
|
77
|
-
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
78
|
-
i++;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
49
|
const controller = createController(registration, i);
|
|
82
50
|
try {
|
|
83
51
|
if (context.aborted) break;
|
|
84
52
|
const result = registration.handler(context.payload, controller);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const handlerResult = await result;
|
|
88
|
-
/** Collect result if handler returned something and wasn't terminated */
|
|
53
|
+
if (registration.config.blocking) {
|
|
54
|
+
const handlerResult = result instanceof Promise ? await result : result;
|
|
89
55
|
if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
|
|
90
|
-
} else if (result
|
|
91
|
-
|
|
92
|
-
if (result instanceof Promise) {
|
|
93
|
-
const promiseWithHandling = result.then((asyncResult) => {
|
|
56
|
+
} else if (result instanceof Promise) {
|
|
57
|
+
const promiseWithErrorHandling = result.then((asyncResult) => {
|
|
94
58
|
if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
|
|
95
59
|
return asyncResult;
|
|
96
60
|
}).catch((error) => {
|
|
97
|
-
|
|
61
|
+
const handlerError = handleExecutionError(error, registration);
|
|
62
|
+
errors.push({
|
|
63
|
+
handlerId: handlerError.handlerId,
|
|
64
|
+
error: handlerError.error,
|
|
65
|
+
timestamp: handlerError.timestamp
|
|
66
|
+
});
|
|
67
|
+
return void 0;
|
|
98
68
|
});
|
|
99
|
-
nonBlockingPromises.push(
|
|
100
|
-
} else context.results.push(result);
|
|
69
|
+
nonBlockingPromises.push(promiseWithErrorHandling);
|
|
70
|
+
} else if (result !== void 0 && !context.terminated) context.results.push(result);
|
|
101
71
|
/** Check if pipeline was terminated by controller.return() */
|
|
102
72
|
if (context.terminated) break;
|
|
103
73
|
/** Handle jump to priority AFTER handler execution */
|
|
@@ -113,11 +83,20 @@ async function executeSequential(context, createController) {
|
|
|
113
83
|
}
|
|
114
84
|
} else i++;
|
|
115
85
|
} catch (error) {
|
|
116
|
-
|
|
117
|
-
throw error;
|
|
86
|
+
const handlerError = handleExecutionError(error, registration);
|
|
87
|
+
throw handlerError.error;
|
|
118
88
|
}
|
|
119
89
|
}
|
|
120
|
-
if (nonBlockingPromises.length > 0) await Promise.
|
|
90
|
+
if (nonBlockingPromises.length > 0) await Promise.allSettled(nonBlockingPromises);
|
|
91
|
+
if (errors.length > 0) {
|
|
92
|
+
const handlerErrors = errors.map((err) => ({
|
|
93
|
+
handlerId: err.handlerId,
|
|
94
|
+
error: err.error,
|
|
95
|
+
timestamp: err.timestamp,
|
|
96
|
+
severity: "non-blocking"
|
|
97
|
+
}));
|
|
98
|
+
context.collectedErrors = handlerErrors;
|
|
99
|
+
}
|
|
121
100
|
}
|
|
122
101
|
/**
|
|
123
102
|
* Execute handlers in parallel mode (all at once)
|
|
@@ -134,46 +113,13 @@ async function executeSequential(context, createController) {
|
|
|
134
113
|
*
|
|
135
114
|
* @throws {Error} When any blocking handler fails
|
|
136
115
|
*
|
|
137
|
-
* @
|
|
138
|
-
* ```typescript
|
|
139
|
-
* // This is called internally by ActionRegister.dispatch()
|
|
140
|
-
* // when executionMode is 'parallel'
|
|
141
|
-
*
|
|
142
|
-
* // All handlers execute simultaneously:
|
|
143
|
-
* // - Analytics handler (non-blocking)
|
|
144
|
-
* // - Validation handler (blocking)
|
|
145
|
-
* // - Update handler (blocking)
|
|
146
|
-
* // - Notification handler (non-blocking)
|
|
147
|
-
*
|
|
148
|
-
* await executeParallel(context, (registration, index) => ({
|
|
149
|
-
* abort: (reason) => { context.aborted = true },
|
|
150
|
-
* setResult: (result) => { context.results.push(result) },
|
|
151
|
-
* // ... other controller methods
|
|
152
|
-
* }))
|
|
153
|
-
* ```
|
|
154
|
-
*
|
|
155
|
-
* @example Use Case
|
|
156
|
-
* ```typescript
|
|
157
|
-
* // Perfect for independent operations
|
|
158
|
-
* register.setActionExecutionMode('logEvent', 'parallel')
|
|
159
|
-
*
|
|
160
|
-
* // These can all run simultaneously:
|
|
161
|
-
* register.register('logEvent', analyticsHandler, { blocking: false })
|
|
162
|
-
* register.register('logEvent', metricsHandler, { blocking: false })
|
|
163
|
-
* register.register('logEvent', auditHandler, { blocking: true })
|
|
164
|
-
* ```
|
|
116
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns#parallel-execution
|
|
165
117
|
*
|
|
166
118
|
* @public
|
|
167
119
|
*/
|
|
168
120
|
async function executeParallel(context, createController) {
|
|
169
|
-
/**
|
|
170
|
-
const runnableHandlers = context.handlers
|
|
171
|
-
/** Check condition */
|
|
172
|
-
if (registration.config.condition && !registration.config.condition()) return false;
|
|
173
|
-
/** Check validation */
|
|
174
|
-
if (registration.config.validation && !registration.config.validation(context.payload)) return false;
|
|
175
|
-
return true;
|
|
176
|
-
});
|
|
121
|
+
/** All handlers are runnable */
|
|
122
|
+
const runnableHandlers = context.handlers;
|
|
177
123
|
/** Create promises for all handlers */
|
|
178
124
|
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
179
125
|
const controller = createController(registration, _index);
|
|
@@ -193,11 +139,12 @@ async function executeParallel(context, createController) {
|
|
|
193
139
|
terminated: context.terminated
|
|
194
140
|
};
|
|
195
141
|
} catch (error) {
|
|
196
|
-
|
|
142
|
+
const handlerError = handleExecutionError(error, registration);
|
|
143
|
+
if (handlerError.severity === "blocking") throw handlerError.error;
|
|
197
144
|
return {
|
|
198
145
|
success: false,
|
|
199
146
|
handlerId: registration.id,
|
|
200
|
-
error
|
|
147
|
+
error: handlerError.error
|
|
201
148
|
};
|
|
202
149
|
}
|
|
203
150
|
});
|
|
@@ -239,52 +186,13 @@ async function executeParallel(context, createController) {
|
|
|
239
186
|
*
|
|
240
187
|
* @throws {Error} When the winning handler fails and is blocking
|
|
241
188
|
*
|
|
242
|
-
* @
|
|
243
|
-
* ```typescript
|
|
244
|
-
* // This is called internally by ActionRegister.dispatch()
|
|
245
|
-
* // when executionMode is 'race'
|
|
246
|
-
*
|
|
247
|
-
* // Multiple data sources racing for fastest response:
|
|
248
|
-
* // - Database handler (might be slow)
|
|
249
|
-
* // - Cache handler (usually fast)
|
|
250
|
-
* // - API handler (variable speed)
|
|
251
|
-
* //
|
|
252
|
-
* // Whichever completes first wins
|
|
253
|
-
*
|
|
254
|
-
* await executeRace(context, (registration, index) => ({
|
|
255
|
-
* return: (result) => {
|
|
256
|
-
* context.terminated = true
|
|
257
|
-
* context.terminationResult = result
|
|
258
|
-
* },
|
|
259
|
-
* // ... other controller methods
|
|
260
|
-
* }))
|
|
261
|
-
* ```
|
|
262
|
-
*
|
|
263
|
-
* @example Use Case
|
|
264
|
-
* ```typescript
|
|
265
|
-
* // Race between multiple data sources
|
|
266
|
-
* register.setActionExecutionMode('fetchUserData', 'race')
|
|
267
|
-
*
|
|
268
|
-
* // These handlers race for fastest response:
|
|
269
|
-
* register.register('fetchUserData', cacheHandler) // Usually fastest
|
|
270
|
-
* register.register('fetchUserData', databaseHandler) // Reliable fallback
|
|
271
|
-
* register.register('fetchUserData', apiHandler) // External source
|
|
272
|
-
*
|
|
273
|
-
* // First to complete wins, others are ignored
|
|
274
|
-
* const result = await register.dispatchWithResult('fetchUserData', { id: '123' })
|
|
275
|
-
* ```
|
|
189
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns#race-execution
|
|
276
190
|
*
|
|
277
191
|
* @public
|
|
278
192
|
*/
|
|
279
193
|
async function executeRace(context, createController) {
|
|
280
|
-
/**
|
|
281
|
-
const runnableHandlers = context.handlers
|
|
282
|
-
/** Check condition */
|
|
283
|
-
if (registration.config.condition && !registration.config.condition()) return false;
|
|
284
|
-
/** Check validation */
|
|
285
|
-
if (registration.config.validation && !registration.config.validation(context.payload)) return false;
|
|
286
|
-
return true;
|
|
287
|
-
});
|
|
194
|
+
/** All handlers are runnable */
|
|
195
|
+
const runnableHandlers = context.handlers;
|
|
288
196
|
if (runnableHandlers.length === 0) return;
|
|
289
197
|
/** Create promises for all handlers */
|
|
290
198
|
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
@@ -304,10 +212,11 @@ async function executeRace(context, createController) {
|
|
|
304
212
|
terminated: context.terminated
|
|
305
213
|
};
|
|
306
214
|
} catch (error) {
|
|
215
|
+
const handlerError = handleExecutionError(error, registration);
|
|
307
216
|
return {
|
|
308
217
|
success: false,
|
|
309
218
|
handlerId: registration.id,
|
|
310
|
-
error,
|
|
219
|
+
error: handlerError.error,
|
|
311
220
|
registration
|
|
312
221
|
};
|
|
313
222
|
}
|
|
@@ -325,67 +234,8 @@ async function executeRace(context, createController) {
|
|
|
325
234
|
}
|
|
326
235
|
}
|
|
327
236
|
|
|
328
|
-
//#endregion
|
|
329
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
330
|
-
var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js": ((exports, module) => {
|
|
331
|
-
function _typeof$2(o) {
|
|
332
|
-
"@babel/helpers - typeof";
|
|
333
|
-
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
334
|
-
return typeof o$1;
|
|
335
|
-
} : function(o$1) {
|
|
336
|
-
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
|
|
337
|
-
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
|
|
338
|
-
}
|
|
339
|
-
module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
340
|
-
}) });
|
|
341
|
-
|
|
342
|
-
//#endregion
|
|
343
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
|
|
344
|
-
var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js": ((exports, module) => {
|
|
345
|
-
var _typeof$1 = require_typeof()["default"];
|
|
346
|
-
function toPrimitive$1(t, r) {
|
|
347
|
-
if ("object" != _typeof$1(t) || !t) return t;
|
|
348
|
-
var e = t[Symbol.toPrimitive];
|
|
349
|
-
if (void 0 !== e) {
|
|
350
|
-
var i = e.call(t, r || "default");
|
|
351
|
-
if ("object" != _typeof$1(i)) return i;
|
|
352
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
353
|
-
}
|
|
354
|
-
return ("string" === r ? String : Number)(t);
|
|
355
|
-
}
|
|
356
|
-
module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
357
|
-
}) });
|
|
358
|
-
|
|
359
|
-
//#endregion
|
|
360
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
|
|
361
|
-
var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js": ((exports, module) => {
|
|
362
|
-
var _typeof = require_typeof()["default"];
|
|
363
|
-
var toPrimitive = require_toPrimitive();
|
|
364
|
-
function toPropertyKey$1(t) {
|
|
365
|
-
var i = toPrimitive(t, "string");
|
|
366
|
-
return "symbol" == _typeof(i) ? i : i + "";
|
|
367
|
-
}
|
|
368
|
-
module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
369
|
-
}) });
|
|
370
|
-
|
|
371
|
-
//#endregion
|
|
372
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
|
|
373
|
-
var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js": ((exports, module) => {
|
|
374
|
-
var toPropertyKey = require_toPropertyKey();
|
|
375
|
-
function _defineProperty$3(e, r, t) {
|
|
376
|
-
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
377
|
-
value: t,
|
|
378
|
-
enumerable: !0,
|
|
379
|
-
configurable: !0,
|
|
380
|
-
writable: !0
|
|
381
|
-
}) : e[r] = t, e;
|
|
382
|
-
}
|
|
383
|
-
module.exports = _defineProperty$3, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
384
|
-
}) });
|
|
385
|
-
|
|
386
237
|
//#endregion
|
|
387
238
|
//#region src/action-guard.ts
|
|
388
|
-
var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
389
239
|
/**
|
|
390
240
|
* Action Guard system for managing action execution timing
|
|
391
241
|
*
|
|
@@ -393,33 +243,9 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
|
|
|
393
243
|
* debouncing and throttling mechanisms. Debouncing waits for a pause in calls
|
|
394
244
|
* before executing, while throttling limits execution frequency.
|
|
395
245
|
*
|
|
396
|
-
* @
|
|
397
|
-
* ```typescript
|
|
398
|
-
* const guard = new ActionGuard()
|
|
246
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
399
247
|
*
|
|
400
|
-
*
|
|
401
|
-
* register.register('searchUsers', async (payload, controller) => {
|
|
402
|
-
* const query = payload.query
|
|
403
|
-
* if (query.length < 2) return
|
|
404
|
-
*
|
|
405
|
-
* const results = await userService.search(query)
|
|
406
|
-
* controller.setResult(results)
|
|
407
|
-
* }, {
|
|
408
|
-
* debounce: 300, // Built into ActionRegister via ActionGuard
|
|
409
|
-
* tags: ['search', 'user-input']
|
|
410
|
-
* })
|
|
411
|
-
* ```
|
|
412
|
-
*
|
|
413
|
-
* @example Throttling High-Frequency Events
|
|
414
|
-
* ```typescript
|
|
415
|
-
* // Limit scroll position updates to once per 100ms
|
|
416
|
-
* register.register('updateScrollPosition', (payload, controller) => {
|
|
417
|
-
* scrollState.setValue(payload.position)
|
|
418
|
-
* }, {
|
|
419
|
-
* throttle: 100, // Built into ActionRegister via ActionGuard
|
|
420
|
-
* tags: ['scroll', 'performance']
|
|
421
|
-
* })
|
|
422
|
-
* ```
|
|
248
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
423
249
|
*
|
|
424
250
|
* @example Manual Usage (Advanced)
|
|
425
251
|
* ```typescript
|
|
@@ -439,8 +265,31 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
|
|
|
439
265
|
* @internal
|
|
440
266
|
*/
|
|
441
267
|
var ActionGuard = class {
|
|
442
|
-
constructor() {
|
|
443
|
-
|
|
268
|
+
constructor(autoCleanup = true) {
|
|
269
|
+
this.guards = /* @__PURE__ */ new Map();
|
|
270
|
+
this.maxIdleTime = 6e4;
|
|
271
|
+
this.cleanupIntervalMs = 3e4;
|
|
272
|
+
if (autoCleanup) this.startAutoCleanup();
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Start automatic cleanup of idle guard states
|
|
276
|
+
*
|
|
277
|
+
* @internal
|
|
278
|
+
*/
|
|
279
|
+
startAutoCleanup() {
|
|
280
|
+
this.cleanupInterval = setInterval(() => {
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
const keysToDelete = [];
|
|
283
|
+
this.guards.forEach((state, key) => {
|
|
284
|
+
const isIdle = now - state.lastExecuted > this.maxIdleTime;
|
|
285
|
+
const hasActiveTimers = state.debounceTimer || state.throttleTimer;
|
|
286
|
+
if (isIdle && !hasActiveTimers) keysToDelete.push(key);
|
|
287
|
+
});
|
|
288
|
+
if (keysToDelete.length > 0) {
|
|
289
|
+
keysToDelete.forEach((key) => this.guards.delete(key));
|
|
290
|
+
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Cleaned up ${keysToDelete.length} idle guards`);
|
|
291
|
+
}
|
|
292
|
+
}, this.cleanupIntervalMs);
|
|
444
293
|
}
|
|
445
294
|
/**
|
|
446
295
|
* Apply debouncing to an action
|
|
@@ -570,10 +419,17 @@ var ActionGuard = class {
|
|
|
570
419
|
/** Clear debounce timer if active to prevent memory leaks */
|
|
571
420
|
if (state.debounceTimer) {
|
|
572
421
|
clearTimeout(state.debounceTimer);
|
|
573
|
-
if (state.debounceResolve)
|
|
422
|
+
if (state.debounceResolve) {
|
|
423
|
+
state.debounceResolve(false);
|
|
424
|
+
state.debounceResolve = void 0;
|
|
425
|
+
}
|
|
426
|
+
state.debounceTimer = void 0;
|
|
574
427
|
}
|
|
575
428
|
/** Clear throttle timer if active to prevent memory leaks */
|
|
576
|
-
if (state.throttleTimer)
|
|
429
|
+
if (state.throttleTimer) {
|
|
430
|
+
clearTimeout(state.throttleTimer);
|
|
431
|
+
state.throttleTimer = void 0;
|
|
432
|
+
}
|
|
577
433
|
/** Remove guard state from memory */
|
|
578
434
|
this.guards.delete(actionKey);
|
|
579
435
|
}
|
|
@@ -589,7 +445,7 @@ var ActionGuard = class {
|
|
|
589
445
|
clearAll() {
|
|
590
446
|
/** Iterate through all guard states and clear their timers */
|
|
591
447
|
/** This prevents memory leaks when clearing the entire guard system */
|
|
592
|
-
|
|
448
|
+
this.guards.forEach((state) => {
|
|
593
449
|
/** Clear any active debounce timers */
|
|
594
450
|
if (state.debounceTimer) {
|
|
595
451
|
clearTimeout(state.debounceTimer);
|
|
@@ -597,7 +453,7 @@ var ActionGuard = class {
|
|
|
597
453
|
}
|
|
598
454
|
/** Clear any active throttle timers */
|
|
599
455
|
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
600
|
-
}
|
|
456
|
+
});
|
|
601
457
|
/** Remove all guard states from memory */
|
|
602
458
|
this.guards.clear();
|
|
603
459
|
}
|
|
@@ -628,11 +484,42 @@ var ActionGuard = class {
|
|
|
628
484
|
getAllGuardStates() {
|
|
629
485
|
return new Map(this.guards);
|
|
630
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* 🆕 Explicit destroy method for comprehensive cleanup
|
|
489
|
+
*
|
|
490
|
+
* Cleans up all timers, promises, and intervals to prevent memory leaks.
|
|
491
|
+
* Should be called when ActionGuard is no longer needed.
|
|
492
|
+
*
|
|
493
|
+
* @internal
|
|
494
|
+
*/
|
|
495
|
+
destroy() {
|
|
496
|
+
if (this.cleanupInterval) {
|
|
497
|
+
clearInterval(this.cleanupInterval);
|
|
498
|
+
this.cleanupInterval = void 0;
|
|
499
|
+
}
|
|
500
|
+
this.clearAll();
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* 🆕 Get statistics about active guards
|
|
504
|
+
*
|
|
505
|
+
* @returns Statistics about guard usage
|
|
506
|
+
*
|
|
507
|
+
* @internal
|
|
508
|
+
*/
|
|
509
|
+
getStats() {
|
|
510
|
+
let withTimers = 0;
|
|
511
|
+
this.guards.forEach((state) => {
|
|
512
|
+
if (state.debounceTimer || state.throttleTimer) withTimers++;
|
|
513
|
+
});
|
|
514
|
+
return {
|
|
515
|
+
activeGuards: this.guards.size,
|
|
516
|
+
withTimers
|
|
517
|
+
};
|
|
518
|
+
}
|
|
631
519
|
};
|
|
632
520
|
|
|
633
521
|
//#endregion
|
|
634
522
|
//#region src/concurrency/OperationQueue.ts
|
|
635
|
-
var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
636
523
|
/**
|
|
637
524
|
* 작업 큐 관리자
|
|
638
525
|
*
|
|
@@ -641,13 +528,17 @@ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(),
|
|
|
641
528
|
* 2. 우선순위 지원 - 중요한 작업 우선 처리
|
|
642
529
|
* 3. 에러 처리 - 개별 작업 실패가 전체에 영향 주지 않음
|
|
643
530
|
* 4. 메모리 관리 - 완료된 작업 자동 정리
|
|
531
|
+
* 5. 🆕 동시성 제어 - maxConcurrency로 동시 실행 제한
|
|
644
532
|
*/
|
|
645
533
|
var OperationQueue = class {
|
|
646
|
-
constructor(name = "OperationQueue") {
|
|
534
|
+
constructor(name = "OperationQueue", maxConcurrency = 1) {
|
|
647
535
|
this.name = name;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
536
|
+
this.queue = [];
|
|
537
|
+
this.processingPromise = null;
|
|
538
|
+
this.operationCounter = 0;
|
|
539
|
+
this.activeOperations = 0;
|
|
540
|
+
this.runningOperations = /* @__PURE__ */ new Set();
|
|
541
|
+
this.maxConcurrency = Math.max(1, maxConcurrency);
|
|
651
542
|
}
|
|
652
543
|
/**
|
|
653
544
|
* 작업을 큐에 추가하고 실행 결과를 반환
|
|
@@ -676,35 +567,56 @@ var OperationQueue = class {
|
|
|
676
567
|
});
|
|
677
568
|
}
|
|
678
569
|
/**
|
|
679
|
-
* 큐 처리 메인 로직
|
|
570
|
+
* 🆕 큐 처리 메인 로직 - 동시성 제어 지원
|
|
680
571
|
*
|
|
681
|
-
*
|
|
572
|
+
* maxConcurrency에 따라 동시 실행 작업 수를 제한하여 동시성 문제 방지
|
|
682
573
|
*/
|
|
683
574
|
async processQueue() {
|
|
684
|
-
if (this.
|
|
685
|
-
this.
|
|
575
|
+
if (this.processingPromise) return this.processingPromise;
|
|
576
|
+
this.processingPromise = this._doProcess();
|
|
686
577
|
try {
|
|
687
|
-
|
|
578
|
+
await this.processingPromise;
|
|
579
|
+
} finally {
|
|
580
|
+
this.processingPromise = null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async _doProcess() {
|
|
584
|
+
while (this.queue.length > 0 || this.runningOperations.size > 0) {
|
|
585
|
+
while (this.queue.length > 0 && this.activeOperations < this.maxConcurrency) {
|
|
688
586
|
const operation = this.queue.shift();
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
587
|
+
this.activeOperations++;
|
|
588
|
+
const operationPromise = this.executeOperation(operation);
|
|
589
|
+
this.runningOperations.add(operationPromise);
|
|
590
|
+
operationPromise.finally(() => {
|
|
591
|
+
this.activeOperations--;
|
|
592
|
+
this.runningOperations.delete(operationPromise);
|
|
593
|
+
});
|
|
695
594
|
}
|
|
696
|
-
|
|
697
|
-
|
|
595
|
+
if (this.runningOperations.size > 0) await Promise.race(this.runningOperations);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* 🆕 개별 작업 실행 로직
|
|
600
|
+
*/
|
|
601
|
+
async executeOperation(operation) {
|
|
602
|
+
try {
|
|
603
|
+
const result = await Promise.resolve(operation.operation());
|
|
604
|
+
operation.resolve(result);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
operation.reject(error);
|
|
698
607
|
}
|
|
699
608
|
}
|
|
700
609
|
/**
|
|
701
|
-
* 현재 큐 상태 조회 (디버깅용)
|
|
610
|
+
* 🆕 현재 큐 상태 조회 (디버깅용) - 동시성 정보 포함
|
|
702
611
|
*/
|
|
703
612
|
getQueueInfo() {
|
|
704
613
|
return {
|
|
705
614
|
name: this.name,
|
|
706
615
|
queueLength: this.queue.length,
|
|
707
|
-
isProcessing: this.
|
|
616
|
+
isProcessing: Boolean(this.processingPromise),
|
|
617
|
+
activeOperations: this.activeOperations,
|
|
618
|
+
maxConcurrency: this.maxConcurrency,
|
|
619
|
+
runningOperationsCount: this.runningOperations.size,
|
|
708
620
|
operations: this.queue.map((op) => ({
|
|
709
621
|
id: op.id,
|
|
710
622
|
priority: op.priority,
|
|
@@ -713,6 +625,18 @@ var OperationQueue = class {
|
|
|
713
625
|
};
|
|
714
626
|
}
|
|
715
627
|
/**
|
|
628
|
+
* 🆕 동시성 설정 조회
|
|
629
|
+
*/
|
|
630
|
+
getConcurrencyInfo() {
|
|
631
|
+
return {
|
|
632
|
+
maxConcurrency: this.maxConcurrency,
|
|
633
|
+
activeOperations: this.activeOperations,
|
|
634
|
+
availableSlots: this.maxConcurrency - this.activeOperations,
|
|
635
|
+
queuedOperations: this.queue.length,
|
|
636
|
+
efficiency: this.activeOperations / this.maxConcurrency
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
716
640
|
* 큐 비우기 (테스트용)
|
|
717
641
|
*/
|
|
718
642
|
clear() {
|
|
@@ -720,7 +644,7 @@ var OperationQueue = class {
|
|
|
720
644
|
operation.reject(/* @__PURE__ */ new Error("Queue cleared"));
|
|
721
645
|
});
|
|
722
646
|
this.queue = [];
|
|
723
|
-
this.
|
|
647
|
+
this.processingPromise = null;
|
|
724
648
|
}
|
|
725
649
|
/**
|
|
726
650
|
* 큐 크기 조회
|
|
@@ -732,13 +656,12 @@ var OperationQueue = class {
|
|
|
732
656
|
* 처리 중 여부 조회
|
|
733
657
|
*/
|
|
734
658
|
get processing() {
|
|
735
|
-
return this.
|
|
659
|
+
return Boolean(this.processingPromise);
|
|
736
660
|
}
|
|
737
661
|
};
|
|
738
662
|
|
|
739
663
|
//#endregion
|
|
740
664
|
//#region src/ActionRegister.ts
|
|
741
|
-
var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
742
665
|
/**
|
|
743
666
|
* Action Register for managing action handlers with priority-based execution
|
|
744
667
|
*
|
|
@@ -748,100 +671,31 @@ var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1)
|
|
|
748
671
|
*
|
|
749
672
|
* @template TActionMap - Action payload mapping interface extending ActionPayloadMap
|
|
750
673
|
*
|
|
751
|
-
* @
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
* updateUser: { id: string; name: string; email: string }
|
|
755
|
-
* deleteUser: { id: string }
|
|
756
|
-
* resetUser: void
|
|
757
|
-
* }
|
|
758
|
-
*
|
|
759
|
-
* const register = new ActionRegister<AppActions>({
|
|
760
|
-
* name: 'AppRegister',
|
|
761
|
-
* registry: { debug: true, maxHandlers: 10 }
|
|
762
|
-
* })
|
|
763
|
-
*
|
|
764
|
-
* // Register handler with priority
|
|
765
|
-
* register.register('updateUser', async (payload, controller) => {
|
|
766
|
-
* await userService.update(payload.id, payload)
|
|
767
|
-
* controller.setResult({ success: true, userId: payload.id })
|
|
768
|
-
* }, { priority: 10, tags: ['user', 'crud'] })
|
|
769
|
-
*
|
|
770
|
-
* // Dispatch action
|
|
771
|
-
* await register.dispatch('updateUser', {
|
|
772
|
-
* id: '123',
|
|
773
|
-
* name: 'John Doe',
|
|
774
|
-
* email: 'john@example.com'
|
|
775
|
-
* })
|
|
776
|
-
* ```
|
|
777
|
-
*
|
|
778
|
-
* @example With Multiple Handlers
|
|
779
|
-
* ```typescript
|
|
780
|
-
* // High priority validation handler
|
|
781
|
-
* register.register('updateUser', async (payload, controller) => {
|
|
782
|
-
* if (!payload.email.includes('@')) {
|
|
783
|
-
* controller.abort('Invalid email format')
|
|
784
|
-
* return
|
|
785
|
-
* }
|
|
786
|
-
* }, { priority: 100, category: 'validation' })
|
|
787
|
-
*
|
|
788
|
-
* // Lower priority update handler
|
|
789
|
-
* register.register('updateUser', async (payload, controller) => {
|
|
790
|
-
* const user = await userService.update(payload.id, payload)
|
|
791
|
-
* controller.setResult(user)
|
|
792
|
-
* }, { priority: 50, category: 'business-logic' })
|
|
793
|
-
* ```
|
|
794
|
-
*
|
|
795
|
-
* @example Advanced Configuration
|
|
796
|
-
* ```typescript
|
|
797
|
-
* const register = new ActionRegister<AppActions>({
|
|
798
|
-
* name: 'AdvancedRegister',
|
|
799
|
-
* registry: {
|
|
800
|
-
* debug: true,
|
|
801
|
-
* maxHandlers: 20,
|
|
802
|
-
* defaultExecutionMode: 'parallel',
|
|
803
|
-
* autoCleanup: true
|
|
804
|
-
* }
|
|
805
|
-
* })
|
|
806
|
-
*
|
|
807
|
-
* // Handler with debouncing and tags
|
|
808
|
-
* register.register('searchUsers', async (payload, controller) => {
|
|
809
|
-
* const results = await userService.search(payload.query)
|
|
810
|
-
* controller.setResult(results)
|
|
811
|
-
* }, {
|
|
812
|
-
* priority: 10,
|
|
813
|
-
* debounce: 300,
|
|
814
|
-
* tags: ['search', 'user'],
|
|
815
|
-
* category: 'query',
|
|
816
|
-
* once: false
|
|
817
|
-
* })
|
|
818
|
-
* ```
|
|
674
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/
|
|
675
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
676
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/register-delegation
|
|
819
677
|
*
|
|
820
678
|
* @public
|
|
821
679
|
*/
|
|
822
680
|
var ActionRegister = class {
|
|
823
681
|
constructor(config = {}) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
(0, import_defineProperty.default)(this, "name", void 0);
|
|
830
|
-
(0, import_defineProperty.default)(this, "registryConfig", void 0);
|
|
831
|
-
(0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
|
|
832
|
-
(0, import_defineProperty.default)(this, "registrationQueue", void 0);
|
|
833
|
-
(0, import_defineProperty.default)(this, "dispatchQueue", void 0);
|
|
682
|
+
this.pipelines = /* @__PURE__ */ new Map();
|
|
683
|
+
this.executionMode = "sequential";
|
|
684
|
+
this.actionExecutionModes = /* @__PURE__ */ new Map();
|
|
685
|
+
this.filterCache = /* @__PURE__ */ new Map();
|
|
686
|
+
this.filterCacheMaxSize = 100;
|
|
834
687
|
this.name = config.name || "ActionRegister";
|
|
835
688
|
this.registryConfig = config.registry;
|
|
836
|
-
this.
|
|
837
|
-
this.
|
|
838
|
-
this.
|
|
689
|
+
this.maxHandlersPerAction = config.registry?.maxHandlersPerAction ?? 1e3;
|
|
690
|
+
this.isDebugMode = Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
|
|
691
|
+
this.actionGuard = new ActionGuard(this.registryConfig?.autoCleanup !== false);
|
|
692
|
+
if (config.registry?.useConcurrencyQueue !== false) this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
|
|
839
693
|
if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
|
|
840
|
-
|
|
694
|
+
this.log("ActionRegister initialized", {
|
|
841
695
|
defaultExecutionMode: this.executionMode,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
696
|
+
autoCleanup: this.registryConfig?.autoCleanup !== false,
|
|
697
|
+
concurrencyQueue: Boolean(this.dispatchQueue),
|
|
698
|
+
debugMode: this.isDebugMode
|
|
845
699
|
});
|
|
846
700
|
}
|
|
847
701
|
/**
|
|
@@ -855,105 +709,89 @@ var ActionRegister = class {
|
|
|
855
709
|
*
|
|
856
710
|
* @throws {Error} When maximum handlers limit is reached
|
|
857
711
|
*
|
|
858
|
-
* @
|
|
859
|
-
* ```typescript
|
|
860
|
-
* const unregister = register.register('updateUser', async (payload, controller) => {
|
|
861
|
-
* await userService.update(payload.id, payload)
|
|
862
|
-
* })
|
|
863
|
-
*
|
|
864
|
-
* // Later remove the handler
|
|
865
|
-
* unregister()
|
|
866
|
-
* ```
|
|
867
|
-
*
|
|
868
|
-
* @example With Priority and Configuration
|
|
869
|
-
* ```typescript
|
|
870
|
-
* register.register('validateUser', async (payload, controller) => {
|
|
871
|
-
* if (!payload.email) {
|
|
872
|
-
* controller.abort('Email is required')
|
|
873
|
-
* }
|
|
874
|
-
* }, {
|
|
875
|
-
* priority: 100,
|
|
876
|
-
* tags: ['validation'],
|
|
877
|
-
* category: 'security',
|
|
878
|
-
* once: false
|
|
879
|
-
* })
|
|
880
|
-
* ```
|
|
712
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
881
713
|
*
|
|
882
714
|
* @public
|
|
883
715
|
*/
|
|
884
716
|
register(action, handler, config = {}) {
|
|
885
|
-
const handlerId = config.id ||
|
|
717
|
+
const handlerId = config.id || this.generateHandlerId(action);
|
|
886
718
|
const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
|
|
887
719
|
return unregisterFn;
|
|
888
720
|
}
|
|
889
721
|
/**
|
|
890
|
-
* 🆕
|
|
722
|
+
* 🆕 Unified logging method with cached debug mode check
|
|
891
723
|
*/
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
})
|
|
949
|
-
|
|
724
|
+
log(message, data, level = "log") {
|
|
725
|
+
if (this.isDebugMode) {
|
|
726
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
727
|
+
console[level](`🎯 [${timestamp}] [${this.name}] ${message}`, data || "");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* 🆕 Generate unique handler ID using crypto
|
|
732
|
+
*/
|
|
733
|
+
generateHandlerId(action) {
|
|
734
|
+
const uuid = crypto.randomUUID();
|
|
735
|
+
return `${String(action)}_${uuid.slice(0, 8)}`;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* 🔧 Create and merge AbortSignal instances with proper cleanup
|
|
739
|
+
*
|
|
740
|
+
* @param options Dispatch options containing signal and autoAbort configuration
|
|
741
|
+
* @returns [effectiveSignal, autoAbortController, cleanupFunction]
|
|
742
|
+
*/
|
|
743
|
+
createAbortSignal(options) {
|
|
744
|
+
const signals = [];
|
|
745
|
+
const cleanups = [];
|
|
746
|
+
let autoAbortController;
|
|
747
|
+
if (options?.signal) signals.push(options.signal);
|
|
748
|
+
if (options?.autoAbort?.enabled) {
|
|
749
|
+
autoAbortController = new AbortController();
|
|
750
|
+
signals.push(autoAbortController.signal);
|
|
751
|
+
}
|
|
752
|
+
if (signals.length === 0) return [
|
|
753
|
+
void 0,
|
|
754
|
+
autoAbortController,
|
|
755
|
+
() => {}
|
|
756
|
+
];
|
|
757
|
+
if (signals.length === 1) return [
|
|
758
|
+
signals[0],
|
|
759
|
+
autoAbortController,
|
|
760
|
+
() => cleanups.forEach((c) => c())
|
|
761
|
+
];
|
|
762
|
+
let effectiveSignal;
|
|
763
|
+
if (typeof AbortSignal.any === "function") effectiveSignal = AbortSignal.any(signals);
|
|
764
|
+
else {
|
|
765
|
+
const mergedController = new AbortController();
|
|
766
|
+
effectiveSignal = mergedController.signal;
|
|
767
|
+
signals.forEach((signal) => {
|
|
768
|
+
if (signal.aborted) mergedController.abort();
|
|
769
|
+
else {
|
|
770
|
+
const abortHandler = () => mergedController.abort();
|
|
771
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
772
|
+
cleanups.push(() => signal.removeEventListener("abort", abortHandler));
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
const cleanup = () => {
|
|
777
|
+
cleanups.forEach((c) => {
|
|
778
|
+
try {
|
|
779
|
+
c();
|
|
780
|
+
} catch (error) {
|
|
781
|
+
this.log("Cleanup error during AbortSignal cleanup", error, "warn");
|
|
782
|
+
}
|
|
783
|
+
});
|
|
950
784
|
};
|
|
785
|
+
return [
|
|
786
|
+
effectiveSignal,
|
|
787
|
+
autoAbortController,
|
|
788
|
+
cleanup
|
|
789
|
+
];
|
|
951
790
|
}
|
|
952
791
|
/**
|
|
953
|
-
* 🆕
|
|
954
|
-
* @deprecated Currently unused - reserved for future queue-based registration
|
|
792
|
+
* 🆕 Perform synchronous handler registration
|
|
955
793
|
*/
|
|
956
|
-
|
|
794
|
+
_performRegistrationSync(action, handler, config, handlerId) {
|
|
957
795
|
const registration = {
|
|
958
796
|
handler,
|
|
959
797
|
config: {
|
|
@@ -961,54 +799,66 @@ var ActionRegister = class {
|
|
|
961
799
|
id: handlerId,
|
|
962
800
|
blocking: config.blocking ?? false,
|
|
963
801
|
once: config.once ?? false,
|
|
964
|
-
condition: config.condition || (() => true),
|
|
965
802
|
debounce: config.debounce ?? void 0,
|
|
966
803
|
throttle: config.throttle ?? void 0,
|
|
967
|
-
|
|
968
|
-
middleware: config.middleware ?? false,
|
|
969
|
-
tags: config.tags ?? [],
|
|
970
|
-
category: config.category ?? void 0,
|
|
971
|
-
description: config.description ?? void 0,
|
|
972
|
-
version: config.version ?? void 0,
|
|
973
|
-
returnType: config.returnType ?? "value",
|
|
974
|
-
timeout: config.timeout ?? void 0,
|
|
975
|
-
retries: config.retries ?? 0,
|
|
976
|
-
dependencies: config.dependencies ?? [],
|
|
977
|
-
conflicts: config.conflicts ?? [],
|
|
978
|
-
environment: config.environment ?? void 0,
|
|
979
|
-
feature: config.feature ?? void 0,
|
|
980
|
-
metrics: config.metrics ?? {
|
|
981
|
-
collectTiming: false,
|
|
982
|
-
collectErrors: false,
|
|
983
|
-
customMetrics: {}
|
|
984
|
-
},
|
|
985
|
-
metadata: config.metadata ?? {}
|
|
804
|
+
replaceExisting: config.replaceExisting ?? false
|
|
986
805
|
},
|
|
987
806
|
id: handlerId
|
|
988
807
|
};
|
|
989
808
|
if (!this.pipelines.has(action)) this.pipelines.set(action, []);
|
|
990
809
|
const pipeline = this.pipelines.get(action);
|
|
810
|
+
if (pipeline.length >= this.maxHandlersPerAction) {
|
|
811
|
+
console.warn(`Handler limit (${this.maxHandlersPerAction}) reached for action "${String(action)}". Registration ignored.`);
|
|
812
|
+
return () => {};
|
|
813
|
+
}
|
|
991
814
|
const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
|
|
992
|
-
if (existingIndex !== -1)
|
|
993
|
-
|
|
815
|
+
if (existingIndex !== -1) if (config.replaceExisting) {
|
|
816
|
+
const oldRegistration = pipeline[existingIndex];
|
|
817
|
+
if (oldRegistration && typeof oldRegistration.cleanup === "function") try {
|
|
818
|
+
oldRegistration.cleanup();
|
|
819
|
+
} catch (cleanupError) {
|
|
820
|
+
this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
|
|
821
|
+
}
|
|
822
|
+
pipeline[existingIndex] = registration;
|
|
823
|
+
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
824
|
+
this.invalidateFilterCache();
|
|
825
|
+
this.log(`Handler replaced: ${String(action)}`, {
|
|
826
|
+
handlerId,
|
|
827
|
+
priority: config.priority,
|
|
828
|
+
totalHandlers: pipeline.length,
|
|
829
|
+
oldHandlerCleaned: Boolean(oldRegistration.cleanup)
|
|
830
|
+
});
|
|
831
|
+
return () => {
|
|
832
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
833
|
+
if (index !== -1) {
|
|
834
|
+
pipeline.splice(index, 1);
|
|
835
|
+
this.invalidateFilterCache();
|
|
836
|
+
this.log(`Replaced handler unregistered: ${String(action)}`, { handlerId });
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
} else {
|
|
840
|
+
this.log(`Handler duplicate ignored: ${String(action)}`, {
|
|
841
|
+
handlerId,
|
|
842
|
+
note: "Use replaceExisting:true to replace"
|
|
843
|
+
}, "warn");
|
|
844
|
+
return () => {};
|
|
845
|
+
}
|
|
994
846
|
pipeline.push(registration);
|
|
995
847
|
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
996
|
-
|
|
848
|
+
this.invalidateFilterCache();
|
|
849
|
+
this.log(`Handler registered: ${String(action)}`, {
|
|
997
850
|
handlerId,
|
|
998
851
|
priority: config.priority,
|
|
999
|
-
|
|
1000
|
-
category: config.category,
|
|
1001
|
-
totalHandlers: pipeline.length,
|
|
1002
|
-
registry: this.name
|
|
852
|
+
totalHandlers: pipeline.length
|
|
1003
853
|
});
|
|
1004
854
|
return () => {
|
|
1005
855
|
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
1006
856
|
if (index !== -1) {
|
|
1007
857
|
pipeline.splice(index, 1);
|
|
1008
|
-
|
|
858
|
+
this.invalidateFilterCache();
|
|
859
|
+
this.log(`Handler unregistered: ${String(action)}`, {
|
|
1009
860
|
handlerId,
|
|
1010
|
-
remainingHandlers: pipeline.length
|
|
1011
|
-
registry: this.name
|
|
861
|
+
remainingHandlers: pipeline.length
|
|
1012
862
|
});
|
|
1013
863
|
}
|
|
1014
864
|
};
|
|
@@ -1024,39 +874,13 @@ var ActionRegister = class {
|
|
|
1024
874
|
*
|
|
1025
875
|
* @throws {Error} When action dispatching fails
|
|
1026
876
|
*
|
|
1027
|
-
* @
|
|
1028
|
-
* ```typescript
|
|
1029
|
-
* await register.dispatch('updateUser', {
|
|
1030
|
-
* id: '123',
|
|
1031
|
-
* name: 'John Doe',
|
|
1032
|
-
* email: 'john@example.com'
|
|
1033
|
-
* })
|
|
1034
|
-
* ```
|
|
1035
|
-
*
|
|
1036
|
-
* @example With Options
|
|
1037
|
-
* ```typescript
|
|
1038
|
-
* await register.dispatch('updateUser', payload, {
|
|
1039
|
-
* executionMode: 'parallel',
|
|
1040
|
-
* timeout: 5000,
|
|
1041
|
-
* filter: {
|
|
1042
|
-
* tags: ['validation', 'business-logic'],
|
|
1043
|
-
* excludeCategory: 'analytics'
|
|
1044
|
-
* }
|
|
1045
|
-
* })
|
|
1046
|
-
* ```
|
|
1047
|
-
*
|
|
1048
|
-
* @example With Throttling
|
|
1049
|
-
* ```typescript
|
|
1050
|
-
* await register.dispatch('searchUsers', { query: 'john' }, {
|
|
1051
|
-
* throttle: 300,
|
|
1052
|
-
* debounce: 100
|
|
1053
|
-
* })
|
|
1054
|
-
* ```
|
|
877
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1055
878
|
*
|
|
1056
879
|
* @public
|
|
1057
880
|
*/
|
|
1058
881
|
async dispatch(action, payload, options) {
|
|
1059
|
-
|
|
882
|
+
if (options?.immediate || !this.dispatchQueue) return this._performDispatch(action, payload, options);
|
|
883
|
+
else return this.dispatchQueue.enqueue(async () => {
|
|
1060
884
|
return this._performDispatch(action, payload, options);
|
|
1061
885
|
});
|
|
1062
886
|
}
|
|
@@ -1064,47 +888,13 @@ var ActionRegister = class {
|
|
|
1064
888
|
* 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
|
|
1065
889
|
*/
|
|
1066
890
|
async _performDispatch(action, payload, options) {
|
|
1067
|
-
if (payload &&
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
payload.preventDefault;
|
|
1071
|
-
payload.stopPropagation;
|
|
1072
|
-
payload.currentTarget;
|
|
1073
|
-
const hasTarget = payload.target !== void 0;
|
|
1074
|
-
hasTarget && payload.target;
|
|
1075
|
-
hasTarget && payload.target instanceof Element;
|
|
1076
|
-
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION || typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
1077
|
-
const nestedDOMProperties = [];
|
|
1078
|
-
Object.keys(payload).forEach((key) => {
|
|
1079
|
-
const prop = payload[key];
|
|
1080
|
-
if (prop instanceof Element || prop instanceof Event) nestedDOMProperties.push(`${key}: ${prop instanceof Element ? "Element" : "Event"}`);
|
|
1081
|
-
});
|
|
1082
|
-
if (nestedDOMProperties.length > 0) console.debug(`[Context-Action] 📋 Nested DOM objects in action "${String(action)}":`, {
|
|
1083
|
-
registry: this.name,
|
|
1084
|
-
nestedDOMProperties,
|
|
1085
|
-
note: "This is informational - usually not a problem"
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
let autoAbortController;
|
|
1090
|
-
let effectiveSignal = options?.signal;
|
|
1091
|
-
if (options?.autoAbort?.enabled) {
|
|
1092
|
-
autoAbortController = new AbortController();
|
|
1093
|
-
effectiveSignal = autoAbortController.signal;
|
|
1094
|
-
if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
|
|
1095
|
-
if (options?.signal) {
|
|
1096
|
-
const originalSignal = options.signal;
|
|
1097
|
-
if (originalSignal.aborted) autoAbortController.abort();
|
|
1098
|
-
else {
|
|
1099
|
-
const abortHandler$1 = () => autoAbortController.abort();
|
|
1100
|
-
originalSignal.addEventListener("abort", abortHandler$1, { once: true });
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
891
|
+
if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
|
|
892
|
+
const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
|
|
893
|
+
if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
|
|
1104
894
|
if (effectiveSignal?.aborted) return;
|
|
1105
895
|
const pipeline = this.pipelines.get(action);
|
|
1106
896
|
if (!pipeline || pipeline.length === 0) return;
|
|
1107
|
-
const filteredHandlers = this.filterHandlers(
|
|
897
|
+
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
1108
898
|
const actionKey = String(action);
|
|
1109
899
|
let throttleMs;
|
|
1110
900
|
let debounceMs;
|
|
@@ -1144,8 +934,6 @@ var ActionRegister = class {
|
|
|
1144
934
|
terminated: false,
|
|
1145
935
|
terminationResult: void 0
|
|
1146
936
|
};
|
|
1147
|
-
const startTime = Date.now();
|
|
1148
|
-
let executionSuccess = true;
|
|
1149
937
|
const abortHandler = effectiveSignal ? () => {
|
|
1150
938
|
context.aborted = true;
|
|
1151
939
|
context.abortReason = "Action dispatch aborted by signal";
|
|
@@ -1153,15 +941,12 @@ var ActionRegister = class {
|
|
|
1153
941
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
1154
942
|
try {
|
|
1155
943
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
1156
|
-
|
|
944
|
+
this.log(`Pipeline execution succeeded for ${String(action)}`);
|
|
1157
945
|
} catch (error) {
|
|
1158
|
-
|
|
1159
|
-
executionSuccess = false;
|
|
946
|
+
this.log(`Pipeline execution failed for ${String(action)}`, error, "error");
|
|
1160
947
|
throw error;
|
|
1161
948
|
} finally {
|
|
1162
|
-
|
|
1163
|
-
const duration = Date.now() - startTime;
|
|
1164
|
-
this.updateExecutionStats(action, executionSuccess, duration);
|
|
949
|
+
cleanup();
|
|
1165
950
|
}
|
|
1166
951
|
}
|
|
1167
952
|
/**
|
|
@@ -1173,65 +958,30 @@ var ActionRegister = class {
|
|
|
1173
958
|
*
|
|
1174
959
|
* @returns Promise resolving to comprehensive execution results
|
|
1175
960
|
*
|
|
1176
|
-
* @
|
|
1177
|
-
* ```typescript
|
|
1178
|
-
* const result = await register.dispatchWithResult('updateUser', payload)
|
|
1179
|
-
*
|
|
1180
|
-
* if (result.success) {
|
|
1181
|
-
* console.log(`Executed ${result.execution.handlersExecuted} handlers`)
|
|
1182
|
-
* console.log(`Duration: ${result.execution.duration}ms`)
|
|
1183
|
-
* }
|
|
1184
|
-
* ```
|
|
1185
|
-
*
|
|
1186
|
-
* @example Advanced Result Processing
|
|
1187
|
-
* ```typescript
|
|
1188
|
-
* const result = await register.dispatchWithResult('processOrder', order, {
|
|
1189
|
-
* result: {
|
|
1190
|
-
* collect: true,
|
|
1191
|
-
* strategy: 'merge',
|
|
1192
|
-
* maxResults: 5,
|
|
1193
|
-
* merger: (results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
|
|
1194
|
-
* }
|
|
1195
|
-
* })
|
|
1196
|
-
*
|
|
1197
|
-
* if (result.terminated) {
|
|
1198
|
-
* console.log('Handler returned early:', result.result)
|
|
1199
|
-
* }
|
|
1200
|
-
* ```
|
|
961
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1201
962
|
*
|
|
1202
963
|
* @public
|
|
1203
964
|
*/
|
|
1204
965
|
async dispatchWithResult(action, payload, options) {
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
if (options?.autoAbort?.enabled) {
|
|
1209
|
-
autoAbortController = new AbortController();
|
|
1210
|
-
effectiveSignal = autoAbortController.signal;
|
|
1211
|
-
if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
|
|
1212
|
-
if (options?.signal) {
|
|
1213
|
-
const originalSignal = options.signal;
|
|
1214
|
-
if (originalSignal.aborted) autoAbortController.abort();
|
|
1215
|
-
else {
|
|
1216
|
-
const abortHandler$1 = () => autoAbortController.abort();
|
|
1217
|
-
originalSignal.addEventListener("abort", abortHandler$1, { once: true });
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
966
|
+
const _startTime = Date.now();
|
|
967
|
+
const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
|
|
968
|
+
if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
|
|
1221
969
|
if (effectiveSignal?.aborted) return {
|
|
1222
970
|
success: false,
|
|
1223
971
|
aborted: true,
|
|
1224
972
|
abortReason: "Action dispatch aborted by signal",
|
|
1225
973
|
terminated: false,
|
|
1226
974
|
result: void 0,
|
|
975
|
+
successResults: [],
|
|
1227
976
|
results: [],
|
|
977
|
+
failedResults: [],
|
|
1228
978
|
execution: {
|
|
1229
979
|
duration: 0,
|
|
1230
980
|
handlersExecuted: 0,
|
|
1231
981
|
handlersSkipped: 0,
|
|
1232
982
|
handlersFailed: 0,
|
|
1233
|
-
startTime,
|
|
1234
|
-
endTime:
|
|
983
|
+
startTime: _startTime,
|
|
984
|
+
endTime: _startTime
|
|
1235
985
|
},
|
|
1236
986
|
handlers: [],
|
|
1237
987
|
errors: []
|
|
@@ -1242,19 +992,21 @@ var ActionRegister = class {
|
|
|
1242
992
|
aborted: false,
|
|
1243
993
|
terminated: false,
|
|
1244
994
|
result: void 0,
|
|
995
|
+
successResults: [],
|
|
1245
996
|
results: [],
|
|
997
|
+
failedResults: [],
|
|
1246
998
|
execution: {
|
|
1247
999
|
duration: 0,
|
|
1248
1000
|
handlersExecuted: 0,
|
|
1249
1001
|
handlersSkipped: 0,
|
|
1250
1002
|
handlersFailed: 0,
|
|
1251
|
-
startTime,
|
|
1252
|
-
endTime:
|
|
1003
|
+
startTime: _startTime,
|
|
1004
|
+
endTime: _startTime
|
|
1253
1005
|
},
|
|
1254
1006
|
handlers: [],
|
|
1255
1007
|
errors: []
|
|
1256
1008
|
};
|
|
1257
|
-
const filteredHandlers = this.filterHandlers(
|
|
1009
|
+
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
1258
1010
|
const actionKey = String(action);
|
|
1259
1011
|
let throttleMs;
|
|
1260
1012
|
let debounceMs;
|
|
@@ -1280,13 +1032,15 @@ var ActionRegister = class {
|
|
|
1280
1032
|
abortReason: "Debounced execution",
|
|
1281
1033
|
terminated: false,
|
|
1282
1034
|
result: void 0,
|
|
1035
|
+
successResults: [],
|
|
1283
1036
|
results: [],
|
|
1037
|
+
failedResults: [],
|
|
1284
1038
|
execution: {
|
|
1285
|
-
duration: Date.now() -
|
|
1039
|
+
duration: Date.now() - _startTime,
|
|
1286
1040
|
handlersExecuted: 0,
|
|
1287
1041
|
handlersSkipped: pipeline.length,
|
|
1288
1042
|
handlersFailed: 0,
|
|
1289
|
-
startTime,
|
|
1043
|
+
startTime: _startTime,
|
|
1290
1044
|
endTime: Date.now()
|
|
1291
1045
|
},
|
|
1292
1046
|
handlers: [],
|
|
@@ -1301,13 +1055,15 @@ var ActionRegister = class {
|
|
|
1301
1055
|
abortReason: "Throttled execution",
|
|
1302
1056
|
terminated: false,
|
|
1303
1057
|
result: void 0,
|
|
1058
|
+
successResults: [],
|
|
1304
1059
|
results: [],
|
|
1060
|
+
failedResults: [],
|
|
1305
1061
|
execution: {
|
|
1306
|
-
duration: Date.now() -
|
|
1062
|
+
duration: Date.now() - _startTime,
|
|
1307
1063
|
handlersExecuted: 0,
|
|
1308
1064
|
handlersSkipped: pipeline.length,
|
|
1309
1065
|
handlersFailed: 0,
|
|
1310
|
-
startTime,
|
|
1066
|
+
startTime: _startTime,
|
|
1311
1067
|
endTime: Date.now()
|
|
1312
1068
|
},
|
|
1313
1069
|
handlers: [],
|
|
@@ -1346,59 +1102,92 @@ var ActionRegister = class {
|
|
|
1346
1102
|
timestamp: Date.now()
|
|
1347
1103
|
});
|
|
1348
1104
|
} finally {
|
|
1349
|
-
|
|
1105
|
+
cleanup();
|
|
1350
1106
|
}
|
|
1351
1107
|
const endTime = Date.now();
|
|
1352
|
-
|
|
1353
|
-
this.updateExecutionStats(action, executionSuccess, endTime - startTime);
|
|
1108
|
+
!executionError && context.aborted;
|
|
1354
1109
|
const processedResult = this.processResults(context, options?.result);
|
|
1110
|
+
const successResults = context.results.filter((result) => result !== void 0);
|
|
1111
|
+
const failedResults = errors.map((err) => ({
|
|
1112
|
+
handlerId: err.handlerId,
|
|
1113
|
+
error: err.error,
|
|
1114
|
+
expectedType: typeof processedResult
|
|
1115
|
+
}));
|
|
1355
1116
|
const executionResult = {
|
|
1356
1117
|
success: !executionError && !context.aborted,
|
|
1357
1118
|
aborted: context.aborted,
|
|
1358
1119
|
abortReason: context.abortReason,
|
|
1359
1120
|
terminated: context.terminated,
|
|
1360
1121
|
result: processedResult,
|
|
1122
|
+
successResults,
|
|
1361
1123
|
results: context.results,
|
|
1124
|
+
failedResults,
|
|
1362
1125
|
execution: {
|
|
1363
|
-
duration: endTime -
|
|
1126
|
+
duration: endTime - _startTime,
|
|
1364
1127
|
handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
|
|
1365
1128
|
handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
|
|
1366
1129
|
handlersFailed: errors.length,
|
|
1367
|
-
startTime,
|
|
1130
|
+
startTime: _startTime,
|
|
1368
1131
|
endTime
|
|
1369
1132
|
},
|
|
1370
1133
|
handlers: handlerResults,
|
|
1371
|
-
errors
|
|
1134
|
+
errors: errors.map((err) => ({
|
|
1135
|
+
handlerId: err.handlerId,
|
|
1136
|
+
error: err.error,
|
|
1137
|
+
timestamp: err.timestamp,
|
|
1138
|
+
severity: "non-blocking"
|
|
1139
|
+
}))
|
|
1372
1140
|
};
|
|
1373
1141
|
/** Clean up one-time handlers after execution */
|
|
1374
1142
|
this.cleanupOneTimeHandlers(action, context.handlers);
|
|
1375
1143
|
return executionResult;
|
|
1376
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* 🔧 Generate cache key for filter options
|
|
1147
|
+
*/
|
|
1148
|
+
generateFilterCacheKey(filterOptions) {
|
|
1149
|
+
if (!filterOptions) return "no-filter";
|
|
1150
|
+
const key = [
|
|
1151
|
+
filterOptions.handlerIds?.sort().join(",") || "none",
|
|
1152
|
+
filterOptions.excludeHandlerIds?.sort().join(",") || "none",
|
|
1153
|
+
filterOptions.priority?.min?.toString() || "none",
|
|
1154
|
+
filterOptions.priority?.max?.toString() || "none",
|
|
1155
|
+
filterOptions.custom ? "custom" : "none"
|
|
1156
|
+
].join("|");
|
|
1157
|
+
return key;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* 🔧 Clear filter cache when pipelines change
|
|
1161
|
+
*/
|
|
1162
|
+
invalidateFilterCache() {
|
|
1163
|
+
this.filterCache.clear();
|
|
1164
|
+
}
|
|
1377
1165
|
filterHandlers(handlers, filterOptions) {
|
|
1378
1166
|
if (!filterOptions) return handlers;
|
|
1379
|
-
|
|
1167
|
+
const cacheKey = this.generateFilterCacheKey(filterOptions);
|
|
1168
|
+
if (!filterOptions.custom) {
|
|
1169
|
+
const cached = this.filterCache.get(cacheKey);
|
|
1170
|
+
if (cached) return cached;
|
|
1171
|
+
}
|
|
1172
|
+
const filtered = handlers.filter((registration) => {
|
|
1380
1173
|
const config = registration.config;
|
|
1381
|
-
if (filterOptions.
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
if (filterOptions.handlerIds && filterOptions.handlerIds.length > 0) {
|
|
1387
|
-
if (!filterOptions.handlerIds.includes(config.id)) return false;
|
|
1388
|
-
}
|
|
1389
|
-
if (filterOptions.environment && config.environment !== filterOptions.environment) return false;
|
|
1390
|
-
if (filterOptions.feature && config.feature !== filterOptions.feature) return false;
|
|
1391
|
-
if (filterOptions.excludeTags && filterOptions.excludeTags.length > 0) {
|
|
1392
|
-
const hasExcludedTag = filterOptions.excludeTags.some((tag) => config.tags.includes(tag));
|
|
1393
|
-
if (hasExcludedTag) return false;
|
|
1394
|
-
}
|
|
1395
|
-
if (filterOptions.excludeCategory && config.category === filterOptions.excludeCategory) return false;
|
|
1396
|
-
if (filterOptions.excludeHandlerIds && filterOptions.excludeHandlerIds.length > 0) {
|
|
1397
|
-
if (filterOptions.excludeHandlerIds.includes(config.id)) return false;
|
|
1174
|
+
if (filterOptions.handlerIds?.length && !filterOptions.handlerIds.includes(config.id)) return false;
|
|
1175
|
+
if (filterOptions.excludeHandlerIds?.length && filterOptions.excludeHandlerIds.includes(config.id)) return false;
|
|
1176
|
+
if (filterOptions.priority) {
|
|
1177
|
+
if (filterOptions.priority.min !== void 0 && config.priority < filterOptions.priority.min) return false;
|
|
1178
|
+
if (filterOptions.priority.max !== void 0 && config.priority > filterOptions.priority.max) return false;
|
|
1398
1179
|
}
|
|
1399
1180
|
if (filterOptions.custom && !filterOptions.custom(config)) return false;
|
|
1400
1181
|
return true;
|
|
1401
1182
|
});
|
|
1183
|
+
if (!filterOptions.custom) {
|
|
1184
|
+
if (this.filterCache.size >= this.filterCacheMaxSize) {
|
|
1185
|
+
const firstKey = this.filterCache.keys().next().value;
|
|
1186
|
+
if (firstKey) this.filterCache.delete(firstKey);
|
|
1187
|
+
}
|
|
1188
|
+
this.filterCache.set(cacheKey, filtered);
|
|
1189
|
+
}
|
|
1190
|
+
return filtered;
|
|
1402
1191
|
}
|
|
1403
1192
|
processResults(context, resultOptions) {
|
|
1404
1193
|
if (!resultOptions || !resultOptions.collect) return void 0;
|
|
@@ -1484,39 +1273,13 @@ var ActionRegister = class {
|
|
|
1484
1273
|
});
|
|
1485
1274
|
}
|
|
1486
1275
|
/**
|
|
1487
|
-
* Update execution statistics for an action
|
|
1488
|
-
*
|
|
1489
|
-
* @param action Action name
|
|
1490
|
-
* @param success Whether execution was successful
|
|
1491
|
-
* @param duration Execution duration in milliseconds
|
|
1492
|
-
*/
|
|
1493
|
-
updateExecutionStats(action, success, duration) {
|
|
1494
|
-
if (!this.executionStats.has(action)) this.executionStats.set(action, {
|
|
1495
|
-
totalExecutions: 0,
|
|
1496
|
-
totalDuration: 0,
|
|
1497
|
-
successCount: 0,
|
|
1498
|
-
errorCount: 0
|
|
1499
|
-
});
|
|
1500
|
-
const stats = this.executionStats.get(action);
|
|
1501
|
-
stats.totalExecutions++;
|
|
1502
|
-
stats.totalDuration += duration;
|
|
1503
|
-
if (success) stats.successCount++;
|
|
1504
|
-
else stats.errorCount++;
|
|
1505
|
-
}
|
|
1506
|
-
/**
|
|
1507
1276
|
* Get the number of registered handlers for an action
|
|
1508
1277
|
*
|
|
1509
1278
|
* @param action - The action type to count handlers for
|
|
1510
1279
|
*
|
|
1511
1280
|
* @returns Number of registered handlers
|
|
1512
1281
|
*
|
|
1513
|
-
* @
|
|
1514
|
-
* ```typescript
|
|
1515
|
-
* register.register('updateUser', handler1)
|
|
1516
|
-
* register.register('updateUser', handler2)
|
|
1517
|
-
*
|
|
1518
|
-
* console.log(register.getHandlerCount('updateUser')) // 2
|
|
1519
|
-
* ```
|
|
1282
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1520
1283
|
*
|
|
1521
1284
|
* @public
|
|
1522
1285
|
*/
|
|
@@ -1531,12 +1294,7 @@ var ActionRegister = class {
|
|
|
1531
1294
|
*
|
|
1532
1295
|
* @returns True if action has handlers, false otherwise
|
|
1533
1296
|
*
|
|
1534
|
-
* @
|
|
1535
|
-
* ```typescript
|
|
1536
|
-
* if (register.hasHandlers('updateUser')) {
|
|
1537
|
-
* await register.dispatch('updateUser', userData)
|
|
1538
|
-
* }
|
|
1539
|
-
* ```
|
|
1297
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1540
1298
|
*
|
|
1541
1299
|
* @public
|
|
1542
1300
|
*/
|
|
@@ -1548,11 +1306,7 @@ var ActionRegister = class {
|
|
|
1548
1306
|
*
|
|
1549
1307
|
* @returns Array of all registered action types
|
|
1550
1308
|
*
|
|
1551
|
-
* @
|
|
1552
|
-
* ```typescript
|
|
1553
|
-
* const actions = register.getRegisteredActions()
|
|
1554
|
-
* console.log('Registered actions:', actions) // ['updateUser', 'deleteUser', 'resetUser']
|
|
1555
|
-
* ```
|
|
1309
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1556
1310
|
*
|
|
1557
1311
|
* @public
|
|
1558
1312
|
*/
|
|
@@ -1564,41 +1318,31 @@ var ActionRegister = class {
|
|
|
1564
1318
|
*
|
|
1565
1319
|
* @param action - The action type to clear handlers for
|
|
1566
1320
|
*
|
|
1567
|
-
* @
|
|
1568
|
-
* ```typescript
|
|
1569
|
-
* register.clearAction('updateUser')
|
|
1570
|
-
* console.log(register.hasHandlers('updateUser')) // false
|
|
1571
|
-
* ```
|
|
1321
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1572
1322
|
*
|
|
1573
1323
|
* @public
|
|
1574
1324
|
*/
|
|
1575
1325
|
clearAction(action) {
|
|
1576
1326
|
this.pipelines.delete(action);
|
|
1327
|
+
this.invalidateFilterCache();
|
|
1577
1328
|
}
|
|
1578
1329
|
/**
|
|
1579
1330
|
* Remove all handlers for all actions
|
|
1580
1331
|
*
|
|
1581
|
-
* @
|
|
1582
|
-
* ```typescript
|
|
1583
|
-
* register.clearAll()
|
|
1584
|
-
* console.log(register.getRegisteredActions().length) // 0
|
|
1585
|
-
* ```
|
|
1332
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1586
1333
|
*
|
|
1587
1334
|
* @public
|
|
1588
1335
|
*/
|
|
1589
1336
|
clearAll() {
|
|
1590
1337
|
this.pipelines.clear();
|
|
1338
|
+
this.invalidateFilterCache();
|
|
1591
1339
|
}
|
|
1592
1340
|
/**
|
|
1593
1341
|
* Get the name of this action register
|
|
1594
1342
|
*
|
|
1595
1343
|
* @returns The register name
|
|
1596
1344
|
*
|
|
1597
|
-
* @
|
|
1598
|
-
* ```typescript
|
|
1599
|
-
* const register = new ActionRegister({ name: 'UserRegister' })
|
|
1600
|
-
* console.log(register.getName()) // 'UserRegister'
|
|
1601
|
-
* ```
|
|
1345
|
+
* @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
|
|
1602
1346
|
*
|
|
1603
1347
|
* @public
|
|
1604
1348
|
*/
|
|
@@ -1637,21 +1381,9 @@ var ActionRegister = class {
|
|
|
1637
1381
|
});
|
|
1638
1382
|
const handlersByPriority = Array.from(priorityMap.entries()).sort(([a], [b]) => b - a).map(([priority, handlers]) => ({
|
|
1639
1383
|
priority,
|
|
1640
|
-
handlers: handlers.map((h) => ({
|
|
1641
|
-
id: h.config.id,
|
|
1642
|
-
tags: h.config.tags,
|
|
1643
|
-
category: h.config.category,
|
|
1644
|
-
description: h.config.description,
|
|
1645
|
-
version: h.config.version
|
|
1646
|
-
}))
|
|
1384
|
+
handlers: handlers.map((h) => ({ id: h.config.id }))
|
|
1647
1385
|
}));
|
|
1648
|
-
const
|
|
1649
|
-
const executionStats = stats ? {
|
|
1650
|
-
totalExecutions: stats.totalExecutions,
|
|
1651
|
-
averageDuration: stats.totalExecutions > 0 ? stats.totalDuration / stats.totalExecutions : 0,
|
|
1652
|
-
successRate: stats.totalExecutions > 0 ? stats.successCount / stats.totalExecutions * 100 : 0,
|
|
1653
|
-
errorCount: stats.errorCount
|
|
1654
|
-
} : void 0;
|
|
1386
|
+
const executionStats = void 0;
|
|
1655
1387
|
return {
|
|
1656
1388
|
action,
|
|
1657
1389
|
handlerCount: pipeline.length,
|
|
@@ -1669,34 +1401,6 @@ var ActionRegister = class {
|
|
|
1669
1401
|
return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
|
|
1670
1402
|
}
|
|
1671
1403
|
/**
|
|
1672
|
-
* Get handlers by tag across all actions
|
|
1673
|
-
*
|
|
1674
|
-
* @param tag Tag to filter handlers by
|
|
1675
|
-
* @returns Map of actions to handlers with the specified tag
|
|
1676
|
-
*/
|
|
1677
|
-
getHandlersByTag(tag) {
|
|
1678
|
-
const result = /* @__PURE__ */ new Map();
|
|
1679
|
-
for (const [action, pipeline] of this.pipelines.entries()) {
|
|
1680
|
-
const matchingHandlers = pipeline.filter((handler) => handler.config.tags.includes(tag));
|
|
1681
|
-
if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
|
|
1682
|
-
}
|
|
1683
|
-
return result;
|
|
1684
|
-
}
|
|
1685
|
-
/**
|
|
1686
|
-
* Get handlers by category across all actions
|
|
1687
|
-
*
|
|
1688
|
-
* @param category Category to filter handlers by
|
|
1689
|
-
* @returns Map of actions to handlers with the specified category
|
|
1690
|
-
*/
|
|
1691
|
-
getHandlersByCategory(category) {
|
|
1692
|
-
const result = /* @__PURE__ */ new Map();
|
|
1693
|
-
for (const [action, pipeline] of this.pipelines.entries()) {
|
|
1694
|
-
const matchingHandlers = pipeline.filter((handler) => handler.config.category === category);
|
|
1695
|
-
if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
|
|
1696
|
-
}
|
|
1697
|
-
return result;
|
|
1698
|
-
}
|
|
1699
|
-
/**
|
|
1700
1404
|
* Set execution mode for a specific action
|
|
1701
1405
|
*
|
|
1702
1406
|
* @param action Action name
|
|
@@ -1725,22 +1429,6 @@ var ActionRegister = class {
|
|
|
1725
1429
|
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
|
|
1726
1430
|
}
|
|
1727
1431
|
/**
|
|
1728
|
-
* Clear execution statistics for all actions
|
|
1729
|
-
*/
|
|
1730
|
-
clearExecutionStats() {
|
|
1731
|
-
this.executionStats.clear();
|
|
1732
|
-
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for registry: ${this.name}`);
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* Clear execution statistics for a specific action
|
|
1736
|
-
*
|
|
1737
|
-
* @param action Action name
|
|
1738
|
-
*/
|
|
1739
|
-
clearActionExecutionStats(action) {
|
|
1740
|
-
this.executionStats.delete(action);
|
|
1741
|
-
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for action: ${String(action)}`);
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
1432
|
* Get registry configuration (for debugging and inspection)
|
|
1745
1433
|
*
|
|
1746
1434
|
* @returns Current registry configuration
|
|
@@ -1754,13 +1442,292 @@ var ActionRegister = class {
|
|
|
1754
1442
|
* @returns Whether debug mode is enabled
|
|
1755
1443
|
*/
|
|
1756
1444
|
isDebugEnabled() {
|
|
1757
|
-
return
|
|
1445
|
+
return this.isDebugMode;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* 🆕 Destroy method for comprehensive cleanup
|
|
1449
|
+
*
|
|
1450
|
+
* Cleans up all internal resources including pipelines, guards, queues, and statistics.
|
|
1451
|
+
* Should be called when the ActionRegister is no longer needed to prevent memory leaks.
|
|
1452
|
+
*
|
|
1453
|
+
* @public
|
|
1454
|
+
*/
|
|
1455
|
+
destroy() {
|
|
1456
|
+
this.pipelines.clear();
|
|
1457
|
+
this.actionGuard.destroy();
|
|
1458
|
+
this.dispatchQueue?.clear?.();
|
|
1459
|
+
this.actionExecutionModes.clear();
|
|
1460
|
+
this.filterCache.clear();
|
|
1461
|
+
this.log("ActionRegister destroyed");
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
//#endregion
|
|
1466
|
+
//#region src/react-helpers.ts
|
|
1467
|
+
/**
|
|
1468
|
+
* 🔧 Create action handler registration configuration for React components
|
|
1469
|
+
*
|
|
1470
|
+
* Creates a configuration object that can be used with React's useEffect to properly
|
|
1471
|
+
* register and unregister action handlers with lifecycle management and cleanup.
|
|
1472
|
+
* This is NOT a hook - it's a factory function for React hook integration.
|
|
1473
|
+
*
|
|
1474
|
+
* @template T - ActionPayloadMap type
|
|
1475
|
+
* @template K - Action key type
|
|
1476
|
+
*
|
|
1477
|
+
* @param registry - ActionRegister instance
|
|
1478
|
+
* @param action - Action name to register handler for
|
|
1479
|
+
* @param handler - Handler function (should be memoized with useCallback)
|
|
1480
|
+
* @param config - Handler configuration
|
|
1481
|
+
*
|
|
1482
|
+
* @returns Configuration object with register/unregister functions
|
|
1483
|
+
*
|
|
1484
|
+
* @example Basic Usage with useEffect
|
|
1485
|
+
* ```tsx
|
|
1486
|
+
* import { useCallback, useEffect } from 'react';
|
|
1487
|
+
* import { createActionHandler } from '@context-action/core/react-helpers';
|
|
1488
|
+
*
|
|
1489
|
+
* function MyComponent() {
|
|
1490
|
+
* const registry = useActionRegister();
|
|
1491
|
+
*
|
|
1492
|
+
* const handleUserUpdate = useCallback(async (payload, controller) => {
|
|
1493
|
+
* // Handler logic here
|
|
1494
|
+
* }, []);
|
|
1495
|
+
*
|
|
1496
|
+
* useEffect(() => {
|
|
1497
|
+
* const { register, unregister } = createActionHandler(
|
|
1498
|
+
* registry,
|
|
1499
|
+
* 'updateUser',
|
|
1500
|
+
* handleUserUpdate,
|
|
1501
|
+
* { priority: 10 }
|
|
1502
|
+
* );
|
|
1503
|
+
*
|
|
1504
|
+
* const cleanup = register();
|
|
1505
|
+
* return () => {
|
|
1506
|
+
* cleanup();
|
|
1507
|
+
* unregister();
|
|
1508
|
+
* };
|
|
1509
|
+
* }, [registry, handleUserUpdate]);
|
|
1510
|
+
* }
|
|
1511
|
+
* ```
|
|
1512
|
+
*
|
|
1513
|
+
* @example With Automatic Cleanup
|
|
1514
|
+
* ```tsx
|
|
1515
|
+
* const [userId, setUserId] = useState('123');
|
|
1516
|
+
*
|
|
1517
|
+
* const handleUserUpdate = useCallback(async (payload, controller) => {
|
|
1518
|
+
* console.log('Updating user:', userId, payload);
|
|
1519
|
+
* }, [userId]);
|
|
1520
|
+
*
|
|
1521
|
+
* useEffect(() => {
|
|
1522
|
+
* const handlerManager = createActionHandler(
|
|
1523
|
+
* registry,
|
|
1524
|
+
* 'updateUser',
|
|
1525
|
+
* handleUserUpdate,
|
|
1526
|
+
* { priority: 10 }
|
|
1527
|
+
* );
|
|
1528
|
+
*
|
|
1529
|
+
* // Simplified registration with automatic cleanup
|
|
1530
|
+
* return handlerManager.registerWithCleanup();
|
|
1531
|
+
* }, [registry, handleUserUpdate, userId]);
|
|
1532
|
+
* ```
|
|
1533
|
+
*
|
|
1534
|
+
* @public
|
|
1535
|
+
*/
|
|
1536
|
+
function createActionHandler(registry, action, handler, config) {
|
|
1537
|
+
const finalConfig = createReactHandlerConfig(String(action), void 0, config);
|
|
1538
|
+
let currentUnregister;
|
|
1539
|
+
let isRegistered = false;
|
|
1540
|
+
return {
|
|
1541
|
+
register() {
|
|
1542
|
+
if (isRegistered && currentUnregister) currentUnregister();
|
|
1543
|
+
currentUnregister = registry.register(action, handler, finalConfig);
|
|
1544
|
+
isRegistered = true;
|
|
1545
|
+
return currentUnregister;
|
|
1546
|
+
},
|
|
1547
|
+
unregister() {
|
|
1548
|
+
if (isRegistered && currentUnregister) {
|
|
1549
|
+
currentUnregister();
|
|
1550
|
+
currentUnregister = void 0;
|
|
1551
|
+
isRegistered = false;
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
registerWithCleanup() {
|
|
1555
|
+
const unregisterFn = this.register();
|
|
1556
|
+
return () => {
|
|
1557
|
+
unregisterFn();
|
|
1558
|
+
this.unregister();
|
|
1559
|
+
};
|
|
1560
|
+
},
|
|
1561
|
+
config: finalConfig
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* 🆕 React handler configuration factory
|
|
1566
|
+
*
|
|
1567
|
+
* Creates optimized handler configurations for React environments with
|
|
1568
|
+
* proper cleanup and unique ID generation.
|
|
1569
|
+
*
|
|
1570
|
+
* @template T - ActionPayloadMap type
|
|
1571
|
+
* @template K - Action key type
|
|
1572
|
+
*
|
|
1573
|
+
* @param action - Action name
|
|
1574
|
+
* @param componentId - Optional component identifier for debugging
|
|
1575
|
+
* @param config - Base handler configuration
|
|
1576
|
+
*
|
|
1577
|
+
* @returns Optimized configuration for React environments
|
|
1578
|
+
*
|
|
1579
|
+
* @example
|
|
1580
|
+
* ```tsx
|
|
1581
|
+
* function MyComponent({ userId }: { userId: string }) {
|
|
1582
|
+
* const registry = useActionRegister();
|
|
1583
|
+
*
|
|
1584
|
+
* useEffect(() => {
|
|
1585
|
+
* const config = createReactHandlerConfig('updateUser', 'MyComponent', {
|
|
1586
|
+
* priority: 10
|
|
1587
|
+
* });
|
|
1588
|
+
*
|
|
1589
|
+
* const unregister = registry.register('updateUser', handler, config);
|
|
1590
|
+
* return unregister;
|
|
1591
|
+
* }, [registry, handler]);
|
|
1592
|
+
* }
|
|
1593
|
+
* ```
|
|
1594
|
+
*
|
|
1595
|
+
* @public
|
|
1596
|
+
*/
|
|
1597
|
+
function createReactHandlerConfig(action, componentId, config = {}) {
|
|
1598
|
+
const timestamp = Date.now();
|
|
1599
|
+
const random = Math.random().toString(36).substr(2, 5);
|
|
1600
|
+
return {
|
|
1601
|
+
priority: config.priority ?? 0,
|
|
1602
|
+
id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
|
|
1603
|
+
blocking: config.blocking ?? false,
|
|
1604
|
+
once: config.once ?? false,
|
|
1605
|
+
debounce: config.debounce ?? void 0,
|
|
1606
|
+
throttle: config.throttle ?? void 0,
|
|
1607
|
+
replaceExisting: true
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* 🆕 React action dispatcher factory
|
|
1612
|
+
*
|
|
1613
|
+
* Creates a dispatcher function optimized for React component usage
|
|
1614
|
+
* with proper error boundaries and async handling.
|
|
1615
|
+
*
|
|
1616
|
+
* @template T - ActionPayloadMap type
|
|
1617
|
+
*
|
|
1618
|
+
* @param registry - ActionRegister instance
|
|
1619
|
+
* @param errorHandler - Optional error handler for unhandled dispatch errors
|
|
1620
|
+
*
|
|
1621
|
+
* @returns Optimized dispatch function for React components
|
|
1622
|
+
*
|
|
1623
|
+
* @example
|
|
1624
|
+
* ```tsx
|
|
1625
|
+
* function MyComponent() {
|
|
1626
|
+
* const registry = useActionRegister();
|
|
1627
|
+
*
|
|
1628
|
+
* const dispatch = createReactDispatcher(registry, (error, action, payload) => {
|
|
1629
|
+
* console.error(`Failed to dispatch ${action}:`, error);
|
|
1630
|
+
* });
|
|
1631
|
+
*
|
|
1632
|
+
* const handleClick = useCallback(() => {
|
|
1633
|
+
* dispatch('userClick', { buttonId: 'submit' });
|
|
1634
|
+
* }, [dispatch]);
|
|
1635
|
+
* }
|
|
1636
|
+
* ```
|
|
1637
|
+
*
|
|
1638
|
+
* @public
|
|
1639
|
+
*/
|
|
1640
|
+
function createReactDispatcher(registry, errorHandler) {
|
|
1641
|
+
return async (action, payload, options) => {
|
|
1642
|
+
try {
|
|
1643
|
+
await registry.dispatch(action, payload, {
|
|
1644
|
+
immediate: false,
|
|
1645
|
+
...options
|
|
1646
|
+
});
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
1649
|
+
if (errorHandler) errorHandler(errorObj, action, payload);
|
|
1650
|
+
else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* 🆕 React development utilities
|
|
1656
|
+
*
|
|
1657
|
+
* Provides debugging and development helpers specifically for React environments.
|
|
1658
|
+
*/
|
|
1659
|
+
const ReactDevUtils = {
|
|
1660
|
+
enableDebugMode() {
|
|
1661
|
+
if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = true;
|
|
1662
|
+
},
|
|
1663
|
+
disableDebugMode() {
|
|
1664
|
+
if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = false;
|
|
1665
|
+
},
|
|
1666
|
+
isDebugMode() {
|
|
1667
|
+
return typeof window !== "undefined" && Boolean(window.__CONTEXT_ACTION_REACT_DEBUG__);
|
|
1668
|
+
},
|
|
1669
|
+
log(component, action, message, data) {
|
|
1670
|
+
if (this.isDebugMode()) console.log(`🎯 [React-ActionRegister] [${component}] ${action}: ${message}`, data || "");
|
|
1671
|
+
},
|
|
1672
|
+
getStats(registry) {
|
|
1673
|
+
const registryInfo = registry.getRegistryInfo();
|
|
1674
|
+
let reactHandlers = 0;
|
|
1675
|
+
registry.getRegisteredActions().forEach((action) => {
|
|
1676
|
+
const stats = registry.getActionStats(action);
|
|
1677
|
+
if (stats) stats.handlersByPriority.forEach((priorityGroup) => {
|
|
1678
|
+
priorityGroup.handlers.forEach((handler) => {
|
|
1679
|
+
if (handler.id.includes("react")) reactHandlers++;
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
});
|
|
1683
|
+
return {
|
|
1684
|
+
totalHandlers: registryInfo.totalHandlers,
|
|
1685
|
+
reactHandlers,
|
|
1686
|
+
registryInfo
|
|
1687
|
+
};
|
|
1758
1688
|
}
|
|
1759
1689
|
};
|
|
1690
|
+
/**
|
|
1691
|
+
* 🆕 React Error Boundary integration
|
|
1692
|
+
*
|
|
1693
|
+
* Utilities for integrating ActionRegister errors with React Error Boundaries.
|
|
1694
|
+
*/
|
|
1695
|
+
var ReactActionError = class ReactActionError extends Error {
|
|
1696
|
+
constructor(message, action, payload, handlerId, originalError) {
|
|
1697
|
+
super(message);
|
|
1698
|
+
this.name = "ReactActionError";
|
|
1699
|
+
this.action = action;
|
|
1700
|
+
this.payload = payload;
|
|
1701
|
+
this.handlerId = handlerId;
|
|
1702
|
+
this.timestamp = Date.now();
|
|
1703
|
+
if (originalError && originalError.stack) this.stack = originalError.stack;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Create a React Error Boundary compatible error
|
|
1707
|
+
*/
|
|
1708
|
+
static fromActionError(originalError, action, payload, handlerId) {
|
|
1709
|
+
return new ReactActionError(`Action '${action}' failed: ${originalError.message}`, action, payload, handlerId, originalError);
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
/**
|
|
1713
|
+
* 🆕 Type guard for React Action Errors
|
|
1714
|
+
*
|
|
1715
|
+
* @param error - Error to check
|
|
1716
|
+
* @returns True if error is a ReactActionError
|
|
1717
|
+
*/
|
|
1718
|
+
function isReactActionError(error) {
|
|
1719
|
+
return error instanceof ReactActionError;
|
|
1720
|
+
}
|
|
1760
1721
|
|
|
1761
1722
|
//#endregion
|
|
1762
1723
|
exports.ActionGuard = ActionGuard;
|
|
1763
1724
|
exports.ActionRegister = ActionRegister;
|
|
1725
|
+
exports.ReactActionError = ReactActionError;
|
|
1726
|
+
exports.ReactDevUtils = ReactDevUtils;
|
|
1727
|
+
exports.createActionHandler = createActionHandler;
|
|
1728
|
+
exports.createReactDispatcher = createReactDispatcher;
|
|
1729
|
+
exports.createReactHandlerConfig = createReactHandlerConfig;
|
|
1764
1730
|
exports.executeParallel = executeParallel;
|
|
1765
1731
|
exports.executeRace = executeRace;
|
|
1766
|
-
exports.executeSequential = executeSequential;
|
|
1732
|
+
exports.executeSequential = executeSequential;
|
|
1733
|
+
exports.isReactActionError = isReactActionError;
|