Template
1
0
Files
boilerplate/libraries/client.ts
2025-04-25 18:41:23 +00:00

105 lines
3.6 KiB
TypeScript

import z, { ZodType } from "zod";
import type { RelayAdapter, RelayRESTInput } from "./adapter.ts";
import { Procedure } from "./procedure.ts";
import type { Relays } from "./relay.ts";
import { Route } from "./route.ts";
/**
* Make a new relay client instance.
*
* @param config - Client configuration.
* @param procedures - Map of procedures to make available to the client.
*/
export function makeRelayClient<TRelays extends Relays>(config: RelayClientConfig, relays: TRelays): RelayClient<TRelays> {
return mapRelays(relays, config.adapter);
}
/*
|--------------------------------------------------------------------------------
| Helpers
|--------------------------------------------------------------------------------
*/
function mapRelays<TRelays extends Relays>(relays: TRelays, adapter: RelayAdapter): RelayClient<TRelays> {
const client: any = {};
for (const key in relays) {
const relay = relays[key];
if (relay instanceof Procedure) {
client[key] = async (params: unknown) => {
const response = await adapter.send({ method: relay.method, params });
if ("error" in response) {
throw new Error(response.error.message);
}
if ("result" in response && relay.state.result !== undefined) {
return relay.state.result.parseAsync(response.result);
}
return response.result;
};
} else if (relay instanceof Route) {
client[key] = async (...args: any[]) => {
const input: RelayRESTInput = { method: relay.state.method, url: `${adapter.url}${relay.state.path}`, query: "" };
let index = 0; // argument incrementor
if (relay.state.params !== undefined) {
const params = args[index++] as { [key: string]: string };
for (const key in params) {
input.url = input.url.replace(`:${key}`, params[key]);
}
}
if (relay.state.query !== undefined) {
const query = args[index++] as { [key: string]: string };
const pieces: string[] = [];
for (const key in query) {
pieces.push(`${key}=${query[key]}`);
}
if (pieces.length > 0) {
input.query = `?${pieces.join("&")}`;
}
}
if (relay.state.body !== undefined) {
input.body = JSON.stringify(args[index++]);
}
// ### Fetch
const data = await adapter.fetch(input);
if (relay.state.output !== undefined) {
return relay.state.output.parse(data);
}
return data;
};
} else {
client[key] = mapRelays(relay, adapter);
}
}
return client;
}
/*
|--------------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------------
*/
export type RelayClient<TRelays extends Relays> = {
[TKey in keyof TRelays]: TRelays[TKey] extends Procedure<infer TState>
? TState["params"] extends ZodType
? (params: z.infer<TState["params"]>) => Promise<TState["result"] extends ZodType ? z.infer<TState["result"]> : void>
: () => Promise<TState["result"] extends ZodType ? z.infer<TState["result"]> : void>
: TRelays[TKey] extends Route
? (...args: TRelays[TKey]["args"]) => Promise<RelayRouteResponse<TRelays[TKey]>>
: TRelays[TKey] extends Relays
? RelayClient<TRelays[TKey]>
: never;
};
type RelayRouteResponse<TRoute extends Route> = TRoute["state"]["output"] extends ZodType ? z.infer<TRoute["state"]["output"]> : void;
export type RelayClientConfig = {
adapter: RelayAdapter;
};