Last active
May 7, 2021 06:24
-
-
Save uinz/40e0d6b6194aaf0cd4b135f8101cb758 to your computer and use it in GitHub Desktop.
http
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 基于 ctx, 类型好友的 js HTTP 设计尝试 | |
type IncomingMessage = object; | |
type Prettier<T> = T extends object ? { [K in keyof T]: T[K] } : T; | |
type Middle = (ctx: BaseCtx) => unknown; | |
type BaseCtx = { readonly _: IncomingMessage }; | |
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T; | |
type NextCtx<T extends Middle> = Awaited<ReturnType<T>>; | |
type KV<P> = P extends `${infer K}=${infer V}` ? { [k in K]: VType<V> } : {}; | |
type Query<T extends string, A = {}> = T extends `${infer P}&${infer Rest}` | |
? Query<Rest, A & KV<P>> | |
: Prettier<KV<T> & A>; | |
type Split< | |
URL extends string, | |
S extends string | |
> = URL extends `${infer Path}${S}${infer Query}` ? [Path, Query] : [URL, ""]; | |
type VType<T> = T extends "<string>" | |
? string | |
: T extends "<number>" | |
? number | |
: T extends "<boolean>" | |
? boolean | |
: unknown; | |
type Normalize<T extends string> = T extends "" | "/" | `/${string}/` | |
? T | |
: T extends `/${infer X}` | |
? `/${X}/` | |
: T extends `${infer X}/` | |
? `/${X}/` | |
: `/${T}/`; | |
type Params< | |
URL extends string, | |
KS extends string = never | |
> = Normalize<URL> extends `${string}/:${infer K}/${infer Rest}` | |
? Params<Normalize<Rest>, KS | K> | |
: { [P in KS]: string }; | |
type CtxIntersection<T> = ( | |
T extends object ? (x: T) => unknown : never | |
) extends (x: infer R) => unknown | |
? R | |
: never; | |
const compose = <T extends Middle[]>(...middles: T) => { | |
return async <U extends BaseCtx>(ctx: U) => { | |
const task = middles.map((m) => m(ctx)); | |
const arr = await Promise.all(task); | |
return arr.reduce(Object.assign, ctx) as U & | |
CtxIntersection<NextCtx<T[number]>>; | |
}; | |
}; | |
function middleA() { | |
return { | |
user: { | |
name: "yinz", | |
}, | |
}; | |
} | |
function middleB() { | |
return { | |
db: { | |
get() {}, | |
set() {}, | |
}, | |
}; | |
} | |
function middleC() { | |
return { hello: "world" }; | |
} | |
const middles = compose(middleB, compose(middleA, middleC)); | |
const run = <T extends Middle>(middle: T) => { | |
return { | |
route<Method extends string, Pattern extends string>( | |
method: Method, | |
pattern: Pattern | |
) { | |
type TEMP = Split<Pattern, "?">; | |
type Ctx = Awaited<ReturnType<T>> & { | |
url: string; | |
method: Method; | |
params: Params<TEMP[0]>; | |
query: Query<TEMP[1]>; | |
}; | |
return { | |
async handle<R>(handler: (ctx: Prettier<Ctx>) => R | Promise<R>) { | |
const ctx: any = await middle({} as BaseCtx); | |
return handler({ ...ctx, method, url: pattern }); | |
}, | |
}; | |
}, | |
}; | |
}; | |
function is<T>(x: T) { | |
// noop | |
} | |
run(middles) | |
.route("GET", "/:user/:id?age=<number>&sex=<string>") | |
.handle((ctx) => { | |
// base | |
is<string>(ctx.url); | |
is<string>(ctx.params.id); | |
is<string>(ctx.params.user); | |
is<number>(ctx.query.age); | |
is<string>(ctx.query.sex); | |
// middleware | |
is<string>(ctx.user.name); | |
is<CallableFunction>(ctx.db.get); | |
is<CallableFunction>(ctx.db.set); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ts playground