@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.
- package/package.json +10 -2
- 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,76 +1,76 @@
|
|
|
1
|
-
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import type { IEventStrategy } from '../context-event.interface.js';
|
|
3
|
-
|
|
4
|
-
export type CompositeEventStrategyErrorMode = 'throw' | 'ignore';
|
|
5
|
-
|
|
6
|
-
export interface CompositeEventStrategyOptions {
|
|
7
|
-
errorMode?: CompositeEventStrategyErrorMode;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class CompositeEventStrategy<P = any, R = any, O = any>
|
|
11
|
-
implements IEventStrategy<P, R[], O>, OnModuleDestroy
|
|
12
|
-
{
|
|
13
|
-
private readonly errorMode: CompositeEventStrategyErrorMode;
|
|
14
|
-
|
|
15
|
-
constructor(
|
|
16
|
-
private readonly strategies: IEventStrategy<P, R, O>[],
|
|
17
|
-
options: CompositeEventStrategyOptions = {},
|
|
18
|
-
) {
|
|
19
|
-
if (strategies.length === 0) {
|
|
20
|
-
throw new Error('CompositeEventStrategy requires at least one strategy.');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
this.errorMode = options.errorMode ?? 'throw';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
emit(path: string, payload: P, options?: O): R[] {
|
|
27
|
-
const results: R[] = [];
|
|
28
|
-
|
|
29
|
-
for (const strategy of this.strategies) {
|
|
30
|
-
try {
|
|
31
|
-
results.push(strategy.emit(path, payload, options));
|
|
32
|
-
} catch (error) {
|
|
33
|
-
if (this.errorMode === 'throw') {
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return results;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async emitAsync(path: string, payload: P, options?: O): Promise<R[]> {
|
|
43
|
-
const settled = await Promise.allSettled(
|
|
44
|
-
this.strategies.map((strategy) =>
|
|
45
|
-
strategy.emitAsync(path, payload, options),
|
|
46
|
-
),
|
|
47
|
-
);
|
|
48
|
-
const results: R[] = [];
|
|
49
|
-
|
|
50
|
-
for (const result of settled) {
|
|
51
|
-
if (result.status === 'fulfilled') {
|
|
52
|
-
results.push(result.value);
|
|
53
|
-
} else if (this.errorMode === 'throw') {
|
|
54
|
-
throw result.reason;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return results;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async onModuleDestroy() {
|
|
62
|
-
await Promise.all(
|
|
63
|
-
this.strategies.map(async (strategy) => {
|
|
64
|
-
await maybeDestroyStrategy(strategy);
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function maybeDestroyStrategy(strategy: unknown) {
|
|
71
|
-
const destroyable = strategy as Partial<OnModuleDestroy>;
|
|
72
|
-
|
|
73
|
-
if (typeof destroyable.onModuleDestroy === 'function') {
|
|
74
|
-
await destroyable.onModuleDestroy();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { IEventStrategy } from '../context-event.interface.js';
|
|
3
|
+
|
|
4
|
+
export type CompositeEventStrategyErrorMode = 'throw' | 'ignore';
|
|
5
|
+
|
|
6
|
+
export interface CompositeEventStrategyOptions {
|
|
7
|
+
errorMode?: CompositeEventStrategyErrorMode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CompositeEventStrategy<P = any, R = any, O = any>
|
|
11
|
+
implements IEventStrategy<P, R[], O>, OnModuleDestroy
|
|
12
|
+
{
|
|
13
|
+
private readonly errorMode: CompositeEventStrategyErrorMode;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly strategies: IEventStrategy<P, R, O>[],
|
|
17
|
+
options: CompositeEventStrategyOptions = {},
|
|
18
|
+
) {
|
|
19
|
+
if (strategies.length === 0) {
|
|
20
|
+
throw new Error('CompositeEventStrategy requires at least one strategy.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.errorMode = options.errorMode ?? 'throw';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
emit(path: string, payload: P, options?: O): R[] {
|
|
27
|
+
const results: R[] = [];
|
|
28
|
+
|
|
29
|
+
for (const strategy of this.strategies) {
|
|
30
|
+
try {
|
|
31
|
+
results.push(strategy.emit(path, payload, options));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (this.errorMode === 'throw') {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async emitAsync(path: string, payload: P, options?: O): Promise<R[]> {
|
|
43
|
+
const settled = await Promise.allSettled(
|
|
44
|
+
this.strategies.map((strategy) =>
|
|
45
|
+
strategy.emitAsync(path, payload, options),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
const results: R[] = [];
|
|
49
|
+
|
|
50
|
+
for (const result of settled) {
|
|
51
|
+
if (result.status === 'fulfilled') {
|
|
52
|
+
results.push(result.value);
|
|
53
|
+
} else if (this.errorMode === 'throw') {
|
|
54
|
+
throw result.reason;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async onModuleDestroy() {
|
|
62
|
+
await Promise.all(
|
|
63
|
+
this.strategies.map(async (strategy) => {
|
|
64
|
+
await maybeDestroyStrategy(strategy);
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function maybeDestroyStrategy(strategy: unknown) {
|
|
71
|
+
const destroyable = strategy as Partial<OnModuleDestroy>;
|
|
72
|
+
|
|
73
|
+
if (typeof destroyable.onModuleDestroy === 'function') {
|
|
74
|
+
await destroyable.onModuleDestroy();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IApiCallStrategy,
|
|
3
|
-
ICallOptions,
|
|
4
|
-
IObjectWithData,
|
|
5
|
-
} from '../context-call.interface.js';
|
|
6
|
-
|
|
7
|
-
export interface ConditionalCallStrategyOptions {
|
|
8
|
-
enabled?: boolean | (() => boolean);
|
|
9
|
-
disabledResponse?: IObjectWithData<any>;
|
|
10
|
-
disabledError?: Error | (() => Error);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ConditionalCallStrategy implements IApiCallStrategy {
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly strategy: IApiCallStrategy,
|
|
16
|
-
private readonly options: ConditionalCallStrategyOptions = {},
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
call<TOptData, TParams extends Record<string, any>, TResData>(
|
|
20
|
-
path: string,
|
|
21
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
22
|
-
): Promise<IObjectWithData<TResData>> {
|
|
23
|
-
return this.whenEnabled(() => this.strategy.call(path, options));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
get<TOptData, TParams extends Record<string, any>, TResData>(
|
|
27
|
-
path: string,
|
|
28
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
29
|
-
): Promise<IObjectWithData<TResData>> {
|
|
30
|
-
return this.whenEnabled(() => this.strategy.get(path, options));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
post<TOptData, TParams extends Record<string, any>, TResData>(
|
|
34
|
-
path: string,
|
|
35
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
36
|
-
): Promise<IObjectWithData<TResData>> {
|
|
37
|
-
return this.whenEnabled(() => this.strategy.post(path, options));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private async whenEnabled<TResData>(
|
|
41
|
-
execute: () => Promise<IObjectWithData<TResData>>,
|
|
42
|
-
) {
|
|
43
|
-
if (this.isEnabled()) {
|
|
44
|
-
return execute();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (this.options.disabledResponse) {
|
|
48
|
-
return this.options.disabledResponse as IObjectWithData<TResData>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
throw this.resolveDisabledError();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private isEnabled() {
|
|
55
|
-
return typeof this.options.enabled === 'function'
|
|
56
|
-
? this.options.enabled()
|
|
57
|
-
: this.options.enabled !== false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private resolveDisabledError() {
|
|
61
|
-
if (typeof this.options.disabledError === 'function') {
|
|
62
|
-
return this.options.disabledError();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
this.options.disabledError ??
|
|
67
|
-
new Error('ConditionalCallStrategy is disabled.')
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
IApiCallStrategy,
|
|
3
|
+
ICallOptions,
|
|
4
|
+
IObjectWithData,
|
|
5
|
+
} from '../context-call.interface.js';
|
|
6
|
+
|
|
7
|
+
export interface ConditionalCallStrategyOptions {
|
|
8
|
+
enabled?: boolean | (() => boolean);
|
|
9
|
+
disabledResponse?: IObjectWithData<any>;
|
|
10
|
+
disabledError?: Error | (() => Error);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ConditionalCallStrategy implements IApiCallStrategy {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly strategy: IApiCallStrategy,
|
|
16
|
+
private readonly options: ConditionalCallStrategyOptions = {},
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
call<TOptData, TParams extends Record<string, any>, TResData>(
|
|
20
|
+
path: string,
|
|
21
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
22
|
+
): Promise<IObjectWithData<TResData>> {
|
|
23
|
+
return this.whenEnabled(() => this.strategy.call(path, options));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get<TOptData, TParams extends Record<string, any>, TResData>(
|
|
27
|
+
path: string,
|
|
28
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
29
|
+
): Promise<IObjectWithData<TResData>> {
|
|
30
|
+
return this.whenEnabled(() => this.strategy.get(path, options));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
post<TOptData, TParams extends Record<string, any>, TResData>(
|
|
34
|
+
path: string,
|
|
35
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
36
|
+
): Promise<IObjectWithData<TResData>> {
|
|
37
|
+
return this.whenEnabled(() => this.strategy.post(path, options));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async whenEnabled<TResData>(
|
|
41
|
+
execute: () => Promise<IObjectWithData<TResData>>,
|
|
42
|
+
) {
|
|
43
|
+
if (this.isEnabled()) {
|
|
44
|
+
return execute();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.options.disabledResponse) {
|
|
48
|
+
return this.options.disabledResponse as IObjectWithData<TResData>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
throw this.resolveDisabledError();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private isEnabled() {
|
|
55
|
+
return typeof this.options.enabled === 'function'
|
|
56
|
+
? this.options.enabled()
|
|
57
|
+
: this.options.enabled !== false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private resolveDisabledError() {
|
|
61
|
+
if (typeof this.options.disabledError === 'function') {
|
|
62
|
+
return this.options.disabledError();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
this.options.disabledError ??
|
|
67
|
+
new Error('ConditionalCallStrategy is disabled.')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import type { IEventStrategy } from '../context-event.interface.js';
|
|
3
|
-
|
|
4
|
-
export interface ConditionalEventStrategyOptions<P = any, R = any, O = any> {
|
|
5
|
-
enabled?: boolean | (() => boolean);
|
|
6
|
-
shouldEmit?: (path: string, payload: P, options?: O) => boolean;
|
|
7
|
-
disabledResult?: R;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class ConditionalEventStrategy<P = any, R = any, O = any>
|
|
11
|
-
implements IEventStrategy<P, R, O>, OnModuleDestroy
|
|
12
|
-
{
|
|
13
|
-
constructor(
|
|
14
|
-
private readonly strategy: IEventStrategy<P, R, O>,
|
|
15
|
-
private readonly options: ConditionalEventStrategyOptions<P, R, O> = {},
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
emit(path: string, payload: P, options?: O): R {
|
|
19
|
-
if (!this.shouldRun(path, payload, options)) {
|
|
20
|
-
return this.options.disabledResult as R;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return this.strategy.emit(path, payload, options);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async emitAsync(path: string, payload: P, options?: O): Promise<R> {
|
|
27
|
-
if (!this.shouldRun(path, payload, options)) {
|
|
28
|
-
return this.options.disabledResult as R;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return this.strategy.emitAsync(path, payload, options);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async onModuleDestroy() {
|
|
35
|
-
const destroyable = this.strategy as Partial<OnModuleDestroy>;
|
|
36
|
-
|
|
37
|
-
if (typeof destroyable.onModuleDestroy === 'function') {
|
|
38
|
-
await destroyable.onModuleDestroy();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private shouldRun(path: string, payload: P, options?: O) {
|
|
43
|
-
const enabled =
|
|
44
|
-
typeof this.options.enabled === 'function'
|
|
45
|
-
? this.options.enabled()
|
|
46
|
-
: this.options.enabled;
|
|
47
|
-
|
|
48
|
-
if (enabled === false) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return this.options.shouldEmit?.(path, payload, options) ?? true;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { IEventStrategy } from '../context-event.interface.js';
|
|
3
|
+
|
|
4
|
+
export interface ConditionalEventStrategyOptions<P = any, R = any, O = any> {
|
|
5
|
+
enabled?: boolean | (() => boolean);
|
|
6
|
+
shouldEmit?: (path: string, payload: P, options?: O) => boolean;
|
|
7
|
+
disabledResult?: R;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ConditionalEventStrategy<P = any, R = any, O = any>
|
|
11
|
+
implements IEventStrategy<P, R, O>, OnModuleDestroy
|
|
12
|
+
{
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly strategy: IEventStrategy<P, R, O>,
|
|
15
|
+
private readonly options: ConditionalEventStrategyOptions<P, R, O> = {},
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
emit(path: string, payload: P, options?: O): R {
|
|
19
|
+
if (!this.shouldRun(path, payload, options)) {
|
|
20
|
+
return this.options.disabledResult as R;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return this.strategy.emit(path, payload, options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async emitAsync(path: string, payload: P, options?: O): Promise<R> {
|
|
27
|
+
if (!this.shouldRun(path, payload, options)) {
|
|
28
|
+
return this.options.disabledResult as R;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.strategy.emitAsync(path, payload, options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async onModuleDestroy() {
|
|
35
|
+
const destroyable = this.strategy as Partial<OnModuleDestroy>;
|
|
36
|
+
|
|
37
|
+
if (typeof destroyable.onModuleDestroy === 'function') {
|
|
38
|
+
await destroyable.onModuleDestroy();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private shouldRun(path: string, payload: P, options?: O) {
|
|
43
|
+
const enabled =
|
|
44
|
+
typeof this.options.enabled === 'function'
|
|
45
|
+
? this.options.enabled()
|
|
46
|
+
: this.options.enabled;
|
|
47
|
+
|
|
48
|
+
if (enabled === false) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.options.shouldEmit?.(path, payload, options) ?? true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IApiCallStrategy,
|
|
3
|
-
ICallOptions,
|
|
4
|
-
IObjectWithData,
|
|
5
|
-
} from '../context-call.interface.js';
|
|
6
|
-
|
|
7
|
-
export interface FallbackCallStrategyOptions {
|
|
8
|
-
shouldFallback?: (error: unknown, strategyIndex: number) => boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class FallbackCallStrategy implements IApiCallStrategy {
|
|
12
|
-
constructor(
|
|
13
|
-
private readonly strategies: IApiCallStrategy[],
|
|
14
|
-
private readonly options: FallbackCallStrategyOptions = {},
|
|
15
|
-
) {
|
|
16
|
-
if (strategies.length === 0) {
|
|
17
|
-
throw new Error('FallbackCallStrategy requires at least one strategy.');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
call<TOptData, TParams extends Record<string, any>, TResData>(
|
|
22
|
-
path: string,
|
|
23
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
24
|
-
): Promise<IObjectWithData<TResData>> {
|
|
25
|
-
return this.execute((strategy) => strategy.call(path, options));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get<TOptData, TParams extends Record<string, any>, TResData>(
|
|
29
|
-
path: string,
|
|
30
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
31
|
-
): Promise<IObjectWithData<TResData>> {
|
|
32
|
-
return this.execute((strategy) => strategy.get(path, options));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
post<TOptData, TParams extends Record<string, any>, TResData>(
|
|
36
|
-
path: string,
|
|
37
|
-
options?: ICallOptions<TOptData, TParams>,
|
|
38
|
-
): Promise<IObjectWithData<TResData>> {
|
|
39
|
-
return this.execute((strategy) => strategy.post(path, options));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private async execute<TResData>(
|
|
43
|
-
callStrategy: (
|
|
44
|
-
strategy: IApiCallStrategy,
|
|
45
|
-
) => Promise<IObjectWithData<TResData>>,
|
|
46
|
-
) {
|
|
47
|
-
let lastError: unknown;
|
|
48
|
-
|
|
49
|
-
for (const [index, strategy] of this.strategies.entries()) {
|
|
50
|
-
try {
|
|
51
|
-
return await callStrategy(strategy);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
lastError = error;
|
|
54
|
-
|
|
55
|
-
if (!this.shouldFallback(error, index)) {
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
throw lastError;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private shouldFallback(error: unknown, strategyIndex: number) {
|
|
65
|
-
return this.options.shouldFallback?.(error, strategyIndex) ?? true;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
IApiCallStrategy,
|
|
3
|
+
ICallOptions,
|
|
4
|
+
IObjectWithData,
|
|
5
|
+
} from '../context-call.interface.js';
|
|
6
|
+
|
|
7
|
+
export interface FallbackCallStrategyOptions {
|
|
8
|
+
shouldFallback?: (error: unknown, strategyIndex: number) => boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class FallbackCallStrategy implements IApiCallStrategy {
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly strategies: IApiCallStrategy[],
|
|
14
|
+
private readonly options: FallbackCallStrategyOptions = {},
|
|
15
|
+
) {
|
|
16
|
+
if (strategies.length === 0) {
|
|
17
|
+
throw new Error('FallbackCallStrategy requires at least one strategy.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
call<TOptData, TParams extends Record<string, any>, TResData>(
|
|
22
|
+
path: string,
|
|
23
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
24
|
+
): Promise<IObjectWithData<TResData>> {
|
|
25
|
+
return this.execute((strategy) => strategy.call(path, options));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get<TOptData, TParams extends Record<string, any>, TResData>(
|
|
29
|
+
path: string,
|
|
30
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
31
|
+
): Promise<IObjectWithData<TResData>> {
|
|
32
|
+
return this.execute((strategy) => strategy.get(path, options));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
post<TOptData, TParams extends Record<string, any>, TResData>(
|
|
36
|
+
path: string,
|
|
37
|
+
options?: ICallOptions<TOptData, TParams>,
|
|
38
|
+
): Promise<IObjectWithData<TResData>> {
|
|
39
|
+
return this.execute((strategy) => strategy.post(path, options));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async execute<TResData>(
|
|
43
|
+
callStrategy: (
|
|
44
|
+
strategy: IApiCallStrategy,
|
|
45
|
+
) => Promise<IObjectWithData<TResData>>,
|
|
46
|
+
) {
|
|
47
|
+
let lastError: unknown;
|
|
48
|
+
|
|
49
|
+
for (const [index, strategy] of this.strategies.entries()) {
|
|
50
|
+
try {
|
|
51
|
+
return await callStrategy(strategy);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
lastError = error;
|
|
54
|
+
|
|
55
|
+
if (!this.shouldFallback(error, index)) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw lastError;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private shouldFallback(error: unknown, strategyIndex: number) {
|
|
65
|
+
return this.options.shouldFallback?.(error, strategyIndex) ?? true;
|
|
66
|
+
}
|
|
67
|
+
}
|