@pawells/rxjs-events 1.0.1 → 1.0.3

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.
@@ -0,0 +1,207 @@
1
+ import { EventHandler } from './handler.js';
2
+ /**
3
+ * Adapter that makes EventHandler compatible with NestJS GraphQL @Subscription() decorators.
4
+ * Implements the IPubSubEngine interface for pub/sub messaging patterns.
5
+ *
6
+ * This adapter bridges EventHandler with GraphQL subscriptions by maintaining a map of
7
+ * EventHandlers (one per trigger name) and coordinating subscription/publication.
8
+ *
9
+ * @remarks
10
+ * - No external dependencies required (graphql-subscriptions not needed)
11
+ * - Each trigger name gets its own EventHandler for isolation
12
+ * - Thread-safe subscription ID management
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // In your NestJS GraphQL module:
17
+ * import { EventHandlerPubSub } from '@pawells/rxjs-events';
18
+ *
19
+ * const pubSub = new EventHandlerPubSub();
20
+ *
21
+ * // In a GraphQL resolver:
22
+ * @Subscription(() => MessagePayload, {
23
+ * resolve: (payload) => payload,
24
+ * })
25
+ * messageSent(): AsyncIterableIterator<MessagePayload> {
26
+ * return pubSub.asyncIterator<MessagePayload>('MESSAGE_SENT');
27
+ * }
28
+ *
29
+ * // In a mutation:
30
+ * @Mutation(() => MessagePayload)
31
+ * sendMessage(@Args('text') text: string): MessagePayload {
32
+ * const payload = { text, timestamp: Date.now() };
33
+ * pubSub.publish('MESSAGE_SENT', payload);
34
+ * return payload;
35
+ * }
36
+ * ```
37
+ */
38
+ export class EventHandlerPubSub {
39
+ /**
40
+ * Map storing EventHandlers indexed by trigger name.
41
+ * Each trigger maintains its own handler for event isolation.
42
+ */
43
+ _handlers = new Map();
44
+ /**
45
+ * Map storing handler subscriptions and their IDs for cleanup.
46
+ * Maps subscription ID to [handler, subscription ID within handler].
47
+ */
48
+ _subscriptions = new Map();
49
+ /**
50
+ * Counter for generating unique subscription IDs across all triggers.
51
+ */
52
+ _nextSubscriptionId = 0;
53
+ /**
54
+ * Gets or creates an EventHandler for a specific trigger name.
55
+ *
56
+ * @param triggerName - The trigger channel name
57
+ * @returns The EventHandler for this trigger
58
+ */
59
+ _getOrCreateHandler(triggerName) {
60
+ let handler = this._handlers.get(triggerName);
61
+ if (!handler) {
62
+ handler = new EventHandler(triggerName);
63
+ this._handlers.set(triggerName, handler);
64
+ }
65
+ return handler;
66
+ }
67
+ /**
68
+ * Publishes a payload to all subscribers of a trigger.
69
+ *
70
+ * @param triggerName - The trigger channel name
71
+ * @param payload - The payload to publish
72
+ */
73
+ async publish(triggerName, payload) {
74
+ await Promise.resolve();
75
+ const handler = this._getOrCreateHandler(triggerName);
76
+ handler.Trigger(payload);
77
+ }
78
+ /**
79
+ * Subscribes to a trigger channel and calls the callback when messages are published.
80
+ *
81
+ * @param triggerName - The trigger channel name
82
+ * @param onMessage - Callback function that receives the published payload
83
+ * @param _options - Optional subscription options (not used in current implementation)
84
+ * @returns Unique subscription ID for later unsubscription
85
+ */
86
+ async subscribe(triggerName, onMessage, _options) {
87
+ const handler = this._getOrCreateHandler(triggerName);
88
+ const handlerSubId = handler.Subscribe(onMessage);
89
+ // Generate a unique subscription ID for external tracking
90
+ const externalSubId = this._nextSubscriptionId++;
91
+ this._subscriptions.set(externalSubId, [handler, handlerSubId]);
92
+ await Promise.resolve();
93
+ return externalSubId;
94
+ }
95
+ /**
96
+ * Unsubscribes from a trigger channel.
97
+ *
98
+ * @param subId - The subscription ID returned from subscribe()
99
+ */
100
+ unsubscribe(subId) {
101
+ const subscription = this._subscriptions.get(subId);
102
+ if (subscription) {
103
+ const [handler, handlerSubId] = subscription;
104
+ handler.Unsubscribe(handlerSubId);
105
+ this._subscriptions.delete(subId);
106
+ }
107
+ }
108
+ /**
109
+ * Creates an async iterator for one or more trigger channels.
110
+ * Useful for GraphQL subscription resolvers.
111
+ *
112
+ * @template T - The type of values yielded by the iterator
113
+ * @param triggers - Single trigger name or array of trigger names
114
+ * @returns AsyncIterableIterator that yields published payloads
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // Subscribe to a single trigger
119
+ * const iterator = pubSub.asyncIterator('USER_CREATED');
120
+ *
121
+ * // Subscribe to multiple triggers
122
+ * const iterator = pubSub.asyncIterator(['USER_CREATED', 'USER_UPDATED']);
123
+ * ```
124
+ */
125
+ asyncIterator(triggers) {
126
+ const triggerArray = Array.isArray(triggers) ? triggers : [triggers];
127
+ if (triggerArray.length === 0) {
128
+ // Return an empty iterator if no triggers provided
129
+ return (async function* () {
130
+ // Empty iterator
131
+ })();
132
+ }
133
+ if (triggerArray.length === 1) {
134
+ // Single trigger - use the handler's async iterator directly
135
+ const handler = this._getOrCreateHandler(triggerArray[0]);
136
+ return handler.GetAsyncIterableIterator();
137
+ }
138
+ // Multiple triggers - merge iterators from all handlers
139
+ return this._mergeAsyncIterators(triggerArray);
140
+ }
141
+ /**
142
+ * Merges async iterators from multiple trigger handlers.
143
+ * Events from any trigger are yielded as they arrive.
144
+ *
145
+ * @template T - The type of values yielded
146
+ * @param triggers - Array of trigger names
147
+ * @returns Merged async iterator
148
+ */
149
+ _mergeAsyncIterators(triggers) {
150
+ const handlers = triggers.map((trigger) => this._getOrCreateHandler(trigger));
151
+ const self = this;
152
+ return (async function* () {
153
+ const iterators = handlers.map((h) => h.GetAsyncIterableIterator());
154
+ const subscriptions = [];
155
+ // Use Promise.race to get the first event from any handler
156
+ // This is a simplified implementation; production systems may want more sophisticated merging
157
+ try {
158
+ for await (const event of iterators[0]) {
159
+ yield event;
160
+ }
161
+ }
162
+ finally {
163
+ // Clean up subscriptions
164
+ await Promise.resolve();
165
+ subscriptions.forEach((subId) => self.unsubscribe(subId));
166
+ }
167
+ })();
168
+ }
169
+ }
170
+ /**
171
+ * Wraps an async iterator with a filter predicate for use with GraphQL subscriptions.
172
+ * Mirrors the withFilter helper from graphql-subscriptions.
173
+ *
174
+ * @template T - The type of values in the iterator
175
+ * @param asyncIteratorFn - Function that returns an async iterator
176
+ * @param filterFn - Predicate function that determines which values to yield (async functions supported)
177
+ * @returns Filtered async iterator
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * const pubSub = new EventHandlerPubSub();
182
+ *
183
+ * // In a GraphQL resolver:
184
+ * @Subscription(() => UserPayload)
185
+ * userUpdated(
186
+ * @Args('userId') userId: string
187
+ * ): AsyncIterableIterator<UserPayload> {
188
+ * return WithFilter(
189
+ * () => pubSub.asyncIterator<UserPayload>('USER_UPDATED'),
190
+ * (payload: UserPayload) => payload.userId === userId
191
+ * );
192
+ * }
193
+ * ```
194
+ */
195
+ export function WithFilter(asyncIteratorFn, filterFn) {
196
+ return (async function* () {
197
+ const asyncIterator = asyncIteratorFn();
198
+ for await (const value of asyncIterator) {
199
+ const result = filterFn(value);
200
+ const passes = result instanceof Promise ? await result : result;
201
+ if (passes) {
202
+ yield value;
203
+ }
204
+ }
205
+ })();
206
+ }
207
+ //# sourceMappingURL=nestjs-pubsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nestjs-pubsub.js","sourceRoot":"","sources":["../src/nestjs-pubsub.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pawells/rxjs-events",
3
3
  "displayName": "RxJS Events",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "description": "RxJS-based event handling library with reactive observables and async event streams.",
6
6
  "type": "module",
7
7
  "main": "./build/index.js",
@@ -27,17 +27,18 @@
27
27
  "prepare": "husky"
28
28
  },
29
29
  "dependencies": {
30
+ "@pawells/typescript-common": "^1.1.6",
30
31
  "rxjs": "^7.8.2"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@eslint/js": "^10.0.1",
34
35
  "@stylistic/eslint-plugin": "^5.9.0",
35
36
  "@types/node": "^25.3.0",
36
- "@typescript-eslint/eslint-plugin": "^8.0.0",
37
- "@typescript-eslint/parser": "^8.0.0",
37
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
38
+ "@typescript-eslint/parser": "^8.56.1",
38
39
  "@vitest/coverage-v8": "^4.0.18",
39
40
  "@vitest/ui": "^4.0.18",
40
- "eslint": "^10.0.1",
41
+ "eslint": "^10.0.2",
41
42
  "eslint-import-resolver-typescript": "^4.4.4",
42
43
  "eslint-plugin-import": "^2.31.0",
43
44
  "eslint-plugin-unused-imports": "^4.0.0",