@nest-yalc-2/api-strategy 1.3.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/LICENSE +661 -0
- package/README.md +40 -0
- package/package.json +33 -0
- package/src/__tests__/call-strategy-wrappers.spec.ts +217 -0
- package/src/__tests__/context-call.service.spec.ts +52 -0
- package/src/__tests__/context-event.service.spec.ts +52 -0
- package/src/__tests__/event-strategy-composition.spec.ts +185 -0
- package/src/__tests__/header-whitelist.helper.spec.ts +30 -0
- package/src/__tests__/http-abstract-call.strategy.spec.ts +17 -0
- package/src/__tests__/nest-http-call.strategy.spec.ts +164 -0
- package/src/__tests__/nest-local-call.strategy.spec.ts +263 -0
- package/src/__tests__/nest-local-event.strategy.spec.ts +55 -0
- package/src/__tests__/rabbitmq-event.strategy.spec.ts +220 -0
- package/src/__tests__/strategy-selector.provider.spec.ts +182 -0
- package/src/context-call.interface.d.ts +12 -0
- package/src/context-call.interface.js +3 -0
- package/src/context-call.interface.js.map +1 -0
- package/src/context-call.interface.ts +49 -0
- package/src/context-call.service.d.ts +7 -0
- package/src/context-call.service.js +32 -0
- package/src/context-call.service.js.map +1 -0
- package/src/context-call.service.ts +31 -0
- package/src/context-event.interface.d.ts +4 -0
- package/src/context-event.interface.js +3 -0
- package/src/context-event.interface.js.map +1 -0
- package/src/context-event.interface.ts +4 -0
- package/src/context-event.service.d.ts +7 -0
- package/src/context-event.service.js +32 -0
- package/src/context-event.service.js.map +1 -0
- package/src/context-event.service.ts +31 -0
- package/src/header-whitelist.helper.d.ts +2 -0
- package/src/header-whitelist.helper.js +13 -0
- package/src/header-whitelist.helper.js.map +1 -0
- package/src/header-whitelist.helper.ts +12 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +32 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +17 -0
- package/src/strategies/composite-event.strategy.d.ts +14 -0
- package/src/strategies/composite-event.strategy.js +52 -0
- package/src/strategies/composite-event.strategy.js.map +1 -0
- package/src/strategies/composite-event.strategy.ts +76 -0
- package/src/strategies/conditional-call.strategy.d.ts +17 -0
- package/src/strategies/conditional-call.strategy.js +41 -0
- package/src/strategies/conditional-call.strategy.js.map +1 -0
- package/src/strategies/conditional-call.strategy.ts +70 -0
- package/src/strategies/conditional-event.strategy.d.ts +16 -0
- package/src/strategies/conditional-event.strategy.js +38 -0
- package/src/strategies/conditional-event.strategy.js.map +1 -0
- package/src/strategies/conditional-event.strategy.ts +54 -0
- package/src/strategies/fallback-call.strategy.d.ts +14 -0
- package/src/strategies/fallback-call.strategy.js +41 -0
- package/src/strategies/fallback-call.strategy.js.map +1 -0
- package/src/strategies/fallback-call.strategy.ts +67 -0
- package/src/strategies/http-abstract-call.strategy.d.ts +44 -0
- package/src/strategies/http-abstract-call.strategy.js +19 -0
- package/src/strategies/http-abstract-call.strategy.js.map +1 -0
- package/src/strategies/http-abstract-call.strategy.ts +238 -0
- package/src/strategies/nest-http-call.strategy.d.ts +30 -0
- package/src/strategies/nest-http-call.strategy.js +68 -0
- package/src/strategies/nest-http-call.strategy.js.map +1 -0
- package/src/strategies/nest-http-call.strategy.ts +121 -0
- package/src/strategies/nest-local-call.strategy.d.ts +32 -0
- package/src/strategies/nest-local-call.strategy.js +84 -0
- package/src/strategies/nest-local-call.strategy.js.map +1 -0
- package/src/strategies/nest-local-call.strategy.ts +153 -0
- package/src/strategies/nest-local-event.strategy.d.ts +17 -0
- package/src/strategies/nest-local-event.strategy.js +30 -0
- package/src/strategies/nest-local-event.strategy.js.map +1 -0
- package/src/strategies/nest-local-event.strategy.ts +42 -0
- package/src/strategies/rabbitmq-event.strategy.d.ts +34 -0
- package/src/strategies/rabbitmq-event.strategy.js +97 -0
- package/src/strategies/rabbitmq-event.strategy.js.map +1 -0
- package/src/strategies/rabbitmq-event.strategy.ts +154 -0
- package/src/strategies/shadow-call.strategy.d.ts +17 -0
- package/src/strategies/shadow-call.strategy.js +42 -0
- package/src/strategies/shadow-call.strategy.js.map +1 -0
- package/src/strategies/shadow-call.strategy.ts +87 -0
- package/src/strategy-selector.provider.d.ts +19 -0
- package/src/strategy-selector.provider.js +52 -0
- package/src/strategy-selector.provider.js.map +1 -0
- package/src/strategy-selector.provider.ts +103 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { HttpAdapterHost } from '@nestjs/core';
|
|
2
|
+
import {
|
|
3
|
+
HttpAbstractStrategy,
|
|
4
|
+
HttpOptions,
|
|
5
|
+
IHttpCallStrategyOptions,
|
|
6
|
+
IHttpCallStrategyResponse,
|
|
7
|
+
} from './http-abstract-call.strategy.js';
|
|
8
|
+
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
|
9
|
+
import { ClassType } from '@nest-yalc-2/types/globals.d.js';
|
|
10
|
+
import { InjectOptions } from 'fastify';
|
|
11
|
+
import { YalcGlobalClsService } from '@nest-yalc-2/app/cls.module.js';
|
|
12
|
+
import { filterHeaders } from '../header-whitelist.helper.js';
|
|
13
|
+
import { AppConfigService } from '@nest-yalc-2/app/app-config.service.js';
|
|
14
|
+
import { MAIN_APP_CONFIG_SERVICE } from '@nest-yalc-2/app/def.const.js';
|
|
15
|
+
|
|
16
|
+
export type NestLocalCallStrategyOptions = IHttpCallStrategyOptions & {
|
|
17
|
+
internalRequestHeader?: string;
|
|
18
|
+
internalRequestToken?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class NestLocalCallStrategy extends HttpAbstractStrategy {
|
|
22
|
+
private readonly internalHeader: string;
|
|
23
|
+
private readonly internalToken?: string;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
protected readonly adapterHost: HttpAdapterHost,
|
|
27
|
+
protected readonly clsService: YalcGlobalClsService,
|
|
28
|
+
protected readonly configService: AppConfigService,
|
|
29
|
+
private baseUrl = '',
|
|
30
|
+
protected readonly options: NestLocalCallStrategyOptions = {},
|
|
31
|
+
) {
|
|
32
|
+
super();
|
|
33
|
+
this.internalHeader =
|
|
34
|
+
options.internalRequestHeader ?? 'x-internal-request-token';
|
|
35
|
+
this.internalToken =
|
|
36
|
+
options.internalRequestToken ??
|
|
37
|
+
(configService as any)?.values?.internalRequestToken;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async call<
|
|
41
|
+
TOptData extends string | object | Buffer | NodeJS.ReadableStream,
|
|
42
|
+
TParams extends Record<string, any>,
|
|
43
|
+
TResData,
|
|
44
|
+
>(
|
|
45
|
+
path: string,
|
|
46
|
+
options?: HttpOptions<TOptData, TParams>,
|
|
47
|
+
): Promise<IHttpCallStrategyResponse<TResData>> {
|
|
48
|
+
const instance: FastifyAdapter = this.adapterHost.httpAdapter.getInstance();
|
|
49
|
+
const clsHeaders = filterHeaders(
|
|
50
|
+
this.clsService.get('headers'),
|
|
51
|
+
this.options.headersWhitelist,
|
|
52
|
+
);
|
|
53
|
+
const headers: Record<string, any> = {
|
|
54
|
+
...clsHeaders,
|
|
55
|
+
...(options?.headers ?? {}),
|
|
56
|
+
};
|
|
57
|
+
if (
|
|
58
|
+
this.internalHeader &&
|
|
59
|
+
this.internalToken &&
|
|
60
|
+
!headers[this.internalHeader]
|
|
61
|
+
) {
|
|
62
|
+
headers[this.internalHeader] = this.internalToken;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* We need this to do a type check on the options and
|
|
66
|
+
* implement the mapping from HttpOptions to InjectOptions;
|
|
67
|
+
*/
|
|
68
|
+
const _options:
|
|
69
|
+
| {
|
|
70
|
+
[k: string | number | symbol]: never;
|
|
71
|
+
}
|
|
72
|
+
| InjectOptions = {
|
|
73
|
+
headers,
|
|
74
|
+
method: options?.method,
|
|
75
|
+
/**@todo investigate where to set thi */
|
|
76
|
+
// signal: options?.signal,
|
|
77
|
+
payload: options?.data, // map data to payload
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const args: InjectOptions = {
|
|
81
|
+
..._options,
|
|
82
|
+
url: `${this.baseUrl}${path}`,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (options?.parameters) {
|
|
86
|
+
args.query = Object.fromEntries(new URLSearchParams(options.parameters));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = await instance.inject(args);
|
|
90
|
+
|
|
91
|
+
let data;
|
|
92
|
+
try {
|
|
93
|
+
if (
|
|
94
|
+
this.options.shouldSkipJsonParse &&
|
|
95
|
+
this.options.shouldSkipJsonParse(result.body)
|
|
96
|
+
) {
|
|
97
|
+
data = result.body;
|
|
98
|
+
} else {
|
|
99
|
+
data = result.json();
|
|
100
|
+
}
|
|
101
|
+
} catch (_e) {
|
|
102
|
+
//The content-type of the response is not application/json
|
|
103
|
+
// if so, we use the body instead
|
|
104
|
+
data = result.body;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
data,
|
|
109
|
+
headers: result.headers,
|
|
110
|
+
status: result.statusCode,
|
|
111
|
+
statusText: result.statusMessage,
|
|
112
|
+
request: result.payload, // TODO: double check if it's the correct value
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface NestLocalCallStrategyProviderOptions {
|
|
118
|
+
baseUrl?: string;
|
|
119
|
+
NestLocalStrategy?: ClassType<NestLocalCallStrategy>;
|
|
120
|
+
headersWhitelist?: string[];
|
|
121
|
+
internalRequestHeader?: string;
|
|
122
|
+
internalRequestToken?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Just a convenient provider to inject the NestLocalCallStrategy
|
|
127
|
+
*/
|
|
128
|
+
export const NestLocalCallStrategyProvider = (
|
|
129
|
+
provide: string,
|
|
130
|
+
options: NestLocalCallStrategyProviderOptions = {},
|
|
131
|
+
) => ({
|
|
132
|
+
provide,
|
|
133
|
+
useFactory: (
|
|
134
|
+
httpAdapter: HttpAdapterHost,
|
|
135
|
+
clsService: YalcGlobalClsService,
|
|
136
|
+
configService: AppConfigService,
|
|
137
|
+
) => {
|
|
138
|
+
const _options = {
|
|
139
|
+
baseUrl: '',
|
|
140
|
+
NestLocalStrategy: NestLocalCallStrategy,
|
|
141
|
+
...options,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return new _options.NestLocalStrategy(
|
|
145
|
+
httpAdapter,
|
|
146
|
+
clsService,
|
|
147
|
+
configService,
|
|
148
|
+
_options.baseUrl,
|
|
149
|
+
_options,
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
inject: [HttpAdapterHost, YalcGlobalClsService, MAIN_APP_CONFIG_SERVICE],
|
|
153
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IEventStrategy } from '../context-event.interface.js';
|
|
2
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
|
+
import { ClassType } from '@nest-yalc-2/types/globals.d.js';
|
|
4
|
+
export declare class NestLocalEventStrategy<P = any, O = any> implements IEventStrategy {
|
|
5
|
+
private eventEmitter;
|
|
6
|
+
constructor(eventEmitter: EventEmitter2);
|
|
7
|
+
emit(path: string, payload: P, options?: O): boolean;
|
|
8
|
+
emitAsync(path: string, payload: P, options?: O): Promise<any>;
|
|
9
|
+
}
|
|
10
|
+
export interface NestLocalEventStrategyProviderOptions {
|
|
11
|
+
NestLocalStrategy?: ClassType<NestLocalEventStrategy>;
|
|
12
|
+
}
|
|
13
|
+
export declare const NestLocalEventStrategyProvider: (provide: string, options?: NestLocalEventStrategyProviderOptions) => {
|
|
14
|
+
provide: string;
|
|
15
|
+
useFactory: (eventEmitter: EventEmitter2) => NestLocalEventStrategy<any, any>;
|
|
16
|
+
inject: (typeof EventEmitter2)[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NestLocalEventStrategyProvider = exports.NestLocalEventStrategy = void 0;
|
|
4
|
+
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
5
|
+
class NestLocalEventStrategy {
|
|
6
|
+
constructor(eventEmitter) {
|
|
7
|
+
this.eventEmitter = eventEmitter;
|
|
8
|
+
}
|
|
9
|
+
emit(path, payload, options) {
|
|
10
|
+
return this.eventEmitter.emit(path, payload, options);
|
|
11
|
+
}
|
|
12
|
+
emitAsync(path, payload, options) {
|
|
13
|
+
return this.eventEmitter.emitAsync(path, payload, options);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.NestLocalEventStrategy = NestLocalEventStrategy;
|
|
17
|
+
const NestLocalEventStrategyProvider = (provide, options = {}) => ({
|
|
18
|
+
provide,
|
|
19
|
+
useFactory: (eventEmitter) => {
|
|
20
|
+
const _options = {
|
|
21
|
+
baseUrl: '',
|
|
22
|
+
NestLocalStrategy: NestLocalEventStrategy,
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
return new _options.NestLocalStrategy(eventEmitter);
|
|
26
|
+
},
|
|
27
|
+
inject: [event_emitter_1.EventEmitter2],
|
|
28
|
+
});
|
|
29
|
+
exports.NestLocalEventStrategyProvider = NestLocalEventStrategyProvider;
|
|
30
|
+
//# sourceMappingURL=nest-local-event.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest-local-event.strategy.js","sourceRoot":"","sources":["nest-local-event.strategy.ts"],"names":[],"mappings":";;;AACA,yDAAsD;AAGtD,MAAa,sBAAsB;IAIjC,YAAoB,YAA2B;QAA3B,iBAAY,GAAZ,YAAY,CAAe;IAAG,CAAC;IAEnD,IAAI,CAAC,IAAY,EAAE,OAAU,EAAE,OAAW;QACxC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,OAAU,EAAE,OAAW;QAC7C,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;CACF;AAbD,wDAaC;AASM,MAAM,8BAA8B,GAAG,CAC5C,OAAe,EACf,UAAiD,EAAE,EACnD,EAAE,CAAC,CAAC;IACJ,OAAO;IACP,UAAU,EAAE,CAAC,YAA2B,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG;YACf,OAAO,EAAE,EAAE;YACX,iBAAiB,EAAE,sBAAsB;YACzC,GAAG,OAAO;SACX,CAAC;QAEF,OAAO,IAAI,QAAQ,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,EAAE,CAAC,6BAAa,CAAC;CACxB,CAAC,CAAC;AAfU,QAAA,8BAA8B,kCAexC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { IEventStrategy } from '../context-event.interface.js';
|
|
2
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
|
+
import { ClassType } from '@nest-yalc-2/types/globals.d.js';
|
|
4
|
+
|
|
5
|
+
export class NestLocalEventStrategy<
|
|
6
|
+
P = any,
|
|
7
|
+
O = any,
|
|
8
|
+
> implements IEventStrategy {
|
|
9
|
+
constructor(private eventEmitter: EventEmitter2) {}
|
|
10
|
+
|
|
11
|
+
emit(path: string, payload: P, options?: O): boolean {
|
|
12
|
+
return this.eventEmitter.emit(path, payload, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
emitAsync(path: string, payload: P, options?: O): Promise<any> {
|
|
16
|
+
return this.eventEmitter.emitAsync(path, payload, options);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NestLocalEventStrategyProviderOptions {
|
|
21
|
+
NestLocalStrategy?: ClassType<NestLocalEventStrategy>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Just a convenient provider to inject the NestLocalCallStrategy
|
|
26
|
+
*/
|
|
27
|
+
export const NestLocalEventStrategyProvider = (
|
|
28
|
+
provide: string,
|
|
29
|
+
options: NestLocalEventStrategyProviderOptions = {},
|
|
30
|
+
) => ({
|
|
31
|
+
provide,
|
|
32
|
+
useFactory: (eventEmitter: EventEmitter2) => {
|
|
33
|
+
const _options = {
|
|
34
|
+
baseUrl: '',
|
|
35
|
+
NestLocalStrategy: NestLocalEventStrategy,
|
|
36
|
+
...options,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return new _options.NestLocalStrategy(eventEmitter);
|
|
40
|
+
},
|
|
41
|
+
inject: [EventEmitter2],
|
|
42
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { ClassType } from '@nest-yalc-2/types/globals.d.js';
|
|
3
|
+
import { Options } from 'amqplib';
|
|
4
|
+
import { IEventStrategy } from '../context-event.interface.js';
|
|
5
|
+
export interface RabbitMqEventStrategyOptions<P = any> {
|
|
6
|
+
url: string;
|
|
7
|
+
exchange: string;
|
|
8
|
+
exchangeType?: string;
|
|
9
|
+
durable?: boolean;
|
|
10
|
+
persistent?: boolean;
|
|
11
|
+
contentType?: string;
|
|
12
|
+
publishOptions?: Options.Publish;
|
|
13
|
+
serialize?: (payload: P) => Buffer | string;
|
|
14
|
+
}
|
|
15
|
+
export declare class RabbitMqEventStrategy<P = any, O = any> implements IEventStrategy<P, boolean, O>, OnModuleDestroy {
|
|
16
|
+
private readonly options;
|
|
17
|
+
private connection?;
|
|
18
|
+
private channel?;
|
|
19
|
+
constructor(options: RabbitMqEventStrategyOptions<P>);
|
|
20
|
+
emit(path: string, payload: P): boolean;
|
|
21
|
+
emitAsync(path: string, payload: P): Promise<boolean>;
|
|
22
|
+
onModuleDestroy(): Promise<void>;
|
|
23
|
+
private publish;
|
|
24
|
+
private serialize;
|
|
25
|
+
private getChannel;
|
|
26
|
+
}
|
|
27
|
+
export interface RabbitMqEventStrategyProviderOptions<P = any> {
|
|
28
|
+
RabbitMqStrategy?: ClassType<RabbitMqEventStrategy<P>>;
|
|
29
|
+
options: RabbitMqEventStrategyOptions<P> | (() => RabbitMqEventStrategyOptions<P>);
|
|
30
|
+
}
|
|
31
|
+
export declare const RabbitMqEventStrategyProvider: <P = any>(provide: string, options: RabbitMqEventStrategyProviderOptions<P>) => {
|
|
32
|
+
provide: string;
|
|
33
|
+
useFactory: () => RabbitMqEventStrategy<P, any> | RabbitMqEventStrategy<any, any>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RabbitMqEventStrategyProvider = exports.RabbitMqEventStrategy = void 0;
|
|
7
|
+
const amqplib_1 = __importDefault(require("amqplib"));
|
|
8
|
+
class RabbitMqEventStrategy {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
emit(path, payload) {
|
|
13
|
+
void this.publish(path, payload).catch(() => undefined);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
async emitAsync(path, payload) {
|
|
17
|
+
return this.publish(path, payload);
|
|
18
|
+
}
|
|
19
|
+
async onModuleDestroy() {
|
|
20
|
+
await closeRabbitResource(this.channel);
|
|
21
|
+
await closeRabbitResource(this.connection);
|
|
22
|
+
this.channel = undefined;
|
|
23
|
+
this.connection = undefined;
|
|
24
|
+
}
|
|
25
|
+
async publish(path, payload) {
|
|
26
|
+
const channel = await this.getChannel();
|
|
27
|
+
const body = this.serialize(payload);
|
|
28
|
+
return channel.publish(this.options.exchange, path, body, {
|
|
29
|
+
contentType: this.options.contentType ?? 'application/json',
|
|
30
|
+
persistent: this.options.persistent ?? true,
|
|
31
|
+
...this.options.publishOptions,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
serialize(payload) {
|
|
35
|
+
const serialized = this.options.serialize?.(payload);
|
|
36
|
+
if (Buffer.isBuffer(serialized)) {
|
|
37
|
+
return serialized;
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(serialized ?? JSON.stringify(payload));
|
|
40
|
+
}
|
|
41
|
+
async getChannel() {
|
|
42
|
+
if (this.channel) {
|
|
43
|
+
return this.channel;
|
|
44
|
+
}
|
|
45
|
+
this.connection = await amqplib_1.default.connect(this.options.url);
|
|
46
|
+
this.channel = await this.connection.createChannel();
|
|
47
|
+
await this.channel.assertExchange(this.options.exchange, this.options.exchangeType ?? 'topic', {
|
|
48
|
+
durable: this.options.durable ?? true,
|
|
49
|
+
});
|
|
50
|
+
return this.channel;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.RabbitMqEventStrategy = RabbitMqEventStrategy;
|
|
54
|
+
const RabbitMqEventStrategyProvider = (provide, options) => ({
|
|
55
|
+
provide,
|
|
56
|
+
useFactory: () => {
|
|
57
|
+
const Strategy = options.RabbitMqStrategy ?? RabbitMqEventStrategy;
|
|
58
|
+
const strategyOptions = typeof options.options === 'function'
|
|
59
|
+
? options.options()
|
|
60
|
+
: options.options;
|
|
61
|
+
return new Strategy(strategyOptions);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
exports.RabbitMqEventStrategyProvider = RabbitMqEventStrategyProvider;
|
|
65
|
+
async function closeRabbitResource(resource) {
|
|
66
|
+
if (!resource) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let timeout;
|
|
70
|
+
try {
|
|
71
|
+
await Promise.race([
|
|
72
|
+
resource.close(),
|
|
73
|
+
new Promise((resolve) => {
|
|
74
|
+
timeout = setTimeout(resolve, 1000);
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof Error) || !error.message.includes('closing')) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (timeout) {
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
}
|
|
87
|
+
forceCloseRabbitResource(resource);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function forceCloseRabbitResource(resource) {
|
|
91
|
+
const connection = resource.connection;
|
|
92
|
+
connection?.heartbeater?.clear();
|
|
93
|
+
resource.heartbeater?.clear();
|
|
94
|
+
connection?.stream?.destroy();
|
|
95
|
+
resource.stream?.destroy();
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=rabbitmq-event.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rabbitmq-event.strategy.js","sourceRoot":"","sources":["rabbitmq-event.strategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { ClassType } from '@nest-yalc-2/types/globals.d.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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IApiCallStrategy, ICallOptions, IObjectWithData } from '../context-call.interface.js';
|
|
2
|
+
export type ShadowCallStrategyErrorMode = 'throw' | 'ignore';
|
|
3
|
+
export interface ShadowCallStrategyOptions {
|
|
4
|
+
awaitShadows?: boolean;
|
|
5
|
+
shadowErrorMode?: ShadowCallStrategyErrorMode;
|
|
6
|
+
}
|
|
7
|
+
export declare class ShadowCallStrategy implements IApiCallStrategy {
|
|
8
|
+
private readonly primary;
|
|
9
|
+
private readonly shadows;
|
|
10
|
+
private readonly awaitShadows;
|
|
11
|
+
private readonly shadowErrorMode;
|
|
12
|
+
constructor(primary: IApiCallStrategy, shadows: IApiCallStrategy[], options?: ShadowCallStrategyOptions);
|
|
13
|
+
call<TOptData, TParams extends Record<string, any>, TResData>(path: string, options?: ICallOptions<TOptData, TParams>): Promise<IObjectWithData<TResData>>;
|
|
14
|
+
get<TOptData, TParams extends Record<string, any>, TResData>(path: string, options?: ICallOptions<TOptData, TParams>): Promise<IObjectWithData<TResData>>;
|
|
15
|
+
post<TOptData, TParams extends Record<string, any>, TResData>(path: string, options?: ICallOptions<TOptData, TParams>): Promise<IObjectWithData<TResData>>;
|
|
16
|
+
private execute;
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShadowCallStrategy = void 0;
|
|
4
|
+
class ShadowCallStrategy {
|
|
5
|
+
constructor(primary, shadows, options = {}) {
|
|
6
|
+
this.primary = primary;
|
|
7
|
+
this.shadows = shadows;
|
|
8
|
+
if (shadows.length === 0) {
|
|
9
|
+
throw new Error('ShadowCallStrategy requires at least one shadow strategy.');
|
|
10
|
+
}
|
|
11
|
+
this.shadowErrorMode = options.shadowErrorMode ?? 'ignore';
|
|
12
|
+
this.awaitShadows =
|
|
13
|
+
options.awaitShadows ?? this.shadowErrorMode === 'throw';
|
|
14
|
+
}
|
|
15
|
+
call(path, options) {
|
|
16
|
+
return this.execute((strategy) => strategy.call(path, options), (strategy) => strategy.call(path, options));
|
|
17
|
+
}
|
|
18
|
+
get(path, options) {
|
|
19
|
+
return this.execute((strategy) => strategy.get(path, options), (strategy) => strategy.get(path, options));
|
|
20
|
+
}
|
|
21
|
+
post(path, options) {
|
|
22
|
+
return this.execute((strategy) => strategy.post(path, options), (strategy) => strategy.post(path, options));
|
|
23
|
+
}
|
|
24
|
+
async execute(primaryCall, shadowCall) {
|
|
25
|
+
const primaryResult = await primaryCall(this.primary);
|
|
26
|
+
const shadowExecution = Promise.all(this.shadows.map((strategy) => shadowCall(strategy)));
|
|
27
|
+
if (this.awaitShadows) {
|
|
28
|
+
if (this.shadowErrorMode === 'throw') {
|
|
29
|
+
await shadowExecution;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await shadowExecution.catch(() => undefined);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
void shadowExecution.catch(() => undefined);
|
|
37
|
+
}
|
|
38
|
+
return primaryResult;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ShadowCallStrategy = ShadowCallStrategy;
|
|
42
|
+
//# sourceMappingURL=shadow-call.strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-call.strategy.js","sourceRoot":"","sources":["shadow-call.strategy.ts"],"names":[],"mappings":";;;AAaA,MAAa,kBAAkB;IAI7B,YACmB,OAAyB,EACzB,OAA2B,EAC5C,UAAqC,EAAE;QAFtB,YAAO,GAAP,OAAO,CAAkB;QACzB,YAAO,GAAP,OAAO,CAAoB;QAG5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,QAAQ,CAAC;QAC3D,IAAI,CAAC,YAAY;YACf,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,eAAe,KAAK,OAAO,CAAC;IAC7D,CAAC;IAED,IAAI,CACF,IAAY,EACZ,OAAyC;QAEzC,OAAO,IAAI,CAAC,OAAO,CACjB,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAC1C,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAC3C,CAAC;IACJ,CAAC;IAED,GAAG,CACD,IAAY,EACZ,OAAyC;QAEzC,OAAO,IAAI,CAAC,OAAO,CACjB,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,EACzC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAC1C,CAAC;IACJ,CAAC;IAED,IAAI,CACF,IAAY,EACZ,OAAyC;QAEzC,OAAO,IAAI,CAAC,OAAO,CACjB,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAC1C,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAC3C,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,WAEuC,EACvC,UAAyE;QAEzE,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CACjC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CACrD,CAAC;QAEF,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;gBACrC,MAAM,eAAe,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,eAAe,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,eAAe,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;CACF;AAzED,gDAyEC"}
|