@rxdi/graphql-rabbitmq-subscriptions 0.7.192 → 0.7.194

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.
@@ -14,6 +14,7 @@ export declare class AmqpPubSub {
14
14
  private currentSubscriptionId;
15
15
  private triggerTransform;
16
16
  private unsubscribeChannelMap;
17
+ private pendingSubscriptions;
17
18
  private logger;
18
19
  constructor(options?: PubSubRabbitMQBusOptions);
19
20
  publish(trigger: string, payload: any, config?: IQueueNameConfig): Promise<void>;
@@ -15,17 +15,18 @@ const rabbitmq_pubsub_1 = require("@rxdi/rabbitmq-pubsub");
15
15
  const child_logger_1 = require("./child-logger");
16
16
  class AmqpPubSub {
17
17
  constructor(options = {}) {
18
- this.subscriptionMap = {};
19
- this.subsRefsMap = {};
18
+ this.subscriptionMap = new Map();
19
+ this.subsRefsMap = new Map();
20
20
  this.currentSubscriptionId = 0;
21
- this.unsubscribeChannelMap = {};
21
+ this.unsubscribeChannelMap = new Map();
22
+ // Track in-progress subscriptions to prevent race conditions
23
+ this.pendingSubscriptions = new Map();
22
24
  this.triggerTransform = options.triggerTransform || ((trigger) => trigger);
23
25
  const config = options.config || { host: '127.0.0.1', port: 5672 };
24
- const { logger } = options;
25
- this.logger = (0, child_logger_1.createChildLogger)(logger, 'AmqpPubSub');
26
- const factory = new rabbitmq_pubsub_1.RabbitMqSingletonConnectionFactory(logger, config);
27
- this.consumer = new rabbitmq_pubsub_1.RabbitMqSubscriber(logger, factory);
28
- this.producer = new rabbitmq_pubsub_1.RabbitMqPublisher(logger, factory);
26
+ this.logger = (0, child_logger_1.createChildLogger)(options.logger, 'AmqpPubSub');
27
+ const factory = new rabbitmq_pubsub_1.RabbitMqSingletonConnectionFactory(options.logger, config);
28
+ this.consumer = new rabbitmq_pubsub_1.RabbitMqSubscriber(options.logger, factory);
29
+ this.producer = new rabbitmq_pubsub_1.RabbitMqPublisher(options.logger, factory);
29
30
  }
30
31
  publish(trigger, payload, config) {
31
32
  return __awaiter(this, void 0, void 0, function* () {
@@ -37,71 +38,93 @@ class AmqpPubSub {
37
38
  return __awaiter(this, void 0, void 0, function* () {
38
39
  const triggerName = this.triggerTransform(trigger, options);
39
40
  const id = this.currentSubscriptionId++;
40
- this.subscriptionMap[id] = [triggerName, onMessage];
41
- let refs = this.subsRefsMap[triggerName];
42
- if (refs && refs.length > 0) {
43
- const newRefs = [...refs, id];
44
- this.subsRefsMap[triggerName] = newRefs;
45
- this.logger.trace("subscriber exist, adding triggerName '%s' to saved list.", triggerName);
41
+ this.subscriptionMap.set(id, [triggerName, onMessage]);
42
+ const refs = this.subsRefsMap.get(triggerName);
43
+ if (refs === null || refs === void 0 ? void 0 : refs.length) {
44
+ this.subsRefsMap.set(triggerName, [...refs, id]);
45
+ this.logger.trace("subscriber exists, adding triggerName '%s' to saved list.", triggerName);
46
46
  return id;
47
47
  }
48
+ // Subscription already in progress
49
+ const pending = this.pendingSubscriptions.get(triggerName);
50
+ if (pending) {
51
+ this.logger.trace("subscription in progress for '%s', waiting for it to complete", triggerName);
52
+ try {
53
+ const disposer = yield pending;
54
+ const nextRefs = [...(this.subsRefsMap.get(triggerName) || []), id];
55
+ this.subsRefsMap.set(triggerName, nextRefs);
56
+ this.unsubscribeChannelMap.set(triggerName, disposer);
57
+ return id;
58
+ }
59
+ catch (err) {
60
+ this.logger.error(err, "failed to receive message from queue '%s'", triggerName);
61
+ return id;
62
+ }
63
+ }
48
64
  this.logger.trace("trying to subscribe to queue '%s'", triggerName);
65
+ const subscriptionPromise = this.consumer.subscribe(triggerName, (msg) => this.onMessage(triggerName, msg), options);
66
+ this.pendingSubscriptions.set(triggerName, subscriptionPromise);
49
67
  try {
50
- const disposer = yield this.consumer.subscribe(triggerName, (msg) => this.onMessage(triggerName, msg), options);
51
- this.subsRefsMap[triggerName] = [...(this.subsRefsMap[triggerName] || []), id];
52
- this.unsubscribeChannelMap[id] = disposer;
68
+ const disposer = yield subscriptionPromise;
69
+ this.subsRefsMap.set(triggerName, [...(this.subsRefsMap.get(triggerName) || []), id]);
70
+ this.unsubscribeChannelMap.set(triggerName, disposer);
71
+ this.pendingSubscriptions.delete(triggerName);
53
72
  return id;
54
73
  }
55
74
  catch (err) {
56
- this.logger.error(err, "failed to recieve message from queue '%s'", triggerName);
75
+ this.logger.error(err, "failed to receive message from queue '%s'", triggerName);
76
+ this.pendingSubscriptions.delete(triggerName);
57
77
  return id;
58
78
  }
59
79
  });
60
80
  }
61
81
  unsubscribe(subId) {
62
- const [triggerName = null] = this.subscriptionMap[subId] || [];
63
- const refs = this.subsRefsMap[triggerName];
64
- if (!refs) {
82
+ const entry = this.subscriptionMap.get(subId);
83
+ const triggerName = entry === null || entry === void 0 ? void 0 : entry[0];
84
+ if (!triggerName) {
65
85
  this.logger.error("There is no subscription of id '%s'", subId);
66
86
  throw new Error(`There is no subscription of id "${subId}"`);
67
87
  }
68
- let newRefs;
88
+ const refs = this.subsRefsMap.get(triggerName);
89
+ if (!refs)
90
+ return;
69
91
  if (refs.length === 1) {
70
- newRefs = [];
71
- if (typeof this.unsubscribeChannelMap[subId] === 'function') {
72
- this.unsubscribeChannelMap[subId]()
92
+ const disposer = this.unsubscribeChannelMap.get(triggerName);
93
+ if (typeof disposer === 'function') {
94
+ disposer()
73
95
  .then(() => {
74
96
  this.logger.trace("cancelled channel from subscribing to queue '%s'", triggerName);
75
97
  })
76
98
  .catch((err) => {
77
- this.logger.error(err, "channel cancellation failed from queue '%j'", triggerName);
99
+ this.logger.error(err, "channel cancellation failed from queue '%s'", triggerName);
100
+ })
101
+ .finally(() => {
102
+ this.unsubscribeChannelMap.delete(triggerName);
78
103
  });
79
104
  }
105
+ this.subsRefsMap.delete(triggerName);
80
106
  }
81
107
  else {
82
- const index = refs.indexOf(subId);
83
- if (index !== -1) {
84
- newRefs = [...refs.slice(0, index), ...refs.slice(index + 1)];
85
- }
86
- this.logger.trace("removing triggerName from listening '%s' ", triggerName);
108
+ this.subsRefsMap.set(triggerName, refs.filter((id) => id !== subId));
109
+ this.logger.trace("removing triggerName from listening '%s'", triggerName);
87
110
  }
88
- this.subsRefsMap[triggerName] = newRefs;
89
- delete this.subscriptionMap[subId];
90
- this.logger.trace("list of subscriptions still available '(%j)'", this.subscriptionMap);
111
+ this.subscriptionMap.delete(subId);
112
+ this.logger.trace("list of subscriptions still available '(%j)'", Array.from(this.subscriptionMap.entries()));
91
113
  }
92
114
  asyncIterator(triggers, options) {
93
115
  return new pubsub_async_iterator_1.PubSubAsyncIterator(this, triggers, options);
94
116
  }
95
117
  onMessage(channel, message) {
96
118
  return __awaiter(this, void 0, void 0, function* () {
97
- const subscribers = this.subsRefsMap[channel];
98
- // Don't work for nothing..
99
- if (!subscribers || !subscribers.length) {
119
+ const subscribers = this.subsRefsMap.get(channel);
120
+ if (!(subscribers === null || subscribers === void 0 ? void 0 : subscribers.length))
100
121
  return;
101
- }
102
122
  this.logger.trace("sending message to subscriber callback function '(%j)'", message);
103
123
  for (const subId of subscribers) {
104
- const [triggerName, listener] = this.subscriptionMap[subId];
124
+ const entry = this.subscriptionMap.get(subId);
125
+ if (!entry)
126
+ continue;
127
+ const [, listener] = entry;
105
128
  yield listener(message);
106
129
  }
107
130
  });
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export { AmqpPubSub } from './amqp-pubsub';
2
+ import { FilterFn, ResolverFn } from 'graphql-subscriptions';
3
+ export declare function withFilter<T>(this: T, asyncIteratorFn: ResolverFn<any, any, any>, filterFn: FilterFn<any, any, any>): import("graphql-subscriptions").IterableResolverFn<any, any, any>;
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AmqpPubSub = void 0;
4
+ exports.withFilter = withFilter;
4
5
  var amqp_pubsub_1 = require("./amqp-pubsub");
5
6
  Object.defineProperty(exports, "AmqpPubSub", { enumerable: true, get: function () { return amqp_pubsub_1.AmqpPubSub; } });
7
+ const graphql_subscriptions_1 = require("graphql-subscriptions");
8
+ function withFilter(asyncIteratorFn, filterFn) {
9
+ return (0, graphql_subscriptions_1.withFilter)(asyncIteratorFn, filterFn);
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxdi/graphql-rabbitmq-subscriptions",
3
- "version": "0.7.192",
3
+ "version": "0.7.194",
4
4
  "description": "A graphql-subscriptions PubSub Engine using RabbitMQ",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "lint": "tslint ./src/**/*.ts"
25
25
  },
26
26
  "dependencies": {
27
- "@rxdi/rabbitmq-pubsub": "^0.7.191",
27
+ "@rxdi/rabbitmq-pubsub": "^0.7.193",
28
28
  "graphql-subscriptions": "^3.0.0"
29
29
  },
30
30
  "devDependencies": {