@wolfstar/plugin-api 0.0.0-next-20260816113531

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ import { n as loadListeners, r as ApiServer, t as loadMiddlewares } from "./src-CscMIChC.js";
2
+ import { Client, Plugin, container, postInitialization, postListen } from "@wolfstar/http-framework";
3
+
4
+ //#region src/register.ts
5
+ /**
6
+ * Registers a standalone {@link ApiServer} for auxiliary REST routes (health checks, dashboards,
7
+ * webhooks from other services, etc), independent from the Discord interactions webhook server.
8
+ *
9
+ * Activate by importing the side-effecting entrypoint before creating the client:
10
+ *
11
+ * ```ts
12
+ * import '@wolfstar/plugin-api/register';
13
+ * ```
14
+ */
15
+ var ApiPlugin = class extends Plugin {
16
+ static [postInitialization](options) {
17
+ const server = new ApiServer(options.api);
18
+ container.stores.register(server.routes).register(server.middlewares);
19
+ loadListeners().catch((error) => console.error("[plugin-api] Failed to load listeners:", error));
20
+ loadMiddlewares().catch((error) => console.error("[plugin-api] Failed to load middlewares:", error));
21
+ }
22
+ static async [postListen](options) {
23
+ if ((options.api?.automaticallyConnect ?? true) === false) return;
24
+ await container.server.connect();
25
+ }
26
+ };
27
+ Client.plugins.registerPostInitializationHook(ApiPlugin[postInitialization], "WolfStar-Api-PostInitialization");
28
+ Client.plugins.registerPostListenHook(ApiPlugin[postListen], "WolfStar-Api-PostListen");
29
+
30
+ //#endregion
31
+ export { ApiPlugin };
32
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","names":[],"sources":["../../src/register.ts"],"sourcesContent":["import {\n Client,\n container,\n Plugin,\n postInitialization,\n postListen,\n type ClientOptions,\n} from \"@wolfstar/http-framework\";\nimport \"./index\";\nimport { ApiServer } from \"./lib/http/ApiServer\";\nimport { loadListeners } from \"./listeners/_load\";\nimport { loadMiddlewares } from \"./middlewares/_load\";\n\n/**\n * Registers a standalone {@link ApiServer} for auxiliary REST routes (health checks, dashboards,\n * webhooks from other services, etc), independent from the Discord interactions webhook server.\n *\n * Activate by importing the side-effecting entrypoint before creating the client:\n *\n * ```ts\n * import '@wolfstar/plugin-api/register';\n * ```\n */\nexport class ApiPlugin extends Plugin {\n public static [postInitialization](this: Client, options: ClientOptions): void {\n const server = new ApiServer(options.api);\n\n container.stores //\n .register(server.routes)\n .register(server.middlewares);\n\n loadListeners().catch((error: unknown) =>\n console.error(\"[plugin-api] Failed to load listeners:\", error),\n );\n loadMiddlewares().catch((error: unknown) =>\n console.error(\"[plugin-api] Failed to load middlewares:\", error),\n );\n }\n\n public static async [postListen](this: Client, options: ClientOptions): Promise<void> {\n if ((options.api?.automaticallyConnect ?? true) === false) return;\n await container.server.connect();\n }\n}\n\nClient.plugins.registerPostInitializationHook(\n ApiPlugin[postInitialization],\n \"WolfStar-Api-PostInitialization\",\n);\nClient.plugins.registerPostListenHook(ApiPlugin[postListen], \"WolfStar-Api-PostListen\");\n"],"mappings":";;;;;;;;;;;;;;AAuBA,IAAa,YAAb,cAA+B,OAAO;CACpC,QAAe,oBAAkC,SAA8B;EAC7E,MAAM,SAAS,IAAI,UAAU,QAAQ,GAAG;EAExC,UAAU,OACP,SAAS,OAAO,MAAM,CAAC,CACvB,SAAS,OAAO,WAAW;EAE9B,cAAc,CAAC,CAAC,OAAO,UACrB,QAAQ,MAAM,0CAA0C,KAAK,CAC/D;EACA,gBAAgB,CAAC,CAAC,OAAO,UACvB,QAAQ,MAAM,4CAA4C,KAAK,CACjE;CACF;CAEA,cAAqB,YAA0B,SAAuC;EACpF,KAAK,QAAQ,KAAK,wBAAwB,UAAU,OAAO;EAC3D,MAAM,UAAU,OAAO,QAAQ;CACjC;AACF;AAEA,OAAO,QAAQ,+BACb,UAAU,qBACV,iCACF;AACA,OAAO,QAAQ,uBAAuB,UAAU,aAAa,yBAAyB"}
@@ -0,0 +1,664 @@
1
+ import { IncomingMessage, ServerResponse, createServer } from "node:http";
2
+ import { HttpCodes, Listener, container } from "@wolfstar/http-framework";
3
+ import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter";
4
+ import { LoaderStrategy, Piece, Store } from "@sapphire/pieces";
5
+
6
+ //#region src/lib/http/ApiRequest.ts
7
+ var ApiRequest = class extends IncomingMessage {
8
+ /**
9
+ * The parsed query string parameters.
10
+ */
11
+ query = new URLSearchParams();
12
+ /**
13
+ * The extracted dynamic (`[param]`) path segment values.
14
+ */
15
+ params = {};
16
+ /**
17
+ * The matched router branch for this request's pathname, if any.
18
+ */
19
+ routerNode = null;
20
+ /**
21
+ * The matched route for this request's pathname and method, if any.
22
+ */
23
+ route = null;
24
+ /**
25
+ * Reads the full request body as a UTF-8 string.
26
+ * @param limit The maximum number of bytes to read before rejecting. Defaults to 1 MiB.
27
+ */
28
+ async readBodyText(limit = 1024 * 1024) {
29
+ const chunks = [];
30
+ let size = 0;
31
+ for await (const chunk of this) {
32
+ size += chunk.length;
33
+ if (size > limit) throw new RangeError(`Request body exceeded the ${limit} byte limit`);
34
+ chunks.push(chunk);
35
+ }
36
+ return Buffer.concat(chunks).toString("utf8");
37
+ }
38
+ /**
39
+ * Reads and parses the request body as JSON.
40
+ * @param limit The maximum number of bytes to read before rejecting. Defaults to 1 MiB.
41
+ */
42
+ async readBodyJson(limit) {
43
+ const text = await this.readBodyText(limit);
44
+ return JSON.parse(text);
45
+ }
46
+ };
47
+
48
+ //#endregion
49
+ //#region src/lib/http/ApiResponse.ts
50
+ var ApiResponse = class extends ServerResponse {
51
+ status(statusCode) {
52
+ this.statusCode = statusCode;
53
+ return this;
54
+ }
55
+ json(body, statusCode = HttpCodes.OK) {
56
+ this.statusCode = statusCode;
57
+ if (!this.hasHeader("Content-Type")) this.setHeader("Content-Type", "application/json; charset=utf-8");
58
+ this.end(JSON.stringify(body));
59
+ return this;
60
+ }
61
+ text(body, statusCode = HttpCodes.OK) {
62
+ this.statusCode = statusCode;
63
+ if (!this.hasHeader("Content-Type")) this.setHeader("Content-Type", "text/plain; charset=utf-8");
64
+ this.end(body);
65
+ return this;
66
+ }
67
+ noContent() {
68
+ this.statusCode = HttpCodes.NoContent;
69
+ this.end();
70
+ return this;
71
+ }
72
+ notFound(body = { error: "Not Found" }) {
73
+ return this.json(body, HttpCodes.NotFound);
74
+ }
75
+ methodNotAllowed(body = { error: "Method Not Allowed" }) {
76
+ return this.json(body, HttpCodes.MethodNotAllowed);
77
+ }
78
+ badRequest(body = { error: "Bad Request" }) {
79
+ return this.json(body, HttpCodes.BadRequest);
80
+ }
81
+ error(statusCode = HttpCodes.InternalServerError, body = { error: "Internal Server Error" }) {
82
+ return this.json(body, statusCode);
83
+ }
84
+ };
85
+
86
+ //#endregion
87
+ //#region src/lib/structures/Middleware.ts
88
+ /**
89
+ * A piece that runs on every incoming request, in ascending {@link Middleware.position} order,
90
+ * before route dispatch. A middleware stops the chain implicitly by ending the response
91
+ * (`response.writableEnded`); there is no explicit `next()` callback.
92
+ */
93
+ var Middleware = class extends Piece {
94
+ /**
95
+ * The built-in middlewares use the following positions:
96
+ * - `headers`: 10 (CORS headers, 404/405 short-circuit)
97
+ * - `body`: 20 (`Content-Length` validation)
98
+ */
99
+ position;
100
+ constructor(context, options) {
101
+ super(context, options);
102
+ this.position = options.position ?? 1e3;
103
+ }
104
+ };
105
+
106
+ //#endregion
107
+ //#region src/lib/structures/MiddlewareStore.ts
108
+ var MiddlewareStore = class extends Store {
109
+ /**
110
+ * Enabled middlewares, kept sorted in ascending {@link Middleware.position} order.
111
+ */
112
+ sortedMiddlewares = [];
113
+ constructor() {
114
+ super(Middleware, { name: "middlewares" });
115
+ }
116
+ /**
117
+ * Runs every enabled middleware in order, stopping early once the response has ended.
118
+ */
119
+ async run(request, response) {
120
+ for (const middleware of this.sortedMiddlewares) {
121
+ if (response.writableEnded) return;
122
+ if (middleware.enabled) await middleware.run(request, response);
123
+ }
124
+ }
125
+ set(key, value) {
126
+ const index = this.sortedMiddlewares.findIndex((middleware) => middleware.position >= value.position);
127
+ if (index === -1) this.sortedMiddlewares.push(value);
128
+ else this.sortedMiddlewares.splice(index, 0, value);
129
+ return super.set(key, value);
130
+ }
131
+ delete(key) {
132
+ const index = this.sortedMiddlewares.findIndex((middleware) => middleware.name === key);
133
+ if (index !== -1) this.sortedMiddlewares.splice(index, 1);
134
+ return super.delete(key);
135
+ }
136
+ clear() {
137
+ this.sortedMiddlewares.length = 0;
138
+ super.clear();
139
+ }
140
+ };
141
+
142
+ //#endregion
143
+ //#region src/lib/http/HttpMethod.ts
144
+ /**
145
+ * The HTTP methods that a {@link Route} can be registered for.
146
+ */
147
+ const HttpMethods = [
148
+ "DELETE",
149
+ "GET",
150
+ "HEAD",
151
+ "OPTIONS",
152
+ "PATCH",
153
+ "POST",
154
+ "PUT"
155
+ ];
156
+
157
+ //#endregion
158
+ //#region src/lib/structures/router/RouterNode.ts
159
+ /**
160
+ * A single path segment's registered routes, keyed by HTTP method.
161
+ */
162
+ var RouterNode = class extends Map {
163
+ branch;
164
+ constructor(branch) {
165
+ super();
166
+ this.branch = branch;
167
+ }
168
+ /**
169
+ * Walks up the branch chain collecting the values captured by dynamic (`[param]`) segments.
170
+ * @param parts The path segments of the matched request, in the same order used to reach this node.
171
+ */
172
+ extractParameters(parts) {
173
+ const params = {};
174
+ let branch = this.branch;
175
+ let index = parts.length - 1;
176
+ while (branch && branch.parent) {
177
+ if (branch.dynamic) params[branch.name] = decodeURIComponent(parts[index]);
178
+ branch = branch.parent;
179
+ index -= 1;
180
+ }
181
+ return params;
182
+ }
183
+ };
184
+
185
+ //#endregion
186
+ //#region src/lib/structures/router/RouterBranch.ts
187
+ /**
188
+ * A single segment of the route trie. Each branch owns exactly one {@link RouterNode} (its
189
+ * method-to-route map) and may have any number of static children plus at most one dynamic
190
+ * (`[param]`) child, which is only checked after every static child fails to match.
191
+ */
192
+ var RouterBranch = class RouterBranch {
193
+ name;
194
+ dynamic;
195
+ parent;
196
+ node;
197
+ staticChildren = [];
198
+ dynamicChild = null;
199
+ constructor(name, dynamic, parent) {
200
+ this.name = name;
201
+ this.dynamic = dynamic;
202
+ this.parent = parent;
203
+ this.node = new RouterNode(this);
204
+ }
205
+ matches(part) {
206
+ return this.dynamic || this.name === part;
207
+ }
208
+ insertAt(parts, index, route) {
209
+ if (index >= parts.length) {
210
+ for (const method of route.methods) this.node.set(method, route);
211
+ return this.node;
212
+ }
213
+ const part = parts[index];
214
+ if (part.startsWith("[") && part.endsWith("]")) {
215
+ if (this.dynamicChild) return this.dynamicChild.insertAt(parts, index + 1, route);
216
+ const branch = new RouterBranch(part.slice(1, -1), true, this);
217
+ this.dynamicChild = branch;
218
+ return branch.insertAt(parts, index + 1, route);
219
+ }
220
+ const staticChild = this.staticChildren.find((branch) => branch.name === part);
221
+ if (staticChild) return staticChild.insertAt(parts, index + 1, route);
222
+ const branch = new RouterBranch(part, false, this);
223
+ this.staticChildren.push(branch);
224
+ return branch.insertAt(parts, index + 1, route);
225
+ }
226
+ removeAt(parts, index, route) {
227
+ if (index >= parts.length) {
228
+ let removed = false;
229
+ for (const method of route.methods) if (this.node.delete(method)) removed = true;
230
+ return removed;
231
+ }
232
+ const part = parts[index];
233
+ const child = this.staticChildren.find((branch) => branch.matches(part)) ?? this.dynamicChild;
234
+ return child ? child.removeAt(parts, index + 1, route) : false;
235
+ }
236
+ findAt(parts, index) {
237
+ if (index >= parts.length) return this;
238
+ const part = parts[index];
239
+ const child = this.staticChildren.find((branch) => branch.matches(part)) ?? this.dynamicChild;
240
+ return child ? child.findAt(parts, index + 1) : null;
241
+ }
242
+ };
243
+
244
+ //#endregion
245
+ //#region src/lib/structures/router/RouterRoot.ts
246
+ const ROOT_NAME = "::ROOT::";
247
+ /**
248
+ * The root of the route trie, owned by {@link RouteStore.router}. Provides the public
249
+ * add/remove/find API plus the static path-parsing helpers used by {@link Route}'s constructor.
250
+ */
251
+ var RouterRoot = class extends RouterBranch {
252
+ constructor() {
253
+ super(ROOT_NAME, false, null);
254
+ }
255
+ add(route) {
256
+ return this.insertAt(route.path, 0, route);
257
+ }
258
+ remove(route) {
259
+ return this.removeAt(route.path, 0, route);
260
+ }
261
+ find(parts) {
262
+ return this.findAt(parts, 0);
263
+ }
264
+ /**
265
+ * Splits a `/`-delimited path into its non-empty segments.
266
+ */
267
+ static normalize(path) {
268
+ if (!path) return [];
269
+ return path.split("/").filter((part) => part.length > 0);
270
+ }
271
+ /**
272
+ * Builds a route path from a piece's directory structure and file name: `(group)`-style
273
+ * directories are skipped, and a file named `index` collapses into its parent directory.
274
+ */
275
+ static makeRoutePathForPiece(directories, name) {
276
+ const parts = [];
277
+ for (const directory of directories) {
278
+ const trimmed = directory.trim();
279
+ if (trimmed.length === 0) continue;
280
+ if (trimmed.startsWith("(") && trimmed.endsWith(")")) continue;
281
+ parts.push(trimmed);
282
+ }
283
+ const trimmedName = name.trim();
284
+ if (trimmedName !== "index") parts.push(trimmedName);
285
+ return parts.join("/");
286
+ }
287
+ /**
288
+ * Extracts a trailing `.<method>` suffix from a piece name, e.g. `hello.post` → `POST`.
289
+ */
290
+ static extractMethod(name) {
291
+ const index = name.lastIndexOf(".");
292
+ if (index === -1) return null;
293
+ const method = name.slice(index + 1).toUpperCase();
294
+ return HttpMethods.includes(method) ? method : null;
295
+ }
296
+ };
297
+
298
+ //#endregion
299
+ //#region src/lib/structures/Route.ts
300
+ /**
301
+ * A single HTTP endpoint. The route's path and methods are either given explicitly via
302
+ * {@link Route.Options.route}/{@link Route.Options.methods}, or inferred from the piece's file
303
+ * system location: directories become path segments (`(group)`-style directories are skipped,
304
+ * `index` collapses into its parent), `[param]` segments become dynamic, and a `.<method>`
305
+ * filename suffix (e.g. `hello.post.ts`) implies that HTTP method.
306
+ */
307
+ var Route = class extends Piece {
308
+ /**
309
+ * The normalized path segments this route is registered under, e.g. `['users', '[id]']`.
310
+ */
311
+ path;
312
+ /**
313
+ * The HTTP methods this route responds to.
314
+ */
315
+ methods;
316
+ constructor(context, options) {
317
+ super(context, options);
318
+ const methods = new Set(options.methods ?? []);
319
+ let path;
320
+ if (options.route) path = options.route;
321
+ else {
322
+ let name = context.name;
323
+ const implied = RouterRoot.extractMethod(name);
324
+ if (implied) {
325
+ name = name.slice(0, name.length - implied.length - 1);
326
+ methods.add(implied);
327
+ }
328
+ path = RouterRoot.makeRoutePathForPiece(this.location.directories, name);
329
+ }
330
+ if (methods.size === 0) methods.add("GET");
331
+ this.path = RouterRoot.normalize(path);
332
+ this.methods = methods;
333
+ }
334
+ };
335
+
336
+ //#endregion
337
+ //#region src/lib/structures/RouteLoaderStrategy.ts
338
+ /**
339
+ * Keeps {@link RouteStore.router} in sync as route pieces are loaded and unloaded.
340
+ */
341
+ var RouteLoaderStrategy = class extends LoaderStrategy {
342
+ onLoad(store, piece) {
343
+ super.onLoad(store, piece);
344
+ store.router.add(piece);
345
+ }
346
+ onUnload(store, piece) {
347
+ super.onUnload(store, piece);
348
+ store.router.remove(piece);
349
+ }
350
+ };
351
+
352
+ //#endregion
353
+ //#region src/lib/structures/RouteStore.ts
354
+ var RouteStore = class extends Store {
355
+ /**
356
+ * The trie used to match an incoming request's pathname to a registered {@link Route}.
357
+ */
358
+ router = new RouterRoot();
359
+ constructor() {
360
+ super(Route, {
361
+ name: "routes",
362
+ strategy: new RouteLoaderStrategy()
363
+ });
364
+ }
365
+ };
366
+
367
+ //#endregion
368
+ //#region src/lib/http/ApiServer.ts
369
+ let ApiServerEvent = /* @__PURE__ */ function(ApiServerEvent) {
370
+ ApiServerEvent["Error"] = "error";
371
+ ApiServerEvent["Request"] = "request";
372
+ ApiServerEvent["RouterBranchNotFound"] = "routerBranchNotFound";
373
+ ApiServerEvent["RouterBranchMethodNotAllowed"] = "routerBranchMethodNotAllowed";
374
+ ApiServerEvent["RouterFound"] = "routerFound";
375
+ ApiServerEvent["RouteError"] = "routeError";
376
+ ApiServerEvent["MiddlewareError"] = "middlewareError";
377
+ return ApiServerEvent;
378
+ }({});
379
+ /**
380
+ * A standalone HTTP server for auxiliary REST routes (health checks, dashboards, webhooks from
381
+ * other services, etc). It is deliberately independent from {@link Client.server}, which is
382
+ * reserved for the Discord interactions webhook.
383
+ */
384
+ var ApiServer = class extends AsyncEventEmitter {
385
+ routes;
386
+ middlewares;
387
+ server;
388
+ options;
389
+ constructor(options = {}) {
390
+ super();
391
+ container.server = this;
392
+ this.options = options;
393
+ const serverOptions = {
394
+ ...options.server,
395
+ IncomingMessage: ApiRequest,
396
+ ServerResponse: ApiResponse
397
+ };
398
+ this.server = createServer(serverOptions);
399
+ this.routes = new RouteStore();
400
+ this.middlewares = new MiddlewareStore();
401
+ this.server.on("error", (error) => this.emit("error", error));
402
+ this.server.on("request", (request, response) => this.emit("request", request, response));
403
+ }
404
+ /**
405
+ * Starts listening for requests.
406
+ */
407
+ connect() {
408
+ return new Promise((resolve, reject) => {
409
+ const onError = (error) => {
410
+ this.server.off("listening", onListening);
411
+ reject(error);
412
+ };
413
+ const onListening = () => {
414
+ this.server.off("error", onError);
415
+ resolve();
416
+ };
417
+ this.server.once("error", onError);
418
+ this.server.once("listening", onListening);
419
+ this.server.listen({
420
+ port: 4e3,
421
+ ...this.options.listenOptions
422
+ });
423
+ });
424
+ }
425
+ /**
426
+ * Stops the server from accepting new connections.
427
+ */
428
+ disconnect() {
429
+ return new Promise((resolve, reject) => {
430
+ this.server.close((error) => error ? reject(error) : resolve());
431
+ });
432
+ }
433
+ };
434
+
435
+ //#endregion
436
+ //#region src/listeners/PluginRouteError.ts
437
+ /**
438
+ * Logs and responds with a 500 when a route's `run` method throws.
439
+ */
440
+ var PluginRouteErrorListener = class extends Listener {
441
+ constructor(context) {
442
+ super(context, {
443
+ emitter: "server",
444
+ event: "routeError"
445
+ });
446
+ }
447
+ run(error, _request, response) {
448
+ console.error("[plugin-api] Route error:", error);
449
+ if (!response.writableEnded) response.error();
450
+ }
451
+ };
452
+
453
+ //#endregion
454
+ //#region src/listeners/PluginServerMiddlewareError.ts
455
+ /**
456
+ * Logs and responds with a 500 when a middleware throws.
457
+ */
458
+ var PluginServerMiddlewareErrorListener = class extends Listener {
459
+ constructor(context) {
460
+ super(context, {
461
+ emitter: "server",
462
+ event: "middlewareError"
463
+ });
464
+ }
465
+ run(error, _request, response) {
466
+ console.error("[plugin-api] Middleware error:", error);
467
+ if (!response.writableEnded) response.error();
468
+ }
469
+ };
470
+
471
+ //#endregion
472
+ //#region src/listeners/PluginServerRequest.ts
473
+ /**
474
+ * Parses the request URL, matches it against the route trie, runs every middleware, then emits
475
+ * the outcome (`routerFound`, `routerBranchNotFound`, or `routerBranchMethodNotAllowed`) for the
476
+ * downstream listeners to handle.
477
+ */
478
+ var PluginServerRequestListener = class extends Listener {
479
+ constructor(context) {
480
+ super(context, {
481
+ emitter: "server",
482
+ event: "request"
483
+ });
484
+ }
485
+ async run(request, response) {
486
+ const [pathname, querystring] = splitUrl(request.url);
487
+ request.query = new URLSearchParams(querystring);
488
+ const parts = RouterRoot.normalize(pathname);
489
+ const branch = container.server.routes.router.find(parts);
490
+ const node = branch?.node ?? null;
491
+ const route = node?.get(request.method ?? "GET") ?? null;
492
+ if (node) request.params = node.extractParameters(parts);
493
+ request.routerNode = branch;
494
+ request.route = route;
495
+ try {
496
+ await container.server.middlewares.run(request, response);
497
+ } catch (error) {
498
+ container.server.emit("middlewareError", error, request, response);
499
+ return;
500
+ }
501
+ if (response.writableEnded) return;
502
+ if (branch === null) container.server.emit("routerBranchNotFound", request, response);
503
+ else if (route === null) container.server.emit("routerBranchMethodNotAllowed", request, response);
504
+ else container.server.emit("routerFound", request, response);
505
+ }
506
+ };
507
+ function splitUrl(url = "/") {
508
+ const index = url.indexOf("?");
509
+ return index === -1 ? [url, ""] : [url.slice(0, index), url.slice(index + 1)];
510
+ }
511
+
512
+ //#endregion
513
+ //#region src/listeners/PluginServerRouterBranchMethodNotAllowed.ts
514
+ /**
515
+ * Responds with a 405 when the pathname matched but no route handles this HTTP method.
516
+ */
517
+ var PluginServerRouterBranchMethodNotAllowedListener = class extends Listener {
518
+ constructor(context) {
519
+ super(context, {
520
+ emitter: "server",
521
+ event: "routerBranchMethodNotAllowed"
522
+ });
523
+ }
524
+ run(_request, response) {
525
+ if (!response.writableEnded) response.methodNotAllowed();
526
+ }
527
+ };
528
+
529
+ //#endregion
530
+ //#region src/listeners/PluginServerRouterBranchNotFound.ts
531
+ /**
532
+ * Responds with a 404 when no route matched the request's pathname.
533
+ */
534
+ var PluginServerRouterBranchNotFoundListener = class extends Listener {
535
+ constructor(context) {
536
+ super(context, {
537
+ emitter: "server",
538
+ event: "routerBranchNotFound"
539
+ });
540
+ }
541
+ run(_request, response) {
542
+ if (!response.writableEnded) response.notFound();
543
+ }
544
+ };
545
+
546
+ //#endregion
547
+ //#region src/listeners/PluginServerRouterFound.ts
548
+ /**
549
+ * Invokes the matched route's `run` method, emitting `routeError` if it throws.
550
+ */
551
+ var PluginServerRouterFoundListener = class extends Listener {
552
+ constructor(context) {
553
+ super(context, {
554
+ emitter: "server",
555
+ event: "routerFound"
556
+ });
557
+ }
558
+ async run(request, response) {
559
+ try {
560
+ await request.route.run(request, response);
561
+ } catch (error) {
562
+ container.server.emit("routeError", error, request, response);
563
+ }
564
+ }
565
+ };
566
+
567
+ //#endregion
568
+ //#region src/listeners/_load.ts
569
+ /**
570
+ * Registers the built-in dispatch-pipeline listeners into the framework's existing listener
571
+ * store, targeting the `server` container entry (see {@link ApiServer}).
572
+ */
573
+ async function loadListeners() {
574
+ await Promise.all([
575
+ container.stores.loadPiece({
576
+ store: "listeners",
577
+ name: "pluginServerRequest",
578
+ piece: PluginServerRequestListener
579
+ }),
580
+ container.stores.loadPiece({
581
+ store: "listeners",
582
+ name: "pluginServerRouterFound",
583
+ piece: PluginServerRouterFoundListener
584
+ }),
585
+ container.stores.loadPiece({
586
+ store: "listeners",
587
+ name: "pluginServerRouterBranchNotFound",
588
+ piece: PluginServerRouterBranchNotFoundListener
589
+ }),
590
+ container.stores.loadPiece({
591
+ store: "listeners",
592
+ name: "pluginServerRouterBranchMethodNotAllowed",
593
+ piece: PluginServerRouterBranchMethodNotAllowedListener
594
+ }),
595
+ container.stores.loadPiece({
596
+ store: "listeners",
597
+ name: "pluginServerMiddlewareError",
598
+ piece: PluginServerMiddlewareErrorListener
599
+ }),
600
+ container.stores.loadPiece({
601
+ store: "listeners",
602
+ name: "pluginRouteError",
603
+ piece: PluginRouteErrorListener
604
+ })
605
+ ]);
606
+ }
607
+
608
+ //#endregion
609
+ //#region src/middlewares/body.ts
610
+ /**
611
+ * Rejects requests whose declared `Content-Length` exceeds {@link ApiServerOptions.maximumBodyLength}.
612
+ * Runs second (position 20), after CORS headers have been set.
613
+ */
614
+ var BodyMiddleware = class extends Middleware {
615
+ constructor(context) {
616
+ super(context, { position: 20 });
617
+ }
618
+ run(request, response) {
619
+ const limit = container.server.options.maximumBodyLength ?? 1024 * 1024 * 50;
620
+ if (Number(request.headers["content-length"] ?? 0) > limit) response.json({ error: "Payload Too Large" }, HttpCodes.PayloadTooLarge);
621
+ }
622
+ };
623
+
624
+ //#endregion
625
+ //#region src/middlewares/headers.ts
626
+ /**
627
+ * Sets CORS headers on every response and short-circuits `OPTIONS` pre-flight requests.
628
+ * Runs first (position 10); 404/405 handling stays the sole responsibility of the router
629
+ * listeners, so it is not duplicated here.
630
+ */
631
+ var HeadersMiddleware = class extends Middleware {
632
+ constructor(context) {
633
+ super(context, { position: 10 });
634
+ }
635
+ run(request, response) {
636
+ const origin = container.server.options.origin ?? "*";
637
+ response.setHeader("Access-Control-Allow-Origin", origin);
638
+ response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
639
+ response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS");
640
+ response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
641
+ if (request.method === "OPTIONS") response.noContent();
642
+ }
643
+ };
644
+
645
+ //#endregion
646
+ //#region src/middlewares/_load.ts
647
+ /**
648
+ * Registers the built-in middlewares (`headers`, `body`) into {@link ApiServer.middlewares}.
649
+ */
650
+ async function loadMiddlewares() {
651
+ await Promise.all([container.stores.loadPiece({
652
+ store: "middlewares",
653
+ name: "headers",
654
+ piece: HeadersMiddleware
655
+ }), container.stores.loadPiece({
656
+ store: "middlewares",
657
+ name: "body",
658
+ piece: BodyMiddleware
659
+ })]);
660
+ }
661
+
662
+ //#endregion
663
+ export { RouteStore as a, RouterBranch as c, MiddlewareStore as d, Middleware as f, ApiServerEvent as i, RouterNode as l, ApiRequest as m, loadListeners as n, Route as o, ApiResponse as p, ApiServer as r, RouterRoot as s, loadMiddlewares as t, HttpMethods as u };
664
+ //# sourceMappingURL=src-CscMIChC.js.map