@apisr/response 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@apisr/response",
3
+ "version": "0.0.1",
4
+ "module": "src/index.ts",
5
+ "devDependencies": {
6
+ "@repo/eslint-config": "*",
7
+ "@repo/typescript-config": "*",
8
+ "@types/bun": "latest",
9
+ "@types/node": "^22.15.3",
10
+ "dotenv": "^17.2.3",
11
+ "eslint": "^9.39.1",
12
+ "typescript": "5.9.2"
13
+ },
14
+ "dependencies": {
15
+ "@apisr/schema": "",
16
+ "@apisr/zod": ""
17
+ },
18
+ "private": false,
19
+ "scripts": {
20
+ "lint": "eslint . --max-warnings 0",
21
+ "check-types": "tsc --noEmit",
22
+ "test": "bun test"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.mts",
27
+ "default": "./dist/index.mjs"
28
+ }
29
+ },
30
+ "type": "module"
31
+ }
@@ -0,0 +1,122 @@
1
+ import { z } from "@apisr/zod";
2
+ import type { ErrorOptions, Options } from "@/options";
3
+ import type { ErrorRegistry } from ".";
4
+
5
+ export function generateDefaultErrors<TOptions extends Options>(
6
+ _mapDefaultError: ErrorOptions.Base["mapDefaultError"]
7
+ ): ErrorRegistry<TOptions> {
8
+ const mapDefaultError = _mapDefaultError ?? ((err) => err);
9
+
10
+ const inputSchema = z
11
+ .object({
12
+ cause: z.any().optional(),
13
+ })
14
+ .optional();
15
+
16
+ return {
17
+ unauthorized: {
18
+ handler: ({ input }) =>
19
+ mapDefaultError({
20
+ message: "Unauthorized",
21
+ code: "UNAUTHORIZED",
22
+ name: "UnauthorizedError",
23
+ cause: input.cause,
24
+ }),
25
+ options: {
26
+ input: inputSchema,
27
+ status: 401,
28
+ statusText: "Unauthorized",
29
+ },
30
+ },
31
+
32
+ forbidden: {
33
+ handler: ({ input }) =>
34
+ mapDefaultError({
35
+ message: "Forbidden",
36
+ code: "FORBIDDEN",
37
+ name: "ForbiddenError",
38
+ cause: input.cause,
39
+ }),
40
+ options: {
41
+ input: inputSchema,
42
+ status: 403,
43
+ statusText: "Forbidden",
44
+ },
45
+ },
46
+
47
+ notFound: {
48
+ handler: ({ input }) =>
49
+ mapDefaultError({
50
+ message: "Not Found",
51
+ code: "NOT_FOUND",
52
+ name: "NotFoundError",
53
+ cause: input.cause,
54
+ }),
55
+ options: {
56
+ input: inputSchema,
57
+ status: 404,
58
+ statusText: "Not Found",
59
+ },
60
+ },
61
+
62
+ badRequest: {
63
+ handler: ({ input }) =>
64
+ mapDefaultError({
65
+ message: "Bad Request",
66
+ code: "BAD_REQUEST",
67
+ name: "BadRequestError",
68
+ cause: input.cause,
69
+ }),
70
+ options: {
71
+ input: inputSchema,
72
+ status: 400,
73
+ statusText: "Bad Request",
74
+ },
75
+ },
76
+
77
+ conflict: {
78
+ handler: ({ input }) =>
79
+ mapDefaultError({
80
+ message: "Conflict",
81
+ code: "CONFLICT",
82
+ name: "ConflictError",
83
+ cause: input.cause,
84
+ }),
85
+ options: {
86
+ input: inputSchema,
87
+ status: 409,
88
+ statusText: "Conflict",
89
+ },
90
+ },
91
+
92
+ tooMany: {
93
+ handler: ({ input }) =>
94
+ mapDefaultError({
95
+ message: "Too Many Requests",
96
+ code: "TOO_MANY_REQUESTS",
97
+ name: "TooManyRequestsError",
98
+ cause: input.cause,
99
+ }),
100
+ options: {
101
+ input: inputSchema,
102
+ status: 429,
103
+ statusText: "Too Many Requests",
104
+ },
105
+ },
106
+
107
+ internal: {
108
+ handler: ({ input }) =>
109
+ mapDefaultError({
110
+ message: "Internal Server Error",
111
+ code: "INTERNAL_SERVER_ERROR",
112
+ name: "InternalServerError",
113
+ cause: input.cause,
114
+ }),
115
+ options: {
116
+ input: inputSchema,
117
+ status: 500,
118
+ statusText: "Internal Server Error",
119
+ },
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,66 @@
1
+ import type {
2
+ ExtractSchema,
3
+ Infer,
4
+ Schema,
5
+ ValidationType,
6
+ } from "@apisr/schema";
7
+ import type { ErrorOptions, Options } from "@/options";
8
+
9
+ export * from "./default";
10
+
11
+ // Start of types ------------------
12
+
13
+ export type DefaultErrorTypes =
14
+ | "unauthorized"
15
+ | "forbidden"
16
+ | "notFound"
17
+ | "badRequest"
18
+ | "conflict"
19
+ | "tooMany"
20
+ | "internal";
21
+
22
+ export type DefaultError = {
23
+ message: string;
24
+ code: string;
25
+ name: string;
26
+
27
+ cause?: unknown;
28
+ stack?: string[];
29
+ };
30
+
31
+ export type ErrorHandler<
32
+ TOptions extends Options,
33
+ THandlerOptions extends ErrorHandlerOptions | undefined,
34
+ > = (data: {
35
+ meta: TOptions["meta"] extends undefined
36
+ ? never
37
+ : Infer<ExtractSchema<TOptions["meta"]>>;
38
+ input: THandlerOptions extends undefined
39
+ ? never
40
+ : Infer<Exclude<THandlerOptions, undefined>["input"]>;
41
+ }) => ErrorOptions.InferedSchema<TOptions>;
42
+
43
+ export interface ErrorHandlerOptions<TSchema extends Schema = Schema> {
44
+ input?: TSchema;
45
+
46
+ status?: number;
47
+ statusText?: string;
48
+ validationType?: ValidationType;
49
+ }
50
+
51
+ export type ErrorDefinition<
52
+ TOptions extends Options,
53
+ THandlerOptions extends ErrorHandlerOptions | undefined,
54
+ > = {
55
+ handler:
56
+ | ErrorHandler<TOptions, THandlerOptions>
57
+ | ErrorOptions.InferedSchema<TOptions>;
58
+ options: THandlerOptions;
59
+ };
60
+
61
+ export type ErrorRegistry<TOptions extends Options> = Record<
62
+ string,
63
+ ErrorDefinition<TOptions, any>
64
+ >;
65
+
66
+ // End of types ------------------
package/src/handler.ts ADDED
@@ -0,0 +1,482 @@
1
+ import { checkSchema, type Infer } from "@apisr/schema";
2
+ import type {
3
+ DefaultErrorTypes,
4
+ ErrorDefinition,
5
+ ErrorHandler,
6
+ ErrorHandlerOptions,
7
+ ErrorRegistry,
8
+ } from "./error";
9
+ import { generateDefaultErrors } from "./error/default";
10
+ import { resolveHeaders } from "./headers";
11
+ import type {
12
+ BinaryOptions,
13
+ ErrorOptions,
14
+ JsonOptions,
15
+ MetaOptions,
16
+ Options,
17
+ } from "./options";
18
+ import { options as optionMethods } from "./options";
19
+ import { BaseResponse } from "./response/base";
20
+ import { type Binary, BinaryResponse } from "./response/binary";
21
+ import type { DefaultResponse } from "./response/default";
22
+ import { ErrorResponse } from "./response/error";
23
+ import { JsonResponse } from "./response/json";
24
+ import { TextResponse } from "./response/text";
25
+ import type { PromiseOr } from "./types";
26
+
27
+ export function createResponseHandler<
28
+ TMeta extends MetaOptions.Base,
29
+ TError extends ErrorOptions.Base,
30
+ TJson extends JsonOptions.Base,
31
+ TBinary extends BinaryOptions.Base,
32
+ TOptions extends Options<TMeta, TError, TJson, TBinary> = Options<
33
+ TMeta,
34
+ TError,
35
+ TJson,
36
+ TBinary
37
+ >,
38
+ >(opts: TOptions | ((options: typeof optionMethods) => TOptions)) {
39
+ const _options = typeof opts === "function" ? opts(optionMethods) : opts;
40
+
41
+ return new ResponseHandler<TMeta, TError, TJson, TBinary, TOptions>({
42
+ options: _options,
43
+ });
44
+ }
45
+
46
+ export type ResolveOptionsMeta<TOptions extends Options> =
47
+ TOptions["meta"] extends undefined
48
+ ? never
49
+ : Exclude<TOptions["meta"], undefined>;
50
+
51
+ export type AnyResponseHandler = ResponseHandler<any, any, any, any, any>;
52
+
53
+ export class ResponseHandler<
54
+ TMeta extends MetaOptions.Base,
55
+ TError extends ErrorOptions.Base,
56
+ TJson extends JsonOptions.Base,
57
+ TBinary extends BinaryOptions.Base,
58
+ TOptions extends Options<TMeta, TError, TJson, TBinary>,
59
+ TErrors extends ErrorRegistry<TOptions> = {},
60
+ > {
61
+ public options: TOptions;
62
+
63
+ private errors: TErrors;
64
+ private preasignedMeta: Partial<MetaOptions.InferedSchema<TOptions>>;
65
+
66
+ /**
67
+ * Create a `ResponseHandler` instance.
68
+ *
69
+ * Prefer using {@link createResponseHandler} instead of calling this directly.
70
+ *
71
+ * @param params - Constructor params.
72
+ * @param params.options - Handler options.
73
+ * @param params.errors - Optional pre-defined error registry.
74
+ * @param params.preasignedMeta - Optional meta merged into each response.
75
+ */
76
+ constructor({
77
+ options,
78
+ errors,
79
+ preasignedMeta,
80
+ }: {
81
+ options: TOptions;
82
+ errors?: TErrors;
83
+ preasignedMeta?: Partial<MetaOptions.InferedSchema<TOptions>>;
84
+ }) {
85
+ this.options = options;
86
+ this.errors =
87
+ errors ??
88
+ (generateDefaultErrors<TOptions>(
89
+ options?.error?.mapDefaultError
90
+ ) as TErrors) ??
91
+ ({} as TErrors);
92
+ this.preasignedMeta = preasignedMeta ?? {};
93
+ }
94
+
95
+ fail<
96
+ TKey extends keyof TErrors & string,
97
+ TInput extends Infer<TErrors[TKey]["options"]["input"]>,
98
+ >(
99
+ name: TKey,
100
+ input?: TInput
101
+ ): ErrorResponse.Base<TKey, MetaOptions.InferedSchema<TOptions>, TInput>;
102
+
103
+ fail(
104
+ name: DefaultErrorTypes,
105
+ input?: Record<string, any>
106
+ ): ErrorResponse.Base<
107
+ DefaultErrorTypes,
108
+ MetaOptions.InferedSchema<TOptions>,
109
+ Record<string, any>
110
+ >;
111
+
112
+ /**
113
+ * Create an `ErrorResponse` by name.
114
+ *
115
+ * If the error has an `input` schema, the `input` is validated according to `validationType`.
116
+ * The resulting error response also includes prepared `meta`.
117
+ *
118
+ * @param name - Error name (registered via `defineError` or built-in default types).
119
+ * @param _input - Optional error payload to validate (if a schema exists).
120
+ * @returns Error response instance.
121
+ */
122
+ fail(name: string, _input?: unknown) {
123
+ // TODO: validate zod schema (input)
124
+ const error = this.errors[name];
125
+ const errorOptions = error?.options as ErrorHandlerOptions | undefined;
126
+
127
+ const input = errorOptions?.input
128
+ ? checkSchema(errorOptions?.input, _input, {
129
+ validationType: errorOptions?.validationType ?? "parse",
130
+ })
131
+ : _input;
132
+
133
+ const meta = this.prepareMeta();
134
+
135
+ const handler = error?.handler;
136
+ const output =
137
+ typeof handler === "function"
138
+ ? // @ts-expect-error
139
+ handler({ meta, input })
140
+ : handler;
141
+
142
+ const status = errorOptions?.status ?? 500;
143
+ const statusText = errorOptions?.statusText ?? "Unknown";
144
+
145
+ return new ErrorResponse.Base({
146
+ meta,
147
+ name,
148
+ output,
149
+
150
+ status,
151
+ statusText,
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Create a JSON `Response`.
157
+ *
158
+ * Validates input/output using configured schemas (if present), applies `onData` transformation,
159
+ * and resolves/merges headers.
160
+ *
161
+ * @typeParam IInput - Inferred input type for configured JSON input schema.
162
+ * @param _input - JSON input value.
163
+ * @param options - Optional response init options.
164
+ * @returns JSON response instance.
165
+ */
166
+ json<IInput extends JsonOptions.InferedSchema<TOptions>>(
167
+ _input: IInput,
168
+ options?: JsonResponse.Options
169
+ ): JsonResponse.Base<IInput> {
170
+ const jsonOptions = this.options?.json;
171
+
172
+ // Check input by schema
173
+ // const input = jsonOptions?.inputSchema
174
+ // ? checkSchema(jsonOptions.inputSchema, _input, {
175
+ // validationType: jsonOptions.validationType ?? "parse"
176
+ // })
177
+ // : _input;
178
+
179
+ // Get raw output from input
180
+ const _output = jsonOptions?.mapData
181
+ ? jsonOptions.mapData(_input)
182
+ : JsonResponse.defaultOnDataOutput(_input);
183
+
184
+ // Check output by schema
185
+ // const output = jsonOptions?.outputSchema
186
+ // ? checkSchema(jsonOptions.outputSchema, _output, {
187
+ // validationType: jsonOptions.validationType ?? "parse"
188
+ // })
189
+ // : _output;
190
+
191
+ const str = JSON.stringify(_output, null, 2);
192
+ const headers: Record<string, string> = {
193
+ "Content-Type": "application/json",
194
+ ...(options?.headers ?? {}),
195
+ ...(resolveHeaders(jsonOptions?.headers, {
196
+ output: _output,
197
+ }) ?? {}),
198
+ };
199
+
200
+ return new JsonResponse.Base(str, {
201
+ headers,
202
+ status: options?.status ?? 200,
203
+ statusText: options?.statusText,
204
+ output: _output,
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Create a plain text `Response`.
210
+ *
211
+ * @param text - Raw text response body.
212
+ * @param options - Optional response init options.
213
+ * @returns Text response instance.
214
+ */
215
+ text(text: string, options?: TextResponse.Options) {
216
+ return new TextResponse.Base(text, {
217
+ ...(options ?? {}),
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Create a binary `Response`.
223
+ *
224
+ * Applies optional `binary.onData` transformation and resolves/merges headers.
225
+ *
226
+ * @param binary - Binary payload (Blob/ArrayBuffer/Uint8Array/ReadableStream).
227
+ * @param options - Optional response init options.
228
+ * @returns Binary response instance.
229
+ */
230
+ binary(binary: Binary, options?: BinaryResponse.Options) {
231
+ const binaryOptions = this.options?.binary;
232
+
233
+ const data = binaryOptions?.mapData
234
+ ? binaryOptions.mapData(binary)
235
+ : binary;
236
+
237
+ const headers: Record<string, string> = {
238
+ ...(options?.headers ?? {}),
239
+ ...(resolveHeaders(binaryOptions?.headers, data) ?? {}),
240
+ };
241
+
242
+ return new BinaryResponse.Base(data as any, {
243
+ headers,
244
+ status: options?.status ?? 200,
245
+ statusText: options?.statusText,
246
+ });
247
+ }
248
+
249
+ /**
250
+ * Create a new `ResponseHandler` with preassigned meta.
251
+ *
252
+ * The provided meta is validated against the configured meta schema (if present).
253
+ *
254
+ * @param _meta - Partial meta that will be merged into each next response.
255
+ * @returns New `ResponseHandler` instance.
256
+ */
257
+ withMeta(
258
+ _meta: Partial<MetaOptions.InferedSchema<TOptions>>
259
+ ): ResponseHandler<TMeta, TError, TJson, TBinary, TOptions, TErrors> {
260
+ const metaOptions = this.options.meta;
261
+ const meta = metaOptions?.schema
262
+ ? checkSchema(metaOptions.schema.partial(), _meta, {
263
+ validationType: metaOptions.validationType ?? "parse",
264
+ })
265
+ : _meta;
266
+
267
+ return new ResponseHandler<
268
+ TMeta,
269
+ TError,
270
+ TJson,
271
+ TBinary,
272
+ TOptions,
273
+ TErrors
274
+ >({
275
+ options: this.options,
276
+ errors: this.errors,
277
+ preasignedMeta: meta as Partial<MetaOptions.InferedSchema<TOptions>>,
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Define a named error handler.
283
+ *
284
+ * Returns a new `ResponseHandler` instance with the extended error registry.
285
+ *
286
+ * @typeParam TName - Error name.
287
+ * @typeParam THandlerOptions - Handler options type.
288
+ * @param name - Error name to register.
289
+ * @param handler - Error handler function or static error output.
290
+ * @param options - Error handler options (e.g. input schema).
291
+ * @returns New `ResponseHandler` instance with the new error registered.
292
+ */
293
+ defineError<
294
+ TName extends string,
295
+ THandlerOptions extends ErrorHandlerOptions | undefined,
296
+ >(
297
+ name: TName,
298
+ handler:
299
+ | ErrorHandler<TOptions, THandlerOptions>
300
+ | ErrorOptions.InferedSchema<TOptions>,
301
+ options?: THandlerOptions
302
+ ): ResponseHandler<
303
+ TMeta,
304
+ TError,
305
+ TJson,
306
+ TBinary,
307
+ TOptions,
308
+ TErrors & Record<TName, ErrorDefinition<TOptions, THandlerOptions>>
309
+ > {
310
+ const nextErrors = {
311
+ ...(this.errors as Record<string, unknown>),
312
+ [name]: {
313
+ handler,
314
+ options,
315
+ },
316
+ } as TErrors & Record<TName, ErrorDefinition<TOptions, THandlerOptions>>;
317
+
318
+ const instance = new ResponseHandler({
319
+ options: this.options,
320
+ errors: nextErrors,
321
+ });
322
+
323
+ return instance;
324
+ }
325
+
326
+ mapResponse(raw: {
327
+ data:
328
+ | JsonOptions.InferedSchemaFromBase<TJson>
329
+ | Binary
330
+ | string
331
+ | undefined;
332
+ error: ErrorOptions.InferedSchemaFromBase<TError> | undefined;
333
+ }): PromiseOr<Response> {
334
+ const mapResponse = this.options.mapResponse;
335
+ if (this.isBinaryData(raw.data)) {
336
+ return this.defaultMapResponse(raw);
337
+ }
338
+
339
+ const response = this.defaultMapResponse(
340
+ raw
341
+ ) as BaseResponse.Base<DefaultResponse>;
342
+
343
+ return mapResponse
344
+ ? mapResponse({
345
+ data: raw.data as
346
+ | JsonOptions.InferedSchemaFromBase<TJson>
347
+ | Binary
348
+ | string,
349
+ error: raw.error as ErrorOptions.InferedSchemaFromBase<TError>,
350
+ response,
351
+ })
352
+ : response;
353
+ }
354
+
355
+ /**
356
+ * Returns response that mapped into `DefaultResponse`:
357
+ * ```
358
+ * export interface DefaultResponse<TData = unknown> {
359
+ * success: boolean;
360
+ * error: ErrorResponse.Base<any, any, any> | null;
361
+ * data: TData | null;
362
+ * metadata: Record<string, unknown>;
363
+ * }
364
+ * ```
365
+ * @param raw
366
+ * @returns Response
367
+ */
368
+ defaultMapResponse(raw: {
369
+ data:
370
+ | JsonOptions.InferedSchemaFromBase<TJson>
371
+ | Binary
372
+ | string
373
+ | undefined;
374
+ error: ErrorOptions.InferedSchemaFromBase<TError> | undefined;
375
+ }): BaseResponse.Base<DefaultResponse> | BaseResponse.Base<Binary> {
376
+ const options = this.options;
377
+ const meta = this.prepareMeta();
378
+ // DefaultError or CustomError (ErrorResponse.Base) or undefined.
379
+ const error = options.error?.mapError
380
+ ? options.error?.mapError?.({
381
+ error: raw.error && raw.error instanceof Error ? raw.error : null,
382
+ meta,
383
+ parsedError:
384
+ raw.error instanceof ErrorResponse.Base ? raw.error : null,
385
+ })
386
+ : raw.error;
387
+
388
+ if (error) {
389
+ const status =
390
+ (error as ErrorResponse.Base<any, any, any>)?.status ?? 500;
391
+ const statusText =
392
+ (error as ErrorResponse.Base<any, any, any>)?.statusText ??
393
+ "Internal Server Error";
394
+
395
+ const payload = {
396
+ error,
397
+ data: null,
398
+ success: false,
399
+ metadata: meta,
400
+ } as DefaultResponse;
401
+
402
+ const headers: Record<string, string> = {
403
+ "Content-Type": "application/json",
404
+ ...(resolveHeaders(options.headers, {
405
+ type: "error",
406
+ data: error,
407
+ }) ?? {}),
408
+ ...(resolveHeaders(options.error?.headers, error) ?? {}),
409
+ };
410
+
411
+ return new BaseResponse.Base<DefaultResponse>(JSON.stringify(payload), {
412
+ status,
413
+ statusText,
414
+ headers,
415
+ payload,
416
+ });
417
+ }
418
+
419
+ if (this.isBinaryData(raw.data)) {
420
+ const headers: Record<string, string> = {
421
+ ...(resolveHeaders(options.headers, {
422
+ type: "binary",
423
+ data: raw.data,
424
+ }) ?? {}),
425
+ ...(resolveHeaders(options.binary?.headers, raw.data) ?? {}),
426
+ };
427
+
428
+ return new BaseResponse.Base<Binary>(raw.data as Binary, {
429
+ status: 200,
430
+ headers,
431
+ payload: raw.data as Binary,
432
+ });
433
+ }
434
+
435
+ const payload = {
436
+ error: null,
437
+ data: raw.data ?? null,
438
+ success: true,
439
+ metadata: meta,
440
+ } as DefaultResponse;
441
+
442
+ const headers: Record<string, string> = {
443
+ "Content-Type": "application/json",
444
+ ...(resolveHeaders(options.headers, {
445
+ type: "json",
446
+ data: raw.data,
447
+ }) ?? {}),
448
+ ...(resolveHeaders(options.json?.headers, {
449
+ output: raw.data,
450
+ }) ?? {}),
451
+ };
452
+
453
+ return new BaseResponse.Base<DefaultResponse>(JSON.stringify(payload), {
454
+ status: 200,
455
+ headers,
456
+ payload,
457
+ });
458
+ }
459
+
460
+ /**
461
+ * Prepare `meta` by merging preassigned meta (from `withMeta`) with default meta (if any).
462
+ *
463
+ * @returns Resolved meta object.
464
+ */
465
+ private prepareMeta(): MetaOptions.InferedSchema<TOptions> {
466
+ const objOrFn = this.options.meta?.default;
467
+
468
+ return {
469
+ ...(typeof objOrFn === "function" ? objOrFn() : objOrFn),
470
+ ...(this.preasignedMeta ?? {}),
471
+ };
472
+ }
473
+
474
+ private isBinaryData(data: unknown): data is Binary {
475
+ return (
476
+ data instanceof Blob ||
477
+ data instanceof ArrayBuffer ||
478
+ data instanceof Uint8Array ||
479
+ (typeof ReadableStream !== "undefined" && data instanceof ReadableStream)
480
+ );
481
+ }
482
+ }
package/src/headers.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { FunctionObject } from "@/types";
2
+
3
+ export type RawHeaders = Record<string, string>;
4
+ export type Headers<T> = FunctionObject<RawHeaders, T>;
5
+
6
+ export function resolveHeaders(
7
+ headers: Headers<any> | undefined,
8
+ input: unknown
9
+ ): RawHeaders | null {
10
+ const result =
11
+ typeof headers === "function"
12
+ ? (headers as (arg: unknown) => RawHeaders)(input)
13
+ : headers;
14
+
15
+ if (!headers) {
16
+ return null;
17
+ }
18
+
19
+ // @ts-ignore
20
+ return result;
21
+ }