0.0.14 • Published 6 months ago

@revas-hq/kit-endpoint v0.0.14

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

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-endpoint

Core 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.

0.0.10

6 months ago

0.0.11

6 months ago

0.0.12

6 months ago

0.0.13

6 months ago

0.0.14

6 months ago

0.0.9

6 months ago

0.0.8

6 months ago

0.0.5

7 months ago

0.0.4

7 months ago

0.0.7

6 months ago

0.0.6

6 months ago

0.0.3

1 year ago

0.0.1

1 year ago