Enhance WebSocket server with new HTTP server implementation and type assertions
* Updated package.json to specify the entry point for the development script and added morgan as a dependency. * Introduced HttpServer class for handling HTTP requests with Express and integrated logging. * Added new assertion methods in the Assert class for better type validation. * Created IRoutes interface to define route handling structure. * Added optional and nullable type definitions for improved type safety. * Implemented initial server setup in src/index.ts.
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
"lint": "eslint --max-warnings 0 .",
|
"lint": "eslint --max-warnings 0 .",
|
||||||
"prelint:fix": "npm run format",
|
"prelint:fix": "npm run format",
|
||||||
"lint:fix": "eslint --fix .",
|
"lint:fix": "eslint --fix .",
|
||||||
"dev": "tsx watch --clear-screen=false",
|
"dev": "tsx watch --clear-screen=false src/index.ts",
|
||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"author": "Alexander Zinn",
|
"author": "Alexander Zinn",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"@eslint/js": "9.36.0",
|
"@eslint/js": "9.36.0",
|
||||||
"@eslint/json": "0.13.2",
|
"@eslint/json": "0.13.2",
|
||||||
"@eslint/markdown": "7.3.0",
|
"@eslint/markdown": "7.3.0",
|
||||||
|
"@types/morgan": "1.9.10",
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
"eslint": "9.36.0",
|
"eslint": "9.36.0",
|
||||||
"globals": "16.4.0",
|
"globals": "16.4.0",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@techniker-me/logger": "0.0.15",
|
"@techniker-me/logger": "0.0.15",
|
||||||
"@techniker-me/tools": "2025.0.16"
|
"@techniker-me/tools": "2025.0.16",
|
||||||
|
"morgan": "1.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Web/WebSocket/websocket/server/src/index.ts
Normal file
18
Web/WebSocket/websocket/server/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import HttpServer from './net/http/HttpServer';
|
||||||
|
import {LoggerFactory} from '@techniker-me/logger';
|
||||||
|
|
||||||
|
const logger = LoggerFactory.getLogger('Server');
|
||||||
|
|
||||||
|
const httpServer = new HttpServer('http', 3000, {
|
||||||
|
getGETRoutes: () => ({}),
|
||||||
|
getPOSTRoutes: () => ({}),
|
||||||
|
getPUTRoutes: () => ({}),
|
||||||
|
getPATCHRoutes: () => ({}),
|
||||||
|
getDELETERoutes: () => ({}),
|
||||||
|
}, {}, '', [], '', {});
|
||||||
|
|
||||||
|
httpServer.on('error', () => {
|
||||||
|
logger.error('[HttpServer] Error');
|
||||||
|
});
|
||||||
|
|
||||||
|
httpServer.start();
|
||||||
@@ -11,7 +11,7 @@ export default class Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isDefined(name: string, value: unknown): asserts value is undefined | null {
|
public static isDefined(name: string, value: unknown): asserts value is NonNullable<typeof value> {
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
throw new Error(`[${name}] must be defined instead received [${typeof value}]`);
|
throw new Error(`[${name}] must be defined instead received [${typeof value}]`);
|
||||||
}
|
}
|
||||||
@@ -73,6 +73,37 @@ export default class Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isFunction(name: string, value: unknown): asserts value is Function {
|
||||||
|
if (typeof value !== 'function') {
|
||||||
|
throw new Error(`[${name}] must be a function, instead received [${typeof value}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static satisfiesInterface<T>(name: string, obj: unknown, requiredProps: (keyof T)[]): asserts obj is T {
|
||||||
|
Assert.isObject(name, obj);
|
||||||
|
|
||||||
|
for (const prop of requiredProps) {
|
||||||
|
if (!(prop in (obj as any))) {
|
||||||
|
throw new Error(`[${name}] missing required property: ${String(prop)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isArray<T>(name: string, value: unknown): asserts value is T[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isStringArray(name: string, value: unknown): asserts value is string[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
throw new Error(`[${name}] must be an array, instead received [${typeof value}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of value) {
|
||||||
|
Assert.isString(name, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static isObject<T>(name: string, value: unknown): asserts value is T {
|
public static isObject<T>(name: string, value: unknown): asserts value is T {
|
||||||
if (value === null || typeof value !== 'object') {
|
if (value === null || typeof value !== 'object') {
|
||||||
throw new Error(`[${name}] must be an object, instead received [${typeof value}]`);
|
throw new Error(`[${name}] must be an object, instead received [${typeof value}]`);
|
||||||
@@ -85,6 +116,16 @@ export default class Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isInstance<T>(name: string, parentClass: new (...args: any[]) => T, object: unknown): asserts object is T {
|
||||||
|
if (object === null || object === undefined || typeof object !== 'object') {
|
||||||
|
throw new Error(`[${name}] must be an instance of [${parentClass.constructor.name}], instead received [${typeof object}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof parentClass)) {
|
||||||
|
throw new Error(`[${name}] must be an instance of [${parentClass.constructor.name}], instead received [${object.constructor.name}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static _isBoolean(value: unknown): value is boolean {
|
private static _isBoolean(value: unknown): value is boolean {
|
||||||
return typeof value === 'boolean';
|
return typeof value === 'boolean';
|
||||||
}
|
}
|
||||||
|
|||||||
68
Web/WebSocket/websocket/server/src/net/http/HttpServer.ts
Normal file
68
Web/WebSocket/websocket/server/src/net/http/HttpServer.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Nullable } from '../../types/optional';
|
||||||
|
import Assert from '../../lang/Assert';
|
||||||
|
import type {Server} from 'node:http';
|
||||||
|
import http from 'node:http';
|
||||||
|
import {EventEmitter} from 'node:events';
|
||||||
|
import type {ILogger} from '@techniker-me/logger';
|
||||||
|
import {LoggerFactory} from '@techniker-me/logger';
|
||||||
|
import IRoutes from './IRoutes';
|
||||||
|
import {Subject} from '@techniker-me/tools';
|
||||||
|
import express from 'express';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
|
||||||
|
export default class HttpServer {
|
||||||
|
private readonly _logger: ILogger = LoggerFactory.getLogger('HttpServer');
|
||||||
|
private readonly _protocol: 'http' | 'https';
|
||||||
|
private readonly _port: number;
|
||||||
|
private readonly _routes: IRoutes;
|
||||||
|
private readonly _viewsPath: object;
|
||||||
|
private readonly _viewParameters: string;
|
||||||
|
private readonly _resourcesPaths: string[];
|
||||||
|
private readonly _favicon: string;
|
||||||
|
private readonly _cors: object;
|
||||||
|
private readonly _app: Subject<Nullable<express.Application>>;
|
||||||
|
private readonly _eventEmitter: EventEmitter;
|
||||||
|
private readonly _server: Subject<Nullable<Server>>;
|
||||||
|
|
||||||
|
constructor(protocol: 'https' | 'http', port: number, routes: IRoutes, viewsPath: object, viewParameters: string, resourcesPaths: string[], favicon: string, cors: object) {
|
||||||
|
Assert.isString('protocol', protocol);
|
||||||
|
Assert.isNumber('port', port);
|
||||||
|
Assert.satisfiesInterface ('routes', routes, ['getGETRoutes', 'getPOSTRoutes', 'getPUTRoutes', 'getPATCHRoutes', 'getDELETERoutes']);
|
||||||
|
Assert.isObject('viewsPath', viewsPath);
|
||||||
|
Assert.isString('viewParameters', viewParameters);
|
||||||
|
Assert.isStringArray('resourcesPaths', resourcesPaths);
|
||||||
|
Assert.isString('favicon', favicon);
|
||||||
|
Assert.isObject('cors', cors);
|
||||||
|
|
||||||
|
this._protocol = protocol;
|
||||||
|
this._port = port;
|
||||||
|
this._routes = routes;
|
||||||
|
this._viewsPath = viewsPath;
|
||||||
|
this._viewParameters = viewParameters;
|
||||||
|
this._resourcesPaths = resourcesPaths;
|
||||||
|
this._favicon = favicon;
|
||||||
|
this._cors = cors;
|
||||||
|
this._app = new Subject<Nullable<express.Application>>(null);
|
||||||
|
this._eventEmitter = new EventEmitter();
|
||||||
|
this._server = new Subject<Nullable<Server>>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
const app = this._app.value = express();
|
||||||
|
|
||||||
|
app.use(morgan('common'));
|
||||||
|
this._server.value = http.createServer(app);
|
||||||
|
|
||||||
|
this._server.value.listen(this._port, () => {
|
||||||
|
this._logger.info(`Server is running on port ${this._port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public on(event: string, handler: () => void): void {
|
||||||
|
Assert.isNonEmptyString('event', event);
|
||||||
|
Assert.isFunction('handler', handler);
|
||||||
|
|
||||||
|
this._eventEmitter.on(event, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Web/WebSocket/websocket/server/src/net/http/IRoutes.ts
Normal file
8
Web/WebSocket/websocket/server/src/net/http/IRoutes.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export default interface IRoutes {
|
||||||
|
getGETRoutes(): Record<string, () => (Promise<void> | void);
|
||||||
|
getPOSTRoutes(): Record<string, () => (Promise<void> | void);
|
||||||
|
getPUTRoutes(): Record<string, () => (Promise<void> | void);
|
||||||
|
getPATCHRoutes(): Record<string, () => (Promise<void> | void);
|
||||||
|
getDELETERoutes(): Record<string, () => (Promise<void> | void);
|
||||||
|
}
|
||||||
2
Web/WebSocket/websocket/server/src/types/optional.ts
Normal file
2
Web/WebSocket/websocket/server/src/types/optional.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export type Optional<T> = T | undefined | null;
|
||||||
|
export type Nullable<T> = T | null;
|
||||||
Reference in New Issue
Block a user