@context-action/core 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -0
- package/dist/index.cjs +779 -62
- package/dist/index.d.cts +658 -30
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +658 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +777 -61
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createLogger, getDebugFromEnv, getLoggerNameFromEnv } from "@context-action/logger";
|
|
2
|
+
|
|
1
3
|
//#region rolldown:runtime
|
|
2
4
|
var __create = Object.create;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
@@ -23,6 +25,197 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
25
|
enumerable: true
|
|
24
26
|
}) : target, mod));
|
|
25
27
|
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/execution-modes.ts
|
|
30
|
+
/**
|
|
31
|
+
* Execute handlers in sequential mode (one after another)
|
|
32
|
+
* @implements execution-modes
|
|
33
|
+
* @implements sequential-execution
|
|
34
|
+
* @memberof core-concepts
|
|
35
|
+
* @internal
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
*
|
|
38
|
+
* Executes action handlers sequentially in priority order, supporting flow control,
|
|
39
|
+
* conditional execution, and priority jumping within the pipeline.
|
|
40
|
+
*
|
|
41
|
+
* @template T - The type of the payload being processed
|
|
42
|
+
* @param context - Pipeline execution context with handlers and state
|
|
43
|
+
* @param createController - Factory function for creating pipeline controllers
|
|
44
|
+
* @param logger - Logger instance for tracing execution
|
|
45
|
+
* @returns Promise that resolves when all handlers complete or pipeline aborts
|
|
46
|
+
*
|
|
47
|
+
* Features:
|
|
48
|
+
* - Priority-based execution order (higher priority first)
|
|
49
|
+
* - Support for priority jumping within execution
|
|
50
|
+
* - Conditional handler execution (condition/validation checks)
|
|
51
|
+
* - Blocking/non-blocking handler support
|
|
52
|
+
* - Comprehensive error handling and recovery
|
|
53
|
+
*/
|
|
54
|
+
async function executeSequential(context, createController, logger) {
|
|
55
|
+
logger.trace("Executing in sequential mode", { handlerCount: context.handlers.length });
|
|
56
|
+
for (let i = 0; i < context.handlers.length; i++) {
|
|
57
|
+
if (context.aborted) {
|
|
58
|
+
logger.trace("Sequential execution aborted", {
|
|
59
|
+
atIndex: i,
|
|
60
|
+
reason: context.abortReason
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (context.jumpToPriority !== void 0) {
|
|
65
|
+
const jumpIndex = context.handlers.findIndex((handler) => handler.config.priority === context.jumpToPriority);
|
|
66
|
+
if (jumpIndex !== -1 && jumpIndex !== i) {
|
|
67
|
+
logger.trace("Jumping to priority", {
|
|
68
|
+
fromIndex: i,
|
|
69
|
+
toIndex: jumpIndex,
|
|
70
|
+
priority: context.jumpToPriority
|
|
71
|
+
});
|
|
72
|
+
i = jumpIndex - 1;
|
|
73
|
+
context.jumpToPriority = void 0;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
context.jumpToPriority = void 0;
|
|
77
|
+
}
|
|
78
|
+
const registration = context.handlers[i];
|
|
79
|
+
context.currentIndex = i;
|
|
80
|
+
if (registration.config.condition && !registration.config.condition()) {
|
|
81
|
+
logger.debug(`Skipping handler '${registration.id}' - condition not met`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
85
|
+
logger.debug(`Skipping handler '${registration.id}' - validation failed`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const controller = createController(registration, i);
|
|
89
|
+
try {
|
|
90
|
+
logger.trace(`Executing handler ${i + 1}/${context.handlers.length}`, {
|
|
91
|
+
handlerId: registration.id,
|
|
92
|
+
priority: registration.config.priority
|
|
93
|
+
});
|
|
94
|
+
const result = registration.handler(context.payload, controller);
|
|
95
|
+
if (registration.config.blocking && result instanceof Promise) {
|
|
96
|
+
logger.trace(`Waiting for blocking handler '${registration.id}'`);
|
|
97
|
+
await result;
|
|
98
|
+
}
|
|
99
|
+
logger.trace(`Handler '${registration.id}' completed`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.error(`Handler '${registration.id}' threw an error`, error);
|
|
102
|
+
if (registration.config.blocking) throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Execute handlers in parallel mode (all at once)
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
async function executeParallel(context, createController, logger) {
|
|
111
|
+
logger.trace("Executing in parallel mode", { handlerCount: context.handlers.length });
|
|
112
|
+
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
113
|
+
if (registration.config.condition && !registration.config.condition()) {
|
|
114
|
+
logger.debug(`Skipping handler '${registration.id}' - condition not met`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
118
|
+
logger.debug(`Skipping handler '${registration.id}' - validation failed`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
logger.trace(`Running ${runnableHandlers.length} handlers in parallel`);
|
|
124
|
+
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
125
|
+
const controller = createController(registration, _index);
|
|
126
|
+
try {
|
|
127
|
+
logger.trace(`Starting parallel handler '${registration.id}'`);
|
|
128
|
+
const result = registration.handler(context.payload, controller);
|
|
129
|
+
if (result instanceof Promise) await result;
|
|
130
|
+
logger.trace(`Parallel handler '${registration.id}' completed`);
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
handlerId: registration.id
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error(`Parallel handler '${registration.id}' failed`, error);
|
|
137
|
+
if (registration.config.blocking) throw error;
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
handlerId: registration.id,
|
|
141
|
+
error
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const results = await Promise.allSettled(handlerPromises);
|
|
146
|
+
const failures = results.filter((result, index) => {
|
|
147
|
+
if (result.status === "rejected") {
|
|
148
|
+
const registration = runnableHandlers[index];
|
|
149
|
+
return registration.config.blocking;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
});
|
|
153
|
+
if (failures.length > 0) {
|
|
154
|
+
const firstFailure = failures[0];
|
|
155
|
+
throw firstFailure.reason;
|
|
156
|
+
}
|
|
157
|
+
logger.trace("Parallel execution completed", {
|
|
158
|
+
successful: results.filter((r) => r.status === "fulfilled").length,
|
|
159
|
+
failed: results.filter((r) => r.status === "rejected").length
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Execute handlers in race mode (first to complete wins)
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
async function executeRace(context, createController, logger) {
|
|
167
|
+
logger.trace("Executing in race mode", { handlerCount: context.handlers.length });
|
|
168
|
+
const runnableHandlers = context.handlers.filter((registration, _index) => {
|
|
169
|
+
if (registration.config.condition && !registration.config.condition()) {
|
|
170
|
+
logger.debug(`Skipping handler '${registration.id}' - condition not met`);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (registration.config.validation && !registration.config.validation(context.payload)) {
|
|
174
|
+
logger.debug(`Skipping handler '${registration.id}' - validation failed`);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
});
|
|
179
|
+
if (runnableHandlers.length === 0) {
|
|
180
|
+
logger.trace("No runnable handlers for race mode");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
logger.trace(`Racing ${runnableHandlers.length} handlers`);
|
|
184
|
+
const handlerPromises = runnableHandlers.map(async (registration, _index) => {
|
|
185
|
+
const controller = createController(registration, _index);
|
|
186
|
+
try {
|
|
187
|
+
logger.trace(`Starting race handler '${registration.id}'`);
|
|
188
|
+
const result = registration.handler(context.payload, controller);
|
|
189
|
+
if (result instanceof Promise) await result;
|
|
190
|
+
logger.trace(`Race handler '${registration.id}' completed`);
|
|
191
|
+
return {
|
|
192
|
+
success: true,
|
|
193
|
+
handlerId: registration.id,
|
|
194
|
+
registration
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
logger.error(`Race handler '${registration.id}' failed`, error);
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
handlerId: registration.id,
|
|
201
|
+
error,
|
|
202
|
+
registration
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
try {
|
|
207
|
+
const winner = await Promise.race(handlerPromises);
|
|
208
|
+
logger.debug("Race completed", {
|
|
209
|
+
winner: winner.handlerId,
|
|
210
|
+
success: winner.success
|
|
211
|
+
});
|
|
212
|
+
if (!winner.success && winner.registration?.config.blocking) throw winner.error;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error("Race execution failed", error);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
26
219
|
//#endregion
|
|
27
220
|
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
28
221
|
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
|
|
@@ -70,7 +263,7 @@ var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
|
|
|
70
263
|
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
|
|
71
264
|
var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
|
|
72
265
|
var toPropertyKey = require_toPropertyKey();
|
|
73
|
-
function _defineProperty$
|
|
266
|
+
function _defineProperty$2(e, r, t) {
|
|
74
267
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
75
268
|
value: t,
|
|
76
269
|
enumerable: !0,
|
|
@@ -78,94 +271,617 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
|
|
|
78
271
|
writable: !0
|
|
79
272
|
}) : e[r] = t, e;
|
|
80
273
|
}
|
|
81
|
-
module.exports = _defineProperty$
|
|
274
|
+
module.exports = _defineProperty$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
82
275
|
} });
|
|
83
276
|
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/action-guard.ts
|
|
279
|
+
var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
|
|
280
|
+
/**
|
|
281
|
+
* Action Guard system for managing action execution timing
|
|
282
|
+
* @implements action-guard
|
|
283
|
+
* @implements performance-optimization
|
|
284
|
+
* @implements user-experience-optimization
|
|
285
|
+
* @memberof core-concepts
|
|
286
|
+
* @internal
|
|
287
|
+
* @since 1.0.0
|
|
288
|
+
*
|
|
289
|
+
* Provides debouncing, throttling, and blocking mechanisms for action execution
|
|
290
|
+
* to optimize performance and enhance user experience. Manages timing state
|
|
291
|
+
* per action to prevent unnecessary or excessive action invocations.
|
|
292
|
+
*
|
|
293
|
+
* Key Features:
|
|
294
|
+
* - Debouncing: Delay execution until activity stops
|
|
295
|
+
* - Throttling: Limit execution frequency to intervals
|
|
296
|
+
* - Per-action state management with automatic cleanup
|
|
297
|
+
* - Memory leak prevention through proper timer management
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const guard = new ActionGuard(logger);
|
|
302
|
+
*
|
|
303
|
+
* // Debounce search input (wait 300ms after typing stops)
|
|
304
|
+
* if (await guard.debounce('search', 300)) {
|
|
305
|
+
* executeSearch();
|
|
306
|
+
* }
|
|
307
|
+
*
|
|
308
|
+
* // Throttle scroll handler (max once per 100ms)
|
|
309
|
+
* if (guard.throttle('scroll', 100)) {
|
|
310
|
+
* updateScrollPosition();
|
|
311
|
+
* }
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
var ActionGuard = class {
|
|
315
|
+
constructor(logger) {
|
|
316
|
+
(0, import_defineProperty$1.default)(this, "guards", /* @__PURE__ */ new Map());
|
|
317
|
+
(0, import_defineProperty$1.default)(this, "logger", void 0);
|
|
318
|
+
this.logger = logger;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if action should be debounced
|
|
322
|
+
* @param actionKey - Unique key for the action
|
|
323
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
324
|
+
* @returns Promise that resolves when debounce period is complete
|
|
325
|
+
*/
|
|
326
|
+
async debounce(actionKey, debounceMs) {
|
|
327
|
+
this.logger.trace(`Checking debounce for '${actionKey}'`, { debounceMs });
|
|
328
|
+
let state = this.guards.get(actionKey);
|
|
329
|
+
if (!state) {
|
|
330
|
+
state = {
|
|
331
|
+
lastExecuted: 0,
|
|
332
|
+
isThrottled: false
|
|
333
|
+
};
|
|
334
|
+
this.guards.set(actionKey, state);
|
|
335
|
+
}
|
|
336
|
+
if (state.debounceTimer) {
|
|
337
|
+
clearTimeout(state.debounceTimer);
|
|
338
|
+
this.logger.trace(`Cleared existing debounce timer for '${actionKey}'`);
|
|
339
|
+
}
|
|
340
|
+
return new Promise((resolve) => {
|
|
341
|
+
state.debounceTimer = setTimeout(() => {
|
|
342
|
+
this.logger.trace(`Debounce completed for '${actionKey}'`);
|
|
343
|
+
state.debounceTimer = void 0;
|
|
344
|
+
state.lastExecuted = Date.now();
|
|
345
|
+
resolve(true);
|
|
346
|
+
}, debounceMs);
|
|
347
|
+
this.logger.trace(`Set debounce timer for '${actionKey}'`, { delay: debounceMs });
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Check if action should be throttled
|
|
352
|
+
* @param actionKey - Unique key for the action
|
|
353
|
+
* @param throttleMs - Throttle delay in milliseconds
|
|
354
|
+
* @returns True if action should proceed, false if throttled
|
|
355
|
+
*/
|
|
356
|
+
throttle(actionKey, throttleMs) {
|
|
357
|
+
this.logger.trace(`Checking throttle for '${actionKey}'`, { throttleMs });
|
|
358
|
+
let state = this.guards.get(actionKey);
|
|
359
|
+
if (!state) {
|
|
360
|
+
state = {
|
|
361
|
+
lastExecuted: 0,
|
|
362
|
+
isThrottled: false
|
|
363
|
+
};
|
|
364
|
+
this.guards.set(actionKey, state);
|
|
365
|
+
}
|
|
366
|
+
const now = Date.now();
|
|
367
|
+
const timeSinceLastExecution = now - state.lastExecuted;
|
|
368
|
+
if (timeSinceLastExecution >= throttleMs) {
|
|
369
|
+
state.lastExecuted = now;
|
|
370
|
+
state.isThrottled = false;
|
|
371
|
+
this.logger.trace(`Throttle passed for '${actionKey}'`, {
|
|
372
|
+
timeSinceLastExecution,
|
|
373
|
+
throttleMs
|
|
374
|
+
});
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
if (state.isThrottled) {
|
|
378
|
+
this.logger.trace(`Action '${actionKey}' is already throttled`);
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
state.isThrottled = true;
|
|
382
|
+
const remainingTime = throttleMs - timeSinceLastExecution;
|
|
383
|
+
state.throttleTimer = setTimeout(() => {
|
|
384
|
+
state.isThrottled = false;
|
|
385
|
+
state.throttleTimer = void 0;
|
|
386
|
+
this.logger.trace(`Throttle period ended for '${actionKey}'`);
|
|
387
|
+
}, remainingTime);
|
|
388
|
+
this.logger.trace(`Action '${actionKey}' throttled`, {
|
|
389
|
+
timeSinceLastExecution,
|
|
390
|
+
remainingTime
|
|
391
|
+
});
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Clear all guards for an action
|
|
396
|
+
* @param actionKey - Action key to clear
|
|
397
|
+
*/
|
|
398
|
+
clearGuards(actionKey) {
|
|
399
|
+
this.logger.trace(`Clearing guards for '${actionKey}'`);
|
|
400
|
+
const state = this.guards.get(actionKey);
|
|
401
|
+
if (state) {
|
|
402
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
403
|
+
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
404
|
+
this.guards.delete(actionKey);
|
|
405
|
+
this.logger.debug(`Cleared guards for '${actionKey}'`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Clear all guards
|
|
410
|
+
*/
|
|
411
|
+
clearAll() {
|
|
412
|
+
this.logger.trace("Clearing all action guards");
|
|
413
|
+
for (const [, state] of this.guards) {
|
|
414
|
+
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
415
|
+
if (state.throttleTimer) clearTimeout(state.throttleTimer);
|
|
416
|
+
}
|
|
417
|
+
this.guards.clear();
|
|
418
|
+
this.logger.debug("Cleared all action guards");
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get current guard state for debugging
|
|
422
|
+
* @param actionKey - Action key to inspect
|
|
423
|
+
*/
|
|
424
|
+
getGuardState(actionKey) {
|
|
425
|
+
return this.guards.get(actionKey);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get all active guards for debugging
|
|
429
|
+
*/
|
|
430
|
+
getAllGuardStates() {
|
|
431
|
+
return new Map(this.guards);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
84
435
|
//#endregion
|
|
85
436
|
//#region src/ActionRegister.ts
|
|
86
437
|
var import_defineProperty = __toESM(require_defineProperty(), 1);
|
|
87
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Simple event emitter implementation for ActionRegister events
|
|
440
|
+
* @internal
|
|
441
|
+
*/
|
|
442
|
+
var SimpleEventEmitter = class {
|
|
88
443
|
constructor() {
|
|
444
|
+
(0, import_defineProperty.default)(this, "listeners", /* @__PURE__ */ new Map());
|
|
445
|
+
}
|
|
446
|
+
on(event, handler) {
|
|
447
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
448
|
+
this.listeners.get(event).add(handler);
|
|
449
|
+
return () => this.off(event, handler);
|
|
450
|
+
}
|
|
451
|
+
emit(event, data) {
|
|
452
|
+
const eventListeners = this.listeners.get(event);
|
|
453
|
+
if (eventListeners) eventListeners.forEach((handler) => {
|
|
454
|
+
try {
|
|
455
|
+
handler(data);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(`Error in event handler for ${String(event)}:`, error);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
off(event, handler) {
|
|
462
|
+
const eventListeners = this.listeners.get(event);
|
|
463
|
+
if (eventListeners) {
|
|
464
|
+
eventListeners.delete(handler);
|
|
465
|
+
if (eventListeners.size === 0) this.listeners.delete(event);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
removeAllListeners(event) {
|
|
469
|
+
if (event) this.listeners.delete(event);
|
|
470
|
+
else this.listeners.clear();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
/**
|
|
474
|
+
* Central action registration and dispatch system
|
|
475
|
+
* @implements action-pipeline-system
|
|
476
|
+
* @implements actionregister
|
|
477
|
+
* @memberof core-concepts
|
|
478
|
+
*
|
|
479
|
+
* Core action pipeline management system with type-safe action dispatch
|
|
480
|
+
* @template T - Action payload map defining available actions and their payload types
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* interface AppActions extends ActionPayloadMap {
|
|
485
|
+
* increment: void;
|
|
486
|
+
* setCount: number;
|
|
487
|
+
* updateUser: { id: string; name: string };
|
|
488
|
+
* }
|
|
489
|
+
*
|
|
490
|
+
* const actionRegister = new ActionRegister<AppActions>();
|
|
491
|
+
*
|
|
492
|
+
* // Register handlers with priority and configuration
|
|
493
|
+
* actionRegister.register('increment', (_, controller) => {
|
|
494
|
+
* console.log('Incremented');
|
|
495
|
+
* controller.next();
|
|
496
|
+
* }, { priority: 10 });
|
|
497
|
+
*
|
|
498
|
+
* actionRegister.register('setCount', (count, controller) => {
|
|
499
|
+
* console.log(`Count: ${count}`);
|
|
500
|
+
* controller.next();
|
|
501
|
+
* });
|
|
502
|
+
*
|
|
503
|
+
* // Dispatch actions with type safety
|
|
504
|
+
* await actionRegister.dispatch('increment');
|
|
505
|
+
* await actionRegister.dispatch('setCount', 42);
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
var ActionRegister = class {
|
|
509
|
+
constructor(config = {}) {
|
|
89
510
|
(0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
|
|
90
|
-
(0, import_defineProperty.default)(this, "
|
|
511
|
+
(0, import_defineProperty.default)(this, "handlerCounter", 0);
|
|
512
|
+
(0, import_defineProperty.default)(this, "logger", void 0);
|
|
513
|
+
(0, import_defineProperty.default)(this, "events", new SimpleEventEmitter());
|
|
514
|
+
(0, import_defineProperty.default)(this, "config", void 0);
|
|
515
|
+
(0, import_defineProperty.default)(this, "actionGuard", void 0);
|
|
516
|
+
(0, import_defineProperty.default)(this, "executionMode", "sequential");
|
|
517
|
+
(0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
|
|
518
|
+
(0, import_defineProperty.default)(
|
|
519
|
+
this,
|
|
520
|
+
/**
|
|
521
|
+
* Dispatch action through pipeline
|
|
522
|
+
* @implements action-dispatcher
|
|
523
|
+
*
|
|
524
|
+
* Dispatch an action through the pipeline
|
|
525
|
+
* Overloaded to provide type safety for actions with and without payloads
|
|
526
|
+
*/
|
|
527
|
+
"dispatch",
|
|
528
|
+
async (action, payload) => {
|
|
529
|
+
const startTime = Date.now();
|
|
530
|
+
this.logger.trace(`Starting dispatch for action '${String(action)}'`, {
|
|
531
|
+
action,
|
|
532
|
+
payload,
|
|
533
|
+
startTime
|
|
534
|
+
});
|
|
535
|
+
this.events.emit("action:start", {
|
|
536
|
+
action,
|
|
537
|
+
payload
|
|
538
|
+
});
|
|
539
|
+
this.logger.trace(`Emitted 'action:start' event`);
|
|
540
|
+
this.logger.debug(`Dispatching action '${String(action)}'`, { payload });
|
|
541
|
+
const pipeline = this.pipelines.get(action);
|
|
542
|
+
if (!pipeline || pipeline.length === 0) {
|
|
543
|
+
this.logger.warn(`No handlers registered for action '${String(action)}'`);
|
|
544
|
+
this.logger.trace(`Dispatch completed early - no handlers`);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.logger.trace(`Found ${pipeline.length} handlers for action '${String(action)}'`, { handlerIds: pipeline.map((reg) => reg.id) });
|
|
548
|
+
const currentExecutionMode = this.actionExecutionModes.get(action) || this.executionMode;
|
|
549
|
+
const context = {
|
|
550
|
+
action: String(action),
|
|
551
|
+
payload,
|
|
552
|
+
handlers: [...pipeline],
|
|
553
|
+
aborted: false,
|
|
554
|
+
abortReason: void 0,
|
|
555
|
+
currentIndex: 0,
|
|
556
|
+
jumpToPriority: void 0,
|
|
557
|
+
executionMode: currentExecutionMode
|
|
558
|
+
};
|
|
559
|
+
try {
|
|
560
|
+
await this.executePipeline(context);
|
|
561
|
+
const metrics = {
|
|
562
|
+
action: String(action),
|
|
563
|
+
executionTime: Date.now() - startTime,
|
|
564
|
+
handlerCount: context.handlers.length,
|
|
565
|
+
success: !context.aborted,
|
|
566
|
+
timestamp: Date.now()
|
|
567
|
+
};
|
|
568
|
+
if (context.aborted) {
|
|
569
|
+
metrics.error = context.abortReason;
|
|
570
|
+
this.events.emit("action:abort", {
|
|
571
|
+
action,
|
|
572
|
+
payload,
|
|
573
|
+
reason: context.abortReason
|
|
574
|
+
});
|
|
575
|
+
} else this.events.emit("action:complete", {
|
|
576
|
+
action,
|
|
577
|
+
payload,
|
|
578
|
+
metrics
|
|
579
|
+
});
|
|
580
|
+
this.logger.debug(`Completed action '${String(action)}'`, metrics);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
const metrics = {
|
|
583
|
+
action: String(action),
|
|
584
|
+
executionTime: Date.now() - startTime,
|
|
585
|
+
handlerCount: context.handlers.length,
|
|
586
|
+
success: false,
|
|
587
|
+
error: error.message || "Unknown error",
|
|
588
|
+
timestamp: Date.now()
|
|
589
|
+
};
|
|
590
|
+
this.logger.error(`Error executing action '${String(action)}'`, metrics);
|
|
591
|
+
this.events.emit("action:error", {
|
|
592
|
+
action,
|
|
593
|
+
payload,
|
|
594
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
595
|
+
});
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
this.config = {
|
|
601
|
+
logger: config.logger || createLogger(config.logLevel),
|
|
602
|
+
logLevel: config.logLevel ?? 3,
|
|
603
|
+
name: config.name || getLoggerNameFromEnv(),
|
|
604
|
+
debug: config.debug ?? getDebugFromEnv(),
|
|
605
|
+
defaultExecutionMode: config.defaultExecutionMode ?? "sequential"
|
|
606
|
+
};
|
|
607
|
+
this.logger = this.config.logger;
|
|
608
|
+
this.actionGuard = new ActionGuard(this.logger);
|
|
609
|
+
this.executionMode = this.config.defaultExecutionMode;
|
|
610
|
+
this.logger.trace(`${this.config.name} constructor called`, { config });
|
|
611
|
+
if (this.config.debug) this.logger.info(`${this.config.name} initialized`, {
|
|
612
|
+
logLevel: this.config.logLevel,
|
|
613
|
+
debug: this.config.debug,
|
|
614
|
+
defaultExecutionMode: this.executionMode
|
|
615
|
+
});
|
|
616
|
+
this.logger.trace(`${this.config.name} constructor completed`);
|
|
91
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Register action handler with pipeline
|
|
620
|
+
* @implements action-handler
|
|
621
|
+
*
|
|
622
|
+
* Register a handler for an action in the pipeline
|
|
623
|
+
* @param action - The action name to handle
|
|
624
|
+
* @param handler - The handler function to execute
|
|
625
|
+
* @param config - Optional configuration for the handler
|
|
626
|
+
* @returns Unregister function to remove the handler
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```typescript
|
|
630
|
+
* const unregister = actionRegister.register('updateUser',
|
|
631
|
+
* async (payload, controller) => {
|
|
632
|
+
* // Validate payload
|
|
633
|
+
* if (!payload.id) {
|
|
634
|
+
* controller.abort('User ID is required');
|
|
635
|
+
* return;
|
|
636
|
+
* }
|
|
637
|
+
*
|
|
638
|
+
* // Process update
|
|
639
|
+
* await updateUserInStore(payload);
|
|
640
|
+
* controller.next();
|
|
641
|
+
* },
|
|
642
|
+
* { priority: 10, blocking: true }
|
|
643
|
+
* );
|
|
644
|
+
*
|
|
645
|
+
* // Later, remove the handler
|
|
646
|
+
* unregister();
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
92
649
|
register(action, handler, config = {}) {
|
|
93
|
-
|
|
650
|
+
this.logger.trace(`Registering handler for action '${String(action)}'`, { config });
|
|
651
|
+
const handlerId = config.id || `handler_${++this.handlerCounter}`;
|
|
652
|
+
this.logger.trace(`Generated handler ID: ${handlerId}`);
|
|
653
|
+
const registration = {
|
|
654
|
+
handler,
|
|
655
|
+
config: {
|
|
656
|
+
priority: config.priority ?? 0,
|
|
657
|
+
id: handlerId,
|
|
658
|
+
blocking: config.blocking ?? false,
|
|
659
|
+
once: config.once ?? false,
|
|
660
|
+
condition: config.condition || (() => true),
|
|
661
|
+
debounce: config.debounce,
|
|
662
|
+
throttle: config.throttle,
|
|
663
|
+
validation: config.validation,
|
|
664
|
+
middleware: config.middleware ?? false
|
|
665
|
+
},
|
|
666
|
+
id: handlerId
|
|
667
|
+
};
|
|
668
|
+
this.logger.trace(`Created handler registration`, { registration: {
|
|
669
|
+
id: handlerId,
|
|
670
|
+
config: registration.config
|
|
671
|
+
} });
|
|
672
|
+
if (!this.pipelines.has(action)) {
|
|
673
|
+
this.logger.trace(`Creating new pipeline for action: ${String(action)}`);
|
|
674
|
+
this.pipelines.set(action, []);
|
|
675
|
+
this.logger.debug(`Created pipeline for action: ${String(action)}`);
|
|
676
|
+
}
|
|
94
677
|
const pipeline = this.pipelines.get(action);
|
|
95
|
-
|
|
96
|
-
if (pipeline.
|
|
97
|
-
|
|
678
|
+
this.logger.trace(`Current pipeline for '${String(action)}' has ${pipeline.length} handlers`);
|
|
679
|
+
if (pipeline.some((reg) => reg.id === handlerId)) {
|
|
680
|
+
this.logger.warn(`Handler with ID '${handlerId}' already exists for action '${String(action)}'`);
|
|
681
|
+
this.logger.trace(`Duplicate handler registration aborted`);
|
|
98
682
|
return () => {};
|
|
99
683
|
}
|
|
100
|
-
pipeline.
|
|
101
|
-
|
|
102
|
-
|
|
684
|
+
pipeline.push(registration);
|
|
685
|
+
this.logger.trace(`Added handler to pipeline, current length: ${pipeline.length}`);
|
|
686
|
+
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
687
|
+
this.logger.trace(`Pipeline sorted by priority`, { priorities: pipeline.map((reg) => ({
|
|
688
|
+
id: reg.id,
|
|
689
|
+
priority: reg.config.priority
|
|
690
|
+
})) });
|
|
691
|
+
this.logger.debug(`Registered handler for action '${String(action)}'`, {
|
|
692
|
+
handlerId,
|
|
693
|
+
priority: registration.config.priority,
|
|
694
|
+
blocking: registration.config.blocking,
|
|
695
|
+
once: registration.config.once
|
|
696
|
+
});
|
|
697
|
+
this.events.emit("handler:register", {
|
|
698
|
+
action,
|
|
699
|
+
handlerId,
|
|
700
|
+
config: registration.config
|
|
103
701
|
});
|
|
104
|
-
this.sortPipeline(action);
|
|
105
702
|
return () => {
|
|
106
|
-
|
|
703
|
+
this.logger.trace(`Unregistering handler '${handlerId}' from action '${String(action)}'`);
|
|
704
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId);
|
|
705
|
+
if (index !== -1) {
|
|
706
|
+
pipeline.splice(index, 1);
|
|
707
|
+
this.logger.debug(`Unregistered handler '${handlerId}' from action '${String(action)}'`);
|
|
708
|
+
this.logger.trace(`Pipeline now has ${pipeline.length} handlers`);
|
|
709
|
+
this.events.emit("handler:unregister", {
|
|
710
|
+
action,
|
|
711
|
+
handlerId
|
|
712
|
+
});
|
|
713
|
+
} else this.logger.trace(`Handler '${handlerId}' not found in pipeline for unregistration`);
|
|
107
714
|
};
|
|
108
715
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
next: () => {
|
|
124
|
-
shouldContinue = true;
|
|
125
|
-
},
|
|
716
|
+
/**
|
|
717
|
+
* Execute the pipeline with proper flow control
|
|
718
|
+
* @internal
|
|
719
|
+
*/
|
|
720
|
+
async executePipeline(context) {
|
|
721
|
+
this.logger.trace(`Starting pipeline execution`, {
|
|
722
|
+
action: context.action,
|
|
723
|
+
handlerCount: context.handlers.length,
|
|
724
|
+
executionMode: context.executionMode,
|
|
725
|
+
payload: context.payload
|
|
726
|
+
});
|
|
727
|
+
const createController = (registration, _index) => {
|
|
728
|
+
return {
|
|
729
|
+
next: () => {},
|
|
126
730
|
abort: (reason) => {
|
|
127
|
-
|
|
128
|
-
|
|
731
|
+
this.logger.trace(`Handler '${registration.id}' is aborting pipeline`, { reason });
|
|
732
|
+
context.aborted = true;
|
|
733
|
+
context.abortReason = reason;
|
|
734
|
+
this.logger.warn(`Pipeline aborted by handler '${registration.id}'`, { reason });
|
|
129
735
|
},
|
|
130
736
|
modifyPayload: (modifier) => {
|
|
131
|
-
|
|
737
|
+
this.logger.trace(`Handler '${registration.id}' is modifying payload`);
|
|
738
|
+
const oldPayload = context.payload;
|
|
739
|
+
context.payload = modifier(context.payload);
|
|
740
|
+
this.logger.debug(`Payload modified by handler '${registration.id}'`);
|
|
741
|
+
this.logger.trace(`Payload change`, {
|
|
742
|
+
oldPayload,
|
|
743
|
+
newPayload: context.payload
|
|
744
|
+
});
|
|
745
|
+
},
|
|
746
|
+
getPayload: () => context.payload,
|
|
747
|
+
jumpToPriority: (priority) => {
|
|
748
|
+
this.logger.trace(`Handler '${registration.id}' jumping to priority ${priority}`);
|
|
749
|
+
context.jumpToPriority = priority;
|
|
132
750
|
}
|
|
133
751
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
752
|
+
};
|
|
753
|
+
switch (context.executionMode) {
|
|
754
|
+
case "sequential":
|
|
755
|
+
await executeSequential(context, createController, this.logger);
|
|
756
|
+
break;
|
|
757
|
+
case "parallel":
|
|
758
|
+
await executeParallel(context, createController, this.logger);
|
|
759
|
+
break;
|
|
760
|
+
case "race":
|
|
761
|
+
await executeRace(context, createController, this.logger);
|
|
762
|
+
break;
|
|
763
|
+
default: throw new Error(`Unknown execution mode: ${context.executionMode}`);
|
|
142
764
|
}
|
|
765
|
+
this.cleanupOneTimeHandlers(context.action, context.handlers);
|
|
143
766
|
}
|
|
144
|
-
|
|
767
|
+
/**
|
|
768
|
+
* Clean up one-time handlers after pipeline execution
|
|
769
|
+
* @internal
|
|
770
|
+
*/
|
|
771
|
+
cleanupOneTimeHandlers(action, executedHandlers) {
|
|
145
772
|
const pipeline = this.pipelines.get(action);
|
|
146
773
|
if (!pipeline) return;
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
774
|
+
const oneTimeHandlers = executedHandlers.filter((reg) => reg.config.once);
|
|
775
|
+
if (oneTimeHandlers.length === 0) return;
|
|
776
|
+
this.logger.trace(`Cleaning up ${oneTimeHandlers.length} one-time handlers`);
|
|
777
|
+
oneTimeHandlers.forEach((registration) => {
|
|
778
|
+
const index = pipeline.findIndex((reg) => reg.id === registration.id);
|
|
779
|
+
if (index !== -1) {
|
|
780
|
+
pipeline.splice(index, 1);
|
|
781
|
+
this.logger.debug(`Removed one-time handler '${registration.id}'`);
|
|
782
|
+
}
|
|
151
783
|
});
|
|
152
|
-
pipeline.
|
|
153
|
-
|
|
784
|
+
this.logger.trace(`Pipeline now has ${pipeline.length} handlers`);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Get the number of handlers registered for an action
|
|
788
|
+
* @param action - The action to check
|
|
789
|
+
* @returns Number of handlers registered
|
|
790
|
+
*/
|
|
791
|
+
getHandlerCount(action) {
|
|
792
|
+
const pipeline = this.pipelines.get(action);
|
|
793
|
+
const count = pipeline ? pipeline.length : 0;
|
|
794
|
+
this.logger.trace(`Handler count for '${String(action)}': ${count}`);
|
|
795
|
+
return count;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Check if any handlers are registered for an action
|
|
799
|
+
* @param action - The action to check
|
|
800
|
+
* @returns True if handlers are registered
|
|
801
|
+
*/
|
|
802
|
+
hasHandlers(action) {
|
|
803
|
+
const hasHandlers = this.getHandlerCount(action) > 0;
|
|
804
|
+
this.logger.trace(`Has handlers for '${String(action)}': ${hasHandlers}`);
|
|
805
|
+
return hasHandlers;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Get all registered action names
|
|
809
|
+
* @returns Array of action names
|
|
810
|
+
*/
|
|
811
|
+
getRegisteredActions() {
|
|
812
|
+
const actions = Array.from(this.pipelines.keys());
|
|
813
|
+
this.logger.trace(`Registered actions`, {
|
|
814
|
+
actions,
|
|
815
|
+
count: actions.length
|
|
816
|
+
});
|
|
817
|
+
return actions;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Clear all handlers for a specific action
|
|
821
|
+
* @param action - The action to clear
|
|
822
|
+
*/
|
|
823
|
+
clearAction(action) {
|
|
824
|
+
this.logger.trace(`Clearing handlers for action '${String(action)}'`);
|
|
825
|
+
const pipeline = this.pipelines.get(action);
|
|
826
|
+
if (pipeline) {
|
|
827
|
+
const handlerCount = pipeline.length;
|
|
828
|
+
this.pipelines.delete(action);
|
|
829
|
+
this.logger.debug(`Cleared ${handlerCount} handlers for action '${String(action)}'`);
|
|
830
|
+
this.logger.trace(`Action '${String(action)}' pipeline removed`);
|
|
831
|
+
} else this.logger.trace(`No pipeline found for action '${String(action)}' to clear`);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Clear all handlers for all actions
|
|
835
|
+
*/
|
|
836
|
+
clearAll() {
|
|
837
|
+
this.logger.trace(`Clearing all handlers and pipelines`);
|
|
838
|
+
const actionCount = this.pipelines.size;
|
|
839
|
+
const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
|
|
840
|
+
this.logger.trace(`Before clear`, {
|
|
841
|
+
actionCount,
|
|
842
|
+
totalHandlers
|
|
843
|
+
});
|
|
844
|
+
this.pipelines.clear();
|
|
845
|
+
this.events.removeAllListeners();
|
|
846
|
+
this.logger.info(`Cleared all handlers`, {
|
|
847
|
+
actionCount,
|
|
848
|
+
totalHandlers
|
|
849
|
+
});
|
|
850
|
+
this.logger.trace(`All pipelines and event listeners cleared`);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Add event listener for ActionRegister events
|
|
854
|
+
* @param event - Event name to listen for
|
|
855
|
+
* @param handler - Event handler function
|
|
856
|
+
* @returns Unregister function to remove the listener
|
|
857
|
+
*/
|
|
858
|
+
on(event, handler) {
|
|
859
|
+
return this.events.on(event, handler);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Remove event listener
|
|
863
|
+
* @param event - Event name
|
|
864
|
+
* @param handler - Event handler to remove
|
|
865
|
+
*/
|
|
866
|
+
off(event, handler) {
|
|
867
|
+
this.events.off(event, handler);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Get current configuration
|
|
871
|
+
* @returns Current ActionRegister configuration
|
|
872
|
+
*/
|
|
873
|
+
getConfig() {
|
|
874
|
+
return { ...this.config };
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Get logger instance
|
|
878
|
+
* @returns Current logger instance
|
|
879
|
+
*/
|
|
880
|
+
getLogger() {
|
|
881
|
+
return this.logger;
|
|
154
882
|
}
|
|
155
883
|
};
|
|
156
884
|
|
|
157
885
|
//#endregion
|
|
158
|
-
|
|
159
|
-
function createAction(type, payload) {
|
|
160
|
-
return {
|
|
161
|
-
type,
|
|
162
|
-
payload
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
function isAction(action, type) {
|
|
166
|
-
return action?.type === type;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
//#endregion
|
|
170
|
-
export { ActionRegister, createAction, isAction };
|
|
886
|
+
export { ActionGuard, ActionRegister, executeParallel, executeRace, executeSequential };
|
|
171
887
|
//# sourceMappingURL=index.js.map
|