Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8dedefb

Browse files
committed
feat: added durable support for event subscriber
1 parent fc91489 commit 8dedefb

File tree

5 files changed

+141
-15
lines changed

5 files changed

+141
-15
lines changed

‎lib/event-subscribers.loader.ts‎

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import {
1111
ModuleRef,
1212
} from '@nestjs/core';
1313
import { Injector } from '@nestjs/core/injector/injector';
14-
import {
15-
ContextId,
16-
InstanceWrapper,
17-
} from '@nestjs/core/injector/instance-wrapper';
14+
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
1815
import { Module } from '@nestjs/core/injector/module';
1916
import { EventEmitter2 } from 'eventemitter2';
2017
import { EventEmitterReadinessWatcher } from './event-emitter-readiness.watcher';
@@ -140,9 +137,10 @@ export class EventSubscribersLoader
140137
listenerMethod(
141138
event,
142139
async (...args: unknown[]) => {
143-
const contextId = ContextIdFactory.create();
140+
const request = this.getRequestFromEventPayload(args);
141+
const contextId = ContextIdFactory.getByRequest({ payload: request });
144142

145-
this.registerEventPayloadByContextId(args, contextId);
143+
this.moduleRef.registerRequestByContextId(request, contextId);
146144

147145
const contextInstance = await this.injector.loadPerContext(
148146
eventListenerInstance,
@@ -161,10 +159,7 @@ export class EventSubscribersLoader
161159
);
162160
}
163161

164-
private registerEventPayloadByContextId(
165-
eventPayload: unknown[],
166-
contextId: ContextId,
167-
) {
162+
private getRequestFromEventPayload(eventPayload: unknown[]): unknown {
168163
/*
169164
**Required explanation for the ternary below**
170165
@@ -179,11 +174,7 @@ export class EventSubscribersLoader
179174
However, whoever is using this library would certainly expect the event payload to be a single string 'payload', not an array,
180175
since this is what we emitted above.
181176
*/
182-
183-
const payloadObjectOrArray =
184-
eventPayload.length > 1 ? eventPayload : eventPayload[0];
185-
186-
this.moduleRef.registerRequestByContextId(payloadObjectOrArray, contextId);
177+
return eventPayload.length > 1 ? eventPayload : eventPayload[0];
187178
}
188179

189180
private async wrapFunctionInTryCatchBlocks(

‎tests/e2e/module-e2e.spec.ts‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { INestApplication } from '@nestjs/common';
2+
import { ContextIdFactory, createContextId } from '@nestjs/core';
23
import { Test } from '@nestjs/testing';
34
import { EventEmitter2 } from 'eventemitter2';
45
import { EventEmitterReadinessWatcher } from '../../lib';
@@ -14,11 +15,21 @@ import { EventsControllerConsumer } from '../src/events-controller.consumer';
1415
import { EventsProviderAliasedConsumer } from '../src/events-provider-aliased.consumer';
1516
import { EventsProviderPrependConsumer } from '../src/events-provider-prepend.consumer';
1617
import { EventsProviderConsumer } from '../src/events-provider.consumer';
18+
import { EventsProviderDurableRequestScopedConsumer } from '../src/events-provider.durable-request-scoped.consumer';
1719
import { EventsProviderRequestScopedConsumer } from '../src/events-provider.request-scoped.consumer';
1820
import { TEST_PROVIDER_TOKEN } from '../src/test-provider';
1921

2022
describe('EventEmitterModule - e2e', () => {
2123
let app: INestApplication;
24+
const durableContextId = createContextId();
25+
26+
beforeAll(() => {
27+
ContextIdFactory.apply({
28+
attach: (contextId, _request) => info => {
29+
return info.isTreeDurable ? durableContextId : contextId;
30+
},
31+
});
32+
});
2233

2334
beforeEach(async () => {
2435
const module = await Test.createTestingModule({
@@ -95,6 +106,15 @@ describe('EventEmitterModule - e2e', () => {
95106
).toEqual(TEST_EVENT_PAYLOAD);
96107
});
97108

109+
it('should be able to emit a durable request-scoped event with a single payload', async () => {
110+
await app.init();
111+
112+
expect(
113+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload
114+
.objectValue,
115+
).toEqual(TEST_EVENT_PAYLOAD);
116+
});
117+
98118
it('should be able to emit a request-scoped event with a string payload', async () => {
99119
await app.init();
100120

@@ -103,6 +123,15 @@ describe('EventEmitterModule - e2e', () => {
103123
).toEqual(TEST_EVENT_STRING_PAYLOAD);
104124
});
105125

126+
it('should be able to emit a durable request-scoped event with a string payload', async () => {
127+
await app.init();
128+
129+
expect(
130+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload
131+
.stringValue,
132+
).toEqual(TEST_EVENT_STRING_PAYLOAD);
133+
});
134+
106135
it('should be able to emit a request-scoped event with multiple payloads', async () => {
107136
await app.init();
108137

@@ -111,6 +140,15 @@ describe('EventEmitterModule - e2e', () => {
111140
).toEqual(TEST_EVENT_MULTIPLE_PAYLOAD);
112141
});
113142

143+
it('should be able to emit a durable request-scoped event with multiple payloads', async () => {
144+
await app.init();
145+
146+
expect(
147+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload
148+
.arrayValue,
149+
).toEqual(TEST_EVENT_MULTIPLE_PAYLOAD);
150+
});
151+
114152
it('should work with non array metadata', async () => {
115153
await app.init();
116154

@@ -192,6 +230,39 @@ describe('EventEmitterModule - e2e', () => {
192230
expect(eventsConsumerRef.eventPayload).toEqual(TEST_EVENT_PAYLOAD);
193231
});
194232

233+
it('should throw when an unexpected error occurs from durable request scoped and suppressErrors is false', async () => {
234+
await app.init();
235+
236+
const eventEmitter = app.get(EventEmitter2);
237+
expect(
238+
eventEmitter.emitAsync('error-throwing.durable-request-scoped'),
239+
).rejects.toThrow('This is a test error');
240+
});
241+
242+
it('should load durable provider once for different event emissions', async () => {
243+
await app.init();
244+
const eventEmitter = app.get(EventEmitter2);
245+
const [durableInstance] = await eventEmitter.emitAsync('durable');
246+
const [durableInstance2] = await eventEmitter.emitAsync('durable');
247+
expect(durableInstance).toBe(durableInstance2);
248+
});
249+
250+
it('should load durable provider once for different event emissions', async () => {
251+
await app.init();
252+
const eventEmitter = app.get(EventEmitter2);
253+
const [durableInstance] = await eventEmitter.emitAsync('durable');
254+
const [durableInstance2] = await eventEmitter.emitAsync('durable');
255+
expect(durableInstance).toBe(durableInstance2);
256+
});
257+
258+
it('should load non-durable provider anew for different event emissions', async () => {
259+
await app.init();
260+
const eventEmitter = app.get(EventEmitter2);
261+
const [notDurableInstance] = await eventEmitter.emitAsync('not-durable');
262+
const [notDurableInstance2] = await eventEmitter.emitAsync('not-durable');
263+
expect(notDurableInstance).not.toBe(notDurableInstance2);
264+
});
265+
195266
afterEach(async () => {
196267
await app.close();
197268
});

‎tests/src/app.module.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EventsControllerConsumer } from './events-controller.consumer';
55
import { EventsProviderAliasedConsumer } from './events-provider-aliased.consumer';
66
import { EventsProviderPrependConsumer } from './events-provider-prepend.consumer';
77
import { EventsProviderConsumer } from './events-provider.consumer';
8+
import { EventsProviderDurableRequestScopedConsumer } from './events-provider.durable-request-scoped.consumer';
89
import { EventsProviderRequestScopedConsumer } from './events-provider.request-scoped.consumer';
910
import { EventsProducer } from './events.producer';
1011
import { TestProvider } from './test-provider';
@@ -22,6 +23,7 @@ import { TestProvider } from './test-provider';
2223
EventsProducer,
2324
TestProvider,
2425
EventsProviderRequestScopedConsumer,
26+
EventsProviderDurableRequestScopedConsumer,
2527
EventsProviderAliasedConsumer,
2628
{
2729
provide: 'AnAliasedConsumer',
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Inject, Injectable, Scope } from '@nestjs/common';
2+
import { OnEvent } from '../../lib';
3+
import { EVENT_PAYLOAD } from '../../lib';
4+
import { RequestScopedEventPayload } from './request-scoped-event-payload';
5+
6+
@Injectable({ scope: Scope.REQUEST, durable: true })
7+
export class EventsProviderDurableRequestScopedConsumer {
8+
constructor(@Inject(EVENT_PAYLOAD) public eventRef: any) {}
9+
10+
public static injectedEventPayload = new RequestScopedEventPayload();
11+
12+
private transformPayload = (payload: unknown[]) =>
13+
payload.length === 1 ? payload[0] : payload;
14+
15+
@OnEvent('test.*')
16+
onTestEvent(...payload: unknown[]) {
17+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload.setPayload(
18+
this.transformPayload(payload),
19+
);
20+
}
21+
22+
@OnEvent('multiple.*')
23+
onMultiplePayloadEvent(...payload: unknown[]) {
24+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload.setPayload(
25+
this.transformPayload(payload),
26+
);
27+
}
28+
29+
@OnEvent('string.*')
30+
onStringPayloadEvent(payload: string) {
31+
EventsProviderDurableRequestScopedConsumer.injectedEventPayload.setPayload(
32+
payload,
33+
);
34+
}
35+
36+
@OnEvent('error-handling.durable-request-scoped')
37+
onErrorHandlingEvent() {
38+
throw new Error('This is a test error');
39+
}
40+
41+
@OnEvent('error-handling-suppressed.durable-request-scoped', {
42+
suppressErrors: true,
43+
})
44+
onErrorHandlingSuppressedEvent() {
45+
throw new Error('This is a test error');
46+
}
47+
48+
@OnEvent('error-throwing.durable-request-scoped', { suppressErrors: false })
49+
onErrorThrowingEvent() {
50+
throw new Error('This is a test error');
51+
}
52+
53+
@OnEvent('durable')
54+
onDurableTest() {
55+
return this;
56+
}
57+
}

‎tests/src/events-provider.request-scoped.consumer.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ export class EventsProviderRequestScopedConsumer {
3636
onErrorThrowingEvent() {
3737
throw new Error('This is a test error');
3838
}
39+
40+
@OnEvent('not-durable')
41+
onDurableTest() {
42+
return this;
43+
}
3944
}

0 commit comments

Comments
 (0)