@nebulae/event-store-tpi-rx6 1.1.4 → 1.2.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.
@@ -21,19 +21,50 @@ class PubSubBroker {
21
21
  )
22
22
  this.senderId = uuidv4();
23
23
  this.pubsubClient = new PubSub({});
24
+ this.subscriptionLock = false;
25
+ this.startLock = false;
24
26
  }
25
27
 
26
28
  /**
27
29
  * Starts Broker connections
28
30
  * Returns an Obserable that resolves to each connection result
31
+ * Uses a semaphore to prevent concurrent access - returns of(null) if busy
29
32
  */
30
- start$() {
33
+ start$() {
31
34
  return new Rx.Observable(async (observer) => {
32
- const [topic] = await this.pubsubClient.createTopic(this.eventsTopic);
33
- this.topic = topic;
34
- this.startMessageListener(topic);
35
- observer.next(`Event Store PubSub Broker listening: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}`);
36
- observer.complete();
35
+ // Check semaphore - return null if already busy
36
+ if (this.startLock) {
37
+ observer.next(null);
38
+ observer.complete();
39
+ return;
40
+ }
41
+
42
+ // Acquire lock
43
+ this.startLock = true;
44
+
45
+ try {
46
+ try {
47
+ const [topic] = await this.pubsubClient.createTopic(this.eventsTopic);
48
+ console.log(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker created topic: Topic=${this.eventsTopic}`);
49
+ this.topic = topic;
50
+ } catch (error) {
51
+ if (error.code === 6) {
52
+ this.topic = this.pubsubClient.topic(this.eventsTopic);
53
+ console.log(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker topic already exists, getting existing topic: Topic=${this.eventsTopic}`);
54
+ } else {
55
+ observer.error(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker failed to create or get topic: Topic=${this.eventsTopic}, Error=${error.message}`);
56
+ return;
57
+ }
58
+ }
59
+ if (this.topic) {
60
+ this.startMessageListener(this.topic);
61
+ observer.next(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker listening: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}`);
62
+ }
63
+ observer.complete();
64
+ } finally {
65
+ // Release lock
66
+ this.startLock = false;
67
+ }
37
68
  });
38
69
  }
39
70
 
@@ -42,10 +73,12 @@ class PubSubBroker {
42
73
  */
43
74
  stop$() {
44
75
  return Rx.Observable.create(observer => {
45
- this.getSubscription$().subscribe(
76
+ Rx.defer(() => this.getSubscription$()).pipe(
77
+ filter(sub => sub != null)
78
+ ).subscribe(
46
79
  (subscription) => {
47
80
  subscription.removeListener(`message`, this.onMessage);
48
- observer.next(`Event Store PubSub Broker removed listener: Topic=${this.eventsTopic}, subscriptionName=${subscription}`);
81
+ observer.next(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker removed listener: Topic=${this.eventsTopic}, subscriptionName=${subscription}`);
49
82
  },
50
83
  (error) => observer.error(error),
51
84
  () => {
@@ -82,50 +115,73 @@ class PubSubBroker {
82
115
  getEventListener$(aggregateType, ignoreSelfEvents = true) {
83
116
  return this.orderedIncomingEvents$.pipe(
84
117
  filter(msg => msg)
85
- ,filter(msg => !ignoreSelfEvents || msg.attributes.senderId !== this.senderId)
86
- ,map(msg => msg.data)
87
- ,filter(evt => evt.at === aggregateType || aggregateType == "*")
88
- )
118
+ , filter(msg => !ignoreSelfEvents || msg.attributes.senderId !== this.senderId)
119
+ , map(msg => msg.data)
120
+ , filter(evt => evt.at === aggregateType || aggregateType == "*")
121
+ )
89
122
  }
90
123
 
91
124
 
92
125
  /**
93
126
  * Returns an Observable that resolves to the subscription
127
+ * Uses a semaphore to prevent concurrent access - returns null if busy
94
128
  */
95
- getSubscription$(topic) {
96
- return Rx.defer(() => (topic || this.topic).createSubscription(this.eventsTopicSubscription)).pipe(
97
- map(([subscription]) => subscription),
98
- );
129
+ async getSubscription$(topic) {
130
+ // Check semaphore - return null if already busy
131
+ if (this.subscriptionLock) {
132
+ //console.log(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker getSubscription$ is busy, returning null`);
133
+ return null;
134
+ }
135
+
136
+ // Acquire lock
137
+ this.subscriptionLock = true;
138
+
139
+ try {
140
+ const [subscription] = await (topic || this.topic).createSubscription(this.eventsTopicSubscription);
141
+ console.log(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker created subscription: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}`);
142
+ return subscription;
143
+ } catch (error) {
144
+ if (error.code === 6) {
145
+ console.log(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker subscription already exists, getting existing subscription: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}`);
146
+ return (topic || this.topic).subscription(this.eventsTopicSubscription);
147
+ } else {
148
+ throw new Error(`@nebulae/event-store-tpi-rx6: Event Store PubSub Broker failed to create or get subscription: Topic=${this.eventsTopic}, subscriptionName=${this.eventsTopicSubscription}, Error=${error.message}`);
149
+ }
150
+ } finally {
151
+ // Release lock
152
+ this.subscriptionLock = false;
153
+ }
99
154
  }
100
155
 
101
156
  /**
102
157
  * Starts to listen messages
103
158
  */
104
159
  startMessageListener(topic) {
105
- this.messageListenerSubscription = this.getSubscription$(topic)
106
- .subscribe(
107
- (pubSubSubscription) => {
108
- this.onMessage = message => {
109
- message.ack();
110
- this.incomingEvents$.next({
111
- data: JSON.parse(message.data),
112
- id: message.id,
113
- attributes: message.attributes,
114
- correlationId: message.attributes.correlationId
115
- });
116
- };
117
- pubSubSubscription.on(`message`, this.onMessage);
118
- pubSubSubscription.on('error', error => {
119
- console.error('@nebulae/event-store-tpi-rx6.PubSubBroker: Received error:', error);
160
+ this.messageListenerSubscription = Rx.defer(() => this.getSubscription$(topic)).pipe(
161
+ filter(sub => sub != null)
162
+ ).subscribe(
163
+ (pubSubSubscription) => {
164
+ this.onMessage = message => {
165
+ message.ack();
166
+ this.incomingEvents$.next({
167
+ data: JSON.parse(message.data),
168
+ id: message.id,
169
+ attributes: message.attributes,
170
+ correlationId: message.attributes.correlationId
120
171
  });
121
- },
122
- (err) => {
123
- console.error('Failed to obtain EventStore subscription', err);
124
- },
125
- () => {
126
- console.log('GatewayEvents listener has completed!');
127
- }
128
- );
172
+ };
173
+ pubSubSubscription.on(`message`, this.onMessage);
174
+ pubSubSubscription.on('error', error => {
175
+ console.error('@nebulae/event-store-tpi-rx6.PubSubBroker: Received error:', error);
176
+ });
177
+ },
178
+ (err) => {
179
+ console.error('Failed to obtain EventStore subscription', err);
180
+ },
181
+ () => {
182
+ console.log('GatewayEvents listener has completed!');
183
+ }
184
+ );
129
185
  }
130
186
 
131
187
  }
package/package.json CHANGED
@@ -64,5 +64,5 @@
64
64
  "scripts": {
65
65
  "test": "mocha --recursive --reporter spec"
66
66
  },
67
- "version": "1.1.4"
67
+ "version": "1.2.0"
68
68
  }