import { type IEither, isEither, type ITraceable, Metric, type PenguenoRequest, type ServerTrace, } from '@emprespresso/pengueno'; export type BodyInit = | ArrayBuffer | AsyncIterable | Blob | FormData | Iterable | NodeJS.ArrayBufferView | URLSearchParams | null | string; export type ResponseBody = object | string; export type TResponseInit = Omit & { status: number; headers?: Record; }; const getResponse = (req: PenguenoRequest, opts: TResponseInit): ResponseInit => { const baseHeaders = req.baseResponseHeaders(); const optHeaders = opts.headers || {}; return { ...opts, headers: { ...baseHeaders, ...optHeaders, 'Content-Type': (optHeaders['Content-Type'] ?? 'text/plain') + '; charset=utf-8', } as Record, }; }; const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`)); export const getResponseMetrics = (status: number) => { const index = Math.floor(status / 100); return ResponseCodeMetrics.map((metric, i) => metric.count.withValue(i === index ? 1.0 : 0.0)); }; export class PenguenoResponse extends Response { constructor(req: ITraceable, msg: BodyInit, opts: TResponseInit) { const responseOpts = getResponse(req.get(), opts); for (const metric of getResponseMetrics(opts.status)) { req.trace.trace(metric); } super(msg, responseOpts); } } export class JsonResponse extends PenguenoResponse { constructor( req: ITraceable, e: BodyInit | IEither, opts: TResponseInit, ) { const optsWithJsonContentType: TResponseInit = { ...opts, headers: { ...opts.headers, 'Content-Type': 'application/json', }, }; if (isEither(e)) { super( req, JSON.stringify(e.fold(({ isLeft, value }) => (isLeft ? { error: value } : { ok: value }))), optsWithJsonContentType, ); return; } super( req, JSON.stringify(Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }), optsWithJsonContentType, ); } }