@nanodi/koa 0.0.0-stage → 0.6.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -2
  3. package/dist/cjs/Controller.d.ts +6 -0
  4. package/dist/cjs/Controller.d.ts.map +1 -0
  5. package/dist/cjs/Controller.js +3 -0
  6. package/dist/cjs/Controller.js.map +1 -0
  7. package/dist/cjs/decorators.d.ts +18 -0
  8. package/dist/cjs/decorators.d.ts.map +1 -0
  9. package/dist/cjs/decorators.js +59 -0
  10. package/dist/cjs/decorators.js.map +1 -0
  11. package/dist/cjs/index.d.ts +11 -0
  12. package/dist/cjs/index.d.ts.map +1 -0
  13. package/dist/cjs/index.js +21 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/middlewares.d.ts +18 -0
  16. package/dist/cjs/middlewares.d.ts.map +1 -0
  17. package/dist/cjs/middlewares.js +104 -0
  18. package/dist/cjs/middlewares.js.map +1 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/cjs/symbols.d.ts +4 -0
  21. package/dist/cjs/symbols.d.ts.map +1 -0
  22. package/dist/cjs/symbols.js +7 -0
  23. package/dist/cjs/symbols.js.map +1 -0
  24. package/dist/esm/Controller.d.ts +6 -0
  25. package/dist/esm/Controller.d.ts.map +1 -0
  26. package/dist/esm/Controller.js +2 -0
  27. package/dist/esm/Controller.js.map +1 -0
  28. package/dist/esm/decorators.d.ts +18 -0
  29. package/dist/esm/decorators.d.ts.map +1 -0
  30. package/dist/esm/decorators.js +52 -0
  31. package/dist/esm/decorators.js.map +1 -0
  32. package/dist/esm/index.d.ts +11 -0
  33. package/dist/esm/index.d.ts.map +1 -0
  34. package/dist/esm/index.js +5 -0
  35. package/dist/esm/index.js.map +1 -0
  36. package/dist/esm/middlewares.d.ts +18 -0
  37. package/dist/esm/middlewares.d.ts.map +1 -0
  38. package/dist/esm/middlewares.js +99 -0
  39. package/dist/esm/middlewares.js.map +1 -0
  40. package/dist/esm/package.json +3 -0
  41. package/dist/esm/symbols.d.ts +4 -0
  42. package/dist/esm/symbols.d.ts.map +1 -0
  43. package/dist/esm/symbols.js +4 -0
  44. package/dist/esm/symbols.js.map +1 -0
  45. package/package.json +49 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # nanodi-koa
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Modern Koa controllers with `@nanodi/core`. Supports routing with `@koa/router`, request-scoped dependency injection and Koa middlewares. Optional auto-registration and discovery using official ECMAScript decorators.
4
+
5
+ ## Usage
6
+
7
+ Write controllers like this:
8
+
9
+ ```ts
10
+ @injectable("scoped", UserService)
11
+ @controller("get", "/users")
12
+ @controller.middleware(requireAuth())
13
+ export class UsersController implements Controller {
14
+ constructor(private users: UserService) {}
15
+
16
+ async handle(ctx: Koa.Context, next: Koa.Next) {
17
+ ctx.body = await this.users.list();
18
+ }
19
+ }
20
+ ```
21
+
22
+ And wire up like this:
23
+
24
+ ```ts
25
+ async function main() {
26
+ const services = new ServiceCollection();
27
+ services.registerScopedValue(ContextKey);
28
+ autobindInjectables(services, [ UserService, UsersController ]);
29
+
30
+ const provider = services.createProvider();
31
+
32
+ const app = new Koa();
33
+ app.use(errorHandler());
34
+ app.use(requestScopeMiddleware(provider));
35
+
36
+ const router = new Router();
37
+ autobindInjectableControllers(router, services);
38
+ app.use(router.routes());
39
+ app.use(router.allowedMethods());
40
+
41
+ app.listen(3000, () => {
42
+ console.log("Server is running on http://localhost:3000");
43
+ });
44
+ }
45
+ ```
46
+
47
+ Errors thrown by controllers propagate to upstream middleware, as usual in Koa.
48
+
49
+ ## Request scope lifetime
50
+
51
+ `requestScopeMiddleware()` creates a scope per request, available as `ctx.scope`, and disposes it when the response closes.
52
+
53
+ The scope is held while the downstream middlewares run, so any middleware that awaits its work can use `ctx.scope` without calling `retain()`, also when the client disconnects early. Streamed bodies keep working since disposal waits for the response to close.
54
+
55
+ Work that is not awaited by the middleware chain, such as callbacks or fire-and-forget promises, should hold the scope itself:
56
+
57
+ ```js
58
+ router.get("/users", (ctx) => {
59
+ const hold = ctx.scope.retain();
60
+ ctx.scope.resolve(UserService).refreshCache(() => {
61
+ hold[Symbol.asyncDispose]();
62
+ });
63
+ ctx.status = 202;
64
+ });
65
+ ```
@@ -0,0 +1,6 @@
1
+ import type * as Koa from "koa";
2
+ /** A request handler class resolved from ctx.scope for each request. Errors propagate to upstream middleware. */
3
+ export interface Controller {
4
+ handle(ctx: Koa.Context, next: Koa.Next): Promise<any>;
5
+ }
6
+ //# sourceMappingURL=Controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Controller.d.ts","sourceRoot":"","sources":["../../src/Controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAEhC,iHAAiH;AACjH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CACxD"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Controller.js","sourceRoot":"","sources":["../../src/Controller.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { RegistrationConstructor, ServiceCollection } from "@nanodi/core";
2
+ import type * as Koa from "koa";
3
+ import type Router from "@koa/router";
4
+ import { Controller } from "./Controller.js";
5
+ type Method = "get" | "post" | "put" | "patch" | "delete";
6
+ /** `@controller(method, path)` routes requests to the class' handle(). Use with autobindControllers() or autobindInjectableControllers(). */
7
+ export declare function controllerDecorator<T extends RegistrationConstructor<Controller>>(method: Method, path: string): (value: T, context: ClassDecoratorContext) => void;
8
+ /** `@controller.middleware(...middlewares)` runs the middlewares before the controller on its route. */
9
+ export declare function middlewareDecorator<T extends RegistrationConstructor<Controller>>(...middlewares: Koa.Middleware[]): (value: T, context: ClassDecoratorContext) => void;
10
+ export declare const controller: typeof controllerDecorator & {
11
+ middleware: typeof middlewareDecorator;
12
+ };
13
+ /** Adds a route to the router for each class decorated with @controller(). The classes must be registered in the provider. */
14
+ export declare function autobindControllers(router: Router, controllerClasses: RegistrationConstructor<Controller>[]): void;
15
+ /** Adds a route to the router for each class in the collection decorated with @controller(). */
16
+ export declare function autobindInjectableControllers(router: Router, services: ServiceCollection): void;
17
+ export {};
18
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAyB,MAAM,cAAc,CAAC;AACjG,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAChC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAa1D,6IAA6I;AAC7I,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,uBAAuB,CAAC,UAAU,CAAC,EAC/E,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,IAEK,OAAO,CAAC,EAAE,SAAS,qBAAqB,UAG1D;AAED,wGAAwG;AACxG,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,uBAAuB,CAAC,UAAU,CAAC,EAAE,GAAG,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,IAChG,OAAO,CAAC,EAAE,SAAS,qBAAqB,UAG1D;AAED,eAAO,MAAM,UAAU;;CAErB,CAAC;AAcH,8HAA8H;AAC9H,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,UAAU,CAAC,EAAE,QAI3G;AAED,gGAAgG;AAChG,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,QAIxF"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.controller = void 0;
4
+ exports.controllerDecorator = controllerDecorator;
5
+ exports.middlewareDecorator = middlewareDecorator;
6
+ exports.autobindControllers = autobindControllers;
7
+ exports.autobindInjectableControllers = autobindInjectableControllers;
8
+ const core_1 = require("@nanodi/core");
9
+ const middlewares_js_1 = require("./middlewares.js");
10
+ function setMetadata(value, context, key, metaValue) {
11
+ var _a;
12
+ if (context.metadata) {
13
+ // Native ES semantics
14
+ context.metadata[key] = metaValue;
15
+ }
16
+ else {
17
+ // TS downlevel semantics
18
+ const meta = value[_a = Symbol.metadata] ?? (value[_a] = {});
19
+ meta[key] = metaValue;
20
+ }
21
+ }
22
+ /** `@controller(method, path)` routes requests to the class' handle(). Use with autobindControllers() or autobindInjectableControllers(). */
23
+ function controllerDecorator(method, path) {
24
+ return function (value, context) {
25
+ setMetadata(value, context, "nanodi:controller", { method, path });
26
+ };
27
+ }
28
+ /** `@controller.middleware(...middlewares)` runs the middlewares before the controller on its route. */
29
+ function middlewareDecorator(...middlewares) {
30
+ return function (value, context) {
31
+ setMetadata(value, context, "nanodi:controller-middlewares", middlewares);
32
+ };
33
+ }
34
+ exports.controller = Object.assign(controllerDecorator, {
35
+ middleware: middlewareDecorator,
36
+ });
37
+ function bindController(router, controllerClass) {
38
+ const metadata = controllerClass[Symbol.metadata];
39
+ const route = metadata?.["nanodi:controller"];
40
+ if (!route) {
41
+ throw new Error(`No @controller metadata found for class: ${controllerClass.name}`);
42
+ }
43
+ const middlewares = metadata["nanodi:controller-middlewares"] ?? [];
44
+ const controllerKey = (0, core_1.getInjectableClassKey)(controllerClass);
45
+ (0, middlewares_js_1.requestHandler)(router, route.method, route.path, middlewares, controllerKey);
46
+ }
47
+ /** Adds a route to the router for each class decorated with @controller(). The classes must be registered in the provider. */
48
+ function autobindControllers(router, controllerClasses) {
49
+ for (const controllerClass of controllerClasses) {
50
+ bindController(router, controllerClass);
51
+ }
52
+ }
53
+ /** Adds a route to the router for each class in the collection decorated with @controller(). */
54
+ function autobindInjectableControllers(router, services) {
55
+ for (const registration of services.getByMetadata("nanodi:controller")) {
56
+ bindController(router, registration.useClass);
57
+ }
58
+ }
59
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":";;;AAoBA,kDAOC;AAGD,kDAIC;AAmBD,kDAIC;AAGD,sEAIC;AAhED,uCAAiG;AAIjG,qDAAkD;AAIlD,SAAS,WAAW,CAAwC,KAAQ,EAAE,OAAiC,EAAE,GAAkB,EAAE,SAAc;;IACzI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,sBAAsB;QACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,yBAAyB;QACzB,MAAM,IAAI,GAAG,KAAK,MAAC,MAAM,CAAC,QAAQ,MAArB,KAAK,OAAsB,EAAE,CAAA,CAAC;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACxB,CAAC;AACH,CAAC;AAED,6IAA6I;AAC7I,SAAgB,mBAAmB,CACjC,MAAc,EACd,IAAY;IAEZ,OAAO,UAAU,KAAQ,EAAE,OAA8B;QACvD,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC;AAED,wGAAwG;AACxG,SAAgB,mBAAmB,CAAgD,GAAG,WAA6B;IACjH,OAAO,UAAU,KAAQ,EAAE,OAA8B;QACvD,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,+BAA+B,EAAE,WAAW,CAAC,CAAC;IAC5E,CAAC,CAAC;AACJ,CAAC;AAEY,QAAA,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE;IAC3D,UAAU,EAAE,mBAAmB;CAChC,CAAC,CAAC;AAEH,SAAS,cAAc,CAAC,MAAc,EAAE,eAAoD;IAC1F,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,mBAAmB,CAAiD,CAAC;IAC9F,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4CAA4C,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,WAAW,GAAG,QAAS,CAAC,+BAA+B,CAAqB,IAAI,EAAE,CAAC;IACzF,MAAM,aAAa,GAAG,IAAA,4BAAqB,EAAC,eAAe,CAAC,CAAC;IAC7D,IAAA,+BAAc,EAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AAC/E,CAAC;AAED,8HAA8H;AAC9H,SAAgB,mBAAmB,CAAC,MAAc,EAAE,iBAAwD;IAC1G,KAAK,MAAM,eAAe,IAAI,iBAAiB,EAAE,CAAC;QAChD,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,gGAAgG;AAChG,SAAgB,6BAA6B,CAAC,MAAc,EAAE,QAA2B;IACvF,KAAK,MAAM,YAAY,IAAI,QAAQ,CAAC,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACvE,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { ServiceProvider } from '@nanodi/core';
2
+ export * from './decorators.js';
3
+ export * from './middlewares.js';
4
+ export * from './symbols.js';
5
+ export * from './Controller.js';
6
+ declare module 'koa' {
7
+ interface DefaultContext {
8
+ scope: ServiceProvider;
9
+ }
10
+ }
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAEhC,OAAO,QAAQ,KAAK,CAAC;IACnB,UAAU,cAAc;QACtB,KAAK,EAAE,eAAe,CAAC;KACxB;CACF"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./decorators.js"), exports);
18
+ __exportStar(require("./middlewares.js"), exports);
19
+ __exportStar(require("./symbols.js"), exports);
20
+ __exportStar(require("./Controller.js"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAEA,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,kDAAgC"}
@@ -0,0 +1,18 @@
1
+ import type * as Koa from "koa";
2
+ import type Router from "@koa/router";
3
+ import { RegistrationKey, ServiceProvider } from "@nanodi/core";
4
+ import { Controller } from "./Controller.js";
5
+ export interface RequestScopeOptions {
6
+ /** Called when disposing a request scope fails. Defaults to console.error. */
7
+ onDisposeError?: (err: unknown) => void;
8
+ }
9
+ /**
10
+ * app.use() this before any routes - makes ctx.scope available to all following middlewares.
11
+ * The scope is held until the downstream middlewares settle, and disposed after the response closes.
12
+ */
13
+ export declare function requestScopeMiddleware(provider: ServiceProvider, options?: RequestScopeOptions): Koa.Middleware;
14
+ /** Runs a controller as middleware */
15
+ export declare function requestHandlerMiddleware(key: RegistrationKey<Controller>): Koa.Middleware;
16
+ /** Registers a controller route on the router */
17
+ export declare function requestHandler(router: Router, method: "get" | "post" | "put" | "patch" | "delete", path: string, middlewares: Koa.Middleware[], key: RegistrationKey<Controller>): void;
18
+ //# sourceMappingURL=middlewares.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middlewares.d.ts","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAChC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,GAAG,CAAC,UAAU,CAkB/G;AAED,sCAAsC;AACtC,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,UAAU,CASzF;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,IAAI,CAEvL"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
3
+ if (value !== null && value !== void 0) {
4
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
5
+ var dispose, inner;
6
+ if (async) {
7
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
8
+ dispose = value[Symbol.asyncDispose];
9
+ }
10
+ if (dispose === void 0) {
11
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
12
+ dispose = value[Symbol.dispose];
13
+ if (async) inner = dispose;
14
+ }
15
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
16
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
17
+ env.stack.push({ value: value, dispose: dispose, async: async });
18
+ }
19
+ else if (async) {
20
+ env.stack.push({ async: true });
21
+ }
22
+ return value;
23
+ };
24
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
25
+ return function (env) {
26
+ function fail(e) {
27
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
28
+ env.hasError = true;
29
+ }
30
+ var r, s = 0;
31
+ function next() {
32
+ while (r = env.stack.pop()) {
33
+ try {
34
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
35
+ if (r.dispose) {
36
+ var result = r.dispose.call(r.value);
37
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
38
+ }
39
+ else s |= 1;
40
+ }
41
+ catch (e) {
42
+ fail(e);
43
+ }
44
+ }
45
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
46
+ if (env.hasError) throw env.error;
47
+ }
48
+ return next();
49
+ };
50
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
51
+ var e = new Error(message);
52
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
+ });
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.requestScopeMiddleware = requestScopeMiddleware;
56
+ exports.requestHandlerMiddleware = requestHandlerMiddleware;
57
+ exports.requestHandler = requestHandler;
58
+ const symbols_js_1 = require("./symbols.js");
59
+ /**
60
+ * app.use() this before any routes - makes ctx.scope available to all following middlewares.
61
+ * The scope is held until the downstream middlewares settle, and disposed after the response closes.
62
+ */
63
+ function requestScopeMiddleware(provider, options) {
64
+ const onDisposeError = options?.onDisposeError ?? (err => console.error("Error disposing request scope", err));
65
+ return async (ctx, next) => {
66
+ const env_1 = { stack: [], error: void 0, hasError: false };
67
+ try {
68
+ const scope = provider.createScope();
69
+ ctx.scope = scope;
70
+ scope.setScopedValue(symbols_js_1.ContextKey, ctx);
71
+ // Streamed bodies are written after the middlewares return, so dispose on close rather than here
72
+ ctx.res.once("close", () => {
73
+ scope[Symbol.asyncDispose]().catch(onDisposeError);
74
+ });
75
+ // Keep the scope alive while downstream runs, also if the response closes early
76
+ const hold = __addDisposableResource(env_1, scope.retain(), true);
77
+ await next();
78
+ }
79
+ catch (e_1) {
80
+ env_1.error = e_1;
81
+ env_1.hasError = true;
82
+ }
83
+ finally {
84
+ const result_1 = __disposeResources(env_1);
85
+ if (result_1)
86
+ await result_1;
87
+ }
88
+ };
89
+ }
90
+ /** Runs a controller as middleware */
91
+ function requestHandlerMiddleware(key) {
92
+ return async (ctx, next) => {
93
+ if (!ctx.scope) {
94
+ throw new Error("ctx.scope is missing. Use requestScopeMiddleware() before any routes");
95
+ }
96
+ const controller = ctx.scope.resolve(key);
97
+ await controller.handle(ctx, next);
98
+ };
99
+ }
100
+ /** Registers a controller route on the router */
101
+ function requestHandler(router, method, path, middlewares, key) {
102
+ router[method](path, ...middlewares, requestHandlerMiddleware(key));
103
+ }
104
+ //# sourceMappingURL=middlewares.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middlewares.js","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,wDAkBC;AAGD,4DASC;AAGD,wCAEC;AA/CD,6CAA0C;AAQ1C;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,QAAyB,EAAE,OAA6B;IAC7F,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC,CAAC;IAE/G,OAAO,KAAK,EAAE,GAAgB,EAAE,IAAc,EAAE,EAAE;;;YAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;YACrC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;YAElB,KAAK,CAAC,cAAc,CAAC,uBAAU,EAAE,GAAG,CAAC,CAAC;YAEtC,iGAAiG;YACjG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACzB,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YACrD,CAAC,CAAC,CAAC;YAEH,gFAAgF;YAChF,MAAY,IAAI,kCAAG,KAAK,CAAC,MAAM,EAAE,OAAA,CAAC;YAClC,MAAM,IAAI,EAAE,CAAC;;;;;;;;;;;KACd,CAAC;AACJ,CAAC;AAED,sCAAsC;AACtC,SAAgB,wBAAwB,CAAC,GAAgC;IACvE,OAAO,KAAK,EAAE,GAAgB,EAAE,IAAc,EAAE,EAAE;QAChD,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC;AACJ,CAAC;AAED,iDAAiD;AACjD,SAAgB,cAAc,CAAC,MAAc,EAAE,MAAmD,EAAE,IAAY,EAAE,WAA6B,EAAE,GAAgC;IAC/K,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,WAAW,EAAE,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,4 @@
1
+ import type * as Koa from "koa";
2
+ /** Scoped value key for the current Koa context. Register with registerScopedValue(). */
3
+ export declare const ContextKey: import("@nanodi/core").RegistrationSymbol<Koa.Context>;
4
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/symbols.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAEhC,yFAAyF;AACzF,eAAO,MAAM,UAAU,wDAA6C,CAAC"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContextKey = void 0;
4
+ const core_1 = require("@nanodi/core");
5
+ /** Scoped value key for the current Koa context. Register with registerScopedValue(). */
6
+ exports.ContextKey = (0, core_1.registrationSymbol)("Context");
7
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/symbols.ts"],"names":[],"mappings":";;;AAAA,uCAAkD;AAGlD,yFAAyF;AAC5E,QAAA,UAAU,GAAG,IAAA,yBAAkB,EAAc,SAAS,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type * as Koa from "koa";
2
+ /** A request handler class resolved from ctx.scope for each request. Errors propagate to upstream middleware. */
3
+ export interface Controller {
4
+ handle(ctx: Koa.Context, next: Koa.Next): Promise<any>;
5
+ }
6
+ //# sourceMappingURL=Controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Controller.d.ts","sourceRoot":"","sources":["../../src/Controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAEhC,iHAAiH;AACjH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CACxD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Controller.js","sourceRoot":"","sources":["../../src/Controller.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { RegistrationConstructor, ServiceCollection } from "@nanodi/core";
2
+ import type * as Koa from "koa";
3
+ import type Router from "@koa/router";
4
+ import { Controller } from "./Controller.js";
5
+ type Method = "get" | "post" | "put" | "patch" | "delete";
6
+ /** `@controller(method, path)` routes requests to the class' handle(). Use with autobindControllers() or autobindInjectableControllers(). */
7
+ export declare function controllerDecorator<T extends RegistrationConstructor<Controller>>(method: Method, path: string): (value: T, context: ClassDecoratorContext) => void;
8
+ /** `@controller.middleware(...middlewares)` runs the middlewares before the controller on its route. */
9
+ export declare function middlewareDecorator<T extends RegistrationConstructor<Controller>>(...middlewares: Koa.Middleware[]): (value: T, context: ClassDecoratorContext) => void;
10
+ export declare const controller: typeof controllerDecorator & {
11
+ middleware: typeof middlewareDecorator;
12
+ };
13
+ /** Adds a route to the router for each class decorated with @controller(). The classes must be registered in the provider. */
14
+ export declare function autobindControllers(router: Router, controllerClasses: RegistrationConstructor<Controller>[]): void;
15
+ /** Adds a route to the router for each class in the collection decorated with @controller(). */
16
+ export declare function autobindInjectableControllers(router: Router, services: ServiceCollection): void;
17
+ export {};
18
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAyB,MAAM,cAAc,CAAC;AACjG,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAChC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAa1D,6IAA6I;AAC7I,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,uBAAuB,CAAC,UAAU,CAAC,EAC/E,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,IAEK,OAAO,CAAC,EAAE,SAAS,qBAAqB,UAG1D;AAED,wGAAwG;AACxG,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,uBAAuB,CAAC,UAAU,CAAC,EAAE,GAAG,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,IAChG,OAAO,CAAC,EAAE,SAAS,qBAAqB,UAG1D;AAED,eAAO,MAAM,UAAU;;CAErB,CAAC;AAcH,8HAA8H;AAC9H,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,UAAU,CAAC,EAAE,QAI3G;AAED,gGAAgG;AAChG,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,QAIxF"}
@@ -0,0 +1,52 @@
1
+ import { getInjectableClassKey } from "@nanodi/core";
2
+ import { requestHandler } from "./middlewares.js";
3
+ function setMetadata(value, context, key, metaValue) {
4
+ var _a;
5
+ if (context.metadata) {
6
+ // Native ES semantics
7
+ context.metadata[key] = metaValue;
8
+ }
9
+ else {
10
+ // TS downlevel semantics
11
+ const meta = value[_a = Symbol.metadata] ?? (value[_a] = {});
12
+ meta[key] = metaValue;
13
+ }
14
+ }
15
+ /** `@controller(method, path)` routes requests to the class' handle(). Use with autobindControllers() or autobindInjectableControllers(). */
16
+ export function controllerDecorator(method, path) {
17
+ return function (value, context) {
18
+ setMetadata(value, context, "nanodi:controller", { method, path });
19
+ };
20
+ }
21
+ /** `@controller.middleware(...middlewares)` runs the middlewares before the controller on its route. */
22
+ export function middlewareDecorator(...middlewares) {
23
+ return function (value, context) {
24
+ setMetadata(value, context, "nanodi:controller-middlewares", middlewares);
25
+ };
26
+ }
27
+ export const controller = Object.assign(controllerDecorator, {
28
+ middleware: middlewareDecorator,
29
+ });
30
+ function bindController(router, controllerClass) {
31
+ const metadata = controllerClass[Symbol.metadata];
32
+ const route = metadata?.["nanodi:controller"];
33
+ if (!route) {
34
+ throw new Error(`No @controller metadata found for class: ${controllerClass.name}`);
35
+ }
36
+ const middlewares = metadata["nanodi:controller-middlewares"] ?? [];
37
+ const controllerKey = getInjectableClassKey(controllerClass);
38
+ requestHandler(router, route.method, route.path, middlewares, controllerKey);
39
+ }
40
+ /** Adds a route to the router for each class decorated with @controller(). The classes must be registered in the provider. */
41
+ export function autobindControllers(router, controllerClasses) {
42
+ for (const controllerClass of controllerClasses) {
43
+ bindController(router, controllerClass);
44
+ }
45
+ }
46
+ /** Adds a route to the router for each class in the collection decorated with @controller(). */
47
+ export function autobindInjectableControllers(router, services) {
48
+ for (const registration of services.getByMetadata("nanodi:controller")) {
49
+ bindController(router, registration.useClass);
50
+ }
51
+ }
52
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAIjG,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAIlD,SAAS,WAAW,CAAwC,KAAQ,EAAE,OAAiC,EAAE,GAAkB,EAAE,SAAc;;IACzI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,sBAAsB;QACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,yBAAyB;QACzB,MAAM,IAAI,GAAG,KAAK,MAAC,MAAM,CAAC,QAAQ,MAArB,KAAK,OAAsB,EAAE,CAAA,CAAC;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACxB,CAAC;AACH,CAAC;AAED,6IAA6I;AAC7I,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,IAAY;IAEZ,OAAO,UAAU,KAAQ,EAAE,OAA8B;QACvD,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;AACJ,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,mBAAmB,CAAgD,GAAG,WAA6B;IACjH,OAAO,UAAU,KAAQ,EAAE,OAA8B;QACvD,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,+BAA+B,EAAE,WAAW,CAAC,CAAC;IAC5E,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE;IAC3D,UAAU,EAAE,mBAAmB;CAChC,CAAC,CAAC;AAEH,SAAS,cAAc,CAAC,MAAc,EAAE,eAAoD;IAC1F,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,mBAAmB,CAAiD,CAAC;IAC9F,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4CAA4C,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,WAAW,GAAG,QAAS,CAAC,+BAA+B,CAAqB,IAAI,EAAE,CAAC;IACzF,MAAM,aAAa,GAAG,qBAAqB,CAAC,eAAe,CAAC,CAAC;IAC7D,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AAC/E,CAAC;AAED,8HAA8H;AAC9H,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,iBAAwD;IAC1G,KAAK,MAAM,eAAe,IAAI,iBAAiB,EAAE,CAAC;QAChD,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,6BAA6B,CAAC,MAAc,EAAE,QAA2B;IACvF,KAAK,MAAM,YAAY,IAAI,QAAQ,CAAC,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACvE,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { ServiceProvider } from '@nanodi/core';
2
+ export * from './decorators.js';
3
+ export * from './middlewares.js';
4
+ export * from './symbols.js';
5
+ export * from './Controller.js';
6
+ declare module 'koa' {
7
+ interface DefaultContext {
8
+ scope: ServiceProvider;
9
+ }
10
+ }
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAEhC,OAAO,QAAQ,KAAK,CAAC;IACnB,UAAU,cAAc;QACtB,KAAK,EAAE,eAAe,CAAC;KACxB;CACF"}
@@ -0,0 +1,5 @@
1
+ export * from './decorators.js';
2
+ export * from './middlewares.js';
3
+ export * from './symbols.js';
4
+ export * from './Controller.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type * as Koa from "koa";
2
+ import type Router from "@koa/router";
3
+ import { RegistrationKey, ServiceProvider } from "@nanodi/core";
4
+ import { Controller } from "./Controller.js";
5
+ export interface RequestScopeOptions {
6
+ /** Called when disposing a request scope fails. Defaults to console.error. */
7
+ onDisposeError?: (err: unknown) => void;
8
+ }
9
+ /**
10
+ * app.use() this before any routes - makes ctx.scope available to all following middlewares.
11
+ * The scope is held until the downstream middlewares settle, and disposed after the response closes.
12
+ */
13
+ export declare function requestScopeMiddleware(provider: ServiceProvider, options?: RequestScopeOptions): Koa.Middleware;
14
+ /** Runs a controller as middleware */
15
+ export declare function requestHandlerMiddleware(key: RegistrationKey<Controller>): Koa.Middleware;
16
+ /** Registers a controller route on the router */
17
+ export declare function requestHandler(router: Router, method: "get" | "post" | "put" | "patch" | "delete", path: string, middlewares: Koa.Middleware[], key: RegistrationKey<Controller>): void;
18
+ //# sourceMappingURL=middlewares.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middlewares.d.ts","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAChC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,GAAG,CAAC,UAAU,CAkB/G;AAED,sCAAsC;AACtC,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,UAAU,CASzF;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,IAAI,CAEvL"}
@@ -0,0 +1,99 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { ContextKey } from "./symbols.js";
54
+ /**
55
+ * app.use() this before any routes - makes ctx.scope available to all following middlewares.
56
+ * The scope is held until the downstream middlewares settle, and disposed after the response closes.
57
+ */
58
+ export function requestScopeMiddleware(provider, options) {
59
+ const onDisposeError = options?.onDisposeError ?? (err => console.error("Error disposing request scope", err));
60
+ return async (ctx, next) => {
61
+ const env_1 = { stack: [], error: void 0, hasError: false };
62
+ try {
63
+ const scope = provider.createScope();
64
+ ctx.scope = scope;
65
+ scope.setScopedValue(ContextKey, ctx);
66
+ // Streamed bodies are written after the middlewares return, so dispose on close rather than here
67
+ ctx.res.once("close", () => {
68
+ scope[Symbol.asyncDispose]().catch(onDisposeError);
69
+ });
70
+ // Keep the scope alive while downstream runs, also if the response closes early
71
+ const hold = __addDisposableResource(env_1, scope.retain(), true);
72
+ await next();
73
+ }
74
+ catch (e_1) {
75
+ env_1.error = e_1;
76
+ env_1.hasError = true;
77
+ }
78
+ finally {
79
+ const result_1 = __disposeResources(env_1);
80
+ if (result_1)
81
+ await result_1;
82
+ }
83
+ };
84
+ }
85
+ /** Runs a controller as middleware */
86
+ export function requestHandlerMiddleware(key) {
87
+ return async (ctx, next) => {
88
+ if (!ctx.scope) {
89
+ throw new Error("ctx.scope is missing. Use requestScopeMiddleware() before any routes");
90
+ }
91
+ const controller = ctx.scope.resolve(key);
92
+ await controller.handle(ctx, next);
93
+ };
94
+ }
95
+ /** Registers a controller route on the router */
96
+ export function requestHandler(router, method, path, middlewares, key) {
97
+ router[method](path, ...middlewares, requestHandlerMiddleware(key));
98
+ }
99
+ //# sourceMappingURL=middlewares.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middlewares.js","sourceRoot":"","sources":["../../src/middlewares.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ1C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAyB,EAAE,OAA6B;IAC7F,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC,CAAC;IAE/G,OAAO,KAAK,EAAE,GAAgB,EAAE,IAAc,EAAE,EAAE;;;YAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;YACrC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;YAElB,KAAK,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAEtC,iGAAiG;YACjG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACzB,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YACrD,CAAC,CAAC,CAAC;YAEH,gFAAgF;YAChF,MAAY,IAAI,kCAAG,KAAK,CAAC,MAAM,EAAE,OAAA,CAAC;YAClC,MAAM,IAAI,EAAE,CAAC;;;;;;;;;;;KACd,CAAC;AACJ,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,wBAAwB,CAAC,GAAgC;IACvE,OAAO,KAAK,EAAE,GAAgB,EAAE,IAAc,EAAE,EAAE;QAChD,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC;AACJ,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAmD,EAAE,IAAY,EAAE,WAA6B,EAAE,GAAgC;IAC/K,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,WAAW,EAAE,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,4 @@
1
+ import type * as Koa from "koa";
2
+ /** Scoped value key for the current Koa context. Register with registerScopedValue(). */
3
+ export declare const ContextKey: import("@nanodi/core").RegistrationSymbol<Koa.Context>;
4
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/symbols.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAEhC,yFAAyF;AACzF,eAAO,MAAM,UAAU,wDAA6C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { registrationSymbol } from "@nanodi/core";
2
+ /** Scoped value key for the current Koa context. Register with registerScopedValue(). */
3
+ export const ContextKey = registrationSymbol("Context");
4
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/symbols.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGlD,yFAAyF;AACzF,MAAM,CAAC,MAAM,UAAU,GAAG,kBAAkB,CAAc,SAAS,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@nanodi/koa",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.6.0",
4
+ "description": "Koa controllers with request-scoped dependency injection using @nanodi/core.",
5
+ "keywords": [
6
+ "dependency-injection",
7
+ "express-middleware",
8
+ "ioc",
9
+ "ioc-container",
10
+ "koa-middleware",
11
+ "typescript"
12
+ ],
13
+ "private": false,
14
+ "types": "./dist/esm/index.d.ts",
15
+ "main": "./dist/cjs/index.js",
16
+ "source": "./src/index.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "homepage": "https://github.com/andersnm/nanodi",
21
+ "repository": "https://github.com/andersnm/nanodi",
22
+ "license": "MIT",
23
+ "exports": {
24
+ ".": {
25
+ "import": "./dist/esm/index.js",
26
+ "require": "./dist/cjs/index.js",
27
+ "types": "./dist/esm/index.d.ts"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "npm run build:esm && npm run build:cjs && npm run build:license",
32
+ "build:esm": "tsc -p tsconfig.esm.json && npm run build:package-esm",
33
+ "build:cjs": "tsc -p tsconfig.cjs.json && npm run build:package-cjs",
34
+ "build:package-esm": "node build/copyfile.mjs build/package.esm.json dist/esm/package.json",
35
+ "build:package-cjs": "node build/copyfile.mjs build/package.cjs.json dist/cjs/package.json",
36
+ "build:license": "node build/copyfile.mjs ../LICENSE LICENSE"
37
+ },
38
+ "devDependencies": {
39
+ "@koa/router": "^15.0.0",
40
+ "@types/koa": "^3.0.0",
41
+ "@types/node": "^18.11.18",
42
+ "koa": "^3.0.0"
43
+ },
44
+ "dependencies": {
45
+ "@nanodi/core": "^0.6.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@koa/router": "^15.0.0",
49
+ "koa": "^2.13.0 || ^3.0.0"
50
+ }
51
+ }