@solid-stack/agnos-express 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/core/ExpressPresentation.d.ts +2 -1
- package/dist/core/ExpressPresentation.d.ts.map +1 -1
- package/dist/index.cjs +69 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -16
- package/dist/index.js.map +1 -1
- package/dist/types/public.d.ts +54 -1
- package/dist/types/public.d.ts.map +1 -1
- package/dist/utils/autoLoadFeature.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -52,11 +52,17 @@ import { loadExpressPresentation } from "@solid-stack/agnos-express";
|
|
|
52
52
|
async function main() {
|
|
53
53
|
const container = new Container();
|
|
54
54
|
|
|
55
|
-
// Load Express presentation layer
|
|
55
|
+
// Load Express presentation layer with optional lifecycle hooks
|
|
56
56
|
await loadExpressPresentation(container, {
|
|
57
57
|
port: 3000,
|
|
58
58
|
featuresDir: "./src/features",
|
|
59
59
|
mainDir: "./src/presentation",
|
|
60
|
+
onBeforeMiddlewares: (app) => {
|
|
61
|
+
// Direct access to Express app before global middlewares load
|
|
62
|
+
},
|
|
63
|
+
onAfterRouters: (app) => {
|
|
64
|
+
// Direct access to Express app after feature routers load
|
|
65
|
+
},
|
|
60
66
|
});
|
|
61
67
|
|
|
62
68
|
// Start Agnos application lifecycle
|
|
@@ -24,7 +24,8 @@ export declare class ExpressPresentation implements IPresentation {
|
|
|
24
24
|
};
|
|
25
25
|
constructor(deps: DepsType<typeof ExpressPresentation.deps>);
|
|
26
26
|
/**
|
|
27
|
-
* Initializes middleware, feature routers, and error handlers on the Express app
|
|
27
|
+
* Initializes middleware, feature routers, and error handlers on the Express app,
|
|
28
|
+
* executing configured onBefore and onAfter lifecycle callbacks at each stage.
|
|
28
29
|
*/
|
|
29
30
|
init(): Promise<void>;
|
|
30
31
|
private attachMiddlewares;
|
|
@@ -1 +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
|
|
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;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA+B1B;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"}
|
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,26 @@ var url = require('url');
|
|
|
9
9
|
|
|
10
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
var express__default = /*#__PURE__*/_interopDefault(express);
|
|
31
|
+
var di__namespace = /*#__PURE__*/_interopNamespace(di);
|
|
13
32
|
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
14
33
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
15
34
|
|
|
@@ -72,13 +91,33 @@ exports.ExpressPresentation = class ExpressPresentation {
|
|
|
72
91
|
logger: agnos.ILogger
|
|
73
92
|
};
|
|
74
93
|
/**
|
|
75
|
-
* Initializes middleware, feature routers, and error handlers on the Express app
|
|
94
|
+
* Initializes middleware, feature routers, and error handlers on the Express app,
|
|
95
|
+
* executing configured onBefore and onAfter lifecycle callbacks at each stage.
|
|
76
96
|
*/
|
|
77
97
|
async init() {
|
|
78
98
|
this.app.use(express__default.default.json());
|
|
99
|
+
const configs = this.deps.configs;
|
|
100
|
+
if (configs?.onBeforeMiddlewares) {
|
|
101
|
+
await configs.onBeforeMiddlewares(this.app);
|
|
102
|
+
}
|
|
79
103
|
this.attachMiddlewares();
|
|
104
|
+
if (configs?.onAfterMiddlewares) {
|
|
105
|
+
await configs.onAfterMiddlewares(this.app);
|
|
106
|
+
}
|
|
107
|
+
if (configs?.onBeforeRouters) {
|
|
108
|
+
await configs.onBeforeRouters(this.app);
|
|
109
|
+
}
|
|
80
110
|
this.attachRouters();
|
|
111
|
+
if (configs?.onAfterRouters) {
|
|
112
|
+
await configs.onAfterRouters(this.app);
|
|
113
|
+
}
|
|
114
|
+
if (configs?.onBeforeErrorHandlers) {
|
|
115
|
+
await configs.onBeforeErrorHandlers(this.app);
|
|
116
|
+
}
|
|
81
117
|
this.attachErrorHandlers();
|
|
118
|
+
if (configs?.onAfterErrorHandlers) {
|
|
119
|
+
await configs.onAfterErrorHandlers(this.app);
|
|
120
|
+
}
|
|
82
121
|
}
|
|
83
122
|
attachMiddlewares() {
|
|
84
123
|
const middlewares = this.deps.middlewares ?? [];
|
|
@@ -307,14 +346,29 @@ async function autoLoadFeature(c, featuresDir) {
|
|
|
307
346
|
`The default export in "${routeFile}" (feature: "${featureName}") does not inherit from ExpressRoute.`
|
|
308
347
|
);
|
|
309
348
|
}
|
|
310
|
-
|
|
311
|
-
|
|
349
|
+
const brand = di__namespace.INJECTABLE_BRAND ?? /* @__PURE__ */ Symbol.for("solid-stack.injectable");
|
|
350
|
+
const diFactoryRegistry2 = di__namespace.diFactoryRegistry;
|
|
351
|
+
if (diFactoryRegistry2) {
|
|
352
|
+
if (!diFactoryRegistry2.has(routeClass)) {
|
|
353
|
+
if (routeClass.deps) {
|
|
354
|
+
di.registerInjectable(routeClass);
|
|
355
|
+
} else {
|
|
356
|
+
diFactoryRegistry2.set(
|
|
357
|
+
routeClass,
|
|
358
|
+
() => new routeClass()
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} else if (!Object.prototype.hasOwnProperty.call(routeClass, brand) || routeClass[brand] !== true) {
|
|
363
|
+
if (routeClass.deps && routeClass.length > 0) {
|
|
312
364
|
di.registerInjectable(routeClass);
|
|
313
365
|
} else {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
366
|
+
Object.defineProperty(routeClass, brand, {
|
|
367
|
+
value: true,
|
|
368
|
+
writable: false,
|
|
369
|
+
enumerable: false,
|
|
370
|
+
configurable: false
|
|
371
|
+
});
|
|
318
372
|
}
|
|
319
373
|
}
|
|
320
374
|
routeClasses.push({
|
|
@@ -361,17 +415,17 @@ async function autoLoadFeature(c, featuresDir) {
|
|
|
361
415
|
|
|
362
416
|
// src/core/loadExpressPresentation.ts
|
|
363
417
|
var loadExpressPresentation = async (container, options) => {
|
|
364
|
-
const
|
|
365
|
-
const
|
|
418
|
+
const { mainDir, featuresDir, ...pxConfigs } = options ?? {};
|
|
419
|
+
const port = pxConfigs.port ?? DEFAULT_PORT;
|
|
366
420
|
container.provideValue(ExpressPxConfigsToken, {
|
|
367
|
-
|
|
368
|
-
|
|
421
|
+
...pxConfigs,
|
|
422
|
+
port
|
|
369
423
|
});
|
|
370
|
-
if (
|
|
371
|
-
await autoLoadMainPx(container,
|
|
424
|
+
if (mainDir) {
|
|
425
|
+
await autoLoadMainPx(container, mainDir);
|
|
372
426
|
}
|
|
373
|
-
if (
|
|
374
|
-
await autoLoadFeature(container,
|
|
427
|
+
if (featuresDir) {
|
|
428
|
+
await autoLoadFeature(container, featuresDir);
|
|
375
429
|
}
|
|
376
430
|
container.provide(agnos.IPresentation, exports.ExpressPresentation);
|
|
377
431
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/public.ts","../src/core/ExpressPresentation.ts","../src/utils/folderExists.ts","../src/utils/autoLoadMainPx.ts","../src/core/ExpressRouter.ts","../src/core/ExpressRoute.ts","../src/constants/index.ts","../src/utils/autoLoadFeature.ts","../src/core/loadExpressPresentation.ts"],"names":["ValueToken","MultiToken","MakeInjectable","ExpressPresentation","express","ILogger","Router","stat","fs","PresentationError","path","pathToFileURL","_init","FeatureLoadError","diFactoryRegistry","registerInjectable","IPresentation"],"mappings":"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","file":"index.cjs","sourcesContent":["import { ValueToken, MultiToken } from \"@solid-stack/di\";\nimport type { RequestHandler, ErrorRequestHandler } from \"express\";\nimport type { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport type { ExpressRouter } from \"../core/ExpressRouter.js\";\n\n/**\n * Express Presentation configuration settings.\n */\nexport interface ExpressPxConfigs {\n /**\n * The port on which the Express HTTP server should listen.\n * @default 3000\n */\n port: number;\n\n /**\n * Optional host interface to bind the server to (e.g. '0.0.0.0' or 'localhost').\n */\n host?: string;\n}\n\n/**\n * Options for configuring and bootstrapping Express presentation via loadExpressPresentation.\n */\nexport interface ExpressPresentationOptions {\n /**\n * Port number for the Express server.\n * @default 3000\n */\n port?: number;\n\n /**\n * Optional host interface to bind the server to.\n */\n host?: string;\n\n /**\n * Absolute or relative path to the main presentation directory containing global middlewares and error handlers.\n */\n mainDir?: string;\n\n /**\n * Absolute or relative path to the features directory containing modular feature folders with Express route handlers.\n */\n featuresDir?: string;\n}\n\n/**\n * Express route HTTP methods supported.\n */\nexport type ExpressRouteMethod =\n | \"get\"\n | \"post\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"options\"\n | \"head\"\n | \"all\";\n\n/**\n * Interface contract for global Express middlewares.\n */\nexport interface ExpressMiddleware {\n handler: RequestHandler;\n}\n\n/**\n * Interface contract for global Express error handlers.\n */\nexport interface ExpressErrorHandler {\n handler: ErrorRequestHandler;\n}\n\n/**\n * DI ValueToken for injecting Express Presentation configuration.\n */\nexport class ExpressPxConfigsToken extends ValueToken<ExpressPxConfigs> {}\n\n/**\n * DI MultiToken for registering and resolving Express routers.\n */\nexport class ExpressRoutersToken extends MultiToken<ExpressRouter> {}\n\n/**\n * DI MultiToken for registering and resolving global Express middlewares.\n */\nexport class ExpressMiddlewaresToken extends MultiToken<ExpressMiddleware> {}\n\n/**\n * DI MultiToken for registering and resolving global Express error handlers.\n */\nexport class ExpressErrorHandlersToken extends MultiToken<ExpressErrorHandler> {}\n","import type { Server } from \"node:http\";\nimport express, {\n Router,\n type Express,\n type RequestHandler,\n type ErrorRequestHandler,\n} from \"express\";\nimport { MakeInjectable, type DepsType } from \"@solid-stack/di\";\nimport { IPresentation, ILogger } from \"@solid-stack/agnos\";\nimport {\n ExpressPxConfigsToken,\n ExpressRoutersToken,\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\n\n/**\n * Express HTTP Presentation Layer implementation for the Agnos framework.\n *\n * Implements the IPresentation lifecycle contract:\n * - init: Configures JSON body parsing, attaches global middlewares, routers, and error handlers.\n * - run: Starts the HTTP server on the configured port and host.\n * - stop: Gracefully shuts down the HTTP server.\n */\n@MakeInjectable\nexport class ExpressPresentation implements IPresentation {\n private readonly app: Express;\n private server: Server | null = null;\n\n public static deps = {\n configs: ExpressPxConfigsToken,\n errorHandlers: ExpressErrorHandlersToken,\n middlewares: ExpressMiddlewaresToken,\n routers: ExpressRoutersToken,\n logger: ILogger,\n };\n\n constructor(public deps: DepsType<typeof ExpressPresentation.deps>) {\n this.app = express();\n }\n\n /**\n * Initializes middleware, feature routers, and error handlers on the Express app.\n */\n async init(): Promise<void> {\n this.app.use(express.json());\n\n this.attachMiddlewares();\n this.attachRouters();\n this.attachErrorHandlers();\n }\n\n private attachMiddlewares(): void {\n const middlewares = this.deps.middlewares ?? [];\n for (const middleware of middlewares) {\n this.app.use((req, res, next) => middleware.handler(req, res, next));\n }\n }\n\n private attachRouters(): void {\n const routers = this.deps.routers ?? [];\n this.deps.logger.debug(\"Registering Express routers...\", {\n count: routers.length,\n });\n\n for (const subRouter of routers) {\n const router = Router();\n\n // Attach router-level middlewares if present\n if (subRouter.middlewares && subRouter.middlewares.length > 0) {\n for (const mw of subRouter.middlewares) {\n router.use(mw);\n }\n }\n\n // Attach individual routes\n for (const route of subRouter.routes) {\n this.deps.logger.debug(\n `Registering route: [${route.method.toUpperCase()}] ${subRouter.path}${route.path}`,\n );\n\n const routeHandler: RequestHandler = (req, res, next) =>\n route.handler(req, res, next);\n\n if (route.middlewares && route.middlewares.length > 0) {\n router[route.method](route.path, ...route.middlewares, routeHandler);\n } else {\n router[route.method](route.path, routeHandler);\n }\n }\n\n this.app.use(subRouter.path, router);\n }\n }\n\n private attachErrorHandlers(): void {\n const errorHandlers = this.deps.errorHandlers ?? [];\n for (const errorHandler of errorHandlers) {\n const handler: ErrorRequestHandler = (err, req, res, next) =>\n errorHandler.handler(err, req, res, next);\n this.app.use(handler);\n }\n }\n\n /**\n * Starts the Express server listening on the configured port and host.\n */\n async run(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const { port, host } = this.deps.configs;\n\n const onListening = () => {\n this.deps.logger.info(\n `Express server listening on ${host ? `${host}:` : \"port \"}${port}`,\n );\n resolve();\n };\n\n if (host) {\n this.server = this.app.listen(port, host, onListening);\n } else {\n this.server = this.app.listen(port, onListening);\n }\n\n this.server.on(\"error\", (err: Error) => {\n this.deps.logger.error(\"Express server error:\", err);\n reject(err);\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Gracefully closes the running HTTP server.\n */\n async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n return resolve();\n }\n\n this.server.close((err) => {\n if (err) {\n this.deps.logger.error(\"Error closing Express server:\", err);\n return reject(err);\n }\n this.deps.logger.info(\"Express server stopped receiving new requests.\");\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Returns the underlying Express application instance.\n */\n public getApp(): Express {\n return this.app;\n }\n\n /**\n * Returns the active Node HTTP server instance, or null if not currently running.\n */\n public getServer(): Server | null {\n return this.server;\n }\n}\n","import { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a given filesystem path exists and is a directory.\n *\n * @param dir - The directory path to check\n * @returns True if the directory exists, false if it does not exist\n */\nexport async function folderExists(dir: string): Promise<boolean> {\n try {\n const stats = await stat(dir);\n return stats.isDirectory();\n } catch (error: any) {\n if (error?.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Container } from \"@solid-stack/di\";\nimport { PresentationError } from \"@solid-stack/agnos\";\nimport {\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\nimport { folderExists } from \"./folderExists.js\";\n\n/**\n * Loads global middlewares and error handlers from the main presentation directory.\n *\n * @param c - DI Container\n * @param mainDir - Directory containing main presentation index file\n */\nexport async function autoLoadMainPx(\n c: Container,\n mainDir: string,\n): Promise<void> {\n if (!(await folderExists(mainDir))) {\n return;\n }\n\n const mainDirContents = await fs.readdir(mainDir, { withFileTypes: true });\n\n const indexFile = mainDirContents.find(\n (f) =>\n f.isFile() &&\n (f.name === \"index.ts\" ||\n f.name === \"index.js\" ||\n f.name === \"index.mjs\" ||\n f.name === \"index.cjs\"),\n );\n\n if (!indexFile) {\n throw new PresentationError(\n `No index.ts or index.js file found in the main presentation directory: ${mainDir}`,\n { phase: \"init\" },\n );\n }\n\n const indexFilePath = path.join(mainDir, indexFile.name);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n // Register middlewares if exported\n const middlewares = imported.middlewares;\n if (middlewares && Array.isArray(middlewares)) {\n for (const middleware of middlewares) {\n c.provideMulti(ExpressMiddlewaresToken, middleware);\n }\n }\n\n // Register error handlers if exported\n const errorHandlers = imported.errorHandlers;\n if (errorHandlers && Array.isArray(errorHandlers)) {\n for (const errorHandler of errorHandlers) {\n c.provideMulti(ExpressErrorHandlersToken, errorHandler);\n }\n }\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRoute } from \"./ExpressRoute.js\";\n\n/**\n * Abstract base class for defining an Express router grouping multiple routes under a common path prefix.\n */\nexport abstract class ExpressRouter {\n /**\n * The base URL path prefix for this router (e.g. '/users' or '/api/v1/orders').\n */\n public abstract get path(): string;\n\n /**\n * The collection of routes registered under this router.\n */\n public abstract get routes(): ExpressRoute[];\n\n /**\n * Optional router-level middlewares executed for all routes in this router.\n */\n public middlewares?: RequestHandler[];\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRouteMethod } from \"../types/index.js\";\n\n/**\n * Abstract base class for defining an Express route handler within an Agnos feature.\n */\nexport abstract class ExpressRoute {\n /**\n * The HTTP method for this route (e.g. 'get', 'post', 'put', 'delete').\n */\n abstract method: ExpressRouteMethod;\n\n /**\n * The sub-path for this route relative to the feature router's base path (e.g. '/' or '/:id').\n */\n abstract path: string;\n\n /**\n * The Express request handler function.\n */\n abstract handler: RequestHandler;\n\n /**\n * Optional route-level middlewares executed before handler.\n */\n middlewares?: RequestHandler[];\n}\n","/**\n * Default configuration constants for Agnos Express presentation.\n */\n\nexport const DEFAULT_PORT = 3000;\nexport const DEFAULT_HOST = \"0.0.0.0\";\nexport const DEFAULT_FEATURE_PX_DIR = \"pxExpress\";\nexport const DEFAULT_HANDLERS_DIR = \"handlers\";\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n MakeInjectable,\n registerInjectable,\n diFactoryRegistry,\n type Container,\n type DepsType,\n} from \"@solid-stack/di\";\nimport { FeatureLoadError } from \"@solid-stack/agnos\";\nimport { ExpressRoutersToken } from \"../types/index.js\";\nimport { ExpressRouter } from \"../core/ExpressRouter.js\";\nimport { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport { folderExists } from \"./folderExists.js\";\nimport {\n DEFAULT_FEATURE_PX_DIR,\n DEFAULT_HANDLERS_DIR,\n} from \"../constants/index.js\";\n\n/**\n * Autoloads Express presentation modules from feature directories.\n *\n * For each feature directory:\n * 1. Checks for 'pxExpress' folder.\n * 2. Checks for index.ts/js exporting 'path' (string prefix).\n * 3. Scans 'handlers' folder for ExpressRoute implementations.\n * 4. Dynamically builds a FeatureRouter class registered with ExpressRoutersToken.\n *\n * @param c - DI Container\n * @param featuresDir - Path to features directory\n */\nexport async function autoLoadFeature(\n c: Container,\n featuresDir: string,\n): Promise<void> {\n if (!(await folderExists(featuresDir))) {\n return;\n }\n\n const features = await fs.readdir(featuresDir, { withFileTypes: true });\n\n for (const feature of features) {\n if (!feature.isDirectory()) continue;\n\n const featureName = feature.name;\n const pxExpressDir = path.join(\n featuresDir,\n featureName,\n DEFAULT_FEATURE_PX_DIR,\n );\n\n if (!(await folderExists(pxExpressDir))) continue;\n\n const pxExpressDirFiles = await fs.readdir(pxExpressDir);\n const indexFile = pxExpressDirFiles.find(\n (f) =>\n f === \"index.ts\" ||\n f === \"index.js\" ||\n f === \"index.mjs\" ||\n f === \"index.cjs\",\n );\n\n if (!indexFile) {\n throw new FeatureLoadError(\n featureName,\n pxExpressDir,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) does not have an index.ts or index.js file.`,\n );\n }\n\n const indexFilePath = path.join(pxExpressDir, indexFile);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n const routePath = imported.path;\n\n if (typeof routePath !== \"string\") {\n throw new FeatureLoadError(\n featureName,\n indexFilePath,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) has an index file, but it doesn't export a string named 'path'.`,\n );\n }\n\n // Load routes from handlers/\n const routeClasses: {\n meta: { name: string };\n class: new (...args: any[]) => ExpressRoute;\n }[] = [];\n\n const routesDir = path.join(pxExpressDir, DEFAULT_HANDLERS_DIR);\n\n if (await folderExists(routesDir)) {\n const routeFiles = await fs.readdir(routesDir);\n\n for (const routeFile of routeFiles) {\n if (\n !routeFile.match(/\\.(js|ts|mjs|cjs)$/) ||\n routeFile.endsWith(\".d.ts\") ||\n routeFile.includes(\".test.\") ||\n routeFile.includes(\".spec.\")\n ) {\n continue;\n }\n\n const routeFilePath = path.join(routesDir, routeFile);\n const routeFileModule = await import(pathToFileURL(routeFilePath).href);\n const routeClass = routeFileModule.default;\n\n if (!routeClass) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The route file \"${routeFile}\" in feature \"${featureName}\" doesn't have a default export.`,\n );\n }\n\n if (\n typeof routeClass !== \"function\" ||\n !(routeClass.prototype instanceof ExpressRoute)\n ) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The default export in \"${routeFile}\" (feature: \"${featureName}\") does not inherit from ExpressRoute.`,\n );\n }\n\n // Ensure DI factory is registered for the route class\n if (!diFactoryRegistry.has(routeClass)) {\n if ((routeClass as any).deps) {\n registerInjectable(routeClass as any);\n } else {\n diFactoryRegistry.set(\n routeClass,\n () => new (routeClass as any)(),\n );\n }\n }\n\n routeClasses.push({\n meta: { name: routeClass.name },\n class: routeClass,\n });\n }\n }\n\n // Build the dependency object for DI resolution\n const featureDeps = routeClasses.reduce(\n (acc, entry) => {\n const depKey =\n entry.meta.name.charAt(0).toLowerCase() + entry.meta.name.slice(1);\n acc[depKey] = entry.class;\n return acc;\n },\n {} as Record<string, new (...args: any[]) => ExpressRoute>,\n );\n\n const optionalMiddlewares = Array.isArray(imported.middlewares)\n ? imported.middlewares\n : undefined;\n\n // Create the router class dynamically\n @MakeInjectable\n class FeatureRouter extends ExpressRouter {\n public static deps = featureDeps;\n public middlewares = optionalMiddlewares;\n\n constructor(public deps: DepsType<typeof featureDeps>) {\n super();\n }\n\n public get path(): string {\n return routePath;\n }\n\n public get routes(): ExpressRoute[] {\n return Object.values(this.deps) as ExpressRoute[];\n }\n }\n\n const routerClassName = `${\n featureName.charAt(0).toUpperCase() + featureName.slice(1)\n }ExpressRouter`;\n\n Object.defineProperty(FeatureRouter, \"name\", {\n value: routerClassName,\n });\n\n c.provideMulti(ExpressRoutersToken, FeatureRouter);\n }\n}\n","import type { Container } from \"@solid-stack/di\";\nimport { IPresentation } from \"@solid-stack/agnos\";\nimport { ExpressPresentation } from \"./ExpressPresentation.js\";\nimport {\n ExpressPxConfigsToken,\n type ExpressPresentationOptions,\n} from \"../types/index.js\";\nimport { autoLoadMainPx } from \"../utils/autoLoadMainPx.js\";\nimport { autoLoadFeature } from \"../utils/autoLoadFeature.js\";\nimport { DEFAULT_PORT } from \"../constants/index.js\";\n\n/**\n * Bootstraps and registers the Express presentation layer in the DI container.\n *\n * Configures:\n * 1. ExpressPxConfigsToken with port and optional host.\n * 2. Autoloads global middlewares and error handlers from mainDir if specified.\n * 3. Autoloads feature routes from featuresDir if specified.\n * 4. Binds IPresentation to ExpressPresentation.\n *\n * @param container - The @solid-stack/di Container instance.\n * @param options - Presentation bootstrap options.\n */\nexport const loadExpressPresentation = async (\n container: Container,\n options?: ExpressPresentationOptions,\n): Promise<void> => {\n const port = options?.port ?? DEFAULT_PORT;\n const host = options?.host;\n\n container.provideValue(ExpressPxConfigsToken, {\n port,\n ...(host ? { host } : {}),\n });\n\n if (options?.mainDir) {\n await autoLoadMainPx(container, options.mainDir);\n }\n\n if (options?.featuresDir) {\n await autoLoadFeature(container, options.featuresDir);\n }\n\n container.provide(IPresentation, ExpressPresentation);\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/public.ts","../src/core/ExpressPresentation.ts","../src/utils/folderExists.ts","../src/utils/autoLoadMainPx.ts","../src/core/ExpressRouter.ts","../src/core/ExpressRoute.ts","../src/constants/index.ts","../src/utils/autoLoadFeature.ts","../src/core/loadExpressPresentation.ts"],"names":["ValueToken","MultiToken","MakeInjectable","ExpressPresentation","express","ILogger","Router","stat","fs","PresentationError","path","pathToFileURL","_init","FeatureLoadError","di","diFactoryRegistry","registerInjectable","IPresentation"],"mappings":"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","file":"index.cjs","sourcesContent":["import { ValueToken, MultiToken } from \"@solid-stack/di\";\nimport type { RequestHandler, ErrorRequestHandler, Express } from \"express\";\nimport type { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport type { ExpressRouter } from \"../core/ExpressRouter.js\";\n\n/**\n * Lifecycle callback function invoked with the active Express application instance.\n * Allows consumers to perform custom configuration or register custom routes/middlewares on the Express app.\n */\nexport type ExpressAppCallback = (app: Express) => void | Promise<void>;\n\n/**\n * Express Presentation configuration settings.\n */\nexport interface ExpressPxConfigs {\n /**\n * The port on which the Express HTTP server should listen.\n * @default 3000\n */\n port: number;\n\n /**\n * Optional host interface to bind the server to (e.g. '0.0.0.0' or 'localhost').\n */\n host?: string;\n\n /**\n * Optional callback executed before global middlewares are attached.\n */\n onBeforeMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed after global middlewares are attached.\n */\n onAfterMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed before routers are attached.\n */\n onBeforeRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed after routers are attached.\n */\n onAfterRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed before error handlers are attached.\n */\n onBeforeErrorHandlers?: ExpressAppCallback;\n\n /**\n * Optional callback executed after error handlers are attached.\n */\n onAfterErrorHandlers?: ExpressAppCallback;\n}\n\n/**\n * Options for configuring and bootstrapping Express presentation via loadExpressPresentation.\n */\nexport interface ExpressPresentationOptions {\n /**\n * Port number for the Express server.\n * @default 3000\n */\n port?: number;\n\n /**\n * Optional host interface to bind the server to.\n */\n host?: string;\n\n /**\n * Absolute or relative path to the main presentation directory containing global middlewares and error handlers.\n */\n mainDir?: string;\n\n /**\n * Absolute or relative path to the features directory containing modular feature folders with Express route handlers.\n */\n featuresDir?: string;\n\n /**\n * Optional callback executed before global middlewares are attached.\n */\n onBeforeMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed after global middlewares are attached.\n */\n onAfterMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed before routers are attached.\n */\n onBeforeRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed after routers are attached.\n */\n onAfterRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed before error handlers are attached.\n */\n onBeforeErrorHandlers?: ExpressAppCallback;\n\n /**\n * Optional callback executed after error handlers are attached.\n */\n onAfterErrorHandlers?: ExpressAppCallback;\n}\n\n/**\n * Express route HTTP methods supported.\n */\nexport type ExpressRouteMethod =\n | \"get\"\n | \"post\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"options\"\n | \"head\"\n | \"all\";\n\n/**\n * Interface contract for global Express middlewares.\n */\nexport interface ExpressMiddleware {\n handler: RequestHandler;\n}\n\n/**\n * Interface contract for global Express error handlers.\n */\nexport interface ExpressErrorHandler {\n handler: ErrorRequestHandler;\n}\n\n/**\n * DI ValueToken for injecting Express Presentation configuration.\n */\nexport class ExpressPxConfigsToken extends ValueToken<ExpressPxConfigs> {}\n\n/**\n * DI MultiToken for registering and resolving Express routers.\n */\nexport class ExpressRoutersToken extends MultiToken<ExpressRouter> {}\n\n/**\n * DI MultiToken for registering and resolving global Express middlewares.\n */\nexport class ExpressMiddlewaresToken extends MultiToken<ExpressMiddleware> {}\n\n/**\n * DI MultiToken for registering and resolving global Express error handlers.\n */\nexport class ExpressErrorHandlersToken extends MultiToken<ExpressErrorHandler> {}\n","import type { Server } from \"node:http\";\nimport express, {\n Router,\n type Express,\n type RequestHandler,\n type ErrorRequestHandler,\n} from \"express\";\nimport { MakeInjectable, type DepsType } from \"@solid-stack/di\";\nimport { IPresentation, ILogger } from \"@solid-stack/agnos\";\nimport {\n ExpressPxConfigsToken,\n ExpressRoutersToken,\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\n\n/**\n * Express HTTP Presentation Layer implementation for the Agnos framework.\n *\n * Implements the IPresentation lifecycle contract:\n * - init: Configures JSON body parsing, attaches global middlewares, routers, and error handlers.\n * - run: Starts the HTTP server on the configured port and host.\n * - stop: Gracefully shuts down the HTTP server.\n */\n@MakeInjectable\nexport class ExpressPresentation implements IPresentation {\n private readonly app: Express;\n private server: Server | null = null;\n\n public static deps = {\n configs: ExpressPxConfigsToken,\n errorHandlers: ExpressErrorHandlersToken,\n middlewares: ExpressMiddlewaresToken,\n routers: ExpressRoutersToken,\n logger: ILogger,\n };\n\n constructor(public deps: DepsType<typeof ExpressPresentation.deps>) {\n this.app = express();\n }\n\n /**\n * Initializes middleware, feature routers, and error handlers on the Express app,\n * executing configured onBefore and onAfter lifecycle callbacks at each stage.\n */\n async init(): Promise<void> {\n this.app.use(express.json());\n\n const configs = this.deps.configs;\n\n // 1. Middlewares stage\n if (configs?.onBeforeMiddlewares) {\n await configs.onBeforeMiddlewares(this.app);\n }\n this.attachMiddlewares();\n if (configs?.onAfterMiddlewares) {\n await configs.onAfterMiddlewares(this.app);\n }\n\n // 2. Routers stage\n if (configs?.onBeforeRouters) {\n await configs.onBeforeRouters(this.app);\n }\n this.attachRouters();\n if (configs?.onAfterRouters) {\n await configs.onAfterRouters(this.app);\n }\n\n // 3. Error handlers stage\n if (configs?.onBeforeErrorHandlers) {\n await configs.onBeforeErrorHandlers(this.app);\n }\n this.attachErrorHandlers();\n if (configs?.onAfterErrorHandlers) {\n await configs.onAfterErrorHandlers(this.app);\n }\n }\n\n private attachMiddlewares(): void {\n const middlewares = this.deps.middlewares ?? [];\n for (const middleware of middlewares) {\n this.app.use((req, res, next) => middleware.handler(req, res, next));\n }\n }\n\n private attachRouters(): void {\n const routers = this.deps.routers ?? [];\n this.deps.logger.debug(\"Registering Express routers...\", {\n count: routers.length,\n });\n\n for (const subRouter of routers) {\n const router = Router();\n\n // Attach router-level middlewares if present\n if (subRouter.middlewares && subRouter.middlewares.length > 0) {\n for (const mw of subRouter.middlewares) {\n router.use(mw);\n }\n }\n\n // Attach individual routes\n for (const route of subRouter.routes) {\n this.deps.logger.debug(\n `Registering route: [${route.method.toUpperCase()}] ${subRouter.path}${route.path}`,\n );\n\n const routeHandler: RequestHandler = (req, res, next) =>\n route.handler(req, res, next);\n\n if (route.middlewares && route.middlewares.length > 0) {\n router[route.method](route.path, ...route.middlewares, routeHandler);\n } else {\n router[route.method](route.path, routeHandler);\n }\n }\n\n this.app.use(subRouter.path, router);\n }\n }\n\n private attachErrorHandlers(): void {\n const errorHandlers = this.deps.errorHandlers ?? [];\n for (const errorHandler of errorHandlers) {\n const handler: ErrorRequestHandler = (err, req, res, next) =>\n errorHandler.handler(err, req, res, next);\n this.app.use(handler);\n }\n }\n\n /**\n * Starts the Express server listening on the configured port and host.\n */\n async run(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const { port, host } = this.deps.configs;\n\n const onListening = () => {\n this.deps.logger.info(\n `Express server listening on ${host ? `${host}:` : \"port \"}${port}`,\n );\n resolve();\n };\n\n if (host) {\n this.server = this.app.listen(port, host, onListening);\n } else {\n this.server = this.app.listen(port, onListening);\n }\n\n this.server.on(\"error\", (err: Error) => {\n this.deps.logger.error(\"Express server error:\", err);\n reject(err);\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Gracefully closes the running HTTP server.\n */\n async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n return resolve();\n }\n\n this.server.close((err) => {\n if (err) {\n this.deps.logger.error(\"Error closing Express server:\", err);\n return reject(err);\n }\n this.deps.logger.info(\"Express server stopped receiving new requests.\");\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Returns the underlying Express application instance.\n */\n public getApp(): Express {\n return this.app;\n }\n\n /**\n * Returns the active Node HTTP server instance, or null if not currently running.\n */\n public getServer(): Server | null {\n return this.server;\n }\n}\n","import { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a given filesystem path exists and is a directory.\n *\n * @param dir - The directory path to check\n * @returns True if the directory exists, false if it does not exist\n */\nexport async function folderExists(dir: string): Promise<boolean> {\n try {\n const stats = await stat(dir);\n return stats.isDirectory();\n } catch (error: any) {\n if (error?.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Container } from \"@solid-stack/di\";\nimport { PresentationError } from \"@solid-stack/agnos\";\nimport {\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\nimport { folderExists } from \"./folderExists.js\";\n\n/**\n * Loads global middlewares and error handlers from the main presentation directory.\n *\n * @param c - DI Container\n * @param mainDir - Directory containing main presentation index file\n */\nexport async function autoLoadMainPx(\n c: Container,\n mainDir: string,\n): Promise<void> {\n if (!(await folderExists(mainDir))) {\n return;\n }\n\n const mainDirContents = await fs.readdir(mainDir, { withFileTypes: true });\n\n const indexFile = mainDirContents.find(\n (f) =>\n f.isFile() &&\n (f.name === \"index.ts\" ||\n f.name === \"index.js\" ||\n f.name === \"index.mjs\" ||\n f.name === \"index.cjs\"),\n );\n\n if (!indexFile) {\n throw new PresentationError(\n `No index.ts or index.js file found in the main presentation directory: ${mainDir}`,\n { phase: \"init\" },\n );\n }\n\n const indexFilePath = path.join(mainDir, indexFile.name);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n // Register middlewares if exported\n const middlewares = imported.middlewares;\n if (middlewares && Array.isArray(middlewares)) {\n for (const middleware of middlewares) {\n c.provideMulti(ExpressMiddlewaresToken, middleware);\n }\n }\n\n // Register error handlers if exported\n const errorHandlers = imported.errorHandlers;\n if (errorHandlers && Array.isArray(errorHandlers)) {\n for (const errorHandler of errorHandlers) {\n c.provideMulti(ExpressErrorHandlersToken, errorHandler);\n }\n }\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRoute } from \"./ExpressRoute.js\";\n\n/**\n * Abstract base class for defining an Express router grouping multiple routes under a common path prefix.\n */\nexport abstract class ExpressRouter {\n /**\n * The base URL path prefix for this router (e.g. '/users' or '/api/v1/orders').\n */\n public abstract get path(): string;\n\n /**\n * The collection of routes registered under this router.\n */\n public abstract get routes(): ExpressRoute[];\n\n /**\n * Optional router-level middlewares executed for all routes in this router.\n */\n public middlewares?: RequestHandler[];\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRouteMethod } from \"../types/index.js\";\n\n/**\n * Abstract base class for defining an Express route handler within an Agnos feature.\n */\nexport abstract class ExpressRoute {\n /**\n * The HTTP method for this route (e.g. 'get', 'post', 'put', 'delete').\n */\n abstract method: ExpressRouteMethod;\n\n /**\n * The sub-path for this route relative to the feature router's base path (e.g. '/' or '/:id').\n */\n abstract path: string;\n\n /**\n * The Express request handler function.\n */\n abstract handler: RequestHandler;\n\n /**\n * Optional route-level middlewares executed before handler.\n */\n middlewares?: RequestHandler[];\n}\n","/**\n * Default configuration constants for Agnos Express presentation.\n */\n\nexport const DEFAULT_PORT = 3000;\nexport const DEFAULT_HOST = \"0.0.0.0\";\nexport const DEFAULT_FEATURE_PX_DIR = \"pxExpress\";\nexport const DEFAULT_HANDLERS_DIR = \"handlers\";\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n MakeInjectable,\n registerInjectable,\n type Container,\n type DepsType,\n} from \"@solid-stack/di\";\nimport * as di from \"@solid-stack/di\";\nimport { FeatureLoadError } from \"@solid-stack/agnos\";\nimport { ExpressRoutersToken } from \"../types/index.js\";\nimport { ExpressRouter } from \"../core/ExpressRouter.js\";\nimport { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport { folderExists } from \"./folderExists.js\";\nimport {\n DEFAULT_FEATURE_PX_DIR,\n DEFAULT_HANDLERS_DIR,\n} from \"../constants/index.js\";\n\n/**\n * Autoloads Express presentation modules from feature directories.\n *\n * For each feature directory:\n * 1. Checks for 'pxExpress' folder.\n * 2. Checks for index.ts/js exporting 'path' (string prefix).\n * 3. Scans 'handlers' folder for ExpressRoute implementations.\n * 4. Dynamically builds a FeatureRouter class registered with ExpressRoutersToken.\n *\n * @param c - DI Container\n * @param featuresDir - Path to features directory\n */\nexport async function autoLoadFeature(\n c: Container,\n featuresDir: string,\n): Promise<void> {\n if (!(await folderExists(featuresDir))) {\n return;\n }\n\n const features = await fs.readdir(featuresDir, { withFileTypes: true });\n\n for (const feature of features) {\n if (!feature.isDirectory()) continue;\n\n const featureName = feature.name;\n const pxExpressDir = path.join(\n featuresDir,\n featureName,\n DEFAULT_FEATURE_PX_DIR,\n );\n\n if (!(await folderExists(pxExpressDir))) continue;\n\n const pxExpressDirFiles = await fs.readdir(pxExpressDir);\n const indexFile = pxExpressDirFiles.find(\n (f) =>\n f === \"index.ts\" ||\n f === \"index.js\" ||\n f === \"index.mjs\" ||\n f === \"index.cjs\",\n );\n\n if (!indexFile) {\n throw new FeatureLoadError(\n featureName,\n pxExpressDir,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) does not have an index.ts or index.js file.`,\n );\n }\n\n const indexFilePath = path.join(pxExpressDir, indexFile);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n const routePath = imported.path;\n\n if (typeof routePath !== \"string\") {\n throw new FeatureLoadError(\n featureName,\n indexFilePath,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) has an index file, but it doesn't export a string named 'path'.`,\n );\n }\n\n // Load routes from handlers/\n const routeClasses: {\n meta: { name: string };\n class: new (...args: any[]) => ExpressRoute;\n }[] = [];\n\n const routesDir = path.join(pxExpressDir, DEFAULT_HANDLERS_DIR);\n\n if (await folderExists(routesDir)) {\n const routeFiles = await fs.readdir(routesDir);\n\n for (const routeFile of routeFiles) {\n if (\n !routeFile.match(/\\.(js|ts|mjs|cjs)$/) ||\n routeFile.endsWith(\".d.ts\") ||\n routeFile.includes(\".test.\") ||\n routeFile.includes(\".spec.\")\n ) {\n continue;\n }\n\n const routeFilePath = path.join(routesDir, routeFile);\n const routeFileModule = await import(pathToFileURL(routeFilePath).href);\n const routeClass = routeFileModule.default;\n\n if (!routeClass) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The route file \"${routeFile}\" in feature \"${featureName}\" doesn't have a default export.`,\n );\n }\n\n if (\n typeof routeClass !== \"function\" ||\n !(routeClass.prototype instanceof ExpressRoute)\n ) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The default export in \"${routeFile}\" (feature: \"${featureName}\") does not inherit from ExpressRoute.`,\n );\n }\n\n // Ensure DI registration for the route class\n const brand =\n (di as any).INJECTABLE_BRAND ?? Symbol.for(\"solid-stack.injectable\");\n const diFactoryRegistry = (di as any).diFactoryRegistry;\n\n if (diFactoryRegistry) {\n if (!diFactoryRegistry.has(routeClass)) {\n if ((routeClass as any).deps) {\n registerInjectable(routeClass as any);\n } else {\n diFactoryRegistry.set(\n routeClass,\n () => new (routeClass as any)(),\n );\n }\n }\n } else if (\n !Object.prototype.hasOwnProperty.call(routeClass, brand) ||\n (routeClass as any)[brand] !== true\n ) {\n if ((routeClass as any).deps && (routeClass as any).length > 0) {\n registerInjectable(routeClass as any);\n } else {\n Object.defineProperty(routeClass, brand, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n }\n\n routeClasses.push({\n meta: { name: routeClass.name },\n class: routeClass,\n });\n }\n }\n\n // Build the dependency object for DI resolution\n const featureDeps = routeClasses.reduce(\n (acc, entry) => {\n const depKey =\n entry.meta.name.charAt(0).toLowerCase() + entry.meta.name.slice(1);\n acc[depKey] = entry.class;\n return acc;\n },\n {} as Record<string, new (...args: any[]) => ExpressRoute>,\n );\n\n const optionalMiddlewares = Array.isArray(imported.middlewares)\n ? imported.middlewares\n : undefined;\n\n // Create the router class dynamically\n @MakeInjectable\n class FeatureRouter extends ExpressRouter {\n public static deps = featureDeps;\n public middlewares = optionalMiddlewares;\n\n constructor(public deps: DepsType<typeof featureDeps>) {\n super();\n }\n\n public get path(): string {\n return routePath;\n }\n\n public get routes(): ExpressRoute[] {\n return Object.values(this.deps) as ExpressRoute[];\n }\n }\n\n const routerClassName = `${\n featureName.charAt(0).toUpperCase() + featureName.slice(1)\n }ExpressRouter`;\n\n Object.defineProperty(FeatureRouter, \"name\", {\n value: routerClassName,\n });\n\n c.provideMulti(ExpressRoutersToken, FeatureRouter);\n }\n}\n","import type { Container } from \"@solid-stack/di\";\nimport { IPresentation } from \"@solid-stack/agnos\";\nimport { ExpressPresentation } from \"./ExpressPresentation.js\";\nimport {\n ExpressPxConfigsToken,\n type ExpressPresentationOptions,\n} from \"../types/index.js\";\nimport { autoLoadMainPx } from \"../utils/autoLoadMainPx.js\";\nimport { autoLoadFeature } from \"../utils/autoLoadFeature.js\";\nimport { DEFAULT_PORT } from \"../constants/index.js\";\n\n/**\n * Bootstraps and registers the Express presentation layer in the DI container.\n *\n * Configures:\n * 1. ExpressPxConfigsToken with port and optional host.\n * 2. Autoloads global middlewares and error handlers from mainDir if specified.\n * 3. Autoloads feature routes from featuresDir if specified.\n * 4. Binds IPresentation to ExpressPresentation.\n *\n * @param container - The @solid-stack/di Container instance.\n * @param options - Presentation bootstrap options.\n */\nexport const loadExpressPresentation = async (\n container: Container,\n options?: ExpressPresentationOptions,\n): Promise<void> => {\n const { mainDir, featuresDir, ...pxConfigs } = options ?? {};\n const port = pxConfigs.port ?? DEFAULT_PORT;\n\n container.provideValue(ExpressPxConfigsToken, {\n ...pxConfigs,\n port,\n });\n\n if (mainDir) {\n await autoLoadMainPx(container, mainDir);\n }\n\n if (featuresDir) {\n await autoLoadFeature(container, featuresDir);\n }\n\n container.provide(IPresentation, ExpressPresentation);\n};\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Express presentation adapter for the Agnos application architecture framework.
|
|
5
5
|
*/
|
|
6
6
|
export { ExpressPresentation, loadExpressPresentation, ExpressRoute, ExpressRouter, } from "./core/index.js";
|
|
7
|
-
export { ExpressPxConfigsToken, ExpressRoutersToken, ExpressMiddlewaresToken, ExpressErrorHandlersToken, type ExpressPxConfigs, type ExpressPresentationOptions, type ExpressMiddleware, type ExpressErrorHandler, type ExpressRouteMethod, } from "./types/index.js";
|
|
7
|
+
export { ExpressPxConfigsToken, ExpressRoutersToken, ExpressMiddlewaresToken, ExpressErrorHandlersToken, type ExpressAppCallback, type ExpressPxConfigs, type ExpressPresentationOptions, type ExpressMiddleware, type ExpressErrorHandler, type ExpressRouteMethod, } from "./types/index.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express, { Router } from 'express';
|
|
2
|
-
import
|
|
2
|
+
import * as di from '@solid-stack/di';
|
|
3
|
+
import { ValueToken, MultiToken, registerInjectable, MakeInjectable } from '@solid-stack/di';
|
|
3
4
|
import { ILogger, IPresentation, PresentationError, FeatureLoadError } from '@solid-stack/agnos';
|
|
4
5
|
import fs2, { stat } from 'fs/promises';
|
|
5
6
|
import path2 from 'path';
|
|
@@ -64,13 +65,33 @@ var ExpressPresentation = class {
|
|
|
64
65
|
logger: ILogger
|
|
65
66
|
};
|
|
66
67
|
/**
|
|
67
|
-
* Initializes middleware, feature routers, and error handlers on the Express app
|
|
68
|
+
* Initializes middleware, feature routers, and error handlers on the Express app,
|
|
69
|
+
* executing configured onBefore and onAfter lifecycle callbacks at each stage.
|
|
68
70
|
*/
|
|
69
71
|
async init() {
|
|
70
72
|
this.app.use(express.json());
|
|
73
|
+
const configs = this.deps.configs;
|
|
74
|
+
if (configs?.onBeforeMiddlewares) {
|
|
75
|
+
await configs.onBeforeMiddlewares(this.app);
|
|
76
|
+
}
|
|
71
77
|
this.attachMiddlewares();
|
|
78
|
+
if (configs?.onAfterMiddlewares) {
|
|
79
|
+
await configs.onAfterMiddlewares(this.app);
|
|
80
|
+
}
|
|
81
|
+
if (configs?.onBeforeRouters) {
|
|
82
|
+
await configs.onBeforeRouters(this.app);
|
|
83
|
+
}
|
|
72
84
|
this.attachRouters();
|
|
85
|
+
if (configs?.onAfterRouters) {
|
|
86
|
+
await configs.onAfterRouters(this.app);
|
|
87
|
+
}
|
|
88
|
+
if (configs?.onBeforeErrorHandlers) {
|
|
89
|
+
await configs.onBeforeErrorHandlers(this.app);
|
|
90
|
+
}
|
|
73
91
|
this.attachErrorHandlers();
|
|
92
|
+
if (configs?.onAfterErrorHandlers) {
|
|
93
|
+
await configs.onAfterErrorHandlers(this.app);
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
attachMiddlewares() {
|
|
76
97
|
const middlewares = this.deps.middlewares ?? [];
|
|
@@ -299,14 +320,29 @@ async function autoLoadFeature(c, featuresDir) {
|
|
|
299
320
|
`The default export in "${routeFile}" (feature: "${featureName}") does not inherit from ExpressRoute.`
|
|
300
321
|
);
|
|
301
322
|
}
|
|
302
|
-
|
|
303
|
-
|
|
323
|
+
const brand = di.INJECTABLE_BRAND ?? /* @__PURE__ */ Symbol.for("solid-stack.injectable");
|
|
324
|
+
const diFactoryRegistry2 = di.diFactoryRegistry;
|
|
325
|
+
if (diFactoryRegistry2) {
|
|
326
|
+
if (!diFactoryRegistry2.has(routeClass)) {
|
|
327
|
+
if (routeClass.deps) {
|
|
328
|
+
registerInjectable(routeClass);
|
|
329
|
+
} else {
|
|
330
|
+
diFactoryRegistry2.set(
|
|
331
|
+
routeClass,
|
|
332
|
+
() => new routeClass()
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else if (!Object.prototype.hasOwnProperty.call(routeClass, brand) || routeClass[brand] !== true) {
|
|
337
|
+
if (routeClass.deps && routeClass.length > 0) {
|
|
304
338
|
registerInjectable(routeClass);
|
|
305
339
|
} else {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
340
|
+
Object.defineProperty(routeClass, brand, {
|
|
341
|
+
value: true,
|
|
342
|
+
writable: false,
|
|
343
|
+
enumerable: false,
|
|
344
|
+
configurable: false
|
|
345
|
+
});
|
|
310
346
|
}
|
|
311
347
|
}
|
|
312
348
|
routeClasses.push({
|
|
@@ -353,17 +389,17 @@ async function autoLoadFeature(c, featuresDir) {
|
|
|
353
389
|
|
|
354
390
|
// src/core/loadExpressPresentation.ts
|
|
355
391
|
var loadExpressPresentation = async (container, options) => {
|
|
356
|
-
const
|
|
357
|
-
const
|
|
392
|
+
const { mainDir, featuresDir, ...pxConfigs } = options ?? {};
|
|
393
|
+
const port = pxConfigs.port ?? DEFAULT_PORT;
|
|
358
394
|
container.provideValue(ExpressPxConfigsToken, {
|
|
359
|
-
|
|
360
|
-
|
|
395
|
+
...pxConfigs,
|
|
396
|
+
port
|
|
361
397
|
});
|
|
362
|
-
if (
|
|
363
|
-
await autoLoadMainPx(container,
|
|
398
|
+
if (mainDir) {
|
|
399
|
+
await autoLoadMainPx(container, mainDir);
|
|
364
400
|
}
|
|
365
|
-
if (
|
|
366
|
-
await autoLoadFeature(container,
|
|
401
|
+
if (featuresDir) {
|
|
402
|
+
await autoLoadFeature(container, featuresDir);
|
|
367
403
|
}
|
|
368
404
|
container.provide(IPresentation, ExpressPresentation);
|
|
369
405
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/public.ts","../src/core/ExpressPresentation.ts","../src/utils/folderExists.ts","../src/utils/autoLoadMainPx.ts","../src/core/ExpressRouter.ts","../src/core/ExpressRoute.ts","../src/constants/index.ts","../src/utils/autoLoadFeature.ts","../src/core/loadExpressPresentation.ts"],"names":["fs","path","_init","pathToFileURL","MakeInjectable","IPresentation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EO,IAAM,qBAAA,GAAN,cAAoC,UAAA,CAA6B;AAAC;AAKlE,IAAM,mBAAA,GAAN,cAAkC,UAAA,CAA0B;AAAC;AAK7D,IAAM,uBAAA,GAAN,cAAsC,UAAA,CAA8B;AAAC;AAKrE,IAAM,yBAAA,GAAN,cAAwC,UAAA,CAAgC;AAAC;;;AC5FhF,IAAA,+BAAA,EAAA,KAAA;AAwBA,+BAAA,GAAA,CAAC,cAAA,CAAA;AACM,IAAM,sBAAN,MAAmD;AAAA,EAYxD,YAAmB,IAAA,EAAiD;AAAjD,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACjB,IAAA,IAAA,CAAK,MAAM,OAAA,EAAQ;AAAA,EACrB;AAAA,EAFmB,IAAA;AAAA,EAXF,GAAA;AAAA,EACT,MAAA,GAAwB,IAAA;AAAA,EAEhC,OAAc,IAAA,GAAO;AAAA,IACnB,OAAA,EAAS,qBAAA;AAAA,IACT,aAAA,EAAe,yBAAA;AAAA,IACf,WAAA,EAAa,uBAAA;AAAA,IACb,OAAA,EAAS,mBAAA;AAAA,IACT,MAAA,EAAQ;AAAA,GACV;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,OAAA,CAAQ,IAAA,EAAM,CAAA;AAE3B,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,IAAA,CAAK,mBAAA,EAAoB;AAAA,EAC3B;AAAA,EAEQ,iBAAA,GAA0B;AAChC,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,IAAA,CAAK,WAAA,IAAe,EAAC;AAC9C,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,MAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,CAAC,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS,UAAA,CAAW,OAAA,CAAQ,GAAA,EAAK,GAAA,EAAK,IAAI,CAAC,CAAA;AAAA,IACrE;AAAA,EACF;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,OAAA,IAAW,EAAC;AACtC,IAAA,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,gCAAA,EAAkC;AAAA,MACvD,OAAO,OAAA,CAAQ;AAAA,KAChB,CAAA;AAED,IAAA,KAAA,MAAW,aAAa,OAAA,EAAS;AAC/B,MAAA,MAAM,SAAS,MAAA,EAAO;AAGtB,MAAA,IAAI,SAAA,CAAU,WAAA,IAAe,SAAA,CAAU,WAAA,CAAY,SAAS,CAAA,EAAG;AAC7D,QAAA,KAAA,MAAW,EAAA,IAAM,UAAU,WAAA,EAAa;AACtC,UAAA,MAAA,CAAO,IAAI,EAAE,CAAA;AAAA,QACf;AAAA,MACF;AAGA,MAAA,KAAA,MAAW,KAAA,IAAS,UAAU,MAAA,EAAQ;AACpC,QAAA,IAAA,CAAK,KAAK,MAAA,CAAO,KAAA;AAAA,UACf,CAAA,oBAAA,EAAuB,KAAA,CAAM,MAAA,CAAO,WAAA,EAAa,KAAK,SAAA,CAAU,IAAI,CAAA,EAAG,KAAA,CAAM,IAAI,CAAA;AAAA,SACnF;AAEA,QAAA,MAAM,YAAA,GAA+B,CAAC,GAAA,EAAK,GAAA,EAAK,SAC9C,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,GAAA,EAAK,IAAI,CAAA;AAE9B,QAAA,IAAI,KAAA,CAAM,WAAA,IAAe,KAAA,CAAM,WAAA,CAAY,SAAS,CAAA,EAAG;AACrD,UAAA,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,CAAE,KAAA,CAAM,MAAM,GAAG,KAAA,CAAM,aAAa,YAAY,CAAA;AAAA,QACrE,CAAA,MAAO;AACL,UAAA,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,CAAE,KAAA,CAAM,MAAM,YAAY,CAAA;AAAA,QAC/C;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,SAAA,CAAU,IAAA,EAAM,MAAM,CAAA;AAAA,IACrC;AAAA,EACF;AAAA,EAEQ,mBAAA,GAA4B;AAClC,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,aAAA,IAAiB,EAAC;AAClD,IAAA,KAAA,MAAW,gBAAgB,aAAA,EAAe;AACxC,MAAA,MAAM,OAAA,GAA+B,CAAC,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,IAAA,KACnD,YAAA,CAAa,OAAA,CAAQ,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,IAAI,CAAA;AAC1C,MAAA,IAAA,CAAK,GAAA,CAAI,IAAI,OAAO,CAAA;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,GAAA,GAAqB;AACzB,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,IAAI;AACF,QAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAK,GAAI,KAAK,IAAA,CAAK,OAAA;AAEjC,QAAA,MAAM,cAAc,MAAM;AACxB,UAAA,IAAA,CAAK,KAAK,MAAA,CAAO,IAAA;AAAA,YACf,+BAA+B,IAAA,GAAO,CAAA,EAAG,IAAI,CAAA,CAAA,CAAA,GAAM,OAAO,GAAG,IAAI,CAAA;AAAA,WACnE;AACA,UAAA,OAAA,EAAQ;AAAA,QACV,CAAA;AAEA,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,IAAA,CAAK,SAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,IAAA,EAAM,MAAM,WAAW,CAAA;AAAA,QACvD,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,MAAM,WAAW,CAAA;AAAA,QACjD;AAEA,QAAA,IAAA,CAAK,MAAA,CAAO,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAe;AACtC,UAAA,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,uBAAA,EAAyB,GAAG,CAAA;AACnD,UAAA,MAAA,CAAO,GAAG,CAAA;AAAA,QACZ,CAAC,CAAA;AAAA,MACH,SAAS,GAAA,EAAK;AACZ,QAAA,MAAA,CAAO,GAAG,CAAA;AAAA,MACZ;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAA,GAAsB;AAC1B,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAChB,QAAA,OAAO,OAAA,EAAQ;AAAA,MACjB;AAEA,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAC,GAAA,KAAQ;AACzB,QAAA,IAAI,GAAA,EAAK;AACP,UAAA,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,+BAAA,EAAiC,GAAG,CAAA;AAC3D,UAAA,OAAO,OAAO,GAAG,CAAA;AAAA,QACnB;AACA,QAAA,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,gDAAgD,CAAA;AACtE,QAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,MAAA,GAAkB;AACvB,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKO,SAAA,GAA2B;AAChC,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAhJO,KAAA,GAAA,gBAAA,CAAA,IAAA,CAAA;AAAM,mBAAA,GAAN,mDADP,+BAAA,EACa,mBAAA,CAAA;AAAN,iBAAA,CAAA,KAAA,EAAA,CAAA,EAAM,mBAAA,CAAA;ACjBb,eAAsB,aAAa,GAAA,EAA+B;AAChE,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,GAAG,CAAA;AAC5B,IAAA,OAAO,MAAM,WAAA,EAAY;AAAA,EAC3B,SAAS,KAAA,EAAY;AACnB,IAAA,IAAI,KAAA,EAAO,SAAS,QAAA,EAAU;AAC5B,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AACF;;;ACDA,eAAsB,cAAA,CACpB,GACA,OAAA,EACe;AACf,EAAA,IAAI,CAAE,MAAM,YAAA,CAAa,OAAO,CAAA,EAAI;AAClC,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,eAAA,GAAkB,MAAMA,GAAA,CAAG,OAAA,CAAQ,SAAS,EAAE,aAAA,EAAe,MAAM,CAAA;AAEzE,EAAA,MAAM,YAAY,eAAA,CAAgB,IAAA;AAAA,IAChC,CAAC,CAAA,KACC,CAAA,CAAE,MAAA,OACD,CAAA,CAAE,IAAA,KAAS,UAAA,IACV,CAAA,CAAE,SAAS,UAAA,IACX,CAAA,CAAE,IAAA,KAAS,WAAA,IACX,EAAE,IAAA,KAAS,WAAA;AAAA,GACjB;AAEA,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,iBAAA;AAAA,MACR,0EAA0E,OAAO,CAAA,CAAA;AAAA,MACjF,EAAE,OAAO,MAAA;AAAO,KAClB;AAAA,EACF;AAEA,EAAA,MAAM,aAAA,GAAgBC,KAAA,CAAK,IAAA,CAAK,OAAA,EAAS,UAAU,IAAI,CAAA;AACvD,EAAA,MAAM,QAAA,GAAW,MAAM,OAAO,aAAA,CAAc,aAAa,CAAA,CAAE,IAAA,CAAA;AAG3D,EAAA,MAAM,cAAc,QAAA,CAAS,WAAA;AAC7B,EAAA,IAAI,WAAA,IAAe,KAAA,CAAM,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC7C,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,MAAA,CAAA,CAAE,YAAA,CAAa,yBAAyB,UAAU,CAAA;AAAA,IACpD;AAAA,EACF;AAGA,EAAA,MAAM,gBAAgB,QAAA,CAAS,aAAA;AAC/B,EAAA,IAAI,aAAA,IAAiB,KAAA,CAAM,OAAA,CAAQ,aAAa,CAAA,EAAG;AACjD,IAAA,KAAA,MAAW,gBAAgB,aAAA,EAAe;AACxC,MAAA,CAAA,CAAE,YAAA,CAAa,2BAA2B,YAAY,CAAA;AAAA,IACxD;AAAA,EACF;AACF;;;ACvDO,IAAe,gBAAf,MAA6B;AAAA;AAAA;AAAA;AAAA,EAc3B,WAAA;AACT;;;ACfO,IAAe,eAAf,MAA4B;AAAA;AAAA;AAAA;AAAA,EAmBjC,WAAA;AACF;;;ACtBO,IAAM,YAAA,GAAe,GAAA;AAErB,IAAM,sBAAA,GAAyB,WAAA;AAC/B,IAAM,oBAAA,GAAuB,UAAA;;;ACyBpC,eAAsB,eAAA,CACpB,GACA,WAAA,EACe;AAnCjB,EAAA,IAAA,yBAAA,EAAAC,MAAAA,EAAA,EAAA;AAoCE,EAAA,IAAI,CAAE,MAAM,YAAA,CAAa,WAAW,CAAA,EAAI;AACtC,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,MAAMF,GAAAA,CAAG,OAAA,CAAQ,aAAa,EAAE,aAAA,EAAe,MAAM,CAAA;AAEtE,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,EAAY,EAAG;AAE5B,IAAA,MAAM,cAAc,OAAA,CAAQ,IAAA;AAC5B,IAAA,MAAM,eAAeC,KAAAA,CAAK,IAAA;AAAA,MACxB,WAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,IAAI,CAAE,MAAM,YAAA,CAAa,YAAY,CAAA,EAAI;AAEzC,IAAA,MAAM,iBAAA,GAAoB,MAAMD,GAAAA,CAAG,OAAA,CAAQ,YAAY,CAAA;AACvD,IAAA,MAAM,YAAY,iBAAA,CAAkB,IAAA;AAAA,MAClC,CAAC,MACC,CAAA,KAAM,UAAA,IACN,MAAM,UAAA,IACN,CAAA,KAAM,eACN,CAAA,KAAM;AAAA,KACV;AAEA,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,IAAI,gBAAA;AAAA,QACR,WAAA;AAAA,QACA,YAAA;AAAA,QACA,CAAA,qCAAA,EAAwC,WAAW,CAAA,GAAA,EAAM,YAAY,CAAA,6CAAA;AAAA,OACvE;AAAA,IACF;AAEA,IAAA,MAAM,aAAA,GAAgBC,KAAAA,CAAK,IAAA,CAAK,YAAA,EAAc,SAAS,CAAA;AACvD,IAAA,MAAM,QAAA,GAAW,MAAM,OAAOE,aAAAA,CAAc,aAAa,CAAA,CAAE,IAAA,CAAA;AAE3D,IAAA,MAAM,YAAY,QAAA,CAAS,IAAA;AAE3B,IAAA,IAAI,OAAO,cAAc,QAAA,EAAU;AACjC,MAAA,MAAM,IAAI,gBAAA;AAAA,QACR,WAAA;AAAA,QACA,aAAA;AAAA,QACA,CAAA,qCAAA,EAAwC,WAAW,CAAA,GAAA,EAAM,YAAY,CAAA,iEAAA;AAAA,OACvE;AAAA,IACF;AAGA,IAAA,MAAM,eAGA,EAAC;AAEP,IAAA,MAAM,SAAA,GAAYF,KAAAA,CAAK,IAAA,CAAK,YAAA,EAAc,oBAAoB,CAAA;AAE9D,IAAA,IAAI,MAAM,YAAA,CAAa,SAAS,CAAA,EAAG;AACjC,MAAA,MAAM,UAAA,GAAa,MAAMD,GAAAA,CAAG,OAAA,CAAQ,SAAS,CAAA;AAE7C,MAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,QAAA,IACE,CAAC,SAAA,CAAU,KAAA,CAAM,oBAAoB,CAAA,IACrC,UAAU,QAAA,CAAS,OAAO,CAAA,IAC1B,SAAA,CAAU,SAAS,QAAQ,CAAA,IAC3B,SAAA,CAAU,QAAA,CAAS,QAAQ,CAAA,EAC3B;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,aAAA,GAAgBC,KAAAA,CAAK,IAAA,CAAK,SAAA,EAAW,SAAS,CAAA;AACpD,QAAA,MAAM,eAAA,GAAkB,MAAM,OAAOE,aAAAA,CAAc,aAAa,CAAA,CAAE,IAAA,CAAA;AAClE,QAAA,MAAM,aAAa,eAAA,CAAgB,OAAA;AAEnC,QAAA,IAAI,CAAC,UAAA,EAAY;AACf,UAAA,MAAM,IAAI,gBAAA;AAAA,YACR,WAAA;AAAA,YACA,aAAA;AAAA,YACA,CAAA,gBAAA,EAAmB,SAAS,CAAA,cAAA,EAAiB,WAAW,CAAA,gCAAA;AAAA,WAC1D;AAAA,QACF;AAEA,QAAA,IACE,OAAO,UAAA,KAAe,UAAA,IACtB,EAAE,UAAA,CAAW,qBAAqB,YAAA,CAAA,EAClC;AACA,UAAA,MAAM,IAAI,gBAAA;AAAA,YACR,WAAA;AAAA,YACA,aAAA;AAAA,YACA,CAAA,uBAAA,EAA0B,SAAS,CAAA,aAAA,EAAgB,WAAW,CAAA,sCAAA;AAAA,WAChE;AAAA,QACF;AAGA,QAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,CAAI,UAAU,CAAA,EAAG;AACtC,UAAA,IAAK,WAAmB,IAAA,EAAM;AAC5B,YAAA,kBAAA,CAAmB,UAAiB,CAAA;AAAA,UACtC,CAAA,MAAO;AACL,YAAA,iBAAA,CAAkB,GAAA;AAAA,cAChB,UAAA;AAAA,cACA,MAAM,IAAK,UAAA;AAAmB,aAChC;AAAA,UACF;AAAA,QACF;AAEA,QAAA,YAAA,CAAa,IAAA,CAAK;AAAA,UAChB,IAAA,EAAM,EAAE,IAAA,EAAM,UAAA,CAAW,IAAA,EAAK;AAAA,UAC9B,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH;AAAA,IACF;AAGA,IAAA,MAAM,cAAc,YAAA,CAAa,MAAA;AAAA,MAC/B,CAAC,KAAK,KAAA,KAAU;AACd,QAAA,MAAM,MAAA,GACJ,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,MAAM,CAAC,CAAA;AACnE,QAAA,GAAA,CAAI,MAAM,IAAI,KAAA,CAAM,KAAA;AACpB,QAAA,OAAO,GAAA;AAAA,MACT,CAAA;AAAA,MACA;AAAC,KACH;AAEA,IAAA,MAAM,sBAAsB,KAAA,CAAM,OAAA,CAAQ,SAAS,WAAW,CAAA,GAC1D,SAAS,WAAA,GACT,MAAA;AAGJ,IAAA,yBAAA,GAAA,CAACC,cAAAA,CAAAA;AAAA,IACD,MAAM,uBAAsB,EAAA,GAAA,aAAA,EAAc;AAAA,MAIxC,YAAmB,IAAA,EAAoC;AACrD,QAAA,KAAA,EAAM;AADW,QAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,MAEnB;AAAA,MAFmB,IAAA;AAAA,MAHnB,OAAc,IAAA,GAAO,WAAA;AAAA,MACd,WAAA,GAAc,mBAAA;AAAA,MAMrB,IAAW,IAAA,GAAe;AACxB,QAAA,OAAO,SAAA;AAAA,MACT;AAAA,MAEA,IAAW,MAAA,GAAyB;AAClC,QAAA,OAAO,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAAA,MAChC;AAAA;AAdF,IAAAF,MAAAA,GAAA,gBAAA,CAAA,EAAA,CAAA;AAAM,IAAA,aAAA,GAAN,iBAAA,CAAAA,MAAAA,EAAA,CAAA,EAAA,eAAA,EADA,yBAAA,EACM,aAAA,CAAA;AAAN,IAAA,iBAAA,CAAAA,QAAA,CAAA,EAAM,aAAA,CAAA;AAiBN,IAAA,MAAM,eAAA,GAAkB,CAAA,EACtB,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,WAAA,CAAY,KAAA,CAAM,CAAC,CAC3D,CAAA,aAAA,CAAA;AAEA,IAAA,MAAA,CAAO,cAAA,CAAe,eAAe,MAAA,EAAQ;AAAA,MAC3C,KAAA,EAAO;AAAA,KACR,CAAA;AAED,IAAA,CAAA,CAAE,YAAA,CAAa,qBAAqB,aAAa,CAAA;AAAA,EACnD;AACF;;;ACxKO,IAAM,uBAAA,GAA0B,OACrC,SAAA,EACA,OAAA,KACkB;AAClB,EAAA,MAAM,IAAA,GAAO,SAAS,IAAA,IAAQ,YAAA;AAC9B,EAAA,MAAM,OAAO,OAAA,EAAS,IAAA;AAEtB,EAAA,SAAA,CAAU,aAAa,qBAAA,EAAuB;AAAA,IAC5C,IAAA;AAAA,IACA,GAAI,IAAA,GAAO,EAAE,IAAA,KAAS;AAAC,GACxB,CAAA;AAED,EAAA,IAAI,SAAS,OAAA,EAAS;AACpB,IAAA,MAAM,cAAA,CAAe,SAAA,EAAW,OAAA,CAAQ,OAAO,CAAA;AAAA,EACjD;AAEA,EAAA,IAAI,SAAS,WAAA,EAAa;AACxB,IAAA,MAAM,eAAA,CAAgB,SAAA,EAAW,OAAA,CAAQ,WAAW,CAAA;AAAA,EACtD;AAEA,EAAA,SAAA,CAAU,OAAA,CAAQG,eAAe,mBAAmB,CAAA;AACtD","file":"index.js","sourcesContent":["import { ValueToken, MultiToken } from \"@solid-stack/di\";\nimport type { RequestHandler, ErrorRequestHandler } from \"express\";\nimport type { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport type { ExpressRouter } from \"../core/ExpressRouter.js\";\n\n/**\n * Express Presentation configuration settings.\n */\nexport interface ExpressPxConfigs {\n /**\n * The port on which the Express HTTP server should listen.\n * @default 3000\n */\n port: number;\n\n /**\n * Optional host interface to bind the server to (e.g. '0.0.0.0' or 'localhost').\n */\n host?: string;\n}\n\n/**\n * Options for configuring and bootstrapping Express presentation via loadExpressPresentation.\n */\nexport interface ExpressPresentationOptions {\n /**\n * Port number for the Express server.\n * @default 3000\n */\n port?: number;\n\n /**\n * Optional host interface to bind the server to.\n */\n host?: string;\n\n /**\n * Absolute or relative path to the main presentation directory containing global middlewares and error handlers.\n */\n mainDir?: string;\n\n /**\n * Absolute or relative path to the features directory containing modular feature folders with Express route handlers.\n */\n featuresDir?: string;\n}\n\n/**\n * Express route HTTP methods supported.\n */\nexport type ExpressRouteMethod =\n | \"get\"\n | \"post\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"options\"\n | \"head\"\n | \"all\";\n\n/**\n * Interface contract for global Express middlewares.\n */\nexport interface ExpressMiddleware {\n handler: RequestHandler;\n}\n\n/**\n * Interface contract for global Express error handlers.\n */\nexport interface ExpressErrorHandler {\n handler: ErrorRequestHandler;\n}\n\n/**\n * DI ValueToken for injecting Express Presentation configuration.\n */\nexport class ExpressPxConfigsToken extends ValueToken<ExpressPxConfigs> {}\n\n/**\n * DI MultiToken for registering and resolving Express routers.\n */\nexport class ExpressRoutersToken extends MultiToken<ExpressRouter> {}\n\n/**\n * DI MultiToken for registering and resolving global Express middlewares.\n */\nexport class ExpressMiddlewaresToken extends MultiToken<ExpressMiddleware> {}\n\n/**\n * DI MultiToken for registering and resolving global Express error handlers.\n */\nexport class ExpressErrorHandlersToken extends MultiToken<ExpressErrorHandler> {}\n","import type { Server } from \"node:http\";\nimport express, {\n Router,\n type Express,\n type RequestHandler,\n type ErrorRequestHandler,\n} from \"express\";\nimport { MakeInjectable, type DepsType } from \"@solid-stack/di\";\nimport { IPresentation, ILogger } from \"@solid-stack/agnos\";\nimport {\n ExpressPxConfigsToken,\n ExpressRoutersToken,\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\n\n/**\n * Express HTTP Presentation Layer implementation for the Agnos framework.\n *\n * Implements the IPresentation lifecycle contract:\n * - init: Configures JSON body parsing, attaches global middlewares, routers, and error handlers.\n * - run: Starts the HTTP server on the configured port and host.\n * - stop: Gracefully shuts down the HTTP server.\n */\n@MakeInjectable\nexport class ExpressPresentation implements IPresentation {\n private readonly app: Express;\n private server: Server | null = null;\n\n public static deps = {\n configs: ExpressPxConfigsToken,\n errorHandlers: ExpressErrorHandlersToken,\n middlewares: ExpressMiddlewaresToken,\n routers: ExpressRoutersToken,\n logger: ILogger,\n };\n\n constructor(public deps: DepsType<typeof ExpressPresentation.deps>) {\n this.app = express();\n }\n\n /**\n * Initializes middleware, feature routers, and error handlers on the Express app.\n */\n async init(): Promise<void> {\n this.app.use(express.json());\n\n this.attachMiddlewares();\n this.attachRouters();\n this.attachErrorHandlers();\n }\n\n private attachMiddlewares(): void {\n const middlewares = this.deps.middlewares ?? [];\n for (const middleware of middlewares) {\n this.app.use((req, res, next) => middleware.handler(req, res, next));\n }\n }\n\n private attachRouters(): void {\n const routers = this.deps.routers ?? [];\n this.deps.logger.debug(\"Registering Express routers...\", {\n count: routers.length,\n });\n\n for (const subRouter of routers) {\n const router = Router();\n\n // Attach router-level middlewares if present\n if (subRouter.middlewares && subRouter.middlewares.length > 0) {\n for (const mw of subRouter.middlewares) {\n router.use(mw);\n }\n }\n\n // Attach individual routes\n for (const route of subRouter.routes) {\n this.deps.logger.debug(\n `Registering route: [${route.method.toUpperCase()}] ${subRouter.path}${route.path}`,\n );\n\n const routeHandler: RequestHandler = (req, res, next) =>\n route.handler(req, res, next);\n\n if (route.middlewares && route.middlewares.length > 0) {\n router[route.method](route.path, ...route.middlewares, routeHandler);\n } else {\n router[route.method](route.path, routeHandler);\n }\n }\n\n this.app.use(subRouter.path, router);\n }\n }\n\n private attachErrorHandlers(): void {\n const errorHandlers = this.deps.errorHandlers ?? [];\n for (const errorHandler of errorHandlers) {\n const handler: ErrorRequestHandler = (err, req, res, next) =>\n errorHandler.handler(err, req, res, next);\n this.app.use(handler);\n }\n }\n\n /**\n * Starts the Express server listening on the configured port and host.\n */\n async run(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const { port, host } = this.deps.configs;\n\n const onListening = () => {\n this.deps.logger.info(\n `Express server listening on ${host ? `${host}:` : \"port \"}${port}`,\n );\n resolve();\n };\n\n if (host) {\n this.server = this.app.listen(port, host, onListening);\n } else {\n this.server = this.app.listen(port, onListening);\n }\n\n this.server.on(\"error\", (err: Error) => {\n this.deps.logger.error(\"Express server error:\", err);\n reject(err);\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Gracefully closes the running HTTP server.\n */\n async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n return resolve();\n }\n\n this.server.close((err) => {\n if (err) {\n this.deps.logger.error(\"Error closing Express server:\", err);\n return reject(err);\n }\n this.deps.logger.info(\"Express server stopped receiving new requests.\");\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Returns the underlying Express application instance.\n */\n public getApp(): Express {\n return this.app;\n }\n\n /**\n * Returns the active Node HTTP server instance, or null if not currently running.\n */\n public getServer(): Server | null {\n return this.server;\n }\n}\n","import { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a given filesystem path exists and is a directory.\n *\n * @param dir - The directory path to check\n * @returns True if the directory exists, false if it does not exist\n */\nexport async function folderExists(dir: string): Promise<boolean> {\n try {\n const stats = await stat(dir);\n return stats.isDirectory();\n } catch (error: any) {\n if (error?.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Container } from \"@solid-stack/di\";\nimport { PresentationError } from \"@solid-stack/agnos\";\nimport {\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\nimport { folderExists } from \"./folderExists.js\";\n\n/**\n * Loads global middlewares and error handlers from the main presentation directory.\n *\n * @param c - DI Container\n * @param mainDir - Directory containing main presentation index file\n */\nexport async function autoLoadMainPx(\n c: Container,\n mainDir: string,\n): Promise<void> {\n if (!(await folderExists(mainDir))) {\n return;\n }\n\n const mainDirContents = await fs.readdir(mainDir, { withFileTypes: true });\n\n const indexFile = mainDirContents.find(\n (f) =>\n f.isFile() &&\n (f.name === \"index.ts\" ||\n f.name === \"index.js\" ||\n f.name === \"index.mjs\" ||\n f.name === \"index.cjs\"),\n );\n\n if (!indexFile) {\n throw new PresentationError(\n `No index.ts or index.js file found in the main presentation directory: ${mainDir}`,\n { phase: \"init\" },\n );\n }\n\n const indexFilePath = path.join(mainDir, indexFile.name);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n // Register middlewares if exported\n const middlewares = imported.middlewares;\n if (middlewares && Array.isArray(middlewares)) {\n for (const middleware of middlewares) {\n c.provideMulti(ExpressMiddlewaresToken, middleware);\n }\n }\n\n // Register error handlers if exported\n const errorHandlers = imported.errorHandlers;\n if (errorHandlers && Array.isArray(errorHandlers)) {\n for (const errorHandler of errorHandlers) {\n c.provideMulti(ExpressErrorHandlersToken, errorHandler);\n }\n }\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRoute } from \"./ExpressRoute.js\";\n\n/**\n * Abstract base class for defining an Express router grouping multiple routes under a common path prefix.\n */\nexport abstract class ExpressRouter {\n /**\n * The base URL path prefix for this router (e.g. '/users' or '/api/v1/orders').\n */\n public abstract get path(): string;\n\n /**\n * The collection of routes registered under this router.\n */\n public abstract get routes(): ExpressRoute[];\n\n /**\n * Optional router-level middlewares executed for all routes in this router.\n */\n public middlewares?: RequestHandler[];\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRouteMethod } from \"../types/index.js\";\n\n/**\n * Abstract base class for defining an Express route handler within an Agnos feature.\n */\nexport abstract class ExpressRoute {\n /**\n * The HTTP method for this route (e.g. 'get', 'post', 'put', 'delete').\n */\n abstract method: ExpressRouteMethod;\n\n /**\n * The sub-path for this route relative to the feature router's base path (e.g. '/' or '/:id').\n */\n abstract path: string;\n\n /**\n * The Express request handler function.\n */\n abstract handler: RequestHandler;\n\n /**\n * Optional route-level middlewares executed before handler.\n */\n middlewares?: RequestHandler[];\n}\n","/**\n * Default configuration constants for Agnos Express presentation.\n */\n\nexport const DEFAULT_PORT = 3000;\nexport const DEFAULT_HOST = \"0.0.0.0\";\nexport const DEFAULT_FEATURE_PX_DIR = \"pxExpress\";\nexport const DEFAULT_HANDLERS_DIR = \"handlers\";\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n MakeInjectable,\n registerInjectable,\n diFactoryRegistry,\n type Container,\n type DepsType,\n} from \"@solid-stack/di\";\nimport { FeatureLoadError } from \"@solid-stack/agnos\";\nimport { ExpressRoutersToken } from \"../types/index.js\";\nimport { ExpressRouter } from \"../core/ExpressRouter.js\";\nimport { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport { folderExists } from \"./folderExists.js\";\nimport {\n DEFAULT_FEATURE_PX_DIR,\n DEFAULT_HANDLERS_DIR,\n} from \"../constants/index.js\";\n\n/**\n * Autoloads Express presentation modules from feature directories.\n *\n * For each feature directory:\n * 1. Checks for 'pxExpress' folder.\n * 2. Checks for index.ts/js exporting 'path' (string prefix).\n * 3. Scans 'handlers' folder for ExpressRoute implementations.\n * 4. Dynamically builds a FeatureRouter class registered with ExpressRoutersToken.\n *\n * @param c - DI Container\n * @param featuresDir - Path to features directory\n */\nexport async function autoLoadFeature(\n c: Container,\n featuresDir: string,\n): Promise<void> {\n if (!(await folderExists(featuresDir))) {\n return;\n }\n\n const features = await fs.readdir(featuresDir, { withFileTypes: true });\n\n for (const feature of features) {\n if (!feature.isDirectory()) continue;\n\n const featureName = feature.name;\n const pxExpressDir = path.join(\n featuresDir,\n featureName,\n DEFAULT_FEATURE_PX_DIR,\n );\n\n if (!(await folderExists(pxExpressDir))) continue;\n\n const pxExpressDirFiles = await fs.readdir(pxExpressDir);\n const indexFile = pxExpressDirFiles.find(\n (f) =>\n f === \"index.ts\" ||\n f === \"index.js\" ||\n f === \"index.mjs\" ||\n f === \"index.cjs\",\n );\n\n if (!indexFile) {\n throw new FeatureLoadError(\n featureName,\n pxExpressDir,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) does not have an index.ts or index.js file.`,\n );\n }\n\n const indexFilePath = path.join(pxExpressDir, indexFile);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n const routePath = imported.path;\n\n if (typeof routePath !== \"string\") {\n throw new FeatureLoadError(\n featureName,\n indexFilePath,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) has an index file, but it doesn't export a string named 'path'.`,\n );\n }\n\n // Load routes from handlers/\n const routeClasses: {\n meta: { name: string };\n class: new (...args: any[]) => ExpressRoute;\n }[] = [];\n\n const routesDir = path.join(pxExpressDir, DEFAULT_HANDLERS_DIR);\n\n if (await folderExists(routesDir)) {\n const routeFiles = await fs.readdir(routesDir);\n\n for (const routeFile of routeFiles) {\n if (\n !routeFile.match(/\\.(js|ts|mjs|cjs)$/) ||\n routeFile.endsWith(\".d.ts\") ||\n routeFile.includes(\".test.\") ||\n routeFile.includes(\".spec.\")\n ) {\n continue;\n }\n\n const routeFilePath = path.join(routesDir, routeFile);\n const routeFileModule = await import(pathToFileURL(routeFilePath).href);\n const routeClass = routeFileModule.default;\n\n if (!routeClass) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The route file \"${routeFile}\" in feature \"${featureName}\" doesn't have a default export.`,\n );\n }\n\n if (\n typeof routeClass !== \"function\" ||\n !(routeClass.prototype instanceof ExpressRoute)\n ) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The default export in \"${routeFile}\" (feature: \"${featureName}\") does not inherit from ExpressRoute.`,\n );\n }\n\n // Ensure DI factory is registered for the route class\n if (!diFactoryRegistry.has(routeClass)) {\n if ((routeClass as any).deps) {\n registerInjectable(routeClass as any);\n } else {\n diFactoryRegistry.set(\n routeClass,\n () => new (routeClass as any)(),\n );\n }\n }\n\n routeClasses.push({\n meta: { name: routeClass.name },\n class: routeClass,\n });\n }\n }\n\n // Build the dependency object for DI resolution\n const featureDeps = routeClasses.reduce(\n (acc, entry) => {\n const depKey =\n entry.meta.name.charAt(0).toLowerCase() + entry.meta.name.slice(1);\n acc[depKey] = entry.class;\n return acc;\n },\n {} as Record<string, new (...args: any[]) => ExpressRoute>,\n );\n\n const optionalMiddlewares = Array.isArray(imported.middlewares)\n ? imported.middlewares\n : undefined;\n\n // Create the router class dynamically\n @MakeInjectable\n class FeatureRouter extends ExpressRouter {\n public static deps = featureDeps;\n public middlewares = optionalMiddlewares;\n\n constructor(public deps: DepsType<typeof featureDeps>) {\n super();\n }\n\n public get path(): string {\n return routePath;\n }\n\n public get routes(): ExpressRoute[] {\n return Object.values(this.deps) as ExpressRoute[];\n }\n }\n\n const routerClassName = `${\n featureName.charAt(0).toUpperCase() + featureName.slice(1)\n }ExpressRouter`;\n\n Object.defineProperty(FeatureRouter, \"name\", {\n value: routerClassName,\n });\n\n c.provideMulti(ExpressRoutersToken, FeatureRouter);\n }\n}\n","import type { Container } from \"@solid-stack/di\";\nimport { IPresentation } from \"@solid-stack/agnos\";\nimport { ExpressPresentation } from \"./ExpressPresentation.js\";\nimport {\n ExpressPxConfigsToken,\n type ExpressPresentationOptions,\n} from \"../types/index.js\";\nimport { autoLoadMainPx } from \"../utils/autoLoadMainPx.js\";\nimport { autoLoadFeature } from \"../utils/autoLoadFeature.js\";\nimport { DEFAULT_PORT } from \"../constants/index.js\";\n\n/**\n * Bootstraps and registers the Express presentation layer in the DI container.\n *\n * Configures:\n * 1. ExpressPxConfigsToken with port and optional host.\n * 2. Autoloads global middlewares and error handlers from mainDir if specified.\n * 3. Autoloads feature routes from featuresDir if specified.\n * 4. Binds IPresentation to ExpressPresentation.\n *\n * @param container - The @solid-stack/di Container instance.\n * @param options - Presentation bootstrap options.\n */\nexport const loadExpressPresentation = async (\n container: Container,\n options?: ExpressPresentationOptions,\n): Promise<void> => {\n const port = options?.port ?? DEFAULT_PORT;\n const host = options?.host;\n\n container.provideValue(ExpressPxConfigsToken, {\n port,\n ...(host ? { host } : {}),\n });\n\n if (options?.mainDir) {\n await autoLoadMainPx(container, options.mainDir);\n }\n\n if (options?.featuresDir) {\n await autoLoadFeature(container, options.featuresDir);\n }\n\n container.provide(IPresentation, ExpressPresentation);\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/public.ts","../src/core/ExpressPresentation.ts","../src/utils/folderExists.ts","../src/utils/autoLoadMainPx.ts","../src/core/ExpressRouter.ts","../src/core/ExpressRoute.ts","../src/constants/index.ts","../src/utils/autoLoadFeature.ts","../src/core/loadExpressPresentation.ts"],"names":["fs","path","_init","pathToFileURL","diFactoryRegistry","MakeInjectable","IPresentation"],"mappings":"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","file":"index.js","sourcesContent":["import { ValueToken, MultiToken } from \"@solid-stack/di\";\nimport type { RequestHandler, ErrorRequestHandler, Express } from \"express\";\nimport type { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport type { ExpressRouter } from \"../core/ExpressRouter.js\";\n\n/**\n * Lifecycle callback function invoked with the active Express application instance.\n * Allows consumers to perform custom configuration or register custom routes/middlewares on the Express app.\n */\nexport type ExpressAppCallback = (app: Express) => void | Promise<void>;\n\n/**\n * Express Presentation configuration settings.\n */\nexport interface ExpressPxConfigs {\n /**\n * The port on which the Express HTTP server should listen.\n * @default 3000\n */\n port: number;\n\n /**\n * Optional host interface to bind the server to (e.g. '0.0.0.0' or 'localhost').\n */\n host?: string;\n\n /**\n * Optional callback executed before global middlewares are attached.\n */\n onBeforeMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed after global middlewares are attached.\n */\n onAfterMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed before routers are attached.\n */\n onBeforeRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed after routers are attached.\n */\n onAfterRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed before error handlers are attached.\n */\n onBeforeErrorHandlers?: ExpressAppCallback;\n\n /**\n * Optional callback executed after error handlers are attached.\n */\n onAfterErrorHandlers?: ExpressAppCallback;\n}\n\n/**\n * Options for configuring and bootstrapping Express presentation via loadExpressPresentation.\n */\nexport interface ExpressPresentationOptions {\n /**\n * Port number for the Express server.\n * @default 3000\n */\n port?: number;\n\n /**\n * Optional host interface to bind the server to.\n */\n host?: string;\n\n /**\n * Absolute or relative path to the main presentation directory containing global middlewares and error handlers.\n */\n mainDir?: string;\n\n /**\n * Absolute or relative path to the features directory containing modular feature folders with Express route handlers.\n */\n featuresDir?: string;\n\n /**\n * Optional callback executed before global middlewares are attached.\n */\n onBeforeMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed after global middlewares are attached.\n */\n onAfterMiddlewares?: ExpressAppCallback;\n\n /**\n * Optional callback executed before routers are attached.\n */\n onBeforeRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed after routers are attached.\n */\n onAfterRouters?: ExpressAppCallback;\n\n /**\n * Optional callback executed before error handlers are attached.\n */\n onBeforeErrorHandlers?: ExpressAppCallback;\n\n /**\n * Optional callback executed after error handlers are attached.\n */\n onAfterErrorHandlers?: ExpressAppCallback;\n}\n\n/**\n * Express route HTTP methods supported.\n */\nexport type ExpressRouteMethod =\n | \"get\"\n | \"post\"\n | \"put\"\n | \"patch\"\n | \"delete\"\n | \"options\"\n | \"head\"\n | \"all\";\n\n/**\n * Interface contract for global Express middlewares.\n */\nexport interface ExpressMiddleware {\n handler: RequestHandler;\n}\n\n/**\n * Interface contract for global Express error handlers.\n */\nexport interface ExpressErrorHandler {\n handler: ErrorRequestHandler;\n}\n\n/**\n * DI ValueToken for injecting Express Presentation configuration.\n */\nexport class ExpressPxConfigsToken extends ValueToken<ExpressPxConfigs> {}\n\n/**\n * DI MultiToken for registering and resolving Express routers.\n */\nexport class ExpressRoutersToken extends MultiToken<ExpressRouter> {}\n\n/**\n * DI MultiToken for registering and resolving global Express middlewares.\n */\nexport class ExpressMiddlewaresToken extends MultiToken<ExpressMiddleware> {}\n\n/**\n * DI MultiToken for registering and resolving global Express error handlers.\n */\nexport class ExpressErrorHandlersToken extends MultiToken<ExpressErrorHandler> {}\n","import type { Server } from \"node:http\";\nimport express, {\n Router,\n type Express,\n type RequestHandler,\n type ErrorRequestHandler,\n} from \"express\";\nimport { MakeInjectable, type DepsType } from \"@solid-stack/di\";\nimport { IPresentation, ILogger } from \"@solid-stack/agnos\";\nimport {\n ExpressPxConfigsToken,\n ExpressRoutersToken,\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\n\n/**\n * Express HTTP Presentation Layer implementation for the Agnos framework.\n *\n * Implements the IPresentation lifecycle contract:\n * - init: Configures JSON body parsing, attaches global middlewares, routers, and error handlers.\n * - run: Starts the HTTP server on the configured port and host.\n * - stop: Gracefully shuts down the HTTP server.\n */\n@MakeInjectable\nexport class ExpressPresentation implements IPresentation {\n private readonly app: Express;\n private server: Server | null = null;\n\n public static deps = {\n configs: ExpressPxConfigsToken,\n errorHandlers: ExpressErrorHandlersToken,\n middlewares: ExpressMiddlewaresToken,\n routers: ExpressRoutersToken,\n logger: ILogger,\n };\n\n constructor(public deps: DepsType<typeof ExpressPresentation.deps>) {\n this.app = express();\n }\n\n /**\n * Initializes middleware, feature routers, and error handlers on the Express app,\n * executing configured onBefore and onAfter lifecycle callbacks at each stage.\n */\n async init(): Promise<void> {\n this.app.use(express.json());\n\n const configs = this.deps.configs;\n\n // 1. Middlewares stage\n if (configs?.onBeforeMiddlewares) {\n await configs.onBeforeMiddlewares(this.app);\n }\n this.attachMiddlewares();\n if (configs?.onAfterMiddlewares) {\n await configs.onAfterMiddlewares(this.app);\n }\n\n // 2. Routers stage\n if (configs?.onBeforeRouters) {\n await configs.onBeforeRouters(this.app);\n }\n this.attachRouters();\n if (configs?.onAfterRouters) {\n await configs.onAfterRouters(this.app);\n }\n\n // 3. Error handlers stage\n if (configs?.onBeforeErrorHandlers) {\n await configs.onBeforeErrorHandlers(this.app);\n }\n this.attachErrorHandlers();\n if (configs?.onAfterErrorHandlers) {\n await configs.onAfterErrorHandlers(this.app);\n }\n }\n\n private attachMiddlewares(): void {\n const middlewares = this.deps.middlewares ?? [];\n for (const middleware of middlewares) {\n this.app.use((req, res, next) => middleware.handler(req, res, next));\n }\n }\n\n private attachRouters(): void {\n const routers = this.deps.routers ?? [];\n this.deps.logger.debug(\"Registering Express routers...\", {\n count: routers.length,\n });\n\n for (const subRouter of routers) {\n const router = Router();\n\n // Attach router-level middlewares if present\n if (subRouter.middlewares && subRouter.middlewares.length > 0) {\n for (const mw of subRouter.middlewares) {\n router.use(mw);\n }\n }\n\n // Attach individual routes\n for (const route of subRouter.routes) {\n this.deps.logger.debug(\n `Registering route: [${route.method.toUpperCase()}] ${subRouter.path}${route.path}`,\n );\n\n const routeHandler: RequestHandler = (req, res, next) =>\n route.handler(req, res, next);\n\n if (route.middlewares && route.middlewares.length > 0) {\n router[route.method](route.path, ...route.middlewares, routeHandler);\n } else {\n router[route.method](route.path, routeHandler);\n }\n }\n\n this.app.use(subRouter.path, router);\n }\n }\n\n private attachErrorHandlers(): void {\n const errorHandlers = this.deps.errorHandlers ?? [];\n for (const errorHandler of errorHandlers) {\n const handler: ErrorRequestHandler = (err, req, res, next) =>\n errorHandler.handler(err, req, res, next);\n this.app.use(handler);\n }\n }\n\n /**\n * Starts the Express server listening on the configured port and host.\n */\n async run(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const { port, host } = this.deps.configs;\n\n const onListening = () => {\n this.deps.logger.info(\n `Express server listening on ${host ? `${host}:` : \"port \"}${port}`,\n );\n resolve();\n };\n\n if (host) {\n this.server = this.app.listen(port, host, onListening);\n } else {\n this.server = this.app.listen(port, onListening);\n }\n\n this.server.on(\"error\", (err: Error) => {\n this.deps.logger.error(\"Express server error:\", err);\n reject(err);\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Gracefully closes the running HTTP server.\n */\n async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n return resolve();\n }\n\n this.server.close((err) => {\n if (err) {\n this.deps.logger.error(\"Error closing Express server:\", err);\n return reject(err);\n }\n this.deps.logger.info(\"Express server stopped receiving new requests.\");\n this.server = null;\n resolve();\n });\n });\n }\n\n /**\n * Returns the underlying Express application instance.\n */\n public getApp(): Express {\n return this.app;\n }\n\n /**\n * Returns the active Node HTTP server instance, or null if not currently running.\n */\n public getServer(): Server | null {\n return this.server;\n }\n}\n","import { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a given filesystem path exists and is a directory.\n *\n * @param dir - The directory path to check\n * @returns True if the directory exists, false if it does not exist\n */\nexport async function folderExists(dir: string): Promise<boolean> {\n try {\n const stats = await stat(dir);\n return stats.isDirectory();\n } catch (error: any) {\n if (error?.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Container } from \"@solid-stack/di\";\nimport { PresentationError } from \"@solid-stack/agnos\";\nimport {\n ExpressMiddlewaresToken,\n ExpressErrorHandlersToken,\n} from \"../types/index.js\";\nimport { folderExists } from \"./folderExists.js\";\n\n/**\n * Loads global middlewares and error handlers from the main presentation directory.\n *\n * @param c - DI Container\n * @param mainDir - Directory containing main presentation index file\n */\nexport async function autoLoadMainPx(\n c: Container,\n mainDir: string,\n): Promise<void> {\n if (!(await folderExists(mainDir))) {\n return;\n }\n\n const mainDirContents = await fs.readdir(mainDir, { withFileTypes: true });\n\n const indexFile = mainDirContents.find(\n (f) =>\n f.isFile() &&\n (f.name === \"index.ts\" ||\n f.name === \"index.js\" ||\n f.name === \"index.mjs\" ||\n f.name === \"index.cjs\"),\n );\n\n if (!indexFile) {\n throw new PresentationError(\n `No index.ts or index.js file found in the main presentation directory: ${mainDir}`,\n { phase: \"init\" },\n );\n }\n\n const indexFilePath = path.join(mainDir, indexFile.name);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n // Register middlewares if exported\n const middlewares = imported.middlewares;\n if (middlewares && Array.isArray(middlewares)) {\n for (const middleware of middlewares) {\n c.provideMulti(ExpressMiddlewaresToken, middleware);\n }\n }\n\n // Register error handlers if exported\n const errorHandlers = imported.errorHandlers;\n if (errorHandlers && Array.isArray(errorHandlers)) {\n for (const errorHandler of errorHandlers) {\n c.provideMulti(ExpressErrorHandlersToken, errorHandler);\n }\n }\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRoute } from \"./ExpressRoute.js\";\n\n/**\n * Abstract base class for defining an Express router grouping multiple routes under a common path prefix.\n */\nexport abstract class ExpressRouter {\n /**\n * The base URL path prefix for this router (e.g. '/users' or '/api/v1/orders').\n */\n public abstract get path(): string;\n\n /**\n * The collection of routes registered under this router.\n */\n public abstract get routes(): ExpressRoute[];\n\n /**\n * Optional router-level middlewares executed for all routes in this router.\n */\n public middlewares?: RequestHandler[];\n}\n","import type { RequestHandler } from \"express\";\nimport type { ExpressRouteMethod } from \"../types/index.js\";\n\n/**\n * Abstract base class for defining an Express route handler within an Agnos feature.\n */\nexport abstract class ExpressRoute {\n /**\n * The HTTP method for this route (e.g. 'get', 'post', 'put', 'delete').\n */\n abstract method: ExpressRouteMethod;\n\n /**\n * The sub-path for this route relative to the feature router's base path (e.g. '/' or '/:id').\n */\n abstract path: string;\n\n /**\n * The Express request handler function.\n */\n abstract handler: RequestHandler;\n\n /**\n * Optional route-level middlewares executed before handler.\n */\n middlewares?: RequestHandler[];\n}\n","/**\n * Default configuration constants for Agnos Express presentation.\n */\n\nexport const DEFAULT_PORT = 3000;\nexport const DEFAULT_HOST = \"0.0.0.0\";\nexport const DEFAULT_FEATURE_PX_DIR = \"pxExpress\";\nexport const DEFAULT_HANDLERS_DIR = \"handlers\";\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n MakeInjectable,\n registerInjectable,\n type Container,\n type DepsType,\n} from \"@solid-stack/di\";\nimport * as di from \"@solid-stack/di\";\nimport { FeatureLoadError } from \"@solid-stack/agnos\";\nimport { ExpressRoutersToken } from \"../types/index.js\";\nimport { ExpressRouter } from \"../core/ExpressRouter.js\";\nimport { ExpressRoute } from \"../core/ExpressRoute.js\";\nimport { folderExists } from \"./folderExists.js\";\nimport {\n DEFAULT_FEATURE_PX_DIR,\n DEFAULT_HANDLERS_DIR,\n} from \"../constants/index.js\";\n\n/**\n * Autoloads Express presentation modules from feature directories.\n *\n * For each feature directory:\n * 1. Checks for 'pxExpress' folder.\n * 2. Checks for index.ts/js exporting 'path' (string prefix).\n * 3. Scans 'handlers' folder for ExpressRoute implementations.\n * 4. Dynamically builds a FeatureRouter class registered with ExpressRoutersToken.\n *\n * @param c - DI Container\n * @param featuresDir - Path to features directory\n */\nexport async function autoLoadFeature(\n c: Container,\n featuresDir: string,\n): Promise<void> {\n if (!(await folderExists(featuresDir))) {\n return;\n }\n\n const features = await fs.readdir(featuresDir, { withFileTypes: true });\n\n for (const feature of features) {\n if (!feature.isDirectory()) continue;\n\n const featureName = feature.name;\n const pxExpressDir = path.join(\n featuresDir,\n featureName,\n DEFAULT_FEATURE_PX_DIR,\n );\n\n if (!(await folderExists(pxExpressDir))) continue;\n\n const pxExpressDirFiles = await fs.readdir(pxExpressDir);\n const indexFile = pxExpressDirFiles.find(\n (f) =>\n f === \"index.ts\" ||\n f === \"index.js\" ||\n f === \"index.mjs\" ||\n f === \"index.cjs\",\n );\n\n if (!indexFile) {\n throw new FeatureLoadError(\n featureName,\n pxExpressDir,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) does not have an index.ts or index.js file.`,\n );\n }\n\n const indexFilePath = path.join(pxExpressDir, indexFile);\n const imported = await import(pathToFileURL(indexFilePath).href);\n\n const routePath = imported.path;\n\n if (typeof routePath !== \"string\") {\n throw new FeatureLoadError(\n featureName,\n indexFilePath,\n `The express presentation of feature \"${featureName}\" (${pxExpressDir}) has an index file, but it doesn't export a string named 'path'.`,\n );\n }\n\n // Load routes from handlers/\n const routeClasses: {\n meta: { name: string };\n class: new (...args: any[]) => ExpressRoute;\n }[] = [];\n\n const routesDir = path.join(pxExpressDir, DEFAULT_HANDLERS_DIR);\n\n if (await folderExists(routesDir)) {\n const routeFiles = await fs.readdir(routesDir);\n\n for (const routeFile of routeFiles) {\n if (\n !routeFile.match(/\\.(js|ts|mjs|cjs)$/) ||\n routeFile.endsWith(\".d.ts\") ||\n routeFile.includes(\".test.\") ||\n routeFile.includes(\".spec.\")\n ) {\n continue;\n }\n\n const routeFilePath = path.join(routesDir, routeFile);\n const routeFileModule = await import(pathToFileURL(routeFilePath).href);\n const routeClass = routeFileModule.default;\n\n if (!routeClass) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The route file \"${routeFile}\" in feature \"${featureName}\" doesn't have a default export.`,\n );\n }\n\n if (\n typeof routeClass !== \"function\" ||\n !(routeClass.prototype instanceof ExpressRoute)\n ) {\n throw new FeatureLoadError(\n featureName,\n routeFilePath,\n `The default export in \"${routeFile}\" (feature: \"${featureName}\") does not inherit from ExpressRoute.`,\n );\n }\n\n // Ensure DI registration for the route class\n const brand =\n (di as any).INJECTABLE_BRAND ?? Symbol.for(\"solid-stack.injectable\");\n const diFactoryRegistry = (di as any).diFactoryRegistry;\n\n if (diFactoryRegistry) {\n if (!diFactoryRegistry.has(routeClass)) {\n if ((routeClass as any).deps) {\n registerInjectable(routeClass as any);\n } else {\n diFactoryRegistry.set(\n routeClass,\n () => new (routeClass as any)(),\n );\n }\n }\n } else if (\n !Object.prototype.hasOwnProperty.call(routeClass, brand) ||\n (routeClass as any)[brand] !== true\n ) {\n if ((routeClass as any).deps && (routeClass as any).length > 0) {\n registerInjectable(routeClass as any);\n } else {\n Object.defineProperty(routeClass, brand, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n }\n\n routeClasses.push({\n meta: { name: routeClass.name },\n class: routeClass,\n });\n }\n }\n\n // Build the dependency object for DI resolution\n const featureDeps = routeClasses.reduce(\n (acc, entry) => {\n const depKey =\n entry.meta.name.charAt(0).toLowerCase() + entry.meta.name.slice(1);\n acc[depKey] = entry.class;\n return acc;\n },\n {} as Record<string, new (...args: any[]) => ExpressRoute>,\n );\n\n const optionalMiddlewares = Array.isArray(imported.middlewares)\n ? imported.middlewares\n : undefined;\n\n // Create the router class dynamically\n @MakeInjectable\n class FeatureRouter extends ExpressRouter {\n public static deps = featureDeps;\n public middlewares = optionalMiddlewares;\n\n constructor(public deps: DepsType<typeof featureDeps>) {\n super();\n }\n\n public get path(): string {\n return routePath;\n }\n\n public get routes(): ExpressRoute[] {\n return Object.values(this.deps) as ExpressRoute[];\n }\n }\n\n const routerClassName = `${\n featureName.charAt(0).toUpperCase() + featureName.slice(1)\n }ExpressRouter`;\n\n Object.defineProperty(FeatureRouter, \"name\", {\n value: routerClassName,\n });\n\n c.provideMulti(ExpressRoutersToken, FeatureRouter);\n }\n}\n","import type { Container } from \"@solid-stack/di\";\nimport { IPresentation } from \"@solid-stack/agnos\";\nimport { ExpressPresentation } from \"./ExpressPresentation.js\";\nimport {\n ExpressPxConfigsToken,\n type ExpressPresentationOptions,\n} from \"../types/index.js\";\nimport { autoLoadMainPx } from \"../utils/autoLoadMainPx.js\";\nimport { autoLoadFeature } from \"../utils/autoLoadFeature.js\";\nimport { DEFAULT_PORT } from \"../constants/index.js\";\n\n/**\n * Bootstraps and registers the Express presentation layer in the DI container.\n *\n * Configures:\n * 1. ExpressPxConfigsToken with port and optional host.\n * 2. Autoloads global middlewares and error handlers from mainDir if specified.\n * 3. Autoloads feature routes from featuresDir if specified.\n * 4. Binds IPresentation to ExpressPresentation.\n *\n * @param container - The @solid-stack/di Container instance.\n * @param options - Presentation bootstrap options.\n */\nexport const loadExpressPresentation = async (\n container: Container,\n options?: ExpressPresentationOptions,\n): Promise<void> => {\n const { mainDir, featuresDir, ...pxConfigs } = options ?? {};\n const port = pxConfigs.port ?? DEFAULT_PORT;\n\n container.provideValue(ExpressPxConfigsToken, {\n ...pxConfigs,\n port,\n });\n\n if (mainDir) {\n await autoLoadMainPx(container, mainDir);\n }\n\n if (featuresDir) {\n await autoLoadFeature(container, featuresDir);\n }\n\n container.provide(IPresentation, ExpressPresentation);\n};\n"]}
|
package/dist/types/public.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { ValueToken, MultiToken } from "@solid-stack/di";
|
|
2
|
-
import type { RequestHandler, ErrorRequestHandler } from "express";
|
|
2
|
+
import type { RequestHandler, ErrorRequestHandler, Express } from "express";
|
|
3
3
|
import type { ExpressRouter } from "../core/ExpressRouter.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lifecycle callback function invoked with the active Express application instance.
|
|
6
|
+
* Allows consumers to perform custom configuration or register custom routes/middlewares on the Express app.
|
|
7
|
+
*/
|
|
8
|
+
export type ExpressAppCallback = (app: Express) => void | Promise<void>;
|
|
4
9
|
/**
|
|
5
10
|
* Express Presentation configuration settings.
|
|
6
11
|
*/
|
|
@@ -14,6 +19,30 @@ export interface ExpressPxConfigs {
|
|
|
14
19
|
* Optional host interface to bind the server to (e.g. '0.0.0.0' or 'localhost').
|
|
15
20
|
*/
|
|
16
21
|
host?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional callback executed before global middlewares are attached.
|
|
24
|
+
*/
|
|
25
|
+
onBeforeMiddlewares?: ExpressAppCallback;
|
|
26
|
+
/**
|
|
27
|
+
* Optional callback executed after global middlewares are attached.
|
|
28
|
+
*/
|
|
29
|
+
onAfterMiddlewares?: ExpressAppCallback;
|
|
30
|
+
/**
|
|
31
|
+
* Optional callback executed before routers are attached.
|
|
32
|
+
*/
|
|
33
|
+
onBeforeRouters?: ExpressAppCallback;
|
|
34
|
+
/**
|
|
35
|
+
* Optional callback executed after routers are attached.
|
|
36
|
+
*/
|
|
37
|
+
onAfterRouters?: ExpressAppCallback;
|
|
38
|
+
/**
|
|
39
|
+
* Optional callback executed before error handlers are attached.
|
|
40
|
+
*/
|
|
41
|
+
onBeforeErrorHandlers?: ExpressAppCallback;
|
|
42
|
+
/**
|
|
43
|
+
* Optional callback executed after error handlers are attached.
|
|
44
|
+
*/
|
|
45
|
+
onAfterErrorHandlers?: ExpressAppCallback;
|
|
17
46
|
}
|
|
18
47
|
/**
|
|
19
48
|
* Options for configuring and bootstrapping Express presentation via loadExpressPresentation.
|
|
@@ -36,6 +65,30 @@ export interface ExpressPresentationOptions {
|
|
|
36
65
|
* Absolute or relative path to the features directory containing modular feature folders with Express route handlers.
|
|
37
66
|
*/
|
|
38
67
|
featuresDir?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Optional callback executed before global middlewares are attached.
|
|
70
|
+
*/
|
|
71
|
+
onBeforeMiddlewares?: ExpressAppCallback;
|
|
72
|
+
/**
|
|
73
|
+
* Optional callback executed after global middlewares are attached.
|
|
74
|
+
*/
|
|
75
|
+
onAfterMiddlewares?: ExpressAppCallback;
|
|
76
|
+
/**
|
|
77
|
+
* Optional callback executed before routers are attached.
|
|
78
|
+
*/
|
|
79
|
+
onBeforeRouters?: ExpressAppCallback;
|
|
80
|
+
/**
|
|
81
|
+
* Optional callback executed after routers are attached.
|
|
82
|
+
*/
|
|
83
|
+
onAfterRouters?: ExpressAppCallback;
|
|
84
|
+
/**
|
|
85
|
+
* Optional callback executed before error handlers are attached.
|
|
86
|
+
*/
|
|
87
|
+
onBeforeErrorHandlers?: ExpressAppCallback;
|
|
88
|
+
/**
|
|
89
|
+
* Optional callback executed after error handlers are attached.
|
|
90
|
+
*/
|
|
91
|
+
onAfterErrorHandlers?: ExpressAppCallback;
|
|
39
92
|
}
|
|
40
93
|
/**
|
|
41
94
|
* Express route HTTP methods supported.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,mBAAmB,CAAC,EAAE,kBAAkB,CAAC;IAEzC;;OAEG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExC;;OAEG;IACH,eAAe,CAAC,EAAE,kBAAkB,CAAC;IAErC;;OAEG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAEpC;;OAEG;IACH,qBAAqB,CAAC,EAAE,kBAAkB,CAAC;IAE3C;;OAEG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,mBAAmB,CAAC,EAAE,kBAAkB,CAAC;IAEzC;;OAEG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExC;;OAEG;IACH,eAAe,CAAC,EAAE,kBAAkB,CAAC;IAErC;;OAEG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAEpC;;OAEG;IACH,qBAAqB,CAAC,EAAE,kBAAkB,CAAC;IAE3C;;OAEG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,MAAM,GACN,KAAK,GACL,OAAO,GACP,QAAQ,GACR,SAAS,GACT,MAAM,GACN,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,cAAc,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,UAAU,CAAC,gBAAgB,CAAC;CAAG;AAE1E;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,UAAU,CAAC,aAAa,CAAC;CAAG;AAErE;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,UAAU,CAAC,iBAAiB,CAAC;CAAG;AAE7E;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,UAAU,CAAC,mBAAmB,CAAC;CAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoLoadFeature.d.ts","sourceRoot":"","sources":["../../src/utils/autoLoadFeature.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"autoLoadFeature.d.ts","sourceRoot":"","sources":["../../src/utils/autoLoadFeature.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,iBAAiB,CAAC;AAYzB;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,SAAS,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAgLf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solid-stack/agnos-express",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Express presentation adapter for the Agnos application framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@solid-stack/di": ">=1.0.0"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@solid-stack/agnos": "^1.0.
|
|
59
|
-
"@solid-stack/di": "^1.0.
|
|
58
|
+
"@solid-stack/agnos": "^1.0.5",
|
|
59
|
+
"@solid-stack/di": "^1.0.4",
|
|
60
60
|
"express": "^5.2.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|