events-ex 2.3.0 → 2.4.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.
Files changed (122) hide show
  1. package/.babelrc +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.cn.md +114 -3
  4. package/README.md +106 -5
  5. package/coverage/all-off.js.html +133 -0
  6. package/coverage/base.css +224 -0
  7. package/coverage/block-navigation.js +87 -0
  8. package/coverage/clover.xml +572 -0
  9. package/coverage/consts.js.html +154 -0
  10. package/coverage/default-methods.js.html +2068 -0
  11. package/coverage/event-emitter.js.html +121 -0
  12. package/coverage/event.js.html +286 -0
  13. package/coverage/eventable.js.html +355 -0
  14. package/coverage/favicon.png +0 -0
  15. package/coverage/has-listeners.js.html +166 -0
  16. package/coverage/index.html +281 -0
  17. package/coverage/lcov-report/all-off.js.html +133 -0
  18. package/coverage/lcov-report/base.css +224 -0
  19. package/coverage/lcov-report/block-navigation.js +87 -0
  20. package/coverage/lcov-report/consts.js.html +154 -0
  21. package/coverage/lcov-report/default-methods.js.html +2068 -0
  22. package/coverage/lcov-report/event-emitter.js.html +121 -0
  23. package/coverage/lcov-report/event.js.html +286 -0
  24. package/coverage/lcov-report/eventable.js.html +355 -0
  25. package/coverage/lcov-report/favicon.png +0 -0
  26. package/coverage/lcov-report/has-listeners.js.html +166 -0
  27. package/coverage/lcov-report/index.html +281 -0
  28. package/coverage/lcov-report/once-promise.js.html +325 -0
  29. package/coverage/lcov-report/pipe-async.js.html +385 -0
  30. package/coverage/lcov-report/pipe.js.html +244 -0
  31. package/coverage/lcov-report/prettify.css +1 -0
  32. package/coverage/lcov-report/prettify.js +2 -0
  33. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  34. package/coverage/lcov-report/sorter.js +210 -0
  35. package/coverage/lcov-report/unify.js.html +259 -0
  36. package/coverage/lcov-report/wrap-event-emitter.js.html +223 -0
  37. package/coverage/lcov.info +1201 -0
  38. package/coverage/once-promise.js.html +325 -0
  39. package/coverage/pipe-async.js.html +385 -0
  40. package/coverage/pipe.js.html +244 -0
  41. package/coverage/prettify.css +1 -0
  42. package/coverage/prettify.js +2 -0
  43. package/coverage/sort-arrow-sprite.png +0 -0
  44. package/coverage/sorter.js +210 -0
  45. package/coverage/unify.js.html +259 -0
  46. package/coverage/wrap-event-emitter.js.html +223 -0
  47. package/docs/README.md +106 -5
  48. package/docs/all-off/functions/allOff.md +1 -1
  49. package/docs/consts/README.md +4 -0
  50. package/docs/consts/functions/createAbortError.md +18 -0
  51. package/docs/consts/variables/RegExpEventSymbol.md +1 -1
  52. package/docs/consts/variables/states.md +2 -2
  53. package/docs/default-methods/functions/getEventableMethods.md +34 -18
  54. package/docs/event/classes/Event.md +23 -13
  55. package/docs/event-emitter/classes/EventEmitter-1.md +2 -2
  56. package/docs/event-emitter/classes/EventEmitter.md +28 -28
  57. package/docs/eventable/functions/eventable.md +1 -1
  58. package/docs/has-listeners/functions/hasListeners.md +2 -2
  59. package/docs/index/README.md +12 -0
  60. package/docs/modules.md +1 -0
  61. package/docs/once-promise/README.md +17 -0
  62. package/docs/once-promise/functions/oncePromise.md +60 -0
  63. package/docs/pipe/functions/pipe.md +3 -3
  64. package/docs/pipe-async/functions/pipeAsync.md +3 -3
  65. package/docs/unify/functions/unify.md +1 -1
  66. package/docs/util/array-remove/functions/remove.md +1 -1
  67. package/docs/util/object-for-each/functions/forEach.md +1 -1
  68. package/docs/util/string-pad/functions/pad.md +1 -1
  69. package/docs/util/to-int/functions/toInt.md +1 -1
  70. package/docs/util/valid-callable/functions/validCallable.md +1 -1
  71. package/docs/util/valid-object/functions/validObject.md +1 -1
  72. package/docs/wrap-event-emitter/functions/wrapEventEmitter.md +3 -3
  73. package/docs/wrap-event-emitter/variables/methods.md +1 -1
  74. package/lib/all-off.mjs +16 -0
  75. package/lib/consts.d.ts +7 -1
  76. package/lib/consts.js +17 -2
  77. package/lib/consts.mjs +23 -0
  78. package/lib/default-methods.d.ts +12 -4
  79. package/lib/default-methods.js +174 -22
  80. package/lib/default-methods.mjs +661 -0
  81. package/lib/event-emitter.mjs +12 -0
  82. package/lib/event.d.ts +10 -4
  83. package/lib/event.js +6 -0
  84. package/lib/event.mjs +67 -0
  85. package/lib/eventable.d.ts +1 -1
  86. package/lib/eventable.mjs +90 -0
  87. package/lib/has-listeners.d.ts +1 -1
  88. package/lib/has-listeners.mjs +27 -0
  89. package/lib/index.d.ts +1 -0
  90. package/lib/index.js +12 -0
  91. package/lib/index.mjs +16 -0
  92. package/lib/once-promise.d.ts +23 -0
  93. package/lib/once-promise.js +82 -0
  94. package/lib/once-promise.mjs +80 -0
  95. package/lib/pipe-async.d.ts +4 -2
  96. package/lib/pipe-async.js +16 -3
  97. package/lib/pipe-async.mjs +100 -0
  98. package/lib/pipe.d.ts +1 -1
  99. package/lib/pipe.mjs +53 -0
  100. package/lib/unify.js +1 -1
  101. package/lib/unify.mjs +58 -0
  102. package/lib/util/array-remove.mjs +16 -0
  103. package/lib/util/object-for-each.mjs +20 -0
  104. package/lib/util/promise-any.mjs +45 -0
  105. package/lib/util/string-pad.mjs +19 -0
  106. package/lib/util/to-int.mjs +9 -0
  107. package/lib/util/valid-callable.mjs +6 -0
  108. package/lib/util/valid-object.mjs +8 -0
  109. package/lib/wrap-event-emitter.d.ts +1 -1
  110. package/lib/wrap-event-emitter.mjs +46 -0
  111. package/package.json +29 -20
  112. package/scripts/copy-to-mjs.mjs +79 -0
  113. package/src/consts.js +10 -0
  114. package/src/default-methods.js +169 -24
  115. package/src/event.js +6 -0
  116. package/src/index.js +1 -0
  117. package/src/once-promise.js +80 -0
  118. package/src/pipe-async.js +16 -3
  119. package/src/unify.js +1 -1
  120. package/tsconfig.json +2 -1
  121. package/vitest.config.js +61 -0
  122. package/.mocharc.yml +0 -3
package/lib/event.d.ts CHANGED
@@ -25,19 +25,25 @@ export class Event {
25
25
  * @type {Object}
26
26
  * @public
27
27
  */
28
- public target: any;
28
+ public target: Object | undefined;
29
29
  /**
30
30
  * Whether stop the bubbling event
31
31
  * @type {boolean}
32
32
  * @public
33
33
  */
34
- public stopped: boolean;
34
+ public stopped: boolean | undefined;
35
+ /**
36
+ * Whether the event emission was aborted via AbortSignal.
37
+ * @type {boolean}
38
+ * @public
39
+ */
40
+ public aborted: boolean | undefined;
35
41
  /**
36
42
  * Whether a result has been resolved (for 'first' result mode)
37
43
  * @type {boolean}
38
44
  * @public
39
45
  */
40
- public resolved: boolean;
46
+ public resolved: boolean | undefined;
41
47
  /**
42
48
  * Keep your event result here if any.
43
49
  * @type {*}
@@ -49,7 +55,7 @@ export class Event {
49
55
  * @type {string}
50
56
  * @public
51
57
  */
52
- public type: string;
58
+ public type: string | undefined;
53
59
  /**
54
60
  * Ends the event and returns the result.
55
61
  * @returns {*} The result of the event.
package/lib/event.js CHANGED
@@ -37,6 +37,12 @@ Event.prototype.init = function (target, type) {
37
37
  * @public
38
38
  */
39
39
  this.stopped = false;
40
+ /**
41
+ * Whether the event emission was aborted via AbortSignal.
42
+ * @type {boolean}
43
+ * @public
44
+ */
45
+ this.aborted = false;
40
46
  /**
41
47
  * Whether a result has been resolved (for 'first' result mode)
42
48
  * @type {boolean}
package/lib/event.mjs ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Creates a new Event object instance that contains information about the event, such as the target element and the return value of the event.
3
+ * @class
4
+ * @classdesc Event Object that contains information about the event, such as the target element and the return value of the event.
5
+ * @param {import('./event-emitter').EventEmitter} target - Who trigger the event
6
+ * @returns {Event} - The new Event instance.
7
+ */
8
+ export function Event(target, type) {
9
+ if (!(this instanceof Event)) {
10
+ const evt = new Event(target, type)
11
+ return evt
12
+ }
13
+ this.init(target, type)
14
+ }
15
+
16
+ /**
17
+ * Initializes the event with the target object.
18
+ * @param {import('./event-emitter').EventEmitter} target - The target object for the event.
19
+ */
20
+ Event.prototype.init = function(target, type) {
21
+ /**
22
+ * Who trigger the event
23
+ * @type {Object}
24
+ * @public
25
+ */
26
+ this.target = target
27
+ /**
28
+ * Whether stop the bubbling event
29
+ * @type {boolean}
30
+ * @public
31
+ */
32
+ this.stopped = false
33
+ /**
34
+ * Whether the event emission was aborted via AbortSignal.
35
+ * @type {boolean}
36
+ * @public
37
+ */
38
+ this.aborted = false
39
+ /**
40
+ * Whether a result has been resolved (for 'first' result mode)
41
+ * @type {boolean}
42
+ * @public
43
+ */
44
+ this.resolved = false
45
+ /**
46
+ * Keep your event result here if any.
47
+ * @type {*}
48
+ * @public
49
+ */
50
+ this.result = undefined
51
+ /**
52
+ * The type of the event.
53
+ * @type {string}
54
+ * @public
55
+ */
56
+ this.type = type;
57
+ }
58
+
59
+ /**
60
+ * Ends the event and returns the result.
61
+ * @returns {*} The result of the event.
62
+ */
63
+ Event.prototype.end = function() {
64
+ return this.result
65
+ }
66
+
67
+ export default Event
@@ -45,7 +45,7 @@
45
45
  * }
46
46
  * });
47
47
  */
48
- export const eventable: <T extends Function | (new (...args: any[]) => any)>(targetClass?: T, options?: import("custom-ability").AbilityOptions) => T & typeof getEventableClass & (T extends new (...args: any[]) => any ? InstanceType<T> : T);
48
+ export const eventable: <T extends Function | (new (...args: any[]) => any)>(targetClass?: T | undefined, options?: import("custom-ability").AbilityOptions) => T & typeof getEventableClass & (T extends new (...args: any[]) => any ? InstanceType<T> : T);
49
49
  export default eventable;
50
50
  /**
51
51
  * Creates an Eventable class with event-emitting capabilities.
@@ -0,0 +1,90 @@
1
+ import {createAbilityInjector} from 'custom-ability'
2
+ import defineProperty from 'util-ex/lib/defineProperty'
3
+ import defaultMethods from './default-methods.mjs'
4
+
5
+ /**
6
+ * Creates an Eventable class with event-emitting capabilities.
7
+ *
8
+ * @param {Function} [aClass] - The target class to which event capabilities will be injected.
9
+ * If not provided, a new base class (`Eventable`) is created and used.
10
+ * @returns {Function} A class with event-emitting capabilities.
11
+ */
12
+ function getEventableClass(aClass) {
13
+ /**
14
+ * Base Eventable class that provides event-related functionality.
15
+ * @class
16
+ */
17
+ function Eventable() {}
18
+
19
+ if (aClass == null) {aClass = Eventable}
20
+ const methods = defaultMethods(aClass);
21
+ defineProperty(Eventable, 'methods', methods);
22
+
23
+ Eventable.defaultMaxListeners = 10;
24
+ Eventable.listenerCount = methods.listenerCount;
25
+ Eventable.prototype.listenerCount = methods.listenerCount;
26
+ Eventable.prototype.emit = methods.emit;
27
+ Eventable.prototype.emitAsync = methods.emitAsync;
28
+ Eventable.prototype.configure = methods.configure;
29
+ Eventable.prototype.parallel = methods.parallel;
30
+ Eventable.prototype.setEmitterOptions = methods.setEmitterOptions;
31
+ Eventable.prototype.on = methods.on;
32
+ Eventable.prototype.addListener = methods.on;
33
+ Eventable.prototype.off = methods.off;
34
+ Eventable.prototype.removeListener = methods.off;
35
+ Eventable.prototype.removeAllListeners = methods.removeAllListeners;
36
+ Eventable.prototype.once = methods.once;
37
+ Eventable.prototype.setMaxListeners = methods.setMaxListeners;
38
+ Eventable.prototype.listeners = methods.listeners;
39
+ return Eventable;
40
+ };
41
+
42
+ /**
43
+ * Adds event-emitting capabilities to a class by injecting necessary methods and properties.
44
+ *
45
+ * This function uses `createAbilityInjector` from `custom-ability` to inject event-related methods
46
+ * into the target class. The injected methods include standard EventEmitter functionality such as
47
+ * `on`, `off`, `emit`, `emitAsync`, `once`, `listeners`, `@listenerCount` and more. It also ensures compatibility with Node.js `EventEmitter`
48
+ * by including methods like `listenerCount`, `setMaxListeners`, `addListener`, `removeListener`, and `removeAllListeners`.
49
+ *
50
+ * @function eventable
51
+ * @param {Class} [aClass] - The target class to which event capabilities will be injected. if no class is provided, a new class with eventable will be created.
52
+ * @param {Object} [options] - Optional configuration for the injection process:
53
+ * * `include` (string[]|string): Specifies which methods should be added.
54
+ * Static methods should use the prefix '@'.
55
+ * * `exclude` (string[]|string): Specifies which methods should not be added.
56
+ * Static methods should use the prefix '@'.
57
+ * * `methods` (Object): Custom methods to override or extend the default behavior.
58
+ * Use `this.super()` to call the original method and `this.self` to access the original context.
59
+ * * `classMethods` (Object): Custom static methods to be added to the class.
60
+ * @returns {Class} The same `aClass` class with event capabilities injected. The return value is the modified `aClass` itself.
61
+ *
62
+ * @example
63
+ * import { eventable } from 'events-ex';
64
+ *
65
+ * class MyClass {}
66
+ *
67
+ * // Inject only specific methods: 'on', 'off', 'emit', 'emitAsync', and the static 'listenerCount'
68
+ * eventable(MyClass, { include: ['on', 'off', 'emit', 'emitAsync', '@listenerCount'] });
69
+ *
70
+ * @example
71
+ * import { eventable } from 'events-ex';
72
+ *
73
+ * class OtherClass {
74
+ * exec() {
75
+ * console.log("Original exec");
76
+ * }
77
+ * }
78
+ *
79
+ * // Inject event capabilities and override the `exec` method
80
+ * eventable(OtherClass, {
81
+ * methods: {
82
+ * exec() {
83
+ * console.log("New exec");
84
+ * this.super(); // Calls the original `exec` method
85
+ * }
86
+ * }
87
+ * });
88
+ */
89
+ export const eventable = createAbilityInjector(getEventableClass, true);
90
+ export default eventable;
@@ -8,5 +8,5 @@
8
8
  * - If `type` is not provided, returns whether the object has any event listeners.
9
9
  * @throws {TypeError} Throws a TypeError if `obj` is null or undefined.
10
10
  */
11
- export function hasListeners(obj: any, type?: string): boolean;
11
+ export function hasListeners(obj: Object, type?: string): boolean;
12
12
  export default hasListeners;
@@ -0,0 +1,27 @@
1
+
2
+ import {isEmptyObject} from 'util-ex'
3
+
4
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
5
+
6
+ function isValue(v) {
7
+ return v!== null && v!== undefined
8
+ }
9
+
10
+ /**
11
+ * Checks if an object has event listeners.
12
+ *
13
+ * @param {Object} obj - The object to check. Must not be null or undefined.
14
+ * @param {string} [type] - Optional parameter specifying the event type. If provided, checks for the existence of listeners for this specific type.
15
+ * @returns {boolean}
16
+ * - If `type` is provided, returns whether listeners for the specified event type exist.
17
+ * - If `type` is not provided, returns whether the object has any event listeners.
18
+ * @throws {TypeError} Throws a TypeError if `obj` is null or undefined.
19
+ */
20
+ export function hasListeners(obj, type) {
21
+ if (!isValue(obj)) {throw new TypeError("Cannot use null or undefined")}
22
+
23
+ const result = type != null ? hasOwnProperty.call(obj, '_events') && obj._events[type] : obj.hasOwnProperty('_events') && !isEmptyObject(obj._events);
24
+ return result;
25
+ };
26
+
27
+ export default hasListeners
package/lib/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./event-emitter";
6
6
  export * from "./has-listeners";
7
7
  export * from "./pipe";
8
8
  export * from "./pipe-async";
9
+ export * from "./once-promise";
9
10
  export * from "./unify";
10
11
  export * from "./wrap-event-emitter";
11
12
  export default EventEmitter;
package/lib/index.js CHANGED
@@ -101,6 +101,18 @@ Object.keys(_pipeAsync).forEach(function (key) {
101
101
  }
102
102
  });
103
103
  });
104
+ var _oncePromise = require("./once-promise");
105
+ Object.keys(_oncePromise).forEach(function (key) {
106
+ if (key === "default" || key === "__esModule") return;
107
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
108
+ if (key in exports && exports[key] === _oncePromise[key]) return;
109
+ Object.defineProperty(exports, key, {
110
+ enumerable: true,
111
+ get: function () {
112
+ return _oncePromise[key];
113
+ }
114
+ });
115
+ });
104
116
  var _unify = require("./unify");
105
117
  Object.keys(_unify).forEach(function (key) {
106
118
  if (key === "default" || key === "__esModule") return;
package/lib/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ // @sourceType: module
2
+ import {EventEmitter} from './event-emitter.mjs'
3
+
4
+ export * from './all-off.mjs'
5
+ export * from './consts.mjs'
6
+ export * from './event.mjs'
7
+ export * from './eventable.mjs'
8
+ export * from './event-emitter.mjs'
9
+ export * from './has-listeners.mjs'
10
+ export * from './pipe.mjs'
11
+ export * from './pipe-async.mjs'
12
+ export * from './once-promise.mjs'
13
+ export * from './unify.mjs'
14
+ export * from './wrap-event-emitter.mjs'
15
+
16
+ export default EventEmitter;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Returns a Promise that resolves with the Event object when the specified event is emitted on the given emitter.
3
+ * If an 'error' event is emitted (and the waiting event is not 'error'), the promise rejects by default.
4
+ * If the provided AbortSignal is aborted, the promise rejects with an AbortError.
5
+ *
6
+ * Note: The resolved Event object's `result` field may not be the final value if other listeners have not yet run.
7
+ * For the definitive emit return value, use `emit()` or `emitAsync()` directly.
8
+ *
9
+ * @param {import('./event-emitter').EventEmitter} emitter - The event emitter to listen on.
10
+ * @param {string|RegExp} type - The event type to wait for.
11
+ * @param {Object} [options] - Optional configuration.
12
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel the wait.
13
+ * @param {boolean|null} [options.raiseError] - Controls behavior when an 'error' event is emitted:
14
+ * - `true` / `undefined` (default): The promise rejects with the error.
15
+ * - `false`: The promise resolves with the error object instead of rejecting.
16
+ * @returns {Promise<import('./event').Event>} - A promise that resolves with the Event object.
17
+ * @throws {TypeError} - If emitter is not a valid event emitter object.
18
+ */
19
+ export function oncePromise(emitter: import("./event-emitter").EventEmitter, type: string | RegExp, options?: {
20
+ signal?: any;
21
+ raiseError?: boolean | null | undefined;
22
+ }): Promise<import("./event").Event>;
23
+ export default oncePromise;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.oncePromise = oncePromise;
8
+ var _validObject = _interopRequireDefault(require("./util/valid-object"));
9
+ var _consts = require("./consts");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ // @sourceType: module
12
+
13
+ /**
14
+ * Returns a Promise that resolves with the Event object when the specified event is emitted on the given emitter.
15
+ * If an 'error' event is emitted (and the waiting event is not 'error'), the promise rejects by default.
16
+ * If the provided AbortSignal is aborted, the promise rejects with an AbortError.
17
+ *
18
+ * Note: The resolved Event object's `result` field may not be the final value if other listeners have not yet run.
19
+ * For the definitive emit return value, use `emit()` or `emitAsync()` directly.
20
+ *
21
+ * @param {import('./event-emitter').EventEmitter} emitter - The event emitter to listen on.
22
+ * @param {string|RegExp} type - The event type to wait for.
23
+ * @param {Object} [options] - Optional configuration.
24
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel the wait.
25
+ * @param {boolean|null} [options.raiseError] - Controls behavior when an 'error' event is emitted:
26
+ * - `true` / `undefined` (default): The promise rejects with the error.
27
+ * - `false`: The promise resolves with the error object instead of rejecting.
28
+ * @returns {Promise<import('./event').Event>} - A promise that resolves with the Event object.
29
+ * @throws {TypeError} - If emitter is not a valid event emitter object.
30
+ */
31
+ function oncePromise(emitter, type, options) {
32
+ (0, _validObject.default)(emitter);
33
+ const signal = options && options.signal;
34
+ const raiseError = options && options.raiseError;
35
+ return new Promise((resolve, reject) => {
36
+ let onAbort = null;
37
+ let aborted = false;
38
+ const cleanup = () => {
39
+ emitter.off(type, onEvent);
40
+ if (type !== 'error') {
41
+ emitter.off('error', onError);
42
+ }
43
+ if (signal && onAbort) {
44
+ signal.removeEventListener('abort', onAbort);
45
+ }
46
+ };
47
+ if (signal) {
48
+ if (signal.aborted) {
49
+ reject((0, _consts.createAbortError)());
50
+ return;
51
+ }
52
+ onAbort = () => {
53
+ /* v8 ignore next — defensive guard, { once: true } ensures callback fires at most once */
54
+ if (!aborted) {
55
+ aborted = true;
56
+ cleanup();
57
+ reject((0, _consts.createAbortError)());
58
+ }
59
+ };
60
+ signal.addEventListener('abort', onAbort, {
61
+ once: true
62
+ });
63
+ }
64
+ function onEvent() {
65
+ cleanup();
66
+ resolve(this); // resolve with the Event object
67
+ }
68
+ function onError(err) {
69
+ cleanup();
70
+ if (raiseError === false) {
71
+ resolve(err); // resolve with the error instead of rejecting
72
+ } else {
73
+ reject(err); // true | null | undefined → reject
74
+ }
75
+ }
76
+ emitter.on(type, onEvent);
77
+ if (type !== 'error') {
78
+ emitter.on('error', onError);
79
+ }
80
+ });
81
+ }
82
+ var _default = exports.default = oncePromise;
@@ -0,0 +1,80 @@
1
+ // @sourceType: module
2
+ import validObject from './util/valid-object.mjs'
3
+ import {createAbortError} from './consts.mjs'
4
+
5
+ /**
6
+ * Returns a Promise that resolves with the Event object when the specified event is emitted on the given emitter.
7
+ * If an 'error' event is emitted (and the waiting event is not 'error'), the promise rejects by default.
8
+ * If the provided AbortSignal is aborted, the promise rejects with an AbortError.
9
+ *
10
+ * Note: The resolved Event object's `result` field may not be the final value if other listeners have not yet run.
11
+ * For the definitive emit return value, use `emit()` or `emitAsync()` directly.
12
+ *
13
+ * @param {import('./event-emitter').EventEmitter} emitter - The event emitter to listen on.
14
+ * @param {string|RegExp} type - The event type to wait for.
15
+ * @param {Object} [options] - Optional configuration.
16
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel the wait.
17
+ * @param {boolean|null} [options.raiseError] - Controls behavior when an 'error' event is emitted:
18
+ * - `true` / `undefined` (default): The promise rejects with the error.
19
+ * - `false`: The promise resolves with the error object instead of rejecting.
20
+ * @returns {Promise<import('./event').Event>} - A promise that resolves with the Event object.
21
+ * @throws {TypeError} - If emitter is not a valid event emitter object.
22
+ */
23
+ export function oncePromise(emitter, type, options) {
24
+ validObject(emitter)
25
+
26
+ const signal = options && options.signal
27
+ const raiseError = options && options.raiseError
28
+
29
+ return new Promise((resolve, reject) => {
30
+ let onAbort = null
31
+ let aborted = false
32
+
33
+ const cleanup = () => {
34
+ emitter.off(type, onEvent)
35
+ if (type !== 'error') {
36
+ emitter.off('error', onError)
37
+ }
38
+ if (signal && onAbort) {
39
+ signal.removeEventListener('abort', onAbort)
40
+ }
41
+ }
42
+
43
+ if (signal) {
44
+ if (signal.aborted) {
45
+ reject(createAbortError())
46
+ return
47
+ }
48
+ onAbort = () => {
49
+ /* v8 ignore next — defensive guard, { once: true } ensures callback fires at most once */
50
+ if (!aborted) {
51
+ aborted = true
52
+ cleanup()
53
+ reject(createAbortError())
54
+ }
55
+ }
56
+ signal.addEventListener('abort', onAbort, { once: true })
57
+ }
58
+
59
+ function onEvent() {
60
+ cleanup()
61
+ resolve(this) // resolve with the Event object
62
+ }
63
+
64
+ function onError(err) {
65
+ cleanup()
66
+ if (raiseError === false) {
67
+ resolve(err) // resolve with the error instead of rejecting
68
+ } else {
69
+ reject(err) // true | null | undefined → reject
70
+ }
71
+ }
72
+
73
+ emitter.on(type, onEvent)
74
+ if (type !== 'error') {
75
+ emitter.on('error', onError)
76
+ }
77
+ })
78
+ }
79
+
80
+ export default oncePromise
@@ -6,9 +6,11 @@
6
6
  * @param {string} [name='emitAsync'] - The name of the event to pipe (defaults to 'emitAsync').
7
7
  * @param {Object} [options] - Configuration for the pipeline.
8
8
  * @param {string} [options.asyncMode='serial'] - The mode of propagation ('serial' or 'parallel').
9
- * @param {string} [options.resultMode] - Strategy for aggregating results from the pipe chain ('collect', 'first').
9
+ * @param {string} [options.resultMode='last'] - Strategy for aggregating results from the pipe chain ('last', 'first', 'collect').
10
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel forwarding to remaining pipe targets (serial mode only).
11
+ * Can also be set on the source emitter via `configure({ signal })`.
10
12
  * @returns {Object} - An object with a `close` method that removes the pipeline between the two event emitters.
11
13
  * @throws {TypeError} - If either of the arguments is not an event emitter object.
12
14
  */
13
- export function pipeAsync(e1: import("./event-emitter").EventEmitter, e2: import("./event-emitter").EventEmitter, ...args: any[]): any;
15
+ export function pipeAsync(e1: import("./event-emitter").EventEmitter, e2: import("./event-emitter").EventEmitter, ...args: any[]): Object;
14
16
  export default pipeAsync;
package/lib/pipe-async.js CHANGED
@@ -23,7 +23,9 @@ const emit = _wrapEventEmitter.methods.emitAsync;
23
23
  * @param {string} [name='emitAsync'] - The name of the event to pipe (defaults to 'emitAsync').
24
24
  * @param {Object} [options] - Configuration for the pipeline.
25
25
  * @param {string} [options.asyncMode='serial'] - The mode of propagation ('serial' or 'parallel').
26
- * @param {string} [options.resultMode] - Strategy for aggregating results from the pipe chain ('collect', 'first').
26
+ * @param {string} [options.resultMode='last'] - Strategy for aggregating results from the pipe chain ('last', 'first', 'collect').
27
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel forwarding to remaining pipe targets (serial mode only).
28
+ * Can also be set on the source emitter via `configure({ signal })`.
27
29
  * @returns {Object} - An object with a `close` method that removes the pipeline between the two event emitters.
28
30
  * @throws {TypeError} - If either of the arguments is not an event emitter object.
29
31
  */
@@ -67,6 +69,9 @@ function pipeAsync(e1, e2 /* , name, options */) {
67
69
  const fn = target[name] || target.emitAsync || emit;
68
70
  return fn.apply(target, args);
69
71
  };
72
+
73
+ // Read signal from source emitter's runtime options, if configured
74
+ const signal = this._eeRuntimeOptions && this._eeRuntimeOptions.signal;
70
75
  if (asyncMode === 'parallel') {
71
76
  const promises = [emit.apply(this, arguments)];
72
77
  for (let i = 0; i < data.length; ++i) {
@@ -75,17 +80,25 @@ function pipeAsync(e1, e2 /* , name, options */) {
75
80
  const results = await Promise.all(promises);
76
81
  if (resultMode === 'collect') return results;
77
82
  if (resultMode === 'first') return results.find(r => r !== undefined);
78
- return results[0]; // Default: return main emitter's result
83
+ // Default: 'last' — return last non-undefined result in the pipe chain
84
+ for (let i = results.length - 1; i >= 0; i--) {
85
+ if (results[i] !== undefined) return results[i];
86
+ }
79
87
  } else {
80
88
  const mainResult = await emit.apply(this, arguments);
81
89
  const allResults = [mainResult];
82
90
  for (let i = 0; i < data.length; ++i) {
91
+ // Check signal before forwarding to each pipe target in serial mode
92
+ if (signal && signal.aborted) break;
83
93
  const res = await forward(data[i], arguments);
84
94
  allResults.push(res);
85
95
  }
86
96
  if (resultMode === 'collect') return allResults;
87
97
  if (resultMode === 'first') return allResults.find(r => r !== undefined);
88
- return mainResult; // Default: return main emitter's result
98
+ // Default: 'last' — return last non-undefined result in the pipe chain
99
+ for (let i = allResults.length - 1; i >= 0; i--) {
100
+ if (allResults[i] !== undefined) return allResults[i];
101
+ }
89
102
  }
90
103
  };
91
104
  (0, _defineProperty.default)(e1, name, desc.value, desc);
@@ -0,0 +1,100 @@
1
+ import defineProperty from 'util-ex/lib/defineProperty'
2
+
3
+ import {methods} from './wrap-event-emitter.mjs'
4
+ import arrRemove from './util/array-remove.mjs'
5
+ import validObject from './util/valid-object.mjs'
6
+
7
+ const hasOwnProperty = Object.prototype.hasOwnProperty
8
+ const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor
9
+ const arrFrom = Array.from
10
+ const emit = methods.emitAsync
11
+
12
+ /**
13
+ * Creates a pipeline between two event emitters, so that any events emitted by the first emitter are also emitted by the second emitter.
14
+ *
15
+ * @param {import('./event-emitter').EventEmitter} e1 - The first event emitter.
16
+ * @param {import('./event-emitter').EventEmitter} e2 - The second event emitter.
17
+ * @param {string} [name='emitAsync'] - The name of the event to pipe (defaults to 'emitAsync').
18
+ * @param {Object} [options] - Configuration for the pipeline.
19
+ * @param {string} [options.asyncMode='serial'] - The mode of propagation ('serial' or 'parallel').
20
+ * @param {string} [options.resultMode='last'] - Strategy for aggregating results from the pipe chain ('last', 'first', 'collect').
21
+ * @param {AbortSignal} [options.signal] - An AbortSignal to cancel forwarding to remaining pipe targets (serial mode only).
22
+ * Can also be set on the source emitter via `configure({ signal })`.
23
+ * @returns {Object} - An object with a `close` method that removes the pipeline between the two event emitters.
24
+ * @throws {TypeError} - If either of the arguments is not an event emitter object.
25
+ */
26
+ export function pipeAsync(e1, e2/* , name, options */) {
27
+ let pipes
28
+
29
+ (validObject(e1) && validObject(e2))
30
+ let name = arguments[2]
31
+ let options = arguments[3]
32
+ if (typeof name === 'object') {
33
+ options = name
34
+ name = undefined
35
+ }
36
+ if (name === undefined) {name = 'emitAsync'}
37
+ if (!options) {options = {}}
38
+
39
+ const result = {
40
+ close() { arrRemove.call(pipes, e2) }
41
+ };
42
+ if (hasOwnProperty.call(e1, '__eePipes__')) {
43
+ (pipes = e1.__eePipes__).push(e2)
44
+ return result
45
+ }
46
+ defineProperty(e1, '__eePipes__', pipes = [e2])
47
+ let desc = getOwnPropertyDescriptor(e1, name)
48
+ if (!desc) {
49
+ desc = {}
50
+ } else {
51
+ delete desc.get
52
+ delete desc.set
53
+ }
54
+ desc.value = async function () {
55
+ const data = arrFrom(pipes)
56
+ const asyncMode = options.asyncMode || 'serial'
57
+ const resultMode = options.resultMode
58
+
59
+ const forward = async (target, args) => {
60
+ const fn = target[name] || target.emitAsync || emit
61
+ return fn.apply(target, args)
62
+ }
63
+
64
+ // Read signal from source emitter's runtime options, if configured
65
+ const signal = this._eeRuntimeOptions && this._eeRuntimeOptions.signal
66
+
67
+ if (asyncMode === 'parallel') {
68
+ const promises = [emit.apply(this, arguments)]
69
+ for (let i = 0; i < data.length; ++i) {
70
+ promises.push(forward(data[i], arguments))
71
+ }
72
+ const results = await Promise.all(promises)
73
+ if (resultMode === 'collect') return results
74
+ if (resultMode === 'first') return results.find(r => r !== undefined)
75
+ // Default: 'last' — return last non-undefined result in the pipe chain
76
+ for (let i = results.length - 1; i >= 0; i--) {
77
+ if (results[i] !== undefined) return results[i]
78
+ }
79
+ } else {
80
+ const mainResult = await emit.apply(this, arguments)
81
+ const allResults = [mainResult]
82
+ for (let i = 0; i < data.length; ++i) {
83
+ // Check signal before forwarding to each pipe target in serial mode
84
+ if (signal && signal.aborted) break
85
+ const res = await forward(data[i], arguments)
86
+ allResults.push(res)
87
+ }
88
+ if (resultMode === 'collect') return allResults
89
+ if (resultMode === 'first') return allResults.find(r => r !== undefined)
90
+ // Default: 'last' — return last non-undefined result in the pipe chain
91
+ for (let i = allResults.length - 1; i >= 0; i--) {
92
+ if (allResults[i] !== undefined) return allResults[i]
93
+ }
94
+ }
95
+ }
96
+ defineProperty(e1, name, desc.value, desc)
97
+ return result
98
+ };
99
+
100
+ export default pipeAsync
package/lib/pipe.d.ts CHANGED
@@ -7,5 +7,5 @@
7
7
  * @returns {Object} - An object with a `close` method that removes the pipeline between the two event emitters.
8
8
  * @throws {TypeError} - If either of the arguments is not an event emitter object.
9
9
  */
10
- export function pipe(e1: import("./event-emitter").EventEmitter, e2: import("./event-emitter").EventEmitter, ...args: any[]): any;
10
+ export function pipe(e1: import("./event-emitter").EventEmitter, e2: import("./event-emitter").EventEmitter, ...args: any[]): Object;
11
11
  export default pipe;