@nest-yalc-2/api-strategy 2.2.1 → 2.2.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.
Files changed (30) hide show
  1. package/package.json +10 -2
  2. package/src/__tests__/call-strategy-wrappers.spec.ts +217 -217
  3. package/src/__tests__/context-call.service.spec.ts +52 -52
  4. package/src/__tests__/context-event.service.spec.ts +52 -52
  5. package/src/__tests__/event-strategy-composition.spec.ts +185 -185
  6. package/src/__tests__/header-whitelist.helper.spec.ts +41 -30
  7. package/src/__tests__/http-abstract-call.strategy.spec.ts +17 -17
  8. package/src/__tests__/nest-http-call.strategy.spec.ts +164 -164
  9. package/src/__tests__/nest-local-call.strategy.spec.ts +263 -263
  10. package/src/__tests__/nest-local-event.strategy.spec.ts +55 -55
  11. package/src/__tests__/rabbitmq-event.strategy.spec.ts +220 -220
  12. package/src/__tests__/strategy-selector.provider.spec.ts +182 -182
  13. package/src/context-call.interface.ts +49 -49
  14. package/src/context-call.service.ts +31 -31
  15. package/src/context-event.interface.ts +4 -4
  16. package/src/context-event.service.ts +31 -31
  17. package/src/header-whitelist.helper.ts +12 -12
  18. package/src/index.ts +17 -17
  19. package/src/strategies/composite-event.strategy.ts +76 -76
  20. package/src/strategies/conditional-call.strategy.ts +70 -70
  21. package/src/strategies/conditional-event.strategy.ts +54 -54
  22. package/src/strategies/fallback-call.strategy.ts +67 -67
  23. package/src/strategies/http-abstract-call.strategy.ts +238 -238
  24. package/src/strategies/nest-http-call.strategy.ts +121 -121
  25. package/src/strategies/nest-local-call.strategy.ts +153 -153
  26. package/src/strategies/nest-local-event.strategy.d.ts +1 -1
  27. package/src/strategies/nest-local-event.strategy.ts +42 -42
  28. package/src/strategies/rabbitmq-event.strategy.ts +154 -154
  29. package/src/strategies/shadow-call.strategy.ts +87 -87
  30. package/src/strategy-selector.provider.ts +103 -103
@@ -1,154 +1,154 @@
1
- import { OnModuleDestroy } from '@nestjs/common';
2
- import type { ClassType } from '@node-yalc/types/globals.js';
3
- import amqp, { Channel, ChannelModel, Options } from 'amqplib';
4
- import { IEventStrategy } from '../context-event.interface.js';
5
-
6
- interface RabbitConnectionResource {
7
- stream?: { destroy: () => void };
8
- heartbeater?: { clear: () => void };
9
- }
10
-
11
- export interface RabbitMqEventStrategyOptions<P = any> {
12
- url: string;
13
- exchange: string;
14
- exchangeType?: string;
15
- durable?: boolean;
16
- persistent?: boolean;
17
- contentType?: string;
18
- publishOptions?: Options.Publish;
19
- serialize?: (payload: P) => Buffer | string;
20
- }
21
-
22
- export class RabbitMqEventStrategy<P = any, O = any>
23
- implements IEventStrategy<P, boolean, O>, OnModuleDestroy
24
- {
25
- private connection?: ChannelModel;
26
- private channel?: Channel;
27
-
28
- constructor(private readonly options: RabbitMqEventStrategyOptions<P>) {}
29
-
30
- emit(path: string, payload: P): boolean {
31
- void this.publish(path, payload).catch(() => undefined);
32
- return true;
33
- }
34
-
35
- async emitAsync(path: string, payload: P): Promise<boolean> {
36
- return this.publish(path, payload);
37
- }
38
-
39
- async onModuleDestroy() {
40
- await closeRabbitResource(this.channel);
41
- await closeRabbitResource(this.connection);
42
- this.channel = undefined;
43
- this.connection = undefined;
44
- }
45
-
46
- private async publish(path: string, payload: P) {
47
- const channel = await this.getChannel();
48
- const body = this.serialize(payload);
49
-
50
- return channel.publish(this.options.exchange, path, body, {
51
- contentType: this.options.contentType ?? 'application/json',
52
- persistent: this.options.persistent ?? true,
53
- ...this.options.publishOptions,
54
- });
55
- }
56
-
57
- private serialize(payload: P) {
58
- const serialized = this.options.serialize?.(payload);
59
-
60
- if (Buffer.isBuffer(serialized)) {
61
- return serialized;
62
- }
63
-
64
- return Buffer.from(serialized ?? JSON.stringify(payload));
65
- }
66
-
67
- private async getChannel() {
68
- if (this.channel) {
69
- return this.channel;
70
- }
71
-
72
- this.connection = await amqp.connect(this.options.url);
73
- this.channel = await this.connection.createChannel();
74
- await this.channel.assertExchange(
75
- this.options.exchange,
76
- this.options.exchangeType ?? 'topic',
77
- {
78
- durable: this.options.durable ?? true,
79
- },
80
- );
81
-
82
- return this.channel;
83
- }
84
- }
85
-
86
- export interface RabbitMqEventStrategyProviderOptions<P = any> {
87
- RabbitMqStrategy?: ClassType<RabbitMqEventStrategy<P>>;
88
- options:
89
- | RabbitMqEventStrategyOptions<P>
90
- | (() => RabbitMqEventStrategyOptions<P>);
91
- }
92
-
93
- export const RabbitMqEventStrategyProvider = <P = any>(
94
- provide: string,
95
- options: RabbitMqEventStrategyProviderOptions<P>,
96
- ) => ({
97
- provide,
98
- useFactory: () => {
99
- const Strategy = options.RabbitMqStrategy ?? RabbitMqEventStrategy;
100
- const strategyOptions =
101
- typeof options.options === 'function'
102
- ? options.options()
103
- : options.options;
104
-
105
- return new Strategy(strategyOptions);
106
- },
107
- });
108
-
109
- async function closeRabbitResource(resource?: {
110
- close: () => Promise<void>;
111
- connection?: unknown;
112
- stream?: { destroy: () => void };
113
- heartbeater?: { clear: () => void };
114
- }) {
115
- if (!resource) {
116
- return;
117
- }
118
-
119
- let timeout: NodeJS.Timeout | undefined;
120
-
121
- try {
122
- await Promise.race([
123
- resource.close(),
124
- new Promise((resolve) => {
125
- timeout = setTimeout(resolve, 1000);
126
- }),
127
- ]);
128
- } catch (error) {
129
- if (!(error instanceof Error) || !error.message.includes('closing')) {
130
- throw error;
131
- }
132
- } finally {
133
- if (timeout) {
134
- clearTimeout(timeout);
135
- }
136
-
137
- forceCloseRabbitResource(resource);
138
- }
139
- }
140
-
141
- function forceCloseRabbitResource(resource: {
142
- connection?: unknown;
143
- stream?: { destroy: () => void };
144
- heartbeater?: { clear: () => void };
145
- }) {
146
- const connection = resource.connection as
147
- | RabbitConnectionResource
148
- | undefined;
149
-
150
- connection?.heartbeater?.clear();
151
- resource.heartbeater?.clear();
152
- connection?.stream?.destroy();
153
- resource.stream?.destroy();
154
- }
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import type { ClassType } from '@node-yalc/types/globals.js';
3
+ import amqp, { Channel, ChannelModel, Options } from 'amqplib';
4
+ import { IEventStrategy } from '../context-event.interface.js';
5
+
6
+ interface RabbitConnectionResource {
7
+ stream?: { destroy: () => void };
8
+ heartbeater?: { clear: () => void };
9
+ }
10
+
11
+ export interface RabbitMqEventStrategyOptions<P = any> {
12
+ url: string;
13
+ exchange: string;
14
+ exchangeType?: string;
15
+ durable?: boolean;
16
+ persistent?: boolean;
17
+ contentType?: string;
18
+ publishOptions?: Options.Publish;
19
+ serialize?: (payload: P) => Buffer | string;
20
+ }
21
+
22
+ export class RabbitMqEventStrategy<P = any, O = any>
23
+ implements IEventStrategy<P, boolean, O>, OnModuleDestroy
24
+ {
25
+ private connection?: ChannelModel;
26
+ private channel?: Channel;
27
+
28
+ constructor(private readonly options: RabbitMqEventStrategyOptions<P>) {}
29
+
30
+ emit(path: string, payload: P): boolean {
31
+ void this.publish(path, payload).catch(() => undefined);
32
+ return true;
33
+ }
34
+
35
+ async emitAsync(path: string, payload: P): Promise<boolean> {
36
+ return this.publish(path, payload);
37
+ }
38
+
39
+ async onModuleDestroy() {
40
+ await closeRabbitResource(this.channel);
41
+ await closeRabbitResource(this.connection);
42
+ this.channel = undefined;
43
+ this.connection = undefined;
44
+ }
45
+
46
+ private async publish(path: string, payload: P) {
47
+ const channel = await this.getChannel();
48
+ const body = this.serialize(payload);
49
+
50
+ return channel.publish(this.options.exchange, path, body, {
51
+ contentType: this.options.contentType ?? 'application/json',
52
+ persistent: this.options.persistent ?? true,
53
+ ...this.options.publishOptions,
54
+ });
55
+ }
56
+
57
+ private serialize(payload: P) {
58
+ const serialized = this.options.serialize?.(payload);
59
+
60
+ if (Buffer.isBuffer(serialized)) {
61
+ return serialized;
62
+ }
63
+
64
+ return Buffer.from(serialized ?? JSON.stringify(payload));
65
+ }
66
+
67
+ private async getChannel() {
68
+ if (this.channel) {
69
+ return this.channel;
70
+ }
71
+
72
+ this.connection = await amqp.connect(this.options.url);
73
+ this.channel = await this.connection.createChannel();
74
+ await this.channel.assertExchange(
75
+ this.options.exchange,
76
+ this.options.exchangeType ?? 'topic',
77
+ {
78
+ durable: this.options.durable ?? true,
79
+ },
80
+ );
81
+
82
+ return this.channel;
83
+ }
84
+ }
85
+
86
+ export interface RabbitMqEventStrategyProviderOptions<P = any> {
87
+ RabbitMqStrategy?: ClassType<RabbitMqEventStrategy<P>>;
88
+ options:
89
+ | RabbitMqEventStrategyOptions<P>
90
+ | (() => RabbitMqEventStrategyOptions<P>);
91
+ }
92
+
93
+ export const RabbitMqEventStrategyProvider = <P = any>(
94
+ provide: string,
95
+ options: RabbitMqEventStrategyProviderOptions<P>,
96
+ ) => ({
97
+ provide,
98
+ useFactory: () => {
99
+ const Strategy = options.RabbitMqStrategy ?? RabbitMqEventStrategy;
100
+ const strategyOptions =
101
+ typeof options.options === 'function'
102
+ ? options.options()
103
+ : options.options;
104
+
105
+ return new Strategy(strategyOptions);
106
+ },
107
+ });
108
+
109
+ async function closeRabbitResource(resource?: {
110
+ close: () => Promise<void>;
111
+ connection?: unknown;
112
+ stream?: { destroy: () => void };
113
+ heartbeater?: { clear: () => void };
114
+ }) {
115
+ if (!resource) {
116
+ return;
117
+ }
118
+
119
+ let timeout: NodeJS.Timeout | undefined;
120
+
121
+ try {
122
+ await Promise.race([
123
+ resource.close(),
124
+ new Promise((resolve) => {
125
+ timeout = setTimeout(resolve, 1000);
126
+ }),
127
+ ]);
128
+ } catch (error) {
129
+ if (!(error instanceof Error) || !error.message.includes('closing')) {
130
+ throw error;
131
+ }
132
+ } finally {
133
+ if (timeout) {
134
+ clearTimeout(timeout);
135
+ }
136
+
137
+ forceCloseRabbitResource(resource);
138
+ }
139
+ }
140
+
141
+ function forceCloseRabbitResource(resource: {
142
+ connection?: unknown;
143
+ stream?: { destroy: () => void };
144
+ heartbeater?: { clear: () => void };
145
+ }) {
146
+ const connection = resource.connection as
147
+ | RabbitConnectionResource
148
+ | undefined;
149
+
150
+ connection?.heartbeater?.clear();
151
+ resource.heartbeater?.clear();
152
+ connection?.stream?.destroy();
153
+ resource.stream?.destroy();
154
+ }
@@ -1,87 +1,87 @@
1
- import {
2
- IApiCallStrategy,
3
- ICallOptions,
4
- IObjectWithData,
5
- } from '../context-call.interface.js';
6
-
7
- export type ShadowCallStrategyErrorMode = 'throw' | 'ignore';
8
-
9
- export interface ShadowCallStrategyOptions {
10
- awaitShadows?: boolean;
11
- shadowErrorMode?: ShadowCallStrategyErrorMode;
12
- }
13
-
14
- export class ShadowCallStrategy implements IApiCallStrategy {
15
- private readonly awaitShadows: boolean;
16
- private readonly shadowErrorMode: ShadowCallStrategyErrorMode;
17
-
18
- constructor(
19
- private readonly primary: IApiCallStrategy,
20
- private readonly shadows: IApiCallStrategy[],
21
- options: ShadowCallStrategyOptions = {},
22
- ) {
23
- if (shadows.length === 0) {
24
- throw new Error(
25
- 'ShadowCallStrategy requires at least one shadow strategy.',
26
- );
27
- }
28
-
29
- this.shadowErrorMode = options.shadowErrorMode ?? 'ignore';
30
- this.awaitShadows =
31
- options.awaitShadows ?? this.shadowErrorMode === 'throw';
32
- }
33
-
34
- call<TOptData, TParams extends Record<string, any>, TResData>(
35
- path: string,
36
- options?: ICallOptions<TOptData, TParams>,
37
- ): Promise<IObjectWithData<TResData>> {
38
- return this.execute(
39
- (strategy) => strategy.call(path, options),
40
- (strategy) => strategy.call(path, options),
41
- );
42
- }
43
-
44
- get<TOptData, TParams extends Record<string, any>, TResData>(
45
- path: string,
46
- options?: ICallOptions<TOptData, TParams>,
47
- ): Promise<IObjectWithData<TResData>> {
48
- return this.execute(
49
- (strategy) => strategy.get(path, options),
50
- (strategy) => strategy.get(path, options),
51
- );
52
- }
53
-
54
- post<TOptData, TParams extends Record<string, any>, TResData>(
55
- path: string,
56
- options?: ICallOptions<TOptData, TParams>,
57
- ): Promise<IObjectWithData<TResData>> {
58
- return this.execute(
59
- (strategy) => strategy.post(path, options),
60
- (strategy) => strategy.post(path, options),
61
- );
62
- }
63
-
64
- private async execute<TResData>(
65
- primaryCall: (
66
- strategy: IApiCallStrategy,
67
- ) => Promise<IObjectWithData<TResData>>,
68
- shadowCall: (strategy: IApiCallStrategy) => Promise<IObjectWithData<any>>,
69
- ) {
70
- const primaryResult = await primaryCall(this.primary);
71
- const shadowExecution = Promise.all(
72
- this.shadows.map((strategy) => shadowCall(strategy)),
73
- );
74
-
75
- if (this.awaitShadows) {
76
- if (this.shadowErrorMode === 'throw') {
77
- await shadowExecution;
78
- } else {
79
- await shadowExecution.catch(() => undefined);
80
- }
81
- } else {
82
- void shadowExecution.catch(() => undefined);
83
- }
84
-
85
- return primaryResult;
86
- }
87
- }
1
+ import {
2
+ IApiCallStrategy,
3
+ ICallOptions,
4
+ IObjectWithData,
5
+ } from '../context-call.interface.js';
6
+
7
+ export type ShadowCallStrategyErrorMode = 'throw' | 'ignore';
8
+
9
+ export interface ShadowCallStrategyOptions {
10
+ awaitShadows?: boolean;
11
+ shadowErrorMode?: ShadowCallStrategyErrorMode;
12
+ }
13
+
14
+ export class ShadowCallStrategy implements IApiCallStrategy {
15
+ private readonly awaitShadows: boolean;
16
+ private readonly shadowErrorMode: ShadowCallStrategyErrorMode;
17
+
18
+ constructor(
19
+ private readonly primary: IApiCallStrategy,
20
+ private readonly shadows: IApiCallStrategy[],
21
+ options: ShadowCallStrategyOptions = {},
22
+ ) {
23
+ if (shadows.length === 0) {
24
+ throw new Error(
25
+ 'ShadowCallStrategy requires at least one shadow strategy.',
26
+ );
27
+ }
28
+
29
+ this.shadowErrorMode = options.shadowErrorMode ?? 'ignore';
30
+ this.awaitShadows =
31
+ options.awaitShadows ?? this.shadowErrorMode === 'throw';
32
+ }
33
+
34
+ call<TOptData, TParams extends Record<string, any>, TResData>(
35
+ path: string,
36
+ options?: ICallOptions<TOptData, TParams>,
37
+ ): Promise<IObjectWithData<TResData>> {
38
+ return this.execute(
39
+ (strategy) => strategy.call(path, options),
40
+ (strategy) => strategy.call(path, options),
41
+ );
42
+ }
43
+
44
+ get<TOptData, TParams extends Record<string, any>, TResData>(
45
+ path: string,
46
+ options?: ICallOptions<TOptData, TParams>,
47
+ ): Promise<IObjectWithData<TResData>> {
48
+ return this.execute(
49
+ (strategy) => strategy.get(path, options),
50
+ (strategy) => strategy.get(path, options),
51
+ );
52
+ }
53
+
54
+ post<TOptData, TParams extends Record<string, any>, TResData>(
55
+ path: string,
56
+ options?: ICallOptions<TOptData, TParams>,
57
+ ): Promise<IObjectWithData<TResData>> {
58
+ return this.execute(
59
+ (strategy) => strategy.post(path, options),
60
+ (strategy) => strategy.post(path, options),
61
+ );
62
+ }
63
+
64
+ private async execute<TResData>(
65
+ primaryCall: (
66
+ strategy: IApiCallStrategy,
67
+ ) => Promise<IObjectWithData<TResData>>,
68
+ shadowCall: (strategy: IApiCallStrategy) => Promise<IObjectWithData<any>>,
69
+ ) {
70
+ const primaryResult = await primaryCall(this.primary);
71
+ const shadowExecution = Promise.all(
72
+ this.shadows.map((strategy) => shadowCall(strategy)),
73
+ );
74
+
75
+ if (this.awaitShadows) {
76
+ if (this.shadowErrorMode === 'throw') {
77
+ await shadowExecution;
78
+ } else {
79
+ await shadowExecution.catch(() => undefined);
80
+ }
81
+ } else {
82
+ void shadowExecution.catch(() => undefined);
83
+ }
84
+
85
+ return primaryResult;
86
+ }
87
+ }