UNPKG

@node-idempotency/nestjs

Version:

Nestjs Plugin that provides Race-Condition free idempotency for HTTP requests, preventing unintended duplicate operations.

148 lines 6.79 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeIdempotencyInterceptor = void 0; const common_1 = require("@nestjs/common"); const core_1 = require("@node-idempotency/core"); const shared_1 = require("@node-idempotency/shared"); const rxjs_1 = require("rxjs"); const core_2 = require("@nestjs/core"); const constants_1 = require("../constants"); const stream_1 = require("stream"); let NodeIdempotencyInterceptor = class NodeIdempotencyInterceptor { constructor(reflector, stoarge, optons) { this.reflector = reflector; this.stoarge = stoarge; this.nodeIdempotency = new core_1.Idempotency(this.stoarge, optons); } async intercept(context, next) { const request = context .switchToHttp() .getRequest(); const response = context .switchToHttp() .getResponse(); const options = this.reflector.get(constants_1.IDEMPOTENCY_OPTIONS, context.getHandler()) ?? this.reflector.get(constants_1.IDEMPOTENCY_OPTIONS, context.getClass()); const idempotencyReq = { headers: request.headers, body: request.body, path: request.url, method: request.method, options, }; try { const idempotencyResponse = await this.nodeIdempotency.onRequest(idempotencyReq); if (!idempotencyResponse) { return await this.handleNewRequest(idempotencyReq, context, next); } if (idempotencyResponse.additional?.statusCode) { const { statusCode } = idempotencyResponse.additional; if ("code" in response && typeof response.code === "function") { void response.code(statusCode); } else { void response.status(statusCode); } } const headers = Object.values(shared_1.headers2Cache).reduce((res, cur) => { if (idempotencyResponse?.additional?.[cur]) { res[cur] = idempotencyResponse.additional[cur]; } return res; }, {}); this.setHeaders(response, { ...headers, [shared_1.HTTPHeaderEnum.idempotentReplayed]: "true", }); if (typeof idempotencyResponse.body !== "undefined") { return (0, rxjs_1.of)(idempotencyResponse.body); } throw this.buildError(idempotencyResponse.error); } catch (err) { if (err instanceof core_1.IdempotencyError) { const status = shared_1.idempotency2HttpCodeMap[err.code] || common_1.HttpStatus.INTERNAL_SERVER_ERROR; if (err.code === core_1.IdempotencyErrorCodes.REQUEST_IN_PROGRESS) { this.setHeaders(response, { [shared_1.HTTPHeaderEnum.retryAfter]: "1" }); } return (0, rxjs_1.throwError)(() => new common_1.HttpException(err.message, status)); } if (err instanceof common_1.HttpException) { return (0, rxjs_1.throwError)(() => err); } } return (0, rxjs_1.throwError)(() => new common_1.InternalServerErrorException()); } buildError(error) { const statusCode = error.status ?? error.response?.statusCode ?? 500; if (statusCode === 500 && !error.response) { return new common_1.InternalServerErrorException(); } return new common_1.HttpException(error.response ?? error.message, statusCode, error.options); } setHeaders(response, headers) { Object.keys(headers).forEach((key) => { if (headers[key]) { if ("set" in response && typeof response.set === "function") { void response.set(key, headers[key]); } else { void response.header(key, headers[key]); } } }); } async handleNewRequest(idempotencyReq, context, next) { return next.handle().pipe((0, rxjs_1.map)(async (response) => { if (response instanceof stream_1.Stream) { return response; } const httpResponse = context .switchToHttp() .getResponse(); const statusCode = httpResponse.statusCode; const additional = { statusCode }; Object.values(shared_1.headers2Cache).forEach((header) => { const head = httpResponse.getHeader(header); if (head) { additional[header] = head; } }); const res = { additional, body: response, }; await this.nodeIdempotency.onResponse(idempotencyReq, res); return response; }), (0, rxjs_1.catchError)((err) => { const httpException = this.buildError(err); const error = err instanceof common_1.HttpException ? err : httpException; const res = { additional: { statusCode: httpException.getStatus() }, error, }; this.nodeIdempotency.onResponse(idempotencyReq, res).catch(() => { }); throw err; })); } }; exports.NodeIdempotencyInterceptor = NodeIdempotencyInterceptor; exports.NodeIdempotencyInterceptor = NodeIdempotencyInterceptor = __decorate([ (0, common_1.Injectable)(), __param(1, (0, common_1.Inject)(constants_1.IDEMPOTENCY_STORAGE)), __param(2, (0, common_1.Inject)(constants_1.IDEMPOTENCY_OPTIONS)), __metadata("design:paramtypes", [core_2.Reflector, Object, Object]) ], NodeIdempotencyInterceptor); //# sourceMappingURL=node-idempotency.iterceptor.js.map