@vandeurenglenn/little-pubsub 1.5.1 → 1.5.2

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 (4) hide show
  1. package/README.md +159 -141
  2. package/index.d.ts +28 -21
  3. package/index.js +66 -42
  4. package/package.json +36 -36
package/README.md CHANGED
@@ -1,141 +1,159 @@
1
- # little-pubsub
2
-
3
- > Small publish & subscribe class
4
-
5
- ## INSTALL
6
-
7
- #### npm
8
-
9
- ```sh
10
- npm i --save @vandeurenglenn/little-pubsub
11
- ```
12
-
13
- ## USAGE
14
-
15
- ```js
16
- import PubSub from '@vandeurenglenn/little-pubsub'
17
- const pubsub = new PubSub()
18
- ```
19
-
20
- ## Breaking Changes
21
-
22
- ### v1.5.0
23
-
24
- `subscribe[context]` & `unsubscribe[context]` -> `subscribe[options({keepValue, context})]`
25
-
26
- ```js
27
- // before
28
- pusbub.subscribe(topic, handler, context)
29
-
30
- // now
31
- pusbub.subscribe(topic, handler, { context })
32
- ```
33
-
34
- ## Example
35
-
36
- ```js
37
- import PubSub from '@vandeurenglenn/little-pubsub'
38
- const pubsub = new PubSub()
39
-
40
- pubsub.subscribe('event', (value) => {
41
- console.log(value)
42
- })
43
-
44
- pubsub.publish('event', 'hello')
45
- // always runs handler
46
- // (can use to overide littlePubsub.verbose setting without changing the behavior of the rest)
47
- pubsub.publishVerbose('event', 'hello')
48
-
49
- pubsub.unsubscribe('event', (value) => {
50
- console.log(value)
51
- })
52
-
53
- pubsub.hasSubscribers('event')
54
-
55
- await pubsub.once('event')
56
- ```
57
-
58
- ## API
59
-
60
- ### pubsub([options])
61
-
62
- `verbose`: when false only fires after value change<br>
63
-
64
- ```js
65
- pubsub = new PubSub({
66
- verbose: false // default: true
67
- })
68
- ```
69
-
70
- #### subscribe
71
-
72
- `name`: name of the channel to subscribe to<br>
73
- `handler`: method<br>
74
- `options`: { context, keepValue }<br>
75
-
76
- subscribing to an event will also return it's initial value
77
-
78
- ```js
79
- pubsub.subscribe('event-name', (data) => {
80
- console.log(data)
81
- })
82
- ```
83
-
84
- #### unsubscribe
85
-
86
- `name`: name of the channel to unsubscribe<br>
87
- `handler`: method<br>
88
- `options`: { context, keepValue }<br>
89
-
90
- ```js
91
- pubsub.unsubscribe(
92
- 'event-name',
93
- (data) => {
94
- console.log(data)
95
- },
96
- { keepValue: false } // default
97
- )
98
- ```
99
-
100
- #### publish
101
-
102
- `name`: name of the channel to publish to<br>
103
- `handler`: method<br>
104
- `verbose`: boolean<br>
105
-
106
- ```js
107
- pubsub.publish('event-name', 'data')
108
- ```
109
-
110
- #### publish
111
-
112
- `name`: name of the channel to publish to<br>
113
- `handler`: method<br>
114
-
115
- ```js
116
- pubsub.publishVerbose('event-name', 'data')
117
- ```
118
-
119
- #### once
120
-
121
- `name`: name of the channel to get the value from<br>
122
-
123
- ```js
124
- pubsub.getValue('event-name')
125
- ```
126
-
127
- #### once
128
-
129
- `name`: name of the channel to publish to<br>
130
-
131
- ```js
132
- await pubsub.once('event-name')
133
- ```
134
-
135
- #### hasSubscribers
136
-
137
- `name`: name of the channel to publish to<br>
138
-
139
- ```js
140
- pubsub.hasSubscribers('event-name')
141
- ```
1
+ # little-pubsub
2
+
3
+ > Small publish & subscribe class
4
+
5
+ ## INSTALL
6
+
7
+ #### npm
8
+
9
+ ```sh
10
+ npm i --save @vandeurenglenn/little-pubsub
11
+ ```
12
+
13
+ ## USAGE
14
+
15
+ ```js
16
+ import PubSub from '@vandeurenglenn/little-pubsub'
17
+ const pubsub = new PubSub()
18
+ ```
19
+
20
+ ## Breaking Changes
21
+
22
+ ### v1.5.0
23
+
24
+ `subscribe[context]` & `unsubscribe[context]` -> `subscribe[options({keepValue, context})]`
25
+
26
+ ```js
27
+ // before
28
+ pusbub.subscribe(topic, handler, context)
29
+
30
+ // now
31
+ pusbub.subscribe(topic, handler, { context })
32
+ ```
33
+
34
+ ## Example
35
+
36
+ ```js
37
+ import PubSub from '@vandeurenglenn/little-pubsub'
38
+ const pubsub = new PubSub()
39
+
40
+ pubsub.subscribe('event', (value) => {
41
+ console.log(value)
42
+ })
43
+
44
+ pubsub.publish('event', 'hello')
45
+ // always runs handler
46
+ // (can use to overide littlePubsub.verbose setting without changing the behavior of the rest)
47
+ pubsub.publishVerbose('event', 'hello')
48
+
49
+ pubsub.unsubscribe('event', (value) => {
50
+ console.log(value)
51
+ })
52
+
53
+ pubsub.hasSubscribers('event')
54
+
55
+ await pubsub.once('event')
56
+ ```
57
+
58
+ ## API
59
+
60
+ ### pubsub(verbose?)
61
+
62
+ `verbose`: when false only fires after value change (default: false)<br>
63
+
64
+ ```js
65
+ const pubsub = new PubSub() // verbose defaults to false
66
+ const pubsub = new PubSub(true) // always trigger handlers
67
+ ```
68
+
69
+ #### subscribe
70
+
71
+ `name`: name of the channel to subscribe to<br>
72
+ `handler`: method<br>
73
+ `options`: { context }<br>
74
+
75
+ Subscribing to an event returns an unsubscribe function. If value already exists, handler is called immediately.
76
+
77
+ ```js
78
+ const unsubscribe = pubsub.subscribe('event-name', (data) => {
79
+ console.log(data)
80
+ })
81
+
82
+ // Later: clean unsubscribe
83
+ unsubscribe()
84
+ ```
85
+
86
+ #### unsubscribe
87
+
88
+ `name`: name of the channel to unsubscribe<br>
89
+ `handler`: method<br>
90
+ `options`: { context, keepValue }<br>
91
+
92
+ ```js
93
+ pubsub.unsubscribe(
94
+ 'event-name',
95
+ (data) => {
96
+ console.log(data)
97
+ },
98
+ { keepValue: false } // default
99
+ )
100
+ ```
101
+
102
+ #### publish
103
+
104
+ `name`: name of the channel to publish to<br>
105
+ `handler`: method<br>
106
+ `verbose`: boolean<br>
107
+
108
+ ```js
109
+ pubsub.publish('event-name', 'data')
110
+ ```
111
+
112
+ #### publish
113
+
114
+ `name`: name of the channel to publish to<br>
115
+ `handler`: method<br>
116
+
117
+ ```js
118
+ pubsub.publishVerbose('event-name', 'data')
119
+ ```
120
+
121
+ #### once
122
+
123
+ `name`: name of the channel to get the value from<br>
124
+
125
+ ```js
126
+ pubsub.getValue('event-name')
127
+ ```
128
+
129
+ #### once
130
+
131
+ `name`: name of the channel to publish to<br>
132
+
133
+ ```js
134
+ await pubsub.once('event-name')
135
+ ```
136
+
137
+ #### hasSubscribers
138
+
139
+ `name`: name of the channel to check<br>
140
+
141
+ ```js
142
+ pubsub.hasSubscribers('event-name') // true or false
143
+ ```
144
+
145
+ #### subscriberCount
146
+
147
+ `name`: name of the channel to count<br>
148
+
149
+ ```js
150
+ pubsub.subscriberCount('event-name') // number of handlers
151
+ ```
152
+
153
+ #### clear
154
+
155
+ Removes all subscribers and values.
156
+
157
+ ```js
158
+ pubsub.clear()
159
+ ```
package/index.d.ts CHANGED
@@ -1,26 +1,33 @@
1
+ export type Handler<T = any> = (value: T, oldValue?: T) => void;
2
+ export interface HandlerEntry<T = any> {
3
+ original: Handler<T>;
4
+ bound: Handler<T>;
5
+ }
6
+ export interface Subscriber<T = any> {
7
+ value?: T;
8
+ handlers: HandlerEntry<T>[];
9
+ }
10
+ export interface SubscribeOptions {
11
+ context?: object;
12
+ }
13
+ export interface UnsubscribeOptions {
14
+ keepValue?: boolean;
15
+ context?: object;
16
+ }
17
+ export interface OnceOptions extends UnsubscribeOptions {
18
+ timeout?: number;
19
+ }
1
20
  export default class LittlePubSub {
2
- subscribers: {
3
- [index: string]: {
4
- value?: any;
5
- handlers?: Function[];
6
- };
7
- };
21
+ subscribers: Map<string, Subscriber>;
8
22
  verbose: boolean;
9
23
  constructor(verbose?: boolean);
10
- _handleContext(handler: Function, context?: Function): Function;
11
24
  hasSubscribers(event: string): boolean;
12
- getValue(event: string): any;
13
- subscribe(event: string, handler: Function, options?: {
14
- context?: Function;
15
- }): void;
16
- unsubscribe(event: string, handler: Function, options?: {
17
- keepValue?: boolean;
18
- context?: Function;
19
- }): void;
20
- publish(event: string, value: any, verbose?: boolean): void;
21
- publishVerbose(event: string, value: any): void;
22
- once(event: string, options?: {
23
- keepValue?: boolean;
24
- context?: Function;
25
- }): Promise<any>;
25
+ subscriberCount(event: string): number;
26
+ clear(): void;
27
+ getValue<T = any>(event: string): T | undefined;
28
+ subscribe<T = any>(event: string, handler: Handler<T>, options?: SubscribeOptions): () => void;
29
+ unsubscribe<T = any>(event: string, handler: Handler<T>, options?: UnsubscribeOptions): void;
30
+ publish<T = any>(event: string, value: T, verbose?: boolean): void;
31
+ publishVerbose<T = any>(event: string, value: T): void;
32
+ once<T = any>(event: string, options?: OnceOptions): Promise<T>;
26
33
  }
package/index.js CHANGED
@@ -1,56 +1,71 @@
1
1
  export default class LittlePubSub {
2
- subscribers = {};
2
+ subscribers = new Map();
3
3
  verbose;
4
4
  constructor(verbose) {
5
- this.verbose = verbose;
6
- }
7
- _handleContext(handler, context) {
8
- if (typeof context === 'undefined') {
9
- context = handler;
10
- }
11
- return context;
5
+ this.verbose = verbose ?? false;
12
6
  }
13
7
  hasSubscribers(event) {
14
- return this.subscribers[event] ? true : false;
8
+ return this.subscribers.has(event);
9
+ }
10
+ subscriberCount(event) {
11
+ return this.subscribers.get(event)?.handlers.length ?? 0;
12
+ }
13
+ clear() {
14
+ this.subscribers.clear();
15
15
  }
16
16
  getValue(event) {
17
- if (this.subscribers[event])
18
- return this.subscribers[event].value;
19
- return undefined;
17
+ return this.subscribers.get(event)?.value;
20
18
  }
21
19
  subscribe(event, handler, options) {
22
- if (!this.hasSubscribers(event))
23
- this.subscribers[event] = { handlers: [], value: undefined };
24
- const context = this._handleContext(handler, options?.context);
25
- const _handler = handler.bind(context);
26
- this.subscribers[event].handlers.push(_handler);
27
- if (this.subscribers[event].value !== undefined)
28
- _handler(this.subscribers[event].value, undefined);
20
+ let subscriber = this.subscribers.get(event);
21
+ if (subscriber === undefined) {
22
+ subscriber = { handlers: [], value: undefined };
23
+ this.subscribers.set(event, subscriber);
24
+ }
25
+ // Only bind if context is provided
26
+ const context = options?.context;
27
+ const boundHandler = context
28
+ ? handler.bind(context)
29
+ : handler;
30
+ subscriber.handlers.push({ original: handler, bound: boundHandler });
31
+ // Call handler immediately if value already exists
32
+ if (subscriber.value !== undefined) {
33
+ boundHandler(subscriber.value, undefined);
34
+ }
35
+ // Return unsubscribe function
36
+ return () => this.unsubscribe(event, handler, options);
29
37
  }
30
38
  unsubscribe(event, handler, options) {
31
- if (!options)
32
- options = { keepValue: false };
33
- if (!this.hasSubscribers(event))
39
+ const subscriber = this.subscribers.get(event);
40
+ if (subscriber === undefined)
34
41
  return;
35
- const context = this._handleContext(handler, options.context);
36
- const index = this.subscribers[event].handlers.indexOf(handler.bind(context));
37
- this.subscribers[event].handlers.splice(index);
38
- // delete event if no handlers left but supports keeping value for later use
39
- // (like when unsubscribing from a value that is still needed because others might subscibe to it)
40
- if (this.subscribers[event].handlers.length === 0 && !options.keepValue)
41
- delete this.subscribers[event];
42
+ const handlers = subscriber.handlers;
43
+ // Find and remove handler by original reference
44
+ for (let i = 0, len = handlers.length; i < len; i++) {
45
+ if (handlers[i].original === handler) {
46
+ handlers.splice(i, 1);
47
+ break;
48
+ }
49
+ }
50
+ // Delete event if no handlers left (unless keepValue is true)
51
+ if (handlers.length === 0 && !options?.keepValue) {
52
+ this.subscribers.delete(event);
53
+ }
42
54
  }
43
55
  publish(event, value, verbose) {
44
- // always set value even when having no subscribers
45
- if (!this.hasSubscribers(event))
46
- this.subscribers[event] = {
47
- handlers: []
48
- };
49
- const oldValue = this.subscribers[event]?.value;
56
+ let subscriber = this.subscribers.get(event);
57
+ if (subscriber === undefined) {
58
+ subscriber = { handlers: [], value: undefined };
59
+ this.subscribers.set(event, subscriber);
60
+ }
61
+ const oldValue = subscriber.value;
62
+ // Only trigger handlers if verbose or value changed
50
63
  if (this.verbose || verbose || oldValue !== value) {
51
- this.subscribers[event].value = value;
52
- for (const handler of this.subscribers[event].handlers) {
53
- handler(value, oldValue);
64
+ subscriber.value = value;
65
+ const handlers = subscriber.handlers;
66
+ const len = handlers.length;
67
+ for (let i = 0; i < len; i++) {
68
+ handlers[i].bound(value, oldValue);
54
69
  }
55
70
  }
56
71
  }
@@ -58,12 +73,21 @@ export default class LittlePubSub {
58
73
  this.publish(event, value, true);
59
74
  }
60
75
  once(event, options) {
61
- return new Promise((resolve) => {
62
- const cb = (value) => {
76
+ return new Promise((resolve, reject) => {
77
+ let timeoutId;
78
+ const handler = (value) => {
79
+ if (timeoutId !== undefined)
80
+ clearTimeout(timeoutId);
63
81
  resolve(value);
64
- this.unsubscribe(event, cb, options);
82
+ this.unsubscribe(event, handler, options);
65
83
  };
66
- this.subscribe(event, cb, options);
84
+ this.subscribe(event, handler, options);
85
+ if (options?.timeout !== undefined) {
86
+ timeoutId = setTimeout(() => {
87
+ this.unsubscribe(event, handler, options);
88
+ reject(new Error(`Timeout waiting for event "${event}"`));
89
+ }, options.timeout);
90
+ }
67
91
  });
68
92
  }
69
93
  }
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "@vandeurenglenn/little-pubsub",
3
- "version": "1.5.1",
4
- "description": "Publish & Subscribe",
5
- "main": "./index.js",
6
- "types": "./index.d.ts",
7
- "exports": {
8
- ".": {
9
- "import": "./index.js",
10
- "types": "./index.d.ts"
11
- }
12
- },
13
- "files": [
14
- "index.js",
15
- "index.d.ts"
16
- ],
17
- "repository": "https://github.com/vandeurenglenn/little-pubsub",
18
- "author": "vandeurenglenn <vandeurenglenn@gmail.com>",
19
- "license": "MIT",
20
- "type": "module",
21
- "private": false,
22
- "scripts": {
23
- "build": "npx tsc",
24
- "test": "node test.js"
25
- },
26
- "keywords": [
27
- "publish",
28
- "subscribe",
29
- "pubsub",
30
- "once"
31
- ],
32
- "devDependencies": {
33
- "tape": "^5.8.1",
34
- "typescript": "^5.6.2"
35
- }
36
- }
1
+ {
2
+ "name": "@vandeurenglenn/little-pubsub",
3
+ "version": "1.5.2",
4
+ "description": "Publish & Subscribe",
5
+ "main": "./index.js",
6
+ "types": "./index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./index.js",
10
+ "types": "./index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "index.d.ts"
16
+ ],
17
+ "repository": "https://github.com/vandeurenglenn/little-pubsub",
18
+ "author": "vandeurenglenn <vandeurenglenn@gmail.com>",
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "private": false,
22
+ "scripts": {
23
+ "build": "npx tsc",
24
+ "test": "node --test test.js",
25
+ "benchmark": "node benchmark.js"
26
+ },
27
+ "keywords": [
28
+ "publish",
29
+ "subscribe",
30
+ "pubsub",
31
+ "once"
32
+ ],
33
+ "devDependencies": {
34
+ "typescript": "^5.9.3"
35
+ }
36
+ }