@lytical/app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -0
- package/app.test.d.ts +2 -0
- package/app.test.js +6 -0
- package/index.d.ts +134 -0
- package/index.js +6 -0
- package/package.json +57 -0
- package/types.d.ts +94 -0
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @lytical/app
|
|
2
|
+
|
|
3
|
+
a typescript api server library built for your express project, with dependency injection support and auto router registration
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- router handler dependency injection
|
|
8
|
+
- auto `app.use()` router registration
|
|
9
|
+
- use middleware, only for routes that require it
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
after installing `@lytical/app` to your express project, configure your `tsconfig.json` file to enable decorators.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
// tsconfig.json
|
|
17
|
+
{
|
|
18
|
+
"compilerOptions": {
|
|
19
|
+
"experimentalDecorators": true,
|
|
20
|
+
"emitDecoratorMetadata": true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
if this is a new project, we recommended installing `express@5.2..`, and the following project structure:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
project
|
|
29
|
+
|- dist
|
|
30
|
+
|- src
|
|
31
|
+
| |- middleware
|
|
32
|
+
| | |- my-middleware.ts
|
|
33
|
+
| | |- ...
|
|
34
|
+
| |- routes
|
|
35
|
+
| | |- my-route.ts
|
|
36
|
+
| | |- ...
|
|
37
|
+
| |- services
|
|
38
|
+
| | |- my-service.ts
|
|
39
|
+
| | |- ...
|
|
40
|
+
| |- index.ts
|
|
41
|
+
|- .gitignore
|
|
42
|
+
|- package.json
|
|
43
|
+
|- tsconfig.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
for the above project structure:
|
|
47
|
+
|
|
48
|
+
- configure your `tsconfig.json` file.
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
// tsconfig.json
|
|
52
|
+
{
|
|
53
|
+
"compilerOptions": {
|
|
54
|
+
"rootDir": "src",
|
|
55
|
+
"outDir": "dist"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- configure your `package.json` file to indicate where your routes are located.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
// package.json
|
|
64
|
+
{
|
|
65
|
+
"main": "./{index.js,routes/**/*.js}"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
a simple project template can be found in github (https://....)
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
create your injectable service class(es) to implement the business logic.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { ioc_injectable } from '@lytical/ioc';
|
|
77
|
+
|
|
78
|
+
@ioc_injectable()
|
|
79
|
+
// src/services/example-svc.ts
|
|
80
|
+
export class example_svc {
|
|
81
|
+
async get_message() {
|
|
82
|
+
return 'Hello from example_svc!';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async get_data() {
|
|
86
|
+
return { message: await this.get_message() };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
create your middleware classes
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// src/middleware/example-mw.ts
|
|
95
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
96
|
+
|
|
97
|
+
import { ioc_inject } from '@lytical/ioc';
|
|
98
|
+
import { example_svc } from '../services/example-svc';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Example middleware class
|
|
102
|
+
* Use for middleware that requires dependency injection
|
|
103
|
+
*/
|
|
104
|
+
export class example_middleware_class {
|
|
105
|
+
// inject your service(es) into the middleware class constructor
|
|
106
|
+
constructor(
|
|
107
|
+
@ioc_inject(example_svc) private readonly _example_svc: example_svc,
|
|
108
|
+
) {}
|
|
109
|
+
|
|
110
|
+
// all middleware classes must implement a default() route handler
|
|
111
|
+
async default(rqs: Request, rsp: Response, nxt: NextFunction) {
|
|
112
|
+
console.debug('example middleware invoked');
|
|
113
|
+
rsp.locals.example_middleware_data = await this._example_svc.get_data();
|
|
114
|
+
// make sure to call nxt() to continue the request processing pipeline
|
|
115
|
+
nxt();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
create your route handler(s)
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// src/routes/example.ts
|
|
124
|
+
import express, {
|
|
125
|
+
type Request,
|
|
126
|
+
type Response,
|
|
127
|
+
type NextFunction,
|
|
128
|
+
} from 'express';
|
|
129
|
+
|
|
130
|
+
import {
|
|
131
|
+
app_middleware_dependency,
|
|
132
|
+
app_route,
|
|
133
|
+
app_route_handler,
|
|
134
|
+
} from '@lytical/app';
|
|
135
|
+
|
|
136
|
+
import { ioc_inject } from '@lytical/ioc';
|
|
137
|
+
import { example_svc } from '../services/example-svc';
|
|
138
|
+
import { example_middleware_class } from '../middleware/example-mw';
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Example route class
|
|
142
|
+
* Use for router class(es) for auto app.use() registration for routes; dependent middleware; and dependency injection
|
|
143
|
+
*/
|
|
144
|
+
@app_route({ route: '/example' })
|
|
145
|
+
export class example_route_class {
|
|
146
|
+
// inject your service(es) into the router class constructor
|
|
147
|
+
constructor(
|
|
148
|
+
@ioc_inject(example_svc) private readonly _example_svc: example_svc,
|
|
149
|
+
) {}
|
|
150
|
+
|
|
151
|
+
// implement your handler methods
|
|
152
|
+
@app_route_handler({
|
|
153
|
+
route: '/', // /example/
|
|
154
|
+
http_method: ['GET'],
|
|
155
|
+
})
|
|
156
|
+
async get_handler(rqs: Request, rsp: Response, nxt: NextFunction) {
|
|
157
|
+
rsp.json({ message: await this._example_svc.get_message() }).end();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@app_route_handler({
|
|
161
|
+
http_method: ['POST'],
|
|
162
|
+
route: '/', // /example/
|
|
163
|
+
dependency: [
|
|
164
|
+
// use only the middleware needed
|
|
165
|
+
express.json(),
|
|
166
|
+
app_middleware_dependency(example_middleware_class),
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
post_handler(rqs: Request, rsp: Response, nxt: NextFunction) {
|
|
170
|
+
rsp.json({ body: rqs.body, locals: rsp.locals }).end();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
now just import app and invoke `start()`
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// src/index.ts
|
|
179
|
+
import app from '@lytical/app';
|
|
180
|
+
|
|
181
|
+
app.start();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`app` emits a few life cycle events
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// src/index.ts
|
|
188
|
+
import app, { app_evt } from './lib/app';
|
|
189
|
+
|
|
190
|
+
// app events occur in the following order:
|
|
191
|
+
// 1. create_server
|
|
192
|
+
// 2. server_starting
|
|
193
|
+
// 3. server_listening
|
|
194
|
+
// 4. server_started
|
|
195
|
+
|
|
196
|
+
app.once(app_evt.create_server, (cfg) => {
|
|
197
|
+
// modify (cfg) as needed, or remove this listener if not needed.
|
|
198
|
+
// for example, create a https server instead of http,
|
|
199
|
+
// and push async operations to fetch keys, to (cfg.wait_for).
|
|
200
|
+
// add middleware to (cfg.express), that applies to all routes, etc.
|
|
201
|
+
console.log(`the root route is (${cfg.root_route})`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
app.once(app_evt.server_starting, (cfg) => {
|
|
205
|
+
// modify (cfg) as needed, or remove this listener if not needed.
|
|
206
|
+
// add middleware to this application after auto registered routes are added.
|
|
207
|
+
// for example error handling middleware, etc.
|
|
208
|
+
// push async operations to fetch settings from a database, to (cfg.wait_for).
|
|
209
|
+
// this is the last to register dependencies in the ioc collection before the server starts.
|
|
210
|
+
console.log(`the hostname is (${cfg.hostname})`);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
app.once(app_evt.server_listening, () => {
|
|
214
|
+
// remove this listener if not needed.
|
|
215
|
+
// use it to perform operations after the server starts listening.
|
|
216
|
+
// the ioc container is ready at this point.
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
app.once(app_evt.server_started, () => {
|
|
220
|
+
// remove this listener if not needed.
|
|
221
|
+
// use it to perform operations after the server has started.
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
app.start();
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Documentation
|
|
228
|
+
|
|
229
|
+
todo: working on this right now...
|
|
230
|
+
|
|
231
|
+
Stay tuned! I have more packages to come.`
|
|
232
|
+
|
|
233
|
+
_lytical(r) is a registered trademark of lytical, inc. all rights are reserved._
|
package/app.test.d.ts
ADDED
package/app.test.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* @preserve
|
|
3
|
+
(c) 2025 lytical, inc. all rights are reserved.
|
|
4
|
+
lytical(r) is a registered trademark of lytical, inc.
|
|
5
|
+
please refer to your license agreement on the use of this file.
|
|
6
|
+
*/var __awaiter=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(a,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n.throw(e))}catch(e){r(e)}}function step(e){var t;e.done?a(e.value):(t=e.value,t instanceof i?t:new i(function(e){e(t)})).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})},__importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const chai_1=require("chai"),mocha_1=require("mocha"),index_1=__importDefault(require("./index"));(0,mocha_1.describe)("an app",()=>{it("can be started",()=>__awaiter(void 0,void 0,void 0,function*(){(0,chai_1.expect)(index_1.default).exist}))});
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:stream';
|
|
2
|
+
import { type Request, type RequestHandler, type Response, type NextFunction } from 'express';
|
|
3
|
+
import type { app_listening_cfg_t, app_server_cfg_t } from './types';
|
|
4
|
+
/** events emitted by the app */
|
|
5
|
+
export declare enum app_evt {
|
|
6
|
+
/**
|
|
7
|
+
* use to create or modify the server before it is started
|
|
8
|
+
*
|
|
9
|
+
* for example, create a https server instead of http,
|
|
10
|
+
* and push async operations to fetch keys, to (cfg.wait_for).
|
|
11
|
+
* add middleware to (cfg.express), that applies to all routes, etc.
|
|
12
|
+
*/
|
|
13
|
+
create_server = "lyt-create-server",
|
|
14
|
+
/**
|
|
15
|
+
* use to modify the server listening configuration before it is started
|
|
16
|
+
*
|
|
17
|
+
* add middleware to this (cfg.express) after auto registered routes are added.
|
|
18
|
+
* for example error handling middleware, etc.
|
|
19
|
+
* push async operations to fetch settings from a database, to (cfg.wait_for).
|
|
20
|
+
* this is the last to register dependencies in the ioc collection before the server starts.
|
|
21
|
+
*/
|
|
22
|
+
server_starting = "lyt-server-starting",
|
|
23
|
+
/**
|
|
24
|
+
* emitted when the server is listening
|
|
25
|
+
*
|
|
26
|
+
* use it to perform operations after the server starts listening.
|
|
27
|
+
* the ioc container is ready at this point.
|
|
28
|
+
*/
|
|
29
|
+
server_listening = "lyt-server-listening",
|
|
30
|
+
/**
|
|
31
|
+
* emitted when the server has started
|
|
32
|
+
*
|
|
33
|
+
* use it to perform operations after the server has started.
|
|
34
|
+
*/
|
|
35
|
+
server_started = "lyt-server-started"
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* app class
|
|
39
|
+
* @description
|
|
40
|
+
* the main app class
|
|
41
|
+
* @emits app_evt.create_server use to create or modify the server before it is started
|
|
42
|
+
* @emits app_evt.server_starting use to modify the server listening configuration before it is started
|
|
43
|
+
* @emits app_evt.server_listening emitted when the server is listening
|
|
44
|
+
* @emits app_evt.server_started emitted when the server has started
|
|
45
|
+
*/
|
|
46
|
+
export declare class app extends EventEmitter {
|
|
47
|
+
once(event: app_evt.create_server, listener: (cfg: app_server_cfg_t) => void): this;
|
|
48
|
+
once(event: app_evt.server_listening, listener: () => void): this;
|
|
49
|
+
once(event: app_evt.server_starting, listener: (cfg: app_listening_cfg_t) => void): this;
|
|
50
|
+
once(event: app_evt.server_started, listener: () => void): this;
|
|
51
|
+
/**
|
|
52
|
+
* start the app
|
|
53
|
+
* @description
|
|
54
|
+
* starts the express app server
|
|
55
|
+
* app events occur in the following order:
|
|
56
|
+
* 1. create_server
|
|
57
|
+
* 2. server_starting
|
|
58
|
+
* 3. server_listening
|
|
59
|
+
* 4. server_started
|
|
60
|
+
*/
|
|
61
|
+
start(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
type cstor_t<_t_ = any> = new (...args: any[]) => _t_;
|
|
64
|
+
/**
|
|
65
|
+
* helper to create a route handler dependency from a middleware class constructor
|
|
66
|
+
* @param {app_route_middleware_t} cstor middleware class constructor
|
|
67
|
+
* @param {unknown[]} arg optional arguments to append to the middleware constructor
|
|
68
|
+
* @returns {app_route_handler_dependency_t} route handler dependency
|
|
69
|
+
*/
|
|
70
|
+
export declare function app_middleware_dependency(cstor: cstor_t<app_route_middleware_t>, ...arg: unknown[]): app_route_handler_dependency_t;
|
|
71
|
+
/**
|
|
72
|
+
* app_route class decorator
|
|
73
|
+
* @description
|
|
74
|
+
* decorator to define a route class
|
|
75
|
+
* support for dependency injection in the constructor
|
|
76
|
+
* @param {app_route_info_t} route information
|
|
77
|
+
*/
|
|
78
|
+
export declare function app_route({ route, arg }: app_route_info_t): (cstr: cstor_t) => cstor_t<any>;
|
|
79
|
+
/**
|
|
80
|
+
* app_route_handler method decorator
|
|
81
|
+
* @param {app_route_handler_info_t} arg route handler information
|
|
82
|
+
*/
|
|
83
|
+
export declare function app_route_handler(arg: app_route_handler_info_t): (cstr: any, method_nm: string, pd: PropertyDescriptor) => PropertyDescriptor;
|
|
84
|
+
/**
|
|
85
|
+
* app_route_info_t
|
|
86
|
+
* @description
|
|
87
|
+
* route information for the app_route class decorator
|
|
88
|
+
*/
|
|
89
|
+
export type app_route_info_t = {
|
|
90
|
+
/** the route path or pattern */
|
|
91
|
+
route: string | RegExp;
|
|
92
|
+
/** optional arguments to append to the route class constructor */
|
|
93
|
+
arg?: unknown[];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* app_route_handler_dependency_t
|
|
97
|
+
* @description
|
|
98
|
+
* route handler dependency type
|
|
99
|
+
* can be a RequestHandler or a middleware class constructor with optional arguments
|
|
100
|
+
*/
|
|
101
|
+
export type app_route_handler_dependency_t = RequestHandler | {
|
|
102
|
+
/** middleware class constructor */
|
|
103
|
+
middleware: cstor_t<app_route_middleware_t>;
|
|
104
|
+
/** optional arguments to append to the middleware constructor */
|
|
105
|
+
arg?: unknown[];
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* app_route_handler_info_t
|
|
109
|
+
* @description
|
|
110
|
+
* route handler information for the app_route_handler method decorator
|
|
111
|
+
*/
|
|
112
|
+
export type app_route_handler_info_t = {
|
|
113
|
+
/** route handler dependencies */
|
|
114
|
+
dependency?: app_route_handler_dependency_t | app_route_handler_dependency_t[];
|
|
115
|
+
/** http method or methods */
|
|
116
|
+
http_method?: string | string[];
|
|
117
|
+
/** the route path or pattern */
|
|
118
|
+
route: string | RegExp;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* app_route_middleware_t
|
|
122
|
+
* @description
|
|
123
|
+
* route middleware class type
|
|
124
|
+
* the class must have a default method that is a RequestHandler
|
|
125
|
+
* this method can be used as a middleware in the app_route_handler_info_t dependency property
|
|
126
|
+
* this method can also have dependencies injected
|
|
127
|
+
*/
|
|
128
|
+
export interface app_route_middleware_t {
|
|
129
|
+
/** the default method of the middleware class */
|
|
130
|
+
default(rqs: Request, rsp: Response, nxt: NextFunction): void | Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
declare const _default: app;
|
|
133
|
+
export default _default;
|
|
134
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* @preserve
|
|
3
|
+
(c) 2025 lytical, inc. all rights are reserved.
|
|
4
|
+
lytical(r) is a registered trademark of lytical, inc.
|
|
5
|
+
please refer to your license agreement on the use of this file.
|
|
6
|
+
*/var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,r,o){void 0===o&&(o=r);var n=Object.getOwnPropertyDescriptor(t,r);n&&!("get"in n?!t.__esModule:n.writable||n.configurable)||(n={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,o,n)}:function(e,t,r,o){void 0===o&&(o=r),e[o]=t[r]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),__importStar=this&&this.__importStar||function(){var ownKeys=function(e){return ownKeys=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r);return t},ownKeys(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r=ownKeys(e),o=0;o<r.length;o++)"default"!==r[o]&&__createBinding(t,e,r[o]);return __setModuleDefault(t,e),t}}(),__awaiter=this&&this.__awaiter||function(e,t,r,o){return new(r||(r=Promise))(function(n,s){function fulfilled(e){try{step(o.next(e))}catch(e){s(e)}}function rejected(e){try{step(o.throw(e))}catch(e){s(e)}}function step(e){var t;e.done?n(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(fulfilled,rejected)}step((o=o.apply(e,t||[])).next())})},__asyncValues=this&&this.__asyncValues||function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,r=e[Symbol.asyncIterator];return r?r.call(e):(e="function"==typeof __values?__values(e):e[Symbol.iterator](),t={},verb("next"),verb("throw"),verb("return"),t[Symbol.asyncIterator]=function(){return this},t);function verb(r){t[r]=e[r]&&function(t){return new Promise(function(o,n){(function(e,t,r,o){Promise.resolve(o).then(function(t){e({value:t,done:r})},t)})(o,n,(t=e[r](t)).done,t.value)})}}},__importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.app=exports.app_evt=void 0,exports.app_middleware_dependency=app_middleware_dependency,exports.app_route=app_route,exports.app_route_handler=app_route_handler;const node_http_1=require("node:http"),node_stream_1=require("node:stream"),promises_1=require("node:fs/promises"),node_path_1=require("node:path"),express_1=__importDefault(require("express")),find_root_1=__importDefault(require("find-root")),ioc_1=require("@lytical/ioc"),collection_1=__importDefault(require("@lytical/ioc/collection"));var app_evt;!function(e){e.create_server="lyt-create-server",e.server_starting="lyt-server-starting",e.server_listening="lyt-server-listening",e.server_started="lyt-server-started"}(app_evt||(exports.app_evt=app_evt={}));const _app=(0,express_1.default)(),_root_route=express_1.default.Router({mergeParams:!0});class app extends node_stream_1.EventEmitter{once(e,t){return super.once(e,t)}start(){return __awaiter(this,void 0,void 0,function*(){var e,t;const r={express:_app,root_route:"/api",wait_for:[]};r.express.use((e,t,r)=>{t.set("X-Powered-By","powered by lytical(r) enterprise solutions, and express"),t.set("X-Lyt-Version","1.0.0"),r()}),console.log("[@lytical/ts-express] creating http server..."),this.emit(app_evt.create_server,r),r.wait_for.length&&(yield Promise.all(r.wait_for)),_app.use(null!==(e=r.root_route)&&void 0!==e?e:"/api",_root_route);const o=null!==(t=r.server)&&void 0!==t?t:(0,node_http_1.createServer)(r.express);yield _register_routes();const n={express:r.express,hostname:process.env.HOSTNAME||"localhost",port:process.env.PORT?parseInt(process.env.PORT,10):3e3,server:o,wait_for:[]};this.emit(app_evt.server_starting,n),n.wait_for.length&&(yield Promise.all(n.wait_for)),yield collection_1.default.create_container(),o.listen(n.port,n.hostname,n.backlog,()=>{this.emit(app_evt.server_listening),console.log(`[@lytical/ts-express] server started on port ${n.port}; hostname ${n.hostname}`)}),this.emit(app_evt.server_started)})}}function _register_routes(){return __awaiter(this,void 0,void 0,function*(){var e,t,r,o,n;const{main:s}=null!==(n=yield Promise.resolve(`${(0,node_path_1.join)((0,find_root_1.default)(__dirname),"package.json")}`).then(e=>__importStar(require(e))))&&void 0!==n?n:{};if(s){const n=yield(0,promises_1.glob)(s),c=process.cwd();try{for(var i,a=!0,_=__asyncValues(n);!(e=(i=yield _.next()).done);a=!0){o=i.value,a=!1;const e=o;console.log(`[@lytical/ts-express] registering routes in module (${e})...`),yield Promise.resolve(`${(0,node_path_1.join)(c,e)}`).then(e=>__importStar(require(e)))}}catch(e){t={error:e}}finally{try{a||e||!(r=_.return)||(yield r.call(_))}finally{if(t)throw t.error}}}})}exports.app=app;const route_handler_method=Symbol("lyt-app-api-route-handler-method");function app_middleware_dependency(e,...t){return{middleware:e,arg:t}}function app_route({route:e,arg:t}){const r=e;return o=>{var n;const s=express_1.default.Router({mergeParams:!0}),i=null!==(n=o.prototype[route_handler_method])&&void 0!==n?n:{};for(const e of Object.keys(i)){const{route:n,dependency:a,http_method:_}=i[e];if(a.length||_.length>1){const i=express_1.default.Router({mergeParams:!0}),c=[];for(let e of a){if("function"==typeof e){i.use(e),c.push(e.name||"anonymous-middleware");continue}const{middleware:t,arg:r}=e;i.use((e,o,n)=>(0,ioc_1.ioc_create_instance)(t,...null!=r?r:[]).default(e,o,n)),c.push(t.name||"anonymous-middleware")}for(const r of _)i[r.toLowerCase()]?i[r.toLowerCase()](n,(r,n,s)=>{const i=(0,ioc_1.ioc_create_instance)(o,...null!=t?t:[]);(0,ioc_1.ioc_invoke_method)(i[e],i,r,n,s)}):i.use(n,(n,s,i)=>{if(r.toUpperCase()!==n.method)return i();const a=(0,ioc_1.ioc_create_instance)(o,...null!=t?t:[]);(0,ioc_1.ioc_invoke_method)(a[e],a,n,s,i)});console.debug(`[@lytical/ts-express] registered (${_}:${r}${n}) route handler (${o.name}.${e}) with dependencies (${c})`),s.use(i);continue}const[c]=_;c?(s[c.toLowerCase()]?s[c.toLowerCase()](n,(r,n,s)=>{const i=(0,ioc_1.ioc_create_instance)(o,...null!=t?t:[]);(0,ioc_1.ioc_invoke_method)(i[e],i,r,n,s)}):s.use(n,(r,n,s)=>{if(c.toUpperCase()!==r.method)return s();const i=(0,ioc_1.ioc_create_instance)(o,...null!=t?t:[]);(0,ioc_1.ioc_invoke_method)(i[e],i,r,n,s)}),console.debug(`[@lytical/ts-express] registered (${c}:${r}${n}) route handler (${o.name}.${e})`)):(s.use(n,(r,n,s)=>{const i=(0,ioc_1.ioc_create_instance)(o,...null!=t?t:[]);(0,ioc_1.ioc_invoke_method)(i[e],i,r,n,s)}),console.debug(`[@lytical/ts-express] registered (ALL-METHODS:${r}${n}) route handler (${o.name}.${e})`))}return _root_route.use(e,s),o}}function app_route_handler(e){return(t,r,o)=>{var n;switch(e.dependency?Array.isArray(e.dependency)||(e.dependency=[e.dependency]):e.dependency=[],typeof e.http_method){case"undefined":e.http_method=[];break;case"string":e.http_method=[e.http_method]}const s=null!==(n=t[route_handler_method])&&void 0!==n?n:{};return s[r]=e,t[route_handler_method]=s,o}}exports.default=new app;
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lytical/app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "a typescript api server library built for your express project, with dependency injection support and auto router registration",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"private": false,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc --build",
|
|
9
|
+
"publish": "npm run clean&&npm run build&&gulp post_build&&cd ./dist&&npm publish --access public",
|
|
10
|
+
"clean": "rimraf ./dist",
|
|
11
|
+
"rebuild": "npm run clean&&npm install&&npm run build",
|
|
12
|
+
"test": "npm run clean&&npm run build&&mocha --recursive ./dist/**/*.test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ioc",
|
|
16
|
+
"express",
|
|
17
|
+
"app",
|
|
18
|
+
"server",
|
|
19
|
+
"dependency-injection",
|
|
20
|
+
"auto-routing",
|
|
21
|
+
"lytical",
|
|
22
|
+
"node",
|
|
23
|
+
"typescript"
|
|
24
|
+
],
|
|
25
|
+
"author": {
|
|
26
|
+
"email": "barry.hayles@lytical.com",
|
|
27
|
+
"name": "Barry Hayles",
|
|
28
|
+
"url": "https://www.lytical.com"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "github",
|
|
32
|
+
"url": "git+https://github.com/lytical/app.git"
|
|
33
|
+
},
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@lytical/ioc": "^1.0.0",
|
|
37
|
+
"express": "^5.2.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/chai": "^5.2.3",
|
|
41
|
+
"@types/express": "^5.0.6",
|
|
42
|
+
"@types/find-root": "^1.1.4",
|
|
43
|
+
"@types/mocha": "^10.0.10",
|
|
44
|
+
"@types/node": "^25.0.9",
|
|
45
|
+
"chai": "^6.2.2",
|
|
46
|
+
"gulp": "^5.0.1",
|
|
47
|
+
"gulp-clean": "^0.4.0",
|
|
48
|
+
"gulp-uglify-es": "^3.0.0",
|
|
49
|
+
"mocha": "^11.7.5",
|
|
50
|
+
"pump": "^3.0.3",
|
|
51
|
+
"rimraf": "^6.1.2",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"find-root": "^1.1.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* @preserve
|
|
2
|
+
(c) 2025 lytical, inc. all rights are reserved.
|
|
3
|
+
lytical(r) is a registered trademark of lytical, inc.
|
|
4
|
+
please refer to your license agreement on the use of this file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Server } from 'node:net';
|
|
8
|
+
import type { Application } from 'express';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* server configuration type
|
|
12
|
+
* @description
|
|
13
|
+
* the configuration used when creating or modifying the server before it is started.
|
|
14
|
+
*/
|
|
15
|
+
export type app_server_cfg_t = {
|
|
16
|
+
/**
|
|
17
|
+
* the express application instance
|
|
18
|
+
* @description
|
|
19
|
+
* the express application instance that will be used to create the server
|
|
20
|
+
* add any middleware to this application before auto registered routes are added.
|
|
21
|
+
*/
|
|
22
|
+
express: Application;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* the root route
|
|
26
|
+
* @description
|
|
27
|
+
* override and provide the root route to be used. if not provided, the default '/api' route will be used.
|
|
28
|
+
*/
|
|
29
|
+
root_route: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* the server instance
|
|
33
|
+
* @description
|
|
34
|
+
* provide the server instance to be used. if not provided, a default http server will be created.
|
|
35
|
+
*/
|
|
36
|
+
server?: Server;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* wait_for
|
|
40
|
+
* @description
|
|
41
|
+
* can be used to delay until some asynchronous operation is complete.
|
|
42
|
+
*/
|
|
43
|
+
wait_for: Promise<any>[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* listening configuration type
|
|
48
|
+
* @description
|
|
49
|
+
* the configuration used when starting the server listening.
|
|
50
|
+
*/
|
|
51
|
+
export type app_listening_cfg_t = {
|
|
52
|
+
/**
|
|
53
|
+
* the maximum length of the queue of pending connections
|
|
54
|
+
* @description
|
|
55
|
+
* provide the maximum length of the queue of pending connections. the default is 511 (node.js default).
|
|
56
|
+
*/
|
|
57
|
+
backlog?: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* the express application instance
|
|
61
|
+
* @description
|
|
62
|
+
* add any middleware to this application after auto registered routes are added.
|
|
63
|
+
*/
|
|
64
|
+
express: Application;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* the hostname to listen on
|
|
68
|
+
* @description
|
|
69
|
+
* provide the hostname to listen on. if not provided, the server will listen on all available interfaces.
|
|
70
|
+
*/
|
|
71
|
+
hostname?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* the port to listen on
|
|
75
|
+
* @description
|
|
76
|
+
* provide the port to listen on. if not provided, the server will listen on port process.env.PORT or 3000.
|
|
77
|
+
*/
|
|
78
|
+
port?: number;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* the server instance
|
|
82
|
+
* @description
|
|
83
|
+
* the server instance that will be used.
|
|
84
|
+
* use to add listeners before the server is started.
|
|
85
|
+
*/
|
|
86
|
+
server: Server;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* wait_for
|
|
90
|
+
* @description
|
|
91
|
+
* can be used to delay until some asynchronous operation is complete.
|
|
92
|
+
* */
|
|
93
|
+
wait_for: Promise<any>[];
|
|
94
|
+
};
|