@maroonedsoftware/koa 1.2.1 → 1.3.0
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 +96 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -35
- package/dist/index.js.map +1 -1
- package/dist/parsers/binary.parser.d.ts +14 -0
- package/dist/parsers/binary.parser.d.ts.map +1 -1
- package/dist/parsers/form.parser.d.ts +47 -2
- package/dist/parsers/form.parser.d.ts.map +1 -1
- package/dist/parsers/json.parser.d.ts +44 -3
- package/dist/parsers/json.parser.d.ts.map +1 -1
- package/dist/parsers/multipart.parser.d.ts +13 -0
- package/dist/parsers/multipart.parser.d.ts.map +1 -1
- package/dist/parsers/serverkit.default.parsers.d.ts +23 -0
- package/dist/parsers/serverkit.default.parsers.d.ts.map +1 -0
- package/dist/parsers/serverkit.parser.d.ts +24 -0
- package/dist/parsers/serverkit.parser.d.ts.map +1 -1
- package/dist/parsers/text.parser.d.ts +34 -2
- package/dist/parsers/text.parser.d.ts.map +1 -1
- package/dist/serverkit.bodyparser.d.ts +34 -0
- package/dist/serverkit.bodyparser.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/parsers.setup.d.ts +0 -5
- package/dist/parsers.setup.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @maroonedsoftware/koa
|
|
2
2
|
|
|
3
|
-
Koa utilities and middleware for ServerKit: typed context, router, CORS, error handling, rate limiting, body parsing, and request-scoped DI via [injectkit](https://www.npmjs.com/package/injectkit).
|
|
3
|
+
Koa utilities and middleware for ServerKit: typed context, router, CORS, error handling, rate limiting, authentication, body parsing, and request-scoped DI via [injectkit](https://www.npmjs.com/package/injectkit).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,14 +12,16 @@ Peer dependencies: `koa`, `@koa/router`, `@koa/cors`.
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **ServerKitContext** — Koa context extended with `container`, `logger`, `requestId`, `correlationId`, and related request metadata
|
|
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
18
|
- **serverKitContextMiddleware** — Populates context with scoped container, logger, and request/correlation IDs
|
|
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)
|
|
22
|
+
- **authenticationMiddleware** — Resolves the `Authorization` header via `AuthenticationSchemeHandler` and populates `ctx.authenticationContext`
|
|
22
23
|
- **bodyParserMiddleware** — Parses JSON, form, text, multipart, or raw body by allowed content types
|
|
24
|
+
- **defaultParserMappings** — Pre-built MIME-type-to-parser map for use with `bodyParserMiddleware`
|
|
23
25
|
|
|
24
26
|
## Usage
|
|
25
27
|
|
|
@@ -29,7 +31,13 @@ Peer dependencies: `koa`, `@koa/router`, `@koa/cors`.
|
|
|
29
31
|
import Koa from 'koa';
|
|
30
32
|
import { InjectKitRegistry } from 'injectkit';
|
|
31
33
|
import { Logger, ConsoleLogger } from '@maroonedsoftware/logger';
|
|
32
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
ServerKitRouter,
|
|
36
|
+
serverKitContextMiddleware,
|
|
37
|
+
corsMiddleware,
|
|
38
|
+
errorMiddleware,
|
|
39
|
+
bodyParserMiddleware,
|
|
40
|
+
} from '@maroonedsoftware/koa';
|
|
33
41
|
|
|
34
42
|
const diRegistry = new InjectKitRegistry();
|
|
35
43
|
diRegistry.register(Logger).useClass(ConsoleLogger).asSingleton();
|
|
@@ -64,6 +72,37 @@ router.get('/api/users/:id', async ctx => {
|
|
|
64
72
|
});
|
|
65
73
|
```
|
|
66
74
|
|
|
75
|
+
### Authentication
|
|
76
|
+
|
|
77
|
+
`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.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import {
|
|
81
|
+
AuthenticationSchemeHandler,
|
|
82
|
+
AuthenticationHandlerMap,
|
|
83
|
+
} from '@maroonedsoftware/authentication';
|
|
84
|
+
import { authenticationMiddleware } from '@maroonedsoftware/koa';
|
|
85
|
+
|
|
86
|
+
// Register your scheme handlers in DI
|
|
87
|
+
diRegistry
|
|
88
|
+
.register(AuthenticationHandlerMap)
|
|
89
|
+
.useMap()
|
|
90
|
+
.add('Bearer', BearerAuthHandler);
|
|
91
|
+
|
|
92
|
+
diRegistry.register(AuthenticationSchemeHandler).asSingleton();
|
|
93
|
+
|
|
94
|
+
// Add to the middleware stack after serverKitContextMiddleware
|
|
95
|
+
app.use(serverKitContextMiddleware(container));
|
|
96
|
+
app.use(authenticationMiddleware());
|
|
97
|
+
|
|
98
|
+
// Access the resolved context in route handlers
|
|
99
|
+
router.get('/api/me', async ctx => {
|
|
100
|
+
const { subject, isAuthenticated } = ctx.authenticationContext;
|
|
101
|
+
if (!isAuthenticated) throw httpError(401);
|
|
102
|
+
ctx.body = { subject };
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
67
106
|
### CORS
|
|
68
107
|
|
|
69
108
|
```typescript
|
|
@@ -110,28 +149,67 @@ router.post('/api/json', bodyParserMiddleware(['application/json']), async ctx =
|
|
|
110
149
|
});
|
|
111
150
|
```
|
|
112
151
|
|
|
152
|
+
### Custom parser mappings
|
|
153
|
+
|
|
154
|
+
`defaultParserMappings` is the built-in MIME-type-to-parser map used by `bodyParserMiddleware`. You can extend or replace it to register additional parsers:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { defaultParserMappings, BinaryParser } from '@maroonedsoftware/koa';
|
|
158
|
+
import { ServerKitBodyParser } from '@maroonedsoftware/koa';
|
|
159
|
+
|
|
160
|
+
const customMappings = {
|
|
161
|
+
...defaultParserMappings,
|
|
162
|
+
pdf: BinaryParser,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Pass to bodyParserMiddleware via a custom ServerKitBodyParser instance
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The default mappings are:
|
|
169
|
+
|
|
170
|
+
| MIME subtype | Parser |
|
|
171
|
+
| -------------------- | --------------- |
|
|
172
|
+
| `json` | `JsonParser` |
|
|
173
|
+
| `application/*+json` | `JsonParser` |
|
|
174
|
+
| `urlencoded` | `FormParser` |
|
|
175
|
+
| `text` | `TextParser` |
|
|
176
|
+
| `multipart` | `MultipartParser` |
|
|
177
|
+
|
|
113
178
|
## API
|
|
114
179
|
|
|
115
180
|
### ServerKitContext
|
|
116
181
|
|
|
117
|
-
| Property
|
|
118
|
-
|
|
|
119
|
-
| `container`
|
|
120
|
-
| `logger`
|
|
121
|
-
| `loggerName`
|
|
122
|
-
| `userAgent`
|
|
123
|
-
| `correlationId`
|
|
124
|
-
| `requestId`
|
|
182
|
+
| Property | Type | Description |
|
|
183
|
+
| ----------------------- | ----------------------- | ------------------------------------------------------ |
|
|
184
|
+
| `container` | `Container` | Request-scoped injectkit container |
|
|
185
|
+
| `logger` | `Logger` | Request-scoped logger |
|
|
186
|
+
| `loggerName` | `string` | Logger name (e.g. request path) |
|
|
187
|
+
| `userAgent` | `string` | `User-Agent` header value |
|
|
188
|
+
| `correlationId` | `string` | From `X-Correlation-Id` header or generated |
|
|
189
|
+
| `requestId` | `string` | From `X-Request-Id` header or generated |
|
|
190
|
+
| `rawBody` | `unknown` | Raw (unparsed) request body |
|
|
191
|
+
| `authenticationContext` | `AuthenticationContext` | Resolved authentication context; set by `authenticationMiddleware` |
|
|
125
192
|
|
|
126
193
|
### Middleware
|
|
127
194
|
|
|
128
|
-
| Middleware | Description
|
|
129
|
-
| --------------------------------------- |
|
|
130
|
-
| `serverKitContextMiddleware(container)` | Sets `ctx.container`, `ctx.logger`, IDs; sets `X-Correlation-Id`, `X-Request-Id` response headers
|
|
131
|
-
| `corsMiddleware(options?)` | CORS via `@koa/cors`; `origin`: `'*'`, string, or `(string \| RegExp)[]`
|
|
132
|
-
| `errorMiddleware()` | Catches errors, maps HTTP errors to status/body, 404/500, emits app events
|
|
133
|
-
| `rateLimiterMiddleware(rateLimiter)` | Consumes one token per request by IP; throws 429 when exceeded
|
|
134
|
-
| `
|
|
195
|
+
| Middleware | Description |
|
|
196
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `serverKitContextMiddleware(container)` | Sets `ctx.container`, `ctx.logger`, IDs; sets `X-Correlation-Id`, `X-Request-Id` response headers |
|
|
198
|
+
| `corsMiddleware(options?)` | CORS via `@koa/cors`; `origin`: `'*'`, string, or `(string \| RegExp)[]` |
|
|
199
|
+
| `errorMiddleware()` | Catches errors, maps HTTP errors to status/body, 404/500, emits app events |
|
|
200
|
+
| `rateLimiterMiddleware(rateLimiter)` | Consumes one token per request by IP; throws 429 when exceeded |
|
|
201
|
+
| `authenticationMiddleware()` | Resolves `Authorization` header via `AuthenticationSchemeHandler`; populates `ctx.authenticationContext` |
|
|
202
|
+
| `bodyParserMiddleware(contentTypes)` | Parses body by allowed MIME types; throws 400/411/415/422 on invalid input |
|
|
203
|
+
|
|
204
|
+
### Parser options
|
|
205
|
+
|
|
206
|
+
Parser options classes are registered with InjectKit and can be configured in the DI container:
|
|
207
|
+
|
|
208
|
+
| Class | Key options |
|
|
209
|
+
| -------------------- | ---------------------------------------------- |
|
|
210
|
+
| `JsonParserOptions` | `strict`, `protoAction`, `reviver`, `encoding`, `limit` |
|
|
211
|
+
| `FormParserOptions` | `allowDots`, `depth`, `parameterLimit`, `encoding`, `limit` |
|
|
212
|
+
| `TextParserOptions` | `encoding`, `limit` |
|
|
135
213
|
|
|
136
214
|
## License
|
|
137
215
|
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,5 @@ export * from './parsers/text.parser.js';
|
|
|
14
14
|
export * from './parsers/form.parser.js';
|
|
15
15
|
export * from './parsers/multipart.parser.js';
|
|
16
16
|
export * from './parsers/binary.parser.js';
|
|
17
|
-
export * from './parsers.
|
|
17
|
+
export * from './parsers/serverkit.default.parsers.js';
|
|
18
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AACjE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qDAAqD,CAAC;AACpE,cAAc,kDAAkD,CAAC;AACjE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -163,6 +163,13 @@ var ServerKitBodyParser = class {
|
|
|
163
163
|
this.parsers = parsers;
|
|
164
164
|
this.mimeTypes = unique(Array.from(this.parsers.keys()));
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Matches the request's `Content-Type` to a registered parser and delegates parsing.
|
|
168
|
+
*
|
|
169
|
+
* @param ctx - The current {@link ServerKitContext}; used to inspect `Content-Type` and access `ctx.req`.
|
|
170
|
+
* @returns The {@link ServerKitParserResult} from the matched parser.
|
|
171
|
+
* @throws HTTP 415 if no parser is registered for the request's content type.
|
|
172
|
+
*/
|
|
166
173
|
async parse(ctx) {
|
|
167
174
|
const mimeType = ctx.request.is(this.mimeTypes);
|
|
168
175
|
if (!mimeType) {
|
|
@@ -260,15 +267,36 @@ function _ts_metadata2(k, v) {
|
|
|
260
267
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
261
268
|
}
|
|
262
269
|
__name(_ts_metadata2, "_ts_metadata");
|
|
270
|
+
var JsonParserOptions = class {
|
|
271
|
+
static {
|
|
272
|
+
__name(this, "JsonParserOptions");
|
|
273
|
+
}
|
|
274
|
+
strict;
|
|
275
|
+
protoAction;
|
|
276
|
+
reviver;
|
|
277
|
+
encoding;
|
|
278
|
+
limit;
|
|
279
|
+
length;
|
|
280
|
+
};
|
|
281
|
+
JsonParserOptions = _ts_decorate3([
|
|
282
|
+
Injectable3()
|
|
283
|
+
], JsonParserOptions);
|
|
263
284
|
var strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;
|
|
264
285
|
var JsonParser = class extends ServerKitParser {
|
|
265
286
|
static {
|
|
266
287
|
__name(this, "JsonParser");
|
|
267
288
|
}
|
|
268
289
|
options;
|
|
269
|
-
constructor(options
|
|
290
|
+
constructor(options) {
|
|
270
291
|
super(), this.options = options;
|
|
271
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Reads, decompresses, and JSON-parses the request body.
|
|
295
|
+
*
|
|
296
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
297
|
+
* @returns `{ parsed: <object|array|undefined>, raw: <original string> }`.
|
|
298
|
+
* @throws HTTP 400 on malformed JSON or strict-mode violation.
|
|
299
|
+
*/
|
|
272
300
|
async parse(req) {
|
|
273
301
|
const len = req.headers["content-length"];
|
|
274
302
|
const contentEncoding = req.headers["content-encoding"] || "identity";
|
|
@@ -343,14 +371,28 @@ function _ts_metadata3(k, v) {
|
|
|
343
371
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
344
372
|
}
|
|
345
373
|
__name(_ts_metadata3, "_ts_metadata");
|
|
374
|
+
var TextParserOptions = class {
|
|
375
|
+
static {
|
|
376
|
+
__name(this, "TextParserOptions");
|
|
377
|
+
}
|
|
378
|
+
encoding;
|
|
379
|
+
limit;
|
|
380
|
+
length;
|
|
381
|
+
};
|
|
346
382
|
var TextParser = class extends ServerKitParser {
|
|
347
383
|
static {
|
|
348
384
|
__name(this, "TextParser");
|
|
349
385
|
}
|
|
350
386
|
options;
|
|
351
|
-
constructor(options
|
|
387
|
+
constructor(options) {
|
|
352
388
|
super(), this.options = options;
|
|
353
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Reads and decompresses the request body into a string.
|
|
392
|
+
*
|
|
393
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
394
|
+
* @returns `{ parsed: <string>, raw: <same string> }`.
|
|
395
|
+
*/
|
|
354
396
|
async parse(req) {
|
|
355
397
|
const len = req.headers["content-length"];
|
|
356
398
|
const contentEncoding = req.headers["content-encoding"] || "identity";
|
|
@@ -393,14 +435,35 @@ function _ts_metadata4(k, v) {
|
|
|
393
435
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
394
436
|
}
|
|
395
437
|
__name(_ts_metadata4, "_ts_metadata");
|
|
438
|
+
var FormParserOptions = class {
|
|
439
|
+
static {
|
|
440
|
+
__name(this, "FormParserOptions");
|
|
441
|
+
}
|
|
442
|
+
encoding;
|
|
443
|
+
limit;
|
|
444
|
+
length;
|
|
445
|
+
allowDots;
|
|
446
|
+
depth;
|
|
447
|
+
parameterLimit;
|
|
448
|
+
};
|
|
449
|
+
FormParserOptions = _ts_decorate5([
|
|
450
|
+
Injectable5()
|
|
451
|
+
], FormParserOptions);
|
|
396
452
|
var FormParser = class extends ServerKitParser {
|
|
397
453
|
static {
|
|
398
454
|
__name(this, "FormParser");
|
|
399
455
|
}
|
|
400
456
|
options;
|
|
401
|
-
constructor(options
|
|
457
|
+
constructor(options) {
|
|
402
458
|
super(), this.options = options;
|
|
403
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Reads, decompresses, and URL-decodes the request body.
|
|
462
|
+
*
|
|
463
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
464
|
+
* @returns `{ parsed: <object>, raw: <original url-encoded string> }`.
|
|
465
|
+
* @throws HTTP 400 if parsing fails.
|
|
466
|
+
*/
|
|
404
467
|
async parse(req) {
|
|
405
468
|
const len = req.headers["content-length"];
|
|
406
469
|
const contentEncoding = req.headers["content-encoding"] || "identity";
|
|
@@ -444,6 +507,12 @@ var MultipartParser = class extends ServerKitParser {
|
|
|
444
507
|
static {
|
|
445
508
|
__name(this, "MultipartParser");
|
|
446
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Creates a {@link MultipartBody} around the request stream.
|
|
512
|
+
*
|
|
513
|
+
* @param req - Incoming HTTP request containing the multipart body.
|
|
514
|
+
* @returns `{ parsed: MultipartBody, raw: undefined }`.
|
|
515
|
+
*/
|
|
447
516
|
async parse(req) {
|
|
448
517
|
return {
|
|
449
518
|
parsed: new MultipartBody(req),
|
|
@@ -470,6 +539,12 @@ var BinaryParser = class extends ServerKitParser {
|
|
|
470
539
|
static {
|
|
471
540
|
__name(this, "BinaryParser");
|
|
472
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Buffers the (optionally compressed) request body into a `Buffer`.
|
|
544
|
+
*
|
|
545
|
+
* @param req - Incoming HTTP request to read.
|
|
546
|
+
* @returns `{ parsed: Buffer, raw: undefined }`.
|
|
547
|
+
*/
|
|
473
548
|
async parse(req) {
|
|
474
549
|
return {
|
|
475
550
|
parsed: await raw4(inflate4(req)),
|
|
@@ -481,55 +556,33 @@ BinaryParser = _ts_decorate7([
|
|
|
481
556
|
Injectable7()
|
|
482
557
|
], BinaryParser);
|
|
483
558
|
|
|
484
|
-
// src/parsers.
|
|
559
|
+
// src/parsers/serverkit.default.parsers.ts
|
|
485
560
|
var defaultParserMappings = {
|
|
486
|
-
|
|
561
|
+
json: JsonParser,
|
|
487
562
|
"application/*+json": JsonParser,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
563
|
+
urlencoded: FormParser,
|
|
564
|
+
text: TextParser,
|
|
565
|
+
multipart: MultipartParser
|
|
491
566
|
};
|
|
492
|
-
var defaultParserClasses = [
|
|
493
|
-
JsonParser,
|
|
494
|
-
FormParser,
|
|
495
|
-
TextParser,
|
|
496
|
-
MultipartParser,
|
|
497
|
-
BinaryParser
|
|
498
|
-
];
|
|
499
|
-
var setupParsers = /* @__PURE__ */ __name((registry, overrides = {}) => {
|
|
500
|
-
const merged = {
|
|
501
|
-
...defaultParserMappings,
|
|
502
|
-
...overrides
|
|
503
|
-
};
|
|
504
|
-
const mapRegistration = registry.register(ServerKitParserMappings).useMap(ServerKitParserMappings);
|
|
505
|
-
for (const [key, parser] of Object.entries(merged)) {
|
|
506
|
-
mapRegistration.set(key, parser);
|
|
507
|
-
}
|
|
508
|
-
const parserClasses = /* @__PURE__ */ new Set([
|
|
509
|
-
...defaultParserClasses,
|
|
510
|
-
...Object.values(overrides)
|
|
511
|
-
]);
|
|
512
|
-
for (const parserClass of parserClasses) {
|
|
513
|
-
registry.register(parserClass).useClass(parserClass).asSingleton();
|
|
514
|
-
}
|
|
515
|
-
registry.register(ServerKitBodyParser).useClass(ServerKitBodyParser).asSingleton();
|
|
516
|
-
}, "setupParsers");
|
|
517
567
|
export {
|
|
518
568
|
BinaryParser,
|
|
519
569
|
FormParser,
|
|
570
|
+
FormParserOptions,
|
|
520
571
|
JsonParser,
|
|
572
|
+
JsonParserOptions,
|
|
521
573
|
MultipartParser,
|
|
522
574
|
ServerKitBodyParser,
|
|
523
575
|
ServerKitParser,
|
|
524
576
|
ServerKitParserMappings,
|
|
525
577
|
ServerKitRouter,
|
|
526
578
|
TextParser,
|
|
579
|
+
TextParserOptions,
|
|
527
580
|
authenticationMiddleware,
|
|
528
581
|
bodyParserMiddleware,
|
|
529
582
|
corsMiddleware,
|
|
583
|
+
defaultParserMappings,
|
|
530
584
|
errorMiddleware,
|
|
531
585
|
rateLimiterMiddleware,
|
|
532
|
-
serverKitContextMiddleware
|
|
533
|
-
setupParsers
|
|
586
|
+
serverKitContextMiddleware
|
|
534
587
|
};
|
|
535
588
|
//# sourceMappingURL=index.js.map
|
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/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.setup.ts"],"sourcesContent":["import { DefaultState } from 'koa';\nimport Router 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 * @returns A new {@link Router} instance.\n */\nexport const ServerKitRouter = <StateT = DefaultState, ContextT = ServerKitContext>() => new Router<StateT, ContextT>();\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 } 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 {\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 } from 'rate-limiter-flexible';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\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) {\n throw httpError(429).withCause(error as Error);\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.correlationId = ctx.get('x-correlation-id') ?? crypto.randomUUID();\n ctx.requestId = ctx.get('x-request-id') ?? 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.serviceLocator.get(AuthenticationSchemeHandler);\n\n ctx.authenticationContext = await schemeHandler.handle(authorizationHeader);\n\n await next();\n };\n};\n","import { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { ServerKitMiddleware } 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 ServerKitMiddleware} 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[]): ServerKitMiddleware => {\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@Injectable()\nexport class ServerKitParserMappings extends Map<string, ServerKitParser> {}\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 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 { IncomingMessage } from 'http';\nimport { Injectable } from 'injectkit';\n\nexport type ServerKitParserResult = {\n parsed: unknown;\n raw: unknown;\n};\n\n@Injectable()\nexport abstract class ServerKitParser {\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\nexport type JsonParserOptions = raw.Options & {\n strict?: boolean;\n protoAction?: 'error' | 'remove' | 'ignore';\n reviver?: Reviver;\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@Injectable()\nexport class JsonParser extends ServerKitParser {\n constructor(private readonly options: JsonParserOptions = {}) {\n super();\n }\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\nexport type TextParserOptions = raw.Options;\n\n@Injectable()\nexport class TextParser extends ServerKitParser {\n constructor(private readonly options: TextParserOptions = {}) {\n super();\n }\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\nexport type FormParserOptions = raw.Options & IParseOptions;\n\n@Injectable()\nexport class FormParser extends ServerKitParser {\n constructor(private readonly options: FormParserOptions = {}) {\n super();\n }\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@Injectable()\nexport class MultipartParser extends ServerKitParser {\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: new MultipartBody(req), raw: undefined };\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@Injectable()\nexport class BinaryParser extends ServerKitParser {\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: await raw(inflate(req)), raw: undefined };\n }\n}\n","import { Constructor, Registry } from 'injectkit';\nimport { ServerKitBodyParser, ServerKitParserMappings } from './serverkit.bodyparser.js';\nimport { ServerKitParser } from './parsers/serverkit.parser.js';\nimport { JsonParser } from './parsers/json.parser.js';\nimport { TextParser } from './parsers/text.parser.js';\nimport { FormParser } from './parsers/form.parser.js';\nimport { MultipartParser } from './parsers/multipart.parser.js';\nimport { BinaryParser } from './parsers/binary.parser.js';\n\nexport type ParserMappingOverrides = Record<string, Constructor<ServerKitParser>>;\n\nconst defaultParserMappings: ParserMappingOverrides = {\n 'json': JsonParser,\n 'application/*+json': JsonParser,\n 'urlencoded': FormParser,\n 'text': TextParser,\n 'multipart': MultipartParser,\n};\n\nconst defaultParserClasses: Constructor<ServerKitParser>[] = [\n JsonParser,\n FormParser,\n TextParser,\n MultipartParser,\n BinaryParser,\n];\n\nexport const setupParsers = (registry: Registry, overrides: ParserMappingOverrides = {}) => {\n const merged = { ...defaultParserMappings, ...overrides };\n\n const mapRegistration = registry\n .register(ServerKitParserMappings)\n .useMap(ServerKitParserMappings);\n\n for (const [key, parser] of Object.entries(merged)) {\n mapRegistration.set(key, parser);\n }\n\n const parserClasses = new Set<Constructor<ServerKitParser>>([\n ...defaultParserClasses,\n ...Object.values(overrides),\n ]);\n\n for (const parserClass of parserClasses) {\n registry.register(parserClass).useClass(parserClass).asSingleton();\n }\n\n registry.register(ServerKitBodyParser).useClass(ServerKitBodyParser).asSingleton();\n};\n"],"mappings":"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","names":["Router","ServerKitRouter","Router","cors","corsMiddleware","options","originMatcher","ctx","origin","get","matchers","matcher","test","cors","allowMethods","secureContext","keepHeadersOnError","privateNetworkAccess","IsHttpError","errorMiddleware","ctx","next","status","body","statusCode","message","details","url","URL","toString","app","emit","error","IsHttpError","headers","entry","Object","entries","set","httpError","rateLimiterMiddleware","rateLimiter","ctx","next","consume","ip","error","httpError","withCause","crypto","Logger","serverKitContextMiddleware","container","ctx","next","createScopedContainer","logger","get","Logger","loggerName","path","userAgent","correlationId","crypto","randomUUID","requestId","headers","set","AuthenticationSchemeHandler","invalidAuthenticationContext","authenticationMiddleware","ctx","next","authenticationContext","invalidAuthenticationContext","authorizationHeader","req","headers","authorization","schemeHandler","serviceLocator","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","Injectable","ServerKitParser","parse","raw","inflate","httpError","Injectable","strictJSONReg","JsonParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","length","undefined","encoding","limit","strict","protoAction","str","raw","inflate","doParse","reviver","err","httpError","withCause","parsed","test","withDetails","body","Injectable","raw","inflate","TextParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","length","undefined","encoding","limit","str","raw","inflate","parsed","httpError","Injectable","parse","raw","inflate","FormParser","ServerKitParser","options","parse","req","len","headers","contentEncoding","length","undefined","encoding","limit","str","raw","inflate","parsed","err","httpError","withCause","Injectable","MultipartBody","MultipartParser","ServerKitParser","parse","req","parsed","MultipartBody","raw","undefined","Injectable","raw","inflate","BinaryParser","ServerKitParser","parse","req","parsed","raw","inflate","undefined","defaultParserMappings","JsonParser","FormParser","TextParser","MultipartParser","defaultParserClasses","BinaryParser","setupParsers","registry","overrides","merged","mapRegistration","register","ServerKitParserMappings","useMap","key","parser","Object","entries","set","parserClasses","Set","values","parserClass","useClass","asSingleton","ServerKitBodyParser"]}
|
|
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/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 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 * @returns A new {@link Router} instance.\n */\nexport const ServerKitRouter = <StateT = DefaultState, ContextT = ServerKitContext>() => new Router<StateT, ContextT>();\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 } 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 {\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 } from 'rate-limiter-flexible';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { httpError } from '@maroonedsoftware/errors';\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) {\n throw httpError(429).withCause(error as Error);\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.correlationId = ctx.get('x-correlation-id') ?? crypto.randomUUID();\n ctx.requestId = ctx.get('x-request-id') ?? 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.serviceLocator.get(AuthenticationSchemeHandler);\n\n ctx.authenticationContext = await schemeHandler.handle(authorizationHeader);\n\n await next();\n };\n};\n","import { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { ServerKitMiddleware } 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 ServerKitMiddleware} 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[]): ServerKitMiddleware => {\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 { 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 as read from the stream (e.g. the original string or `Buffer`).\n * May be `undefined` for parsers that do not retain a raw representation (e.g. binary, multipart).\n */\nexport type ServerKitParserResult = {\n parsed: unknown;\n raw: unknown;\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: undefined }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: new MultipartBody(req), raw: undefined };\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: undefined }`.\n */\n async parse(req: IncomingMessage): Promise<ServerKitParserResult> {\n return { parsed: await raw(inflate(req)), raw: undefined };\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":["Router","ServerKitRouter","Router","cors","corsMiddleware","options","originMatcher","ctx","origin","get","matchers","matcher","test","cors","allowMethods","secureContext","keepHeadersOnError","privateNetworkAccess","IsHttpError","errorMiddleware","ctx","next","status","body","statusCode","message","details","url","URL","toString","app","emit","error","IsHttpError","headers","entry","Object","entries","set","httpError","rateLimiterMiddleware","rateLimiter","ctx","next","consume","ip","error","httpError","withCause","crypto","Logger","serverKitContextMiddleware","container","ctx","next","createScopedContainer","logger","get","Logger","loggerName","path","userAgent","correlationId","crypto","randomUUID","requestId","headers","set","AuthenticationSchemeHandler","invalidAuthenticationContext","authenticationMiddleware","ctx","next","authenticationContext","invalidAuthenticationContext","authorizationHeader","req","headers","authorization","schemeHandler","serviceLocator","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","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","undefined","Injectable","raw","inflate","BinaryParser","ServerKitParser","parse","req","parsed","raw","inflate","undefined","defaultParserMappings","json","JsonParser","urlencoded","FormParser","text","TextParser","multipart","MultipartParser"]}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';
|
|
2
2
|
import { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Reads the request body as a raw `Buffer` without any text decoding or structural parsing.
|
|
5
|
+
*
|
|
6
|
+
* Useful for binary payloads (e.g. PDFs, images, protobuf) where the caller needs the
|
|
7
|
+
* untransformed bytes. Decompresses the stream via `inflation` before buffering.
|
|
8
|
+
*
|
|
9
|
+
* `raw` is always `undefined` because the `Buffer` itself is both the parsed and raw form.
|
|
10
|
+
*/
|
|
3
11
|
export declare class BinaryParser extends ServerKitParser {
|
|
12
|
+
/**
|
|
13
|
+
* Buffers the (optionally compressed) request body into a `Buffer`.
|
|
14
|
+
*
|
|
15
|
+
* @param req - Incoming HTTP request to read.
|
|
16
|
+
* @returns `{ parsed: Buffer, raw: undefined }`.
|
|
17
|
+
*/
|
|
4
18
|
parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
5
19
|
}
|
|
6
20
|
//# sourceMappingURL=binary.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binary.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/binary.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAIvC,qBACa,YAAa,SAAQ,eAAe;
|
|
1
|
+
{"version":3,"file":"binary.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/binary.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAIvC;;;;;;;GAOG;AACH,qBACa,YAAa,SAAQ,eAAe;IAC/C;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAGlE"}
|
|
@@ -2,10 +2,55 @@ import { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';
|
|
|
2
2
|
import { IncomingMessage } from 'http';
|
|
3
3
|
import { IParseOptions } from 'qs';
|
|
4
4
|
import raw from 'raw-body';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for {@link FormParser}.
|
|
7
|
+
*
|
|
8
|
+
* Combines `raw-body` read options with `qs` parse options.
|
|
9
|
+
* All fields are optional; defaults are applied by the parser itself.
|
|
10
|
+
*
|
|
11
|
+
* @property encoding - Body text encoding (default: `'utf8'`).
|
|
12
|
+
* @property limit - Maximum body size (default: `'56kb'`).
|
|
13
|
+
* @property length - Expected byte length from `Content-Length` (auto-set by the parser).
|
|
14
|
+
* @property allowDots - When `true`, `qs` interprets dot notation (`user.name`) as nested objects.
|
|
15
|
+
* @property depth - Maximum nesting depth for parsed objects (default: `qs` default of `5`).
|
|
16
|
+
* @property parameterLimit - Maximum number of parameters to parse (default: `qs` default of `1000`).
|
|
17
|
+
*/
|
|
18
|
+
export declare class FormParserOptions implements raw.Options, IParseOptions {
|
|
19
|
+
encoding?: string;
|
|
20
|
+
limit?: string;
|
|
21
|
+
length?: number;
|
|
22
|
+
allowDots?: boolean;
|
|
23
|
+
depth?: number;
|
|
24
|
+
parameterLimit?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parses a URL-encoded (`application/x-www-form-urlencoded`) request body using `qs`.
|
|
28
|
+
*
|
|
29
|
+
* Supports nested objects via bracket notation (`user[name]=alice`) by default.
|
|
30
|
+
* Dot notation (`user.name=alice`) requires `allowDots: true` in the options.
|
|
31
|
+
* An empty body resolves to `{ parsed: {} }`.
|
|
32
|
+
*
|
|
33
|
+
* @throws HTTP 400 if `qs.parse` throws unexpectedly.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Default options (injectable)
|
|
38
|
+
* const parser = new FormParser(new FormParserOptions());
|
|
39
|
+
*
|
|
40
|
+
* // Enable dot notation
|
|
41
|
+
* const parser = new FormParser({ allowDots: true });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
6
44
|
export declare class FormParser extends ServerKitParser {
|
|
7
45
|
private readonly options;
|
|
8
|
-
constructor(options
|
|
46
|
+
constructor(options: FormParserOptions);
|
|
47
|
+
/**
|
|
48
|
+
* Reads, decompresses, and URL-decodes the request body.
|
|
49
|
+
*
|
|
50
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
51
|
+
* @returns `{ parsed: <object>, raw: <original url-encoded string> }`.
|
|
52
|
+
* @throws HTTP 400 if parsing fails.
|
|
53
|
+
*/
|
|
9
54
|
parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
10
55
|
}
|
|
11
56
|
//# sourceMappingURL=form.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/form.parser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAS,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1C,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"form.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/form.parser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAS,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1C,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B;;;;;;;;;;;;GAYG;AACH,qBACa,iBAAkB,YAAW,GAAG,CAAC,OAAO,EAAE,aAAa;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,UAAW,SAAQ,eAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAIvD;;;;;;OAMG;IACG,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAelE"}
|
|
@@ -2,14 +2,55 @@ import { Reviver } from '@hapi/bourne';
|
|
|
2
2
|
import { IncomingMessage } from 'http';
|
|
3
3
|
import raw from 'raw-body';
|
|
4
4
|
import { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for {@link JsonParser}.
|
|
7
|
+
*
|
|
8
|
+
* All fields are optional; defaults are applied by the parser itself.
|
|
9
|
+
*
|
|
10
|
+
* @property strict - When `true` (default), only JSON objects `{}` and arrays `[]` are accepted.
|
|
11
|
+
* When `false`, any valid JSON value (string, number, etc.) is allowed.
|
|
12
|
+
* @property protoAction - How `@hapi/bourne` handles `__proto__` keys. Defaults to `'error'`.
|
|
13
|
+
* @property reviver - Optional JSON reviver function passed to `@hapi/bourne`.
|
|
14
|
+
* @property encoding - Body text encoding (default: `'utf8'`).
|
|
15
|
+
* @property limit - Maximum body size (default: `'1mb'`).
|
|
16
|
+
* @property length - Expected byte length from `Content-Length` (auto-set by the parser).
|
|
17
|
+
*/
|
|
18
|
+
export declare class JsonParserOptions implements raw.Options {
|
|
6
19
|
strict?: boolean;
|
|
7
20
|
protoAction?: 'error' | 'remove' | 'ignore';
|
|
8
21
|
reviver?: Reviver;
|
|
9
|
-
|
|
22
|
+
encoding?: string;
|
|
23
|
+
limit?: string;
|
|
24
|
+
length?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parses a JSON request body using `@hapi/bourne` for prototype-pollution protection.
|
|
28
|
+
*
|
|
29
|
+
* In strict mode (default), only top-level objects `{}` and arrays `[]` are accepted;
|
|
30
|
+
* any other value throws HTTP 400. In non-strict mode, any valid JSON value is accepted.
|
|
31
|
+
* An empty body always resolves to `{ parsed: undefined }`.
|
|
32
|
+
*
|
|
33
|
+
* @throws HTTP 400 if the body is not valid JSON or fails the strict-mode check.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Default strict mode (injectable)
|
|
38
|
+
* const parser = new JsonParser(new JsonParserOptions());
|
|
39
|
+
*
|
|
40
|
+
* // Custom options
|
|
41
|
+
* const lenient = new JsonParser({ strict: false, protoAction: 'remove' });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
10
44
|
export declare class JsonParser extends ServerKitParser {
|
|
11
45
|
private readonly options;
|
|
12
|
-
constructor(options
|
|
46
|
+
constructor(options: JsonParserOptions);
|
|
47
|
+
/**
|
|
48
|
+
* Reads, decompresses, and JSON-parses the request body.
|
|
49
|
+
*
|
|
50
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
51
|
+
* @returns `{ parsed: <object|array|undefined>, raw: <original string> }`.
|
|
52
|
+
* @throws HTTP 400 on malformed JSON or strict-mode violation.
|
|
53
|
+
*/
|
|
13
54
|
parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
14
55
|
}
|
|
15
56
|
//# sourceMappingURL=json.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/json.parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG/E,
|
|
1
|
+
{"version":3,"file":"json.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/json.parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG/E;;;;;;;;;;;;GAYG;AACH,qBACa,iBAAkB,YAAW,GAAG,CAAC,OAAO;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,UAAW,SAAQ,eAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAIvD;;;;;;OAMG;IACG,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAgClE"}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';
|
|
2
2
|
import { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the request stream in a {@link MultipartBody} for lazy multipart/form-data parsing.
|
|
5
|
+
*
|
|
6
|
+
* The body is **not** eagerly consumed; fields and files are read on-demand through the
|
|
7
|
+
* `MultipartBody` API. `raw` is always `undefined` because the stream cannot be replayed
|
|
8
|
+
* once consumed.
|
|
9
|
+
*/
|
|
3
10
|
export declare class MultipartParser extends ServerKitParser {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a {@link MultipartBody} around the request stream.
|
|
13
|
+
*
|
|
14
|
+
* @param req - Incoming HTTP request containing the multipart body.
|
|
15
|
+
* @returns `{ parsed: MultipartBody, raw: undefined }`.
|
|
16
|
+
*/
|
|
4
17
|
parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
5
18
|
}
|
|
6
19
|
//# sourceMappingURL=multipart.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipart.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/multipart.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC,qBACa,eAAgB,SAAQ,eAAe;
|
|
1
|
+
{"version":3,"file":"multipart.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/multipart.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC;;;;;;GAMG;AACH,qBACa,eAAgB,SAAQ,eAAe;IAClD;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAGlE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Identifier } from 'injectkit';
|
|
2
|
+
import { ServerKitParser } from './serverkit.parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Built-in MIME-subtype-to-parser mappings used by {@link bodyParserMiddleware}.
|
|
5
|
+
*
|
|
6
|
+
* Each key is a MIME subtype (matched against `Content-Type` via Koa's `ctx.request.is()`)
|
|
7
|
+
* and the value is the InjectKit {@link Identifier} for the corresponding {@link ServerKitParser}.
|
|
8
|
+
*
|
|
9
|
+
* | MIME subtype | Parser |
|
|
10
|
+
* | --------------------- | ----------------- |
|
|
11
|
+
* | `json` | {@link JsonParser} |
|
|
12
|
+
* | `application/*+json` | {@link JsonParser} |
|
|
13
|
+
* | `urlencoded` | {@link FormParser} |
|
|
14
|
+
* | `text` | {@link TextParser} |
|
|
15
|
+
* | `multipart` | {@link MultipartParser} |
|
|
16
|
+
*
|
|
17
|
+
* Extend by spreading into a new object and registering the result in the DI container:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const myMappings = { ...defaultParserMappings, pdf: BinaryParser };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const defaultParserMappings: Record<string, Identifier<ServerKitParser>>;
|
|
23
|
+
//# sourceMappingURL=serverkit.default.parsers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverkit.default.parsers.d.ts","sourceRoot":"","sources":["../../src/parsers/serverkit.default.parsers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,eAAe,CAAC,CAMpE,CAAC"}
|
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import { IncomingMessage } from 'http';
|
|
2
|
+
/**
|
|
3
|
+
* The result returned by every {@link ServerKitParser}.
|
|
4
|
+
*
|
|
5
|
+
* @property parsed - The structured/deserialized value derived from the body (e.g. a plain object for JSON).
|
|
6
|
+
* @property raw - The unprocessed body as read from the stream (e.g. the original string or `Buffer`).
|
|
7
|
+
* May be `undefined` for parsers that do not retain a raw representation (e.g. binary, multipart).
|
|
8
|
+
*/
|
|
2
9
|
export type ServerKitParserResult = {
|
|
3
10
|
parsed: unknown;
|
|
4
11
|
raw: unknown;
|
|
5
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Abstract base class for all ServerKit body parsers.
|
|
15
|
+
*
|
|
16
|
+
* Implementations read from an {@link IncomingMessage} stream and return a
|
|
17
|
+
* {@link ServerKitParserResult} containing both the parsed value and the raw body.
|
|
18
|
+
* Each parser is registered in the DI container and selected by MIME type via
|
|
19
|
+
* {@link ServerKitBodyParser}.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link ServerKitBodyParser} – dispatcher that selects the right parser by MIME type
|
|
22
|
+
* @see {@link defaultParserMappings} – built-in MIME-type-to-parser map
|
|
23
|
+
*/
|
|
6
24
|
export declare abstract class ServerKitParser {
|
|
25
|
+
/**
|
|
26
|
+
* Reads and parses the body from the given request stream.
|
|
27
|
+
*
|
|
28
|
+
* @param req - The incoming HTTP request whose body should be consumed.
|
|
29
|
+
* @returns A promise resolving to a {@link ServerKitParserResult}.
|
|
30
|
+
*/
|
|
7
31
|
abstract parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
8
32
|
}
|
|
9
33
|
//# sourceMappingURL=serverkit.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverkit.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/serverkit.parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,8BACsB,eAAe;IACnC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CACrE"}
|
|
1
|
+
{"version":3,"file":"serverkit.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/serverkit.parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAGvC;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,8BACsB,eAAe;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CACrE"}
|
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
import { ServerKitParser, ServerKitParserResult } from './serverkit.parser.js';
|
|
2
2
|
import { IncomingMessage } from 'http';
|
|
3
3
|
import raw from 'raw-body';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for {@link TextParser}.
|
|
6
|
+
*
|
|
7
|
+
* All fields are optional; defaults are applied by the parser itself.
|
|
8
|
+
*
|
|
9
|
+
* @property encoding - Body text encoding (default: `'utf8'`).
|
|
10
|
+
* @property limit - Maximum body size (default: `'1mb'`).
|
|
11
|
+
* @property length - Expected byte length from `Content-Length` (auto-set by the parser).
|
|
12
|
+
*/
|
|
13
|
+
export declare class TextParserOptions implements raw.Options {
|
|
14
|
+
encoding?: string;
|
|
15
|
+
limit?: string;
|
|
16
|
+
length?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reads the request body as a plain string.
|
|
20
|
+
*
|
|
21
|
+
* No structural parsing is performed; the raw text is returned as both `parsed` and `raw`.
|
|
22
|
+
* Suitable for `text/plain` and similar content types. Decompresses the stream via `inflation`
|
|
23
|
+
* before buffering.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const parser = new TextParser(new TextParserOptions());
|
|
28
|
+
* const { parsed } = await parser.parse(req); // parsed is a string
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
5
31
|
export declare class TextParser extends ServerKitParser {
|
|
6
32
|
private readonly options;
|
|
7
|
-
constructor(options
|
|
33
|
+
constructor(options: TextParserOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Reads and decompresses the request body into a string.
|
|
36
|
+
*
|
|
37
|
+
* @param req - Incoming HTTP request whose body will be consumed.
|
|
38
|
+
* @returns `{ parsed: <string>, raw: <same string> }`.
|
|
39
|
+
*/
|
|
8
40
|
parse(req: IncomingMessage): Promise<ServerKitParserResult>;
|
|
9
41
|
}
|
|
10
42
|
//# sourceMappingURL=text.parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/text.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B,
|
|
1
|
+
{"version":3,"file":"text.parser.d.ts","sourceRoot":"","sources":["../../src/parsers/text.parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,GAAG,MAAM,UAAU,CAAC;AAG3B;;;;;;;;GAQG;AACH,qBAAa,iBAAkB,YAAW,GAAG,CAAC,OAAO;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,qBACa,UAAW,SAAQ,eAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAIvD;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAWlE"}
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import { ServerKitParser, ServerKitParserResult } from './parsers/serverkit.parser.js';
|
|
2
2
|
import { ServerKitContext } from './serverkit.context.js';
|
|
3
|
+
/**
|
|
4
|
+
* DI-injectable map of MIME subtypes to {@link ServerKitParser} instances.
|
|
5
|
+
*
|
|
6
|
+
* Register parser instances against their MIME subtypes, then bind this map
|
|
7
|
+
* in the InjectKit container so {@link ServerKitBodyParser} can resolve them.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* registry
|
|
12
|
+
* .register(ServerKitParserMappings)
|
|
13
|
+
* .useMap()
|
|
14
|
+
* .add('json', JsonParser)
|
|
15
|
+
* .add('urlencoded', FormParser);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
3
18
|
export declare class ServerKitParserMappings extends Map<string, ServerKitParser> {
|
|
4
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Selects and invokes the appropriate {@link ServerKitParser} for the incoming request
|
|
22
|
+
* based on its `Content-Type` header.
|
|
23
|
+
*
|
|
24
|
+
* The set of supported MIME types is derived from the keys of the injected
|
|
25
|
+
* {@link ServerKitParserMappings}. Duplicate keys are deduplicated automatically.
|
|
26
|
+
*
|
|
27
|
+
* @throws HTTP 415 if the request's `Content-Type` does not match any registered parser.
|
|
28
|
+
*
|
|
29
|
+
* @see {@link defaultParserMappings} – convenience map for the standard parsers
|
|
30
|
+
* @see {@link bodyParserMiddleware} – the middleware that invokes this class
|
|
31
|
+
*/
|
|
5
32
|
export declare class ServerKitBodyParser {
|
|
6
33
|
private readonly parsers;
|
|
7
34
|
private readonly mimeTypes;
|
|
8
35
|
constructor(parsers: ServerKitParserMappings);
|
|
36
|
+
/**
|
|
37
|
+
* Matches the request's `Content-Type` to a registered parser and delegates parsing.
|
|
38
|
+
*
|
|
39
|
+
* @param ctx - The current {@link ServerKitContext}; used to inspect `Content-Type` and access `ctx.req`.
|
|
40
|
+
* @returns The {@link ServerKitParserResult} from the matched parser.
|
|
41
|
+
* @throws HTTP 415 if no parser is registered for the request's content type.
|
|
42
|
+
*/
|
|
9
43
|
parse(ctx: ServerKitContext): Promise<ServerKitParserResult>;
|
|
10
44
|
}
|
|
11
45
|
//# sourceMappingURL=serverkit.bodyparser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverkit.bodyparser.d.ts","sourceRoot":"","sources":["../src/serverkit.bodyparser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI1D,qBACa,uBAAwB,SAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;CAAG;AAE5E,qBACa,mBAAmB;IAElB,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;gBACR,OAAO,EAAE,uBAAuB;
|
|
1
|
+
{"version":3,"file":"serverkit.bodyparser.d.ts","sourceRoot":"","sources":["../src/serverkit.bodyparser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI1D;;;;;;;;;;;;;;GAcG;AACH,qBACa,uBAAwB,SAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;CAAG;AAE5E;;;;;;;;;;;GAWG;AACH,qBACa,mBAAmB;IAElB,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;gBACR,OAAO,EAAE,uBAAuB;IAI7D;;;;;;OAMG;IACG,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAWnE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedsoftware/koa",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Koa middleware, body parsing, and utilities for ServerKit",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"raw-body": "^3.0.2",
|
|
40
40
|
"@maroonedsoftware/errors": "1.2.0",
|
|
41
41
|
"@maroonedsoftware/multipart": "1.0.2",
|
|
42
|
+
"@maroonedsoftware/logger": "1.0.0",
|
|
42
43
|
"@maroonedsoftware/utilities": "1.1.0",
|
|
43
|
-
"@maroonedsoftware/authentication": "0.0.1"
|
|
44
|
-
"@maroonedsoftware/logger": "1.0.0"
|
|
44
|
+
"@maroonedsoftware/authentication": "0.0.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@koa/cors": "^5.0.0",
|
package/dist/parsers.setup.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { Constructor, Registry } from 'injectkit';
|
|
2
|
-
import { ServerKitParser } from './parsers/serverkit.parser.js';
|
|
3
|
-
export type ParserMappingOverrides = Record<string, Constructor<ServerKitParser>>;
|
|
4
|
-
export declare const setupParsers: (registry: Registry, overrides?: ParserMappingOverrides) => void;
|
|
5
|
-
//# sourceMappingURL=parsers.setup.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parsers.setup.d.ts","sourceRoot":"","sources":["../src/parsers.setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOhE,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC;AAkBlF,eAAO,MAAM,YAAY,GAAI,UAAU,QAAQ,EAAE,YAAW,sBAA2B,SAqBtF,CAAC"}
|