Template
1
0

feat: add functional authentication

This commit is contained in:
2025-08-12 23:11:08 +02:00
parent f0630d43b7
commit 82d7a0d9cd
74 changed files with 763 additions and 396 deletions

View File

@@ -1,4 +1,6 @@
import z, { ZodType } from "zod";
/* eslint-disable @typescript-eslint/no-empty-object-type */
import z, { ZodObject, ZodType } from "zod";
import type { RelayAdapter, RelayInput, RelayResponse } from "./adapter.ts";
import { Route, type Routes } from "./route.ts";
@@ -45,7 +47,7 @@ function getNestedRoute<TRoutes extends Routes>(config: Config, routes: TRoutes)
}
function getRouteFn(route: Route, { adapter }: Config) {
return async (options: any) => {
return async (options: any = {}) => {
const input: RelayInput = {
method: route.state.method,
endpoint: route.state.path,
@@ -146,34 +148,45 @@ type RelayRequest = {
type RelayRoutes<TRoutes extends Routes> = {
[TKey in keyof TRoutes]: TRoutes[TKey] extends Route
? ((
payload: OmitNever<{
params: TRoutes[TKey]["$params"];
query: TRoutes[TKey]["$query"];
body: TRoutes[TKey]["$body"];
headers?: Headers;
}>,
) => Promise<RelayResponse<RelayRouteResponse<TRoutes[TKey]>, RelayRouteErrors<TRoutes[TKey]>>>) & {
$params: TRoutes[TKey]["$params"];
$query: TRoutes[TKey]["$query"];
$body: TRoutes[TKey]["$body"];
$response: TRoutes[TKey]["$response"];
}
? HasPayload<TRoutes[TKey]> extends true
? (
payload: Prettify<
(TRoutes[TKey]["state"]["params"] extends ZodObject ? { params: TRoutes[TKey]["$params"] } : {}) &
(TRoutes[TKey]["state"]["query"] extends ZodObject ? { query: TRoutes[TKey]["$query"] } : {}) &
(TRoutes[TKey]["state"]["body"] extends ZodType ? { body: TRoutes[TKey]["$body"] } : {}) & {
headers?: HeadersInit;
}
>,
) => RouteResponse<TRoutes[TKey]>
: (payload?: { headers: HeadersInit }) => RouteResponse<TRoutes[TKey]>
: TRoutes[TKey] extends Routes
? RelayClient<TRoutes[TKey]>
? RelayRoutes<TRoutes[TKey]>
: never;
};
type RelayRouteResponse<TRoute extends Route> = TRoute["state"]["output"] extends ZodType
type HasPayload<TRoute extends Route> = TRoute["state"]["params"] extends ZodObject
? true
: TRoute["state"]["query"] extends ZodObject
? true
: TRoute["state"]["body"] extends ZodType
? true
: false;
type RouteResponse<TRoute extends Route> = Promise<RelayResponse<RouteOutput<TRoute>, RouteErrors<TRoute>>> & {
$params: TRoute["$params"];
$query: TRoute["$query"];
$body: TRoute["$body"];
$response: TRoute["$response"];
};
type RouteOutput<TRoute extends Route> = TRoute["state"]["output"] extends ZodType
? z.infer<TRoute["state"]["output"]>
: null;
type RelayRouteErrors<TRoute extends Route> = InstanceType<TRoute["state"]["errors"][number]>;
type OmitNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
};
type RouteErrors<TRoute extends Route> = InstanceType<TRoute["state"]["errors"][number]>;
type Config = {
adapter: RelayAdapter;
};
type Prettify<T> = { [K in keyof T]: T[K] } & {};

View File

@@ -10,7 +10,7 @@ export class Route<const TState extends RouteState = RouteState> {
declare readonly $params: TState["params"] extends ZodObject ? z.input<TState["params"]> : never;
declare readonly $query: TState["query"] extends ZodObject ? z.input<TState["query"]> : never;
declare readonly $body: TState["body"] extends ZodType ? z.input<TState["body"]> : never;
declare readonly $response: TState["output"] extends ZodType ? z.output<TState["output"]> : never;
declare readonly $response: TState["response"] extends ZodType ? z.output<TState["response"]> : never;
#matchFn?: MatchFunction<any>;
@@ -69,16 +69,6 @@ export class Route<const TState extends RouteState = RouteState> {
return result.params as TParams;
}
/**
* Set the content the route expects, 'json' or 'form-data' which the client uses
* to determine which adapter operation to execute on requests.
*
* @param content - Content expected during transfers.
*/
content<TContent extends RouteContent>(content: TContent): Route<Omit<TState, "content"> & { content: TContent }> {
return new Route({ ...this.state, content });
}
/**
* Set the meta data for this route which can be used in e.g. OpenAPI generation
*
@@ -218,33 +208,6 @@ export class Route<const TState extends RouteState = RouteState> {
return new Route({ ...this.state, body });
}
/**
* Shape of the success 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
* route
* .post("/foo")
* .response(
* z.object({
* bar: z.number()
* })
* )
* .handle(async () => {
* return {
* bar: 1
* }
* });
* ```
*/
response<TResponse extends ZodType>(output: TResponse): Route<Omit<TState, "output"> & { output: TResponse }> {
return new Route({ ...this.state, output });
}
/**
* Instances of the possible error responses this route produces.
*
@@ -267,6 +230,33 @@ export class Route<const TState extends RouteState = RouteState> {
return new Route({ ...this.state, errors });
}
/**
* Shape of the success 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
* route
* .post("/foo")
* .response(
* z.object({
* bar: z.number()
* })
* )
* .handle(async () => {
* return {
* bar: 1
* }
* });
* ```
*/
response<TResponse extends ZodType>(response: TResponse): Route<Omit<TState, "response"> & { response: TResponse }> {
return new Route({ ...this.state, response });
}
/**
* Server handler callback method.
*
@@ -286,7 +276,7 @@ export class Route<const TState extends RouteState = RouteState> {
* .handle(async ({ bar }, [ "string", number ]) => {});
* ```
*/
handle<THandleFn extends HandleFn<ServerArgs<TState>, TState["output"]>>(
handle<THandleFn extends HandleFn<ServerArgs<TState>, TState["response"]>>(
handle: THandleFn,
): Route<Omit<TState, "handle"> & { handle: THandleFn }> {
return new Route({ ...this.state, handle });
@@ -433,14 +423,13 @@ export type Routes = {
type RouteState = {
method: RouteMethod;
path: string;
content: RouteContent;
meta?: RouteMeta;
access?: RouteAccess;
params?: ZodObject;
query?: ZodObject;
body?: ZodType;
output?: ZodType;
errors: ServerErrorClass[];
response?: ZodType;
handle?: HandleFn;
hooks?: Hooks;
};
@@ -454,8 +443,6 @@ export type RouteMeta = {
export type RouteMethod = "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
export type RouteContent = "json" | "form-data";
export type RouteAccess = "public" | "session" | (() => boolean)[];
export type AccessFn = (resource: string, action: string) => () => boolean;
@@ -466,8 +453,8 @@ export interface ServerContext {}
type HandleFn<TArgs extends Array<any> = any[], TResponse = any> = (
...args: TArgs
) => TResponse extends ZodType
? Promise<z.infer<TResponse> | Response | ServerError | unknown>
: Promise<Response | ServerError | unknown | void>;
? Promise<z.infer<TResponse> | Response | ServerError>
: Promise<Response | ServerError | void>;
type ServerArgs<TState extends RouteState> =
HasInputArgs<TState> extends true