comes 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/CLAUDE.md ADDED
@@ -0,0 +1,35 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ **comes** is a TypeScript event system library for simple pub/sub communication. It provides an `EventSystem` class with address-based events, lazy loaders, and interceptors.
8
+
9
+ ## Commands
10
+
11
+ - **Build:** `npm run build` (cleans `build/` then runs `tsc`)
12
+ - **Test:** `npm test` (runs vitest with coverage enabled by default)
13
+ - **Run single test:** `npx vitest run -t "Test name"` or `npx vitest run test/simple-emit.test.ts`
14
+
15
+ ## Architecture
16
+
17
+ Single-file library (`src/index.ts`) exporting:
18
+
19
+ - **`EventSystem`** class — the core. Manages a `data` map (keyed by string "address") where each address holds an `ES_ValueType` containing the last value, listeners array, optional loader, and loader error handler.
20
+ - **`es`** — a default singleton `EventSystem` instance exported for convenience.
21
+
22
+ Key concepts:
23
+ - **Addresses** are string keys identifying event channels.
24
+ - **`send(id, event)`** — async; runs interceptors (chain of responsibility), then notifies all listeners. Both interceptors and listeners are awaited sequentially.
25
+ - **`listen(id, listener)`** — registers a listener. If a value was already sent (has a `date`), the listener is immediately called with the cached `last` value. If no value exists but a loader is configured, the loader is triggered. Returns an unregister function.
26
+ - **Interceptors** (`addInter`) — transform/validate events before they reach listeners. The empty string `""` address matches all events. Throwing in an interceptor stops the chain and prevents listener notification.
27
+ - **Loaders** (`setLoader`) — lazy data fetchers invoked when the first listener registers on an address that has no value yet. `loaderCatch` handles loader errors and can optionally return a fallback value.
28
+
29
+ ## Build Output
30
+
31
+ TypeScript compiles from `src/` to `build/` (ES2017, Node16 modules). Declarations (`.d.ts`) are generated. Entry point: `build/index.js`.
32
+
33
+ ## Test Structure
34
+
35
+ Tests are in `test/` using vitest. A single test file (`test/simple-emit.test.ts`) covers: send/get, listen before/after emit, unregistering, listener errors, loader lifecycle (with/without error handlers), and interceptor chains.
package/README.md CHANGED
@@ -1,3 +1,272 @@
1
1
  # comes - Communication Event System
2
2
 
3
+ A simple, lightweight event system for TypeScript/JavaScript applications. It provides address-based pub/sub communication with built-in value caching, lazy loaders, and interceptors.
3
4
 
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install comes
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { es } from 'comes';
15
+
16
+ // Send a value to an address
17
+ await es.send('user/name', 'Alice');
18
+
19
+ // Listen for values on an address
20
+ const unregister = es.listen('user/name', (value) => {
21
+ console.log(value); // "Alice" (immediately, from cache)
22
+ });
23
+
24
+ // Stop listening
25
+ unregister();
26
+ ```
27
+
28
+ ## Core Concepts
29
+
30
+ - **Address**: A string key that identifies an event channel (e.g. `'user/name'`, `'app/status'`).
31
+ - **Value caching**: The last value sent to each address is cached. When a new listener registers, it immediately receives the cached value.
32
+ - **Interceptors**: Middleware functions that can transform, validate, or monitor values before they reach listeners.
33
+ - **Loaders**: Lazy data fetchers that run automatically when the first listener registers on an address that has no value yet.
34
+
35
+ ## API
36
+
37
+ ### Importing
38
+
39
+ ```ts
40
+ // Use the default singleton instance
41
+ import { es } from 'comes';
42
+
43
+ // Or create your own isolated instance
44
+ import { EventSystem } from 'comes';
45
+ const es = new EventSystem();
46
+ ```
47
+
48
+ ---
49
+
50
+ ### `send<T>(id: string, event: T): Promise<T>`
51
+
52
+ Sends a value to all listeners of the given address. Interceptors are executed first (in order), then each listener is called with the resulting value. The final value (after interceptors) is cached.
53
+
54
+ Returns the value after interceptor transformations.
55
+
56
+ ```ts
57
+ await es.send('counter', 1);
58
+ await es.send('user/profile', { name: 'Alice', age: 30 });
59
+ ```
60
+
61
+ If an interceptor or listener throws an error, `send` will throw that error.
62
+
63
+ ---
64
+
65
+ ### `listen(id: string, listener: EventListenerType): () => void`
66
+
67
+ Registers a listener on the given address. Returns an **unregister function** to remove the listener later.
68
+
69
+ **Behavior on registration:**
70
+ - If a value was already sent to this address, the listener is **immediately called** with the cached value.
71
+ - If no value exists but a **loader** is configured, the loader is triggered to fetch the initial value.
72
+ - If neither exists, the listener simply waits for the next `send`.
73
+
74
+ ```ts
75
+ // Basic usage
76
+ const unregister = es.listen('counter', (value) => {
77
+ console.log('Counter:', value);
78
+ });
79
+
80
+ // Later, stop listening
81
+ unregister();
82
+ ```
83
+
84
+ ---
85
+
86
+ ### `unlisten(id: string, listener: EventListenerType): void`
87
+
88
+ Removes a specific listener from the given address. This is useful when you need to unregister **inside** the listener itself, where the unregister function returned by `listen` may not be assigned yet.
89
+
90
+ ```ts
91
+ const listener = (value: string) => {
92
+ es.unlisten('my-event', listener); // safe to call here
93
+ console.log('Received once:', value);
94
+ };
95
+ es.listen('my-event', listener);
96
+ ```
97
+
98
+ ---
99
+
100
+ ### `get(id: string): ES_ValueType`
101
+
102
+ Returns a reference to the internal state object for the given address. Useful for inspecting the current cached value, the listener list, or the loader state.
103
+
104
+ ```ts
105
+ await es.send('status', 'online');
106
+
107
+ const data = es.get('status');
108
+ console.log(data.last); // "online"
109
+ console.log(data.date); // Date when last value was sent
110
+ console.log(data.listeners); // Array of registered listeners
111
+ ```
112
+
113
+ **`ES_ValueType` fields:**
114
+
115
+ | Field | Type | Description |
116
+ |---|---|---|
117
+ | `last` | `any` | The last value sent to this address |
118
+ | `listeners` | `EventListenerType[]` | Array of registered listeners |
119
+ | `date` | `Date \| undefined` | Timestamp of the last `send`. `undefined` means no value was sent yet |
120
+ | `loader` | `Function \| undefined` | The loader function, if configured |
121
+ | `loaderCatch` | `Function \| undefined` | The loader error handler, if configured |
122
+ | `loaderProm` | `Promise \| undefined` | The loader promise, while the loader is running |
123
+
124
+ ---
125
+
126
+ ### `addInter(id: string, interceptor: EventInterceptorType): () => void`
127
+
128
+ Adds an interceptor for the given address. Interceptors are called **before** listeners, in the order they were added. Each interceptor receives the value, can transform it, and must return the (possibly modified) value for the next interceptor in the chain.
129
+
130
+ Returns an **unregister function** to remove the interceptor.
131
+
132
+ **Special address `""`:** An interceptor registered with an empty string `""` as the address is called for **every** event on the `EventSystem`, regardless of address.
133
+
134
+ ```ts
135
+ // Transform values before listeners receive them
136
+ es.addInter('counter', async (id, event, es) => {
137
+ return event * 2; // double the value
138
+ });
139
+
140
+ es.listen('counter', (value) => {
141
+ console.log(value); // 20
142
+ });
143
+
144
+ await es.send('counter', 10);
145
+ ```
146
+
147
+ **Interceptor chain:**
148
+
149
+ ```ts
150
+ es.addInter('price', async (id, event, es) => {
151
+ return event + 1; // first: add 1
152
+ });
153
+
154
+ es.addInter('price', async (id, event, es) => {
155
+ return event * 10; // second: multiply by 10
156
+ });
157
+
158
+ await es.send('price', 5); // (5 + 1) * 10 = 60
159
+ ```
160
+
161
+ **Global interceptor (logging example):**
162
+
163
+ ```ts
164
+ es.addInter('', async (id, event, es) => {
165
+ console.log(`[${id}]`, event);
166
+ return event; // pass through unchanged
167
+ });
168
+ ```
169
+
170
+ If an interceptor throws an error, the chain stops and listeners are **not** called.
171
+
172
+ ---
173
+
174
+ ### `removeInter(id: string, interceptor: EventInterceptorType): void`
175
+
176
+ Removes a specific interceptor from the given address.
177
+
178
+ ```ts
179
+ const interceptor = async (id: string, event: number, es: EventSystem) => {
180
+ return event + 1;
181
+ };
182
+
183
+ es.addInter('counter', interceptor);
184
+ es.removeInter('counter', interceptor);
185
+ ```
186
+
187
+ ---
188
+
189
+ ### `setLoader(id: string, loader, loaderCatch?): void`
190
+
191
+ Configures a **loader** function for the given address. The loader is a lazy data fetcher that runs automatically when:
192
+
193
+ 1. The first listener registers on an address, **and**
194
+ 2. No value has been sent to that address yet.
195
+
196
+ If a value was already sent before the first listener, the loader is **never** called.
197
+
198
+ The loader function receives the address `id` as the first argument, and should call `es.send(id, value)` to deliver the loaded value.
199
+
200
+ ```ts
201
+ es.setLoader('user/profile', async (id) => {
202
+ const response = await fetch('/api/profile');
203
+ const data = await response.json();
204
+ es.send(id, data);
205
+ });
206
+
207
+ // The loader runs automatically when the first listener registers
208
+ es.listen('user/profile', (profile) => {
209
+ console.log(profile); // data from API
210
+ });
211
+ ```
212
+
213
+ **With error handler (`loaderCatch`):**
214
+
215
+ The optional third argument is an error handler invoked when the loader throws. It has three possible behaviors:
216
+
217
+ - **Return a value**: The loader resolves successfully with that value (sent to listeners).
218
+ - **Return `undefined`** (or no return): The loader rejects with the original error.
219
+ - **Throw a new error**: The loader rejects with the new error, and the original error is attached as `error.loaderEx`.
220
+
221
+ ```ts
222
+ es.setLoader(
223
+ 'user/profile',
224
+ async (id) => {
225
+ throw new Error('Network error');
226
+ },
227
+ async (id, error) => {
228
+ console.warn('Loader failed:', error.message);
229
+ return { name: 'Guest' }; // fallback value sent to listeners
230
+ }
231
+ );
232
+ ```
233
+
234
+ ---
235
+
236
+ ### `setLoaderCatch(id: string, loaderCatch): void`
237
+
238
+ Sets (or replaces) the error handler for the loader of a given address, independently from `setLoader`.
239
+
240
+ ```ts
241
+ es.setLoaderCatch('user/profile', async (id, error) => {
242
+ return { name: 'Default' }; // fallback value
243
+ });
244
+ ```
245
+
246
+ ---
247
+
248
+ ### `load<T>(id: string, ...args: any[]): Promise<T>`
249
+
250
+ Manually triggers the loader for the given address. Extra arguments are forwarded to the loader function.
251
+
252
+ If no loader is configured, returns the current cached value immediately.
253
+
254
+ ```ts
255
+ es.setLoader('data', async (id, page, limit) => {
256
+ const res = await fetch(`/api/data?page=${page}&limit=${limit}`);
257
+ es.send(id, await res.json());
258
+ });
259
+
260
+ // Manually trigger with parameters
261
+ await es.load('data', 1, 20);
262
+ ```
263
+
264
+ ## Types
265
+
266
+ ```ts
267
+ // Listener function signature
268
+ type EventListenerType<T = any> = (event: T) => void;
269
+
270
+ // Interceptor function signature
271
+ type EventInterceptorType<T = any> = (id: string, event: T, es: EventSystem) => T;
272
+ ```
package/build/index.d.ts CHANGED
@@ -1,9 +1,5 @@
1
- /**
2
- * Function to exclude an item from the array.
3
- * @param item Item that will be removed
4
- * @param list Array where to find the item
5
- */
6
- export declare function deleteFromArray(item: any, list: any[]): void;
1
+ export type EventListenerType<T = any> = (event: T) => void;
2
+ export type EventInterceptorType<T = any> = (id: string, event: T, es: EventSystem) => T;
7
3
  /**
8
4
  * Type that holds the value for an event address.
9
5
  */
@@ -15,14 +11,14 @@ export type ES_ValueType = {
15
11
  /**
16
12
  * Listeners of this event.
17
13
  */
18
- listeners: ((event: any) => void)[];
14
+ listeners: EventListenerType[];
19
15
  /**
20
16
  * Last time an event was emitted.
21
17
  *
22
18
  * If this value is "null" or "undefined" so no event/value was emitted yet.
23
19
  * In this case, the "last" field will be "null" or "undefined" also.
24
20
  */
25
- date?: Date;
21
+ date?: number;
26
22
  /**
27
23
  * Loader to run when the first listeners is registered and no event was
28
24
  * emitted yet.
@@ -72,6 +68,19 @@ export declare class EventSystem {
72
68
  data: {
73
69
  [id: string]: ES_ValueType;
74
70
  };
71
+ /**
72
+ * Interceptors to transform data.
73
+ * Interceptors are called in the installation order.
74
+ * The special empty string event address will be called for all events sent.
75
+ *
76
+ * The interceptors exists to transform, monitor, prepare or validate (or many other uses)
77
+ * the event sent to an address. The sequence of interceptors will be called like a
78
+ * "chain of responsibility" pattern, if any exception is thrown the following interceptors
79
+ * will not be executed, and the listeners not called.
80
+ */
81
+ inters: {
82
+ [id: string]: EventInterceptorType[];
83
+ };
75
84
  /**
76
85
  * Gets a reference to {@link ES_ValueType} of the address informed.
77
86
  * @param id The address name of the event
@@ -81,9 +90,8 @@ export declare class EventSystem {
81
90
  * Sends the value to the listeners of the event address.
82
91
  * @param id The address name of the event
83
92
  * @param event The value to send to listeners
84
- * @returns The value informed
85
93
  */
86
- emit<T>(id: string, event: T): T;
94
+ send<T>(id: string, event: T): Promise<T>;
87
95
  /**
88
96
  * Register a listener for the address.
89
97
  * The listener will receive the last value emitted.
@@ -95,7 +103,7 @@ export declare class EventSystem {
95
103
  * @param listener The listener, will be called every time a new value is emitted to this address
96
104
  * @returns An unregister function. Use this function to remove the listener
97
105
  */
98
- listen(id: string, listener: (event: any) => void): () => void;
106
+ listen(id: string, listener: EventListenerType): () => void;
99
107
  /**
100
108
  * Remove the listener from the list of this event address.
101
109
  *
@@ -115,7 +123,33 @@ export declare class EventSystem {
115
123
  * @param id The address name of the event
116
124
  * @param listener Listener to remove
117
125
  */
118
- unlisten(id: string, listener: (event: any) => void): void;
126
+ unlisten(id: string, listener: EventListenerType): void;
127
+ /**
128
+ * Add an interceptor to transform data.
129
+ * Interceptors are called in the installation order.
130
+ * The special empty string event address will be called for all events sent.
131
+ *
132
+ * The interceptors exists to transform, monitor, prepare or validate (or many other uses)
133
+ * the event sent to an address. The sequence of interceptors will be called like a
134
+ * "chain of responsibility" pattern, if any exception is thrown the following interceptors
135
+ * will not be executed, and the listeners not called.
136
+ *
137
+ * Interceptors will be executed synchronous in the call of the {@link send} method.
138
+ * So any exception thrown will be thrown also in the {@link send} call.
139
+ *
140
+ * @param id The address name of the event
141
+ * @param listener Interceptor to add
142
+ * @returns An unregister function. Use this function to remove the interceptor
143
+ */
144
+ addInter(id: string, listener: EventInterceptorType): () => void;
145
+ /**
146
+ * Remove an interceptor.
147
+ *
148
+ * @param id The address name of the event
149
+ * @param listener Interceptor to add
150
+ * @returns An unregister function. Use this function to remove the interceptor
151
+ */
152
+ removeInter(id: string, listener: EventInterceptorType): void;
119
153
  /**
120
154
  * Configure a {@link ES_ValueType.loader loader} to execute when the first listener is registered and no value exists yet.
121
155
  *
package/build/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.es = exports.EventSystem = void 0;
4
- exports.deleteFromArray = deleteFromArray;
5
4
  // -------
6
5
  /**
7
6
  * Function to exclude an item from the array.
@@ -9,13 +8,9 @@ exports.deleteFromArray = deleteFromArray;
9
8
  * @param list Array where to find the item
10
9
  */
11
10
  function deleteFromArray(item, list) {
12
- for (let i = list.length; i > -1; i--) {
13
- let it = list[i];
14
- if (it === item) {
15
- list.splice(i, 1);
16
- break;
17
- }
18
- }
11
+ const i = list.indexOf(item);
12
+ if (i !== -1)
13
+ list.splice(i, 1);
19
14
  }
20
15
  /**
21
16
  * Class that creates the field through which communication will occur.
@@ -27,6 +22,17 @@ class EventSystem {
27
22
  * holds the last value for the address.
28
23
  */
29
24
  this.data = {};
25
+ /**
26
+ * Interceptors to transform data.
27
+ * Interceptors are called in the installation order.
28
+ * The special empty string event address will be called for all events sent.
29
+ *
30
+ * The interceptors exists to transform, monitor, prepare or validate (or many other uses)
31
+ * the event sent to an address. The sequence of interceptors will be called like a
32
+ * "chain of responsibility" pattern, if any exception is thrown the following interceptors
33
+ * will not be executed, and the listeners not called.
34
+ */
35
+ this.inters = {};
30
36
  }
31
37
  /**
32
38
  * Gets a reference to {@link ES_ValueType} of the address informed.
@@ -41,15 +47,32 @@ class EventSystem {
41
47
  * Sends the value to the listeners of the event address.
42
48
  * @param id The address name of the event
43
49
  * @param event The value to send to listeners
44
- * @returns The value informed
45
50
  */
46
- emit(id, event) {
51
+ async send(id, event) {
52
+ let inters = (this.inters[''] || []).concat(this.inters[id] || []);
53
+ let newValue = event;
54
+ try {
55
+ for (let inte of inters) {
56
+ newValue = await inte(id, newValue, this);
57
+ }
58
+ }
59
+ catch (err) {
60
+ console.error(`inter:${id}`, err);
61
+ throw err;
62
+ }
47
63
  let esData = this.get(id);
48
- esData.last = event;
49
- esData.date = new Date();
50
- for (let func of esData.listeners)
51
- func(event);
52
- return event;
64
+ esData.last = newValue;
65
+ esData.date = Date.now();
66
+ for (let func of esData.listeners) {
67
+ try {
68
+ await func(newValue);
69
+ }
70
+ catch (err) {
71
+ console.error(`listen:${id}`, err);
72
+ throw err;
73
+ }
74
+ }
75
+ return newValue;
53
76
  }
54
77
  /**
55
78
  * Register a listener for the address.
@@ -94,6 +117,39 @@ class EventSystem {
94
117
  let esData = this.get(id);
95
118
  deleteFromArray(listener, esData.listeners);
96
119
  }
120
+ /**
121
+ * Add an interceptor to transform data.
122
+ * Interceptors are called in the installation order.
123
+ * The special empty string event address will be called for all events sent.
124
+ *
125
+ * The interceptors exists to transform, monitor, prepare or validate (or many other uses)
126
+ * the event sent to an address. The sequence of interceptors will be called like a
127
+ * "chain of responsibility" pattern, if any exception is thrown the following interceptors
128
+ * will not be executed, and the listeners not called.
129
+ *
130
+ * Interceptors will be executed synchronous in the call of the {@link send} method.
131
+ * So any exception thrown will be thrown also in the {@link send} call.
132
+ *
133
+ * @param id The address name of the event
134
+ * @param listener Interceptor to add
135
+ * @returns An unregister function. Use this function to remove the interceptor
136
+ */
137
+ addInter(id, listener) {
138
+ if (!this.inters[id])
139
+ this.inters[id] = [];
140
+ this.inters[id].push(listener);
141
+ return () => this.removeInter(id, listener);
142
+ }
143
+ /**
144
+ * Remove an interceptor.
145
+ *
146
+ * @param id The address name of the event
147
+ * @param listener Interceptor to add
148
+ * @returns An unregister function. Use this function to remove the interceptor
149
+ */
150
+ removeInter(id, listener) {
151
+ deleteFromArray(listener, this.inters[id]);
152
+ }
97
153
  /**
98
154
  * Configure a {@link ES_ValueType.loader loader} to execute when the first listener is registered and no value exists yet.
99
155
  *
@@ -149,7 +205,7 @@ class EventSystem {
149
205
  try {
150
206
  const catchRes = await esData.loaderCatch(id, ex);
151
207
  if (catchRes !== undefined) {
152
- this.emit(id, catchRes);
208
+ this.send(id, catchRes);
153
209
  res(catchRes);
154
210
  }
155
211
  }
package/nul ADDED
@@ -0,0 +1,2 @@
1
+ ERRO: Argumento/op��o inv�lido - 'F:/'.
2
+ Digite "TASKKILL /?" para obter detalhes sobre o uso.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comes",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Simple Event System Communication",
5
5
  "keywords": [
6
6
  "event",