npm package: @revas-hq/kit-endpoint
Description: 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 (li
Latest version: 0.0.16

# 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:

```sh
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.

```ts
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.

```ts
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.

```ts
// --- 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.

```ts
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).

```ts
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`).

```ts
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.

```ts
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.

```ts
// 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.