@nest-yalc-2/api-strategy 2.2.2 → 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.
- package/package.json +5 -3
- package/src/__tests__/call-strategy-wrappers.spec.ts +217 -217
- package/src/__tests__/context-call.service.spec.ts +52 -52
- package/src/__tests__/context-event.service.spec.ts +52 -52
- package/src/__tests__/event-strategy-composition.spec.ts +185 -185
- package/src/__tests__/header-whitelist.helper.spec.ts +41 -30
- package/src/__tests__/http-abstract-call.strategy.spec.ts +17 -17
- package/src/__tests__/nest-http-call.strategy.spec.ts +164 -164
- package/src/__tests__/nest-local-call.strategy.spec.ts +263 -263
- package/src/__tests__/nest-local-event.strategy.spec.ts +55 -55
- package/src/__tests__/rabbitmq-event.strategy.spec.ts +220 -220
- package/src/__tests__/strategy-selector.provider.spec.ts +182 -182
- package/src/context-call.interface.ts +49 -49
- package/src/context-call.service.ts +31 -31
- package/src/context-event.interface.ts +4 -4
- package/src/context-event.service.ts +31 -31
- package/src/header-whitelist.helper.ts +12 -12
- package/src/index.ts +17 -17
- package/src/strategies/composite-event.strategy.ts +76 -76
- package/src/strategies/conditional-call.strategy.ts +70 -70
- package/src/strategies/conditional-event.strategy.ts +54 -54
- package/src/strategies/fallback-call.strategy.ts +67 -67
- package/src/strategies/http-abstract-call.strategy.ts +238 -238
- package/src/strategies/nest-http-call.strategy.ts +121 -121
- package/src/strategies/nest-local-call.strategy.ts +153 -153
- package/src/strategies/nest-local-event.strategy.d.ts +1 -1
- package/src/strategies/nest-local-event.strategy.ts +42 -42
- package/src/strategies/rabbitmq-event.strategy.ts +154 -154
- package/src/strategies/shadow-call.strategy.ts +87 -87
- 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
|
+
}
|