@axijs/emitter 1.0.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 ADDED
@@ -0,0 +1,113 @@
1
+ # @axijs/emitter
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/@axijs/emitter.svg)](https://www.npmjs.com/package/@axijs/emitter)
4
+
5
+ A minimalistic, type-safe library for single-event and state emitting.
6
+
7
+ Inspired by the Observer pattern and RxJS (like `BehaviorSubject`),
8
+ it provides a clean way to manage subscriptions.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @axijs/emitter
14
+ # or
15
+ pnpm add @axijs/emitter
16
+ # or
17
+ yarn add @axijs/emitter
18
+ ```
19
+
20
+ ## Features
21
+
22
+ - **Strictly Typed**: Full TypeScript support for event arguments.
23
+ - **No Magic Strings**: Object-based emitters instead of string keys (e.g., `on('event-name')`).
24
+ - **State Emitting**: `StateEmitter` remembers the last emitted value and immediately triggers new subscribers.
25
+ - **Composite Subscriptions**: Easily group multiple subscriptions and teardown logic into a single `Subscription` object to prevent memory leaks.
26
+
27
+ ## Usage
28
+
29
+ ### 1. Basic Event Emitter
30
+ Create an isolated, strongly-typed event emitter.
31
+
32
+ ```typescript
33
+ import { Emitter } from '@axijs/emitter';
34
+
35
+ // Define an emitter that expects a string and a number
36
+ const onPlayerMove = new Emitter<[string, number]>();
37
+
38
+ // Subscribe to the event
39
+ const sub = onPlayerMove.subscribe((player, x) => {
40
+ console.log(`${player} moved to ${x}`);
41
+ });
42
+
43
+ // Fire the event
44
+ onPlayerMove.emit('Alice', 10);
45
+
46
+ // Subscribe to fire only once
47
+ onPlayerMove.once((player, x) => {
48
+ console.log('This will only run once!');
49
+ });
50
+
51
+ // Unsubscribe when no longer needed
52
+ sub.unsubscribe();
53
+ ```
54
+
55
+ ### 2. State Emitter
56
+ Acts like a state container (similar to `BehaviorSubject`).
57
+ It holds the latest value and immediately provides it to new subscribers.
58
+
59
+ ```typescript
60
+ import { StateEmitter } from '@axijs/emitter';
61
+
62
+ // Initialize with a default state
63
+ const health = new StateEmitter<[number]>([100]);
64
+
65
+ // New subscribers immediately receive the current state (100)
66
+ health.subscribe((currentHealth) => {
67
+ console.log(`Health is: ${currentHealth}`);
68
+ });
69
+
70
+ // Emit a new state. All listeners will be notified.
71
+ health.emit(80);
72
+
73
+ // Get current state synchronously without subscribing
74
+ console.log(health.value); // [80]
75
+ ```
76
+
77
+ ### 3. Composite Subscriptions
78
+ Group multiple unsubscriptions together. Very useful for cleaning up UI components or game objects.
79
+
80
+ ```typescript
81
+ import { Emitter, Subscription } from '@axijs/emitter';
82
+
83
+ const onJump = new Emitter<[]>();
84
+ const onShoot = new Emitter<[]>();
85
+
86
+ const masterSub = new Subscription();
87
+
88
+ // Add multiple subscriptions to the master Subscription
89
+ masterSub.add(onJump.subscribe(() => console.log('Jumped!')));
90
+ masterSub.add(onShoot.subscribe(() => console.log('Pew pew!')));
91
+
92
+ // You can also add custom teardown functions
93
+ masterSub.add(() => {
94
+ console.log('Custom cleanup logic executed');
95
+ });
96
+
97
+ // Later, when the component/object is destroyed:
98
+ masterSub.unsubscribe();
99
+ // This automatically unsubscribes from both events and runs the custom logic
100
+ ```
101
+
102
+ ## API Documentation
103
+
104
+ For a complete list of classes, interfaces, and methods, please visit the [API Documentation](https://github.com/axijs/tools/tree/main/packages/emitter/docs/api).
105
+
106
+ ## License
107
+
108
+ MIT
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Defines the public, read-only contract for an event emitter.
3
+ * It allows subscribing to an event but not emitting it.
4
+ * @template T A tuple representing the types of the event arguments.
5
+ */
6
+ type Subscribable<T extends any[]> = {
7
+ readonly listenerCount: number;
8
+ /**
9
+ * Subscribes a listener to this event.
10
+ * @returns A function to unsubscribe the listener.
11
+ */
12
+ subscribe(listener: (...args: T) => void): Unsubscribable;
13
+ unsubscribe(listener: (...args: T) => void): boolean;
14
+ clear(): void;
15
+ };
16
+ /**
17
+ * Describes an object that can be unsubscribed from.
18
+ */
19
+ interface Unsubscribable {
20
+ unsubscribe(): void;
21
+ }
22
+
23
+ /**
24
+ * Represents a disposable resource, such as the execution of an Observable or an Event Listener.
25
+ * Allows grouping multiple teardown logic into a single unit (Composite Subscription).
26
+ */
27
+ declare class Subscription implements Unsubscribable {
28
+ private _closed;
29
+ private _teardowns;
30
+ /**
31
+ * Indicates whether this subscription has already been unsubscribed.
32
+ */
33
+ get closed(): boolean;
34
+ /**
35
+ * @param teardown Optional initial teardown logic to execute when unsubscribed.
36
+ */
37
+ constructor(teardown?: () => void);
38
+ /**
39
+ * Adds a teardown logic to this subscription.
40
+ * If the subscription is already closed, the teardown is executed immediately.
41
+ * @param teardown A function or another Unsubscribable object to be managed.
42
+ */
43
+ add(teardown: Unsubscribable | (() => void)): void;
44
+ /**
45
+ * Disposes the resources held by the subscription.
46
+ * Executes all attached teardown logic and clears the list.
47
+ */
48
+ unsubscribe(): void;
49
+ }
50
+
51
+ /**
52
+ * A minimal, type-safe event emitter for a single event.
53
+ * It does not manage state, it only manages subscribers and event dispatching.
54
+ * @template T A tuple representing the types of the event arguments.
55
+ */
56
+ declare class Emitter<T extends any[]> implements Subscribable<T> {
57
+ private listeners;
58
+ /**
59
+ * Returns the number of listeners.
60
+ */
61
+ get listenerCount(): number;
62
+ /**
63
+ * Subscribes a listener to this event.
64
+ * @returns A Subscription object to manage the unsubscription.
65
+ */
66
+ subscribe(listener: (...args: T) => void): Subscription;
67
+ /**
68
+ * Subscribes a listener that triggers only once and then automatically unsubscribes.
69
+ * @param listener The callback function to execute once.
70
+ * @returns A Subscription object (can be used to cancel before the event fires).
71
+ */
72
+ once(listener: (...args: T) => void): Subscription;
73
+ /**
74
+ * Manually unsubscribe by listener
75
+ * @returns returns true if an listener has been removed, or false if the listener does not exist.
76
+ */
77
+ unsubscribe(listener: (...args: T) => void): boolean;
78
+ /**
79
+ * Dispatches the event to all subscribed listeners.
80
+ */
81
+ emit(...args: T): void;
82
+ /**
83
+ * Clears all listeners.
84
+ */
85
+ clear(): void;
86
+ }
87
+
88
+ /**
89
+ * An Emitter that stores the last emitted value.
90
+ * New subscribers immediately receive the last value upon subscription.
91
+ */
92
+ declare class StateEmitter<T extends any[]> extends Emitter<T> {
93
+ private _lastValue;
94
+ /**
95
+ * @param initialValue Optional initial value to set.
96
+ */
97
+ constructor(initialValue?: T);
98
+ /**
99
+ * Gets the current value synchronously without subscribing.
100
+ */
101
+ get value(): T | undefined;
102
+ /**
103
+ * Updates the state and notifies all listeners.
104
+ * @param args The new value(s).
105
+ */
106
+ emit(...args: T): void;
107
+ /**
108
+ * Subscribes to the event. If a value exists, the listener is called immediately.
109
+ * @param listener The callback function.
110
+ * @returns A Subscription object.
111
+ */
112
+ subscribe(listener: (...args: T) => void): Subscription;
113
+ clear(): void;
114
+ }
115
+
116
+ export { Emitter, StateEmitter, type Subscribable, Subscription, type Unsubscribable };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Defines the public, read-only contract for an event emitter.
3
+ * It allows subscribing to an event but not emitting it.
4
+ * @template T A tuple representing the types of the event arguments.
5
+ */
6
+ type Subscribable<T extends any[]> = {
7
+ readonly listenerCount: number;
8
+ /**
9
+ * Subscribes a listener to this event.
10
+ * @returns A function to unsubscribe the listener.
11
+ */
12
+ subscribe(listener: (...args: T) => void): Unsubscribable;
13
+ unsubscribe(listener: (...args: T) => void): boolean;
14
+ clear(): void;
15
+ };
16
+ /**
17
+ * Describes an object that can be unsubscribed from.
18
+ */
19
+ interface Unsubscribable {
20
+ unsubscribe(): void;
21
+ }
22
+
23
+ /**
24
+ * Represents a disposable resource, such as the execution of an Observable or an Event Listener.
25
+ * Allows grouping multiple teardown logic into a single unit (Composite Subscription).
26
+ */
27
+ declare class Subscription implements Unsubscribable {
28
+ private _closed;
29
+ private _teardowns;
30
+ /**
31
+ * Indicates whether this subscription has already been unsubscribed.
32
+ */
33
+ get closed(): boolean;
34
+ /**
35
+ * @param teardown Optional initial teardown logic to execute when unsubscribed.
36
+ */
37
+ constructor(teardown?: () => void);
38
+ /**
39
+ * Adds a teardown logic to this subscription.
40
+ * If the subscription is already closed, the teardown is executed immediately.
41
+ * @param teardown A function or another Unsubscribable object to be managed.
42
+ */
43
+ add(teardown: Unsubscribable | (() => void)): void;
44
+ /**
45
+ * Disposes the resources held by the subscription.
46
+ * Executes all attached teardown logic and clears the list.
47
+ */
48
+ unsubscribe(): void;
49
+ }
50
+
51
+ /**
52
+ * A minimal, type-safe event emitter for a single event.
53
+ * It does not manage state, it only manages subscribers and event dispatching.
54
+ * @template T A tuple representing the types of the event arguments.
55
+ */
56
+ declare class Emitter<T extends any[]> implements Subscribable<T> {
57
+ private listeners;
58
+ /**
59
+ * Returns the number of listeners.
60
+ */
61
+ get listenerCount(): number;
62
+ /**
63
+ * Subscribes a listener to this event.
64
+ * @returns A Subscription object to manage the unsubscription.
65
+ */
66
+ subscribe(listener: (...args: T) => void): Subscription;
67
+ /**
68
+ * Subscribes a listener that triggers only once and then automatically unsubscribes.
69
+ * @param listener The callback function to execute once.
70
+ * @returns A Subscription object (can be used to cancel before the event fires).
71
+ */
72
+ once(listener: (...args: T) => void): Subscription;
73
+ /**
74
+ * Manually unsubscribe by listener
75
+ * @returns returns true if an listener has been removed, or false if the listener does not exist.
76
+ */
77
+ unsubscribe(listener: (...args: T) => void): boolean;
78
+ /**
79
+ * Dispatches the event to all subscribed listeners.
80
+ */
81
+ emit(...args: T): void;
82
+ /**
83
+ * Clears all listeners.
84
+ */
85
+ clear(): void;
86
+ }
87
+
88
+ /**
89
+ * An Emitter that stores the last emitted value.
90
+ * New subscribers immediately receive the last value upon subscription.
91
+ */
92
+ declare class StateEmitter<T extends any[]> extends Emitter<T> {
93
+ private _lastValue;
94
+ /**
95
+ * @param initialValue Optional initial value to set.
96
+ */
97
+ constructor(initialValue?: T);
98
+ /**
99
+ * Gets the current value synchronously without subscribing.
100
+ */
101
+ get value(): T | undefined;
102
+ /**
103
+ * Updates the state and notifies all listeners.
104
+ * @param args The new value(s).
105
+ */
106
+ emit(...args: T): void;
107
+ /**
108
+ * Subscribes to the event. If a value exists, the listener is called immediately.
109
+ * @param listener The callback function.
110
+ * @returns A Subscription object.
111
+ */
112
+ subscribe(listener: (...args: T) => void): Subscription;
113
+ clear(): void;
114
+ }
115
+
116
+ export { Emitter, StateEmitter, type Subscribable, Subscription, type Unsubscribable };
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Emitter: () => Emitter,
24
+ StateEmitter: () => StateEmitter,
25
+ Subscription: () => Subscription
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // ../ensure/dist/index.mjs
30
+ function isFunction(value) {
31
+ return typeof value === "function";
32
+ }
33
+
34
+ // src/subscription.ts
35
+ var Subscription = class {
36
+ _closed = false;
37
+ _teardowns = [];
38
+ /**
39
+ * Indicates whether this subscription has already been unsubscribed.
40
+ */
41
+ get closed() {
42
+ return this._closed;
43
+ }
44
+ /**
45
+ * @param teardown Optional initial teardown logic to execute when unsubscribed.
46
+ */
47
+ constructor(teardown) {
48
+ if (teardown) {
49
+ this._teardowns.push(teardown);
50
+ }
51
+ }
52
+ /**
53
+ * Adds a teardown logic to this subscription.
54
+ * If the subscription is already closed, the teardown is executed immediately.
55
+ * @param teardown A function or another Unsubscribable object to be managed.
56
+ */
57
+ add(teardown) {
58
+ if (this._closed) {
59
+ isFunction(teardown) ? teardown() : teardown.unsubscribe();
60
+ return;
61
+ }
62
+ this._teardowns.push(isFunction(teardown) ? teardown : () => teardown.unsubscribe());
63
+ }
64
+ /**
65
+ * Disposes the resources held by the subscription.
66
+ * Executes all attached teardown logic and clears the list.
67
+ */
68
+ unsubscribe() {
69
+ if (this._closed) return;
70
+ this._closed = true;
71
+ this._teardowns.forEach((fn) => fn());
72
+ this._teardowns = [];
73
+ }
74
+ };
75
+
76
+ // src/emitter.ts
77
+ var Emitter = class {
78
+ listeners = /* @__PURE__ */ new Set();
79
+ /**
80
+ * Returns the number of listeners.
81
+ */
82
+ get listenerCount() {
83
+ return this.listeners.size;
84
+ }
85
+ /**
86
+ * Subscribes a listener to this event.
87
+ * @returns A Subscription object to manage the unsubscription.
88
+ */
89
+ subscribe(listener) {
90
+ this.listeners.add(listener);
91
+ return new Subscription(() => this.unsubscribe(listener));
92
+ }
93
+ /**
94
+ * Subscribes a listener that triggers only once and then automatically unsubscribes.
95
+ * @param listener The callback function to execute once.
96
+ * @returns A Subscription object (can be used to cancel before the event fires).
97
+ */
98
+ once(listener) {
99
+ const wrapper = (...args) => {
100
+ this.unsubscribe(wrapper);
101
+ listener(...args);
102
+ };
103
+ return this.subscribe(wrapper);
104
+ }
105
+ /**
106
+ * Manually unsubscribe by listener
107
+ * @returns returns true if an listener has been removed, or false if the listener does not exist.
108
+ */
109
+ unsubscribe(listener) {
110
+ return this.listeners.delete(listener);
111
+ }
112
+ /**
113
+ * Dispatches the event to all subscribed listeners.
114
+ */
115
+ emit(...args) {
116
+ this.listeners.forEach((listener) => listener(...args));
117
+ }
118
+ /**
119
+ * Clears all listeners.
120
+ */
121
+ clear() {
122
+ this.listeners.clear();
123
+ }
124
+ };
125
+
126
+ // src/state-emitter.ts
127
+ var StateEmitter = class extends Emitter {
128
+ _lastValue;
129
+ /**
130
+ * @param initialValue Optional initial value to set.
131
+ */
132
+ constructor(initialValue) {
133
+ super();
134
+ this._lastValue = initialValue ?? void 0;
135
+ }
136
+ /**
137
+ * Gets the current value synchronously without subscribing.
138
+ */
139
+ get value() {
140
+ return this._lastValue;
141
+ }
142
+ /**
143
+ * Updates the state and notifies all listeners.
144
+ * @param args The new value(s).
145
+ */
146
+ emit(...args) {
147
+ this._lastValue = args;
148
+ super.emit(...args);
149
+ }
150
+ /**
151
+ * Subscribes to the event. If a value exists, the listener is called immediately.
152
+ * @param listener The callback function.
153
+ * @returns A Subscription object.
154
+ */
155
+ subscribe(listener) {
156
+ const unsubscribe = super.subscribe(listener);
157
+ if (this._lastValue !== void 0) {
158
+ listener(...this._lastValue);
159
+ }
160
+ return unsubscribe;
161
+ }
162
+ clear() {
163
+ super.clear();
164
+ this._lastValue = void 0;
165
+ }
166
+ };
167
+ // Annotate the CommonJS export names for ESM import in node:
168
+ 0 && (module.exports = {
169
+ Emitter,
170
+ StateEmitter,
171
+ Subscription
172
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,143 @@
1
+ // ../ensure/dist/index.mjs
2
+ function isFunction(value) {
3
+ return typeof value === "function";
4
+ }
5
+
6
+ // src/subscription.ts
7
+ var Subscription = class {
8
+ _closed = false;
9
+ _teardowns = [];
10
+ /**
11
+ * Indicates whether this subscription has already been unsubscribed.
12
+ */
13
+ get closed() {
14
+ return this._closed;
15
+ }
16
+ /**
17
+ * @param teardown Optional initial teardown logic to execute when unsubscribed.
18
+ */
19
+ constructor(teardown) {
20
+ if (teardown) {
21
+ this._teardowns.push(teardown);
22
+ }
23
+ }
24
+ /**
25
+ * Adds a teardown logic to this subscription.
26
+ * If the subscription is already closed, the teardown is executed immediately.
27
+ * @param teardown A function or another Unsubscribable object to be managed.
28
+ */
29
+ add(teardown) {
30
+ if (this._closed) {
31
+ isFunction(teardown) ? teardown() : teardown.unsubscribe();
32
+ return;
33
+ }
34
+ this._teardowns.push(isFunction(teardown) ? teardown : () => teardown.unsubscribe());
35
+ }
36
+ /**
37
+ * Disposes the resources held by the subscription.
38
+ * Executes all attached teardown logic and clears the list.
39
+ */
40
+ unsubscribe() {
41
+ if (this._closed) return;
42
+ this._closed = true;
43
+ this._teardowns.forEach((fn) => fn());
44
+ this._teardowns = [];
45
+ }
46
+ };
47
+
48
+ // src/emitter.ts
49
+ var Emitter = class {
50
+ listeners = /* @__PURE__ */ new Set();
51
+ /**
52
+ * Returns the number of listeners.
53
+ */
54
+ get listenerCount() {
55
+ return this.listeners.size;
56
+ }
57
+ /**
58
+ * Subscribes a listener to this event.
59
+ * @returns A Subscription object to manage the unsubscription.
60
+ */
61
+ subscribe(listener) {
62
+ this.listeners.add(listener);
63
+ return new Subscription(() => this.unsubscribe(listener));
64
+ }
65
+ /**
66
+ * Subscribes a listener that triggers only once and then automatically unsubscribes.
67
+ * @param listener The callback function to execute once.
68
+ * @returns A Subscription object (can be used to cancel before the event fires).
69
+ */
70
+ once(listener) {
71
+ const wrapper = (...args) => {
72
+ this.unsubscribe(wrapper);
73
+ listener(...args);
74
+ };
75
+ return this.subscribe(wrapper);
76
+ }
77
+ /**
78
+ * Manually unsubscribe by listener
79
+ * @returns returns true if an listener has been removed, or false if the listener does not exist.
80
+ */
81
+ unsubscribe(listener) {
82
+ return this.listeners.delete(listener);
83
+ }
84
+ /**
85
+ * Dispatches the event to all subscribed listeners.
86
+ */
87
+ emit(...args) {
88
+ this.listeners.forEach((listener) => listener(...args));
89
+ }
90
+ /**
91
+ * Clears all listeners.
92
+ */
93
+ clear() {
94
+ this.listeners.clear();
95
+ }
96
+ };
97
+
98
+ // src/state-emitter.ts
99
+ var StateEmitter = class extends Emitter {
100
+ _lastValue;
101
+ /**
102
+ * @param initialValue Optional initial value to set.
103
+ */
104
+ constructor(initialValue) {
105
+ super();
106
+ this._lastValue = initialValue ?? void 0;
107
+ }
108
+ /**
109
+ * Gets the current value synchronously without subscribing.
110
+ */
111
+ get value() {
112
+ return this._lastValue;
113
+ }
114
+ /**
115
+ * Updates the state and notifies all listeners.
116
+ * @param args The new value(s).
117
+ */
118
+ emit(...args) {
119
+ this._lastValue = args;
120
+ super.emit(...args);
121
+ }
122
+ /**
123
+ * Subscribes to the event. If a value exists, the listener is called immediately.
124
+ * @param listener The callback function.
125
+ * @returns A Subscription object.
126
+ */
127
+ subscribe(listener) {
128
+ const unsubscribe = super.subscribe(listener);
129
+ if (this._lastValue !== void 0) {
130
+ listener(...this._lastValue);
131
+ }
132
+ return unsubscribe;
133
+ }
134
+ clear() {
135
+ super.clear();
136
+ this._lastValue = void 0;
137
+ }
138
+ };
139
+ export {
140
+ Emitter,
141
+ StateEmitter,
142
+ Subscription
143
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@axijs/emitter",
3
+ "version": "1.0.0",
4
+ "description": "A minimalistic, type-safe library for single-event and state emitting",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/axijs/tools/tree/main/packages/emitter",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/axijs/tools.git",
10
+ "directory": "packages/emitter"
11
+ },
12
+ "keywords": [
13
+ ],
14
+ "types": "./dist/index.d.ts",
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "prebuild": "npm test",
27
+ "docs": "typedoc src/index.ts --out docs/api --options ../../typedoc.json",
28
+ "test": "vitest run"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ]
33
+ }