@maroonedsoftware/koa 1.16.6 → 1.17.1

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.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Peer dependencies: `koa`, `@koa/router`, `@koa/cors`.
15
15
  - **ServerKitContext** — Koa context extended with `container`, `logger`, `requestId`, `correlationId`, `authenticationContext`, and related request metadata
16
16
  - **ServerKitRouter** — Router typed for `ServerKitContext`
17
17
  - **ServerKitMiddleware** — Middleware type bound to `ServerKitContext`
18
- - **serverKitContextMiddleware** — Populates context with scoped container, logger, and request/correlation IDs
18
+ - **serverKitContextMiddleware** — Populates context with scoped container, logger, and request/correlation IDs; registers the live context against the `ServerKitContext` injection token so request-scoped services can inject it
19
19
  - **corsMiddleware** — CORS headers with `'*'`, string, or RegExp origin matching
20
20
  - **errorMiddleware** — Central error handler; maps HTTP errors to status/body, 404 for unmatched routes, 500 for unknown errors
21
21
  - **rateLimiterMiddleware** — Per-IP rate limiting via `rate-limiter-flexible` (429 when exceeded)
@@ -74,6 +74,24 @@ router.get('/api/users/:id', async ctx => {
74
74
  });
75
75
  ```
76
76
 
77
+ ### Injecting the context
78
+
79
+ `ServerKitContext` is also exported as an injectkit token. After `serverKitContextMiddleware` runs, the live `ctx` is registered against this token in the request-scoped container, so services resolved from `ctx.container` can declare it as a dependency:
80
+
81
+ ```typescript
82
+ import { Injectable } from 'injectkit';
83
+ import { ServerKitContext } from '@maroonedsoftware/koa';
84
+
85
+ @Injectable()
86
+ class CurrentUserService {
87
+ constructor(private readonly ctx: ServerKitContext) {}
88
+
89
+ get actorId() {
90
+ return this.ctx.authenticationContext?.actorId;
91
+ }
92
+ }
93
+ ```
94
+
77
95
  ### Authentication
78
96
 
79
97
  `authenticationMiddleware` reads the `Authorization` header, delegates resolution to the `AuthenticationSchemeHandler` registered in the DI container, and populates `ctx.authenticationContext`. The header is deleted from `ctx.req.headers` immediately after reading so it cannot be captured by downstream logging.
package/dist/index.js CHANGED
@@ -1,6 +1,24 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/serverkit.context.ts
5
+ import { Injectable } from "injectkit";
6
+ function _ts_decorate(decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
9
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
10
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
11
+ }
12
+ __name(_ts_decorate, "_ts_decorate");
13
+ var ServerKitContext = class {
14
+ static {
15
+ __name(this, "ServerKitContext");
16
+ }
17
+ };
18
+ ServerKitContext = _ts_decorate([
19
+ Injectable()
20
+ ], ServerKitContext);
21
+
4
22
  // src/serverkit.router.ts
5
23
  import Router from "@koa/router";
6
24
  var ServerKitRouter = /* @__PURE__ */ __name((options) => new Router(options), "ServerKitRouter");
@@ -120,8 +138,10 @@ import crypto from "crypto";
120
138
  import { Logger } from "@maroonedsoftware/logger";
121
139
  var serverKitContextMiddleware = /* @__PURE__ */ __name((container) => {
122
140
  return async (ctx, next) => {
123
- ctx.container = container.createScopedContainer();
124
- ctx.logger = container.get(Logger);
141
+ const scopedContainer = container.createScopedContainer();
142
+ ctx.container = scopedContainer;
143
+ scopedContainer.override(ServerKitContext, ctx);
144
+ ctx.logger = ctx.container.get(Logger);
125
145
  ctx.loggerName = ctx.path;
126
146
  ctx.userAgent = ctx.get("user-agent");
127
147
  ctx.ipAddress = ctx.ip;
@@ -153,16 +173,16 @@ var authenticationMiddleware = /* @__PURE__ */ __name(() => {
153
173
  import { httpError as httpError3, IsHttpError as IsHttpError2 } from "@maroonedsoftware/errors";
154
174
 
155
175
  // src/serverkit.bodyparser.ts
156
- import { Injectable } from "injectkit";
176
+ import { Injectable as Injectable2 } from "injectkit";
157
177
  import { unique } from "@maroonedsoftware/utilities";
158
178
  import { httpError as httpError2 } from "@maroonedsoftware/errors";
159
- function _ts_decorate(decorators, target, key, desc) {
179
+ function _ts_decorate2(decorators, target, key, desc) {
160
180
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
161
181
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
162
182
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
163
183
  return c > 3 && r && Object.defineProperty(target, key, r), r;
164
184
  }
165
- __name(_ts_decorate, "_ts_decorate");
185
+ __name(_ts_decorate2, "_ts_decorate");
166
186
  function _ts_metadata(k, v) {
167
187
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
168
188
  }
@@ -172,8 +192,8 @@ var ServerKitParserMappings = class extends Map {
172
192
  __name(this, "ServerKitParserMappings");
173
193
  }
174
194
  };
175
- ServerKitParserMappings = _ts_decorate([
176
- Injectable()
195
+ ServerKitParserMappings = _ts_decorate2([
196
+ Injectable2()
177
197
  ], ServerKitParserMappings);
178
198
  var ServerKitBodyParser = class {
179
199
  static {
@@ -208,8 +228,8 @@ var ServerKitBodyParser = class {
208
228
  return parser.parse(ctx.req);
209
229
  }
210
230
  };
211
- ServerKitBodyParser = _ts_decorate([
212
- Injectable(),
231
+ ServerKitBodyParser = _ts_decorate2([
232
+ Injectable2(),
213
233
  _ts_metadata("design:type", Function),
214
234
  _ts_metadata("design:paramtypes", [
215
235
  typeof ServerKitParserMappings === "undefined" ? Object : ServerKitParserMappings
@@ -300,21 +320,21 @@ var requireSignature = /* @__PURE__ */ __name((optionsKey) => {
300
320
  }, "requireSignature");
301
321
 
302
322
  // src/parsers/serverkit.parser.ts
303
- import { Injectable as Injectable2 } from "injectkit";
304
- function _ts_decorate2(decorators, target, key, desc) {
323
+ import { Injectable as Injectable3 } from "injectkit";
324
+ function _ts_decorate3(decorators, target, key, desc) {
305
325
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
306
326
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
307
327
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
308
328
  return c > 3 && r && Object.defineProperty(target, key, r), r;
309
329
  }
310
- __name(_ts_decorate2, "_ts_decorate");
330
+ __name(_ts_decorate3, "_ts_decorate");
311
331
  var ServerKitParser = class {
312
332
  static {
313
333
  __name(this, "ServerKitParser");
314
334
  }
315
335
  };
316
- ServerKitParser = _ts_decorate2([
317
- Injectable2()
336
+ ServerKitParser = _ts_decorate3([
337
+ Injectable3()
318
338
  ], ServerKitParser);
319
339
 
320
340
  // src/parsers/json.parser.ts
@@ -322,14 +342,14 @@ import { parse } from "@hapi/bourne";
322
342
  import raw from "raw-body";
323
343
  import inflate from "inflation";
324
344
  import { httpError as httpError6 } from "@maroonedsoftware/errors";
325
- import { Injectable as Injectable3 } from "injectkit";
326
- function _ts_decorate3(decorators, target, key, desc) {
345
+ import { Injectable as Injectable4 } from "injectkit";
346
+ function _ts_decorate4(decorators, target, key, desc) {
327
347
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
328
348
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
329
349
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
330
350
  return c > 3 && r && Object.defineProperty(target, key, r), r;
331
351
  }
332
- __name(_ts_decorate3, "_ts_decorate");
352
+ __name(_ts_decorate4, "_ts_decorate");
333
353
  function _ts_metadata2(k, v) {
334
354
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
335
355
  }
@@ -345,8 +365,8 @@ var JsonParserOptions = class {
345
365
  limit;
346
366
  length;
347
367
  };
348
- JsonParserOptions = _ts_decorate3([
349
- Injectable3()
368
+ JsonParserOptions = _ts_decorate4([
369
+ Injectable4()
350
370
  ], JsonParserOptions);
351
371
  var strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;
352
372
  var JsonParser = class extends ServerKitParser {
@@ -415,8 +435,8 @@ var JsonParser = class extends ServerKitParser {
415
435
  };
416
436
  }
417
437
  };
418
- JsonParser = _ts_decorate3([
419
- Injectable3(),
438
+ JsonParser = _ts_decorate4([
439
+ Injectable4(),
420
440
  _ts_metadata2("design:type", Function),
421
441
  _ts_metadata2("design:paramtypes", [
422
442
  typeof JsonParserOptions === "undefined" ? Object : JsonParserOptions
@@ -424,16 +444,16 @@ JsonParser = _ts_decorate3([
424
444
  ], JsonParser);
425
445
 
426
446
  // src/parsers/text.parser.ts
427
- import { Injectable as Injectable4 } from "injectkit";
447
+ import { Injectable as Injectable5 } from "injectkit";
428
448
  import raw2 from "raw-body";
429
449
  import inflate2 from "inflation";
430
- function _ts_decorate4(decorators, target, key, desc) {
450
+ function _ts_decorate5(decorators, target, key, desc) {
431
451
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
432
452
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
433
453
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
434
454
  return c > 3 && r && Object.defineProperty(target, key, r), r;
435
455
  }
436
- __name(_ts_decorate4, "_ts_decorate");
456
+ __name(_ts_decorate5, "_ts_decorate");
437
457
  function _ts_metadata3(k, v) {
438
458
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
439
459
  }
@@ -477,8 +497,8 @@ var TextParser = class extends ServerKitParser {
477
497
  };
478
498
  }
479
499
  };
480
- TextParser = _ts_decorate4([
481
- Injectable4(),
500
+ TextParser = _ts_decorate5([
501
+ Injectable5(),
482
502
  _ts_metadata3("design:type", Function),
483
503
  _ts_metadata3("design:paramtypes", [
484
504
  typeof TextParserOptions === "undefined" ? Object : TextParserOptions
@@ -487,17 +507,17 @@ TextParser = _ts_decorate4([
487
507
 
488
508
  // src/parsers/form.parser.ts
489
509
  import { httpError as httpError7 } from "@maroonedsoftware/errors";
490
- import { Injectable as Injectable5 } from "injectkit";
510
+ import { Injectable as Injectable6 } from "injectkit";
491
511
  import { parse as parse2 } from "qs";
492
512
  import raw3 from "raw-body";
493
513
  import inflate3 from "inflation";
494
- function _ts_decorate5(decorators, target, key, desc) {
514
+ function _ts_decorate6(decorators, target, key, desc) {
495
515
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
496
516
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
497
517
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
498
518
  return c > 3 && r && Object.defineProperty(target, key, r), r;
499
519
  }
500
- __name(_ts_decorate5, "_ts_decorate");
520
+ __name(_ts_decorate6, "_ts_decorate");
501
521
  function _ts_metadata4(k, v) {
502
522
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
503
523
  }
@@ -513,8 +533,8 @@ var FormParserOptions = class {
513
533
  depth;
514
534
  parameterLimit;
515
535
  };
516
- FormParserOptions = _ts_decorate5([
517
- Injectable5()
536
+ FormParserOptions = _ts_decorate6([
537
+ Injectable6()
518
538
  ], FormParserOptions);
519
539
  var FormParser = class extends ServerKitParser {
520
540
  static {
@@ -552,8 +572,8 @@ var FormParser = class extends ServerKitParser {
552
572
  }
553
573
  }
554
574
  };
555
- FormParser = _ts_decorate5([
556
- Injectable5(),
575
+ FormParser = _ts_decorate6([
576
+ Injectable6(),
557
577
  _ts_metadata4("design:type", Function),
558
578
  _ts_metadata4("design:paramtypes", [
559
579
  typeof FormParserOptions === "undefined" ? Object : FormParserOptions
@@ -561,15 +581,15 @@ FormParser = _ts_decorate5([
561
581
  ], FormParser);
562
582
 
563
583
  // src/parsers/multipart.parser.ts
564
- import { Injectable as Injectable6 } from "injectkit";
584
+ import { Injectable as Injectable7 } from "injectkit";
565
585
  import { MultipartBody } from "@maroonedsoftware/multipart";
566
- function _ts_decorate6(decorators, target, key, desc) {
586
+ function _ts_decorate7(decorators, target, key, desc) {
567
587
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
568
588
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
569
589
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
570
590
  return c > 3 && r && Object.defineProperty(target, key, r), r;
571
591
  }
572
- __name(_ts_decorate6, "_ts_decorate");
592
+ __name(_ts_decorate7, "_ts_decorate");
573
593
  var MultipartParser = class extends ServerKitParser {
574
594
  static {
575
595
  __name(this, "MultipartParser");
@@ -587,21 +607,21 @@ var MultipartParser = class extends ServerKitParser {
587
607
  };
588
608
  }
589
609
  };
590
- MultipartParser = _ts_decorate6([
591
- Injectable6()
610
+ MultipartParser = _ts_decorate7([
611
+ Injectable7()
592
612
  ], MultipartParser);
593
613
 
594
614
  // src/parsers/binary.parser.ts
595
- import { Injectable as Injectable7 } from "injectkit";
615
+ import { Injectable as Injectable8 } from "injectkit";
596
616
  import raw4 from "raw-body";
597
617
  import inflate4 from "inflation";
598
- function _ts_decorate7(decorators, target, key, desc) {
618
+ function _ts_decorate8(decorators, target, key, desc) {
599
619
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
600
620
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
601
621
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
602
622
  return c > 3 && r && Object.defineProperty(target, key, r), r;
603
623
  }
604
- __name(_ts_decorate7, "_ts_decorate");
624
+ __name(_ts_decorate8, "_ts_decorate");
605
625
  var BinaryParser = class extends ServerKitParser {
606
626
  static {
607
627
  __name(this, "BinaryParser");
@@ -619,8 +639,8 @@ var BinaryParser = class extends ServerKitParser {
619
639
  };
620
640
  }
621
641
  };
622
- BinaryParser = _ts_decorate7([
623
- Injectable7()
642
+ BinaryParser = _ts_decorate8([
643
+ Injectable8()
624
644
  ], BinaryParser);
625
645
 
626
646
  // src/parsers/serverkit.default.parsers.ts
@@ -639,6 +659,7 @@ export {
639
659
  JsonParserOptions,
640
660
  MultipartParser,
641
661
  ServerKitBodyParser,
662
+ ServerKitContext,
642
663
  ServerKitParser,
643
664
  ServerKitParserMappings,
644
665
  ServerKitRouter,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/serverkit.router.ts","../src/middleware/server/cors.middleware.ts","../src/middleware/server/error.middleware.ts","../src/middleware/server/rate.limiter.middleware.ts","../src/middleware/server/serverkit.context.middleware.ts","../src/middleware/server/authentication.middleware.ts","../src/middleware/router/body.parser.middleware.ts","../src/serverkit.bodyparser.ts","../src/middleware/router/require.security.middleware.ts","../src/middleware/router/require.signature.middleware.ts","../src/parsers/serverkit.parser.ts","../src/parsers/json.parser.ts","../src/parsers/text.parser.ts","../src/parsers/form.parser.ts","../src/parsers/multipart.parser.ts","../src/parsers/binary.parser.ts","../src/parsers/serverkit.default.parsers.ts"],"sourcesContent":["import { DefaultState } from 'koa';\nimport Router, { RouterOptions } from '@koa/router';\nimport { ServerKitContext } from './serverkit.context.js';\n\n/**\n * Creates a new Koa router typed for ServerKit state and context.\n * Use with {@link ServerKitContext} for full typing of `ctx` in route handlers.\n *\n * @typeParam StateT - Koa state type (defaults to `DefaultState`).\n * @typeParam ContextT - Context type (defaults to `ServerKitContext`).\n * @param options - Router options (defaults to `undefined`).\n * @returns A new {@link Router} instance with the given options.\n */\nexport const ServerKitRouter = <StateT = DefaultState, ContextT = ServerKitContext>(options?: RouterOptions) => new Router<StateT, ContextT>(options);\n","import cors from '@koa/cors';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { Context } from 'koa';\n\n/**\n * CORS options for {@link corsMiddleware}.\n * Extends `@koa/cors` options with an `origin` that may be a string or array of strings/RegExps.\n */\nexport interface CorsOptions extends Omit<cors.Options, 'origin'> {\n /** Allowed origin(s): `'*'`, a single origin string, or an array of strings/RegExps to match. */\n origin?: string | (string | RegExp)[];\n}\n\n/**\n * Adds CORS headers to responses using `@koa/cors` with ServerKit-compatible origin matching.\n * Supports `'*'`, exact string origins, and RegExp patterns.\n *\n * @param options - Optional {@link CorsOptions}; defaults to `GET,HEAD,PUT,POST,DELETE,PATCH` methods.\n * @returns {@link ServerKitMiddleware} that applies CORS headers.\n */\nexport const corsMiddleware = (options?: CorsOptions): ServerKitMiddleware => {\n // return the request origin as its own matcher to support RegExp\n const originMatcher = (ctx: Context): string => {\n const origin = ctx.get('origin');\n const matchers = options?.origin ?? ['*'];\n for (const matcher of matchers) {\n if (matcher === '*') {\n return origin;\n }\n\n if (typeof matcher === 'string') {\n if (matcher === origin) {\n return origin;\n }\n continue;\n }\n\n if (matcher.test(origin)) {\n return origin;\n }\n }\n\n // return the zero value to prevent matches\n return '';\n };\n\n return cors({\n ...options,\n origin: originMatcher,\n allowMethods: options?.allowMethods ?? 'GET,HEAD,PUT,POST,DELETE,PATCH',\n secureContext: options?.secureContext ?? false,\n keepHeadersOnError: options?.keepHeadersOnError ?? false,\n privateNetworkAccess: options?.privateNetworkAccess ?? false,\n });\n};\n","import { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { IsHttpError, IsServerkitError } from '@maroonedsoftware/errors';\n\n/**\n * Central error handler: catches thrown errors, sets status/body from HTTP errors,\n * returns 404 for unmatched routes, and 500 for unknown errors.\n * Emits `error` or `warn` on the app for logging.\n *\n * @returns {@link ServerKitMiddleware} that wraps the stack in try/catch and normalizes responses.\n */\nexport const errorMiddleware = (): ServerKitMiddleware => {\n return async (ctx, next) => {\n try {\n await next();\n if (ctx.status === 404 && !ctx.body) {\n const body = {\n statusCode: 404,\n message: 'Not Found',\n details: { url: ctx.URL.toString() },\n };\n ctx.status = 404;\n ctx.body = body;\n ctx.app.emit('warn', body, ctx);\n }\n } catch (error) {\n if (IsHttpError(error)) {\n ctx.status = error.statusCode;\n ctx.body = {\n statusCode: error.statusCode,\n message: error.message,\n details: error.details,\n };\n if (error.headers) {\n for (const entry of Object.entries(error.headers)) {\n ctx.set(entry[0], entry[1]);\n }\n }\n } else if (IsServerkitError(error)) {\n ctx.status = 500;\n ctx.body = {\n statusCode: 500,\n message: error.message,\n details: error.details,\n };\n } else {\n ctx.status = 500;\n ctx.body = {\n statusCode: 500,\n message: 'Internal Server Error',\n };\n }\n\n ctx.app.emit('error', error, ctx);\n }\n };\n};\n","import { RateLimiterAbstract, RateLimiterRes } from 'rate-limiter-flexible';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\n\n/** Type guard that narrows `error` to a `RateLimiterRes` (i.e. a rate-limit exceeded response). */\nconst isRateLimiterError = (error: unknown): error is RateLimiterRes => {\n return error instanceof RateLimiterRes || ('msBeforeNext' in (error as object) && 'remainingPoints' in (error as object));\n};\n\n/**\n * Enforces rate limiting per client IP using a `rate-limiter-flexible` instance.\n * Consumes one token per request; throws HTTP 429 when the limit is exceeded.\n *\n * @param rateLimiter - A {@link RateLimiterAbstract} instance (e.g. `RateLimiterMemory`, `RateLimiterRedis`).\n * @returns {@link ServerKitMiddleware} that consumes a token and continues or throws 429.\n */\nexport const rateLimiterMiddleware = (rateLimiter: RateLimiterAbstract): ServerKitMiddleware => {\n return async (ctx, next) => {\n try {\n await rateLimiter.consume(ctx.ip);\n } catch (error: unknown) {\n let headers: Record<string, string> = {};\n if (isRateLimiterError(error)) {\n headers = {\n 'retry-after': (error.msBeforeNext / 1000).toString(),\n 'x-ratelimit-limit': rateLimiter.points.toString(),\n 'x-ratelimit-remaining': error.remainingPoints.toString(),\n 'x-ratelimit-reset': Math.ceil((Date.now() + error.msBeforeNext) / 1000).toString(),\n };\n }\n\n throw httpError(429)\n .withCause(error as Error)\n .withHeaders(headers);\n }\n\n await next();\n };\n};\n","import crypto from 'crypto';\nimport { Container } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\n\n/**\n * Populates {@link ServerKitContext} for each request: scoped container, logger,\n * logger name, user-agent, correlation ID, and request ID.\n * Reads or generates `X-Correlation-Id` and `X-Request-Id` and sets response headers.\n * Should be applied early so downstream middleware and routes can use `ctx.container` and `ctx.logger`.\n *\n * @param container - Root injectkit {@link Container} used to create a scoped container and resolve {@link Logger}.\n * @returns {@link ServerKitMiddleware} that attaches ServerKit context to `ctx`.\n */\nexport const serverKitContextMiddleware = (container: Container): ServerKitMiddleware => {\n return async (ctx, next) => {\n ctx.container = container.createScopedContainer();\n ctx.logger = container.get(Logger);\n ctx.loggerName = ctx.path;\n\n ctx.userAgent = ctx.get('user-agent');\n ctx.ipAddress = ctx.ip;\n\n const correlationId = ctx.headers['x-correlation-id'];\n ctx.correlationId = Array.isArray(correlationId) ? (correlationId[0] ?? crypto.randomUUID()) : (correlationId ?? crypto.randomUUID());\n\n ctx.requestId = crypto.randomUUID();\n\n ctx.headers['x-correlation-id'] = ctx.correlationId;\n ctx.set('x-correlation-id', ctx.correlationId);\n\n ctx.headers['x-request-id'] = ctx.requestId;\n ctx.set('x-request-id', ctx.requestId);\n\n await next();\n };\n};\n","import { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { AuthenticationSchemeHandler, invalidAuthenticationContext } from '@maroonedsoftware/authentication';\n\n/**\n * Resolves the `Authorization` request header into an {@link AuthenticationContext}\n * and attaches it to `ctx.authenticationContext`.\n *\n * The header is immediately removed from `ctx.req.headers` after being read so it\n * cannot be accidentally captured by downstream logging or serialization.\n *\n * Resolution is delegated to the {@link AuthenticationSchemeHandler} registered in\n * the DI container. `ctx.authenticationContext` is initialised to\n * {@link invalidAuthenticationContext} before delegation, ensuring that any error\n * thrown by the scheme handler leaves the context in a safe, unauthenticated state.\n *\n * @returns A {@link ServerKitMiddleware} that populates `ctx.authenticationContext`.\n *\n * @example\n * ```typescript\n * app.use(authenticationMiddleware());\n * ```\n */\nexport const authenticationMiddleware = (): ServerKitMiddleware => {\n return async (ctx, next) => {\n ctx.authenticationContext = invalidAuthenticationContext; // bad initial state so it will fail verification\n\n // NOTE: we delete the auth headers on the request here to ensure we don't accidentally log it\n const authorizationHeader = ctx.req.headers.authorization;\n delete ctx.req.headers.authorization;\n\n const schemeHandler = ctx.container.get(AuthenticationSchemeHandler);\n\n ctx.authenticationContext = await schemeHandler.handle(authorizationHeader);\n\n await next();\n };\n};\n","import { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { ServerKitBodyParser } from '../../serverkit.bodyparser.js';\n\n/**\n * Parses the request body based on `Content-Type` and assigns it to `ctx.body`.\n * Rejects requests with unexpected or unsupported content types.\n *\n * Supported types: JSON, URL-encoded form, text, multipart, PDF (raw buffer).\n * Requires a body when `contentTypes` is non-empty; otherwise rejects bodies.\n *\n * @param contentTypes - Allowed MIME types (e.g. `['application/json', 'application/x-www-form-urlencoded']`).\n * Use an empty array to disallow any request body.\n * @returns {@link ServerKitRouterMiddleware} that parses the body and sets `ctx.body`.\n * @throws HTTP 400 if body is present when no content types are allowed.\n * @throws HTTP 411 if body is required but missing.\n * @throws HTTP 415 if `Content-Type` is not in `contentTypes`.\n * @throws HTTP 422 if body is invalid or media type is unsupported.\n */\nexport const bodyParserMiddleware = (contentTypes: string[]): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n if (contentTypes.length === 0) {\n if (ctx.request.length > 0) {\n throw httpError(400).withDetails({ body: 'Unexpected body' });\n }\n } else {\n if (ctx.request.length > 0) {\n if (!ctx.request.is(contentTypes)) {\n throw httpError(415).withDetails({\n 'content-type': `must be ${contentTypes.length > 1 ? 'one of ' : ''}${contentTypes.join(', ')}`,\n value: ctx.request.type,\n });\n }\n\n try {\n const parser = ctx.container.get(ServerKitBodyParser);\n const result = await parser.parse(ctx);\n ctx.body = result.parsed;\n ctx.rawBody = result.raw;\n } catch (error) {\n if (IsHttpError(error)) {\n throw error;\n }\n throw httpError(422)\n .withCause(error as Error)\n .withDetails({ body: 'Invalid request body format' });\n }\n } else {\n throw httpError(411);\n }\n }\n await next();\n };\n};\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './parsers/serverkit.parser.js';\nimport { ServerKitContext } from './serverkit.context.js';\nimport { unique } from '@maroonedsoftware/utilities';\nimport { httpError } from '@maroonedsoftware/errors';\n\n/**\n * DI-injectable map of MIME subtypes to {@link ServerKitParser} instances.\n *\n * Register parser instances against their MIME subtypes, then bind this map\n * in the InjectKit container so {@link ServerKitBodyParser} can resolve them.\n *\n * @example\n * ```typescript\n * registry\n * .register(ServerKitParserMappings)\n * .useMap()\n * .add('json', JsonParser)\n * .add('urlencoded', FormParser);\n * ```\n */\n@Injectable()\nexport class ServerKitParserMappings extends Map<string, ServerKitParser> {}\n\n/**\n * Selects and invokes the appropriate {@link ServerKitParser} for the incoming request\n * based on its `Content-Type` header.\n *\n * The set of supported MIME types is derived from the keys of the injected\n * {@link ServerKitParserMappings}. Duplicate keys are deduplicated automatically.\n *\n * @throws HTTP 415 if the request's `Content-Type` does not match any registered parser.\n *\n * @see {@link defaultParserMappings} – convenience map for the standard parsers\n * @see {@link bodyParserMiddleware} – the middleware that invokes this class\n */\n@Injectable()\nexport class ServerKitBodyParser {\n private readonly mimeTypes: string[];\n constructor(private readonly parsers: ServerKitParserMappings) {\n this.mimeTypes = unique(Array.from(this.parsers.keys()));\n }\n\n /**\n * Matches the request's `Content-Type` to a registered parser and delegates parsing.\n *\n * @param ctx - The current {@link ServerKitContext}; used to inspect `Content-Type` and access `ctx.req`.\n * @returns The {@link ServerKitParserResult} from the matched parser.\n * @throws HTTP 415 if no parser is registered for the request's content type.\n */\n async parse(ctx: ServerKitContext): Promise<ServerKitParserResult> {\n const mimeType = ctx.request.is(this.mimeTypes);\n if (!mimeType) {\n throw httpError(415).withDetails({ body: 'Unsupported media type' });\n }\n const parser = this.parsers.get(mimeType);\n if (!parser) {\n throw httpError(415).withDetails({ body: 'Unsupported media type' });\n }\n return parser.parse(ctx.req);\n }\n}\n","import { invalidAuthenticationContext } from '@maroonedsoftware/authentication';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { httpError, unauthorizedError } from '@maroonedsoftware/errors';\n\n/**\n * Options for {@link requireSecurity}.\n */\ntype SecurityOptions = {\n /** When set, the authenticated user must have this role in their `AuthenticationContext.roles` array. */\n roles?: string[];\n};\n\n/**\n * Router middleware that enforces authentication and optional role-based authorization.\n *\n * Reads `ctx.authenticationContext` (set by `authenticationMiddleware`) and:\n * - Throws HTTP 401 with a `WWW-Authenticate: Bearer error=\"invalid_token\"` header\n * if the context is `invalidAuthenticationContext`.\n * - Throws HTTP 403 if `options.role` is specified and the user does not have that role.\n * - Calls `next()` otherwise.\n *\n * @param options - Optional {@link SecurityOptions} for role-based access control.\n * @returns A {@link ServerKitRouterMiddleware} that guards the route.\n *\n * @example\n * ```typescript\n * // Require any authenticated user\n * router.get('/profile', requireSecurity(), handler);\n *\n * // Require the 'admin' role\n * router.delete('/users/:id', requireSecurity({ role: 'admin' }), handler);\n * ```\n */\nexport const requireSecurity = (options?: SecurityOptions): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n const authenticationContext = ctx.authenticationContext;\n\n if (authenticationContext === invalidAuthenticationContext) {\n throw unauthorizedError('Bearer error=\"invalid_token\"');\n }\n\n if (options?.roles && options.roles.length > 0 && !options.roles.some(role => authenticationContext.roles.includes(role))) {\n throw httpError(403).withInternalDetails({\n message: 'Insufficient role',\n requiredRoles: options.roles,\n userRoles: authenticationContext.roles.join(', '),\n });\n }\n\n await next();\n };\n};\n","import { createHmac, BinaryToTextEncoding } from 'node:crypto';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\nimport { AppConfig } from '@maroonedsoftware/appconfig';\n\n/**\n * Configuration for {@link requireSignature}.\n *\n * Stored in `AppConfig` and retrieved by key at request time, so the values\n * can be loaded from any AppConfig source (JSON, `.env`, GCP secrets, etc.).\n */\nexport type SignatureOptions = {\n /** Name of the request header that carries the HMAC signature (e.g. `'X-Signature'`). */\n header: string;\n /** Secret key used to compute the HMAC. */\n secret: string;\n /** HMAC algorithm passed to `crypto.createHmac` (e.g. `'sha256'`, `'sha512'`). */\n algorithm: string;\n /** Output encoding for `hmac.digest()` (e.g. `'hex'`, `'base64'`). */\n digest: BinaryToTextEncoding;\n};\n\n/**\n * Router middleware that verifies a request signature against an HMAC of `ctx.rawBody`.\n *\n * Reads {@link SignatureOptions} from `AppConfig` using `optionsKey`, then:\n * - Computes `HMAC(algorithm, secret).update(ctx.rawBody).digest(digest)`\n * - Reads the expected signature from the request header named `options.header`\n * - Throws HTTP 401 (with internal diagnostics) if the signatures do not match\n * - Calls `next()` otherwise\n *\n * Requires `ctx.rawBody` to be populated before this middleware runs — use\n * {@link bodyParserMiddleware} upstream to ensure the raw bytes are captured.\n *\n * @param optionsKey - Key used to retrieve {@link SignatureOptions} from `AppConfig` via `getAs`.\n * @returns A {@link ServerKitRouterMiddleware} that guards the route.\n *\n * @example\n * ```typescript\n * // config.json\n * // { \"webhook\": { \"header\": \"X-Hub-Signature-256\", \"secret\": \"${env:WEBHOOK_SECRET}\", \"algorithm\": \"sha256\", \"digest\": \"hex\" } }\n *\n * router.post('/webhooks/github', requireSignature('webhook'), handler);\n * ```\n */\nexport const requireSignature = (optionsKey: string): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n const options = ctx.container.get(AppConfig).getAs<SignatureOptions>(optionsKey);\n\n const { header, secret, algorithm, digest } = options;\n\n const hmac = createHmac(algorithm, secret).update(ctx.rawBody);\n\n const signature = ctx.get(header);\n const computedSignature = hmac.digest(digest);\n if (computedSignature !== signature) {\n throw httpError(401).withInternalDetails({\n message: 'Invalid signature',\n header,\n computedSignature,\n signature,\n algorithm,\n digest,\n });\n }\n\n await next();\n };\n};\n","import { BinaryLike } from 'node:crypto';\nimport { IncomingMessage } from 'http';\nimport { Injectable } from 'injectkit';\n\n/**\n * The result returned by every {@link ServerKitParser}.\n *\n * @property parsed - The structured/deserialized value derived from the body (e.g. a plain object for JSON).\n * @property raw - The unprocessed body bytes as read from the stream. Parsers that do not retain a raw\n * representation (e.g. binary, multipart) return an empty `Buffer`.\n */\nexport type ServerKitParserResult = {\n parsed: unknown;\n raw: BinaryLike;\n};\n\n/**\n * Abstract base class for all ServerKit body parsers.\n *\n * Implementations read from an {@link IncomingMessage} stream and return a\n * {@link ServerKitParserResult} containing both the parsed value and the raw body.\n * Each parser is registered in the DI container and selected by MIME type via\n * {@link ServerKitBodyParser}.\n *\n * @see {@link ServerKitBodyParser} – dispatcher that selects the right parser by MIME type\n * @see {@link defaultParserMappings} – built-in MIME-type-to-parser map\n */\n@Injectable()\nexport abstract class ServerKitParser {\n /**\n * Reads and parses the body from the given request stream.\n *\n * @param req - The incoming HTTP request whose body should be consumed.\n * @returns A promise resolving to a {@link ServerKitParserResult}.\n */\n abstract parse(req: IncomingMessage): Promise<ServerKitParserResult>;\n}\n","import { parse, Reviver } from '@hapi/bourne';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\nimport { httpError } from '@maroonedsoftware/errors';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { Injectable } from 'injectkit';\n\n/**\n * Configuration options for {@link JsonParser}.\n *\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property strict - When `true` (default), only JSON objects `{}` and arrays `[]` are accepted.\n * When `false`, any valid JSON value (string, number, etc.) is allowed.\n * @property protoAction - How `@hapi/bourne` handles `__proto__` keys. Defaults to `'error'`.\n * @property reviver - Optional JSON reviver function passed to `@hapi/bourne`.\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'1mb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n */\n@Injectable()\nexport class JsonParserOptions implements raw.Options {\n strict?: boolean;\n protoAction?: 'error' | 'remove' | 'ignore';\n reviver?: Reviver;\n encoding?: string;\n limit?: string;\n length?: number;\n}\n\n// Allowed whitespace is defined in RFC 7159\n// http://www.rfc-editor.org/rfc/rfc7159.txt\n/* eslint-disable-next-line no-control-regex */\nconst strictJSONReg = /^[\\x20\\x09\\x0a\\x0d]*(\\[|\\{)/;\n\n/**\n * Parses a JSON request body using `@hapi/bourne` for prototype-pollution protection.\n *\n * In strict mode (default), only top-level objects `{}` and arrays `[]` are accepted;\n * any other value throws HTTP 400. In non-strict mode, any valid JSON value is accepted.\n * An empty body always resolves to `{ parsed: undefined }`.\n *\n * @throws HTTP 400 if the body is not valid JSON or fails the strict-mode check.\n *\n * @example\n * ```typescript\n * // Default strict mode (injectable)\n * const parser = new JsonParser(new JsonParserOptions());\n *\n * // Custom options\n * const lenient = new JsonParser({ strict: false, protoAction: 'remove' });\n * ```\n */\n@Injectable()\nexport class JsonParser extends ServerKitParser {\n constructor(private readonly options: JsonParserOptions) {\n super();\n }\n\n /**\n * Reads, decompresses, and JSON-parses the request body.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <object|array|undefined>, raw: <original string> }`.\n * @throws HTTP 400 on malformed JSON or strict-mode violation.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '1mb';\n\n const strict = this.options.strict ?? true;\n const protoAction = this.options.protoAction ?? 'error';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n const doParse = (str: string) => {\n try {\n if (this.options.reviver) {\n return parse(str, this.options.reviver, { protoAction });\n }\n return parse(str, { protoAction });\n } catch (err) {\n throw httpError(400).withCause(err as Error);\n }\n };\n\n if (!strict) {\n return str ? { parsed: doParse(str), raw: str } : { parsed: undefined, raw: str };\n } else if (!str) {\n return { parsed: undefined, raw: str };\n } else if (!strictJSONReg.test(str)) {\n throw httpError(400).withDetails({ body: 'Invalid JSON, only supports object and array' });\n }\n return { parsed: doParse(str), raw: str };\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Configuration options for {@link TextParser}.\n *\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'1mb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n */\nexport class TextParserOptions implements raw.Options {\n encoding?: string;\n limit?: string;\n length?: number;\n}\n\n/**\n * Reads the request body as a plain string.\n *\n * No structural parsing is performed; the raw text is returned as both `parsed` and `raw`.\n * Suitable for `text/plain` and similar content types. Decompresses the stream via `inflation`\n * before buffering.\n *\n * @example\n * ```typescript\n * const parser = new TextParser(new TextParserOptions());\n * const { parsed } = await parser.parse(req); // parsed is a string\n * ```\n */\n@Injectable()\nexport class TextParser extends ServerKitParser {\n constructor(private readonly options: TextParserOptions) {\n super();\n }\n\n /**\n * Reads and decompresses the request body into a string.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <string>, raw: <same string> }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '1mb';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n return { parsed: str, raw: str };\n }\n}\n","import { httpError } from '@maroonedsoftware/errors';\nimport { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport { parse, IParseOptions } from 'qs';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Configuration options for {@link FormParser}.\n *\n * Combines `raw-body` read options with `qs` parse options.\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'56kb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n * @property allowDots - When `true`, `qs` interprets dot notation (`user.name`) as nested objects.\n * @property depth - Maximum nesting depth for parsed objects (default: `qs` default of `5`).\n * @property parameterLimit - Maximum number of parameters to parse (default: `qs` default of `1000`).\n */\n@Injectable()\nexport class FormParserOptions implements raw.Options, IParseOptions {\n encoding?: string;\n limit?: string;\n length?: number;\n allowDots?: boolean;\n depth?: number;\n parameterLimit?: number;\n}\n\n/**\n * Parses a URL-encoded (`application/x-www-form-urlencoded`) request body using `qs`.\n *\n * Supports nested objects via bracket notation (`user[name]=alice`) by default.\n * Dot notation (`user.name=alice`) requires `allowDots: true` in the options.\n * An empty body resolves to `{ parsed: {} }`.\n *\n * @throws HTTP 400 if `qs.parse` throws unexpectedly.\n *\n * @example\n * ```typescript\n * // Default options (injectable)\n * const parser = new FormParser(new FormParserOptions());\n *\n * // Enable dot notation\n * const parser = new FormParser({ allowDots: true });\n * ```\n */\n@Injectable()\nexport class FormParser extends ServerKitParser {\n constructor(private readonly options: FormParserOptions) {\n super();\n }\n\n /**\n * Reads, decompresses, and URL-decodes the request body.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <object>, raw: <original url-encoded string> }`.\n * @throws HTTP 400 if parsing fails.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '56kb';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n try {\n return { parsed: parse(str, this.options), raw: str };\n } catch (err) {\n throw httpError(400).withCause(err as Error);\n }\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport { MultipartBody } from '@maroonedsoftware/multipart';\n\n/**\n * Wraps the request stream in a {@link MultipartBody} for lazy multipart/form-data parsing.\n *\n * The body is **not** eagerly consumed; fields and files are read on-demand through the\n * `MultipartBody` API. `raw` is always `undefined` because the stream cannot be replayed\n * once consumed.\n */\n@Injectable()\nexport class MultipartParser extends ServerKitParser {\n /**\n * Creates a {@link MultipartBody} around the request stream.\n *\n * @param req - Incoming HTTP request containing the multipart body.\n * @returns `{ parsed: MultipartBody, raw: Buffer(0) }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: new MultipartBody(req), raw: Buffer.from('') };\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Reads the request body as a raw `Buffer` without any text decoding or structural parsing.\n *\n * Useful for binary payloads (e.g. PDFs, images, protobuf) where the caller needs the\n * untransformed bytes. Decompresses the stream via `inflation` before buffering.\n *\n * `raw` is always `undefined` because the `Buffer` itself is both the parsed and raw form.\n */\n@Injectable()\nexport class BinaryParser extends ServerKitParser {\n /**\n * Buffers the (optionally compressed) request body into a `Buffer`.\n *\n * @param req - Incoming HTTP request to read.\n * @returns `{ parsed: Buffer, raw: Buffer(0) }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: await raw(inflate(req)), raw: Buffer.from('') };\n }\n}\n","import { Identifier } from 'injectkit';\nimport { FormParser } from './form.parser.js';\nimport { JsonParser } from './json.parser.js';\nimport { MultipartParser } from './multipart.parser.js';\nimport { ServerKitParser } from './serverkit.parser.js';\nimport { TextParser } from './text.parser.js';\n\n/**\n * Built-in MIME-subtype-to-parser mappings used by {@link bodyParserMiddleware}.\n *\n * Each key is a MIME subtype (matched against `Content-Type` via Koa's `ctx.request.is()`)\n * and the value is the InjectKit {@link Identifier} for the corresponding {@link ServerKitParser}.\n *\n * | MIME subtype | Parser |\n * | --------------------- | ----------------- |\n * | `json` | {@link JsonParser} |\n * | `application/*+json` | {@link JsonParser} |\n * | `urlencoded` | {@link FormParser} |\n * | `text` | {@link TextParser} |\n * | `multipart` | {@link MultipartParser} |\n *\n * Extend by spreading into a new object and registering the result in the DI container:\n * ```typescript\n * const myMappings = { ...defaultParserMappings, pdf: BinaryParser };\n * ```\n */\nexport const defaultParserMappings: Record<string, Identifier<ServerKitParser>> = {\n json: JsonParser,\n 'application/*+json': JsonParser,\n urlencoded: FormParser,\n text: TextParser,\n multipart: MultipartParser,\n} as const;\n"],"mappings":";;;;AACA,OAAOA,YAA+B;AAY/B,IAAMC,kBAAkB,wBAAqDC,YAA4B,IAAIC,OAAyBD,OAAAA,GAA9G;;;ACb/B,OAAOE,UAAU;AAoBV,IAAMC,iBAAiB,wBAACC,YAAAA;AAE7B,QAAMC,gBAAgB,wBAACC,QAAAA;AACrB,UAAMC,SAASD,IAAIE,IAAI,QAAA;AACvB,UAAMC,WAAWL,SAASG,UAAU;MAAC;;AACrC,eAAWG,WAAWD,UAAU;AAC9B,UAAIC,YAAY,KAAK;AACnB,eAAOH;MACT;AAEA,UAAI,OAAOG,YAAY,UAAU;AAC/B,YAAIA,YAAYH,QAAQ;AACtB,iBAAOA;QACT;AACA;MACF;AAEA,UAAIG,QAAQC,KAAKJ,MAAAA,GAAS;AACxB,eAAOA;MACT;IACF;AAGA,WAAO;EACT,GAtBsB;AAwBtB,SAAOK,KAAK;IACV,GAAGR;IACHG,QAAQF;IACRQ,cAAcT,SAASS,gBAAgB;IACvCC,eAAeV,SAASU,iBAAiB;IACzCC,oBAAoBX,SAASW,sBAAsB;IACnDC,sBAAsBZ,SAASY,wBAAwB;EACzD,CAAA;AACF,GAlC8B;;;ACnB9B,SAASC,aAAaC,wBAAwB;AASvC,IAAMC,kBAAkB,6BAAA;AAC7B,SAAO,OAAOC,KAAKC,SAAAA;AACjB,QAAI;AACF,YAAMA,KAAAA;AACN,UAAID,IAAIE,WAAW,OAAO,CAACF,IAAIG,MAAM;AACnC,cAAMA,OAAO;UACXC,YAAY;UACZC,SAAS;UACTC,SAAS;YAAEC,KAAKP,IAAIQ,IAAIC,SAAQ;UAAG;QACrC;AACAT,YAAIE,SAAS;AACbF,YAAIG,OAAOA;AACXH,YAAIU,IAAIC,KAAK,QAAQR,MAAMH,GAAAA;MAC7B;IACF,SAASY,OAAO;AACd,UAAIC,YAAYD,KAAAA,GAAQ;AACtBZ,YAAIE,SAASU,MAAMR;AACnBJ,YAAIG,OAAO;UACTC,YAAYQ,MAAMR;UAClBC,SAASO,MAAMP;UACfC,SAASM,MAAMN;QACjB;AACA,YAAIM,MAAME,SAAS;AACjB,qBAAWC,SAASC,OAAOC,QAAQL,MAAME,OAAO,GAAG;AACjDd,gBAAIkB,IAAIH,MAAM,CAAA,GAAIA,MAAM,CAAA,CAAE;UAC5B;QACF;MACF,WAAWI,iBAAiBP,KAAAA,GAAQ;AAClCZ,YAAIE,SAAS;AACbF,YAAIG,OAAO;UACTC,YAAY;UACZC,SAASO,MAAMP;UACfC,SAASM,MAAMN;QACjB;MACF,OAAO;AACLN,YAAIE,SAAS;AACbF,YAAIG,OAAO;UACTC,YAAY;UACZC,SAAS;QACX;MACF;AAEAL,UAAIU,IAAIC,KAAK,SAASC,OAAOZ,GAAAA;IAC/B;EACF;AACF,GA7C+B;;;ACV/B,SAA8BoB,sBAAsB;AAEpD,SAASC,iBAAiB;AAG1B,IAAMC,qBAAqB,wBAACC,UAAAA;AAC1B,SAAOA,iBAAiBC,kBAAmB,kBAAmBD,SAAoB,qBAAsBA;AAC1G,GAF2B;AAWpB,IAAME,wBAAwB,wBAACC,gBAAAA;AACpC,SAAO,OAAOC,KAAKC,SAAAA;AACjB,QAAI;AACF,YAAMF,YAAYG,QAAQF,IAAIG,EAAE;IAClC,SAASP,OAAgB;AACvB,UAAIQ,UAAkC,CAAC;AACvC,UAAIT,mBAAmBC,KAAAA,GAAQ;AAC7BQ,kBAAU;UACR,gBAAgBR,MAAMS,eAAe,KAAMC,SAAQ;UACnD,qBAAqBP,YAAYQ,OAAOD,SAAQ;UAChD,yBAAyBV,MAAMY,gBAAgBF,SAAQ;UACvD,qBAAqBG,KAAKC,MAAMC,KAAKC,IAAG,IAAKhB,MAAMS,gBAAgB,GAAA,EAAMC,SAAQ;QACnF;MACF;AAEA,YAAMO,UAAU,GAAA,EACbC,UAAUlB,KAAAA,EACVmB,YAAYX,OAAAA;IACjB;AAEA,UAAMH,KAAAA;EACR;AACF,GAtBqC;;;AChBrC,OAAOe,YAAY;AAEnB,SAASC,cAAc;AAYhB,IAAMC,6BAA6B,wBAACC,cAAAA;AACzC,SAAO,OAAOC,KAAKC,SAAAA;AACjBD,QAAID,YAAYA,UAAUG,sBAAqB;AAC/CF,QAAIG,SAASJ,UAAUK,IAAIC,MAAAA;AAC3BL,QAAIM,aAAaN,IAAIO;AAErBP,QAAIQ,YAAYR,IAAII,IAAI,YAAA;AACxBJ,QAAIS,YAAYT,IAAIU;AAEpB,UAAMC,gBAAgBX,IAAIY,QAAQ,kBAAA;AAClCZ,QAAIW,gBAAgBE,MAAMC,QAAQH,aAAAA,IAAkBA,cAAc,CAAA,KAAMI,OAAOC,WAAU,IAAOL,iBAAiBI,OAAOC,WAAU;AAElIhB,QAAIiB,YAAYF,OAAOC,WAAU;AAEjChB,QAAIY,QAAQ,kBAAA,IAAsBZ,IAAIW;AACtCX,QAAIkB,IAAI,oBAAoBlB,IAAIW,aAAa;AAE7CX,QAAIY,QAAQ,cAAA,IAAkBZ,IAAIiB;AAClCjB,QAAIkB,IAAI,gBAAgBlB,IAAIiB,SAAS;AAErC,UAAMhB,KAAAA;EACR;AACF,GAtB0C;;;ACb1C,SAASkB,6BAA6BC,oCAAoC;AAqBnE,IAAMC,2BAA2B,6BAAA;AACtC,SAAO,OAAOC,KAAKC,SAAAA;AACjBD,QAAIE,wBAAwBC;AAG5B,UAAMC,sBAAsBJ,IAAIK,IAAIC,QAAQC;AAC5C,WAAOP,IAAIK,IAAIC,QAAQC;AAEvB,UAAMC,gBAAgBR,IAAIS,UAAUC,IAAIC,2BAAAA;AAExCX,QAAIE,wBAAwB,MAAMM,cAAcI,OAAOR,mBAAAA;AAEvD,UAAMH,KAAAA;EACR;AACF,GAdwC;;;ACtBxC,SAASY,aAAAA,YAAWC,eAAAA,oBAAmB;;;ACAvC,SAASC,kBAAkB;AAG3B,SAASC,cAAc;AACvB,SAASC,aAAAA,kBAAiB;;;;;;;;;;;;AAkBnB,IAAMC,0BAAN,cAAsCC,IAAAA;SAAAA;;;AAA8B;;;;AAepE,IAAMC,sBAAN,MAAMA;SAAAA;;;;EACMC;EACjB,YAA6BC,SAAkC;SAAlCA,UAAAA;AAC3B,SAAKD,YAAYE,OAAOC,MAAMC,KAAK,KAAKH,QAAQI,KAAI,CAAA,CAAA;EACtD;;;;;;;;EASA,MAAMC,MAAMC,KAAuD;AACjE,UAAMC,WAAWD,IAAIE,QAAQC,GAAG,KAAKV,SAAS;AAC9C,QAAI,CAACQ,UAAU;AACb,YAAMG,WAAU,GAAA,EAAKC,YAAY;QAAEC,MAAM;MAAyB,CAAA;IACpE;AACA,UAAMC,SAAS,KAAKb,QAAQc,IAAIP,QAAAA;AAChC,QAAI,CAACM,QAAQ;AACX,YAAMH,WAAU,GAAA,EAAKC,YAAY;QAAEC,MAAM;MAAyB,CAAA;IACpE;AACA,WAAOC,OAAOR,MAAMC,IAAIS,GAAG;EAC7B;AACF;;;;;;;;;;AD1CO,IAAMC,uBAAuB,wBAACC,iBAAAA;AACnC,SAAO,OAAOC,KAAKC,SAAAA;AACjB,QAAIF,aAAaG,WAAW,GAAG;AAC7B,UAAIF,IAAIG,QAAQD,SAAS,GAAG;AAC1B,cAAME,WAAU,GAAA,EAAKC,YAAY;UAAEC,MAAM;QAAkB,CAAA;MAC7D;IACF,OAAO;AACL,UAAIN,IAAIG,QAAQD,SAAS,GAAG;AAC1B,YAAI,CAACF,IAAIG,QAAQI,GAAGR,YAAAA,GAAe;AACjC,gBAAMK,WAAU,GAAA,EAAKC,YAAY;YAC/B,gBAAgB,WAAWN,aAAaG,SAAS,IAAI,YAAY,EAAA,GAAKH,aAAaS,KAAK,IAAA,CAAA;YACxFC,OAAOT,IAAIG,QAAQO;UACrB,CAAA;QACF;AAEA,YAAI;AACF,gBAAMC,SAASX,IAAIY,UAAUC,IAAIC,mBAAAA;AACjC,gBAAMC,SAAS,MAAMJ,OAAOK,MAAMhB,GAAAA;AAClCA,cAAIM,OAAOS,OAAOE;AAClBjB,cAAIkB,UAAUH,OAAOI;QACvB,SAASC,OAAO;AACd,cAAIC,aAAYD,KAAAA,GAAQ;AACtB,kBAAMA;UACR;AACA,gBAAMhB,WAAU,GAAA,EACbkB,UAAUF,KAAAA,EACVf,YAAY;YAAEC,MAAM;UAA8B,CAAA;QACvD;MACF,OAAO;AACL,cAAMF,WAAU,GAAA;MAClB;IACF;AACA,UAAMH,KAAAA;EACR;AACF,GAlCoC;;;AEnBpC,SAASsB,gCAAAA,qCAAoC;AAE7C,SAASC,aAAAA,YAAWC,yBAAyB;AA+BtC,IAAMC,kBAAkB,wBAACC,YAAAA;AAC9B,SAAO,OAAOC,KAAKC,SAAAA;AACjB,UAAMC,wBAAwBF,IAAIE;AAElC,QAAIA,0BAA0BC,+BAA8B;AAC1D,YAAMC,kBAAkB,8BAAA;IAC1B;AAEA,QAAIL,SAASM,SAASN,QAAQM,MAAMC,SAAS,KAAK,CAACP,QAAQM,MAAME,KAAKC,CAAAA,SAAQN,sBAAsBG,MAAMI,SAASD,IAAAA,CAAAA,GAAQ;AACzH,YAAME,WAAU,GAAA,EAAKC,oBAAoB;QACvCC,SAAS;QACTC,eAAed,QAAQM;QACvBS,WAAWZ,sBAAsBG,MAAMU,KAAK,IAAA;MAC9C,CAAA;IACF;AAEA,UAAMd,KAAAA;EACR;AACF,GAlB+B;;;ACjC/B,SAASe,kBAAwC;AAEjD,SAASC,aAAAA,kBAAiB;AAC1B,SAASC,iBAAiB;AA0CnB,IAAMC,mBAAmB,wBAACC,eAAAA;AAC/B,SAAO,OAAOC,KAAKC,SAAAA;AACjB,UAAMC,UAAUF,IAAIG,UAAUC,IAAIC,SAAAA,EAAWC,MAAwBP,UAAAA;AAErE,UAAM,EAAEQ,QAAQC,QAAQC,WAAWC,OAAM,IAAKR;AAE9C,UAAMS,OAAOC,WAAWH,WAAWD,MAAAA,EAAQK,OAAOb,IAAIc,OAAO;AAE7D,UAAMC,YAAYf,IAAII,IAAIG,MAAAA;AAC1B,UAAMS,oBAAoBL,KAAKD,OAAOA,MAAAA;AACtC,QAAIM,sBAAsBD,WAAW;AACnC,YAAME,WAAU,GAAA,EAAKC,oBAAoB;QACvCC,SAAS;QACTZ;QACAS;QACAD;QACAN;QACAC;MACF,CAAA;IACF;AAEA,UAAMT,KAAAA;EACR;AACF,GAvBgC;;;AC3ChC,SAASmB,cAAAA,mBAAkB;;;;;;;;AA0BpB,IAAeC,kBAAf,MAAeA;SAAAA;;;AAQtB;;;;;;ACpCA,SAASC,aAAsB;AAE/B,OAAOC,SAAS;AAChB,OAAOC,aAAa;AACpB,SAASC,aAAAA,kBAAiB;AAE1B,SAASC,cAAAA,mBAAkB;;;;;;;;;;;;AAgBpB,IAAMC,oBAAN,MAAMA;SAAAA;;;EACXC;EACAC;EACAC;EACAC;EACAC;EACAC;AACF;;;;AAKA,IAAMC,gBAAgB;AAqBf,IAAMC,aAAN,cAAyBC,gBAAAA;SAAAA;;;;EAC9B,YAA6BC,SAA4B;AACvD,UAAK,GAAA,KADsBA,UAAAA;EAE7B;;;;;;;;EASA,MAAMC,MAAMC,KAAsD;AAChE,UAAMC,MAAMD,IAAIE,QAAQ,gBAAA;AACxB,UAAMC,kBAAkBH,IAAIE,QAAQ,kBAAA,KAAuB;AAC3D,UAAMR,SAA6BO,OAAOE,oBAAoB,aAAa,CAAC,CAACF,MAAMG;AACnF,UAAMZ,WAAW,KAAKM,QAAQN,YAAY;AAC1C,UAAMC,QAAQ,KAAKK,QAAQL,SAAS;AAEpC,UAAMJ,SAAS,KAAKS,QAAQT,UAAU;AACtC,UAAMC,cAAc,KAAKQ,QAAQR,eAAe;AAEhD,UAAMe,MAAM,MAAMC,IAAIC,QAAQP,GAAAA,GAAM;MAAER;MAAUC;MAAOC;IAAO,CAAA;AAE9D,UAAMc,UAAU,wBAACH,SAAAA;AACf,UAAI;AACF,YAAI,KAAKP,QAAQP,SAAS;AACxB,iBAAOQ,MAAMM,MAAK,KAAKP,QAAQP,SAAS;YAAED;UAAY,CAAA;QACxD;AACA,eAAOS,MAAMM,MAAK;UAAEf;QAAY,CAAA;MAClC,SAASmB,KAAK;AACZ,cAAMC,WAAU,GAAA,EAAKC,UAAUF,GAAAA;MACjC;IACF,GATgB;AAWhB,QAAI,CAACpB,QAAQ;AACX,aAAOgB,MAAM;QAAEO,QAAQJ,QAAQH,GAAAA;QAAMC,KAAKD;MAAI,IAAI;QAAEO,QAAQR;QAAWE,KAAKD;MAAI;IAClF,WAAW,CAACA,KAAK;AACf,aAAO;QAAEO,QAAQR;QAAWE,KAAKD;MAAI;IACvC,WAAW,CAACV,cAAckB,KAAKR,GAAAA,GAAM;AACnC,YAAMK,WAAU,GAAA,EAAKI,YAAY;QAAEC,MAAM;MAA+C,CAAA;IAC1F;AACA,WAAO;MAAEH,QAAQJ,QAAQH,GAAAA;MAAMC,KAAKD;IAAI;EAC1C;AACF;;;;;;;;;;ACnGA,SAASW,cAAAA,mBAAkB;AAG3B,OAAOC,UAAS;AAChB,OAAOC,cAAa;;;;;;;;;;;;AAWb,IAAMC,oBAAN,MAAMA;SAAAA;;;EACXC;EACAC;EACAC;AACF;AAgBO,IAAMC,aAAN,cAAyBC,gBAAAA;SAAAA;;;;EAC9B,YAA6BC,SAA4B;AACvD,UAAK,GAAA,KADsBA,UAAAA;EAE7B;;;;;;;EAQA,MAAMC,MAAMC,KAAsD;AAChE,UAAMC,MAAMD,IAAIE,QAAQ,gBAAA;AACxB,UAAMC,kBAAkBH,IAAIE,QAAQ,kBAAA,KAAuB;AAC3D,UAAMP,SAA6BM,OAAOE,oBAAoB,aAAa,CAAC,CAACF,MAAMG;AACnF,UAAMX,WAAW,KAAKK,QAAQL,YAAY;AAC1C,UAAMC,QAAQ,KAAKI,QAAQJ,SAAS;AAEpC,UAAMW,MAAM,MAAMC,KAAIC,SAAQP,GAAAA,GAAM;MAAEP;MAAUC;MAAOC;IAAO,CAAA;AAE9D,WAAO;MAAEa,QAAQH;MAAKC,KAAKD;IAAI;EACjC;AACF;;;;;;;;;;ACzDA,SAASI,aAAAA,kBAAiB;AAC1B,SAASC,cAAAA,mBAAkB;AAG3B,SAASC,SAAAA,cAA4B;AACrC,OAAOC,UAAS;AAChB,OAAOC,cAAa;;;;;;;;;;;;AAgBb,IAAMC,oBAAN,MAAMA;SAAAA;;;EACXC;EACAC;EACAC;EACAC;EACAC;EACAC;AACF;;;;AAqBO,IAAMC,aAAN,cAAyBC,gBAAAA;SAAAA;;;;EAC9B,YAA6BC,SAA4B;AACvD,UAAK,GAAA,KADsBA,UAAAA;EAE7B;;;;;;;;EASA,MAAMC,MAAMC,KAAsD;AAChE,UAAMC,MAAMD,IAAIE,QAAQ,gBAAA;AACxB,UAAMC,kBAAkBH,IAAIE,QAAQ,kBAAA,KAAuB;AAC3D,UAAMV,SAA6BS,OAAOE,oBAAoB,aAAa,CAAC,CAACF,MAAMG;AACnF,UAAMd,WAAW,KAAKQ,QAAQR,YAAY;AAC1C,UAAMC,QAAQ,KAAKO,QAAQP,SAAS;AAEpC,UAAMc,MAAM,MAAMC,KAAIC,SAAQP,GAAAA,GAAM;MAAEV;MAAUC;MAAOC;IAAO,CAAA;AAE9D,QAAI;AACF,aAAO;QAAEgB,QAAQT,OAAMM,KAAK,KAAKP,OAAO;QAAGQ,KAAKD;MAAI;IACtD,SAASI,KAAK;AACZ,YAAMC,WAAU,GAAA,EAAKC,UAAUF,GAAAA;IACjC;EACF;AACF;;;;;;;;;;AC7EA,SAASG,cAAAA,mBAAkB;AAG3B,SAASC,qBAAqB;;;;;;;;AAUvB,IAAMC,kBAAN,cAA8BC,gBAAAA;SAAAA;;;;;;;;;EAOnC,MAAMC,MAAMC,KAAsD;AAChE,WAAO;MAAEC,QAAQ,IAAIC,cAAcF,GAAAA;MAAMG,KAAKC,OAAOC,KAAK,EAAA;IAAI;EAChE;AACF;;;;;;ACvBA,SAASC,cAAAA,mBAAkB;AAG3B,OAAOC,UAAS;AAChB,OAAOC,cAAa;;;;;;;;AAWb,IAAMC,eAAN,cAA2BC,gBAAAA;SAAAA;;;;;;;;;EAOhC,MAAMC,MAAMC,KAAsD;AAChE,WAAO;MAAEC,QAAQ,MAAMC,KAAIC,SAAQH,GAAAA,CAAAA;MAAOE,KAAKE,OAAOC,KAAK,EAAA;IAAI;EACjE;AACF;;;;;;ACCO,IAAMC,wBAAqE;EAChFC,MAAMC;EACN,sBAAsBA;EACtBC,YAAYC;EACZC,MAAMC;EACNC,WAAWC;AACb;","names":["Router","ServerKitRouter","options","Router","cors","corsMiddleware","options","originMatcher","ctx","origin","get","matchers","matcher","test","cors","allowMethods","secureContext","keepHeadersOnError","privateNetworkAccess","IsHttpError","IsServerkitError","errorMiddleware","ctx","next","status","body","statusCode","message","details","url","URL","toString","app","emit","error","IsHttpError","headers","entry","Object","entries","set","IsServerkitError","RateLimiterRes","httpError","isRateLimiterError","error","RateLimiterRes","rateLimiterMiddleware","rateLimiter","ctx","next","consume","ip","headers","msBeforeNext","toString","points","remainingPoints","Math","ceil","Date","now","httpError","withCause","withHeaders","crypto","Logger","serverKitContextMiddleware","container","ctx","next","createScopedContainer","logger","get","Logger","loggerName","path","userAgent","ipAddress","ip","correlationId","headers","Array","isArray","crypto","randomUUID","requestId","set","AuthenticationSchemeHandler","invalidAuthenticationContext","authenticationMiddleware","ctx","next","authenticationContext","invalidAuthenticationContext","authorizationHeader","req","headers","authorization","schemeHandler","container","get","AuthenticationSchemeHandler","handle","httpError","IsHttpError","Injectable","unique","httpError","ServerKitParserMappings","Map","ServerKitBodyParser","mimeTypes","parsers","unique","Array","from","keys","parse","ctx","mimeType","request","is","httpError","withDetails","body","parser","get","req","bodyParserMiddleware","contentTypes","ctx","next","length","request","httpError","withDetails","body","is","join","value","type","parser","container","get","ServerKitBodyParser","result","parse","parsed","rawBody","raw","error","IsHttpError","withCause","invalidAuthenticationContext","httpError","unauthorizedError","requireSecurity","options","ctx","next","authenticationContext","invalidAuthenticationContext","unauthorizedError","roles","length","some","role","includes","httpError","withInternalDetails","message","requiredRoles","userRoles","join","createHmac","httpError","AppConfig","requireSignature","optionsKey","ctx","next","options","container","get","AppConfig","getAs","header","secret","algorithm","digest","hmac","createHmac","update","rawBody","signature","computedSignature","httpError","withInternalDetails","message","Injectable","ServerKitParser","parse","raw","inflate","httpError","Injectable","JsonParserOptions","strict","protoAction","reviver","encoding","limit","length","strictJSONReg","JsonParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","doParse","err","httpError","withCause","parsed","test","withDetails","body","Injectable","raw","inflate","TextParserOptions","encoding","limit","length","TextParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","parsed","httpError","Injectable","parse","raw","inflate","FormParserOptions","encoding","limit","length","allowDots","depth","parameterLimit","FormParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","parsed","err","httpError","withCause","Injectable","MultipartBody","MultipartParser","ServerKitParser","parse","req","parsed","MultipartBody","raw","Buffer","from","Injectable","raw","inflate","BinaryParser","ServerKitParser","parse","req","parsed","raw","inflate","Buffer","from","defaultParserMappings","json","JsonParser","urlencoded","FormParser","text","TextParser","multipart","MultipartParser"]}
1
+ {"version":3,"sources":["../src/serverkit.context.ts","../src/serverkit.router.ts","../src/middleware/server/cors.middleware.ts","../src/middleware/server/error.middleware.ts","../src/middleware/server/rate.limiter.middleware.ts","../src/middleware/server/serverkit.context.middleware.ts","../src/middleware/server/authentication.middleware.ts","../src/middleware/router/body.parser.middleware.ts","../src/serverkit.bodyparser.ts","../src/middleware/router/require.security.middleware.ts","../src/middleware/router/require.signature.middleware.ts","../src/parsers/serverkit.parser.ts","../src/parsers/json.parser.ts","../src/parsers/text.parser.ts","../src/parsers/form.parser.ts","../src/parsers/multipart.parser.ts","../src/parsers/binary.parser.ts","../src/parsers/serverkit.default.parsers.ts"],"sourcesContent":["import { Context } from 'koa';\nimport { Container, Injectable } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport { AuthenticationContext } from '@maroonedsoftware/authentication';\nimport { BinaryLike } from 'node:crypto';\n\n/**\n * Koa context extended with ServerKit request-scoped services and metadata.\n * Populated by {@link serverKitContextMiddleware}.\n * Use this as the context type for route handlers to get full typing of `ctx.container`, `ctx.logger`, and request IDs.\n *\n * @extends Context\n * @see {@link serverKitContextMiddleware} – middleware that populates this context on each request\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\nexport interface ServerKitContext extends Context {\n /** Scoped injectkit container for this request; use for request-scoped DI. */\n container: Container;\n /** Request-scoped logger instance. */\n logger: Logger;\n /** Logger name for this request (e.g. request path or route identifier). */\n loggerName: string;\n /** Value of the `User-Agent` request header, or empty string if absent. */\n userAgent: string;\n /** IP address of the client. */\n ipAddress: string;\n /** Correlation ID for tracing; from `X-Correlation-Id` header or generated. */\n correlationId: string;\n /** Request ID; from `X-Request-Id` header or generated. */\n requestId: string;\n /** Raw body for this request. */\n rawBody: BinaryLike;\n /** Authentication context. */\n authenticationContext: AuthenticationContext;\n}\n\n/**\n * Abstract class merged with the {@link ServerKitContext} interface so it can serve as an\n * injectkit injection token. {@link serverKitContextMiddleware} registers the live `ctx`\n * against this token in the request-scoped container, allowing services to declare\n * `ServerKitContext` as a constructor dependency and receive the current Koa context.\n */\n@Injectable()\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\nexport abstract class ServerKitContext implements ServerKitContext {}\n","import { DefaultState } from 'koa';\nimport Router, { RouterOptions } from '@koa/router';\nimport { ServerKitContext } from './serverkit.context.js';\n\n/**\n * Creates a new Koa router typed for ServerKit state and context.\n * Use with {@link ServerKitContext} for full typing of `ctx` in route handlers.\n *\n * @typeParam StateT - Koa state type (defaults to `DefaultState`).\n * @typeParam ContextT - Context type (defaults to `ServerKitContext`).\n * @param options - Router options (defaults to `undefined`).\n * @returns A new {@link Router} instance with the given options.\n */\nexport const ServerKitRouter = <StateT = DefaultState, ContextT = ServerKitContext>(options?: RouterOptions) => new Router<StateT, ContextT>(options);\n","import cors from '@koa/cors';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { Context } from 'koa';\n\n/**\n * CORS options for {@link corsMiddleware}.\n * Extends `@koa/cors` options with an `origin` that may be a string or array of strings/RegExps.\n */\nexport interface CorsOptions extends Omit<cors.Options, 'origin'> {\n /** Allowed origin(s): `'*'`, a single origin string, or an array of strings/RegExps to match. */\n origin?: string | (string | RegExp)[];\n}\n\n/**\n * Adds CORS headers to responses using `@koa/cors` with ServerKit-compatible origin matching.\n * Supports `'*'`, exact string origins, and RegExp patterns.\n *\n * @param options - Optional {@link CorsOptions}; defaults to `GET,HEAD,PUT,POST,DELETE,PATCH` methods.\n * @returns {@link ServerKitMiddleware} that applies CORS headers.\n */\nexport const corsMiddleware = (options?: CorsOptions): ServerKitMiddleware => {\n // return the request origin as its own matcher to support RegExp\n const originMatcher = (ctx: Context): string => {\n const origin = ctx.get('origin');\n const matchers = options?.origin ?? ['*'];\n for (const matcher of matchers) {\n if (matcher === '*') {\n return origin;\n }\n\n if (typeof matcher === 'string') {\n if (matcher === origin) {\n return origin;\n }\n continue;\n }\n\n if (matcher.test(origin)) {\n return origin;\n }\n }\n\n // return the zero value to prevent matches\n return '';\n };\n\n return cors({\n ...options,\n origin: originMatcher,\n allowMethods: options?.allowMethods ?? 'GET,HEAD,PUT,POST,DELETE,PATCH',\n secureContext: options?.secureContext ?? false,\n keepHeadersOnError: options?.keepHeadersOnError ?? false,\n privateNetworkAccess: options?.privateNetworkAccess ?? false,\n });\n};\n","import { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { IsHttpError, IsServerkitError } from '@maroonedsoftware/errors';\n\n/**\n * Central error handler: catches thrown errors, sets status/body from HTTP errors,\n * returns 404 for unmatched routes, and 500 for unknown errors.\n * Emits `error` or `warn` on the app for logging.\n *\n * @returns {@link ServerKitMiddleware} that wraps the stack in try/catch and normalizes responses.\n */\nexport const errorMiddleware = (): ServerKitMiddleware => {\n return async (ctx, next) => {\n try {\n await next();\n if (ctx.status === 404 && !ctx.body) {\n const body = {\n statusCode: 404,\n message: 'Not Found',\n details: { url: ctx.URL.toString() },\n };\n ctx.status = 404;\n ctx.body = body;\n ctx.app.emit('warn', body, ctx);\n }\n } catch (error) {\n if (IsHttpError(error)) {\n ctx.status = error.statusCode;\n ctx.body = {\n statusCode: error.statusCode,\n message: error.message,\n details: error.details,\n };\n if (error.headers) {\n for (const entry of Object.entries(error.headers)) {\n ctx.set(entry[0], entry[1]);\n }\n }\n } else if (IsServerkitError(error)) {\n ctx.status = 500;\n ctx.body = {\n statusCode: 500,\n message: error.message,\n details: error.details,\n };\n } else {\n ctx.status = 500;\n ctx.body = {\n statusCode: 500,\n message: 'Internal Server Error',\n };\n }\n\n ctx.app.emit('error', error, ctx);\n }\n };\n};\n","import { RateLimiterAbstract, RateLimiterRes } from 'rate-limiter-flexible';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\n\n/** Type guard that narrows `error` to a `RateLimiterRes` (i.e. a rate-limit exceeded response). */\nconst isRateLimiterError = (error: unknown): error is RateLimiterRes => {\n return error instanceof RateLimiterRes || ('msBeforeNext' in (error as object) && 'remainingPoints' in (error as object));\n};\n\n/**\n * Enforces rate limiting per client IP using a `rate-limiter-flexible` instance.\n * Consumes one token per request; throws HTTP 429 when the limit is exceeded.\n *\n * @param rateLimiter - A {@link RateLimiterAbstract} instance (e.g. `RateLimiterMemory`, `RateLimiterRedis`).\n * @returns {@link ServerKitMiddleware} that consumes a token and continues or throws 429.\n */\nexport const rateLimiterMiddleware = (rateLimiter: RateLimiterAbstract): ServerKitMiddleware => {\n return async (ctx, next) => {\n try {\n await rateLimiter.consume(ctx.ip);\n } catch (error: unknown) {\n let headers: Record<string, string> = {};\n if (isRateLimiterError(error)) {\n headers = {\n 'retry-after': (error.msBeforeNext / 1000).toString(),\n 'x-ratelimit-limit': rateLimiter.points.toString(),\n 'x-ratelimit-remaining': error.remainingPoints.toString(),\n 'x-ratelimit-reset': Math.ceil((Date.now() + error.msBeforeNext) / 1000).toString(),\n };\n }\n\n throw httpError(429)\n .withCause(error as Error)\n .withHeaders(headers);\n }\n\n await next();\n };\n};\n","import crypto from 'crypto';\nimport { Container } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { ServerKitContext } from '../../serverkit.context.js';\n\n/**\n * Populates {@link ServerKitContext} for each request: scoped container, logger,\n * logger name, user-agent, correlation ID, and request ID.\n * Reads or generates `X-Correlation-Id` and `X-Request-Id` and sets response headers.\n * Registers the live `ctx` against the {@link ServerKitContext} injection token in the\n * request-scoped container so downstream services can inject the current context.\n * Should be applied early so downstream middleware and routes can use `ctx.container` and `ctx.logger`.\n *\n * @param container - Root injectkit {@link Container} used to create a scoped container and resolve {@link Logger}.\n * @returns {@link ServerKitMiddleware} that attaches ServerKit context to `ctx`.\n */\nexport const serverKitContextMiddleware = (container: Container): ServerKitMiddleware => {\n return async (ctx, next) => {\n const scopedContainer = container.createScopedContainer();\n ctx.container = scopedContainer;\n\n scopedContainer.override(ServerKitContext, ctx);\n\n ctx.logger = ctx.container.get(Logger);\n ctx.loggerName = ctx.path;\n\n ctx.userAgent = ctx.get('user-agent');\n ctx.ipAddress = ctx.ip;\n\n const correlationId = ctx.headers['x-correlation-id'];\n ctx.correlationId = Array.isArray(correlationId) ? (correlationId[0] ?? crypto.randomUUID()) : (correlationId ?? crypto.randomUUID());\n\n ctx.requestId = crypto.randomUUID();\n\n ctx.headers['x-correlation-id'] = ctx.correlationId;\n ctx.set('x-correlation-id', ctx.correlationId);\n\n ctx.headers['x-request-id'] = ctx.requestId;\n ctx.set('x-request-id', ctx.requestId);\n\n await next();\n };\n};\n","import { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { AuthenticationSchemeHandler, invalidAuthenticationContext } from '@maroonedsoftware/authentication';\n\n/**\n * Resolves the `Authorization` request header into an {@link AuthenticationContext}\n * and attaches it to `ctx.authenticationContext`.\n *\n * The header is immediately removed from `ctx.req.headers` after being read so it\n * cannot be accidentally captured by downstream logging or serialization.\n *\n * Resolution is delegated to the {@link AuthenticationSchemeHandler} registered in\n * the DI container. `ctx.authenticationContext` is initialised to\n * {@link invalidAuthenticationContext} before delegation, ensuring that any error\n * thrown by the scheme handler leaves the context in a safe, unauthenticated state.\n *\n * @returns A {@link ServerKitMiddleware} that populates `ctx.authenticationContext`.\n *\n * @example\n * ```typescript\n * app.use(authenticationMiddleware());\n * ```\n */\nexport const authenticationMiddleware = (): ServerKitMiddleware => {\n return async (ctx, next) => {\n ctx.authenticationContext = invalidAuthenticationContext; // bad initial state so it will fail verification\n\n // NOTE: we delete the auth headers on the request here to ensure we don't accidentally log it\n const authorizationHeader = ctx.req.headers.authorization;\n delete ctx.req.headers.authorization;\n\n const schemeHandler = ctx.container.get(AuthenticationSchemeHandler);\n\n ctx.authenticationContext = await schemeHandler.handle(authorizationHeader);\n\n await next();\n };\n};\n","import { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { ServerKitBodyParser } from '../../serverkit.bodyparser.js';\n\n/**\n * Parses the request body based on `Content-Type` and assigns it to `ctx.body`.\n * Rejects requests with unexpected or unsupported content types.\n *\n * Supported types: JSON, URL-encoded form, text, multipart, PDF (raw buffer).\n * Requires a body when `contentTypes` is non-empty; otherwise rejects bodies.\n *\n * @param contentTypes - Allowed MIME types (e.g. `['application/json', 'application/x-www-form-urlencoded']`).\n * Use an empty array to disallow any request body.\n * @returns {@link ServerKitRouterMiddleware} that parses the body and sets `ctx.body`.\n * @throws HTTP 400 if body is present when no content types are allowed.\n * @throws HTTP 411 if body is required but missing.\n * @throws HTTP 415 if `Content-Type` is not in `contentTypes`.\n * @throws HTTP 422 if body is invalid or media type is unsupported.\n */\nexport const bodyParserMiddleware = (contentTypes: string[]): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n if (contentTypes.length === 0) {\n if (ctx.request.length > 0) {\n throw httpError(400).withDetails({ body: 'Unexpected body' });\n }\n } else {\n if (ctx.request.length > 0) {\n if (!ctx.request.is(contentTypes)) {\n throw httpError(415).withDetails({\n 'content-type': `must be ${contentTypes.length > 1 ? 'one of ' : ''}${contentTypes.join(', ')}`,\n value: ctx.request.type,\n });\n }\n\n try {\n const parser = ctx.container.get(ServerKitBodyParser);\n const result = await parser.parse(ctx);\n ctx.body = result.parsed;\n ctx.rawBody = result.raw;\n } catch (error) {\n if (IsHttpError(error)) {\n throw error;\n }\n throw httpError(422)\n .withCause(error as Error)\n .withDetails({ body: 'Invalid request body format' });\n }\n } else {\n throw httpError(411);\n }\n }\n await next();\n };\n};\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './parsers/serverkit.parser.js';\nimport { ServerKitContext } from './serverkit.context.js';\nimport { unique } from '@maroonedsoftware/utilities';\nimport { httpError } from '@maroonedsoftware/errors';\n\n/**\n * DI-injectable map of MIME subtypes to {@link ServerKitParser} instances.\n *\n * Register parser instances against their MIME subtypes, then bind this map\n * in the InjectKit container so {@link ServerKitBodyParser} can resolve them.\n *\n * @example\n * ```typescript\n * registry\n * .register(ServerKitParserMappings)\n * .useMap()\n * .add('json', JsonParser)\n * .add('urlencoded', FormParser);\n * ```\n */\n@Injectable()\nexport class ServerKitParserMappings extends Map<string, ServerKitParser> {}\n\n/**\n * Selects and invokes the appropriate {@link ServerKitParser} for the incoming request\n * based on its `Content-Type` header.\n *\n * The set of supported MIME types is derived from the keys of the injected\n * {@link ServerKitParserMappings}. Duplicate keys are deduplicated automatically.\n *\n * @throws HTTP 415 if the request's `Content-Type` does not match any registered parser.\n *\n * @see {@link defaultParserMappings} – convenience map for the standard parsers\n * @see {@link bodyParserMiddleware} – the middleware that invokes this class\n */\n@Injectable()\nexport class ServerKitBodyParser {\n private readonly mimeTypes: string[];\n constructor(private readonly parsers: ServerKitParserMappings) {\n this.mimeTypes = unique(Array.from(this.parsers.keys()));\n }\n\n /**\n * Matches the request's `Content-Type` to a registered parser and delegates parsing.\n *\n * @param ctx - The current {@link ServerKitContext}; used to inspect `Content-Type` and access `ctx.req`.\n * @returns The {@link ServerKitParserResult} from the matched parser.\n * @throws HTTP 415 if no parser is registered for the request's content type.\n */\n async parse(ctx: ServerKitContext): Promise<ServerKitParserResult> {\n const mimeType = ctx.request.is(this.mimeTypes);\n if (!mimeType) {\n throw httpError(415).withDetails({ body: 'Unsupported media type' });\n }\n const parser = this.parsers.get(mimeType);\n if (!parser) {\n throw httpError(415).withDetails({ body: 'Unsupported media type' });\n }\n return parser.parse(ctx.req);\n }\n}\n","import { invalidAuthenticationContext } from '@maroonedsoftware/authentication';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { httpError, unauthorizedError } from '@maroonedsoftware/errors';\n\n/**\n * Options for {@link requireSecurity}.\n */\ntype SecurityOptions = {\n /** When set, the authenticated user must have this role in their `AuthenticationContext.roles` array. */\n roles?: string[];\n};\n\n/**\n * Router middleware that enforces authentication and optional role-based authorization.\n *\n * Reads `ctx.authenticationContext` (set by `authenticationMiddleware`) and:\n * - Throws HTTP 401 with a `WWW-Authenticate: Bearer error=\"invalid_token\"` header\n * if the context is `invalidAuthenticationContext`.\n * - Throws HTTP 403 if `options.role` is specified and the user does not have that role.\n * - Calls `next()` otherwise.\n *\n * @param options - Optional {@link SecurityOptions} for role-based access control.\n * @returns A {@link ServerKitRouterMiddleware} that guards the route.\n *\n * @example\n * ```typescript\n * // Require any authenticated user\n * router.get('/profile', requireSecurity(), handler);\n *\n * // Require the 'admin' role\n * router.delete('/users/:id', requireSecurity({ role: 'admin' }), handler);\n * ```\n */\nexport const requireSecurity = (options?: SecurityOptions): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n const authenticationContext = ctx.authenticationContext;\n\n if (authenticationContext === invalidAuthenticationContext) {\n throw unauthorizedError('Bearer error=\"invalid_token\"');\n }\n\n if (options?.roles && options.roles.length > 0 && !options.roles.some(role => authenticationContext.roles.includes(role))) {\n throw httpError(403).withInternalDetails({\n message: 'Insufficient role',\n requiredRoles: options.roles,\n userRoles: authenticationContext.roles.join(', '),\n });\n }\n\n await next();\n };\n};\n","import { createHmac, BinaryToTextEncoding } from 'node:crypto';\nimport { ServerKitRouterMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\nimport { AppConfig } from '@maroonedsoftware/appconfig';\n\n/**\n * Configuration for {@link requireSignature}.\n *\n * Stored in `AppConfig` and retrieved by key at request time, so the values\n * can be loaded from any AppConfig source (JSON, `.env`, GCP secrets, etc.).\n */\nexport type SignatureOptions = {\n /** Name of the request header that carries the HMAC signature (e.g. `'X-Signature'`). */\n header: string;\n /** Secret key used to compute the HMAC. */\n secret: string;\n /** HMAC algorithm passed to `crypto.createHmac` (e.g. `'sha256'`, `'sha512'`). */\n algorithm: string;\n /** Output encoding for `hmac.digest()` (e.g. `'hex'`, `'base64'`). */\n digest: BinaryToTextEncoding;\n};\n\n/**\n * Router middleware that verifies a request signature against an HMAC of `ctx.rawBody`.\n *\n * Reads {@link SignatureOptions} from `AppConfig` using `optionsKey`, then:\n * - Computes `HMAC(algorithm, secret).update(ctx.rawBody).digest(digest)`\n * - Reads the expected signature from the request header named `options.header`\n * - Throws HTTP 401 (with internal diagnostics) if the signatures do not match\n * - Calls `next()` otherwise\n *\n * Requires `ctx.rawBody` to be populated before this middleware runs — use\n * {@link bodyParserMiddleware} upstream to ensure the raw bytes are captured.\n *\n * @param optionsKey - Key used to retrieve {@link SignatureOptions} from `AppConfig` via `getAs`.\n * @returns A {@link ServerKitRouterMiddleware} that guards the route.\n *\n * @example\n * ```typescript\n * // config.json\n * // { \"webhook\": { \"header\": \"X-Hub-Signature-256\", \"secret\": \"${env:WEBHOOK_SECRET}\", \"algorithm\": \"sha256\", \"digest\": \"hex\" } }\n *\n * router.post('/webhooks/github', requireSignature('webhook'), handler);\n * ```\n */\nexport const requireSignature = (optionsKey: string): ServerKitRouterMiddleware => {\n return async (ctx, next) => {\n const options = ctx.container.get(AppConfig).getAs<SignatureOptions>(optionsKey);\n\n const { header, secret, algorithm, digest } = options;\n\n const hmac = createHmac(algorithm, secret).update(ctx.rawBody);\n\n const signature = ctx.get(header);\n const computedSignature = hmac.digest(digest);\n if (computedSignature !== signature) {\n throw httpError(401).withInternalDetails({\n message: 'Invalid signature',\n header,\n computedSignature,\n signature,\n algorithm,\n digest,\n });\n }\n\n await next();\n };\n};\n","import { BinaryLike } from 'node:crypto';\nimport { IncomingMessage } from 'http';\nimport { Injectable } from 'injectkit';\n\n/**\n * The result returned by every {@link ServerKitParser}.\n *\n * @property parsed - The structured/deserialized value derived from the body (e.g. a plain object for JSON).\n * @property raw - The unprocessed body bytes as read from the stream. Parsers that do not retain a raw\n * representation (e.g. binary, multipart) return an empty `Buffer`.\n */\nexport type ServerKitParserResult = {\n parsed: unknown;\n raw: BinaryLike;\n};\n\n/**\n * Abstract base class for all ServerKit body parsers.\n *\n * Implementations read from an {@link IncomingMessage} stream and return a\n * {@link ServerKitParserResult} containing both the parsed value and the raw body.\n * Each parser is registered in the DI container and selected by MIME type via\n * {@link ServerKitBodyParser}.\n *\n * @see {@link ServerKitBodyParser} – dispatcher that selects the right parser by MIME type\n * @see {@link defaultParserMappings} – built-in MIME-type-to-parser map\n */\n@Injectable()\nexport abstract class ServerKitParser {\n /**\n * Reads and parses the body from the given request stream.\n *\n * @param req - The incoming HTTP request whose body should be consumed.\n * @returns A promise resolving to a {@link ServerKitParserResult}.\n */\n abstract parse(req: IncomingMessage): Promise<ServerKitParserResult>;\n}\n","import { parse, Reviver } from '@hapi/bourne';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\nimport { httpError } from '@maroonedsoftware/errors';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { Injectable } from 'injectkit';\n\n/**\n * Configuration options for {@link JsonParser}.\n *\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property strict - When `true` (default), only JSON objects `{}` and arrays `[]` are accepted.\n * When `false`, any valid JSON value (string, number, etc.) is allowed.\n * @property protoAction - How `@hapi/bourne` handles `__proto__` keys. Defaults to `'error'`.\n * @property reviver - Optional JSON reviver function passed to `@hapi/bourne`.\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'1mb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n */\n@Injectable()\nexport class JsonParserOptions implements raw.Options {\n strict?: boolean;\n protoAction?: 'error' | 'remove' | 'ignore';\n reviver?: Reviver;\n encoding?: string;\n limit?: string;\n length?: number;\n}\n\n// Allowed whitespace is defined in RFC 7159\n// http://www.rfc-editor.org/rfc/rfc7159.txt\n/* eslint-disable-next-line no-control-regex */\nconst strictJSONReg = /^[\\x20\\x09\\x0a\\x0d]*(\\[|\\{)/;\n\n/**\n * Parses a JSON request body using `@hapi/bourne` for prototype-pollution protection.\n *\n * In strict mode (default), only top-level objects `{}` and arrays `[]` are accepted;\n * any other value throws HTTP 400. In non-strict mode, any valid JSON value is accepted.\n * An empty body always resolves to `{ parsed: undefined }`.\n *\n * @throws HTTP 400 if the body is not valid JSON or fails the strict-mode check.\n *\n * @example\n * ```typescript\n * // Default strict mode (injectable)\n * const parser = new JsonParser(new JsonParserOptions());\n *\n * // Custom options\n * const lenient = new JsonParser({ strict: false, protoAction: 'remove' });\n * ```\n */\n@Injectable()\nexport class JsonParser extends ServerKitParser {\n constructor(private readonly options: JsonParserOptions) {\n super();\n }\n\n /**\n * Reads, decompresses, and JSON-parses the request body.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <object|array|undefined>, raw: <original string> }`.\n * @throws HTTP 400 on malformed JSON or strict-mode violation.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '1mb';\n\n const strict = this.options.strict ?? true;\n const protoAction = this.options.protoAction ?? 'error';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n const doParse = (str: string) => {\n try {\n if (this.options.reviver) {\n return parse(str, this.options.reviver, { protoAction });\n }\n return parse(str, { protoAction });\n } catch (err) {\n throw httpError(400).withCause(err as Error);\n }\n };\n\n if (!strict) {\n return str ? { parsed: doParse(str), raw: str } : { parsed: undefined, raw: str };\n } else if (!str) {\n return { parsed: undefined, raw: str };\n } else if (!strictJSONReg.test(str)) {\n throw httpError(400).withDetails({ body: 'Invalid JSON, only supports object and array' });\n }\n return { parsed: doParse(str), raw: str };\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Configuration options for {@link TextParser}.\n *\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'1mb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n */\nexport class TextParserOptions implements raw.Options {\n encoding?: string;\n limit?: string;\n length?: number;\n}\n\n/**\n * Reads the request body as a plain string.\n *\n * No structural parsing is performed; the raw text is returned as both `parsed` and `raw`.\n * Suitable for `text/plain` and similar content types. Decompresses the stream via `inflation`\n * before buffering.\n *\n * @example\n * ```typescript\n * const parser = new TextParser(new TextParserOptions());\n * const { parsed } = await parser.parse(req); // parsed is a string\n * ```\n */\n@Injectable()\nexport class TextParser extends ServerKitParser {\n constructor(private readonly options: TextParserOptions) {\n super();\n }\n\n /**\n * Reads and decompresses the request body into a string.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <string>, raw: <same string> }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '1mb';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n return { parsed: str, raw: str };\n }\n}\n","import { httpError } from '@maroonedsoftware/errors';\nimport { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport { parse, IParseOptions } from 'qs';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Configuration options for {@link FormParser}.\n *\n * Combines `raw-body` read options with `qs` parse options.\n * All fields are optional; defaults are applied by the parser itself.\n *\n * @property encoding - Body text encoding (default: `'utf8'`).\n * @property limit - Maximum body size (default: `'56kb'`).\n * @property length - Expected byte length from `Content-Length` (auto-set by the parser).\n * @property allowDots - When `true`, `qs` interprets dot notation (`user.name`) as nested objects.\n * @property depth - Maximum nesting depth for parsed objects (default: `qs` default of `5`).\n * @property parameterLimit - Maximum number of parameters to parse (default: `qs` default of `1000`).\n */\n@Injectable()\nexport class FormParserOptions implements raw.Options, IParseOptions {\n encoding?: string;\n limit?: string;\n length?: number;\n allowDots?: boolean;\n depth?: number;\n parameterLimit?: number;\n}\n\n/**\n * Parses a URL-encoded (`application/x-www-form-urlencoded`) request body using `qs`.\n *\n * Supports nested objects via bracket notation (`user[name]=alice`) by default.\n * Dot notation (`user.name=alice`) requires `allowDots: true` in the options.\n * An empty body resolves to `{ parsed: {} }`.\n *\n * @throws HTTP 400 if `qs.parse` throws unexpectedly.\n *\n * @example\n * ```typescript\n * // Default options (injectable)\n * const parser = new FormParser(new FormParserOptions());\n *\n * // Enable dot notation\n * const parser = new FormParser({ allowDots: true });\n * ```\n */\n@Injectable()\nexport class FormParser extends ServerKitParser {\n constructor(private readonly options: FormParserOptions) {\n super();\n }\n\n /**\n * Reads, decompresses, and URL-decodes the request body.\n *\n * @param req - Incoming HTTP request whose body will be consumed.\n * @returns `{ parsed: <object>, raw: <original url-encoded string> }`.\n * @throws HTTP 400 if parsing fails.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n const len = req.headers['content-length'];\n const contentEncoding = req.headers['content-encoding'] || 'identity';\n const length: number | undefined = len && contentEncoding === 'identity' ? ~~len : undefined;\n const encoding = this.options.encoding ?? 'utf8';\n const limit = this.options.limit ?? '56kb';\n\n const str = await raw(inflate(req), { encoding, limit, length });\n\n try {\n return { parsed: parse(str, this.options), raw: str };\n } catch (err) {\n throw httpError(400).withCause(err as Error);\n }\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport { MultipartBody } from '@maroonedsoftware/multipart';\n\n/**\n * Wraps the request stream in a {@link MultipartBody} for lazy multipart/form-data parsing.\n *\n * The body is **not** eagerly consumed; fields and files are read on-demand through the\n * `MultipartBody` API. `raw` is always `undefined` because the stream cannot be replayed\n * once consumed.\n */\n@Injectable()\nexport class MultipartParser extends ServerKitParser {\n /**\n * Creates a {@link MultipartBody} around the request stream.\n *\n * @param req - Incoming HTTP request containing the multipart body.\n * @returns `{ parsed: MultipartBody, raw: Buffer(0) }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: new MultipartBody(req), raw: Buffer.from('') };\n }\n}\n","import { Injectable } from 'injectkit';\nimport { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';\nimport { IncomingMessage } from 'http';\nimport raw from 'raw-body';\nimport inflate from 'inflation';\n\n/**\n * Reads the request body as a raw `Buffer` without any text decoding or structural parsing.\n *\n * Useful for binary payloads (e.g. PDFs, images, protobuf) where the caller needs the\n * untransformed bytes. Decompresses the stream via `inflation` before buffering.\n *\n * `raw` is always `undefined` because the `Buffer` itself is both the parsed and raw form.\n */\n@Injectable()\nexport class BinaryParser extends ServerKitParser {\n /**\n * Buffers the (optionally compressed) request body into a `Buffer`.\n *\n * @param req - Incoming HTTP request to read.\n * @returns `{ parsed: Buffer, raw: Buffer(0) }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: await raw(inflate(req)), raw: Buffer.from('') };\n }\n}\n","import { Identifier } from 'injectkit';\nimport { FormParser } from './form.parser.js';\nimport { JsonParser } from './json.parser.js';\nimport { MultipartParser } from './multipart.parser.js';\nimport { ServerKitParser } from './serverkit.parser.js';\nimport { TextParser } from './text.parser.js';\n\n/**\n * Built-in MIME-subtype-to-parser mappings used by {@link bodyParserMiddleware}.\n *\n * Each key is a MIME subtype (matched against `Content-Type` via Koa's `ctx.request.is()`)\n * and the value is the InjectKit {@link Identifier} for the corresponding {@link ServerKitParser}.\n *\n * | MIME subtype | Parser |\n * | --------------------- | ----------------- |\n * | `json` | {@link JsonParser} |\n * | `application/*+json` | {@link JsonParser} |\n * | `urlencoded` | {@link FormParser} |\n * | `text` | {@link TextParser} |\n * | `multipart` | {@link MultipartParser} |\n *\n * Extend by spreading into a new object and registering the result in the DI container:\n * ```typescript\n * const myMappings = { ...defaultParserMappings, pdf: BinaryParser };\n * ```\n */\nexport const defaultParserMappings: Record<string, Identifier<ServerKitParser>> = {\n json: JsonParser,\n 'application/*+json': JsonParser,\n urlencoded: FormParser,\n text: TextParser,\n multipart: MultipartParser,\n} as const;\n"],"mappings":"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","names":["Injectable","ServerKitContext","Router","ServerKitRouter","options","Router","cors","corsMiddleware","options","originMatcher","ctx","origin","get","matchers","matcher","test","cors","allowMethods","secureContext","keepHeadersOnError","privateNetworkAccess","IsHttpError","IsServerkitError","errorMiddleware","ctx","next","status","body","statusCode","message","details","url","URL","toString","app","emit","error","IsHttpError","headers","entry","Object","entries","set","IsServerkitError","RateLimiterRes","httpError","isRateLimiterError","error","RateLimiterRes","rateLimiterMiddleware","rateLimiter","ctx","next","consume","ip","headers","msBeforeNext","toString","points","remainingPoints","Math","ceil","Date","now","httpError","withCause","withHeaders","crypto","Logger","serverKitContextMiddleware","container","ctx","next","scopedContainer","createScopedContainer","override","ServerKitContext","logger","get","Logger","loggerName","path","userAgent","ipAddress","ip","correlationId","headers","Array","isArray","crypto","randomUUID","requestId","set","AuthenticationSchemeHandler","invalidAuthenticationContext","authenticationMiddleware","ctx","next","authenticationContext","invalidAuthenticationContext","authorizationHeader","req","headers","authorization","schemeHandler","container","get","AuthenticationSchemeHandler","handle","httpError","IsHttpError","Injectable","unique","httpError","ServerKitParserMappings","Map","ServerKitBodyParser","mimeTypes","parsers","unique","Array","from","keys","parse","ctx","mimeType","request","is","httpError","withDetails","body","parser","get","req","bodyParserMiddleware","contentTypes","ctx","next","length","request","httpError","withDetails","body","is","join","value","type","parser","container","get","ServerKitBodyParser","result","parse","parsed","rawBody","raw","error","IsHttpError","withCause","invalidAuthenticationContext","httpError","unauthorizedError","requireSecurity","options","ctx","next","authenticationContext","invalidAuthenticationContext","unauthorizedError","roles","length","some","role","includes","httpError","withInternalDetails","message","requiredRoles","userRoles","join","createHmac","httpError","AppConfig","requireSignature","optionsKey","ctx","next","options","container","get","AppConfig","getAs","header","secret","algorithm","digest","hmac","createHmac","update","rawBody","signature","computedSignature","httpError","withInternalDetails","message","Injectable","ServerKitParser","parse","raw","inflate","httpError","Injectable","JsonParserOptions","strict","protoAction","reviver","encoding","limit","length","strictJSONReg","JsonParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","doParse","err","httpError","withCause","parsed","test","withDetails","body","Injectable","raw","inflate","TextParserOptions","encoding","limit","length","TextParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","parsed","httpError","Injectable","parse","raw","inflate","FormParserOptions","encoding","limit","length","allowDots","depth","parameterLimit","FormParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","undefined","str","raw","inflate","parsed","err","httpError","withCause","Injectable","MultipartBody","MultipartParser","ServerKitParser","parse","req","parsed","MultipartBody","raw","Buffer","from","Injectable","raw","inflate","BinaryParser","ServerKitParser","parse","req","parsed","raw","inflate","Buffer","from","defaultParserMappings","json","JsonParser","urlencoded","FormParser","text","TextParser","multipart","MultipartParser"]}
@@ -4,6 +4,8 @@ import { ServerKitMiddleware } from '../../serverkit.middleware.js';
4
4
  * Populates {@link ServerKitContext} for each request: scoped container, logger,
5
5
  * logger name, user-agent, correlation ID, and request ID.
6
6
  * Reads or generates `X-Correlation-Id` and `X-Request-Id` and sets response headers.
7
+ * Registers the live `ctx` against the {@link ServerKitContext} injection token in the
8
+ * request-scoped container so downstream services can inject the current context.
7
9
  * Should be applied early so downstream middleware and routes can use `ctx.container` and `ctx.logger`.
8
10
  *
9
11
  * @param container - Root injectkit {@link Container} used to create a scoped container and resolve {@link Logger}.
@@ -1 +1 @@
1
- {"version":3,"file":"serverkit.context.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/serverkit.context.middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,WAAW,SAAS,KAAG,mBAsBjE,CAAC"}
1
+ {"version":3,"file":"serverkit.context.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/serverkit.context.middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,GAAI,WAAW,SAAS,KAAG,mBA0BjE,CAAC"}
@@ -31,4 +31,12 @@ export interface ServerKitContext extends Context {
31
31
  /** Authentication context. */
32
32
  authenticationContext: AuthenticationContext;
33
33
  }
34
+ /**
35
+ * Abstract class merged with the {@link ServerKitContext} interface so it can serve as an
36
+ * injectkit injection token. {@link serverKitContextMiddleware} registers the live `ctx`
37
+ * against this token in the request-scoped container, allowing services to declare
38
+ * `ServerKitContext` as a constructor dependency and receive the current Koa context.
39
+ */
40
+ export declare abstract class ServerKitContext implements ServerKitContext {
41
+ }
34
42
  //# sourceMappingURL=serverkit.context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverkit.context.d.ts","sourceRoot":"","sources":["../src/serverkit.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAiB,SAAQ,OAAO;IAC/C,8EAA8E;IAC9E,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,8BAA8B;IAC9B,qBAAqB,EAAE,qBAAqB,CAAC;CAC9C"}
1
+ {"version":3,"file":"serverkit.context.d.ts","sourceRoot":"","sources":["../src/serverkit.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAiB,SAAQ,OAAO;IAC/C,8EAA8E;IAC9E,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,8BAA8B;IAC9B,qBAAqB,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;;GAKG;AACH,8BAEsB,gBAAiB,YAAW,gBAAgB;CAAG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maroonedsoftware/koa",
3
- "version": "1.16.6",
3
+ "version": "1.17.1",
4
4
  "description": "Koa middleware, body parsing, and utilities for ServerKit",
5
5
  "author": {
6
6
  "name": "Marooned Software",
@@ -38,7 +38,7 @@
38
38
  "rate-limiter-flexible": "^11.0.1",
39
39
  "raw-body": "^3.0.2",
40
40
  "@maroonedsoftware/appconfig": "1.4.0",
41
- "@maroonedsoftware/authentication": "0.21.0",
41
+ "@maroonedsoftware/authentication": "0.22.0",
42
42
  "@maroonedsoftware/errors": "1.6.0",
43
43
  "@maroonedsoftware/logger": "1.1.0",
44
44
  "@maroonedsoftware/multipart": "1.1.2",