import z, { ZodObject, ZodType } from "zod"; import { Action } from "./action.ts"; import { RelayError } from "./errors.ts"; export class Procedure { readonly type = "rpc" as const; declare readonly args: Args; constructor(readonly state: TState) {} get method(): TState["method"] { return this.state.method; } /** * Params allows for custom casting of URL parameters. If a parameter does not * have a corresponding zod schema the default param type is "string". * * @param params - URL params. * * @examples * * ```ts * relay * .method("user:create") * .params( * z.object({ * bar: z.number() * }) * ) * .handle(async ({ bar }) => { * console.log(typeof bar); // => number * }); * ``` */ params(params: TParams): Procedure & { params: TParams }> { return new Procedure({ ...this.state, params }); } /** * List of route level middleware action to execute before running the * route handler. * * @param actions - Actions to execute on this route. * * @examples * * ```ts * const hasFooBar = action * .make("hasFooBar") * .output(z.object({ foobar: z.number() })) * .handle(async () => { * return { * foobar: 1, * }; * }); * * relay * .method("foo") * .actions([hasFooBar]) * .handle(async ({ foobar }) => { * console.log(typeof foobar); // => number * }); * ``` */ actions>( actions: (TAction | [TAction, TActionFn])[], ): Procedure & { actions: TAction[] }> { return new Procedure({ ...this.state, actions: actions as TAction[] }); } /** * Shape of the response this route produces. This is used by the transform * tools to ensure the client receives parsed data. * * @param response - Response shape of the route. * * @examples * * ```ts * relay * .method("foo") * .result( * z.object({ * bar: z.number() * }) * ) * .handle(async () => { * return { bar: 1 }; * }); * ``` */ result(result: TResult): Procedure & { result: TResult }> { return new Procedure({ ...this.state, result }); } /** * Server handler callback method. * * @param handle - Handle function to trigger when the route is executed. */ handle>(handle: THandleFn): Procedure & { handle: THandleFn }> { return new Procedure({ ...this.state, handle }); } } /* |-------------------------------------------------------------------------------- | Factories |-------------------------------------------------------------------------------- */ export const rpc: { method(method: TMethod): Procedure<{ type: "rpc"; method: TMethod }>; } = { method(method: TMethod): Procedure<{ type: "rpc"; method: TMethod }> { return new Procedure({ type: "rpc", method }); }, }; /* |-------------------------------------------------------------------------------- | Types |-------------------------------------------------------------------------------- */ type State = { method: string; params?: ZodType; actions?: Array; result?: ZodType; handle?: HandleFn; }; type ActionFn = TState["params"] extends ZodType ? (params: z.infer) => TAction["state"]["input"] extends ZodType ? z.infer : void : () => TAction["state"]["input"] extends ZodType ? z.infer : void; type HandleFn = any[], TResponse = any> = ( ...args: TArgs ) => TResponse extends ZodType ? Promise | Response | RelayError> : Promise; type Args = [ ...(TState["params"] extends ZodType ? [z.infer] : []), ...(TState["actions"] extends Array ? [UnionToIntersection>] : []), ]; type MergeAction> = TActions[number] extends Action ? (TState["output"] extends ZodObject ? z.infer : object) : object; type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;