import { NextApiRequest , NextApiResponse } from 'next' ; export interface BaseAPIResponse { status : number ; } export interface ErroredAPIResponse extends BaseAPIResponse { success : false ; data : null ; message : string ; } export type Method = 'POST' | 'DELETE' | 'GET' | 'PATCH' | 'PUT' ; export interface SuccessAPIResponse < T > extends BaseAPIResponse { success : true ; data : T ; message : null ; } export type APIResponse < T > = SuccessAPIResponse < T > | ErroredAPIResponse ; export type NextkitErrorHandler = ( req : NextApiRequest , res : NextApiResponse < ErroredAPIResponse > , error : Error ) => Promise < { status : number ; message : string ; } > ; export interface ConfigWithoutContext { onError : NextkitErrorHandler ; } export type Redirect = { _redirect : string ; } ; export type NextkitHandler < Context , Result > = ( data : { context : Context ; req : NextApiRequest ; res : NextApiResponse < APIResponse < Result > > ; } ) => Promise < Result | Redirect > ; export type HandlerInit = Partial < Record < Method , unknown > > ; export interface ConfigWithContext < Context > extends ConfigWithoutContext { getContext ( req , res ) : Promise < Context > ; } export class NextkitException extends Error { constructor ( public readonly code , message ) { super ( message ) ; } } export class WrappedNonErrorException < T > extends Error { constructor ( public readonly data ) { super ( 'Some data was thrown, but it was not an instance of an Error, so a WrappedNonErrorException was thrown instead. Access the .data property to read the original data that was thrown' ) ; } } export type Config < Context = null > = ConfigWithContext < Context > | ConfigWithoutContext ; export type HandlersMap < Context , Init > = { [ Method in keyof Init ] : NextkitHandler < Context , Init [ Method ] | Redirect > ; } ; export type ExportedHandler < Handlers extends HandlerInit > = ( req : NextApiRequest , res : NextApiResponse < APIResponse < Handlers [ keyof Handlers ] > > ) => Promise < void > ; export type MapHandlerResults < Context , Handlers extends HandlersMap < Context , HandlerInit > > = { [ Method in keyof Handlers ] : Handlers [ Method ] extends NextkitHandler < Context , infer R > ? R : never ; } ; export type Then < T > = T extends PromiseLike < infer R > ? Then < R > : T ; export type ThenFn < T > = T extends ( ... args : any ) => PromiseLike < infer R > ? R : T ; export type InferAPIResponses < T > = T extends ExportedHandler < MapHandlerResults < any , infer Handlers > > ? { [ Method in keyof Handlers ] : Exclude < ThenFn < Handlers [ Method ] > , Redirect > ; } : never ; export type InferAPIResponse < T , M extends Method > = InferAPIResponses < T > [ M ] ; export function hasProp < Prop extends string | number | symbol > ( value , prop ) { if ( typeof value !== 'object' ) { return false ; } if ( ! value ) { return false ; } return prop in value ; } export default function createAPI < Context = null > ( config ) { return < Init extends HandlerInit , Handlers extends HandlersMap < Context , Init > = HandlersMap < Context , Init > > ( handlers ) : ExportedHandler < MapHandlerResults < Context , Handlers > > => { return async ( req , res ) => { const handler = handlers [ req . method as Method ] ; try { if ( ! handler ) { throw new NextkitException ( 405 , `Cannot ${ req . method ?? 'n/a' } this route` ) ; } const context = 'getContext' in config ? await config . getContext ( req , res ) : ( null as never ) ; const result = await handler ( { context , req , res : res as NextApiResponse < APIResponse < unknown > > , } ) ; if ( hasProp ( result , '_redirect' ) ) { res . redirect ( result . _redirect ) ; return ; } res . json ( { success : true , data : result as MapHandlerResults < Context , Handlers > [ keyof Handlers ] , status : 200 , message : null , } ) ; } catch ( error ) { if ( error instanceof NextkitException ) { res . status ( error . code ) . json ( { status : error . code , message : error . message , data : null , success : false , } ) ; return ; } const wrapped = error instanceof Error ? error : new WrappedNonErrorException ( error ) ; const result = await config . onError ( req , res , wrapped ) ; res . status ( result . status ) . json ( { success : false , data : null , message : result . message , status : result . status , } ) ; } } ; } ; } export { createAPI } ;