|
import { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; |
|
|
|
export type Ctx = GetServerSidePropsContext; |
|
export type Result = GetServerSidePropsResult<any>; |
|
|
|
export type NextFn = () => Result | Promise<Result>; |
|
|
|
export type Middleware<Vars extends {} = {}> = ( |
|
ctx: Ctx, |
|
vars: Partial<Vars>, |
|
next: NextFn |
|
) => Promise<Result> | Result; |
|
|
|
// Like `Middleware` but allows call site to decide which vars have already been applied |
|
type AppliedMiddleware<Vars extends {}> = ( |
|
ctx: Ctx, |
|
vars: Vars, |
|
next: NextFn |
|
) => Promise<Result> | Result; |
|
|
|
interface Use { |
|
<Fn1Vars>(fn1: Middleware<Fn1Vars>): Middleware<Partial<Fn1Vars>>; |
|
<Fn1Vars, Fn2Vars>( |
|
fn1: Middleware<Fn1Vars>, |
|
fn2: AppliedMiddleware<Fn1Vars & Partial<Fn2Vars>> |
|
): Middleware<Fn1Vars & Fn2Vars>; |
|
<Fn1Vars, Fn2Vars, Fn3Vars>( |
|
fn1: Middleware<Fn1Vars>, |
|
fn2: AppliedMiddleware<Fn1Vars & Partial<Fn2Vars>>, |
|
fn3: AppliedMiddleware<Fn1Vars & Fn2Vars & Partial<Fn3Vars>> |
|
): Middleware<Fn1Vars & Fn2Vars & Fn3Vars>; |
|
<Fn1Vars, Fn2Vars, Fn3Vars, Fn4Vars>( |
|
fn1: Middleware<Fn1Vars>, |
|
fn2: AppliedMiddleware<Fn1Vars & Partial<Fn2Vars>>, |
|
fn3: AppliedMiddleware<Fn1Vars & Fn2Vars & Partial<Fn3Vars>>, |
|
fn4: AppliedMiddleware<Fn1Vars & Fn2Vars & Fn3Vars & Partial<Fn4Vars>> |
|
): Middleware<Fn1Vars & Fn2Vars & Fn3Vars & Fn4Vars>; |
|
<Fn1Vars, Fn2Vars, Fn3Vars, Fn4Vars, Fn5Vars>( |
|
fn1: Middleware<Fn1Vars>, |
|
fn2: AppliedMiddleware<Fn1Vars & Partial<Fn2Vars>>, |
|
fn3: AppliedMiddleware<Fn1Vars & Fn2Vars & Partial<Fn3Vars>>, |
|
fn4: AppliedMiddleware<Fn1Vars & Fn2Vars & Fn3Vars & Partial<Fn4Vars>>, |
|
fn5: AppliedMiddleware< |
|
Fn1Vars & Fn2Vars & Fn3Vars & Fn4Vars & Partial<Fn5Vars> |
|
> |
|
): Middleware<Fn1Vars & Fn2Vars & Fn3Vars & Fn4Vars & Fn5Vars>; |
|
} |
|
|
|
export const use: Use = |
|
(...middleware: Middleware<any>[]) => |
|
async (ctx: Ctx, vars: any, next: NextFn) => { |
|
let idx = 0; |
|
const _next = () => { |
|
const fn = middleware[idx++]; |
|
if (!fn) { |
|
return next(); |
|
} |
|
return fn(ctx, vars, _next); |
|
}; |
|
return _next(); |
|
}; |
|
|
|
export const wrap = |
|
(middleware: Middleware<any>) => |
|
async (ctx: Ctx): Promise<Result> => { |
|
const result = await middleware(ctx, {}, () => { |
|
// TODO: Can this be caught with TS; e.g. FinalMiddleware? |
|
throw new Error("Final middleware cannot call next()"); |
|
}); |
|
if (!result) { |
|
// TODO: Can this be caught with TS; e.g. FinalMiddleware? |
|
throw new Error("Final middleware must return a `Result`"); |
|
} |
|
return result; |
|
}; |
Improvements
any
)FinalMiddleware
which doesn't acceptnext
and can't returnundefined
?use
calls won't be expected to have a final middleware 🤔use(parallel(withUser, withSomeOtherAsyncData), withVersion, () => {});
vars
intonext
so that you're not forced to mutateexpress
fn which interoperates with express-style middlewareuse(express((req, res, next) => { req.foo = true; next(new Error('nope')) }), (ctx) => { props: { foo: ctx.req.foo } })
vars
toctx.vars
so middleware signature is just(ctx, next) => Promise<Result>
/(req, res, next) => Promise<void>
nextjs-use