Template
1
0

feat: convert to rpc pattern

This commit is contained in:
2025-04-21 00:18:46 +00:00
parent e5f2be1995
commit 3b9a5cb456
15 changed files with 489 additions and 806 deletions

View File

@@ -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;