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, TPostIndex = RouteIndex<"POST", TRelays>, TGetIndex = RouteIndex<"GET", TRelays>, TPutIndex = RouteIndex<"PUT", TRelays>, TPatchIndex = RouteIndex<"PATCH", TRelays>, TDeleteIndex = RouteIndex<"DELETE", TRelays>, > { readonly #index = new Map(); declare readonly $inferClient: RelayClient; /** * 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(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(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(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(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(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(path: TPath): TDeleteIndex[TPath] { return this.#index.get(`DELETE ${path as string}`) as TDeleteIndex[TPath]; } } /* |-------------------------------------------------------------------------------- | Helpers |-------------------------------------------------------------------------------- */ function indexRelays(relays: Relays, index: Map) { 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 = MergeUnion>; type RouteIndex = MergeUnion>; type FlattenRPCRelays = { [TKey in keyof TRelays]: TRelays[TKey] extends Procedure ? Record : TRelays[TKey] extends Relays ? FlattenRPCRelays : never; }[keyof TRelays]; type FlattenRouteRelays = { [TKey in keyof TRelays]: TRelays[TKey] extends { state: { method: TMethod; path: infer TPath extends string } } ? Record : TRelays[TKey] extends Relays ? FlattenRouteRelays : never; }[keyof TRelays]; type MergeUnion = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? { [K in keyof I]: I[K] } : never;