Initial commit

main 2022.07.27
idylls 2 years ago
parent 4449ef4213
commit 8efc9398a2
Signed by: idylls
GPG Key ID: 8A7167CBC2CC9F0F

158
mod.ts

@ -0,0 +1,158 @@
import { TypeOf, Schema, ZodError } from "https://deno.land/x/zod@v3.17.10/mod.ts";
export interface Sig<I extends Schema<any> | undefined, O extends Schema<any> | undefined> {
_sig: true,
input: I,
output: O,
}
export const sig = <I extends Schema<any> | undefined, O extends Schema<any> | undefined>(i: I, o: O): Sig<I, O> => ({
_sig: true,
input: i,
output: o,
});
type MakeHandlersObject<T> = {
[K in keyof T]: MakeHandlers<T[K]>
};
type MakeHandlersSig<T extends Sig<any, any>> =
T["input"] extends undefined ? (
T["output"] extends undefined
? () => Promise<void>
: () => Promise<TypeOf<T["output"]>>
) : (
T["output"] extends undefined
? (i: TypeOf<T["input"]>) => Promise<void>
: (i: TypeOf<T["input"]>) => Promise<TypeOf<T["output"]>>
);
export type MakeHandlers<T> =
T extends Sig<any, any>
? MakeHandlersSig<T>
: MakeHandlersObject<T>;
type Handler = (req: Request) => Promise<Response>;
const makeServe_ = <T>(
api: T,
handlers: MakeHandlers<T>,
path: string,
paths: Map<string, Handler>,
) => {
const a = api as any;
Object.entries(handlers).forEach(([k, v]) => {
const p = `${path}/${k}`;
if (typeof v === "function") {
paths.set(p, async (req: Request) => {
if (a[k].input) {
const body = await req.json();
const parsed = a[k].input.parse(body);
return v(parsed);
}
return v();
});
}
makeServe_(a[k], v, p, paths);
});
}
export const makeServe = <T>(
api: T,
handlers: MakeHandlers<T>,
path: string,
) => {
const paths = new Map<string, Handler>();
makeServe_(api, handlers, path, paths);
return async (req: Request) => {
const path = new URL(req.url).pathname;
const handler = paths.get(path);
if (!handler) {
return new Response(null, {
status: 404,
});
}
const res = await handler(req);
return new Response(res && JSON.stringify(res));
};
};
type MakeClient<T> =
T extends Sig<any, any>
? MakeClientSig<T>
: MakeClientObject<T>;
type MakeClientObject<T> = {
[K in keyof T]: MakeClient<T[K]>
};
type ClientHttpError = Response;
interface ClientValidationError<T> {
error: ZodError<T>,
}
export type ClientResponse<T> = T | ClientHttpError | ClientValidationError<T>;
export type MakeClientSig<T extends Sig<any, any>> =
T["input"] extends undefined ? (
T["output"] extends undefined
? () => Promise<ClientResponse<void>>
: () => Promise<ClientResponse<TypeOf<T["output"]>>>
) : (
T["output"] extends undefined
? (i: TypeOf<T["input"]>) => Promise<ClientResponse<void>>
: (i: TypeOf<T["input"]>) => Promise<ClientResponse<TypeOf<T["output"]>>>
);
export const makeClient = <T>(api: T, path: string): MakeClient<T> => {
return Object.fromEntries(Object.entries(api).map(([k, v]) => {
const p = `${path}/${k}` as string;
if ("_sig" in v) {
const v_ = v as Sig<Schema<any> | undefined, Schema<any> | undefined>;
return [k, async (body: any) => {
const b = body ? JSON.stringify(body) : null;
const re = await fetch(p, { body: b, method: "POST" });
if (!re.ok) {
return re;
}
if (v_.output) {
const res = await v_.output.safeParseAsync(await re.json());
if (!res.success) {
return {
error: res.error,
}
}
return res.data;
}
}]
}
return [k, makeClient(v, p)];
})) as MakeClient<T>;
};
export const isHttpError = (v: any): v is ClientHttpError => {
return v instanceof Response;
}
export const isValidationError = (v: any): v is ClientValidationError<any> => {
return (typeof v === "object" && v.error instanceof ZodError);
}
export const assert = <T>(res: ClientResponse<T>): T => {
if (isHttpError(res) || isValidationError(res)) {
throw res;
}
return res;
}
Loading…
Cancel
Save