UNPKG

try-catch-cloud

Version:

Advanced error logging for your app with low code integration

301 lines (295 loc) 8.23 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/hono/factory.ts var tryCatchFactory = /* @__PURE__ */ __name((factoryOptions) => { return (options) => { const context = factoryOptions.createContext(options); return factoryOptions.handler(context); }; }, "tryCatchFactory"); // src/helpers.ts function isAxiosError(e) { return e !== null && typeof e === "object" && e.isAxiosError === true; } __name(isAxiosError, "isAxiosError"); // src/core/types.ts var WrappedError = class extends Error { static { __name(this, "WrappedError"); } value; constructor(message, value) { super(message); this.value = value; } }; // src/core/baseUtility.ts var BaseErrorUtility = class { constructor(projectName, apiKey, debug) { this.projectName = projectName; this.apiKey = apiKey; this.debug = debug; if (!projectName.length) { throw new Error("Invalid project name"); } if (!apiKey.length) { throw new Error("Invalid API key"); } } static { __name(this, "BaseErrorUtility"); } async sendError(error, userContext) { try { const payload = await this.mapErrorRequestPayload( error, void 0, userContext ); return this.send(payload); } catch (err) { if (this.debug) { console.log(err); } } } /** * Sends error and request details to try-catch-cloud service. * You can manually map details from your route to request object. * * ``` * errorTrack.sendErrorFromEndpoint( * error, * { * url: req.originalUrl.split("?")[0], * method: req.method, * headers: req.headers, * query: req.query, * body, * }, * { ...context } * ); * ``` */ async sendErrorFromEndPoint(error, request, userContext) { try { const payload = await this.mapErrorRequestPayload( error, request, userContext ); return this.send(payload); } catch (err) { if (this.debug) { console.log(err); } } } async send(payload) { const result = await fetch("https://oleksiikilevoi.site/api/err-log/new", { method: "POST", body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } }); if (this.debug) { console.info("Send result", await result.text()); } const data = await result.json(); return data; } /** * It has to return Promise to be compatible with different implementations * e.g. Hono returns Promise when accessing req.json() */ async mapErrorRequestPayload(error, request, context) { if (!(error instanceof Error)) { return this.mapErrorRequestPayload( new WrappedError("An unexpected value was thrown", error), request, context ); } if (isAxiosError(error)) { return this.mapAxiosError(error, request, context); } const { stack, ...restErrorDetails } = error; return { ...request ? await this.mapRequest(request) : {}, errorName: error.name, errorDetails: this.sanitizeValue({ ...restErrorDetails, name: error.name, message: error.message }), stack: error.stack ?? null, context: this.sanitizeValue(context ?? null), user: this.apiKey, projectName: this.projectName }; } mapAxiosRequestBody(error) { try { const config = error.config; const headers = config?.headers ?? {}; const contentType = headers["content-type"] ?? headers["Content-Type"] ?? ""; if (!contentType.length) { return null; } if (contentType.startsWith("multipart/form-data")) { return "#form-data"; } if (contentType === "application/json") { return JSON.parse(config?.data); } if (contentType.match(/^text\/.+$/)) { return config?.data; } if (contentType === "application/x-www-form-urlencoded") { return new URLSearchParams(config?.data).toString(); } if (contentType === "application/xml") { return decodeURIComponent(config?.data); } if (contentType === "application/octet-stream") { return "#binary-data"; } return "#tcc/mapping-failed"; } catch (e) { if (this.debug) { console.error(e); } return "#tcc/mapping-failed"; } } async mapAxiosError(error, request, context) { const { config, response } = error; const requestBody = this.mapAxiosRequestBody(error); const responseBody = response?.data; const rawUrl = config?.url ?? ""; const query = Object.fromEntries([ ...URL.canParse(rawUrl) ? new URL(rawUrl).searchParams.entries() : [] ]); const axiosDetails = { url: config?.url ?? null, query: Object.keys(query).length ? query : null, params: config?.params ?? null, method: config?.method?.toUpperCase() || null, statusCode: response?.status ?? 0, requestBody, responseBody: responseBody ?? null, headers: config?.headers ?? {} }; return { ...request ? await this.mapRequest(request) : {}, errorName: error.constructor.name ?? error.name, errorDetails: this.sanitizeValue({ ...axiosDetails, name: error.name, message: error.message }), stack: error.stack ?? null, context: context ?? null, user: this.apiKey, projectName: this.projectName }; } sanitizeValue(value, level = 1) { if (typeof value === "object" && value !== null && !Array.isArray(value)) { const result = {}; for (const [key, objValue] of Object.entries(value)) { result[key] = this.sanitizeValue(objValue, level + 1); } return result; } if (typeof value === "object" && Array.isArray(value)) { return value.map((item) => this.sanitizeValue(item, level + 1)); } if (typeof value === "function") { return void 0; } return value; } }; // src/hono/utility.ts import { URL as URL2 } from "url"; var HonoErrorUtility = class extends BaseErrorUtility { static { __name(this, "HonoErrorUtility"); } async mapRequest(req) { const parsedUrl = new URL2(req.url); const [url] = parsedUrl.pathname.split("?"); let requestBody; try { requestBody = await req.json(); } catch (e) { requestBody = null; } const query = req.query(); const result = { method: req.method.toUpperCase(), url, requestBody, headers: req.header(), query: Object.keys(query).length ? query : null }; return result; } /** * Sends error and Hono request details to try-catch-cloud service. * ``` * app.onError(async (err, ctx) => { * errorUtility.sendErrorFromEndPoint(err, ctx.req, { ...errorContext }) * .catch((e) => { ... }); * * return ctx.json({ message: 'Internal server error' }, 500); * }); * ``` */ async sendErrorFromEndPoint(error, request, userContext) { return super.sendErrorFromEndPoint(error, request, userContext); } }; // src/hono/index.ts var tryCatch = tryCatchFactory({ createContext: (options) => ({ instance: new HonoErrorUtility( options.projectName, options.apiKey, options.debug ) }), handler: ({ instance }) => { return async (ctx, next) => { let errorContext = {}; ctx.set("tryCatch", { addContext: (errCtx) => { errorContext = { ...errorContext, ...errCtx }; } }); await next(); if (ctx.error) { const error = ctx.error; if (error instanceof Error) { const ctrName = error.constructor.name; if (ctrName === "HTTPException") { return; } } instance.sendErrorFromEndPoint(ctx.error, ctx.req, errorContext).then(() => { }).catch((e) => { }); } }; } }); export { HonoErrorUtility, tryCatch }; //# sourceMappingURL=index.mjs.map