@revas-hq/kit-endpoint v0.0.14
Revas Kit: Endpoint (@revas-hq/kit-endpoint)
The @revas-hq/kit-endpoint package provides the core TypeScript types for defining business logic endpoints and composable middleware, inspired by Go-Kit. It establishes a clear contract for request handling, separate from transport-specific details (like HTTP).
Installation
Install the package using npm or yarn:
npm install @revas-hq/kit-endpoint
# or
yarn add @revas-hq/kit-endpointCore Concepts
- Endpoint: A function representing a single business logic operation. It receives context and a typed request, performs work, and returns a result (success or failure) along with the context.
- Middleware: Higher-order functions that wrap Endpoints to add cross-cutting concerns (logging, auth, validation, metrics, etc.).
- Type Safety: Uses generics (
<TRequest, TResponse>) and discriminated unions (EndpointResult) for robust type checking.
Types and Interfaces
Endpoint<TRequest, TResponse>
The primary type alias for an endpoint function.
import type { Context } from '@revas-hq/kit-context';
import type { EndpointArgs, EndpointResult } from '@revas-hq/kit-endpoint';
// TRequest: Type of the decoded request object for the endpoint (defaults to unknown)
// TResponse: Type of the response payload on success (defaults to unknown)
export type Endpoint<TRequest = unknown, TResponse = unknown> = (
args: EndpointArgs<TRequest> // Takes EndpointArgs object
) => Promise<EndpointResult<TResponse>>; // Returns EndpointResult (discriminated union)EndpointArgs<TRequest>
The structure passed as an argument to an Endpoint.
export interface EndpointArgs<TRequest = unknown> {
/** The request-scoped context (from @revas-hq/kit-context). */
context: Context;
/** The domain-specific request object, decoded by transport layer. */
request: TRequest;
}EndpointResult<TResponse> (Discriminated Union)
The result returned by an Endpoint, indicating success or failure. Check the success property to determine the outcome.
// --- Success Case ---
export interface EndpointSuccessResult<TResponse = unknown> {
success: true;
context: Context; // Potentially modified context
response: TResponse; // The successful response payload
error?: undefined;
}
// --- Failure Case ---
export interface EndpointFailureResult {
success: false;
context: Context; // Context at the point of failure
error: EndpointError; // Details about the error
response?: undefined;
}
// --- The Union Type ---
export type EndpointResult<TResponse = unknown> =
| EndpointSuccessResult<TResponse>
| EndpointFailureResult;EndpointError
Represents a structured error returned on failure.
export interface EndpointError {
/** Numeric code (e.g., HTTP status, gRPC code). */
code: number;
/** Human-readable error message. */
message: string;
/** Optional additional details (type: unknown for safety). */
details?: unknown;
}EndpointMiddleware<TRequest, TResponse>
A higher-order function signature for endpoint middleware (onion pattern).
export type EndpointMiddleware<TRequest = unknown, TResponse = unknown> = (
// Takes the next Endpoint in the chain
next: Endpoint<TRequest, TResponse>
// Returns a new Endpoint that wraps 'next'
) => Endpoint<TRequest, TResponse>;Usage
Defining an Endpoint
Define an async function matching the Endpoint signature. It should handle its specific request type (TRequest) and return either a success (EndpointSuccessResult) or failure (EndpointFailureResult).
import type { Context } from '@revas-hq/kit-context';
import type { Endpoint, EndpointArgs, EndpointResult, EndpointSuccessResult, EndpointFailureResult } from "@revas-hq/kit-endpoint";
// Define specific request and response types
interface GreetRequest {
name: string;
}
interface GreetResponse {
greeting: string;
}
// Implement the endpoint
const greetEndpoint: Endpoint<GreetRequest, GreetResponse> = async (
args: EndpointArgs<GreetRequest>, // Use EndpointArgs with specific request type
): Promise<EndpointResult<GreetResponse>> => {
const { context, request } = args;
// Input validation (example)
if (!request.name || request.name.trim() === '') {
// Return failure result
const failure: EndpointFailureResult = {
success: false,
context: context, // Pass context through
error: {
code: 400, // Bad Request
message: "Name cannot be empty",
},
};
return failure;
}
// Business logic
const greeting = `Hello, ${request.name}!`;
// Return success result
const success: EndpointSuccessResult<GreetResponse> = {
success: true,
context: context, // Pass context through (potentially modified if needed)
response: { greeting },
};
return success;
};Defining Middleware
Define middleware as a function matching the EndpointMiddleware signature.
import type { Endpoint, EndpointArgs, EndpointMiddleware, EndpointResult } from "@revas-hq/kit-endpoint";
// Example: Middleware to log request/response (using generic types)
const loggingMiddleware = <TRequest, TResponse>(
/* options? */
): EndpointMiddleware<TRequest, TResponse> => {
// Return the function that takes the next endpoint
return (next: Endpoint<TRequest, TResponse>): Endpoint<TRequest, TResponse> => {
// Return the wrapped endpoint function
return async (args: EndpointArgs<TRequest>): Promise<EndpointResult<TResponse>> => {
console.log("Endpoint called with request:", args.request);
// Call the next endpoint in the chain
const result = await next(args);
// Log based on success/failure using the discriminated union
if (result.success) {
console.log("Endpoint returned success response:", result.response);
} else {
console.error("Endpoint returned error:", result.error);
}
return result;
};
};
};Applying Middleware
Apply middleware by calling the middleware function with the next endpoint in the chain. Middleware composes like nested function calls.
// Assuming greetEndpoint and loggingMiddleware from above
// Create a specific instance of the logging middleware
const logger = loggingMiddleware<GreetRequest, GreetResponse>();
// Apply the middleware to the endpoint
const endpointWithLogging = logger(greetEndpoint);
// --- Example Invocation (usually done by transport layer like kit-http-react-router) ---
async function invokeEndpoint() {
const context: Context = /* ... get context ... */ ;
const request: GreetRequest = { name: "World" };
const result = await endpointWithLogging({ context, request });
if (result.success) {
console.log("Final Greeting:", result.response.greeting);
} else {
console.error("Invocation Failed:", result.error.message);
}
}
// invokeEndpoint();License
This project is licensed under the MIT License. See the LICENSE file for details.