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

149 lines
4.8 KiB
TypeScript

import { makeRelayClient, RelayClient, RelayClientConfig } from "./client.ts";
import { Procedure } from "./procedure.ts";
import { Route, RouteMethod } from "./route.ts";
export class Relay<
TRelays extends Relays,
TRPCIndex = RPCIndex<TRelays>,
TPostIndex = RouteIndex<"POST", TRelays>,
TGetIndex = RouteIndex<"GET", TRelays>,
TPutIndex = RouteIndex<"PUT", TRelays>,
TPatchIndex = RouteIndex<"PATCH", TRelays>,
TDeleteIndex = RouteIndex<"DELETE", TRelays>,
> {
readonly #index = new Map<string, Procedure | Route>();
declare readonly $inferClient: RelayClient<TRelays>;
/**
* Instantiate a new Relay instance.
*
* @param procedures - Procedures to register with the instance.
*/
constructor(readonly relays: TRelays) {
indexRelays(relays, this.#index);
}
/**
* Create a new relay client instance from the instance procedures.
*
* @param config - Client configuration.
*/
client(config: RelayClientConfig): this["$inferClient"] {
return makeRelayClient(config, this.relays) as any;
}
/**
* Retrieve a registered procedure registered with the relay instance.
*
* @param method - Method name assigned to the procedure.
*/
method<TMethod extends keyof TRPCIndex>(method: TMethod): TRPCIndex[TMethod] {
return this.#index.get(method as string) as TRPCIndex[TMethod];
}
/**
* Retrieve a registered 'POST' route registered with the relay instance.
*
* @param path - Route path to retrieve.
*/
post<TPath extends keyof TPostIndex>(path: TPath): TPostIndex[TPath] {
return this.#index.get(`POST ${path as string}`) as TPostIndex[TPath];
}
/**
* Retrieve a registered 'GET' route registered with the relay instance.
*
* @param path - Route path to retrieve.
*/
get<TPath extends keyof TGetIndex>(path: TPath): TGetIndex[TPath] {
return this.#index.get(`GET ${path as string}`) as TGetIndex[TPath];
}
/**
* Retrieve a registered 'PUT' route registered with the relay instance.
*
* @param path - Route path to retrieve.
*/
put<TPath extends keyof TPutIndex>(path: TPath): TPutIndex[TPath] {
return this.#index.get(`PUT ${path as string}`) as TPutIndex[TPath];
}
/**
* Retrieve a registered 'PATCH' route registered with the relay instance.
*
* @param path - Route path to retrieve.
*/
patch<TPath extends keyof TPatchIndex>(path: TPath): TPatchIndex[TPath] {
return this.#index.get(`PATCH ${path as string}`) as TPatchIndex[TPath];
}
/**
* Retrieve a registered 'DELETE' route registered with the relay instance.
*
* @param path - Route path to retrieve.
*/
delete<TPath extends keyof TDeleteIndex>(path: TPath): TDeleteIndex[TPath] {
return this.#index.get(`DELETE ${path as string}`) as TDeleteIndex[TPath];
}
}
/*
|--------------------------------------------------------------------------------
| Helpers
|--------------------------------------------------------------------------------
*/
function indexRelays(relays: Relays, index: Map<string, Procedure | Route>) {
for (const key in relays) {
const relay = relays[key];
if (relay instanceof Procedure) {
const method = relay.method;
if (index.has(method)) {
throw new Error(`Relay > Procedure with method '${method}' already exists!`);
}
index.set(method, relay);
} else if (relay instanceof Route) {
const path = `${relay.method} ${relay.path}`;
if (index.has(path)) {
throw new Error(`Relay > Procedure with path 'path' already exists!`);
}
index.set(path, relay);
} else {
indexRelays(relay, index);
}
}
}
/*
|--------------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------------
*/
export type Relays = {
[key: string]: Relays | Procedure | Route;
};
type RPCIndex<TRelays extends Relays> = MergeUnion<FlattenRPCRelays<TRelays>>;
type RouteIndex<TMethod extends RouteMethod, TRelays extends Relays> = MergeUnion<FlattenRouteRelays<TMethod, TRelays>>;
type FlattenRPCRelays<TRelays extends Relays> = {
[TKey in keyof TRelays]: TRelays[TKey] extends Procedure<infer TState>
? Record<TState["method"], TRelays[TKey]>
: TRelays[TKey] extends Relays
? FlattenRPCRelays<TRelays[TKey]>
: never;
}[keyof TRelays];
type FlattenRouteRelays<TMethod extends RouteMethod, TRelays extends Relays> = {
[TKey in keyof TRelays]: TRelays[TKey] extends { state: { method: TMethod; path: infer TPath extends string } }
? Record<TPath, TRelays[TKey]>
: TRelays[TKey] extends Relays
? FlattenRouteRelays<TMethod, TRelays[TKey]>
: never;
}[keyof TRelays];
type MergeUnion<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? { [K in keyof I]: I[K] } : never;