npm
Source
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
>=22.0.0 and @wolfstar/http-framework ^3.6.0 || ^5.0.0 || ^6.0.0.
Usage
Import the side-effectingregister 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.
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:
./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 areRoute pieces loaded from a routes directory, exactly like commands/listeners:
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.
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:
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:
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-incookies 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.
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 areMiddleware 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:
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 answers404. Add a method suffix to the file name or passmethods(see Writing a route). - Response helpers return
void. OnlystatusandsetContentTypechain. - Body limit.
readBodyJson()no longer takes alimitargument. SetmaximumBodyLengthinServerOptionsor per route instead. request.queryis a plainRecord<string, string | string[]>, no longer aURLSearchParams.OPTIONSpre-flight requests answer200with an empty body instead of204.404and405responses from theheadersmiddleware also have empty bodies.- Listener piece names are PascalCase, and
ServerEvent.Errorcarries 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.
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-Lengthare not length-limited by thebodymiddleware. - A request with a
Content-Lengthbut noContent-Typebypasses the body limit. - The IPv6
Hostheader edge case is not fixed. response.cookies.add()without an explicitdomainthrows when the requestHostis an IP address. The error surfaces as a route error and a generic500.
Fixed relative to upstream
RouterBranch#pathno longer drops ancestor segments.- Removing a route no longer deletes sibling methods or indexes the wrong branch.
supportedMethodsincludes grandchildren.CookieStoreno longer crashes on malformedCookieheaders.- A
:8080Hostheader no longer produces aNaNport. Server#connect()rejects instead of throwing uncaught when the port is in use.- The request stream is closed when a client disconnects mid-body.