@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phillip Aaron Wells
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # RxJS Events
2
+
3
+ [![npm](https://img.shields.io/npm/v/@pawells/rxjs-events)](https://www.npmjs.com/package/@pawells/rxjs-events)
4
+ [![GitHub Release](https://img.shields.io/github/v/release/PhillipAWells/rxjs-events)](https://github.com/PhillipAWells/rxjs-events/releases)
5
+ [![CI](https://github.com/PhillipAWells/rxjs-events/actions/workflows/ci.yml/badge.svg)](https://github.com/PhillipAWells/rxjs-events/actions/workflows/ci.yml)
6
+ [![Node](https://img.shields.io/badge/node-%3E%3D24-brightgreen)](https://nodejs.org)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/PhillipAWells?style=social)](https://github.com/sponsors/PhillipAWells)
9
+
10
+ RxJS-based event handling library with reactive observables and async event streams. Supports subscription management, backpressure-aware async iteration, and TC39 Explicit Resource Management (`await using`).
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @pawells/rxjs-events
16
+ # or
17
+ yarn add @pawells/rxjs-events
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Defining an event shape
23
+
24
+ Events must have **exactly one top-level key** whose name is the event type and whose value is the payload:
25
+
26
+ ```typescript
27
+ import type { TEventData } from '@pawells/rxjs-events';
28
+
29
+ interface UserCreatedEvent extends TEventData {
30
+ UserCreated: { userId: string; username: string };
31
+ }
32
+ ```
33
+
34
+ ### EventHandler — subscribe, trigger, and unsubscribe
35
+
36
+ ```typescript
37
+ import { EventHandler } from '@pawells/rxjs-events';
38
+
39
+ const handler = new EventHandler<{ UserCreated: { userId: string; username: string } }, 'UserCreated'>('UserCreated');
40
+
41
+ // Subscribe — returns a numeric ID
42
+ const id = handler.Subscribe((payload) => {
43
+ console.log('New user:', payload.userId);
44
+ });
45
+
46
+ // Trigger the event
47
+ handler.Trigger({ userId: '42', username: 'alice' });
48
+
49
+ // Unsubscribe by ID
50
+ handler.Unsubscribe(id);
51
+
52
+ // Destroy — completes the underlying Subject and removes all subscriptions
53
+ handler.Destroy();
54
+ ```
55
+
56
+ ### Async iteration with `GetAsyncIterableIterator`
57
+
58
+ ```typescript
59
+ const handler = new EventHandler<{ MessageReceived: { text: string } }, 'MessageReceived'>('MessageReceived');
60
+
61
+ async function processMessages() {
62
+ for await (const payload of handler.GetAsyncIterableIterator()) {
63
+ console.log('Message:', payload.text);
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Explicit Resource Management (`await using`)
69
+
70
+ `EventHandler` exposes `GetAsyncIterator()`, which returns an `IAsyncGeneratorESN` implementing `Symbol.asyncDispose`:
71
+
72
+ ```typescript
73
+ async function processOnce() {
74
+ await using iter = handler.GetAsyncIterator();
75
+ const { value, done } = await iter.next();
76
+ if (!done) console.log(value);
77
+ // iter is automatically disposed at block exit
78
+ }
79
+ ```
80
+
81
+ ### AsyncObservable with backpressure
82
+
83
+ `AsyncObservable` is an `Observable` subclass with a push buffer and configurable overflow handling:
84
+
85
+ ```typescript
86
+ import { AsyncObservable, BackpressureStrategy } from '@pawells/rxjs-events';
87
+
88
+ const obs = new AsyncObservable<string>({
89
+ bufferSize: 100,
90
+ strategy: BackpressureStrategy.DropOldest,
91
+ });
92
+
93
+ for await (const item of obs) {
94
+ console.log(item);
95
+ }
96
+ ```
97
+
98
+ ### Filtering events
99
+
100
+ ```typescript
101
+ import { EventFilter } from '@pawells/rxjs-events';
102
+ import type { IFilterCriteria } from '@pawells/rxjs-events';
103
+
104
+ const criteria: IFilterCriteria = { userId: '42' };
105
+ const event = { UserCreated: { userId: '42', username: 'alice' } };
106
+
107
+ if (EventFilter(event, criteria)) {
108
+ // event matches all criteria
109
+ }
110
+ ```
111
+
112
+ ## API
113
+
114
+ ### `EventHandler<TObject, TEvent>`
115
+
116
+ Main event handler class wrapping an RxJS `Subject`.
117
+
118
+ | Method | Signature | Description |
119
+ |--------|-----------|-------------|
120
+ | `constructor` | `(name: TEvent)` | Creates a handler with the given event name |
121
+ | `Name` | `string` (getter) | Returns the event name |
122
+ | `Subscribe` | `(handler: TEventFunction<TObject[TEvent]>, options?: ISubscriptionOptions) => number` | Subscribes and returns a numeric ID |
123
+ | `Unsubscribe` | `(id: number) => void` | Removes subscription by ID |
124
+ | `Trigger` | `(data: TObject[TEvent]) => void` | Emits the event with the given payload |
125
+ | `Destroy` | `() => void` | Completes the Subject and cleans up all subscriptions |
126
+ | `GetAsyncIterableIterator` | `() => AsyncIterableIterator<TObject[TEvent]>` | Returns an async iterable iterator |
127
+ | `GetAsyncIterator` | `() => IAsyncGeneratorESN<TObject[TEvent]>` | Returns a disposable async generator |
128
+
129
+ Subscription IDs are recycled internally — allocation is O(1).
130
+
131
+ ### `AsyncObservable<T>`
132
+
133
+ An `Observable` subclass with a push buffer and configurable backpressure. Implements `Symbol.asyncIterator`.
134
+
135
+ | Option | Type | Description |
136
+ |--------|------|-------------|
137
+ | `bufferSize` | `number` | Maximum number of buffered items |
138
+ | `strategy` | `BackpressureStrategy` | `DropOldest`, `DropNewest`, or `Error` |
139
+
140
+ Throws `BufferOverflowError` when strategy is `Error` and the buffer is full.
141
+
142
+ ### `EventFilter(event, criteria)`
143
+
144
+ Filters a single-key event object against an `IFilterCriteria` map using strict equality. Returns `true` if all criteria match.
145
+
146
+ ### Types
147
+
148
+ | Export | Description |
149
+ |--------|-------------|
150
+ | `TEventData` | Base type alias (`Record<string, unknown>`) — all event shapes extend this |
151
+ | `TEventFunction<TEvent>` | Handler callback: `(data: TEvent) => Promise<void> \| void` |
152
+ | `TEventHandler` | Union type for handler references |
153
+ | `TEventFilter` | Filter function type |
154
+ | `TAsyncObserver` | Async observer callback type |
155
+ | `TUnsubscribe` | Unsubscribe function type |
156
+ | `IFilterCriteria` | Index-signature interface `{ [key: string]: unknown }` |
157
+ | `ISubscriptionOptions` | Options passed to `Subscribe` |
158
+ | `IBackpressureConfig` | Configuration object for `AsyncObservable` |
159
+ | `IAsyncGeneratorESN<T, TReturn, TNext>` | `AsyncGenerator` extended with `Symbol.asyncDispose` |
160
+ | `TExtractEventPayload<TEvent>` | Utility type — extracts payload type from a `TEventData` object |
161
+ | `BackpressureStrategy` | Enum: `DropOldest`, `DropNewest`, `Error` |
162
+ | `BufferOverflowError` | Error thrown on buffer overflow when strategy is `Error` |
163
+
164
+ ### Mocks (test helpers)
165
+
166
+ Import from `@pawells/rxjs-events/src/mocks/index.js` in tests (not re-exported from the main entry point):
167
+
168
+ | Export | Description |
169
+ |--------|-------------|
170
+ | `MockEventHandler` | Controllable mock with spy capabilities for asserting triggers and subscriptions |
171
+ | `MockAsyncObservable` | Controllable mock exposing push/complete/error helpers |
172
+ | `SetupMatchers` | Registers all custom Vitest matchers — call from `vitest.setup.ts` |
173
+ | `ToHaveSubscribers` | Custom matcher: asserts a handler has N active subscribers |
174
+ | `ToHaveTriggeredEvent` | Custom matcher: asserts a mock handler triggered a specific event |
175
+ | `ToMatchEventFilter` | Custom matcher: asserts an event matches a filter criteria map |
176
+ | `GenerateUserEvents` | Factory for typed user-event objects |
177
+ | `GenerateMessageEvents` | Factory for message-event objects |
178
+ | `GenerateEventData` | Generic event-data factory |
179
+ | `GenerateFilterCriteria` | Produces `IFilterCriteria` maps for filter tests |
180
+ | `GenerateSubscriptionScenarios` | Generates multi-subscription test scenarios |
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ yarn build # Compile TypeScript → ./build/
186
+ yarn dev # Build and run
187
+ yarn watch # TypeScript watch mode
188
+ yarn typecheck # Type check without emitting
189
+ yarn lint # ESLint src/
190
+ yarn lint:fix # ESLint with auto-fix
191
+ yarn test # Run Vitest tests
192
+ yarn test:ui # Open interactive Vitest UI in a browser
193
+ yarn test:coverage # Run tests with coverage report
194
+ yarn start # Run built output
195
+ ```
196
+
197
+ To run a single test file:
198
+
199
+ ```bash
200
+ yarn vitest run src/path/to/file.test.ts
201
+ ```
202
+
203
+ ## Requirements
204
+
205
+ - Node.js >= 24.0.0
206
+ - ESM-only (`"type": "module"`) — use ESM imports throughout
207
+
208
+ ## License
209
+
210
+ MIT — See [LICENSE](./LICENSE) for details.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Extended AsyncGenerator interface with async disposal support.
3
+ *
4
+ * ESN = Explicit Symbol Notation — extends AsyncGenerator with `Symbol.asyncDispose` support
5
+ * for the TC39 Explicit Resource Management proposal (`using` keyword). `Symbol.asyncDispose`
6
+ * is called when the generator is used in a `await using` block.
7
+ *
8
+ * @template T The type of the values yielded by the generator.
9
+ * @template TReturn The type of the value returned by the generator.
10
+ * @template TNext The type of the value that can be passed to the generator's next() method.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * async function* myGenerator(): IAsyncGeneratorESN<string, void, void> {
15
+ * yield 'value1';
16
+ * yield 'value2';
17
+ * }
18
+ *
19
+ * // With await using (requires TypeScript 5.2+)
20
+ * await using gen = myGenerator() as unknown as AsyncDisposable;
21
+ * // gen is automatically disposed when it goes out of scope
22
+ * ```
23
+ */
24
+ export interface IAsyncGeneratorESN<T = unknown, TReturn = unknown, TNext = unknown> extends AsyncGenerator<T, TReturn, TNext> {
25
+ [Symbol.asyncDispose](): PromiseLike<void>;
26
+ }
27
+ //# sourceMappingURL=async-generator-esn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-generator-esn.d.ts","sourceRoot":"","sources":["../src/async-generator-esn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,CAAE,SAAQ,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC;IAC7H,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAA;CAC1C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=async-generator-esn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-generator-esn.js","sourceRoot":"","sources":["../src/async-generator-esn.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ import { Observable } from 'rxjs';
2
+ /**
3
+ * Error thrown when the observable buffer overflows.
4
+ */
5
+ export declare class BufferOverflowError extends Error {
6
+ constructor(maxSize: number);
7
+ }
8
+ /**
9
+ * Backpressure overflow strategy
10
+ */
11
+ export declare enum BackpressureStrategy {
12
+ /** Drop oldest events when buffer is full */
13
+ DropOldest = "DropOldest",
14
+ /** Drop newest events when buffer is full */
15
+ DropNewest = "DropNewest",
16
+ /** Throw error when buffer is full */
17
+ Error = "Error"
18
+ }
19
+ /**
20
+ * Configuration for AsyncObservable backpressure
21
+ */
22
+ export interface IBackpressureConfig {
23
+ /** Maximum buffer size (default: 1000) */
24
+ maxBufferSize?: number;
25
+ /** Strategy when buffer overflows (default: DropOldest) */
26
+ overflowStrategy?: BackpressureStrategy;
27
+ }
28
+ /**
29
+ * An Observable that can be used as an async iterator with backpressure support.
30
+ * @template T The type of the values emitted by the observable.
31
+ */
32
+ export declare class AsyncObservable<T> extends Observable<T> {
33
+ private readonly _Subject;
34
+ private readonly _Buffer;
35
+ private readonly _MaxBufferSize;
36
+ private readonly _OverflowStrategy;
37
+ /**
38
+ * @remarks
39
+ * **Buffer replay on subscription:** every new subscriber receives all buffered items
40
+ * (those added via `Push()` before the subscriber connected), followed by live items.
41
+ * This is intentional "replay" behaviour — late subscribers see historical data.
42
+ * If you only want live items, subscribe before pushing any data.
43
+ */
44
+ constructor(config?: IBackpressureConfig);
45
+ /**
46
+ * Adds an event to the buffer with backpressure handling
47
+ */
48
+ Push(value: T): void;
49
+ private _HandleOverflow;
50
+ /**
51
+ * Destroys the observable and completes the internal subject, cleaning up all resources.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const observable = new AsyncObservable<number>();
56
+ * observable.Push(1);
57
+ * observable.Destroy(); // Completes the subject; subscribers stop receiving events
58
+ * ```
59
+ */
60
+ Destroy(): void;
61
+ /**
62
+ * Returns an async generator that can be used to iterate over the values emitted by the observable.
63
+ *
64
+ * Each call creates an independent iterator with its own local queue, so multiple concurrent
65
+ * iterators on the same AsyncObservable are safe — they do not interfere with each other or
66
+ * with the shared replay buffer.
67
+ *
68
+ * @returns An async generator.
69
+ */
70
+ [Symbol.asyncIterator](): AsyncGenerator<T, void, void>;
71
+ }
72
+ //# sourceMappingURL=async-observable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-observable.d.ts","sourceRoot":"","sources":["../src/async-observable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAyB,MAAM,MAAM,CAAC;AAGzD;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI3B;AAED;;GAEG;AACH,oBAAY,oBAAoB;IAC/B,6CAA6C;IAC7C,UAAU,eAAe;IACzB,6CAA6C;IAC7C,UAAU,eAAe;IACzB,sCAAsC;IACtC,KAAK,UAAU;CACf;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;CACxC;AAKD;;;GAGG;AACH,qBAAa,eAAe,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAE7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IAEnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAuB;IAEzD;;;;;;OAMG;gBACS,MAAM,CAAC,EAAE,mBAAmB;IAqBxC;;OAEG;IACI,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAS3B,OAAO,CAAC,eAAe;IAevB;;;;;;;;;OASG;IACI,OAAO,IAAI,IAAI;IAKtB;;;;;;;;OAQG;IACI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC;CAwG9D"}
@@ -0,0 +1,201 @@
1
+ import { Observable, Subject } from 'rxjs';
2
+ /**
3
+ * Error thrown when the observable buffer overflows.
4
+ */
5
+ export class BufferOverflowError extends Error {
6
+ constructor(maxSize) {
7
+ super(`Buffer overflow: maximum buffer size of ${maxSize} exceeded`);
8
+ this.name = 'BufferOverflowError';
9
+ }
10
+ }
11
+ /**
12
+ * Backpressure overflow strategy
13
+ */
14
+ export var BackpressureStrategy;
15
+ (function (BackpressureStrategy) {
16
+ /** Drop oldest events when buffer is full */
17
+ BackpressureStrategy["DropOldest"] = "DropOldest";
18
+ /** Drop newest events when buffer is full */
19
+ BackpressureStrategy["DropNewest"] = "DropNewest";
20
+ /** Throw error when buffer is full */
21
+ BackpressureStrategy["Error"] = "Error";
22
+ })(BackpressureStrategy || (BackpressureStrategy = {}));
23
+ /** Default maximum buffer size for AsyncObservable instances */
24
+ const DEFAULT_MAX_BUFFER_SIZE = 1000;
25
+ /**
26
+ * An Observable that can be used as an async iterator with backpressure support.
27
+ * @template T The type of the values emitted by the observable.
28
+ */
29
+ export class AsyncObservable extends Observable {
30
+ _Subject = new Subject();
31
+ _Buffer = [];
32
+ _MaxBufferSize;
33
+ _OverflowStrategy;
34
+ /**
35
+ * @remarks
36
+ * **Buffer replay on subscription:** every new subscriber receives all buffered items
37
+ * (those added via `Push()` before the subscriber connected), followed by live items.
38
+ * This is intentional "replay" behaviour — late subscribers see historical data.
39
+ * If you only want live items, subscribe before pushing any data.
40
+ */
41
+ constructor(config) {
42
+ super(observer => {
43
+ // Connect the observer to the live subject first so it will receive future pushes,
44
+ // then replay already-buffered items in order. Because JavaScript is single-threaded,
45
+ // no new Push() can interleave between these two steps, so replay order is guaranteed.
46
+ const innerSub = this._Subject.subscribe(observer);
47
+ for (const item of this._Buffer) {
48
+ observer.next(item);
49
+ }
50
+ return () => innerSub.unsubscribe();
51
+ });
52
+ this._MaxBufferSize = config?.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
53
+ this._OverflowStrategy = config?.overflowStrategy ?? BackpressureStrategy.DropOldest;
54
+ // Validate buffer size
55
+ if (this._MaxBufferSize <= 0) {
56
+ throw new RangeError('maxBufferSize must be a positive integer');
57
+ }
58
+ }
59
+ /**
60
+ * Adds an event to the buffer with backpressure handling
61
+ */
62
+ Push(value) {
63
+ if (this._Buffer.length >= this._MaxBufferSize) {
64
+ this._HandleOverflow(value);
65
+ }
66
+ else {
67
+ this._Buffer.push(value);
68
+ this._Subject.next(value);
69
+ }
70
+ }
71
+ _HandleOverflow(value) {
72
+ switch (this._OverflowStrategy) {
73
+ case BackpressureStrategy.DropOldest:
74
+ this._Buffer.shift();
75
+ this._Buffer.push(value);
76
+ this._Subject.next(value);
77
+ break;
78
+ case BackpressureStrategy.DropNewest:
79
+ // Simply don't add the new value
80
+ break;
81
+ case BackpressureStrategy.Error:
82
+ throw new BufferOverflowError(this._MaxBufferSize);
83
+ }
84
+ }
85
+ /**
86
+ * Destroys the observable and completes the internal subject, cleaning up all resources.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const observable = new AsyncObservable<number>();
91
+ * observable.Push(1);
92
+ * observable.Destroy(); // Completes the subject; subscribers stop receiving events
93
+ * ```
94
+ */
95
+ Destroy() {
96
+ this._Subject.complete();
97
+ this._Buffer.length = 0;
98
+ }
99
+ /**
100
+ * Returns an async generator that can be used to iterate over the values emitted by the observable.
101
+ *
102
+ * Each call creates an independent iterator with its own local queue, so multiple concurrent
103
+ * iterators on the same AsyncObservable are safe — they do not interfere with each other or
104
+ * with the shared replay buffer.
105
+ *
106
+ * @returns An async generator.
107
+ */
108
+ [Symbol.asyncIterator]() {
109
+ let subscription;
110
+ let hasError = false;
111
+ let error;
112
+ let completed = false;
113
+ // Per-iterator queue — never mutates the shared _Buffer.
114
+ const localQueue = [];
115
+ const deferreds = [];
116
+ const handleError = (err) => {
117
+ hasError = true;
118
+ error = err;
119
+ while (deferreds.length) {
120
+ const deferred = deferreds.shift();
121
+ if (deferred) {
122
+ const [, reject] = deferred;
123
+ reject(err);
124
+ }
125
+ }
126
+ };
127
+ const handleComplete = () => {
128
+ completed = true;
129
+ while (deferreds.length) {
130
+ const deferred = deferreds.shift();
131
+ if (deferred) {
132
+ const [resolve] = deferred;
133
+ resolve({ done: true, value: undefined });
134
+ }
135
+ }
136
+ };
137
+ const generator = {
138
+ [Symbol.asyncDispose]: () => {
139
+ return new Promise((resolve) => {
140
+ subscription?.unsubscribe();
141
+ resolve();
142
+ });
143
+ },
144
+ next: () => {
145
+ // Lazily create the subscription on first next() call.
146
+ // Because JS is single-threaded, the Observable's subscriber function runs
147
+ // synchronously: it first connects to _Subject (for live items), then replays
148
+ // all buffered items through the `next` handler below. At that point `deferreds`
149
+ // is always empty, so every replayed item lands in `localQueue`. This means the
150
+ // shared _Buffer is never mutated here and multiple concurrent iterators are safe.
151
+ subscription ??= this.subscribe({
152
+ complete: handleComplete,
153
+ error: handleError,
154
+ next: (value) => {
155
+ if (deferreds.length) {
156
+ // A consumer is already waiting; deliver directly.
157
+ const deferred = deferreds.shift();
158
+ if (deferred) {
159
+ const [resolve] = deferred;
160
+ resolve({ done: false, value });
161
+ }
162
+ }
163
+ else {
164
+ localQueue.push(value);
165
+ }
166
+ },
167
+ });
168
+ if (localQueue.length) {
169
+ const value = localQueue.shift();
170
+ return Promise.resolve({ done: false, value });
171
+ }
172
+ if (completed) {
173
+ return Promise.resolve({ done: true, value: undefined });
174
+ }
175
+ if (hasError) {
176
+ return Promise.reject(error);
177
+ }
178
+ return new Promise((resolve, reject) => {
179
+ deferreds.push([resolve, reject]);
180
+ });
181
+ },
182
+ return: () => {
183
+ subscription?.unsubscribe();
184
+ // Resolve all pending next() promises so callers are not left hanging.
185
+ handleComplete();
186
+ return Promise.resolve({ done: true, value: null });
187
+ },
188
+ throw: (err) => {
189
+ subscription?.unsubscribe();
190
+ // Reject all pending next() promises so callers are not left hanging.
191
+ handleError(err);
192
+ return Promise.reject(err);
193
+ },
194
+ [Symbol.asyncIterator]() {
195
+ return this;
196
+ },
197
+ };
198
+ return generator;
199
+ }
200
+ }
201
+ //# sourceMappingURL=async-observable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-observable.js","sourceRoot":"","sources":["../src/async-observable.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Base interface for all event data structures.
3
+ * Events should have a single key representing the event type with associated payload data.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * interface UserCreatedEvent extends TEventData {
8
+ * UserCreated: {
9
+ * userId: string;
10
+ * username: string;
11
+ * email: string;
12
+ * };
13
+ * }
14
+ * ```
15
+ */
16
+ export type TEventData = Record<string, unknown>;
17
+ //# sourceMappingURL=event-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-data.d.ts","sourceRoot":"","sources":["../src/event-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=event-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-data.js","sourceRoot":"","sources":["../src/event-data.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { TEventData } from './event-data.js';
2
+ import { IFilterCriteria } from './filter-criteria.js';
3
+ /**
4
+ * Filters events based on payload property matching criteria.
5
+ * Performs strict equality comparison between event payload properties and filter arguments.
6
+ *
7
+ * @template TEvent - The event data type extending TEventData
8
+ * @param event - The event to filter, must have exactly one property representing the event type
9
+ * @param args - Filter criteria object with property-value pairs to match against the event payload
10
+ * @returns true if the event matches all filter criteria or if no filter is provided, false otherwise
11
+ *
12
+ * @throws {Error} 'No Event' - When event is null or undefined
13
+ * @throws {Error} 'More than one payload structure.' - When event has more than one top-level property
14
+ * @throws {Error} 'No Payload' - When the event's payload is null or undefined
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * interface UserEvent extends TEventData {
19
+ * UserCreated: {
20
+ * userId: string;
21
+ * username: string;
22
+ * role: string;
23
+ * };
24
+ * }
25
+ *
26
+ * const event: UserEvent = {
27
+ * UserCreated: { userId: '123', username: 'john', role: 'admin' }
28
+ * };
29
+ *
30
+ * // Match by single property
31
+ * EventFilter(event, { role: 'admin' }); // true
32
+ * EventFilter(event, { role: 'user' }); // false
33
+ *
34
+ * // Match by multiple properties
35
+ * EventFilter(event, { username: 'john', role: 'admin' }); // true
36
+ * EventFilter(event, { username: 'john', role: 'user' }); // false
37
+ *
38
+ * // No filter (always passes)
39
+ * EventFilter(event, null); // true
40
+ * EventFilter(event, undefined); // true
41
+ * ```
42
+ */
43
+ export declare function EventFilter<TEvent extends TEventData = TEventData>(event: TEvent, args: IFilterCriteria | null | undefined): boolean;
44
+ //# sourceMappingURL=event-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-filter.d.ts","sourceRoot":"","sources":["../src/event-filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,WAAW,CAAC,MAAM,SAAS,UAAU,GAAG,UAAU,EACjE,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CA0BT"}