@solid-stack/agnos-express 1.0.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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2026 Solid Stack Digital
2
+
3
+ All rights reserved.
4
+
5
+ This software and its associated documentation files (the "Software") are
6
+ proprietary to Solid Stack Digital.
7
+
8
+ Strictly confidential. No part of this Software may be used, copied,
9
+ modified, merged, published, distributed, sublicensed, or sold in any form
10
+ or by any means without the prior written permission of the copyright owner.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @solid-stack/agnos-express
2
+
3
+ Express presentation adapter for the [Agnos](https://github.com/solid-stack-digital/agnos) clean application architecture framework.
4
+
5
+ ---
6
+
7
+ ## ✨ Features
8
+
9
+ - ⚡ **Seamless Agnos Integration**: Implements the `IPresentation` lifecycle interface (`init`, `run`, `stop`).
10
+ - 🧩 **Dependency Injection**: Fully integrated with `@solid-stack/di` using `ValueToken` and `MultiToken` patterns.
11
+ - 🚀 **Auto-Discovery**: Automatic feature route discovery from `pxExpress` directories and global middleware loading.
12
+ - 🛡️ **Clean Encapsulation**: Minimal public API surface — all internals are abstracted.
13
+ - 📦 **Dual ESM & CommonJS Output**: Bundled via `tsup` with complete TypeScript type declarations.
14
+
15
+ ---
16
+
17
+ ## 📦 Installation
18
+
19
+ ```bash
20
+ pnpm add @solid-stack/agnos-express @solid-stack/agnos @solid-stack/di express
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 🚀 Quick Start
26
+
27
+ ### 1. Define a Route
28
+
29
+ Create a route handler extending `ExpressRoute`:
30
+
31
+ ```typescript
32
+ import { ExpressRoute } from "@solid-stack/agnos-express";
33
+ import type { Request, Response } from "express";
34
+
35
+ export default class GetUsersRoute extends ExpressRoute {
36
+ method = "get" as const;
37
+ path = "/";
38
+
39
+ handler = async (req: Request, res: Response) => {
40
+ res.json({ users: [{ id: 1, name: "Alice" }] });
41
+ };
42
+ }
43
+ ```
44
+
45
+ ### 2. Bootstrap Application
46
+
47
+ ```typescript
48
+ import { Container } from "@solid-stack/di";
49
+ import { start } from "@solid-stack/agnos";
50
+ import { loadExpressPresentation } from "@solid-stack/agnos-express";
51
+
52
+ async function main() {
53
+ const container = new Container();
54
+
55
+ // Load Express presentation layer
56
+ await loadExpressPresentation(container, {
57
+ port: 3000,
58
+ featuresDir: "./src/features",
59
+ mainDir: "./src/presentation",
60
+ });
61
+
62
+ // Start Agnos application lifecycle
63
+ await start(container, {
64
+ featuresDir: "./src/features",
65
+ });
66
+ }
67
+
68
+ main().catch(console.error);
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 📁 Directory Architecture
74
+
75
+ ```text
76
+ my-app/
77
+ ├── src/
78
+ │ ├── features/
79
+ │ │ └── users/
80
+ │ │ ├── pxExpress/
81
+ │ │ │ ├── index.ts # export const path = '/users';
82
+ │ │ │ └── handlers/
83
+ │ │ │ └── getUsers.ts # export default class GetUsersRoute extends ExpressRoute
84
+ │ │ └── DepsProvider.ts
85
+ │ └── presentation/
86
+ │ └── index.ts # export const middlewares = [...]; export const errorHandlers = [...];
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 🛠️ Available Scripts
92
+
93
+ | Command | Description |
94
+ | --- | --- |
95
+ | `pnpm dev` | Starts `tsup` in watch mode for development |
96
+ | `pnpm build` | Bundles ESM/CJS outputs and generates TypeScript type declarations |
97
+ | `pnpm test` | Runs tests using Vitest |
98
+ | `pnpm test:watch` | Runs Vitest in interactive watch mode |
99
+ | `pnpm test:coverage` | Runs tests with code coverage reporting |
100
+ | `pnpm typecheck` | Typechecks the project using TypeScript (`tsc --noEmit`) |
101
+ | `pnpm check` | Runs typecheck, tests, and build in sequence |
102
+
103
+ ---
104
+
105
+ ## 📄 License
106
+
107
+ MIT © [Solid Stack Digital](https://github.com/solid-stack-digital)
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Default configuration constants for Agnos Express presentation.
3
+ */
4
+ export declare const DEFAULT_PORT = 3000;
5
+ export declare const DEFAULT_HOST = "0.0.0.0";
6
+ export declare const DEFAULT_FEATURE_PX_DIR = "pxExpress";
7
+ export declare const DEFAULT_HANDLERS_DIR = "handlers";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,YAAY,YAAY,CAAC;AACtC,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAClD,eAAO,MAAM,oBAAoB,aAAa,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { Server } from "node:http";
2
+ import { type Express } from "express";
3
+ import { type DepsType } from "@solid-stack/di";
4
+ import { IPresentation, ILogger } from "@solid-stack/agnos";
5
+ import { ExpressPxConfigsToken, ExpressRoutersToken, ExpressMiddlewaresToken, ExpressErrorHandlersToken } from "../types/index.js";
6
+ /**
7
+ * Express HTTP Presentation Layer implementation for the Agnos framework.
8
+ *
9
+ * Implements the IPresentation lifecycle contract:
10
+ * - init: Configures JSON body parsing, attaches global middlewares, routers, and error handlers.
11
+ * - run: Starts the HTTP server on the configured port and host.
12
+ * - stop: Gracefully shuts down the HTTP server.
13
+ */
14
+ export declare class ExpressPresentation implements IPresentation {
15
+ deps: DepsType<typeof ExpressPresentation.deps>;
16
+ private readonly app;
17
+ private server;
18
+ static deps: {
19
+ configs: typeof ExpressPxConfigsToken;
20
+ errorHandlers: typeof ExpressErrorHandlersToken;
21
+ middlewares: typeof ExpressMiddlewaresToken;
22
+ routers: typeof ExpressRoutersToken;
23
+ logger: typeof ILogger;
24
+ };
25
+ constructor(deps: DepsType<typeof ExpressPresentation.deps>);
26
+ /**
27
+ * Initializes middleware, feature routers, and error handlers on the Express app.
28
+ */
29
+ init(): Promise<void>;
30
+ private attachMiddlewares;
31
+ private attachRouters;
32
+ private attachErrorHandlers;
33
+ /**
34
+ * Starts the Express server listening on the configured port and host.
35
+ */
36
+ run(): Promise<void>;
37
+ /**
38
+ * Gracefully closes the running HTTP server.
39
+ */
40
+ stop(): Promise<void>;
41
+ /**
42
+ * Returns the underlying Express application instance.
43
+ */
44
+ getApp(): Express;
45
+ /**
46
+ * Returns the active Node HTTP server instance, or null if not currently running.
47
+ */
48
+ getServer(): Server | null;
49
+ }
50
+ //# sourceMappingURL=ExpressPresentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpressPresentation.d.ts","sourceRoot":"","sources":["../../src/core/ExpressPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAgB,EAEd,KAAK,OAAO,EAGb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;GAOG;AACH,qBACa,mBAAoB,YAAW,aAAa;IAYpC,IAAI,EAAE,QAAQ,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC;IAXlE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,MAAM,CAAuB;IAErC,OAAc,IAAI;QAChB,OAAO;QACP,aAAa;QACb,WAAW;QACX,OAAO;QACP,MAAM;MACN;IAEF,YAAmB,IAAI,EAAE,QAAQ,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC,EAEjE;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1B;IAED,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA0BzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB1B;IAED;;OAEG;IACI,MAAM,IAAI,OAAO,CAEvB;IAED;;OAEG;IACI,SAAS,IAAI,MAAM,GAAG,IAAI,CAEhC;CACF"}
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler } from "express";
2
+ import type { ExpressRouteMethod } from "../types/index.js";
3
+ /**
4
+ * Abstract base class for defining an Express route handler within an Agnos feature.
5
+ */
6
+ export declare abstract class ExpressRoute {
7
+ /**
8
+ * The HTTP method for this route (e.g. 'get', 'post', 'put', 'delete').
9
+ */
10
+ abstract method: ExpressRouteMethod;
11
+ /**
12
+ * The sub-path for this route relative to the feature router's base path (e.g. '/' or '/:id').
13
+ */
14
+ abstract path: string;
15
+ /**
16
+ * The Express request handler function.
17
+ */
18
+ abstract handler: RequestHandler;
19
+ /**
20
+ * Optional route-level middlewares executed before handler.
21
+ */
22
+ middlewares?: RequestHandler[];
23
+ }
24
+ //# sourceMappingURL=ExpressRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpressRoute.d.ts","sourceRoot":"","sources":["../../src/core/ExpressRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;GAEG;AACH,8BAAsB,YAAY;IAChC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IAEpC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAEjC;;OAEG;IACH,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC"}
@@ -0,0 +1,20 @@
1
+ import type { RequestHandler } from "express";
2
+ import type { ExpressRoute } from "./ExpressRoute.js";
3
+ /**
4
+ * Abstract base class for defining an Express router grouping multiple routes under a common path prefix.
5
+ */
6
+ export declare abstract class ExpressRouter {
7
+ /**
8
+ * The base URL path prefix for this router (e.g. '/users' or '/api/v1/orders').
9
+ */
10
+ abstract get path(): string;
11
+ /**
12
+ * The collection of routes registered under this router.
13
+ */
14
+ abstract get routes(): ExpressRoute[];
15
+ /**
16
+ * Optional router-level middlewares executed for all routes in this router.
17
+ */
18
+ middlewares?: RequestHandler[];
19
+ }
20
+ //# sourceMappingURL=ExpressRouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpressRouter.d.ts","sourceRoot":"","sources":["../../src/core/ExpressRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,8BAAsB,aAAa;IACjC;;OAEG;IACH,aAAoB,IAAI,IAAI,MAAM,CAAC;IAEnC;;OAEG;IACH,aAAoB,MAAM,IAAI,YAAY,EAAE,CAAC;IAE7C;;OAEG;IACI,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CACvC"}
@@ -0,0 +1,5 @@
1
+ export { ExpressPresentation } from "./ExpressPresentation.js";
2
+ export { loadExpressPresentation } from "./loadExpressPresentation.js";
3
+ export { ExpressRoute } from "./ExpressRoute.js";
4
+ export { ExpressRouter } from "./ExpressRouter.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Container } from "@solid-stack/di";
2
+ import { type ExpressPresentationOptions } from "../types/index.js";
3
+ /**
4
+ * Bootstraps and registers the Express presentation layer in the DI container.
5
+ *
6
+ * Configures:
7
+ * 1. ExpressPxConfigsToken with port and optional host.
8
+ * 2. Autoloads global middlewares and error handlers from mainDir if specified.
9
+ * 3. Autoloads feature routes from featuresDir if specified.
10
+ * 4. Binds IPresentation to ExpressPresentation.
11
+ *
12
+ * @param container - The @solid-stack/di Container instance.
13
+ * @param options - Presentation bootstrap options.
14
+ */
15
+ export declare const loadExpressPresentation: (container: Container, options?: ExpressPresentationOptions) => Promise<void>;
16
+ //# sourceMappingURL=loadExpressPresentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadExpressPresentation.d.ts","sourceRoot":"","sources":["../../src/core/loadExpressPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,mBAAmB,CAAC;AAK3B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,cACvB,SAAS,YACV,0BAA0B,KACnC,OAAO,CAAC,IAAI,CAkBd,CAAC"}
package/dist/index.cjs ADDED
@@ -0,0 +1,387 @@
1
+ 'use strict';
2
+
3
+ var express = require('express');
4
+ var di = require('@solid-stack/di');
5
+ var agnos = require('@solid-stack/agnos');
6
+ var fs2 = require('fs/promises');
7
+ var path2 = require('path');
8
+ var url = require('url');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var express__default = /*#__PURE__*/_interopDefault(express);
13
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
14
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
15
+
16
+ var __create = Object.create;
17
+ var __defProp = Object.defineProperty;
18
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
19
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
20
+ var __typeError = (msg) => {
21
+ throw TypeError(msg);
22
+ };
23
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
24
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
25
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
26
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
27
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
28
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
29
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
30
+ var __runInitializers = (array, flags, self, value) => {
31
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
32
+ return value;
33
+ };
34
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
35
+ var it, done, ctx, k = flags & 7, p = false;
36
+ var j = 0;
37
+ var extraInitializers = array[j] || (array[j] = []);
38
+ var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name));
39
+ __name(target, name);
40
+ for (var i = decorators.length - 1; i >= 0; i--) {
41
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
42
+ it = (0, decorators[i])(target, ctx), done._ = 1;
43
+ __expectFn(it) && (target = it);
44
+ }
45
+ return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
46
+ };
47
+ var ExpressPxConfigsToken = class extends di.ValueToken {
48
+ };
49
+ var ExpressRoutersToken = class extends di.MultiToken {
50
+ };
51
+ var ExpressMiddlewaresToken = class extends di.MultiToken {
52
+ };
53
+ var ExpressErrorHandlersToken = class extends di.MultiToken {
54
+ };
55
+
56
+ // src/core/ExpressPresentation.ts
57
+ var _ExpressPresentation_decorators, _init;
58
+ _ExpressPresentation_decorators = [di.MakeInjectable];
59
+ exports.ExpressPresentation = class ExpressPresentation {
60
+ constructor(deps) {
61
+ this.deps = deps;
62
+ this.app = express__default.default();
63
+ }
64
+ deps;
65
+ app;
66
+ server = null;
67
+ static deps = {
68
+ configs: ExpressPxConfigsToken,
69
+ errorHandlers: ExpressErrorHandlersToken,
70
+ middlewares: ExpressMiddlewaresToken,
71
+ routers: ExpressRoutersToken,
72
+ logger: agnos.ILogger
73
+ };
74
+ /**
75
+ * Initializes middleware, feature routers, and error handlers on the Express app.
76
+ */
77
+ async init() {
78
+ this.app.use(express__default.default.json());
79
+ this.attachMiddlewares();
80
+ this.attachRouters();
81
+ this.attachErrorHandlers();
82
+ }
83
+ attachMiddlewares() {
84
+ const middlewares = this.deps.middlewares ?? [];
85
+ for (const middleware of middlewares) {
86
+ this.app.use((req, res, next) => middleware.handler(req, res, next));
87
+ }
88
+ }
89
+ attachRouters() {
90
+ const routers = this.deps.routers ?? [];
91
+ this.deps.logger.debug("Registering Express routers...", {
92
+ count: routers.length
93
+ });
94
+ for (const subRouter of routers) {
95
+ const router = express.Router();
96
+ if (subRouter.middlewares && subRouter.middlewares.length > 0) {
97
+ for (const mw of subRouter.middlewares) {
98
+ router.use(mw);
99
+ }
100
+ }
101
+ for (const route of subRouter.routes) {
102
+ this.deps.logger.debug(
103
+ `Registering route: [${route.method.toUpperCase()}] ${subRouter.path}${route.path}`
104
+ );
105
+ const routeHandler = (req, res, next) => route.handler(req, res, next);
106
+ if (route.middlewares && route.middlewares.length > 0) {
107
+ router[route.method](route.path, ...route.middlewares, routeHandler);
108
+ } else {
109
+ router[route.method](route.path, routeHandler);
110
+ }
111
+ }
112
+ this.app.use(subRouter.path, router);
113
+ }
114
+ }
115
+ attachErrorHandlers() {
116
+ const errorHandlers = this.deps.errorHandlers ?? [];
117
+ for (const errorHandler of errorHandlers) {
118
+ const handler = (err, req, res, next) => errorHandler.handler(err, req, res, next);
119
+ this.app.use(handler);
120
+ }
121
+ }
122
+ /**
123
+ * Starts the Express server listening on the configured port and host.
124
+ */
125
+ async run() {
126
+ return new Promise((resolve, reject) => {
127
+ try {
128
+ const { port, host } = this.deps.configs;
129
+ const onListening = () => {
130
+ this.deps.logger.info(
131
+ `Express server listening on ${host ? `${host}:` : "port "}${port}`
132
+ );
133
+ resolve();
134
+ };
135
+ if (host) {
136
+ this.server = this.app.listen(port, host, onListening);
137
+ } else {
138
+ this.server = this.app.listen(port, onListening);
139
+ }
140
+ this.server.on("error", (err) => {
141
+ this.deps.logger.error("Express server error:", err);
142
+ reject(err);
143
+ });
144
+ } catch (err) {
145
+ reject(err);
146
+ }
147
+ });
148
+ }
149
+ /**
150
+ * Gracefully closes the running HTTP server.
151
+ */
152
+ async stop() {
153
+ return new Promise((resolve, reject) => {
154
+ if (!this.server) {
155
+ return resolve();
156
+ }
157
+ this.server.close((err) => {
158
+ if (err) {
159
+ this.deps.logger.error("Error closing Express server:", err);
160
+ return reject(err);
161
+ }
162
+ this.deps.logger.info("Express server stopped receiving new requests.");
163
+ this.server = null;
164
+ resolve();
165
+ });
166
+ });
167
+ }
168
+ /**
169
+ * Returns the underlying Express application instance.
170
+ */
171
+ getApp() {
172
+ return this.app;
173
+ }
174
+ /**
175
+ * Returns the active Node HTTP server instance, or null if not currently running.
176
+ */
177
+ getServer() {
178
+ return this.server;
179
+ }
180
+ };
181
+ _init = __decoratorStart(null);
182
+ exports.ExpressPresentation = __decorateElement(_init, 0, "ExpressPresentation", _ExpressPresentation_decorators, exports.ExpressPresentation);
183
+ __runInitializers(_init, 1, exports.ExpressPresentation);
184
+ async function folderExists(dir) {
185
+ try {
186
+ const stats = await fs2.stat(dir);
187
+ return stats.isDirectory();
188
+ } catch (error) {
189
+ if (error?.code === "ENOENT") {
190
+ return false;
191
+ }
192
+ throw error;
193
+ }
194
+ }
195
+
196
+ // src/utils/autoLoadMainPx.ts
197
+ async function autoLoadMainPx(c, mainDir) {
198
+ if (!await folderExists(mainDir)) {
199
+ return;
200
+ }
201
+ const mainDirContents = await fs2__default.default.readdir(mainDir, { withFileTypes: true });
202
+ const indexFile = mainDirContents.find(
203
+ (f) => f.isFile() && (f.name === "index.ts" || f.name === "index.js" || f.name === "index.mjs" || f.name === "index.cjs")
204
+ );
205
+ if (!indexFile) {
206
+ throw new agnos.PresentationError(
207
+ `No index.ts or index.js file found in the main presentation directory: ${mainDir}`,
208
+ { phase: "init" }
209
+ );
210
+ }
211
+ const indexFilePath = path2__default.default.join(mainDir, indexFile.name);
212
+ const imported = await import(url.pathToFileURL(indexFilePath).href);
213
+ const middlewares = imported.middlewares;
214
+ if (middlewares && Array.isArray(middlewares)) {
215
+ for (const middleware of middlewares) {
216
+ c.provideMulti(ExpressMiddlewaresToken, middleware);
217
+ }
218
+ }
219
+ const errorHandlers = imported.errorHandlers;
220
+ if (errorHandlers && Array.isArray(errorHandlers)) {
221
+ for (const errorHandler of errorHandlers) {
222
+ c.provideMulti(ExpressErrorHandlersToken, errorHandler);
223
+ }
224
+ }
225
+ }
226
+
227
+ // src/core/ExpressRouter.ts
228
+ var ExpressRouter = class {
229
+ /**
230
+ * Optional router-level middlewares executed for all routes in this router.
231
+ */
232
+ middlewares;
233
+ };
234
+
235
+ // src/core/ExpressRoute.ts
236
+ var ExpressRoute = class {
237
+ /**
238
+ * Optional route-level middlewares executed before handler.
239
+ */
240
+ middlewares;
241
+ };
242
+
243
+ // src/constants/index.ts
244
+ var DEFAULT_PORT = 3e3;
245
+ var DEFAULT_FEATURE_PX_DIR = "pxExpress";
246
+ var DEFAULT_HANDLERS_DIR = "handlers";
247
+
248
+ // src/utils/autoLoadFeature.ts
249
+ async function autoLoadFeature(c, featuresDir) {
250
+ var _FeatureRouter_decorators, _init2, _a;
251
+ if (!await folderExists(featuresDir)) {
252
+ return;
253
+ }
254
+ const features = await fs2__default.default.readdir(featuresDir, { withFileTypes: true });
255
+ for (const feature of features) {
256
+ if (!feature.isDirectory()) continue;
257
+ const featureName = feature.name;
258
+ const pxExpressDir = path2__default.default.join(
259
+ featuresDir,
260
+ featureName,
261
+ DEFAULT_FEATURE_PX_DIR
262
+ );
263
+ if (!await folderExists(pxExpressDir)) continue;
264
+ const pxExpressDirFiles = await fs2__default.default.readdir(pxExpressDir);
265
+ const indexFile = pxExpressDirFiles.find(
266
+ (f) => f === "index.ts" || f === "index.js" || f === "index.mjs" || f === "index.cjs"
267
+ );
268
+ if (!indexFile) {
269
+ throw new agnos.FeatureLoadError(
270
+ featureName,
271
+ pxExpressDir,
272
+ `The express presentation of feature "${featureName}" (${pxExpressDir}) does not have an index.ts or index.js file.`
273
+ );
274
+ }
275
+ const indexFilePath = path2__default.default.join(pxExpressDir, indexFile);
276
+ const imported = await import(url.pathToFileURL(indexFilePath).href);
277
+ const routePath = imported.path;
278
+ if (typeof routePath !== "string") {
279
+ throw new agnos.FeatureLoadError(
280
+ featureName,
281
+ indexFilePath,
282
+ `The express presentation of feature "${featureName}" (${pxExpressDir}) has an index file, but it doesn't export a string named 'path'.`
283
+ );
284
+ }
285
+ const routeClasses = [];
286
+ const routesDir = path2__default.default.join(pxExpressDir, DEFAULT_HANDLERS_DIR);
287
+ if (await folderExists(routesDir)) {
288
+ const routeFiles = await fs2__default.default.readdir(routesDir);
289
+ for (const routeFile of routeFiles) {
290
+ if (!routeFile.match(/\.(js|ts|mjs|cjs)$/) || routeFile.endsWith(".d.ts") || routeFile.includes(".test.") || routeFile.includes(".spec.")) {
291
+ continue;
292
+ }
293
+ const routeFilePath = path2__default.default.join(routesDir, routeFile);
294
+ const routeFileModule = await import(url.pathToFileURL(routeFilePath).href);
295
+ const routeClass = routeFileModule.default;
296
+ if (!routeClass) {
297
+ throw new agnos.FeatureLoadError(
298
+ featureName,
299
+ routeFilePath,
300
+ `The route file "${routeFile}" in feature "${featureName}" doesn't have a default export.`
301
+ );
302
+ }
303
+ if (typeof routeClass !== "function" || !(routeClass.prototype instanceof ExpressRoute)) {
304
+ throw new agnos.FeatureLoadError(
305
+ featureName,
306
+ routeFilePath,
307
+ `The default export in "${routeFile}" (feature: "${featureName}") does not inherit from ExpressRoute.`
308
+ );
309
+ }
310
+ if (!di.diFactoryRegistry.has(routeClass)) {
311
+ if (routeClass.deps) {
312
+ di.registerInjectable(routeClass);
313
+ } else {
314
+ di.diFactoryRegistry.set(
315
+ routeClass,
316
+ () => new routeClass()
317
+ );
318
+ }
319
+ }
320
+ routeClasses.push({
321
+ meta: { name: routeClass.name },
322
+ class: routeClass
323
+ });
324
+ }
325
+ }
326
+ const featureDeps = routeClasses.reduce(
327
+ (acc, entry) => {
328
+ const depKey = entry.meta.name.charAt(0).toLowerCase() + entry.meta.name.slice(1);
329
+ acc[depKey] = entry.class;
330
+ return acc;
331
+ },
332
+ {}
333
+ );
334
+ const optionalMiddlewares = Array.isArray(imported.middlewares) ? imported.middlewares : void 0;
335
+ _FeatureRouter_decorators = [di.MakeInjectable];
336
+ class FeatureRouter extends (_a = ExpressRouter) {
337
+ constructor(deps) {
338
+ super();
339
+ this.deps = deps;
340
+ }
341
+ deps;
342
+ static deps = featureDeps;
343
+ middlewares = optionalMiddlewares;
344
+ get path() {
345
+ return routePath;
346
+ }
347
+ get routes() {
348
+ return Object.values(this.deps);
349
+ }
350
+ }
351
+ _init2 = __decoratorStart(_a);
352
+ FeatureRouter = __decorateElement(_init2, 0, "FeatureRouter", _FeatureRouter_decorators, FeatureRouter);
353
+ __runInitializers(_init2, 1, FeatureRouter);
354
+ const routerClassName = `${featureName.charAt(0).toUpperCase() + featureName.slice(1)}ExpressRouter`;
355
+ Object.defineProperty(FeatureRouter, "name", {
356
+ value: routerClassName
357
+ });
358
+ c.provideMulti(ExpressRoutersToken, FeatureRouter);
359
+ }
360
+ }
361
+
362
+ // src/core/loadExpressPresentation.ts
363
+ var loadExpressPresentation = async (container, options) => {
364
+ const port = options?.port ?? DEFAULT_PORT;
365
+ const host = options?.host;
366
+ container.provideValue(ExpressPxConfigsToken, {
367
+ port,
368
+ ...host ? { host } : {}
369
+ });
370
+ if (options?.mainDir) {
371
+ await autoLoadMainPx(container, options.mainDir);
372
+ }
373
+ if (options?.featuresDir) {
374
+ await autoLoadFeature(container, options.featuresDir);
375
+ }
376
+ container.provide(agnos.IPresentation, exports.ExpressPresentation);
377
+ };
378
+
379
+ exports.ExpressErrorHandlersToken = ExpressErrorHandlersToken;
380
+ exports.ExpressMiddlewaresToken = ExpressMiddlewaresToken;
381
+ exports.ExpressPxConfigsToken = ExpressPxConfigsToken;
382
+ exports.ExpressRoute = ExpressRoute;
383
+ exports.ExpressRouter = ExpressRouter;
384
+ exports.ExpressRoutersToken = ExpressRoutersToken;
385
+ exports.loadExpressPresentation = loadExpressPresentation;
386
+ //# sourceMappingURL=index.cjs.map
387
+ //# sourceMappingURL=index.cjs.map