Skip to main content

npm

Source

See the Plugins guide for how plugins from wolfstar-project/plugins fit alongside the core framework.

Description

A plugin for @wolfstar/http-framework, ported from @sapphire/plugin-api 8.3.1. It exposes a standalone REST API server for health checks, dashboards, or webhooks from other services. It is built on the same @sapphire/pieces Route/Middleware conventions. The plugin is intentionally independent from Client#server (the interactions webhook). That server already claims every path on its port and answers 404 for anything that doesn’t match. This plugin binds its own Server on a separate port instead of sharing the same listener.
Version 2.0.0 matches @sapphire/plugin-api 8.3.1 except for authentication, and renames most exports. If you are upgrading, see Migrating from 1.x.

Installation

Requires Node.js >=22.0.0 and @wolfstar/http-framework ^3.6.0 || ^5.0.0 || ^6.0.0.

Usage

Import the side-effecting register entrypoint before you create your Client:

Activating with modules

On framework 6.1 and later, list the ./module entry in modules in stars.config instead of importing register. It needs the optional @wolfstar/kit peer dependency, at ^0.2.0 || ^0.3.0.
The module reads its options from the api key of stars.config. The [specifier, options] tuple of modules still works. Its options are merged over the api key, so they win. The optional @wolfstar/schema peer dependency types the api key in defineConfig. TypeScript only loads that type once the module is imported, so import @wolfstar/plugin-api/module in stars.config.ts. Module options must be JSON-serialisable. Set anything else, such as server options holding functions, through ClientOptions.api, which is merged over the module options. Without the stars CLI, pass the ./plugin definePlugin factory to the plugins client option:
Use only one of the module, the ./plugin factory, and register. Combining them installs the plugin’s hooks twice. container.server is the running Server. It is an AsyncEventEmitter (see Events). The package also exports version, a string with the installed @wolfstar/plugin-api version:

Writing a route

Routes are Route pieces loaded from a routes directory, exactly like commands/listeners:
The route’s path and method are inferred from its location. src/routes/health.get.ts registers GET /health, and a file named index maps to its directory. A folder named [id] becomes a dynamic segment (request.params.id), and a (group)-style folder is skipped when building the path. You can override the path, methods, and body limit explicitly:
ServerOptions.prefix is prepended to every route path, so with prefix: 'v1' the route above answers GET /v1/status. Params are percent-decoded. A malformed sequence (for example %E0%A4%A) falls back to the raw value instead of failing the request.
A route declared with no methods matches nothing, and its path answers 404. There is no implicit GET. A route whose file name has no method suffix and that passes no methods option is never reachable. Name the file health.get.ts or pass methods.

Reading the request

ApiRequest (Route.Request) extends IncomingMessage: Readers return unknown. Narrow the result with your own validator. Any function works, so Zod, Valibot, or a hand-written guard are all valid:
A malformed body rejects. An uncaught rejection becomes a generic 500. The plugin logs the parser message and never sends it to the client.

Writing the response

ApiResponse (Route.Response) extends ServerResponse. Every helper returns void except status and setContentType, which return this so they chain:
Available helpers: ok, created, noContent, badRequest, unauthorized, forbidden, notFound, methodNotAllowed, conflict, error, respond, status, json, text, image, html, and setContentType. HttpCodes is re-exported from @wolfstar/http-framework, so you can import it from either package.

Cookies

The built-in cookies middleware creates response.cookies, a CookieStore. It is a Map of the parsed Cookie header. Cookies you add or remove are written to Set-Cookie when the response is sent. They are marked Secure when NODE_ENV=production.
The CookieStore skips malformed Cookie pairs (bad percent-encoding or a missing =). When the request Host is an IP address there is no default cookie domain, so pass domain explicitly.

Writing a middleware

Middlewares are Middleware pieces loaded from a middlewares directory. They run in ascending position order (default 1000) before route dispatch. A middleware stops the chain by ending the response:
Built-in middlewares:

ServerOptions

Pass these under the api key of your ClientOptions: Server#connect() rejects (for example with EADDRINUSE) instead of throwing uncaught.

Events

container.server emits ServerEvent members. The built-in listeners implement the pipeline request → middlewares → routerFound / routerBranchNotFound / routerBranchMethodNotAllowed. routerFound then emits middlewareSuccess (the route runs) or middlewareFailure (a middleware already ended the response). Errors surface as middlewareError or routeError. Both built-in listeners log with container.logger.fatal(error) and answer a generic 500. ServerEvent.Error carries an optional request and response. Listener piece names are PascalCase (for example PluginServerRequest).

Migrating from 2.x

Version 3.0.0 drops Node.js 20. The only breaking change is the Node.js requirement, which is now >=22.0.0. The API is the same as in 2.x, so upgrade Node.js and bump the package:

Migrating from 1.x

Version 2.0.0 contains breaking changes. Update your code as follows: Behavior changes:
  • No implicit GET. A route without methods matches nothing, and its path answers 404. Add a method suffix to the file name or pass methods (see Writing a route).
  • Response helpers return void. Only status and setContentType chain.
  • Body limit. readBodyJson() no longer takes a limit argument. Set maximumBodyLength in ServerOptions or per route instead.
  • request.query is a plain Record<string, string | string[]>, no longer a URLSearchParams.
  • OPTIONS pre-flight requests answer 200 with an empty body instead of 204. 404 and 405 responses from the headers middleware also have empty bodies.
  • Listener piece names are PascalCase, and ServerEvent.Error carries an optional request and response.
  • Requirements. @wolfstar/http-framework ^3.6.0 || ^5.0.0 || ^6.0.0. Version 3.0.0 also requires Node.js >=22.0.0 (see Migrating from 2.x).

Scope

The plugin matches @sapphire/plugin-api 8.3.1 except for authentication.
Authentication and OAuth2 are not ported. Auth, ServerOptions.auth, ApiRequest.auth, the auth middleware, and the built-in OAuth routes (loadRoutes) do not exist in this plugin. Write your own auth middleware and routes as needed.
Also not ported: the Client.server augmentation (that is the interactions server here) and the CommonJS build. The framework’s postListen hook replaces preLogin.

Known limitations

These behaviors are kept as they are upstream:
  • Chunked request bodies without a Content-Length are not length-limited by the body middleware.
  • A request with a Content-Length but no Content-Type bypasses the body limit.
  • The IPv6 Host header edge case is not fixed.
  • response.cookies.add() without an explicit domain throws when the request Host is an IP address. The error surfaces as a route error and a generic 500.

Fixed relative to upstream

  • RouterBranch#path no longer drops ancestor segments.
  • Removing a route no longer deletes sibling methods or indexes the wrong branch.
  • supportedMethods includes grandchildren.
  • CookieStore no longer crashes on malformed Cookie headers.
  • A :8080 Host header no longer produces a NaN port.
  • Server#connect() rejects instead of throwing uncaught when the port is in use.
  • The request stream is closed when a client disconnects mid-body.