release: WRNexusJS 0.8.0
This commit is contained in:
@@ -4,6 +4,13 @@ export interface SchemaLike<T> {
|
||||
parse(input: unknown): T;
|
||||
}
|
||||
|
||||
export interface OutputSchemaLike<T> {
|
||||
readonly __output: T;
|
||||
parse(input: unknown): unknown;
|
||||
}
|
||||
export type InferEndpointSchema<TSchema> =
|
||||
TSchema extends OutputSchemaLike<infer TValue> ? TValue : never;
|
||||
|
||||
export interface EndpointErrorBody {
|
||||
code: string;
|
||||
message: string;
|
||||
@@ -23,8 +30,8 @@ export class EndpointError extends Error {
|
||||
}
|
||||
|
||||
export interface EndpointDefinition<I, O> {
|
||||
input?: SchemaLike<I>;
|
||||
output?: SchemaLike<O>;
|
||||
input?: SchemaLike<I> | OutputSchemaLike<I>;
|
||||
output?: SchemaLike<O> | OutputSchemaLike<O>;
|
||||
auth?: "optional" | "required";
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
@@ -43,18 +50,55 @@ function json(body: unknown, status = 200): Response {
|
||||
});
|
||||
}
|
||||
|
||||
function schemaValue<T>(schema: SchemaLike<T> | OutputSchemaLike<T>, input: unknown): T {
|
||||
const parsed = schema.parse(input);
|
||||
if (
|
||||
parsed &&
|
||||
typeof parsed === "object" &&
|
||||
"ok" in parsed &&
|
||||
"value" in parsed &&
|
||||
typeof (parsed as { ok?: unknown }).ok === "boolean"
|
||||
) {
|
||||
const result = parsed as { ok: boolean; value: T; errors?: unknown };
|
||||
if (!result.ok)
|
||||
throw new EndpointError(
|
||||
400,
|
||||
"VALIDATION_ERROR",
|
||||
"Endpoint validation failed.",
|
||||
result.errors,
|
||||
);
|
||||
return result.value;
|
||||
}
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
/** Define a validated, typed endpoint that can also drive SDK/OpenAPI generation. */
|
||||
export function defineEndpoint<
|
||||
InputSchema extends OutputSchemaLike<unknown>,
|
||||
OutputSchema extends OutputSchemaLike<unknown>,
|
||||
>(
|
||||
definition: Omit<
|
||||
EndpointDefinition<InferEndpointSchema<InputSchema>, InferEndpointSchema<OutputSchema>>,
|
||||
"input" | "output"
|
||||
> & {
|
||||
input: InputSchema;
|
||||
output: OutputSchema;
|
||||
},
|
||||
): DefinedEndpoint<InferEndpointSchema<InputSchema>, InferEndpointSchema<OutputSchema>>;
|
||||
export function defineEndpoint<I = unknown, O = unknown>(
|
||||
definition: EndpointDefinition<I, O>,
|
||||
): DefinedEndpoint<I, O> {
|
||||
): DefinedEndpoint<I, O>;
|
||||
export function defineEndpoint(
|
||||
definition: EndpointDefinition<unknown, unknown>,
|
||||
): DefinedEndpoint<unknown, unknown> {
|
||||
const endpoint = async (ctx: Context, rawInput?: unknown): Promise<Response> => {
|
||||
try {
|
||||
if (definition.auth === "required" && !ctx.user) {
|
||||
throw new EndpointError(401, "UNAUTHENTICATED", "Authentication is required.");
|
||||
}
|
||||
const input = definition.input ? definition.input.parse(rawInput) : (rawInput as I);
|
||||
const input = definition.input ? schemaValue(definition.input, rawInput) : rawInput;
|
||||
const rawOutput = await definition.handler(input, ctx);
|
||||
const output = definition.output ? definition.output.parse(rawOutput) : rawOutput;
|
||||
const output = definition.output ? schemaValue(definition.output, rawOutput) : rawOutput;
|
||||
return output instanceof Response ? output : json({ data: output });
|
||||
} catch (error) {
|
||||
if (error instanceof EndpointError) {
|
||||
|
||||
Reference in New Issue
Block a user