Skip to content

Instantly share code, notes, and snippets.

@uinz
Last active May 7, 2021 06:24
Show Gist options
  • Save uinz/40e0d6b6194aaf0cd4b135f8101cb758 to your computer and use it in GitHub Desktop.
Save uinz/40e0d6b6194aaf0cd4b135f8101cb758 to your computer and use it in GitHub Desktop.
http
// 基于 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);
});
@uinz
Copy link
Author

uinz commented May 7, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment