@pawells/rxjs-events 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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/build/async-generator-esn.d.ts +27 -0
  4. package/build/async-generator-esn.d.ts.map +1 -0
  5. package/build/async-generator-esn.js +2 -0
  6. package/build/async-generator-esn.js.map +1 -0
  7. package/build/async-observable.d.ts +72 -0
  8. package/build/async-observable.d.ts.map +1 -0
  9. package/build/async-observable.js +201 -0
  10. package/build/async-observable.js.map +1 -0
  11. package/build/event-data.d.ts +17 -0
  12. package/build/event-data.d.ts.map +1 -0
  13. package/build/event-data.js +2 -0
  14. package/build/event-data.js.map +1 -0
  15. package/build/event-filter.d.ts +44 -0
  16. package/build/event-filter.d.ts.map +1 -0
  17. package/build/event-filter.js +66 -0
  18. package/build/event-filter.js.map +1 -0
  19. package/build/event-function.d.ts +23 -0
  20. package/build/event-function.d.ts.map +1 -0
  21. package/build/event-function.js +2 -0
  22. package/build/event-function.js.map +1 -0
  23. package/build/extract-event-payload.d.ts +7 -0
  24. package/build/extract-event-payload.d.ts.map +1 -0
  25. package/build/extract-event-payload.js +2 -0
  26. package/build/extract-event-payload.js.map +1 -0
  27. package/build/filter-criteria.d.ts +8 -0
  28. package/build/filter-criteria.d.ts.map +1 -0
  29. package/build/filter-criteria.js +2 -0
  30. package/build/filter-criteria.js.map +1 -0
  31. package/build/handler.d.ts +225 -0
  32. package/build/handler.d.ts.map +1 -0
  33. package/build/handler.js +341 -0
  34. package/build/handler.js.map +1 -0
  35. package/build/index.d.ts +9 -0
  36. package/build/index.d.ts.map +1 -0
  37. package/build/index.js +7 -0
  38. package/build/index.js.map +1 -0
  39. package/build/types.d.ts +20 -0
  40. package/build/types.d.ts.map +1 -0
  41. package/build/types.js +2 -0
  42. package/build/types.js.map +1 -0
  43. package/package.json +80 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Filters events based on payload property matching criteria.
3
+ * Performs strict equality comparison between event payload properties and filter arguments.
4
+ *
5
+ * @template TEvent - The event data type extending TEventData
6
+ * @param event - The event to filter, must have exactly one property representing the event type
7
+ * @param args - Filter criteria object with property-value pairs to match against the event payload
8
+ * @returns true if the event matches all filter criteria or if no filter is provided, false otherwise
9
+ *
10
+ * @throws {Error} 'No Event' - When event is null or undefined
11
+ * @throws {Error} 'More than one payload structure.' - When event has more than one top-level property
12
+ * @throws {Error} 'No Payload' - When the event's payload is null or undefined
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * interface UserEvent extends TEventData {
17
+ * UserCreated: {
18
+ * userId: string;
19
+ * username: string;
20
+ * role: string;
21
+ * };
22
+ * }
23
+ *
24
+ * const event: UserEvent = {
25
+ * UserCreated: { userId: '123', username: 'john', role: 'admin' }
26
+ * };
27
+ *
28
+ * // Match by single property
29
+ * EventFilter(event, { role: 'admin' }); // true
30
+ * EventFilter(event, { role: 'user' }); // false
31
+ *
32
+ * // Match by multiple properties
33
+ * EventFilter(event, { username: 'john', role: 'admin' }); // true
34
+ * EventFilter(event, { username: 'john', role: 'user' }); // false
35
+ *
36
+ * // No filter (always passes)
37
+ * EventFilter(event, null); // true
38
+ * EventFilter(event, undefined); // true
39
+ * ```
40
+ */
41
+ export function EventFilter(event, args) {
42
+ if (!event)
43
+ throw new Error('No Event');
44
+ // If no arguments are provided, then we are not filtering anything.
45
+ if (!args)
46
+ return true;
47
+ // Identify Payload Key
48
+ const eventKeys = Object.keys(event);
49
+ if (eventKeys.length === 0)
50
+ throw new Error('No payload structure.');
51
+ if (eventKeys.length > 1)
52
+ throw new Error('More than one payload structure.');
53
+ const eventKey = eventKeys[0];
54
+ // Get Payload - now properly typed
55
+ const payload = event[eventKey];
56
+ if (!payload)
57
+ throw new Error('No Payload');
58
+ for (const [key, filterValue] of Object.entries(args)) {
59
+ // Type-safe property access with proper unknown handling
60
+ const payloadValue = payload[key];
61
+ if (payloadValue !== filterValue)
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+ //# sourceMappingURL=event-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-filter.js","sourceRoot":"","sources":["../src/event-filter.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,WAAW,CAC1B,KAAa,EACb,IAAwC;IAExC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;IACxC,oEAAoE;IACpE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,uBAAuB;IACvB,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAErC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACrE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAE9E,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAiB,CAAC;IAE9C,mCAAmC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAiC,CAAC;IAEhE,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;IAE5C,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,yDAAyD;QACzD,MAAM,YAAY,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;QAE/D,IAAI,YAAY,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC;IAChD,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { TEventData } from './event-data.js';
2
+ /**
3
+ * Function type for handling events. Can be synchronous or asynchronous.
4
+ *
5
+ * @template TEvent - The specific event data type extending TEventData
6
+ * @param data - The event data to be processed
7
+ * @returns Promise<void> for async handlers, void for sync handlers
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Synchronous event handler
12
+ * const syncHandler: TEventFunction<UserCreatedEvent> = (event) => {
13
+ * console.log('User created:', event.UserCreated.username);
14
+ * };
15
+ *
16
+ * // Asynchronous event handler
17
+ * const asyncHandler: TEventFunction<UserCreatedEvent> = async (event) => {
18
+ * await saveUserToDatabase(event.UserCreated);
19
+ * };
20
+ * ```
21
+ */
22
+ export type TEventFunction<TEvent extends TEventData = TEventData> = (_data: TEvent) => Promise<void> | void;
23
+ //# sourceMappingURL=event-function.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-function.d.ts","sourceRoot":"","sources":["../src/event-function.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,cAAc,CAAC,MAAM,SAAS,UAAU,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=event-function.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-function.js","sourceRoot":"","sources":["../src/event-function.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import { TEventData } from './event-data.js';
2
+ /**
3
+ * Type to extract the payload type from an event.
4
+ * Takes the first (and only) property value from an TEventData object.
5
+ */
6
+ export type TExtractEventPayload<TEvent extends TEventData> = TEvent[keyof TEvent];
7
+ //# sourceMappingURL=extract-event-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-event-payload.d.ts","sourceRoot":"","sources":["../src/extract-event-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;GAGG;AACH,MAAM,MAAM,oBAAoB,CAAC,MAAM,SAAS,UAAU,IAAI,MAAM,CAAC,MAAM,MAAM,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=extract-event-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-event-payload.js","sourceRoot":"","sources":["../src/extract-event-payload.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Filter criteria interface for event filtering.
3
+ * Represents property-value pairs to match against event payloads.
4
+ */
5
+ export interface IFilterCriteria {
6
+ [key: string]: unknown;
7
+ }
8
+ //# sourceMappingURL=filter-criteria.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-criteria.d.ts","sourceRoot":"","sources":["../src/filter-criteria.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=filter-criteria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-criteria.js","sourceRoot":"","sources":["../src/filter-criteria.ts"],"names":[],"mappings":""}
@@ -0,0 +1,225 @@
1
+ import { Subscription } from 'rxjs';
2
+ import { TEventData } from './event-data.js';
3
+ import { TEventFunction } from './event-function.js';
4
+ /**
5
+ * Event handler class that provides reactive event management with RxJS integration.
6
+ * Supports subscription management, event triggering, and async iteration patterns.
7
+ *
8
+ * @template TObject - The type of data objects that can be triggered as events
9
+ * @template TEvent - The event data type extending TEventData that will be received by subscribers
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * interface MessageData {
14
+ * id: number;
15
+ * text: string;
16
+ * }
17
+ *
18
+ * interface MessageEvent extends TEventData {
19
+ * MessageReceived: MessageData;
20
+ * }
21
+ *
22
+ * // Create handler
23
+ * const handler = new EventHandler<MessageData, MessageEvent>('MessageReceived');
24
+ *
25
+ * // Subscribe to events
26
+ * const subscription = handler.Subscribe(async (event) => {
27
+ * console.log('Message:', event.MessageReceived.text);
28
+ * });
29
+ *
30
+ * // Trigger events
31
+ * handler.Trigger({ id: 1, text: 'Hello World' });
32
+ *
33
+ * // Unsubscribe
34
+ * handler.Unsubscribe(subscription);
35
+ *
36
+ * // Async iteration
37
+ * for await (const event of handler.GetAsyncIterableIterator()) {
38
+ * console.log('Async event:', event.MessageReceived.text);
39
+ * break; // Important: break to avoid infinite loop
40
+ * }
41
+ * ```
42
+ */
43
+ export declare class EventHandler<TObject extends object = object, TEvent extends TEventData = TEventData> {
44
+ /**
45
+ * The name of the event type this handler manages.
46
+ * Used as the key when wrapping triggered data into event objects.
47
+ */
48
+ readonly Name: string;
49
+ /**
50
+ * Creates a new EventHandler instance.
51
+ *
52
+ * @param name - The name of the event type to handle. Cannot be empty.
53
+ * @throws {Error} 'Event Name is Empty' - When the provided name is an empty string
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const handler = new EventHandler('UserRegistered');
58
+ * console.log(handler.Name); // 'UserRegistered'
59
+ * ```
60
+ */
61
+ constructor(name: string);
62
+ /** Internal map storing active subscriptions by their unique IDs */
63
+ protected _subscriptions: Map<number, Subscription>;
64
+ /**
65
+ * Set of available subscription IDs for efficient reuse.
66
+ * IDs are reused in insertion order (i.e. the order they were freed via Unsubscribe).
67
+ * This is deterministic but not necessarily ascending; callers must not depend on a
68
+ * specific reuse order.
69
+ */
70
+ private readonly _AvailableIds;
71
+ /** Next sequential ID to assign when no IDs are available for reuse */
72
+ private _NextId;
73
+ /** Internal RxJS Subject for event broadcasting */
74
+ private readonly _Subject;
75
+ /**
76
+ * Returns an async iterable iterator for receiving events.
77
+ * Allows using for-await-of loops to process events as they arrive.
78
+ *
79
+ * @returns AsyncIterableIterator that yields events as they are triggered
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const handler = new EventHandler<MessageData, MessageEvent>('Message');
84
+ *
85
+ * // Process events in a background task
86
+ * const processEvents = async () => {
87
+ * for await (const event of handler.GetAsyncIterableIterator()) {
88
+ * console.log('Received:', event.Message.text);
89
+ * // Important: include break condition to avoid infinite loop
90
+ * if (shouldStop) break;
91
+ * }
92
+ * };
93
+ *
94
+ * processEvents();
95
+ * handler.Trigger({ text: 'Hello' }); // Will be processed by the loop
96
+ * ```
97
+ */
98
+ GetAsyncIterableIterator(): AsyncIterableIterator<TEvent>;
99
+ /**
100
+ * Returns an async iterator for receiving events.
101
+ * Provides lower-level access to the event stream compared to GetAsyncIterableIterator.
102
+ *
103
+ * @returns AsyncIterator that can be manually advanced with next() calls
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const handler = new EventHandler<MessageData, MessageEvent>('Message');
108
+ * const iterator = handler.GetAsyncIterator();
109
+ *
110
+ * // Manually advance the iterator
111
+ * const result1 = await iterator.next();
112
+ * console.log('First event:', result1.value?.Message.text);
113
+ *
114
+ * const result2 = await iterator.next();
115
+ * console.log('Second event:', result2.value?.Message.text);
116
+ * ```
117
+ */
118
+ GetAsyncIterator(): AsyncIterator<TEvent>;
119
+ /**
120
+ * Makes EventHandler directly usable in for-await-of loops.
121
+ * Delegates to GetAsyncIterableIterator().
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * for await (const event of handler) {
126
+ * console.log(event);
127
+ * if (shouldStop) break;
128
+ * }
129
+ * ```
130
+ */
131
+ [Symbol.asyncIterator](): AsyncIterableIterator<TEvent>;
132
+ /**
133
+ * Triggers an event with the provided data.
134
+ * Wraps the data in an event object using the handler's name as the key.
135
+ *
136
+ * @param data - The data to wrap and broadcast as an event
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * interface UserData {
141
+ * id: string;
142
+ * name: string;
143
+ * }
144
+ *
145
+ * const handler = new EventHandler<UserData, any>('UserCreated');
146
+ * handler.Trigger({ id: '123', name: 'John' });
147
+ * // Subscribers will receive: { UserCreated: { id: '123', name: 'John' } }
148
+ * ```
149
+ */
150
+ Trigger(data: TObject): void;
151
+ /**
152
+ * Subscribes to events with the provided handler function.
153
+ * Uses optimized O(1) ID allocation for efficient subscription management.
154
+ *
155
+ * @param onEvent - Function to call when events are triggered
156
+ * @returns number representing the unique subscription ID
157
+ *
158
+ * @remarks
159
+ * **Error handling:** if the internal RxJS Subject ever errors (which only happens if
160
+ * external code accesses `_Subject` directly), the error is logged via `console.error`
161
+ * and the subscription silently stops receiving events. If you need in-band error
162
+ * delivery use `GetAsyncIterableIterator()` instead, which propagates Subject errors as
163
+ * iterator rejections.
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * interface MessageEvent extends TEventData {
168
+ * MessageReceived: { text: string };
169
+ * }
170
+ *
171
+ * const handler = new EventHandler<any, MessageEvent>('MessageReceived');
172
+ *
173
+ * // Synchronous subscription
174
+ * const subId = handler.Subscribe((event) => {
175
+ * console.log('Message:', event.MessageReceived.text);
176
+ * });
177
+ *
178
+ * // Asynchronous subscription
179
+ * const asyncSubId = handler.Subscribe(async (event) => {
180
+ * await processMessage(event.MessageReceived);
181
+ * });
182
+ *
183
+ * // Later unsubscribe
184
+ * handler.Unsubscribe(subId);
185
+ * handler.Unsubscribe(asyncSubId);
186
+ * ```
187
+ */
188
+ Subscribe(onEvent: TEventFunction<TEvent>): number;
189
+ /**
190
+ * Unsubscribes from events using the subscription ID.
191
+ * Safely handles non-existent subscription IDs without throwing errors.
192
+ * Freed IDs are made available for reuse to optimize memory usage.
193
+ *
194
+ * @param subscription - The unique subscription ID returned from Subscribe()
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * const handler = new EventHandler('TestEvent');
199
+ *
200
+ * const subId = handler.Subscribe((event) => {
201
+ * console.log('Event received');
202
+ * });
203
+ *
204
+ * // Later, unsubscribe
205
+ * handler.Unsubscribe(subId);
206
+ *
207
+ * // Safe to call with non-existent IDs
208
+ * handler.Unsubscribe(999); // No error thrown
209
+ * ```
210
+ */
211
+ Unsubscribe(subscription: number): void;
212
+ /**
213
+ * Destroys the event handler and cleans up all resources.
214
+ * Completes the internal subject and unsubscribes all active subscriptions.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const handler = new EventHandler('TestEvent');
219
+ * handler.Subscribe((event) => { });
220
+ * handler.Destroy(); // Cleanup
221
+ * ```
222
+ */
223
+ Destroy(): void;
224
+ }
225
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,YAAY,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU;IAChG;;;OAGG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B;;;;;;;;;;;OAWG;gBACS,IAAI,EAAE,MAAM;IAKxB,oEAAoE;IACpE,SAAS,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAmC;IAEtF;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,uEAAuE;IACvE,OAAO,CAAC,OAAO,CAAa;IAE5B,mDAAmD;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IAEnE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACY,wBAAwB,IAAI,qBAAqB,CAAC,MAAM,CAAC;IAsExE;;;;;;;;;;;;;;;;;;OAkBG;IACI,gBAAgB,IAAI,aAAa,CAAC,MAAM,CAAC;IAIhD;;;;;;;;;;;OAWG;IACI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,MAAM,CAAC;IAI9D;;;;;;;;;;;;;;;;;OAiBG;IACI,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAKnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACI,SAAS,CAAC,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,MAAM;IA6BzD;;;;;;;;;;;;;;;;;;;;;OAqBG;IACI,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAW9C;;;;;;;;;;OAUG;IACI,OAAO,IAAI,IAAI;CAYtB"}