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