@maroonedsoftware/koa 1.0.0 → 1.1.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 CHANGED
@@ -1,91 +1,138 @@
1
- # @maroonedsoftware/logger
1
+ # @maroonedsoftware/koa
2
2
 
3
- A simple, dependency injection friendly logging abstraction with multiple log levels.
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).
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pnpm add @maroonedsoftware/logger
8
+ pnpm add @maroonedsoftware/koa koa @koa/router @koa/cors
9
9
  ```
10
10
 
11
- ## Usage
12
-
13
- ### Basic Usage
14
-
15
- ```typescript
16
- import { ConsoleLogger } from '@maroonedsoftware/logger';
11
+ Peer dependencies: `koa`, `@koa/router`, `@koa/cors`.
17
12
 
18
- const logger = new ConsoleLogger();
13
+ ## Features
19
14
 
20
- logger.error('Something went wrong', { details: 'error context' });
21
- logger.warn('This might be a problem');
22
- logger.info('Application started');
23
- logger.debug('Processing request', requestData);
24
- logger.trace('Entering function');
25
- ```
15
+ - **ServerKitContext** — Koa context extended with `container`, `logger`, `requestId`, `correlationId`, and related request metadata
16
+ - **ServerKitRouter** Router typed for `ServerKitContext`
17
+ - **ServerKitMiddleware** — Middleware type bound to `ServerKitContext`
18
+ - **serverKitContextMiddleware** — Populates context with scoped container, logger, and request/correlation IDs
19
+ - **corsMiddleware** — CORS headers with `'*'`, string, or RegExp origin matching
20
+ - **errorMiddleware** — Central error handler; maps HTTP errors to status/body, 404 for unmatched routes, 500 for unknown errors
21
+ - **rateLimiterMiddleware** — Per-IP rate limiting via `rate-limiter-flexible` (429 when exceeded)
22
+ - **bodyParserMiddleware** — Parses JSON, form, text, multipart, or raw body by allowed content types
26
23
 
27
- ### With Dependency Injection
24
+ ## Usage
28
25
 
29
- The `Logger` abstract class is decorated with `@Injectable()` from [injectkit](https://www.npmjs.com/package/injectkit), making it easy to use with DI containers:
26
+ ### Basic setup
30
27
 
31
28
  ```typescript
32
- import 'reflect-metadata';
29
+ import Koa from 'koa';
33
30
  import { InjectKitRegistry } from 'injectkit';
34
31
  import { Logger, ConsoleLogger } from '@maroonedsoftware/logger';
32
+ import { ServerKitRouter, serverKitContextMiddleware, corsMiddleware, errorMiddleware, bodyParserMiddleware } from '@maroonedsoftware/koa';
35
33
 
36
- // Set up dependency injection registry
37
34
  const diRegistry = new InjectKitRegistry();
38
35
  diRegistry.register(Logger).useClass(ConsoleLogger).asSingleton();
39
-
40
- // Build the container
41
36
  const container = diRegistry.build();
42
37
 
43
- // Resolve the logger
44
- const logger = container.get(Logger);
45
- logger.info('Application started');
46
- ```
38
+ const app = new Koa();
39
+ const router = new ServerKitRouter();
47
40
 
48
- In your services, use constructor injection:
41
+ app.use(errorMiddleware());
42
+ app.use(serverKitContextMiddleware(container));
43
+ app.use(corsMiddleware({ origin: ['*'] }));
49
44
 
50
- ```typescript
51
- import { Injectable } from 'injectkit';
52
- import { Logger } from '@maroonedsoftware/logger';
45
+ router.post('/api/echo', bodyParserMiddleware(['application/json']), async ctx => {
46
+ ctx.body = { echoed: ctx.body, requestId: ctx.requestId };
47
+ });
53
48
 
54
- @Injectable()
55
- class MyService {
56
- constructor(private logger: Logger) {}
49
+ app.use(router.routes()).use(router.allowedMethods());
57
50
 
58
- doSomething() {
59
- this.logger.info('Doing something');
60
- }
61
- }
51
+ app.listen(3000);
62
52
  ```
63
53
 
64
- ## API
54
+ ### Route handlers with ServerKitContext
55
+
56
+ Handlers receive `ctx` as `ServerKitContext` with `ctx.container`, `ctx.logger`, `ctx.requestId`, `ctx.correlationId`, and `ctx.userAgent`:
65
57
 
66
- ### Logger Interface
58
+ ```typescript
59
+ router.get('/api/users/:id', async ctx => {
60
+ ctx.logger.info('Fetching user', { id: ctx.params.id });
61
+ const user = await ctx.container.get(UserService).findById(ctx.params.id);
62
+ if (!user) throw httpError(404);
63
+ ctx.body = user;
64
+ });
65
+ ```
67
66
 
68
- | Method | Description |
69
- | --------------------------- | ----------------------------- |
70
- | `error(message, ...params)` | Logs an error message |
71
- | `warn(message, ...params)` | Logs a warning message |
72
- | `info(message, ...params)` | Logs an informational message |
73
- | `debug(message, ...params)` | Logs a debug message |
74
- | `trace(message, ...params)` | Logs a trace message |
67
+ ### CORS
75
68
 
76
- ### ConsoleLogger
69
+ ```typescript
70
+ // Allow all origins
71
+ app.use(corsMiddleware({ origin: ['*'] }));
72
+
73
+ // Single origin
74
+ app.use(corsMiddleware({ origin: ['https://app.example.com'] }));
75
+
76
+ // Multiple origins or RegExps
77
+ app.use(
78
+ corsMiddleware({
79
+ origin: ['https://app.example.com', /^https:\/\/.*\.example\.com$/],
80
+ }),
81
+ );
82
+ ```
77
83
 
78
- A concrete implementation that outputs to the standard console. Accepts an optional `Console` instance in the constructor for custom console implementations.
84
+ ### Rate limiting
79
85
 
80
86
  ```typescript
81
- // Use global console (default)
82
- const logger = new ConsoleLogger();
87
+ import { RateLimiterMemory } from 'rate-limiter-flexible';
88
+ import { rateLimiterMiddleware } from '@maroonedsoftware/koa';
89
+
90
+ const rateLimiter = new RateLimiterMemory({
91
+ points: 100,
92
+ duration: 60,
93
+ });
94
+ app.use(rateLimiterMiddleware(rateLimiter));
95
+ ```
96
+
97
+ ### Body parser
83
98
 
84
- // Use custom console
85
- const customConsole = { ... };
86
- const logger = new ConsoleLogger(customConsole);
99
+ Allow specific content types; empty array disallows any body. Supports JSON, urlencoded, text, multipart, and raw (e.g. PDF).
100
+
101
+ ```typescript
102
+ router.post('/api/upload', bodyParserMiddleware(['multipart/form-data']), async ctx => {
103
+ const body = ctx.body as MultipartBody;
104
+ // ...
105
+ });
106
+
107
+ router.post('/api/json', bodyParserMiddleware(['application/json']), async ctx => {
108
+ const data = ctx.body as Record<string, unknown>;
109
+ // ...
110
+ });
87
111
  ```
88
112
 
113
+ ## API
114
+
115
+ ### ServerKitContext
116
+
117
+ | Property | Type | Description |
118
+ | --------------- | ----------- | ------------------------------------ |
119
+ | `container` | `Container` | Request-scoped injectkit container |
120
+ | `logger` | `Logger` | Request-scoped logger |
121
+ | `loggerName` | `string` | Logger name (e.g. request path) |
122
+ | `userAgent` | `string` | `User-Agent` header value |
123
+ | `correlationId` | `string` | From `X-Correlation-Id` or generated |
124
+ | `requestId` | `string` | From `X-Request-Id` or generated |
125
+
126
+ ### Middleware
127
+
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
+ | `bodyParserMiddleware(contentTypes)` | Parses body by allowed MIME types; throws 400/411/415/422 on invalid input |
135
+
89
136
  ## License
90
137
 
91
138
  MIT
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from './serverkit.context.js';
2
2
  export * from './serverkit.middleware.js';
3
3
  export * from './serverkit.router.js';
4
+ export * from './middleware/server/cors.middleware.js';
4
5
  export * from './middleware/server/error.middleware.js';
5
- export * from './middleware/server/injectkit.middleware.js';
6
+ export * from './middleware/server/rate.limiter.middleware.js';
7
+ export * from './middleware/server/serverkit.context.middleware.js';
6
8
  export * from './middleware/router/body.parser.middleware.js';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -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,yCAAyC,CAAC;AACxD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+CAA+C,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,+CAA+C,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,40 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
5
5
  import Router from "@koa/router";
6
6
  var ServerKitRouter = /* @__PURE__ */ __name(() => new Router(), "ServerKitRouter");
7
7
 
8
+ // src/middleware/server/cors.middleware.ts
9
+ import cors from "@koa/cors";
10
+ var corsMiddleware = /* @__PURE__ */ __name((options) => {
11
+ const originMatcher = /* @__PURE__ */ __name((ctx) => {
12
+ const origin = ctx.get("origin");
13
+ const matchers = options?.origin ?? [
14
+ "*"
15
+ ];
16
+ for (const matcher of matchers) {
17
+ if (matcher === "*") {
18
+ return origin;
19
+ }
20
+ if (typeof matcher === "string") {
21
+ if (matcher === origin) {
22
+ return origin;
23
+ }
24
+ continue;
25
+ }
26
+ if (matcher.test(origin)) {
27
+ return origin;
28
+ }
29
+ }
30
+ return "";
31
+ }, "originMatcher");
32
+ return cors({
33
+ ...options,
34
+ origin: originMatcher,
35
+ allowMethods: options?.allowMethods ?? "GET,HEAD,PUT,POST,DELETE,PATCH",
36
+ secureContext: options?.secureContext ?? false,
37
+ keepHeadersOnError: options?.keepHeadersOnError ?? false,
38
+ privateNetworkAccess: options?.privateNetworkAccess ?? false
39
+ });
40
+ }, "corsMiddleware");
41
+
8
42
  // src/middleware/server/error.middleware.ts
9
43
  import { IsHttpError } from "@maroonedsoftware/errors";
10
44
  var errorMiddleware = /* @__PURE__ */ __name(() => {
@@ -48,33 +82,55 @@ var errorMiddleware = /* @__PURE__ */ __name(() => {
48
82
  };
49
83
  }, "errorMiddleware");
50
84
 
51
- // src/middleware/server/injectkit.middleware.ts
85
+ // src/middleware/server/rate.limiter.middleware.ts
86
+ import { httpError } from "@maroonedsoftware/errors";
87
+ var rateLimiterMiddleware = /* @__PURE__ */ __name((rateLimiter) => {
88
+ return async (ctx, next) => {
89
+ try {
90
+ await rateLimiter.consume(ctx.ip);
91
+ } catch (error) {
92
+ throw httpError(429).withCause(error);
93
+ }
94
+ await next();
95
+ };
96
+ }, "rateLimiterMiddleware");
97
+
98
+ // src/middleware/server/serverkit.context.middleware.ts
99
+ import crypto from "crypto";
52
100
  import { Logger } from "@maroonedsoftware/logger";
53
- var injectkitMiddleware = /* @__PURE__ */ __name((container) => {
101
+ var serverKitContextMiddleware = /* @__PURE__ */ __name((container) => {
54
102
  return async (ctx, next) => {
55
103
  ctx.container = container.createScopedContainer();
56
104
  ctx.logger = container.get(Logger);
105
+ ctx.loggerName = ctx.path;
106
+ ctx.userAgent = ctx.get("user-agent") ?? "";
107
+ ctx.correlationId = ctx.get("x-correlation-id") ?? crypto.randomUUID();
108
+ ctx.requestId = ctx.get("x-request-id") ?? crypto.randomUUID();
109
+ ctx.headers["x-correlation-id"] = ctx.correlationId;
110
+ ctx.set("x-correlation-id", ctx.correlationId);
111
+ ctx.headers["x-request-id"] = ctx.requestId;
112
+ ctx.set("x-request-id", ctx.requestId);
57
113
  await next();
58
114
  };
59
- }, "injectkitMiddleware");
115
+ }, "serverKitContextMiddleware");
60
116
 
61
117
  // src/middleware/router/body.parser.middleware.ts
62
118
  import coBody from "co-body";
63
- import { httpError, IsHttpError as IsHttpError2 } from "@maroonedsoftware/errors";
119
+ import { httpError as httpError2, IsHttpError as IsHttpError2 } from "@maroonedsoftware/errors";
64
120
  import { MultipartBody } from "@maroonedsoftware/multipart";
65
121
  import rawBody from "raw-body";
66
122
  var bodyParserMiddleware = /* @__PURE__ */ __name((contentTypes) => {
67
123
  return async (ctx, next) => {
68
124
  if (contentTypes.length === 0) {
69
125
  if (ctx.request.length > 0) {
70
- throw httpError(400).withErrors({
126
+ throw httpError2(400).withDetails({
71
127
  body: "Unexpected body"
72
128
  });
73
129
  }
74
130
  } else {
75
131
  if (ctx.request.length > 0) {
76
132
  if (!ctx.request.is(contentTypes)) {
77
- throw httpError(415).withErrors({
133
+ throw httpError2(415).withDetails({
78
134
  "content-type": `must be ${contentTypes.length > 1 ? "one of " : ""}${contentTypes.join(", ")}`,
79
135
  value: ctx.request.type
80
136
  });
@@ -91,7 +147,7 @@ var bodyParserMiddleware = /* @__PURE__ */ __name((contentTypes) => {
91
147
  } else if (ctx.request.is("pdf")) {
92
148
  ctx.body = await rawBody(ctx.req);
93
149
  } else {
94
- throw httpError(422).withErrors({
150
+ throw httpError2(422).withDetails({
95
151
  body: "Unsupported media type"
96
152
  });
97
153
  }
@@ -99,12 +155,12 @@ var bodyParserMiddleware = /* @__PURE__ */ __name((contentTypes) => {
99
155
  if (IsHttpError2(error)) {
100
156
  throw error;
101
157
  }
102
- throw httpError(422).withCause(error).withErrors({
158
+ throw httpError2(422).withCause(error).withDetails({
103
159
  body: "Invalid request body format"
104
160
  });
105
161
  }
106
162
  } else {
107
- throw httpError(411);
163
+ throw httpError2(411);
108
164
  }
109
165
  }
110
166
  await next();
@@ -113,7 +169,9 @@ var bodyParserMiddleware = /* @__PURE__ */ __name((contentTypes) => {
113
169
  export {
114
170
  ServerKitRouter,
115
171
  bodyParserMiddleware,
172
+ corsMiddleware,
116
173
  errorMiddleware,
117
- injectkitMiddleware
174
+ rateLimiterMiddleware,
175
+ serverKitContextMiddleware
118
176
  };
119
177
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/serverkit.router.ts","../src/middleware/server/error.middleware.ts","../src/middleware/server/injectkit.middleware.ts","../src/middleware/router/body.parser.middleware.ts"],"sourcesContent":["import { DefaultState } from 'koa';\nimport Router from '@koa/router';\nimport { ServerKitContext } from './serverkit.context.js';\n\nexport const ServerKitRouter = <StateT = DefaultState, ContextT = ServerKitContext>() => new Router<StateT, ContextT>();\n","import { ServerKitMiddleware } from '../../serverkit.middleware.js';\nimport { IsHttpError } from '@maroonedsoftware/errors';\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 { Container } from 'injectkit';\nimport { Logger } from '@maroonedsoftware/logger';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\n\nexport const injectkitMiddleware = (container: Container): ServerKitMiddleware => {\n return async (ctx, next) => {\n ctx.container = container.createScopedContainer();\n ctx.logger = container.get(Logger);\n await next();\n };\n};\n","import coBody from 'co-body';\nimport { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { MultipartBody } from '@maroonedsoftware/multipart';\nimport rawBody from 'raw-body';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.js';\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).withErrors({ body: 'Unexpected body' });\n }\n } else {\n if (ctx.request.length > 0) {\n if (!ctx.request.is(contentTypes)) {\n throw httpError(415).withErrors({\n 'content-type': `must be ${contentTypes.length > 1 ? 'one of ' : ''}${contentTypes.join(', ')}`,\n value: ctx.request.type,\n });\n }\n\n try {\n if (ctx.request.is('json', 'application/*+json')) {\n ctx.body = await coBody.json(ctx);\n } else if (ctx.request.is('urlencoded')) {\n ctx.body = await coBody.form(ctx);\n } else if (ctx.request.is('text/*')) {\n ctx.body = await coBody.text(ctx);\n } else if (ctx.request.is('multipart')) {\n ctx.body = new MultipartBody(ctx.req);\n } else if (ctx.request.is('pdf')) {\n ctx.body = await rawBody(ctx.req);\n } else {\n throw httpError(422).withErrors({ body: 'Unsupported media type' });\n }\n } catch (error) {\n if (IsHttpError(error)) {\n throw error;\n }\n throw httpError(422)\n .withCause(error as Error)\n .withErrors({ body: 'Invalid request body format' });\n }\n } else {\n throw httpError(411);\n }\n }\n await next();\n };\n};\n"],"mappings":"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","names":["Router","ServerKitRouter","Router","IsHttpError","errorMiddleware","ctx","next","status","body","statusCode","message","details","url","URL","toString","app","emit","error","IsHttpError","headers","entry","Object","entries","set","Logger","injectkitMiddleware","container","ctx","next","createScopedContainer","logger","get","Logger","coBody","httpError","IsHttpError","MultipartBody","rawBody","bodyParserMiddleware","contentTypes","ctx","next","length","request","httpError","withErrors","body","is","join","value","type","coBody","json","form","text","MultipartBody","req","rawBody","error","IsHttpError","withCause"]}
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/router/body.parser.middleware.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 coBody from 'co-body';\nimport { httpError, IsHttpError } from '@maroonedsoftware/errors';\nimport { MultipartBody } from '@maroonedsoftware/multipart';\nimport rawBody from 'raw-body';\nimport { ServerKitMiddleware } from '../../serverkit.middleware.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 if (ctx.request.is('json', 'application/*+json')) {\n ctx.body = await coBody.json(ctx);\n } else if (ctx.request.is('urlencoded')) {\n ctx.body = await coBody.form(ctx);\n } else if (ctx.request.is('text/*')) {\n ctx.body = await coBody.text(ctx);\n } else if (ctx.request.is('multipart')) {\n ctx.body = new MultipartBody(ctx.req);\n } else if (ctx.request.is('pdf')) {\n ctx.body = await rawBody(ctx.req);\n } else {\n throw httpError(422).withDetails({ body: 'Unsupported media type' });\n }\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"],"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","coBody","httpError","IsHttpError","MultipartBody","rawBody","bodyParserMiddleware","contentTypes","ctx","next","length","request","httpError","withDetails","body","is","join","value","type","coBody","json","form","text","MultipartBody","req","rawBody","error","IsHttpError","withCause"]}
@@ -1,3 +1,18 @@
1
1
  import { ServerKitMiddleware } from '../../serverkit.middleware.js';
2
+ /**
3
+ * Parses the request body based on `Content-Type` and assigns it to `ctx.body`.
4
+ * Rejects requests with unexpected or unsupported content types.
5
+ *
6
+ * Supported types: JSON, URL-encoded form, text, multipart, PDF (raw buffer).
7
+ * Requires a body when `contentTypes` is non-empty; otherwise rejects bodies.
8
+ *
9
+ * @param contentTypes - Allowed MIME types (e.g. `['application/json', 'application/x-www-form-urlencoded']`).
10
+ * Use an empty array to disallow any request body.
11
+ * @returns {@link ServerKitMiddleware} that parses the body and sets `ctx.body`.
12
+ * @throws HTTP 400 if body is present when no content types are allowed.
13
+ * @throws HTTP 411 if body is required but missing.
14
+ * @throws HTTP 415 if `Content-Type` is not in `contentTypes`.
15
+ * @throws HTTP 422 if body is invalid or media type is unsupported.
16
+ */
2
17
  export declare const bodyParserMiddleware: (contentTypes: string[]) => ServerKitMiddleware;
3
18
  //# sourceMappingURL=body.parser.middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"body.parser.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/router/body.parser.middleware.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,eAAO,MAAM,oBAAoB,GAAI,cAAc,MAAM,EAAE,KAAG,mBA2C7D,CAAC"}
1
+ {"version":3,"file":"body.parser.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/router/body.parser.middleware.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAAI,cAAc,MAAM,EAAE,KAAG,mBA2C7D,CAAC"}
@@ -0,0 +1,19 @@
1
+ import cors from '@koa/cors';
2
+ import { ServerKitMiddleware } from '../../serverkit.middleware.js';
3
+ /**
4
+ * CORS options for {@link corsMiddleware}.
5
+ * Extends `@koa/cors` options with an `origin` that may be a string or array of strings/RegExps.
6
+ */
7
+ export interface CorsOptions extends Omit<cors.Options, 'origin'> {
8
+ /** Allowed origin(s): `'*'`, a single origin string, or an array of strings/RegExps to match. */
9
+ origin?: string | (string | RegExp)[];
10
+ }
11
+ /**
12
+ * Adds CORS headers to responses using `@koa/cors` with ServerKit-compatible origin matching.
13
+ * Supports `'*'`, exact string origins, and RegExp patterns.
14
+ *
15
+ * @param options - Optional {@link CorsOptions}; defaults to `GET,HEAD,PUT,POST,DELETE,PATCH` methods.
16
+ * @returns {@link ServerKitMiddleware} that applies CORS headers.
17
+ */
18
+ export declare const corsMiddleware: (options?: CorsOptions) => ServerKitMiddleware;
19
+ //# sourceMappingURL=cors.middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/cors.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC/D,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CACvC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,WAAW,KAAG,mBAkCtD,CAAC"}
@@ -1,3 +1,10 @@
1
1
  import { ServerKitMiddleware } from '../../serverkit.middleware.js';
2
+ /**
3
+ * Central error handler: catches thrown errors, sets status/body from HTTP errors,
4
+ * returns 404 for unmatched routes, and 500 for unknown errors.
5
+ * Emits `error` or `warn` on the app for logging.
6
+ *
7
+ * @returns {@link ServerKitMiddleware} that wraps the stack in try/catch and normalizes responses.
8
+ */
2
9
  export declare const errorMiddleware: () => ServerKitMiddleware;
3
10
  //# sourceMappingURL=error.middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/error.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE,eAAO,MAAM,eAAe,QAAO,mBAsClC,CAAC"}
1
+ {"version":3,"file":"error.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/error.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAO,mBAsClC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { RateLimiterAbstract } from 'rate-limiter-flexible';
2
+ import { ServerKitMiddleware } from '../../serverkit.middleware.js';
3
+ /**
4
+ * Enforces rate limiting per client IP using a `rate-limiter-flexible` instance.
5
+ * Consumes one token per request; throws HTTP 429 when the limit is exceeded.
6
+ *
7
+ * @param rateLimiter - A {@link RateLimiterAbstract} instance (e.g. `RateLimiterMemory`, `RateLimiterRedis`).
8
+ * @returns {@link ServerKitMiddleware} that consumes a token and continues or throws 429.
9
+ */
10
+ export declare const rateLimiterMiddleware: (rateLimiter: RateLimiterAbstract) => ServerKitMiddleware;
11
+ //# sourceMappingURL=rate.limiter.middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate.limiter.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/rate.limiter.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAGpE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,mBAAmB,KAAG,mBAUxE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Container } from 'injectkit';
2
+ import { ServerKitMiddleware } from '../../serverkit.middleware.js';
3
+ /**
4
+ * Populates {@link ServerKitContext} for each request: scoped container, logger,
5
+ * logger name, user-agent, correlation ID, and request ID.
6
+ * Reads or generates `X-Correlation-Id` and `X-Request-Id` and sets response headers.
7
+ * Should be applied early so downstream middleware and routes can use `ctx.container` and `ctx.logger`.
8
+ *
9
+ * @param container - Root injectkit {@link Container} used to create a scoped container and resolve {@link Logger}.
10
+ * @returns {@link ServerKitMiddleware} that attaches ServerKit context to `ctx`.
11
+ */
12
+ export declare const serverKitContextMiddleware: (container: Container) => ServerKitMiddleware;
13
+ //# sourceMappingURL=serverkit.context.middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverkit.context.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/serverkit.context.middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,WAAW,SAAS,KAAG,mBAkBjE,CAAC"}
@@ -1,8 +1,26 @@
1
1
  import { Context } from 'koa';
2
2
  import { Container } from 'injectkit';
3
3
  import { Logger } from '@maroonedsoftware/logger';
4
+ /**
5
+ * Koa context extended with ServerKit request-scoped services and metadata.
6
+ * Populated by {@link serverKitContextMiddleware}.
7
+ * Use this as the context type for route handlers to get full typing of `ctx.container`, `ctx.logger`, and request IDs.
8
+ *
9
+ * @extends Context
10
+ * @see {@link serverKitContextMiddleware} – middleware that populates this context on each request
11
+ */
4
12
  export interface ServerKitContext extends Context {
13
+ /** Scoped injectkit container for this request; use for request-scoped DI. */
5
14
  container: Container;
15
+ /** Request-scoped logger instance. */
6
16
  logger: Logger;
17
+ /** Logger name for this request (e.g. request path or route identifier). */
18
+ loggerName: string;
19
+ /** Value of the `User-Agent` request header, or empty string if absent. */
20
+ userAgent: string;
21
+ /** Correlation ID for tracing; from `X-Correlation-Id` header or generated. */
22
+ correlationId: string;
23
+ /** Request ID; from `X-Request-Id` header or generated. */
24
+ requestId: string;
7
25
  }
8
26
  //# sourceMappingURL=serverkit.context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverkit.context.d.ts","sourceRoot":"","sources":["../src/serverkit.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAElD,MAAM,WAAW,gBAAiB,SAAQ,OAAO;IAC/C,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"serverkit.context.d.ts","sourceRoot":"","sources":["../src/serverkit.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAiB,SAAQ,OAAO;IAC/C,8EAA8E;IAC9E,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -1,4 +1,12 @@
1
1
  import { DefaultState, Middleware } from 'koa';
2
2
  import { ServerKitContext } from './serverkit.context.js';
3
+ /**
4
+ * Koa middleware type bound to {@link ServerKitContext}.
5
+ * Use this for middleware that relies on ServerKit context (container, logger, etc.).
6
+ *
7
+ * @typeParam ResponseBody - Type of the response body (defaults to `unknown`).
8
+ * @typeParam State - Koa state type (defaults to `DefaultState`).
9
+ * @typeParam Context - Context type (defaults to `ServerKitContext`).
10
+ */
3
11
  export type ServerKitMiddleware<ResponseBody = unknown, State = DefaultState, Context extends ServerKitContext = ServerKitContext> = Middleware<State, Context, ResponseBody>;
4
12
  //# sourceMappingURL=serverkit.middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverkit.middleware.d.ts","sourceRoot":"","sources":["../src/serverkit.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,MAAM,MAAM,mBAAmB,CAAC,YAAY,GAAG,OAAO,EAAE,KAAK,GAAG,YAAY,EAAE,OAAO,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,UAAU,CAC7I,KAAK,EACL,OAAO,EACP,YAAY,CACb,CAAC"}
1
+ {"version":3,"file":"serverkit.middleware.d.ts","sourceRoot":"","sources":["../src/serverkit.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,CAAC,YAAY,GAAG,OAAO,EAAE,KAAK,GAAG,YAAY,EAAE,OAAO,SAAS,gBAAgB,GAAG,gBAAgB,IAAI,UAAU,CAC7I,KAAK,EACL,OAAO,EACP,YAAY,CACb,CAAC"}
@@ -1,5 +1,13 @@
1
1
  import { DefaultState } from 'koa';
2
2
  import Router from '@koa/router';
3
3
  import { ServerKitContext } from './serverkit.context.js';
4
+ /**
5
+ * Creates a new Koa router typed for ServerKit state and context.
6
+ * Use with {@link ServerKitContext} for full typing of `ctx` in route handlers.
7
+ *
8
+ * @typeParam StateT - Koa state type (defaults to `DefaultState`).
9
+ * @typeParam ContextT - Context type (defaults to `ServerKitContext`).
10
+ * @returns A new {@link Router} instance.
11
+ */
4
12
  export declare const ServerKitRouter: <StateT = DefaultState, ContextT = ServerKitContext>() => Router<StateT, ContextT>;
5
13
  //# sourceMappingURL=serverkit.router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverkit.router.d.ts","sourceRoot":"","sources":["../src/serverkit.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,eAAO,MAAM,eAAe,GAAI,MAAM,GAAG,YAAY,EAAE,QAAQ,GAAG,gBAAgB,+BAAqC,CAAC"}
1
+ {"version":3,"file":"serverkit.router.d.ts","sourceRoot":"","sources":["../src/serverkit.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,GAAG,YAAY,EAAE,QAAQ,GAAG,gBAAgB,+BAAqC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maroonedsoftware/koa",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "author": {
6
6
  "name": "Marooned Software",
@@ -32,23 +32,26 @@
32
32
  "dependencies": {
33
33
  "co-body": "^6.2.0",
34
34
  "injectkit": "^1.0.3",
35
+ "rate-limiter-flexible": "^9.0.1",
35
36
  "raw-body": "^3.0.2",
37
+ "@maroonedsoftware/multipart": "1.0.1",
36
38
  "@maroonedsoftware/logger": "1.0.0",
37
- "@maroonedsoftware/multipart": "1.0.0",
38
- "@maroonedsoftware/errors": "1.0.0"
39
+ "@maroonedsoftware/errors": "1.1.0"
39
40
  },
40
41
  "peerDependencies": {
41
- "koa": "^3.1.1",
42
- "@koa/router": "^15.2.0"
42
+ "@koa/cors": "^5.0.0",
43
+ "@koa/router": "^15.2.0",
44
+ "koa": "^3.1.1"
43
45
  },
44
46
  "devDependencies": {
47
+ "@koa/cors": "^5.0.0",
48
+ "@koa/router": "^15.2.0",
45
49
  "@types/co-body": "^6.1.3",
46
50
  "@types/koa": "^3.0.1",
47
- "@types/koa__router": "^12.0.5",
51
+ "@types/koa__cors": "^5.0.1",
48
52
  "koa": "^3.1.1",
49
- "@koa/router": "^15.2.0",
50
- "@repo/config-eslint": "0.0.0",
51
- "@repo/config-typescript": "0.0.0"
53
+ "@repo/config-typescript": "0.0.0",
54
+ "@repo/config-eslint": "0.0.0"
52
55
  },
53
56
  "scripts": {
54
57
  "build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
@@ -1,4 +0,0 @@
1
- import { Container } from 'injectkit';
2
- import { ServerKitMiddleware } from '../../serverkit.middleware.js';
3
- export declare const injectkitMiddleware: (container: Container) => ServerKitMiddleware;
4
- //# sourceMappingURL=injectkit.middleware.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"injectkit.middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/server/injectkit.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,eAAO,MAAM,mBAAmB,GAAI,WAAW,SAAS,KAAG,mBAM1D,CAAC"}