@node-idempotency/express
Version:
Express middleware to provide Race-Condition free idempotency for HTTP requests, preventing unintended duplicate operations.
101 lines • 3.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.idempotencyAsMiddleware = void 0;
const shared_1 = require("@node-idempotency/shared");
const core_1 = require("@node-idempotency/core");
const getIdempotencyInstance = async (options) => {
const storageAdapter = await (0, shared_1.buildStorageAdapter)(options.storage);
return new core_1.Idempotency(storageAdapter, options);
};
const setHeaders = (response, headers) => {
Object.keys(headers).forEach((key) => {
if (headers[key]) {
void response.header(key, headers[key]);
}
});
};
const handleResponse = async (idempotencyReq, nodeIdempotency, response, payload) => {
const { statusCode } = response;
const headers = response.getHeaders();
const additional = { statusCode };
Object.values(shared_1.headers2Cache).forEach((header) => {
const head = headers[header] ?? headers[header.toLowerCase()];
if (head) {
additional[header] = head;
}
});
try {
const isJson = (headers["content-type"] ?? headers["Content-Type"])
?.toString()
?.toLowerCase()
?.includes("application/json");
if (isJson && typeof payload === "string") {
payload = JSON.parse(payload);
}
}
catch {
}
const res = {
additional,
...(statusCode < 400 ? { body: payload } : { error: payload }),
};
await nodeIdempotency.onResponse(idempotencyReq, res);
};
const successHandler = (nodeIdempotency) => {
return async (request, response, next) => {
const idempotencyReq = {
headers: request.headers,
body: request.body,
path: request.url,
method: request.method,
};
try {
const idempotencyResponse = await nodeIdempotency.onRequest(idempotencyReq);
if (!idempotencyResponse) {
const originalSend = response.send.bind(response);
response.send = function (body) {
void handleResponse(idempotencyReq, nodeIdempotency, response, body);
return originalSend(body);
};
next();
return;
}
if (idempotencyResponse.additional?.statusCode) {
const { statusCode } = idempotencyResponse.additional;
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;
}, {});
setHeaders(response, {
...headers,
[shared_1.HTTPHeaderEnum.idempotentReplayed]: "true",
});
if (idempotencyResponse.body) {
response.send(idempotencyResponse.body);
}
else {
next(idempotencyResponse.error);
}
}
catch (err) {
if (err instanceof core_1.IdempotencyError) {
const status = shared_1.idempotency2HttpCodeMap[err.code] || 500;
if (err.code === core_1.IdempotencyErrorCodes.REQUEST_IN_PROGRESS) {
setHeaders(response, { [shared_1.HTTPHeaderEnum.retryAfter]: "1" });
}
void response.status(status);
next(err);
}
}
};
};
const idempotencyAsMiddleware = async (options) => {
const idempotency = await getIdempotencyInstance(options);
return [successHandler(idempotency)];
};
exports.idempotencyAsMiddleware = idempotencyAsMiddleware;
//# sourceMappingURL=middleware.js.map