parent
4449ef4213
commit
8efc9398a2
@ -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…
Reference in New Issue