feat: convert to rpc pattern
This commit is contained in:
@@ -1,64 +1,77 @@
|
||||
import type { Route, RouteMethod } from "./route.ts";
|
||||
import { makeRelayClient, RelayClient, RelayClientConfig } from "./client.ts";
|
||||
import { Procedure, Procedures } from "./procedure.ts";
|
||||
|
||||
export class Relay<TRoutes extends Route[]> {
|
||||
/**
|
||||
* Route index in the '${method} ${path}' format allowing for quick access to
|
||||
* a specific route.
|
||||
*/
|
||||
readonly #index = new Map<string, Route>();
|
||||
export class Relay<TProcedures extends Procedures, TProcedureIndex = ProcedureIndex<TProcedures>> {
|
||||
readonly #index = new Map<keyof TProcedureIndex, Procedure>();
|
||||
|
||||
declare readonly $inferRoutes: TRoutes;
|
||||
declare readonly $inferClient: RelayClient<TProcedures>;
|
||||
declare readonly $inferIndex: TProcedureIndex;
|
||||
|
||||
/**
|
||||
* Instantiate a new Relay instance.
|
||||
*
|
||||
* @param config - Relay configuration to apply to the instance.
|
||||
* @param routes - Routes to register with the instance.
|
||||
* @param procedures - Procedures to register with the instance.
|
||||
*/
|
||||
constructor(readonly routes: TRoutes) {
|
||||
for (const route of routes) {
|
||||
this.#index.set(`${route.method} ${route.path}`, route);
|
||||
}
|
||||
constructor(readonly procedures: TProcedures) {
|
||||
indexProcedures(procedures, this.#index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a route for the given method/path combination which can be further extended
|
||||
* for serving incoming third party requests.
|
||||
* Retrieve a registered procedure registered with the relay instance.
|
||||
*
|
||||
* @param method - Method the route is registered for.
|
||||
* @param path - Path the route is registered under.
|
||||
*
|
||||
* @examples
|
||||
*
|
||||
* ```ts
|
||||
* const relay = new Relay([
|
||||
* route
|
||||
* .post("/users")
|
||||
* .body(
|
||||
* z.object({
|
||||
* name: z.object({ family: z.string(), given: z.string() }),
|
||||
* email: z.string().check(z.email()),
|
||||
* })
|
||||
* )
|
||||
* ]);
|
||||
*
|
||||
* relay
|
||||
* .route("POST", "/users")
|
||||
* .actions([hasSessionUser, hasAccess("users", "create")])
|
||||
* .handle(async ({ name, email, sessionUserId }) => {
|
||||
* // await db.users.insert({ name, email, createdBy: sessionUserId });
|
||||
* })
|
||||
* ```
|
||||
* @param method - Method name assigned to the procedure.
|
||||
*/
|
||||
route<
|
||||
TMethod extends RouteMethod,
|
||||
TPath extends Extract<TRoutes[number], { state: { method: TMethod } }>["state"]["path"],
|
||||
TRoute extends Extract<TRoutes[number], { state: { method: TMethod; path: TPath } }>,
|
||||
>(method: TMethod, path: TPath): TRoute {
|
||||
const route = this.#index.get(`${method} ${path}`);
|
||||
if (route === undefined) {
|
||||
throw new Error(`Relay > Route not found at '${method} ${path}' index`);
|
||||
}
|
||||
return route as TRoute;
|
||||
procedure<TMethod extends keyof TProcedureIndex>(method: TMethod): TProcedureIndex[TMethod] {
|
||||
return this.#index.get(method) as TProcedureIndex[TMethod];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new relay client instance from the instance procedures.
|
||||
*
|
||||
* @param config - Client configuration.
|
||||
*/
|
||||
client(config: RelayClientConfig): this["$inferClient"] {
|
||||
return makeRelayClient(config, this.procedures) as any;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Types
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
function indexProcedures<TProcedures extends Procedures, TProcedureIndex = ProcedureIndex<TProcedures>, TProcedureKey = keyof TProcedureIndex>(
|
||||
procedures: TProcedures,
|
||||
index: Map<TProcedureKey, Procedure>,
|
||||
) {
|
||||
for (const key in procedures) {
|
||||
if (procedures[key] instanceof Procedure) {
|
||||
const method = procedures[key].method as TProcedureKey;
|
||||
if (index.has(method)) {
|
||||
throw new Error(`Relay > Procedure with method '${method}' already exists!`);
|
||||
}
|
||||
index.set(method, procedures[key]);
|
||||
} else {
|
||||
indexProcedures(procedures[key], index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Types
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
type ProcedureIndex<TProcedures extends Procedures> = MergeUnion<FlattenProcedures<TProcedures>>;
|
||||
|
||||
type FlattenProcedures<TProcedures extends Procedures> = {
|
||||
[TKey in keyof TProcedures]: TProcedures[TKey] extends Procedure<infer TState>
|
||||
? Record<TState["method"], TProcedures[TKey]>
|
||||
: TProcedures[TKey] extends Procedures
|
||||
? FlattenProcedures<TProcedures[TKey]>
|
||||
: never;
|
||||
}[keyof TProcedures];
|
||||
|
||||
type MergeUnion<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? { [K in keyof I]: I[K] } : never;
|
||||
|
||||
Reference in New Issue
Block a user