Skip to content

Latest commit

 

History

History
311 lines (233 loc) · 9.05 KB

File metadata and controls

311 lines (233 loc) · 9.05 KB

Parameter Handling

The Interface API provides a parameter injection system for controller methods. Parameters are automatically extracted from various request sources and processed before being passed to handler methods.

Parameter sources

The API extracts parameters from the following sources:

  • Route parameters - Segments of the URL path marked with :paramName
  • Query parameters - Values from the URL query string
  • HTTP headers - Request header values
  • Request body - Data submitted in the request body
  • Request context - The complete request context object

Parameter decorators

@Parameter

The @Parameter decorator extracts a single value from route parameters, query strings, or headers.

import { Controller, Get, Parameter } from "@antelopejs/interface-api";

class UsersController extends Controller("/users") {
  @Get(":id")
  async getUser(@Parameter("id") id: string) {
    return { id, name: "Example User" };
  }

  @Get()
  async listUsers(@Parameter("page", "query") page: string) {
    // For a request to /users?page=2, page is "2"
    return { users: ["user1", "user2"], page };
  }

  @Get(":userId/posts/:postId")
  async getUserPost(
    @Parameter("userId") userId: string,
    @Parameter("postId") postId: string,
  ) {
    return { userId, postId };
  }
}

The second argument specifies the source:

Source Description Example
"param" Route parameters (default) /users/:id extracts id
"query" URL query parameters /users?page=2 extracts page
"header" HTTP request headers Extracts Authorization header value

@MultiParameter

The @MultiParameter decorator extracts multiple values for a parameter as an array. This is useful for query parameters or headers that appear more than once.

import { Controller, Get, MultiParameter } from "@antelopejs/interface-api";

class ProductsController extends Controller("/products") {
  @Get("search")
  async searchProducts(
    @MultiParameter("tag") tags: string[],
  ) {
    // For /products/search?tag=js&tag=api, tags is ["js", "api"]
    return { tags, results: ["product1", "product2"] };
  }
}

The @MultiParameter decorator accepts a source argument of "query" (default) or "header".

@RawBody

The @RawBody decorator provides the raw HTTP request body as a Buffer.

import { Controller, Post, RawBody } from "@antelopejs/interface-api";

class PostsController extends Controller("/posts") {
  @Post()
  async createPost(@RawBody() postData: Buffer) {
    const parsed = JSON.parse(postData.toString());
    return { id: "new-post-id", ...parsed };
  }
}

@JSONBody

The @JSONBody decorator parses the HTTP request body as JSON and provides the resulting object.

import { Controller, Post, JSONBody } from "@antelopejs/interface-api";

class UsersController extends Controller("/users") {
  @Post()
  async createUser(@JSONBody() userData: { name: string; email: string }) {
    return { id: "new-user-id", ...userData };
  }
}

@Context

The @Context decorator provides access to the complete RequestContext object.

import { Controller, Get, Context, RequestContext } from "@antelopejs/interface-api";

class InfoController extends Controller("/info") {
  @Get()
  handleRequest(@Context() ctx: RequestContext) {
    const clientIP = ctx.rawRequest.socket.remoteAddress;
    const requestUrl = ctx.url.toString();
    return { ip: clientIP, url: requestUrl };
  }
}

@Result

The @Result decorator provides access to the HTTPResult response object. This is typically used in postfix handlers to modify the response.

import { Controller, Get, Postfix, Result, HTTPResult } from "@antelopejs/interface-api";

class ResponseController extends Controller("/api") {
  @Get("data")
  async getData() {
    return { data: "example" };
  }

  @Postfix("get", "data")
  async addMetadata(@Result() result: HTTPResult) {
    result.addHeader("X-Timestamp", new Date().toISOString());
  }
}

@WriteStream

The @WriteStream decorator provides a writable stream for sending data to the client. This is useful for streaming responses or server-sent events.

import { Controller, Get, WriteStream, Context, RequestContext } from "@antelopejs/interface-api";
import { PassThrough } from "node:stream";

class StreamController extends Controller("/stream") {
  @Get()
  async streamData(
    @Context() context: RequestContext,
    @WriteStream("text/event-stream") stream: PassThrough,
  ) {
    stream.write("data: Hello\n\n");

    setTimeout(() => {
      stream.write("data: World\n\n");
      stream.end();
    }, 1000);

    return context.response;
  }
}

@Connection

The @Connection decorator provides access to the WebSocket connection object. It can only be used with @WebsocketHandler.

import { Controller, WebsocketHandler, Connection } from "@antelopejs/interface-api";

class ChatController extends Controller("/chat") {
  @WebsocketHandler()
  handleChat(@Connection() connection: any) {
    connection.on("message", (data: string) => {
      connection.send("Received: " + data);
    });
  }
}

Use decorators on class properties

Both @Parameter and @MultiParameter (as well as @RawBody, @JSONBody, @Context, and @Connection) can be applied to class properties. The property values are automatically populated from the request for each handler invocation.

import { Controller, Get, Parameter, MultiParameter } from "@antelopejs/interface-api";

class ConfigurableController extends Controller("/config") {
  @Parameter("apiKey", "header")
  private apiKey!: string;

  @MultiParameter("tags", "query")
  private tags!: string[];

  @Get()
  async getConfig() {
    return {
      authenticated: Boolean(this.apiKey),
      appliedTags: this.tags,
    };
  }
}

Combine multiple parameters

Handler methods can accept multiple decorated parameters from different sources.

import { Controller, Put, Parameter, JSONBody } from "@antelopejs/interface-api";

class ArticlesController extends Controller("/articles") {
  @Put(":id")
  async updateArticle(
    @Parameter("id") id: string,
    @JSONBody() data: { title: string; content: string },
  ) {
    return { id, ...data, updated: true };
  }
}

Note: Each parameter should have exactly one provider decorator. Do not apply @Parameter to a parameter that already has @RawBody, @JSONBody, or another provider.

Parameter providers and modifiers

The API uses a system of providers and modifiers to process parameters.

Parameter providers

A parameter provider extracts the initial value from the request. The SetParameterProvider function registers a custom provider.

import {
  SetParameterProvider,
  ReadBody,
  RequestContext,
} from "@antelopejs/interface-api";
import { MakeParameterDecorator } from "@antelopejs/interface-core/decorators";

const RawBody = MakeParameterDecorator((target, key, param) =>
  SetParameterProvider(target, key, param, (context: RequestContext) => {
    return ReadBody(context);
  }),
);

Parameter modifiers

A parameter modifier transforms the value after the provider runs. Unlike providers, you can chain multiple modifiers on a single parameter. The AddParameterModifier function registers a modifier.

import {
  AddParameterModifier,
  RequestContext,
} from "@antelopejs/interface-api";
import { MakeParameterDecorator } from "@antelopejs/interface-core/decorators";

const EnsureNumber = MakeParameterDecorator((target, key, param) =>
  AddParameterModifier(target, key, param, (context: RequestContext, value: unknown) => {
    const num = Number(value);
    return isNaN(num) ? 0 : num;
  }),
);

Use custom modifiers alongside built-in decorators:

import { Controller, Get, Parameter } from "@antelopejs/interface-api";

class ExampleController extends Controller("/example") {
  @Get(":id")
  async getExample(@Parameter("id") @EnsureNumber() id: number) {
    return { id, doubled: id * 2 };
  }
}

The @Transform decorator

The @Transform decorator applies a transformation function to any parameter value.

import { Controller, Get, Parameter, Transform, RequestContext } from "@antelopejs/interface-api";

function parseId(context: RequestContext, value: unknown): number {
  return parseInt(value as string, 10);
}

class ItemController extends Controller("/items") {
  @Get(":id")
  getItem(@Parameter("id") @Transform(parseId) id: number) {
    return { id };
  }
}

Processing chain

Parameters follow a processing chain from extraction to injection:

Provider => [Modifier 1] => [Modifier 2] => ... => Parameter/Property

The provider runs first to extract the raw value, then each modifier transforms the value in sequence before the final result is passed to the handler.