@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 +18 -0
- package/README.md +107 -0
- package/dist/constants/index.d.ts +8 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/core/ExpressPresentation.d.ts +50 -0
- package/dist/core/ExpressPresentation.d.ts.map +1 -0
- package/dist/core/ExpressRoute.d.ts +24 -0
- package/dist/core/ExpressRoute.d.ts.map +1 -0
- package/dist/core/ExpressRouter.d.ts +20 -0
- package/dist/core/ExpressRouter.d.ts.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/loadExpressPresentation.d.ts +16 -0
- package/dist/core/loadExpressPresentation.d.ts.map +1 -0
- package/dist/index.cjs +387 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +373 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/public.d.ts +76 -0
- package/dist/types/public.d.ts.map +1 -0
- package/dist/utils/autoLoadFeature.d.ts +15 -0
- package/dist/utils/autoLoadFeature.d.ts.map +1 -0
- package/dist/utils/autoLoadMainPx.d.ts +9 -0
- package/dist/utils/autoLoadMainPx.d.ts.map +1 -0
- package/dist/utils/folderExists.d.ts +8 -0
- package/dist/utils/folderExists.d.ts.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +81 -0
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
|