@node-idempotency/nestjs
Version:
Nestjs Plugin that provides Race-Condition free idempotency for HTTP requests, preventing unintended duplicate operations.
148 lines • 6.79 kB
JavaScript
;
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