Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: ['src/**/*.ts', '!src/**/index.ts', '!src/**/*.interface.ts'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.spec.json',
},
},
globals: {},
setupFilesAfterEnv: ["./jest.setup.js"],
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{tsconfig: './tsconfig.spec.json'},
],
}
};
3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
jest.setTimeout(30000);

require("reflect-metadata");
14,437 changes: 5,211 additions & 9,226 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 22 additions & 36 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,34 @@
"test:watch": "jest --watch",
"test:ci": "jest --runInBand --no-cache --coverage --verbose"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.md": [
"npm run prettier:fix"
],
"*.ts": [
"npm run prettier:fix"
]
},
"dependencies": {
"@types/path-to-regexp": "^1.7.0",
"class-transformer": "^0.4.0",
"glob": "^7.1.7",
"path-to-regexp": "^6.2.0",
"class-transformer": "^0.5.1",
"glob": "^8.0.3",
"path-to-regexp": "^6.2.1",
"reflect-metadata": "^0.1.13",
"socket.io": "^2.4.0"
"socket.io": "^4.5.4"
},
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/jest": "^26.0.23",
"@types/node": "^15.12.1",
"@types/glob": "^8.0.0",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.17",
"@types/path-to-regexp": "^1.7.0",
"@types/socket.io": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^24.3.6",
"express": "^4.17.1",
"husky": "^4.3.8",
"jest": "^26.6.3",
"lint-staged": "^11.0.0",
"prettier": "^2.3.1",
"reflect-metadata": "0.1.13",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.1.7",
"express": "^4.18.2",
"husky": "^8.0.0",
"jest": "^29.3.1",
"lint-staged": "^13.1.0",
"prettier": "^2.8.1",
"rimraf": "3.0.2",
"socket.io-client": "^2.4.0",
"ts-jest": "^26.5.6",
"ts-node": "^10.0.0",
"socket.io-client": "^4.5.4",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typedi": "^0.10.0",
"typescript": "^4.3.2"
"typescript": "^4.9.4"
}
}
34 changes: 17 additions & 17 deletions src/SocketControllerExecutor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MetadataBuilder } from './metadata-builder/MetadataBuilder';
import { ActionMetadata } from './metadata/ActionMetadata';
import { classToPlain, ClassTransformOptions, plainToClass } from 'class-transformer';
import { instanceToPlain, ClassTransformOptions, plainToInstance } from 'class-transformer';
import { ActionTypes } from './metadata/types/ActionTypes';
import { ParamMetadata } from './metadata/ParamMetadata';
import { ParameterParseJsonError } from './error/ParameterParseJsonError';
Expand All @@ -20,19 +20,19 @@ export class SocketControllerExecutor {
* Indicates if class-transformer package should be used to perform message body serialization / deserialization.
* By default its enabled.
*/
useClassTransformer: boolean;
useClassTransformer?: boolean;

/**
* Global class transformer options passed to class-transformer during classToPlain operation.
* This operation is being executed when server returns response to user.
*/
classToPlainTransformOptions: ClassTransformOptions;
classToPlainTransformOptions?: ClassTransformOptions;

/**
* Global class transformer options passed to class-transformer during plainToClass operation.
* Global class transformer options passed to class-transformer during plainToInstance operation.
* This operation is being executed when parsing user parameters.
*/
plainToClassTransformOptions: ClassTransformOptions;
plainToClassTransformOptions?: ClassTransformOptions;

// -------------------------------------------------------------------------
// Private properties
Expand Down Expand Up @@ -68,7 +68,7 @@ export class SocketControllerExecutor {
const middlewares = this.metadataBuilder.buildMiddlewareMetadata(classes);

middlewares
.sort((middleware1, middleware2) => middleware1.priority - middleware2.priority)
.sort((middleware1, middleware2) => (middleware1.priority || 0) - (middleware2.priority || 0))
.forEach(middleware => {
this.io.use((socket: any, next: (err?: any) => any) => {
middleware.instance.use(socket, next);
Expand All @@ -91,8 +91,8 @@ export class SocketControllerExecutor {

// register controllers with namespaces
controllersWithNamespaces.forEach(controller => {
let namespace: string | RegExp = controller.namespace;
if (!(namespace instanceof RegExp)) {
let namespace: string | RegExp | undefined = controller.namespace;
if (namespace && !(namespace instanceof RegExp)) {
namespace = pathToRegexp(namespace);
}
this.io.of(namespace).on('connection', (socket: any) => this.handleConnection([controller], socket));
Expand All @@ -103,7 +103,7 @@ export class SocketControllerExecutor {

private handleConnection(controllers: ControllerMetadata[], socket: any) {
controllers.forEach(controller => {
controller.actions.forEach(action => {
(controller.actions || []).forEach(action => {
if (action.type === ActionTypes.CONNECT) {
this.handleAction(action, { socket: socket })
.then(result => this.handleSuccessResult(result, action, socket))
Expand All @@ -128,7 +128,7 @@ export class SocketControllerExecutor {

private handleAction(action: ActionMetadata, options: { socket?: any; data?: any }): Promise<any> {
// compute all parameters
const paramsPromises = action.params
const paramsPromises = (action.params || [])
.sort((param1, param2) => param1.index - param2.index)
.map(param => {
if (param.type === ParamTypes.CONNECTED_SOCKET) {
Expand Down Expand Up @@ -203,7 +203,7 @@ export class SocketControllerExecutor {
const parseValue = typeof value === 'string' ? JSON.parse(value) : value;
if (paramMetadata.reflectedType !== Object && paramMetadata.reflectedType && this.useClassTransformer) {
const options = paramMetadata.classTransformOptions || this.plainToClassTransformOptions;
return plainToClass(paramMetadata.reflectedType, parseValue, options);
return plainToInstance(paramMetadata.reflectedType as never, parseValue as never, options);
} else {
return parseValue;
}
Expand All @@ -216,7 +216,7 @@ export class SocketControllerExecutor {
if (result !== null && result !== undefined && action.emitOnSuccess) {
const transformOptions = action.emitOnSuccess.classTransformOptions || this.classToPlainTransformOptions;
const transformedResult =
this.useClassTransformer && result instanceof Object ? classToPlain(result, transformOptions) : result;
this.useClassTransformer && result instanceof Object ? instanceToPlain(result, transformOptions) : result;
socket.emit(action.emitOnSuccess.value, transformedResult);
} else if ((result === null || result === undefined) && action.emitOnSuccess && !action.skipEmitOnEmptyResult) {
socket.emit(action.emitOnSuccess.value);
Expand All @@ -225,10 +225,10 @@ export class SocketControllerExecutor {

private handleFailResult(result: any, action: ActionMetadata, socket: any) {
if (result !== null && result !== undefined && action.emitOnFail) {
const transformOptions = action.emitOnSuccess.classTransformOptions || this.classToPlainTransformOptions;
const transformOptions = action.emitOnSuccess?.classTransformOptions || this.classToPlainTransformOptions;
let transformedResult =
this.useClassTransformer && result instanceof Object ? classToPlain(result, transformOptions) : result;
if (result instanceof Error && !Object.keys(transformedResult).length) {
this.useClassTransformer && result instanceof Object ? instanceToPlain(result, transformOptions) : result;
if (result instanceof Error && !Object.keys(transformedResult as never).length) {
transformedResult = result.toString();
}
socket.emit(action.emitOnFail.value, transformedResult);
Expand All @@ -239,8 +239,8 @@ export class SocketControllerExecutor {

private handleNamespaceParams(socket: any, action: ActionMetadata, param: ParamMetadata): any[] {
const keys: any[] = [];
const regexp = pathToRegexp(action.controllerMetadata.namespace, keys);
const parts: any[] = regexp.exec(socket.nsp.name);
const regexp = pathToRegexp(action.controllerMetadata.namespace || '/', keys);
const parts: any[] = regexp.exec(socket.nsp.name as string) || [];
const params: any[] = [];
keys.forEach((key: any, index: number) => {
params[key.name] = this.handleParamFormat(parts[index + 1], param);
Expand Down
2 changes: 1 addition & 1 deletion src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const defaultContainer: { get<T>(someClass: { new (...args: any[]): T } | Functi
})();

let userContainer: { get<T>(someClass: { new (...args: any[]): T } | Function): T };
let userContainerOptions: UseContainerOptions;
let userContainerOptions: UseContainerOptions | undefined;

/**
* Sets container to be used by this library.
Expand Down
19 changes: 12 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ function createExecutor(io: any, options: SocketControllersOptions): void {
const executor = new SocketControllerExecutor(io);

// second import all controllers and middlewares and error handlers
let controllerClasses: Function[];
if (options && options.controllers && options.controllers.length)
let controllerClasses: Function[] = [];
if (options?.controllers?.length) {
controllerClasses = (options.controllers as any[]).filter(controller => controller instanceof Function);
const controllerDirs = (options.controllers as any[]).filter(controller => typeof controller === 'string');
controllerClasses.push(...importClassesFromDirectories(controllerDirs));
const controllerDirs = (options.controllers as any[]).filter(
controller => typeof controller === 'string'
) as string[];
controllerClasses.push(...importClassesFromDirectories(controllerDirs));
}

let middlewareClasses: Function[];
if (options && options.middlewares && options.middlewares.length) {
let middlewareClasses: Function[] = [];
if (options?.middlewares?.length) {
middlewareClasses = (options.middlewares as any[]).filter(controller => controller instanceof Function);
const middlewareDirs = (options.middlewares as any[]).filter(controller => typeof controller === 'string');
const middlewareDirs = (options.middlewares as any[]).filter(
controller => typeof controller === 'string'
) as string[];
middlewareClasses.push(...importClassesFromDirectories(middlewareDirs));
}

Expand Down
32 changes: 16 additions & 16 deletions src/metadata/ActionMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ export class ActionMetadata {
/**
* Action's parameters.
*/
params: ParamMetadata[];
params?: ParamMetadata[];

/**
* Action's result handlers.
*/
results: ResultMetadata[];
results?: ResultMetadata[];

/**
* Message name served by this action.
*/
name: string;
name?: string;

/**
* Class on which's method this action is attached.
Expand Down Expand Up @@ -58,29 +58,29 @@ export class ActionMetadata {
this.type = args.type;
}

// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------

executeAction(params: any[]) {
// TODO: remove fix this eslint warning
// eslint-disable-next-line prefer-spread
return this.controllerMetadata.instance[this.method].apply(this.controllerMetadata.instance, params);
}

// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------

get emitOnSuccess() {
return this.results.find(resultHandler => resultHandler.type === ResultTypes.EMIT_ON_SUCCESS);
return (this.results || []).find(resultHandler => resultHandler.type === ResultTypes.EMIT_ON_SUCCESS);
}

get emitOnFail() {
return this.results.find(resultHandler => resultHandler.type === ResultTypes.EMIT_ON_FAIL);
return (this.results || []).find(resultHandler => resultHandler.type === ResultTypes.EMIT_ON_FAIL);
}

get skipEmitOnEmptyResult() {
return this.results.find(resultHandler => resultHandler.type === ResultTypes.SKIP_EMIT_ON_EMPTY_RESULT);
return (this.results || []).find(resultHandler => resultHandler.type === ResultTypes.SKIP_EMIT_ON_EMPTY_RESULT);
}

// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------

executeAction(params: any[]) {
// TODO: remove fix this eslint warning
// eslint-disable-next-line prefer-spread
return this.controllerMetadata.instance[this.method].apply(this.controllerMetadata.instance, params);
}
}
4 changes: 2 additions & 2 deletions src/metadata/ControllerMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class ControllerMetadata {
/**
* Controller actions.
*/
actions: ActionMetadata[];
actions?: ActionMetadata[];

/**
* Indicates object which is used by this controller.
Expand All @@ -20,7 +20,7 @@ export class ControllerMetadata {
/**
* Base route for all actions registered in this controller.
*/
namespace: string | RegExp;
namespace?: string | RegExp;

// -------------------------------------------------------------------------
// Constructor
Expand Down
2 changes: 1 addition & 1 deletion src/metadata/MiddlewareMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class MiddlewareMetadata {
// -------------------------------------------------------------------------

target: Function;
priority: number;
priority?: number;

// -------------------------------------------------------------------------
// Constructor
Expand Down
6 changes: 3 additions & 3 deletions src/metadata/ParamMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class ParamMetadata {
/**
* Extra parameter value.
*/
value: any;
value?: any;

/**
* Reflected type of the parameter.
Expand All @@ -46,12 +46,12 @@ export class ParamMetadata {
/**
* Transforms the value.
*/
transform: (value: any, socket: any) => Promise<any> | any;
transform?: (value: any, socket: any) => Promise<any> | any;

/**
* Class transform options used to perform plainToClass operation.
*/
classTransformOptions: ClassTransformOptions;
classTransformOptions?: ClassTransformOptions;

// -------------------------------------------------------------------------
// Public Methods
Expand Down
4 changes: 2 additions & 2 deletions src/metadata/ResultMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export class ResultMetadata {

/**
*/
value: any;
value?: any;

classTransformOptions: ClassTransformOptions;
classTransformOptions?: ClassTransformOptions;

// -------------------------------------------------------------------------
// Public Methods
Expand Down
8 changes: 4 additions & 4 deletions src/util/DirectoryExportedClassesLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import * as glob from 'glob';
* Loads all exported classes from the given directory.
*/
export function importClassesFromDirectories(directories: string[], formats = ['.js', '.ts']): Function[] {
const loadFileClasses = function (exported: any, allLoaded: Function[]) {
const loadFileClasses = function (exported: Function | Record<string, Function> | Function[], allLoaded: Function[]) {
if (exported instanceof Function) {
allLoaded.push(exported);
} else if (exported instanceof Object) {
} else if (typeof exported === 'object' && !Array.isArray(exported)) {
Object.keys(exported).forEach(key => loadFileClasses(exported[key], allLoaded));
} else if (exported instanceof Array) {
exported.forEach((i: any) => loadFileClasses(i, allLoaded));
} else if (Array.isArray(exported)) {
exported.forEach((i: Function | Record<string, Function> | Function[]) => loadFileClasses(i, allLoaded));
}

return allLoaded;
Expand Down