From 31517b742ea378f7ade818113f84372ab7e744ef Mon Sep 17 00:00:00 2001 From: sebastianhoitz Date: Thu, 7 Feb 2019 15:41:41 +0100 Subject: [PATCH 1/5] Add prettier and tslint configuration, reformat --- .gitignore | 3 +- .prettierrc | 7 + package.json | 4 + packages/core/src/decorators.ts | 287 +++-- packages/core/src/mapper.ts | 1287 +++++++++++-------- packages/core/src/utils.ts | 138 +- packages/core/src/validation.ts | 278 ++-- packages/core/tests/big-entity.ts | 743 +++++------ packages/core/tests/class-to.spec.ts | 87 +- packages/core/tests/decorator.spec.ts | 531 ++++---- packages/core/tests/entities.ts | 192 +-- packages/core/tests/plain-to.spec.ts | 582 +++++---- packages/core/tests/speed.spec.ts | 280 ++--- packages/core/tests/to-plain.spec.ts | 71 +- packages/core/tests/utils.spec.ts | 79 +- packages/core/tests/validation.spec.ts | 403 +++--- packages/mongo/src/database.ts | 517 ++++---- packages/mongo/src/mapping.ts | 916 ++++++++------ packages/mongo/tests/class-to.spec.ts | 18 +- packages/mongo/tests/mongo-query.spec.ts | 112 +- packages/mongo/tests/mongo.spec.ts | 609 ++++----- packages/mongo/tests/to-class.spec.ts | 1461 +++++++++++----------- packages/mongo/tests/to-mongo.spec.ts | 475 +++---- packages/mongo/tests/to-plain.spec.ts | 83 +- packages/nest/src/nest.ts | 43 +- packages/nest/tests/validation.spec.ts | 188 +-- tslint.json | 5 + 27 files changed, 5162 insertions(+), 4237 deletions(-) create mode 100644 .prettierrc diff --git a/.gitignore b/.gitignore index 2147b1f16..c86acb9f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules .idea lib/ -coverage \ No newline at end of file +coverage +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..1e43a30a1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "arrowParens": "always" +} diff --git a/package.json b/package.json index 9a3517d1c..820273ef7 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,13 @@ "jest": "^23.6.0", "jest-extended": "^0.11.0", "lerna": "^3.4.3", + "prettier": "^1.16.4", "reflect-metadata": "^0.1.12", "ts-jest": "^23.10.4", "ts-node": "^6.0.0", + "tslint": "^5.12.1", + "tslint-config-prettier": "^1.18.0", + "tslint-plugin-prettier": "^2.0.1", "typescript": "^2.9.2" }, "engines": { diff --git a/packages/core/src/decorators.ts b/packages/core/src/decorators.ts index b2ef8af80..cebcd64a2 100644 --- a/packages/core/src/decorators.ts +++ b/packages/core/src/decorators.ts @@ -1,224 +1,271 @@ -import {Types} from "./mapper"; -import {ClassType, getClassName} from "./utils"; -import {AddValidator, PropertyValidator, PropertyValidatorError} from "./validation"; +import { Types } from './mapper'; +import { ClassType, getClassName } from './utils'; +import { + AddValidator, + PropertyValidator, + PropertyValidatorError, +} from './validation'; export function Entity(name: string, collectionName?: string) { - return (target: Object) => { - Reflect.defineMetadata('marshal:entityName', name, target); - Reflect.defineMetadata('marshal:collectionName', collectionName || (name + 's'), target); - }; + return (target: Object) => { + Reflect.defineMetadata('marshal:entityName', name, target); + Reflect.defineMetadata( + 'marshal:collectionName', + collectionName || name + 's', + target + ); + }; } export function DatabaseName(name: string) { - return (target: Object) => { - Reflect.defineMetadata('marshal:databaseName', name, target); - }; + return (target: Object) => { + Reflect.defineMetadata('marshal:databaseName', name, target); + }; } export function Decorator() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:dataDecorator', property, target); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:dataDecorator', property, target); + }; } export function ID() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:idField', property, target); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:idField', property, target); + }; } export function ParentReference() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:parentReference', true, target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:parentReference', true, target, property); + }; } function addMetadataArray(metadataKey: string, target: Object, item: any) { - const array = Reflect.getMetadata(metadataKey, target) || []; - if (-1 === array.indexOf(item)) { - array.push(item); - } + const array = Reflect.getMetadata(metadataKey, target) || []; + if (-1 === array.indexOf(item)) { + array.push(item); + } - Reflect.defineMetadata(metadataKey, array, target); + Reflect.defineMetadata(metadataKey, array, target); } /** * Executes the method when the current class is instantiated and populated. */ -export function OnLoad(options: {fullLoad?: boolean} = {}) { - return (target: Object, property: string) => { - addMetadataArray('marshal:onLoad', target, { - property: property, - options: options - }); - }; +export function OnLoad(options: { fullLoad?: boolean } = {}) { + return (target: Object, property: string) => { + addMetadataArray('marshal:onLoad', target, { + property: property, + options: options, + }); + }; } /** * Exclude in *toMongo and *toPlain. */ export function Exclude() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'all', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'all', target, property); + }; } export function ExcludeToMongo() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'mongo', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'mongo', target, property); + }; } export function ExcludeToPlain() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'plain', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'plain', target, property); + }; } export function registerProperty(target: Object, property: string) { - addMetadataArray('marshal:properties', target, property) + addMetadataArray('marshal:properties', target, property); } - export function Type(type: Types) { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:dataType', type, target, property); - registerProperty(target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:dataType', type, target, property); + registerProperty(target, property); + }; } export function ArrayType() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:isArray', true, target, property); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:isArray', true, target, property); + }; } export function MapType() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:isMap', true, target, property); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:isMap', true, target, property); + }; } export function ClassCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - Type('class')(target, property); - Reflect.defineMetadata('marshal:dataTypeValue', classType, target, property); - Reflect.defineMetadata('marshal:dataTypeValueCircular', true, target, property); - }; + return (target: Object, property: string) => { + Type('class')(target, property); + Reflect.defineMetadata( + 'marshal:dataTypeValue', + classType, + target, + property + ); + Reflect.defineMetadata( + 'marshal:dataTypeValueCircular', + true, + target, + property + ); + }; } export function Class(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error(`${getClassName(target)}::${property} has @Class but argument is empty. Use @ClassCircular(() => YourClass) to work around circular dependencies.`); - } + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @Class but argument is empty. Use @ClassCircular(() => YourClass) to work around circular dependencies.` + ); + } - Type('class')(target, property); - Reflect.defineMetadata('marshal:dataTypeValue', classType, target, property); - }; + Type('class')(target, property); + Reflect.defineMetadata( + 'marshal:dataTypeValue', + classType, + target, + property + ); + }; } export function ClassMap(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error(`${getClassName(target)}::${property} has @ClassMap but argument is empty. Use @ClassMap(() => YourClass) to work around circular dependencies.`); - } + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @ClassMap but argument is empty. Use @ClassMap(() => YourClass) to work around circular dependencies.` + ); + } - Class(classType)(target, property); - MapType()(target, property); - }; + Class(classType)(target, property); + MapType()(target, property); + }; } export function ClassMapCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - ClassCircular(classType)(target, property); - MapType()(target, property); - }; + return (target: Object, property: string) => { + ClassCircular(classType)(target, property); + MapType()(target, property); + }; } export function ClassArray(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error(`${getClassName(target)}::${property} has @ClassArray but argument is empty. Use @ClassArrayCircular(() => YourClass) to work around circular dependencies.`); - } + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @ClassArray but argument is empty. Use @ClassArrayCircular(() => YourClass) to work around circular dependencies.` + ); + } - Class(classType)(target, property); - ArrayType()(target, property); - }; + Class(classType)(target, property); + ArrayType()(target, property); + }; } export function ClassArrayCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - ClassCircular(classType)(target, property); - ArrayType()(target, property); - }; + return (target: Object, property: string) => { + ClassCircular(classType)(target, property); + ArrayType()(target, property); + }; } function concat(...decorators: ((target: Object, property: string) => void)[]) { - return (target: Object, property: string) => { - for (const decorator of decorators) { - decorator(target, property); - } + return (target: Object, property: string) => { + for (const decorator of decorators) { + decorator(target, property); } + }; } export function MongoIdType() { - return Type('objectId'); + return Type('objectId'); } export function UUIDType() { - return Type('uuid'); + return Type('uuid'); } export function DateType() { - return Type('date'); + return Type('date'); } export function BinaryType() { - return Type('binary'); + return Type('binary'); } export function StringType() { - class Validator implements PropertyValidator { - async validate(value: any, target: ClassType, property: string): Promise { - if ('string' !== typeof value) { - return new PropertyValidatorError('No String given'); - } - } + class Validator implements PropertyValidator { + async validate( + value: any, + target: ClassType, + property: string + ): Promise { + if ('string' !== typeof value) { + return new PropertyValidatorError('No String given'); + } } + } - return concat(AddValidator(Validator), Type('string')); + return concat(AddValidator(Validator), Type('string')); } export function AnyType() { - return Type('any'); + return Type('any'); } export function NumberType() { - class Validator implements PropertyValidator { - async validate(value: any, target: ClassType, property: string): Promise { - value = parseFloat(value); - - if (!Number.isFinite(value)) { - return new PropertyValidatorError('No Number given'); - } - } + class Validator implements PropertyValidator { + async validate( + value: any, + target: ClassType, + property: string + ): Promise { + value = parseFloat(value); + + if (!Number.isFinite(value)) { + return new PropertyValidatorError('No Number given'); + } } + } - return concat(AddValidator(Validator), Type('number')); + return concat(AddValidator(Validator), Type('number')); } export function BooleanType() { - return Type('boolean'); + return Type('boolean'); } export function EnumType(type: any, allowLabelsAsValue = false) { - return (target: Object, property: string) => { - Type('enum')(target, property); - Reflect.defineMetadata('marshal:dataTypeValue', type, target, property); - Reflect.defineMetadata('marshal:enum:allowLabelsAsValue', allowLabelsAsValue, target, property); - } + return (target: Object, property: string) => { + Type('enum')(target, property); + Reflect.defineMetadata('marshal:dataTypeValue', type, target, property); + Reflect.defineMetadata( + 'marshal:enum:allowLabelsAsValue', + allowLabelsAsValue, + target, + property + ); + }; } diff --git a/packages/core/src/mapper.ts b/packages/core/src/mapper.ts index a7a05345f..f534d3f9c 100644 --- a/packages/core/src/mapper.ts +++ b/packages/core/src/mapper.ts @@ -1,570 +1,750 @@ import { - ClassType, - isArray, - isObject, - isUndefined, - getEnumKeys, - isValidEnumValue, - getValidEnumValue, - getClassPropertyName, - getClassName, - getEnumLabels + ClassType, + isArray, + isObject, + isUndefined, + getEnumKeys, + isValidEnumValue, + getValidEnumValue, + getClassPropertyName, + getClassName, + getEnumLabels, } from './utils'; -import {isOptional} from "./validation"; +import { isOptional } from './validation'; import * as clone from 'clone'; import * as getParameterNames from 'get-parameter-names'; -import {Buffer} from 'buffer'; - -export type Types = 'objectId' | 'uuid' | 'binary' | 'class' | 'date' | 'string' | 'boolean' | 'number' | 'enum' | 'any'; +import { Buffer } from 'buffer'; + +export type Types = + | 'objectId' + | 'uuid' + | 'binary' + | 'class' + | 'date' + | 'string' + | 'boolean' + | 'number' + | 'enum' + | 'any'; const cache = new Map>(); -function getCachedMetaData(key: string, target: Object, propertyName?: string): any { - let valueMap = cache.get(target); - if (!valueMap) { - valueMap = new Map(); - cache.set(target, valueMap); - } - - const cacheKey = key + '::' + propertyName; - let value = valueMap.get(cacheKey); - - if (undefined === value) { - if (propertyName) { - value = Reflect.getMetadata(key, target, propertyName) - } else { - value = Reflect.getMetadata(key, target); - } - valueMap.set(cacheKey, value || ''); - } - - return value || ''; +function getCachedMetaData( + key: string, + target: Object, + propertyName?: string +): any { + let valueMap = cache.get(target); + if (!valueMap) { + valueMap = new Map(); + cache.set(target, valueMap); + } + + const cacheKey = key + '::' + propertyName; + let value = valueMap.get(cacheKey); + + if (undefined === value) { + if (propertyName) { + value = Reflect.getMetadata(key, target, propertyName); + } else { + value = Reflect.getMetadata(key, target); + } + valueMap.set(cacheKey, value || ''); + } + + return value || ''; } export function getCachedParameterNames(classType: ClassType): string[] { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); - } - - let value = valueMap.get('parameter_names'); - if (!value) { - value = getParameterNames(classType.prototype.constructor); - valueMap.set('parameter_names', value); - } - - return value; + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } + + let value = valueMap.get('parameter_names'); + if (!value) { + value = getParameterNames(classType.prototype.constructor); + valueMap.set('parameter_names', value); + } + + return value; } -export function isCircularDataType(classType: ClassType, propertyName: string): boolean { - return getCachedMetaData('marshal:dataTypeValueCircular', classType.prototype, propertyName) || false; +export function isCircularDataType( + classType: ClassType, + propertyName: string +): boolean { + return ( + getCachedMetaData( + 'marshal:dataTypeValueCircular', + classType.prototype, + propertyName + ) || false + ); } -export function getOnLoad(classType: ClassType): {property: string, options: {fullLoad?: false}}[] { - return getCachedMetaData('marshal:onLoad', classType.prototype) || []; +export function getOnLoad( + classType: ClassType +): { property: string; options: { fullLoad?: false } }[] { + return getCachedMetaData('marshal:onLoad', classType.prototype) || []; } - export interface ResolvedReflectionFound { - resolvedClassType: ClassType; - resolvedPropertyName: string; - type: Types; - typeValue: any; - array: boolean; - map: boolean; + resolvedClassType: ClassType; + resolvedPropertyName: string; + type: Types; + typeValue: any; + array: boolean; + map: boolean; } export type ResolvedReflection = ResolvedReflectionFound | null; -export function getResolvedReflection(classType: ClassType, propertyPath: string): ResolvedReflection { - const names = propertyPath.split('.'); - let resolvedClassType: ClassType = classType; - let resolvedTypeCandidate: Types | undefined; - let resolvedClassTypeCandidate: ClassType | undefined = undefined; - let resolvedPropertyName: string = ''; - let inArrayOrMap = false; - let inClassField = false; - let isArray = false; - let isMap = false; - - for (let i = 0; i < names.length; i++) { - const name = names[i]; - - if (inArrayOrMap) { - if (inClassField && resolvedClassTypeCandidate) { - const {type} = getReflectionType(resolvedClassTypeCandidate, name); - if (!type) { - return null; - } - inClassField = false; - inArrayOrMap = false; - resolvedClassType = resolvedClassTypeCandidate; - } else { - inClassField = true; - continue; - } +export function getResolvedReflection( + classType: ClassType, + propertyPath: string +): ResolvedReflection { + const names = propertyPath.split('.'); + let resolvedClassType: ClassType = classType; + let resolvedTypeCandidate: Types | undefined; + let resolvedClassTypeCandidate: ClassType | undefined; + let resolvedPropertyName: string = ''; + let inArrayOrMap = false; + let inClassField = false; + let isArray = false; + let isMap = false; + + for (let i = 0; i < names.length; i++) { + const name = names[i]; + + if (inArrayOrMap) { + if (inClassField && resolvedClassTypeCandidate) { + const { type } = getReflectionType(resolvedClassTypeCandidate, name); + if (!type) { + return null; } + inClassField = false; + inArrayOrMap = false; + resolvedClassType = resolvedClassTypeCandidate; + } else { + inClassField = true; + continue; + } + } - const {type, typeValue} = getReflectionType(resolvedClassType, name); + const { type, typeValue } = getReflectionType(resolvedClassType, name); - if (!type) { - return null; - } + if (!type) { + return null; + } + + resolvedPropertyName = name; + isArray = isArrayType(resolvedClassType, resolvedPropertyName); + isMap = isMapType(resolvedClassType, resolvedPropertyName); + if (isArray || isMap) { + inArrayOrMap = true; + } + + if (type === 'class') { + resolvedClassTypeCandidate = typeValue; + if (resolvedClassTypeCandidate) { + const decorator = getDecorator(resolvedClassTypeCandidate); + if (decorator) { + const { type, typeValue } = getReflectionType( + resolvedClassTypeCandidate, + decorator + ); - resolvedPropertyName = name; - isArray = isArrayType(resolvedClassType, resolvedPropertyName); - isMap = isMapType(resolvedClassType, resolvedPropertyName); - if (isArray || isMap) { + if ( + isArrayType(resolvedClassTypeCandidate, decorator) || + isMapType(resolvedClassTypeCandidate, decorator) + ) { inArrayOrMap = true; - } + } - if (type === 'class') { + if (type === 'class') { + if (!typeValue) { + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} has no class defined. Use Circular decorator if that class really exists.` + ); + } + resolvedTypeCandidate = type; resolvedClassTypeCandidate = typeValue; - if (resolvedClassTypeCandidate) { - const decorator = getDecorator(resolvedClassTypeCandidate); - if (decorator) { - const {type, typeValue} = getReflectionType(resolvedClassTypeCandidate, decorator); - - if (isArrayType(resolvedClassTypeCandidate, decorator) || isMapType(resolvedClassTypeCandidate, decorator)) { - inArrayOrMap = true; - } - - if (type === 'class') { - if (!typeValue) { - throw new Error(`${getClassPropertyName(resolvedClassType, resolvedPropertyName)} has no class defined. Use Circular decorator if that class really exists.`); - } - resolvedTypeCandidate = type; - resolvedClassTypeCandidate = typeValue; - } else if (type){ - if (names[i+1]) { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: type, - typeValue: typeValue, - array: false, - map: false, - } - } else { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: 'class', - typeValue: resolvedClassTypeCandidate, - array: isArray, - map: isMap, - } - } - } else { - return null; - } - } + } else if (type) { + if (names[i + 1]) { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: type, + typeValue: typeValue, + array: false, + map: false, + }; } else { - throw new Error(`${getClassPropertyName(resolvedClassType, resolvedPropertyName)} has no class defined. Use Circular decorator if that class really exists.`); - } - } - } - - if (inClassField) { - isArray = false; - isMap = false; - - if (resolvedTypeCandidate) { - return { + return { resolvedClassType: resolvedClassType, resolvedPropertyName: resolvedPropertyName, - type: resolvedTypeCandidate, + type: 'class', typeValue: resolvedClassTypeCandidate, array: isArray, map: isMap, - }; - } - } - - const {type, typeValue} = getReflectionType(resolvedClassType, resolvedPropertyName); - if (type) { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: type, - typeValue: typeValue, - array: isArray, - map: isMap, - } - } + }; + } + } else { + return null; + } + } + } else { + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} has no class defined. Use Circular decorator if that class really exists.` + ); + } + } + } + + if (inClassField) { + isArray = false; + isMap = false; + + if (resolvedTypeCandidate) { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: resolvedTypeCandidate, + typeValue: resolvedClassTypeCandidate, + array: isArray, + map: isMap, + }; + } + } + + const { type, typeValue } = getReflectionType( + resolvedClassType, + resolvedPropertyName + ); + if (type) { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: type, + typeValue: typeValue, + array: isArray, + map: isMap, + }; + } + + return null; +} - return null; +export function getReflectionType( + classType: ClassType, + propertyName: string +): { type: Types | null; typeValue: any | null } { + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } + + let value = valueMap.get('getReflectionType::' + propertyName); + + if (undefined === value) { + const type = + Reflect.getMetadata( + 'marshal:dataType', + classType.prototype, + propertyName + ) || null; + let typeValue = + Reflect.getMetadata( + 'marshal:dataTypeValue', + classType.prototype, + propertyName + ) || null; + + if (isCircularDataType(classType, propertyName)) { + typeValue = typeValue(); + } + value = { + type: type, + typeValue: typeValue, + }; + + valueMap.set('getReflectionType::' + propertyName, value); + } + + return value; } -export function getReflectionType(classType: ClassType, propertyName: string): { type: Types | null, typeValue: any | null } { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); - } +export function getParentReferenceClass( + classType: ClassType, + propertyName: string +): any { + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } + + let value = valueMap.get('ParentReferenceClass::' + propertyName); + if (undefined === value) { + const parentReference = + Reflect.getMetadata( + 'marshal:parentReference', + classType.prototype, + propertyName + ) || false; + + if (parentReference) { + const { typeValue } = getReflectionType(classType, propertyName); + + if (!typeValue) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} has @ParentReference but no @Class defined.` + ); + } + value = typeValue; + } + valueMap.set('ParentReferenceClass::' + propertyName, value || ''); + } + return value; +} - let value = valueMap.get('getReflectionType::' + propertyName); +export function propertyClassToPlain( + classType: ClassType, + propertyName: string, + propertyValue: any +) { + if (undefined === propertyValue) { + return undefined; + } - if (undefined === value) { - const type = Reflect.getMetadata('marshal:dataType', classType.prototype, propertyName) || null; - let typeValue = Reflect.getMetadata('marshal:dataTypeValue', classType.prototype, propertyName) || null; + if (null === propertyValue) { + return null; + } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - if (isCircularDataType(classType, propertyName)) { - typeValue = typeValue(); - } - value = { - type: type, - typeValue: typeValue - }; + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; - valueMap.set('getReflectionType::' + propertyName, value); + function convert(value: any) { + if ('date' === type && value instanceof Date) { + return value.toJSON(); } - return value; -} - -export function getParentReferenceClass(classType: ClassType, propertyName: string): any { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); + if ('string' === type) { + return String(value); } - let value = valueMap.get('ParentReferenceClass::' + propertyName); - if (undefined === value) { - const parentReference = Reflect.getMetadata('marshal:parentReference', classType.prototype, propertyName) || false; - - if (parentReference) { - const {typeValue} = getReflectionType(classType, propertyName); - - if (!typeValue) { - throw new Error(`${getClassPropertyName(classType, propertyName)} has @ParentReference but no @Class defined.`); - } - value = typeValue; - } - valueMap.set('ParentReferenceClass::' + propertyName, value || ''); + if ('enum' === type) { + //the class instance itself can only have the actual value which can be used in plain as well + return value; } - return value; -} -export function propertyClassToPlain(classType: ClassType, propertyName: string, propertyValue: any) { - - if (undefined === propertyValue) { - return undefined; + if ('binary' === type && value.toString) { + return value.toString('base64'); } - if (null === propertyValue) { - return null; + if ('any' === type) { + return clone(value, false); } - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const {resolvedClassType, resolvedPropertyName, type, typeValue, array, map} = reflection; - - function convert(value: any) { - if ('date' === type && value instanceof Date) { - return value.toJSON(); - } - - if ('string' === type) { - return String(value); - } - - if ('enum' === type) { - //the class instance itself can only have the actual value which can be used in plain as well - return value; - } - if ('binary' === type && value.toString) { - return value.toString('base64'); - } - - if ('any' === type) { - return clone(value, false); - } + if (type === 'class') { + if (!(value instanceof typeValue)) { + throw new Error( + `Could not convert ${getClassPropertyName( + classType, + propertyName + )} since target is not a ` + + `class instance of ${getClassName(typeValue)}. Got ${getClassName( + value + )}` + ); + } - if (type === 'class') { - if (!(value instanceof typeValue)) { - throw new Error( - `Could not convert ${getClassPropertyName(classType, propertyName)} since target is not a `+ - `class instance of ${getClassName(typeValue)}. Got ${getClassName(value)}`); - } - - return classToPlain(typeValue, value); - } - - return value; + return classToPlain(typeValue, value); } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map(v => convert(v)); - } + return value; + } - return []; + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); } - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } - } - return result; + return []; + } + + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } } + return result; + } - return convert(propertyValue); + return convert(propertyValue); } export function propertyPlainToClass( - classType: ClassType, - propertyName: string, - propertyValue: any, - parents: any[], - incomingLevel: number, - state: ToClassState + classType: ClassType, + propertyName: string, + propertyValue: any, + parents: any[], + incomingLevel: number, + state: ToClassState ) { - if (isUndefined(propertyValue)) { - return undefined; - } - - if (null === propertyValue) { - return null; - } + if (isUndefined(propertyValue)) { + return undefined; + } - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; + if (null === propertyValue) { + return null; + } - const {resolvedClassType, resolvedPropertyName, type, typeValue, array, map} = reflection; + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - function convert(value: any) { - if ('date' === type && ('string' === typeof value || 'number' === typeof value)) { - return new Date(value); - } + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; - if ('string' === type && 'string' !== typeof value) { - return String(value); - } + function convert(value: any) { + if ( + 'date' === type && + ('string' === typeof value || 'number' === typeof value) + ) { + return new Date(value); + } - if ('number' === type && 'number' !== typeof value) { - return +value; - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - if ('binary' === type && 'string' === typeof value) { - return new Buffer(value, 'base64'); - } + if ('number' === type && 'number' !== typeof value) { + return +value; + } - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + if ('binary' === type && 'string' === typeof value) { + return new Buffer(value, 'base64'); + } - return true === value; - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - if ('any' === type) { - return clone(value, false); - } + return true === value; + } - if ('enum' === type) { - const allowLabelsAsValue = isEnumAllowLabelsAsValue(resolvedClassType, resolvedPropertyName); - if (undefined !== value && !isValidEnumValue(typeValue, value, allowLabelsAsValue)) { - const valids = getEnumKeys(typeValue); - if (allowLabelsAsValue) { - for (const label of getEnumLabels(typeValue)) { - valids.push(label); - } - } - throw new Error(`Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${valids.join(',')}`); - } + if ('any' === type) { + return clone(value, false); + } - return getValidEnumValue(typeValue, value, allowLabelsAsValue); + if ('enum' === type) { + const allowLabelsAsValue = isEnumAllowLabelsAsValue( + resolvedClassType, + resolvedPropertyName + ); + if ( + undefined !== value && + !isValidEnumValue(typeValue, value, allowLabelsAsValue) + ) { + const valids = getEnumKeys(typeValue); + if (allowLabelsAsValue) { + for (const label of getEnumLabels(typeValue)) { + valids.push(label); + } } + throw new Error( + `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${valids.join( + ',' + )}` + ); + } - if (type === 'class') { - if (value instanceof typeValue) { - //already the target type, this is an error - throw new Error(`${getClassPropertyName(resolvedClassType, resolvedPropertyName)} is already in target format. Are you calling plainToClass() with an class instance?`); - } + return getValidEnumValue(typeValue, value, allowLabelsAsValue); + } - return toClass(typeValue, value, propertyPlainToClass, parents, incomingLevel, state); - } + if (type === 'class') { + if (value instanceof typeValue) { + //already the target type, this is an error + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} is already in target format. Are you calling plainToClass() with an class instance?` + ); + } - return value; + return toClass( + typeValue, + value, + propertyPlainToClass, + parents, + incomingLevel, + state + ); } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map(v => convert(v)); - } + return value; + } - return []; + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); } - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } - } - return result; + return []; + } + + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } } + return result; + } - return convert(propertyValue); + return convert(propertyValue); } - export function cloneClass(target: T, parents?: any[]): T { - return plainToClass(target.constructor as ClassType, classToPlain(target.constructor as ClassType, target), parents); + return plainToClass( + target.constructor as ClassType, + classToPlain(target.constructor as ClassType, target), + parents + ); } export function classToPlain(classType: ClassType, target: T): any { - const result: any = {}; + const result: any = {}; - if (!(target instanceof classType)) { - throw new Error(`Could not classToPlain since target is not a class instance of ${getClassName(classType)}`); - } + if (!(target instanceof classType)) { + throw new Error( + `Could not classToPlain since target is not a class instance of ${getClassName( + classType + )}` + ); + } - const decoratorName = getDecorator(classType); - if (decoratorName) { - return propertyClassToPlain(classType, decoratorName, (target as any)[decoratorName]); - } + const decoratorName = getDecorator(classType); + if (decoratorName) { + return propertyClassToPlain( + classType, + decoratorName, + (target as any)[decoratorName] + ); + } - const propertyNames = getRegisteredProperties(classType); + const propertyNames = getRegisteredProperties(classType); - for (const propertyName of propertyNames) { - if (undefined === (target as any)[propertyName]) { - continue; - } - - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; - } + for (const propertyName of propertyNames) { + if (undefined === (target as any)[propertyName]) { + continue; + } - if (isExcluded(classType, propertyName, 'plain')) { - continue - } + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; + } - result[propertyName] = propertyClassToPlain(classType, propertyName, (target as any)[propertyName]); + if (isExcluded(classType, propertyName, 'plain')) { + continue; } - return result; + result[propertyName] = propertyClassToPlain( + classType, + propertyName, + (target as any)[propertyName] + ); + } + + return result; } export class ToClassState { - onFullLoadCallbacks: (() => void)[] = []; + onFullLoadCallbacks: (() => void)[] = []; } const propertyNamesCache = new Map, string[]>(); -const parentReferencesCache = new Map, {[propertyName: string]: any}>(); +const parentReferencesCache = new Map< + ClassType, + { [propertyName: string]: any } +>(); function findParent(parents: any[], parentType: ClassType): T | null { - for (let i = parents.length - 1; i >= 0; i--) { - if (parents[i] instanceof parentType) { - return parents[i]; - } + for (let i = parents.length - 1; i >= 0; i--) { + if (parents[i] instanceof parentType) { + return parents[i]; } + } - return null; + return null; } export function toClass( + classType: ClassType, + cloned: object, + converter: ( classType: ClassType, - cloned: object, - converter: (classType: ClassType, propertyName: string, propertyValue: any, parents: any[], incomingLevel: number, state: ToClassState) => any, + propertyName: string, + propertyValue: any, parents: any[], - incomingLevel, + incomingLevel: number, state: ToClassState + ) => any, + parents: any[], + incomingLevel, + state: ToClassState ): T { - const assignedViaConstructor: { [propertyName: string]: boolean } = {}; - - let propertyNames = propertyNamesCache.get(classType); - if (!propertyNames) { - propertyNames = getRegisteredProperties(classType); - propertyNamesCache.set(classType, propertyNames); - } + const assignedViaConstructor: { [propertyName: string]: boolean } = {}; - let parentReferences = parentReferencesCache.get(classType); - if (!parentReferences) { - parentReferences = {}; - for (const propertyName of propertyNames) { - parentReferences[propertyName] = getParentReferenceClass(classType, propertyName); - } - parentReferencesCache.set(classType, parentReferences); - } - - const parameterNames = getCachedParameterNames(classType); - - const decoratorName = getDecorator(classType); - const backupedClone = cloned; - - if (!isObject(cloned)) { - cloned = {}; - } - - const args: any[] = []; - for (const propertyName of parameterNames) { - if (decoratorName && propertyName === decoratorName) { - cloned[propertyName] = converter(classType, decoratorName, backupedClone, parents, incomingLevel, state); - } else if (parentReferences[propertyName]) { - const parent = findParent(parents, parentReferences[propertyName]); - if (parent) { - cloned[propertyName] = parent; - } else if (!isOptional(classType, propertyName)) { - throw new Error(`${getClassPropertyName(classType, propertyName)} is in constructor ` + - `has @ParentReference() and NOT @Optional(), but no parent of type ${getClassName(parentReferences[propertyName])} found. ` + - `In case of circular reference, remove '${propertyName}' from constructor, or make sure you provided all parents.` - ); - } - } else { - cloned[propertyName] = converter(classType, propertyName, cloned[propertyName], parents, incomingLevel + 1, state); - } - - assignedViaConstructor[propertyName] = true; - args.push(cloned[propertyName]); - } - - const item = new classType(...args); - - const parentsWithItem = parents.slice(0); - parentsWithItem.push(item); + let propertyNames = propertyNamesCache.get(classType); + if (!propertyNames) { + propertyNames = getRegisteredProperties(classType); + propertyNamesCache.set(classType, propertyNames); + } + let parentReferences = parentReferencesCache.get(classType); + if (!parentReferences) { + parentReferences = {}; for (const propertyName of propertyNames) { - if (assignedViaConstructor[propertyName]) { - //already given via constructor - continue; - } - - if (parentReferences[propertyName]) { - const parent = findParent(parents, parentReferences[propertyName]); - if (parent) { - item[propertyName] = parent; - } else if (!isOptional(classType, propertyName)) { - throw new Error(`${getClassPropertyName(classType, propertyName)} is defined as @ParentReference() and `+ - `NOT @Optional(), but no parent found. Add @Optional() or provide ${propertyName} in parents to fix that.`); - } - } else if (undefined !== cloned[propertyName]) { - item[propertyName] = converter(classType, propertyName, cloned[propertyName], parentsWithItem, incomingLevel + 1, state); - } - } - - const onLoads = getOnLoad(classType); - for (const onLoad of onLoads) { - if (onLoad.options.fullLoad) { - state.onFullLoadCallbacks.push(() => { - item[onLoad.property](); - }); - } else { - item[onLoad.property](); - } - } - - return item; + parentReferences[propertyName] = getParentReferenceClass( + classType, + propertyName + ); + } + parentReferencesCache.set(classType, parentReferences); + } + + const parameterNames = getCachedParameterNames(classType); + + const decoratorName = getDecorator(classType); + const backupedClone = cloned; + + if (!isObject(cloned)) { + cloned = {}; + } + + const args: any[] = []; + for (const propertyName of parameterNames) { + if (decoratorName && propertyName === decoratorName) { + cloned[propertyName] = converter( + classType, + decoratorName, + backupedClone, + parents, + incomingLevel, + state + ); + } else if (parentReferences[propertyName]) { + const parent = findParent(parents, parentReferences[propertyName]); + if (parent) { + cloned[propertyName] = parent; + } else if (!isOptional(classType, propertyName)) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} is in constructor ` + + `has @ParentReference() and NOT @Optional(), but no parent of type ${getClassName( + parentReferences[propertyName] + )} found. ` + + `In case of circular reference, remove '${propertyName}' from constructor, or make sure you provided all parents.` + ); + } + } else { + cloned[propertyName] = converter( + classType, + propertyName, + cloned[propertyName], + parents, + incomingLevel + 1, + state + ); + } + + assignedViaConstructor[propertyName] = true; + args.push(cloned[propertyName]); + } + + const item = new classType(...args); + + const parentsWithItem = parents.slice(0); + parentsWithItem.push(item); + + for (const propertyName of propertyNames) { + if (assignedViaConstructor[propertyName]) { + //already given via constructor + continue; + } + + if (parentReferences[propertyName]) { + const parent = findParent(parents, parentReferences[propertyName]); + if (parent) { + item[propertyName] = parent; + } else if (!isOptional(classType, propertyName)) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} is defined as @ParentReference() and ` + + `NOT @Optional(), but no parent found. Add @Optional() or provide ${propertyName} in parents to fix that.` + ); + } + } else if (undefined !== cloned[propertyName]) { + item[propertyName] = converter( + classType, + propertyName, + cloned[propertyName], + parentsWithItem, + incomingLevel + 1, + state + ); + } + } + + const onLoads = getOnLoad(classType); + for (const onLoad of onLoads) { + if (onLoad.options.fullLoad) { + state.onFullLoadCallbacks.push(() => { + item[onLoad.property](); + }); + } else { + item[onLoad.property](); + } + } + + return item; } /** @@ -572,134 +752,193 @@ export function toClass( * * Returns a new regular object again. */ -export function partialPlainToClass(classType: ClassType, target: {[path: string]: any}, parents?: any[]): Partial<{[F in K]: any}> { - const result: Partial<{[F in K]: any}> = {}; - const state = new ToClassState(); - - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; - result[i] = propertyPlainToClass(classType, i, target[i], parents || [], 1, state); - } - - return result; +export function partialPlainToClass( + classType: ClassType, + target: { [path: string]: any }, + parents?: any[] +): Partial<{ [F in K]: any }> { + const result: Partial<{ [F in K]: any }> = {}; + const state = new ToClassState(); + + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; + result[i] = propertyPlainToClass( + classType, + i, + target[i], + parents || [], + 1, + state + ); + } + + return result; } - /** * Takes a object with partial class fields defined of classType and converts only them into the plain variant. * * Returns a new regular object again. */ -export function partialClassToPlain(classType: ClassType, target: {[path: string]: any}): Partial<{[F in K]: any}> { - const result: Partial<{[F in K]: any}> = {}; - - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; - result[i] = propertyClassToPlain(classType, i, target[i]); - } - - return result; +export function partialClassToPlain( + classType: ClassType, + target: { [path: string]: any } +): Partial<{ [F in K]: any }> { + const result: Partial<{ [F in K]: any }> = {}; + + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; + result[i] = propertyClassToPlain(classType, i, target[i]); + } + + return result; } - /** * Take a regular object with all fields default (missing default to class property default or undefined) * and returns an instance of classType. */ -export function plainToClass(classType: ClassType, target: object, parents?: any[]): T { - const state = new ToClassState(); - const item = toClass(classType, target, propertyPlainToClass, parents || [], 1, state); - - for (const callback of state.onFullLoadCallbacks) { - callback(); - } - - return item; +export function plainToClass( + classType: ClassType, + target: object, + parents?: any[] +): T { + const state = new ToClassState(); + const item = toClass( + classType, + target, + propertyPlainToClass, + parents || [], + 1, + state + ); + + for (const callback of state.onFullLoadCallbacks) { + callback(); + } + + return item; } -export function deleteExcludedPropertiesFor(classType: ClassType, item: any, target: 'mongo' | 'plain') { - for (const propertyName in item) { - if (!item.hasOwnProperty(propertyName)) continue; - if (isExcluded(classType, propertyName, target)) { - delete item[propertyName]; - } +export function deleteExcludedPropertiesFor( + classType: ClassType, + item: any, + target: 'mongo' | 'plain' +) { + for (const propertyName in item) { + if (!item.hasOwnProperty(propertyName)) continue; + if (isExcluded(classType, propertyName, target)) { + delete item[propertyName]; } + } } export function getIdField(classType: ClassType): string | null { - return getCachedMetaData('marshal:idField', classType.prototype) || null; + return getCachedMetaData('marshal:idField', classType.prototype) || null; } export function getIdFieldValue(classType: ClassType, target: any): any { - const id = getIdField(classType); - return id ? target[id] : null; + const id = getIdField(classType); + return id ? target[id] : null; } export function getDecorator(classType: ClassType): string | null { - return getCachedMetaData('marshal:dataDecorator', classType.prototype) || null; + return ( + getCachedMetaData('marshal:dataDecorator', classType.prototype) || null + ); } export function getRegisteredProperties(classType: ClassType): string[] { - return getCachedMetaData('marshal:properties', classType.prototype) || []; + return getCachedMetaData('marshal:properties', classType.prototype) || []; } -export function isArrayType(classType: ClassType, property: string): boolean { - return getCachedMetaData('marshal:isArray', classType.prototype, property) || false; +export function isArrayType( + classType: ClassType, + property: string +): boolean { + return ( + getCachedMetaData('marshal:isArray', classType.prototype, property) || false + ); } -export function isMapType(classType: ClassType, property: string): boolean { - return getCachedMetaData('marshal:isMap', classType.prototype, property) || false; +export function isMapType( + classType: ClassType, + property: string +): boolean { + return ( + getCachedMetaData('marshal:isMap', classType.prototype, property) || false + ); } -export function isEnumAllowLabelsAsValue(classType: ClassType, property: string): boolean { - return getCachedMetaData('marshal:enum:allowLabelsAsValue', classType.prototype, property) || false; +export function isEnumAllowLabelsAsValue( + classType: ClassType, + property: string +): boolean { + return ( + getCachedMetaData( + 'marshal:enum:allowLabelsAsValue', + classType.prototype, + property + ) || false + ); } -export function isExcluded(classType: ClassType, property: string, wantedTarget: 'mongo' | 'plain'): boolean { - const mode = getCachedMetaData('marshal:exclude', classType.prototype, property); - - if ('all' === mode) { - return true; - } - - return mode === wantedTarget; +export function isExcluded( + classType: ClassType, + property: string, + wantedTarget: 'mongo' | 'plain' +): boolean { + const mode = getCachedMetaData( + 'marshal:exclude', + classType.prototype, + property + ); + + if ('all' === mode) { + return true; + } + + return mode === wantedTarget; } export function getEntityName(classType: ClassType): string { - const name = getCachedMetaData('marshal:entityName', classType); + const name = getCachedMetaData('marshal:entityName', classType); - if (!name) { - throw new Error('No @Entity() defined for class ' + classType); - } + if (!name) { + throw new Error('No @Entity() defined for class ' + classType); + } - return name; + return name; } export function getDatabaseName(classType: ClassType): string | null { - return getCachedMetaData('marshal:databaseName', classType) || null; + return getCachedMetaData('marshal:databaseName', classType) || null; } export function getCollectionName(classType: ClassType): string { - const name = getCachedMetaData('marshal:collectionName', classType); + const name = getCachedMetaData('marshal:collectionName', classType); - if (!name) { - throw new Error('No @Entity() defined for class ' + classType); - } + if (!name) { + throw new Error('No @Entity() defined for class ' + classType); + } - return name; + return name; } -export function applyDefaultValues(classType: ClassType, value: { [name: string]: any }): object { - if (!isObject(value)) return {}; +export function applyDefaultValues( + classType: ClassType, + value: { [name: string]: any } +): object { + if (!isObject(value)) return {}; - const valueWithDefaults = value; - const instance = plainToClass(classType, value); + const valueWithDefaults = value; + const instance = plainToClass(classType, value); - for (const i of getRegisteredProperties(classType)) { - if (undefined === value[i]) { - valueWithDefaults[i] = (instance as any)[i]; - } + for (const i of getRegisteredProperties(classType)) { + if (undefined === value[i]) { + valueWithDefaults[i] = (instance as any)[i]; } + } - return valueWithDefaults; -} \ No newline at end of file + return valueWithDefaults; +} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 251d48cf5..0894c6ca2 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,97 +1,121 @@ -import {v4} from 'uuid'; +import { v4 } from 'uuid'; export function getClassName(classType: ClassType | Object): string { - return classType['name'] || (classType.constructor ? classType.constructor.name : ''); + return ( + classType['name'] || + (classType.constructor ? classType.constructor.name : '') + ); } -export function getClassPropertyName(classType: ClassType | Object, propertyName: string): string { - const name = getClassName(classType); +export function getClassPropertyName( + classType: ClassType | Object, + propertyName: string +): string { + const name = getClassName(classType); - return `${name}::${propertyName}`; + return `${name}::${propertyName}`; } export function uuid(): string { - return v4(); + return v4(); } -export interface ClassType { - new(...args: any[]): T; -} +export type ClassType = new (...args: any[]) => T; export function typeOf(obj: any) { - return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); + return {}.toString + .call(obj) + .match(/\s([a-zA-Z]+)/)[1] + .toLowerCase(); } export function isObject(obj: any): obj is object { - if (obj === null) { - return false; - } - return ((typeof obj === 'function') || (typeof obj === 'object' && !isArray(obj))); + if (obj === null) { + return false; + } + return ( + typeof obj === 'function' || (typeof obj === 'object' && !isArray(obj)) + ); } export function isArray(obj: any): obj is any[] { - return Array.isArray(obj) + return Array.isArray(obj); } export function isUndefined(obj: any): obj is undefined { - return undefined === obj; + return undefined === obj; } const cacheEnumLabels = new Map(); export function getEnumLabels(enumDefinition: any) { - let value = cacheEnumLabels.get(enumDefinition); - if (!value) { - value = Object.keys(enumDefinition).filter(v => !Number.isFinite(parseInt(v))); - cacheEnumLabels.set(enumDefinition, value); - } - - return value; + let value = cacheEnumLabels.get(enumDefinition); + if (!value) { + value = Object.keys(enumDefinition).filter( + (v) => !Number.isFinite(parseInt(v)) + ); + cacheEnumLabels.set(enumDefinition, value); + } + + return value; } const cacheEnumKeys = new Map(); export function getEnumKeys(enumDefinition: any): any[] { - let value = cacheEnumKeys.get(enumDefinition); - if (!value) { - const labels = getEnumLabels(enumDefinition); - value = Object.values(enumDefinition) - .filter(v => -1 === labels.indexOf(v as string)) as any[]; + let value = cacheEnumKeys.get(enumDefinition); + if (!value) { + const labels = getEnumLabels(enumDefinition); + value = Object.values(enumDefinition).filter( + (v) => -1 === labels.indexOf(v as string) + ) as any[]; - cacheEnumKeys.set(enumDefinition, value); - } + cacheEnumKeys.set(enumDefinition, value); + } - return value; + return value; } -export function isValidEnumValue(enumDefinition: any, value: any, allowLabelsAsValue = false) { - if (allowLabelsAsValue) { - const labels = getEnumLabels(enumDefinition); - if (-1 !== labels.indexOf(String(value))) { - return true; - } +export function isValidEnumValue( + enumDefinition: any, + value: any, + allowLabelsAsValue = false +) { + if (allowLabelsAsValue) { + const labels = getEnumLabels(enumDefinition); + if (-1 !== labels.indexOf(String(value))) { + return true; } - - const keys = getEnumKeys(enumDefinition); - return -1 !== keys.indexOf(+value) || -1 !== keys.indexOf(value) || -1 !== keys.indexOf(String(value)); + } + + const keys = getEnumKeys(enumDefinition); + return ( + -1 !== keys.indexOf(+value) || + -1 !== keys.indexOf(value) || + -1 !== keys.indexOf(String(value)) + ); } -export function getValidEnumValue(enumDefinition: any, value: any, allowLabelsAsValue = false) { - if (allowLabelsAsValue) { - const labels = getEnumLabels(enumDefinition); - if (-1 !== labels.indexOf(String(value))) { - return enumDefinition[String(value)]; - } +export function getValidEnumValue( + enumDefinition: any, + value: any, + allowLabelsAsValue = false +) { + if (allowLabelsAsValue) { + const labels = getEnumLabels(enumDefinition); + if (-1 !== labels.indexOf(String(value))) { + return enumDefinition[String(value)]; } + } - const keys = getEnumKeys(enumDefinition); - if (-1 !== keys.indexOf(value)) { - return value; - } - if (-1 !== keys.indexOf(+value)) { - return +value; - } - if (-1 !== keys.indexOf(String(value))) { - return String(value); - } -} \ No newline at end of file + const keys = getEnumKeys(enumDefinition); + if (-1 !== keys.indexOf(value)) { + return value; + } + if (-1 !== keys.indexOf(+value)) { + return +value; + } + if (-1 !== keys.indexOf(String(value))) { + return String(value); + } +} diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index ef862c6a2..b1ef467d4 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -1,143 +1,213 @@ -import {ClassType, isArray, isObject, typeOf} from "./utils"; -import {applyDefaultValues, getReflectionType, getRegisteredProperties, isArrayType, isMapType} from "./mapper"; - -export function addValidator(target: Object, property: string, validator: ClassType) { - const validators = Reflect.getMetadata('marshal:validators', target, property) || []; - if (-1 === validators.indexOf(validator)) { - validators.push(validator); - } - - Reflect.defineMetadata('marshal:validators', validators, target, property); +import { ClassType, isArray, isObject, typeOf } from './utils'; +import { + applyDefaultValues, + getReflectionType, + getRegisteredProperties, + isArrayType, + isMapType, +} from './mapper'; + +export function addValidator( + target: Object, + property: string, + validator: ClassType +) { + const validators = + Reflect.getMetadata('marshal:validators', target, property) || []; + if (-1 === validators.indexOf(validator)) { + validators.push(validator); + } + + Reflect.defineMetadata('marshal:validators', validators, target, property); } export class PropertyValidatorError { - message: string; + message: string; - constructor(message: string) { - this.message = message; - } + constructor(message: string) { + this.message = message; + } } export interface PropertyValidator { - validate(value: any, target: ClassType, propertyName: string): Promise; + validate( + value: any, + target: ClassType, + propertyName: string + ): Promise; } -export function getValidators(classType: ClassType, propertyName: string): ClassType[] { - return Reflect.getMetadata('marshal:validators', classType.prototype, propertyName) || []; +export function getValidators( + classType: ClassType, + propertyName: string +): ClassType[] { + return ( + Reflect.getMetadata( + 'marshal:validators', + classType.prototype, + propertyName + ) || [] + ); } export function AddValidator(validator: ClassType) { - return (target: Object, property: string) => { - addValidator(target, property, validator); - }; + return (target: Object, property: string) => { + addValidator(target, property, validator); + }; } export class RequiredValidator implements PropertyValidator { - async validate(value: any, target: ClassType, propertyName: string): Promise { - if (undefined === value) { - return new PropertyValidatorError('Required value is undefined'); - } + async validate( + value: any, + target: ClassType, + propertyName: string + ): Promise { + if (undefined === value) { + return new PropertyValidatorError('Required value is undefined'); } + } } export function Optional() { - return (target: Object, propertyName: string) => { - Reflect.defineMetadata('marshal:isOptional', true, target, propertyName); - }; + return (target: Object, propertyName: string) => { + Reflect.defineMetadata('marshal:isOptional', true, target, propertyName); + }; } -export function isOptional(classType: ClassType, propertyName: string): boolean { - return Reflect.getMetadata('marshal:isOptional', classType.prototype, propertyName) || false; +export function isOptional( + classType: ClassType, + propertyName: string +): boolean { + return ( + Reflect.getMetadata( + 'marshal:isOptional', + classType.prototype, + propertyName + ) || false + ); } export class ValidationError { - path: string; - message: string; + path: string; + message: string; + + constructor(path: string, message: string) { + this.path = path; + this.message = message; + } + + static createInvalidType(path: string, expectedType: string, actual: any) { + return new ValidationError( + path, + `Invalid type. Expected ${expectedType}, but got ${typeOf(actual)}` + ); + } +} - constructor(path: string, message: string) { - this.path = path; - this.message = message; +export async function validate( + classType: ClassType, + item: { [name: string]: any }, + path?: string +): Promise { + const properties = getRegisteredProperties(classType); + const errors: ValidationError[] = []; + + if (!(item instanceof classType)) { + item = applyDefaultValues(classType, item as object); + } + + async function handleValidator( + validatorType: ClassType, + value: any, + propertyName: string, + propertyPath: string + ): Promise { + const instance = new validatorType(); + const result = await instance.validate(value, classType, propertyName); + if (result instanceof PropertyValidatorError) { + errors.push(new ValidationError(propertyPath, result.message)); + return true; } - static createInvalidType(path: string, expectedType: string, actual: any) { - return new ValidationError(path, `Invalid type. Expected ${expectedType}, but got ${typeOf(actual)}`); + return false; + } + + for (const propertyName of properties) { + const { type, typeValue } = getReflectionType(classType, propertyName); + const propertyPath = path ? path + '.' + propertyName : propertyName; + const validators = getValidators(classType, propertyName); + const propertyValue: any = item[propertyName]; + const array = isArrayType(classType, propertyName); + const map = isMapType(classType, propertyName); + + if (!isOptional(classType, propertyName)) { + await handleValidator( + RequiredValidator, + propertyValue, + propertyName, + propertyPath + ); } -} -export async function validate(classType: ClassType, item: {[name: string]: any}, path?: string): Promise { - const properties = getRegisteredProperties(classType); - const errors: ValidationError[] = []; - - if (!(item instanceof classType)) { - item = applyDefaultValues(classType, item as object); + if (undefined === propertyValue) { + //there's no need to continue validation without a value. + continue; } - async function handleValidator( - validatorType: ClassType, - value: any, - propertyName: string, - propertyPath: string - ): Promise { - const instance = new validatorType; - const result = await instance.validate(value, classType, propertyName); - if (result instanceof PropertyValidatorError) { - errors.push(new ValidationError(propertyPath, result.message)); - return true; + if (array) { + if (!isArray(propertyValue)) { + errors.push( + ValidationError.createInvalidType( + propertyPath, + 'array', + propertyValue + ) + ); + continue; + } + } else { + if (type === 'class' || map) { + if (!isObject(propertyValue)) { + errors.push( + ValidationError.createInvalidType( + propertyPath, + 'object', + propertyValue + ) + ); + continue; } - - return false; + } } - for (const propertyName of properties) { - const {type, typeValue} = getReflectionType(classType, propertyName); - const propertyPath = path ? path + '.' + propertyName : propertyName; - const validators = getValidators(classType, propertyName); - const propertyValue: any = item[propertyName]; - const array = isArrayType(classType, propertyName); - const map = isMapType(classType, propertyName); - - if (!isOptional(classType, propertyName)) { - await handleValidator(RequiredValidator, propertyValue, propertyName, propertyPath); - } - - if (undefined === propertyValue) { - //there's no need to continue validation without a value. - continue; - } - - if (array) { - if (!isArray(propertyValue)) { - errors.push(ValidationError.createInvalidType(propertyPath, 'array', propertyValue)); - continue; - } - } else { - if (type === 'class' || map) { - if (!isObject(propertyValue)) { - errors.push(ValidationError.createInvalidType(propertyPath, 'object', propertyValue)); - continue; - } - } - } + for (const validatorType of validators) { + await handleValidator( + validatorType, + propertyValue, + propertyName, + propertyPath + ); + } - for (const validatorType of validators) { - await handleValidator(validatorType, propertyValue, propertyName, propertyPath); - } + if (type === 'class') { + if (map || array) { + if (array && !isArray(propertyValue)) continue; + if (map && !isObject(propertyValue)) continue; - if (type === 'class') { - if (map || array) { - if (array && !isArray(propertyValue)) continue; - if (map && !isObject(propertyValue)) continue; - - for (const i in propertyValue) { - const deepPropertyPath = propertyPath + '.' + i; - errors.push(...await validate(typeValue, propertyValue[i], deepPropertyPath)); - } - } else { - //deep validation - errors.push(...await validate(typeValue, propertyValue, propertyPath)); - } + for (const i in propertyValue) { + const deepPropertyPath = propertyPath + '.' + i; + errors.push( + ...(await validate(typeValue, propertyValue[i], deepPropertyPath)) + ); } + } else { + //deep validation + errors.push( + ...(await validate(typeValue, propertyValue, propertyPath)) + ); + } } + } - return errors; -} \ No newline at end of file + return errors; +} diff --git a/packages/core/tests/big-entity.ts b/packages/core/tests/big-entity.ts index 42129dab7..c11bdf04b 100644 --- a/packages/core/tests/big-entity.ts +++ b/packages/core/tests/big-entity.ts @@ -1,559 +1,562 @@ import { - AnyType, - ArrayType, BooleanType, - Class, - ClassArray, - ClassMap, - DateType, - Entity, EnumType, - ID, MapType, NumberType, Optional, - StringType, - UUIDType -} from "../"; + AnyType, + ArrayType, + BooleanType, + Class, + ClassArray, + ClassMap, + DateType, + Entity, + EnumType, + ID, + MapType, + NumberType, + Optional, + StringType, + UUIDType, +} from '../'; export class JobConfigDocker { - @StringType() - @ArrayType() - env: string[] = []; //e.g. ["PATH=bla"] + @StringType() + @ArrayType() + env: string[] = []; //e.g. ["PATH=bla"] - @StringType() - @ArrayType() - binds: string[] = []; //e.g. ["/tmp:/tmp"] + @StringType() + @ArrayType() + binds: string[] = []; //e.g. ["/tmp:/tmp"] - @StringType() - @ArrayType() - links: string[] = []; //e.g. ["redis3:redis"] + @StringType() + @ArrayType() + links: string[] = []; //e.g. ["redis3:redis"] } export class JobResources { - @NumberType() - cpu: number = 1; + @NumberType() + cpu: number = 1; - @NumberType() - memory: number = 1; + @NumberType() + memory: number = 1; - @NumberType() - gpu: number = 0; + @NumberType() + gpu: number = 0; - @NumberType() - gpuMemory: number = 0; + @NumberType() + gpuMemory: number = 0; } export class JobTaskCommand { - @StringType() - name: string = ''; + @StringType() + name: string = ''; - @StringType() - command: string = ''; + @StringType() + command: string = ''; - constructor(name: string, command: string) { - this.name = name; - this.command = command; - } + constructor(name: string, command: string) { + this.name = name; + this.command = command; + } } export class JobTaskConfigBase { - @StringType() - @ArrayType() - install: string[] | null = null; + @StringType() + @ArrayType() + install: string[] | null = null; - @StringType() - dockerfile: string = ''; + @StringType() + dockerfile: string = ''; - @StringType() - @ArrayType() - install_files: string[] | null = null; + @StringType() + @ArrayType() + install_files: string[] | null = null; - @StringType() - image: string = ''; + @StringType() + image: string = ''; - @StringType() - @ArrayType() - environmentVariables: string[] = []; + @StringType() + @ArrayType() + environmentVariables: string[] = []; - @StringType() - @ArrayType() - servers: string[] = []; + @StringType() + @ArrayType() + servers: string[] = []; - @ClassArray(JobTaskCommand) - commands: JobTaskCommand[] = []; + @ClassArray(JobTaskCommand) + commands: JobTaskCommand[] = []; - @StringType() - @ArrayType() - args: null | string[] = null; + @StringType() + @ArrayType() + args: null | string[] = null; - @Class(JobResources) - resources: JobResources = new JobResources; + @Class(JobResources) + resources: JobResources = new JobResources(); - @Class(JobConfigDocker) - docker: JobConfigDocker = new JobConfigDocker; + @Class(JobConfigDocker) + docker: JobConfigDocker = new JobConfigDocker(); - public isDockerImage(): boolean { - return !!this.image; - } + public isDockerImage(): boolean { + return !!this.image; + } - public hasCommand() { - return this.commands.length > 0; - } + public hasCommand() { + return this.commands.length > 0; + } } export class JobTaskConfig extends JobTaskConfigBase { - /** - * Will be set by config loading. - */ - @StringType() - name: string = ''; - - @NumberType() - replicas: number = 1; - - @StringType() - @ArrayType() - depends_on: string[] = []; + /** + * Will be set by config loading. + */ + @StringType() + name: string = ''; + + @NumberType() + replicas: number = 1; + + @StringType() + @ArrayType() + depends_on: string[] = []; } export class JobConfig extends JobTaskConfigBase { - public static readonly inheritTaskProperties: string[] = [ - 'install', - 'dockerfile', - 'install_files', - 'image', - 'environmentVariables', - 'servers', - 'resources', - 'commands', - 'args', - 'docker' - ]; - - @AnyType() - parameters: { [name: string]: any } = {}; - - @StringType() - @ArrayType() - ignore: string[] = []; - - @NumberType() - priority: number = 0; - - @StringType() - import: string = ''; - - @ClassMap(JobTaskConfig) - tasks: { [name: string]: JobTaskConfig } = {}; + public static readonly inheritTaskProperties: string[] = [ + 'install', + 'dockerfile', + 'install_files', + 'image', + 'environmentVariables', + 'servers', + 'resources', + 'commands', + 'args', + 'docker', + ]; + + @AnyType() + parameters: { [name: string]: any } = {}; + + @StringType() + @ArrayType() + ignore: string[] = []; + + @NumberType() + priority: number = 0; + + @StringType() + import: string = ''; + + @ClassMap(JobTaskConfig) + tasks: { [name: string]: JobTaskConfig } = {}; } export class JobEnvironmentPython { - @StringType() - version?: string; + @StringType() + version?: string; - @StringType() - binary?: string; + @StringType() + binary?: string; - @StringType() - sdkVersion?: string; + @StringType() + sdkVersion?: string; - @StringType() - @MapType() - pipPackages: { [name: string]: string } = {}; + @StringType() + @MapType() + pipPackages: { [name: string]: string } = {}; } export class JobEnvironment { - @StringType() - hostname?: string; + @StringType() + hostname?: string; - @StringType() - username?: string; + @StringType() + username?: string; - @StringType() - platform?: string; + @StringType() + platform?: string; - @StringType() - release?: string; + @StringType() + release?: string; - @StringType() - arch?: string; + @StringType() + arch?: string; - @NumberType() - uptime?: number; + @NumberType() + uptime?: number; - @StringType() - nodeVersion?: string; + @StringType() + nodeVersion?: string; - @StringType() - @MapType() - environmentVariables?: { [name: string]: string }; + @StringType() + @MapType() + environmentVariables?: { [name: string]: string }; - @Class(JobEnvironmentPython) - python?: JobEnvironmentPython; + @Class(JobEnvironmentPython) + python?: JobEnvironmentPython; } export class JobGit { - @StringType() - author?: string; + @StringType() + author?: string; - @StringType() - branch?: string; + @StringType() + branch?: string; - @StringType() - commit?: string; + @StringType() + commit?: string; - @StringType() - message?: string; + @StringType() + message?: string; - @StringType() - origin?: string; + @StringType() + origin?: string; } export class JobDocker { - runOnVersion?: string; + runOnVersion?: string; } export class JobDockerImage { - @StringType() - name?: string; + @StringType() + name?: string; - @StringType() - id?: string; + @StringType() + id?: string; - @NumberType() - size?: number; + @NumberType() + size?: number; - @StringType() - os?: string; + @StringType() + os?: string; - @StringType() - arch?: string; + @StringType() + arch?: string; - @DateType() - created?: Date; + @DateType() + created?: Date; - @StringType() - builtWithDockerVersion?: string; + @StringType() + builtWithDockerVersion?: string; } export enum JobStatus { - creating = 0, - created = 50, //when all files are attached + creating = 0, + created = 50, //when all files are attached - started = 100, + started = 100, - done = 150, //when all tasks are done - aborted = 200, //when at least one task aborted + done = 150, //when all tasks are done + aborted = 200, //when at least one task aborted - failed = 250, //when at least one task failed - crashed = 300, //when at least one task crashed + failed = 250, //when at least one task failed + crashed = 300, //when at least one task crashed } export enum JobTaskStatus { - pending = 0, + pending = 0, - queued = 100, //when the job got a queue position assigned and queue results - assigned = 150, //when a server or multiple servers are assigned and at least one replica is about to start + queued = 100, //when the job got a queue position assigned and queue results + assigned = 150, //when a server or multiple servers are assigned and at least one replica is about to start - started = 300, + started = 300, - //beginning with that ended - done = 500, - aborted = 550, - failed = 600, - crashed = 650, + //beginning with that ended + done = 500, + aborted = 550, + failed = 600, + crashed = 650, } export enum JobTaskInstanceStatus { - pending = 0, + pending = 0, - booting = 200, //is starting the job's task - docker_pull = 220, //joining docker's network - docker_build = 230, //joining docker's network - joining_network = 250, //joining docker's network + booting = 200, //is starting the job's task + docker_pull = 220, //joining docker's network + docker_build = 230, //joining docker's network + joining_network = 250, //joining docker's network - started = 300, + started = 300, - //beginning with that ended - done = 500, - aborted = 550, - failed = 600, - crashed = 650, + //beginning with that ended + done = 500, + aborted = 550, + failed = 600, + crashed = 650, } export class Channel { - @StringType() - @ArrayType() - traces: string[] = []; + @StringType() + @ArrayType() + traces: string[] = []; - @BooleanType() - main?: boolean; + @BooleanType() + main?: boolean; - @BooleanType() - kpi?: boolean; + @BooleanType() + kpi?: boolean; - @NumberType() - kpiTrace: number = 0; + @NumberType() + kpiTrace: number = 0; - @BooleanType() - maxOptimization: boolean = true; + @BooleanType() + maxOptimization: boolean = true; - @AnyType() - @ArrayType() - lastValue: any[] = []; + @AnyType() + @ArrayType() + lastValue: any[] = []; - @AnyType() - xaxis?: object; + @AnyType() + xaxis?: object; - @AnyType() - yaxis?: object; + @AnyType() + yaxis?: object; - @AnyType() - layout?: object; + @AnyType() + layout?: object; } export class JobAssignedResourcesGpu { - @NumberType() - id: number; + @NumberType() + id: number; - @NumberType() - memory: number; + @NumberType() + memory: number; - constructor(id: number, memory: number) { - this.id = id; - this.memory = memory; - } + constructor(id: number, memory: number) { + this.id = id; + this.memory = memory; + } } export class JobAssignedResources { - @NumberType() - cpu: number = 0; + @NumberType() + cpu: number = 0; - @NumberType() - memory: number = 0; + @NumberType() + memory: number = 0; - @ClassArray(JobAssignedResourcesGpu) - gpus: JobAssignedResourcesGpu[] = []; + @ClassArray(JobAssignedResourcesGpu) + gpus: JobAssignedResourcesGpu[] = []; - public getMinGpuMemory(): number { - let minGpuMemory = 0; + public getMinGpuMemory(): number { + let minGpuMemory = 0; - for (const gpu of this.gpus) { - if (gpu.memory < minGpuMemory) minGpuMemory = gpu.memory; - } - - return minGpuMemory; + for (const gpu of this.gpus) { + if (gpu.memory < minGpuMemory) minGpuMemory = gpu.memory; } - public getMaxGpuMemory(): number { - let maxGpuMemory = 0; + return minGpuMemory; + } - for (const gpu of this.gpus) { - if (gpu.memory > maxGpuMemory) maxGpuMemory = gpu.memory; - } + public getMaxGpuMemory(): number { + let maxGpuMemory = 0; - return maxGpuMemory; + for (const gpu of this.gpus) { + if (gpu.memory > maxGpuMemory) maxGpuMemory = gpu.memory; } - public getGpuMemoryRange(): string { - const min = this.getMinGpuMemory(); - const max = this.getMaxGpuMemory(); + return maxGpuMemory; + } - if (min === max) return `${min}`; + public getGpuMemoryRange(): string { + const min = this.getMinGpuMemory(); + const max = this.getMaxGpuMemory(); - return `${min}-${max}`; - } + if (min === max) return `${min}`; + + return `${min}-${max}`; + } } export class JobTaskInstance { - @NumberType() - id: number; + @NumberType() + id: number; - @EnumType(JobTaskInstanceStatus) - status: JobTaskInstanceStatus = JobTaskInstanceStatus.pending; + @EnumType(JobTaskInstanceStatus) + status: JobTaskInstanceStatus = JobTaskInstanceStatus.pending; - @Class(JobEnvironment) - environment: JobEnvironment = new JobEnvironment; + @Class(JobEnvironment) + environment: JobEnvironment = new JobEnvironment(); - @UUIDType() - server?: string; + @UUIDType() + server?: string; - @Class(JobAssignedResources) - assignedResources: JobAssignedResources = new JobAssignedResources; + @Class(JobAssignedResources) + assignedResources: JobAssignedResources = new JobAssignedResources(); - constructor(id: number) { - this.id = id; - } + constructor(id: number) { + this.id = id; + } - public isStarted() { - return this.status >= JobTaskInstanceStatus.booting; - } + public isStarted() { + return this.status >= JobTaskInstanceStatus.booting; + } - public isRunning() { - return this.isStarted() && !this.isEnded(); - } + public isRunning() { + return this.isStarted() && !this.isEnded(); + } - public isEnded() { - return this.status >= JobTaskInstanceStatus.done; - } + public isEnded() { + return this.status >= JobTaskInstanceStatus.done; + } } export class JobTaskQueue { - @NumberType() - position: number = 0; + @NumberType() + position: number = 0; - @NumberType() - tries: number = 0; + @NumberType() + tries: number = 0; - @StringType() - result: string = ''; + @StringType() + result: string = ''; - @DateType() - added: Date = new Date(); + @DateType() + added: Date = new Date(); } export class JobTask { - @Class(JobTaskQueue) - queue: JobTaskQueue = new JobTaskQueue; + @Class(JobTaskQueue) + queue: JobTaskQueue = new JobTaskQueue(); - @StringType() - name: string; + @StringType() + name: string; - @Class(JobDocker) - docker: JobDocker = new JobDocker; + @Class(JobDocker) + docker: JobDocker = new JobDocker(); - @Class(JobDockerImage) - dockerImage: JobDockerImage = new JobDockerImage; + @Class(JobDockerImage) + dockerImage: JobDockerImage = new JobDockerImage(); - // exitCodes: { [key: string]: number } = {}; + // exitCodes: { [key: string]: number } = {}; - // @UUIDType() - // server: string | null = null; + // @UUIDType() + // server: string | null = null; - @EnumType(JobTaskStatus) - status: JobTaskStatus = JobTaskStatus.pending; + @EnumType(JobTaskStatus) + status: JobTaskStatus = JobTaskStatus.pending; - // @Class(JobAssignedResources) - // assignedResources: JobAssignedResources = new JobAssignedResources; + // @Class(JobAssignedResources) + // assignedResources: JobAssignedResources = new JobAssignedResources; - @DateType() - assigned: Date | null = null; + @DateType() + assigned: Date | null = null; - @DateType() - started: Date | null = null; + @DateType() + started: Date | null = null; - @DateType() - ended: Date | null = null; + @DateType() + ended: Date | null = null; - @ClassMap(JobTaskInstance) - private instances: { [name: string]: JobTaskInstance } = {}; + @ClassMap(JobTaskInstance) + private instances: { [name: string]: JobTaskInstance } = {}; - constructor(name: string, replicas: number) { - this.name = name; + constructor(name: string, replicas: number) { + this.name = name; - for (let i = 1; i <= replicas; i++) { - this.instances[this.name + '_' + i] = new JobTaskInstance(i); - } + for (let i = 1; i <= replicas; i++) { + this.instances[this.name + '_' + i] = new JobTaskInstance(i); } - + } } - @Entity('job', 'jobs') export class Job { - @ID() - @UUIDType() - id: string; + @ID() + @UUIDType() + id: string; - @UUIDType() - project: string; + @UUIDType() + project: string; - @NumberType() - number: number = 0; + @NumberType() + number: number = 0; - @NumberType() - version: number = 1; + @NumberType() + version: number = 1; - //obsolete - alive: boolean = false; + //obsolete + alive: boolean = false; - @DateType() - created: Date = new Date(); + @DateType() + created: Date = new Date(); - @DateType() - updated: Date = new Date(); + @DateType() + updated: Date = new Date(); - // exitCode = 0; - @StringType() - author?: string; + // exitCode = 0; + @StringType() + author?: string; - @Class(JobConfig) - config: JobConfig = new JobConfig; + @Class(JobConfig) + config: JobConfig = new JobConfig(); - @Class(JobGit) - @Optional() - git?: JobGit; + @Class(JobGit) + @Optional() + git?: JobGit; - @StringType() - configFile?: string; + @StringType() + configFile?: string; - @EnumType(JobStatus) - status: JobStatus = JobStatus.creating; + @EnumType(JobStatus) + status: JobStatus = JobStatus.creating; - @StringType() - title: string = ''; + @StringType() + title: string = ''; - @ClassMap(JobTask) - tasks: { [name: string]: JobTask } = {}; + @ClassMap(JobTask) + tasks: { [name: string]: JobTask } = {}; - @BooleanType() - runOnCluster: boolean = false; + @BooleanType() + runOnCluster: boolean = false; - @DateType() - assigned: Date | null = null; + @DateType() + assigned: Date | null = null; - @DateType() - started: Date | null = null; + @DateType() + started: Date | null = null; - @DateType() - ended: Date | null = null; + @DateType() + ended: Date | null = null; - @DateType() - ping: Date | null = null; + @DateType() + ping: Date | null = null; - //aka epochs - @NumberType() - iteration: number = 0; + //aka epochs + @NumberType() + iteration: number = 0; - @NumberType() - iterations: number = 0; + @NumberType() + iterations: number = 0; - @NumberType() - secondsPerIteration: number = 0; + @NumberType() + secondsPerIteration: number = 0; - //aka batches - @NumberType() - step: number = 0; + //aka batches + @NumberType() + step: number = 0; - @NumberType() - steps: number = 0; + @NumberType() + steps: number = 0; - @StringType() - stepLabel: string = 'step'; + @StringType() + stepLabel: string = 'step'; - /** - * ETA in seconds. Time left. - */ - @NumberType() - eta: number = 0; + /** + * ETA in seconds. Time left. + */ + @NumberType() + eta: number = 0; - @NumberType() - speed: number = 0; + @NumberType() + speed: number = 0; - @StringType() - speedLabel: string = 'sample/s'; + @StringType() + speedLabel: string = 'sample/s'; - @ClassMap(Channel) - channels: { [name: string]: Channel } = {}; + @ClassMap(Channel) + channels: { [name: string]: Channel } = {}; - constructor(id: string, project: string) { - this.id = id; - this.project = project; - } + constructor(id: string, project: string) { + this.id = id; + this.project = project; + } } diff --git a/packages/core/tests/class-to.spec.ts b/packages/core/tests/class-to.spec.ts index 17af887fc..112bd2bdd 100644 --- a/packages/core/tests/class-to.spec.ts +++ b/packages/core/tests/class-to.spec.ts @@ -1,52 +1,55 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {CollectionWrapper, SimpleModel, StringCollectionWrapper, SubModel} from "./entities"; -import {classToPlain, partialClassToPlain} from "../src/mapper"; +import { + CollectionWrapper, + SimpleModel, + StringCollectionWrapper, + SubModel, +} from './entities'; +import { classToPlain, partialClassToPlain } from '../src/mapper'; test('class-to test simple model', () => { - - expect(() => { - const instance = classToPlain(SimpleModel, { - id: '21313', - name: 'Hi' - }); - }).toThrow(`Could not classToPlain since target is not a class instance`); + expect(() => { + const instance = classToPlain(SimpleModel, { + id: '21313', + name: 'Hi', + }); + }).toThrow(`Could not classToPlain since target is not a class instance`); }); - test('partial', () => { - const i = new SimpleModel('Hi'); - i.children.push(new SubModel('Foo')); - - const plain = partialClassToPlain(SimpleModel, { - name: i.name, - children: i.children, - }); - - expect(plain).not.toBeInstanceOf(SimpleModel); - expect(plain['id']).toBeUndefined(); - expect(plain['type']).toBeUndefined(); - expect(plain.name).toBe('Hi'); - expect(plain.children[0].label).toBe('Foo'); + const i = new SimpleModel('Hi'); + i.children.push(new SubModel('Foo')); + + const plain = partialClassToPlain(SimpleModel, { + name: i.name, + children: i.children, + }); + + expect(plain).not.toBeInstanceOf(SimpleModel); + expect(plain['id']).toBeUndefined(); + expect(plain['type']).toBeUndefined(); + expect(plain.name).toBe('Hi'); + expect(plain.children[0].label).toBe('Foo'); }); test('partial 2', () => { - const i = new SimpleModel('Hi'); - i.children.push(new SubModel('Foo')); - - const plain = partialClassToPlain(SimpleModel, { - 'children.0': i.children[0], - 'stringChildrenCollection': new StringCollectionWrapper(['Foo', 'Bar']), - 'childrenCollection': new CollectionWrapper([new SubModel('Bar3')]), - 'childrenCollection.1': new SubModel('Bar4'), - 'stringChildrenCollection.0': 'Bar2', - 'childrenCollection.2.label': 'Bar5', - }); - - expect(plain['children.0'].label).toBe('Foo'); - expect(plain['stringChildrenCollection']).toEqual(['Foo', 'Bar']); - expect(plain['stringChildrenCollection.0']).toEqual('Bar2'); - expect(plain['childrenCollection']).toEqual([{label: 'Bar3'}]); - expect(plain['childrenCollection.1']).toEqual({label: 'Bar4'}); - expect(plain['childrenCollection.2.label']).toEqual('Bar5'); + const i = new SimpleModel('Hi'); + i.children.push(new SubModel('Foo')); + + const plain = partialClassToPlain(SimpleModel, { + 'children.0': i.children[0], + stringChildrenCollection: new StringCollectionWrapper(['Foo', 'Bar']), + childrenCollection: new CollectionWrapper([new SubModel('Bar3')]), + 'childrenCollection.1': new SubModel('Bar4'), + 'stringChildrenCollection.0': 'Bar2', + 'childrenCollection.2.label': 'Bar5', + }); + + expect(plain['children.0'].label).toBe('Foo'); + expect(plain['stringChildrenCollection']).toEqual(['Foo', 'Bar']); + expect(plain['stringChildrenCollection.0']).toEqual('Bar2'); + expect(plain['childrenCollection']).toEqual([{ label: 'Bar3' }]); + expect(plain['childrenCollection.1']).toEqual({ label: 'Bar4' }); + expect(plain['childrenCollection.2.label']).toEqual('Bar5'); }); diff --git a/packages/core/tests/decorator.spec.ts b/packages/core/tests/decorator.spec.ts index bb0693aee..887d8f209 100644 --- a/packages/core/tests/decorator.spec.ts +++ b/packages/core/tests/decorator.spec.ts @@ -1,300 +1,307 @@ -import 'jest-extended' +import 'jest-extended'; import { - AnyType, - ArrayType, - BooleanType, - Class, - DatabaseName, - Entity, - ID, - MapType, - MongoIdType, - StringType, - getCollectionName, - getDatabaseName, - getEntityName, - getReflectionType, - isArrayType, - isMapType, - plainToClass, - ClassMap, - ClassArray, - ClassCircular, - ClassArrayCircular, - ClassMapCircular, - ParentReference, - getParentReferenceClass, BinaryType, classToPlain -} from "../"; -import {Buffer} from "buffer"; + AnyType, + ArrayType, + BooleanType, + Class, + DatabaseName, + Entity, + ID, + MapType, + MongoIdType, + StringType, + getCollectionName, + getDatabaseName, + getEntityName, + getReflectionType, + isArrayType, + isMapType, + plainToClass, + ClassMap, + ClassArray, + ClassCircular, + ClassArrayCircular, + ClassMapCircular, + ParentReference, + getParentReferenceClass, + BinaryType, + classToPlain, +} from '../'; +import { Buffer } from 'buffer'; test('test entity database', async () => { - - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing1') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - class Child extends DifferentDataBase {} - - @Entity('DifferentDataBase2', 'differentCollection2') - @DatabaseName('testing2') - class Child2 extends DifferentDataBase {} - - expect(getDatabaseName(DifferentDataBase)).toBe('testing1'); - expect(getEntityName(DifferentDataBase)).toBe('DifferentDataBase'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(getDatabaseName(Child2)).toBe('testing2'); - expect(getEntityName(Child2)).toBe('DifferentDataBase2'); - expect(getCollectionName(Child2)).toBe('differentCollection2'); - - expect(getDatabaseName(Child)).toBe('testing1'); - expect(getEntityName(Child)).toBe('DifferentDataBase'); - expect(getCollectionName(Child)).toBe('differentCollection'); + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing1') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + class Child extends DifferentDataBase {} + + @Entity('DifferentDataBase2', 'differentCollection2') + @DatabaseName('testing2') + class Child2 extends DifferentDataBase {} + + expect(getDatabaseName(DifferentDataBase)).toBe('testing1'); + expect(getEntityName(DifferentDataBase)).toBe('DifferentDataBase'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(getDatabaseName(Child2)).toBe('testing2'); + expect(getEntityName(Child2)).toBe('DifferentDataBase2'); + expect(getCollectionName(Child2)).toBe('differentCollection2'); + + expect(getDatabaseName(Child)).toBe('testing1'); + expect(getEntityName(Child)).toBe('DifferentDataBase'); + expect(getCollectionName(Child)).toBe('differentCollection'); }); test('test no entity throw error', () => { - - expect(() => { - class Model {} - getEntityName(Model); - }).toThrowError('No @Entity() defined for class class Model'); - - expect(() => { - class Model {} - getCollectionName(Model); - }).toThrowError('No @Entity() defined for class class Model'); + expect(() => { + class Model {} + getEntityName(Model); + }).toThrowError('No @Entity() defined for class class Model'); + + expect(() => { + class Model {} + getCollectionName(Model); + }).toThrowError('No @Entity() defined for class class Model'); }); test('test decorator errors', () => { - class Sub {} - - expect(() => { - class Model { - @Class(undefined) - sub?: Sub; - } - }).toThrowError('Model::sub has @Class but argument is empty.'); - - expect(() => { - class Model { - @ClassMap(undefined) - sub?: Sub; - } - }).toThrowError('Model::sub has @ClassMap but argument is empty.'); - - expect(() => { - class Model { - @ClassArray(undefined) - sub?: Sub; - } - }).toThrowError('Model::sub has @ClassArray but argument is empty.'); -}); - -test('test decorator ParentReference without class', () => { - class Sub {} - - expect(() => { - class Model { - @ParentReference() - sub?: Sub; - } - getParentReferenceClass(Model, 'sub'); - }).toThrowError('Model::sub has @ParentReference but no @Class defined.'); -}); + class Sub {} -test('test decorator circular', () => { - class Sub {} - - { - class Model { - @ClassCircular(() => Sub) - sub?: Sub; - } - expect(getReflectionType(Model, 'sub')).toEqual({type: 'class', typeValue: Sub}); + expect(() => { + class Model { + @Class(undefined) + sub?: Sub; } + }).toThrowError('Model::sub has @Class but argument is empty.'); - { - class Model { - @ClassMapCircular(() => Sub) - sub?: Sub; - } - expect(getReflectionType(Model, 'sub')).toEqual({type: 'class', typeValue: Sub}); - expect(isMapType(Model, 'sub')).toBeTrue(); + expect(() => { + class Model { + @ClassMap(undefined) + sub?: Sub; } + }).toThrowError('Model::sub has @ClassMap but argument is empty.'); - { - class Model { - @ClassArrayCircular(() => Sub) - sub?: Sub; - } - expect(getReflectionType(Model, 'sub')).toEqual({type: 'class', typeValue: Sub}); - expect(isArrayType(Model, 'sub')).toBeTrue(); + expect(() => { + class Model { + @ClassArray(undefined) + sub?: Sub; } + }).toThrowError('Model::sub has @ClassArray but argument is empty.'); }); +test('test decorator ParentReference without class', () => { + class Sub {} - -test('test properties', () => { - class DataValue { } - class DataValue2 { } - - @Entity('Model') + expect(() => { class Model { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - - @Class(DataValue) - data?: DataValue; - } - - @Entity('SubModel') - class SubModel extends Model { - @Class(DataValue2) - data2?: DataValue2; - } - - { - const {type, typeValue} = getReflectionType(Model, '_id'); - expect(type).toBe('objectId'); - expect(typeValue).toBeNull() - } - - { - const {type, typeValue} = getReflectionType(Model, 'data'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue) - } - - { - const {type, typeValue} = getReflectionType(Model, 'data2'); - expect(type).toBeNull(); - expect(typeValue).toBeNull(); - } - - { - const {type, typeValue} = getReflectionType(SubModel, '_id'); - expect(type).toBe('objectId'); - expect(typeValue).toBeNull() - } - { - const {type, typeValue} = getReflectionType(SubModel, 'data'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue) - } - { - const {type, typeValue} = getReflectionType(SubModel, 'data2'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue2) + @ParentReference() + sub?: Sub; } + getParentReferenceClass(Model, 'sub'); + }).toThrowError('Model::sub has @ParentReference but no @Class defined.'); }); -test('more decorator', () => { - class Model { - @BooleanType() - bool: boolean = false; +test('test decorator circular', () => { + class Sub {} - @AnyType() - whatever: any; + { + class Model { + @ClassCircular(() => Sub) + sub?: Sub; } + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); + } - { - const instance = plainToClass(Model, { - bool: 'wow', - whatever: {'any': false} - }); - - expect(instance.bool).toBeFalse(); - expect(instance.whatever).toEqual({any: false}); + { + class Model { + @ClassMapCircular(() => Sub) + sub?: Sub; } - - { - const instance = plainToClass(Model, { - bool: 'true', - }); - expect(instance.bool).toBeTrue(); + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); + expect(isMapType(Model, 'sub')).toBeTrue(); + } + + { + class Model { + @ClassArrayCircular(() => Sub) + sub?: Sub; } + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); + expect(isArrayType(Model, 'sub')).toBeTrue(); + } +}); - { - const instance = plainToClass(Model, { - bool: '1', - }); - expect(instance.bool).toBeTrue(); - } +test('test properties', () => { + class DataValue {} + class DataValue2 {} + + @Entity('Model') + class Model { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + + @Class(DataValue) + data?: DataValue; + } + + @Entity('SubModel') + class SubModel extends Model { + @Class(DataValue2) + data2?: DataValue2; + } + + { + const { type, typeValue } = getReflectionType(Model, '_id'); + expect(type).toBe('objectId'); + expect(typeValue).toBeNull(); + } - { - const instance = plainToClass(Model, { - bool: 1, - }); - expect(instance.bool).toBeTrue(); - } + { + const { type, typeValue } = getReflectionType(Model, 'data'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue); + } - { - const instance = plainToClass(Model, { - bool: 'false', - }); - expect(instance.bool).toBeFalse(); - } + { + const { type, typeValue } = getReflectionType(Model, 'data2'); + expect(type).toBeNull(); + expect(typeValue).toBeNull(); + } - { - const instance = plainToClass(Model, { - bool: '0', - }); - expect(instance.bool).toBeFalse(); - } + { + const { type, typeValue } = getReflectionType(SubModel, '_id'); + expect(type).toBe('objectId'); + expect(typeValue).toBeNull(); + } + { + const { type, typeValue } = getReflectionType(SubModel, 'data'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue); + } + { + const { type, typeValue } = getReflectionType(SubModel, 'data2'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue2); + } +}); - { - const instance = plainToClass(Model, { - bool: 0, - }); - expect(instance.bool).toBeFalse(); - } +test('more decorator', () => { + class Model { + @BooleanType() + bool: boolean = false; + + @AnyType() + whatever: any; + } + + { + const instance = plainToClass(Model, { + bool: 'wow', + whatever: { any: false }, + }); + + expect(instance.bool).toBeFalse(); + expect(instance.whatever).toEqual({ any: false }); + } + + { + const instance = plainToClass(Model, { + bool: 'true', + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: '1', + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: 1, + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: 'false', + }); + expect(instance.bool).toBeFalse(); + } + + { + const instance = plainToClass(Model, { + bool: '0', + }); + expect(instance.bool).toBeFalse(); + } + + { + const instance = plainToClass(Model, { + bool: 0, + }); + expect(instance.bool).toBeFalse(); + } }); test('more array/map', () => { - class Model { - @BooleanType() - @ArrayType() - bools?: boolean[]; - - @AnyType() - @MapType() - whatever?: any[]; - } - - expect(isArrayType(Model, 'bools')).toBeTrue(); - expect(isMapType(Model, 'whatever')).toBeTrue(); + class Model { + @BooleanType() + @ArrayType() + bools?: boolean[]; + + @AnyType() + @MapType() + whatever?: any[]; + } + + expect(isArrayType(Model, 'bools')).toBeTrue(); + expect(isMapType(Model, 'whatever')).toBeTrue(); }); test('binary', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } - - const {type, typeValue} = getReflectionType(Model, 'preview'); - expect(type).toBe('binary'); - expect(typeValue).toBeNull(); - - const i = new Model(); - expect(i.preview.toString('utf8')).toBe('FooBar'); - - const plain = classToPlain(Model, i); - expect(plain.preview).toBe('Rm9vQmFy'); - expect(plain.preview).toBe(new Buffer('FooBar', 'utf8').toString('base64')); - - const back = plainToClass(Model, plain); - expect(back.preview).toBeInstanceOf(Buffer); - expect(back.preview.toString('utf8')).toBe('FooBar'); - expect(back.preview.length).toBe(6); -}); \ No newline at end of file + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } + + const { type, typeValue } = getReflectionType(Model, 'preview'); + expect(type).toBe('binary'); + expect(typeValue).toBeNull(); + + const i = new Model(); + expect(i.preview.toString('utf8')).toBe('FooBar'); + + const plain = classToPlain(Model, i); + expect(plain.preview).toBe('Rm9vQmFy'); + expect(plain.preview).toBe(new Buffer('FooBar', 'utf8').toString('base64')); + + const back = plainToClass(Model, plain); + expect(back.preview).toBeInstanceOf(Buffer); + expect(back.preview.toString('utf8')).toBe('FooBar'); + expect(back.preview.length).toBe(6); +}); diff --git a/packages/core/tests/entities.ts b/packages/core/tests/entities.ts index 170b38766..072348752 100644 --- a/packages/core/tests/entities.ts +++ b/packages/core/tests/entities.ts @@ -1,147 +1,151 @@ import { - DateType, - Entity, - EnumType, - ID, - NumberType, - StringType, - ClassArray, - ClassMap, - UUIDType, - uuid, - Exclude, - MongoIdType, - Decorator, - Class, ExcludeToMongo, ExcludeToPlain, ArrayType, BooleanType + DateType, + Entity, + EnumType, + ID, + NumberType, + StringType, + ClassArray, + ClassMap, + UUIDType, + uuid, + Exclude, + MongoIdType, + Decorator, + Class, + ExcludeToMongo, + ExcludeToPlain, + ArrayType, + BooleanType, } from '..'; @Entity('sub') export class SubModel { - @StringType() - label: string; + @StringType() + label: string; - constructorUsed = false; + constructorUsed = false; - constructor(label: string) { - this.label = label; - this.constructorUsed = true; - } + constructor(label: string) { + this.label = label; + this.constructorUsed = true; + } } export enum Plan { - DEFAULT, - PRO, - ENTERPRISE, + DEFAULT, + PRO, + ENTERPRISE, } export const now = new Date(); export class CollectionWrapper { - @ClassArray(SubModel) - @Decorator() - public items: SubModel[]; + @ClassArray(SubModel) + @Decorator() + public items: SubModel[]; - constructor(items: SubModel[]) { - this.items = items; - } + constructor(items: SubModel[]) { + this.items = items; + } - public add(item: SubModel) { - this.items.push(item); - } + public add(item: SubModel) { + this.items.push(item); + } } export class StringCollectionWrapper { - @Decorator() - @StringType() - @ArrayType() - public items: string[]; - - constructor(items: string[]) { - this.items = items; - } - - public add(item: string) { - this.items.push(item); - } + @Decorator() + @StringType() + @ArrayType() + public items: string[]; + + constructor(items: string[]) { + this.items = items; + } + + public add(item: string) { + this.items.push(item); + } } @Entity('SimpleModel') export class SimpleModel { - @ID() - @UUIDType() - id: string = uuid(); + @ID() + @UUIDType() + id: string = uuid(); - @StringType() - name: string; + @StringType() + name: string; - @NumberType() - type: number = 0; + @NumberType() + type: number = 0; - @BooleanType() - yesNo: boolean = false; + @BooleanType() + yesNo: boolean = false; - @EnumType(Plan) - plan: Plan = Plan.DEFAULT; + @EnumType(Plan) + plan: Plan = Plan.DEFAULT; - @DateType() - created: Date = now; + @DateType() + created: Date = now; - @ArrayType() - @StringType() - types: string[] = []; + @ArrayType() + @StringType() + types: string[] = []; - @ClassArray(SubModel) - children: SubModel[] = []; + @ClassArray(SubModel) + children: SubModel[] = []; - @ClassMap(SubModel) - childrenMap: {[key: string]: SubModel} = {}; + @ClassMap(SubModel) + childrenMap: { [key: string]: SubModel } = {}; - @Class(CollectionWrapper) - childrenCollection: CollectionWrapper = new CollectionWrapper([]); + @Class(CollectionWrapper) + childrenCollection: CollectionWrapper = new CollectionWrapper([]); - @Class(StringCollectionWrapper) - stringChildrenCollection: StringCollectionWrapper = new StringCollectionWrapper([]); + @Class(StringCollectionWrapper) + stringChildrenCollection: StringCollectionWrapper = new StringCollectionWrapper( + [] + ); - notMapped: {[key: string]: any} = {}; + notMapped: { [key: string]: any } = {}; - @Exclude() - @StringType() - excluded: string = 'default'; + @Exclude() + @StringType() + excluded: string = 'default'; - @ExcludeToMongo() - @StringType() - excludedForMongo: string = 'excludedForMongo'; + @ExcludeToMongo() + @StringType() + excludedForMongo: string = 'excludedForMongo'; - @ExcludeToPlain() - @StringType() - excludedForPlain: string = 'excludedForPlain'; + @ExcludeToPlain() + @StringType() + excludedForPlain: string = 'excludedForPlain'; - constructor(name: string) { - this.name = name; - } + constructor(name: string) { + this.name = name; + } } @Entity('SuperSimple') export class SuperSimple { - @ID() - @MongoIdType() - _id?: string; + @ID() + @MongoIdType() + _id?: string; - @StringType() - name?: string; + @StringType() + name?: string; } @Entity('BaseClass') export class BaseClass { - @ID() - @MongoIdType() - _id?: string; + @ID() + @MongoIdType() + _id?: string; } - @Entity('ChildClass') export class ChildClass extends BaseClass { - @StringType() - name?: string; + @StringType() + name?: string; } - diff --git a/packages/core/tests/plain-to.spec.ts b/packages/core/tests/plain-to.spec.ts index 7313666b7..16e9e652f 100644 --- a/packages/core/tests/plain-to.spec.ts +++ b/packages/core/tests/plain-to.spec.ts @@ -1,279 +1,393 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {Plan, SimpleModel, StringCollectionWrapper, SubModel} from "./entities"; -import {getReflectionType, getResolvedReflection, partialPlainToClass, plainToClass, Types} from "../src/mapper"; -import {ClassType, EnumType} from ".."; -import {DocumentClass} from "./document-scenario/DocumentClass"; -import {PageClass} from "./document-scenario/PageClass"; -import {PageCollection} from "./document-scenario/PageCollection"; +import { + Plan, + SimpleModel, + StringCollectionWrapper, + SubModel, +} from './entities'; +import { + getReflectionType, + getResolvedReflection, + partialPlainToClass, + plainToClass, + Types, +} from '../src/mapper'; +import { ClassType, EnumType } from '..'; +import { DocumentClass } from './document-scenario/DocumentClass'; +import { PageClass } from './document-scenario/PageClass'; +import { PageCollection } from './document-scenario/PageCollection'; test('getResolvedReflection simple', () => { - expect(getResolvedReflection(SimpleModel, 'id')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'id')!.resolvedPropertyName).toBe('id'); - expect(getResolvedReflection(SimpleModel, 'id')!.type).toBe('uuid'); - expect(getResolvedReflection(SimpleModel, 'id')!.typeValue).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'id')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'id')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedPropertyName).toBe('plan'); - expect(getResolvedReflection(SimpleModel, 'plan')!.type).toBe('enum'); - expect(getResolvedReflection(SimpleModel, 'plan')!.typeValue).toBe(Plan); - expect(getResolvedReflection(SimpleModel, 'plan')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'plan')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'children')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'children')!.resolvedPropertyName).toBe('children'); - expect(getResolvedReflection(SimpleModel, 'children')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'children')!.typeValue).toBe(SubModel); - expect(getResolvedReflection(SimpleModel, 'children')!.array).toBe(true); - expect(getResolvedReflection(SimpleModel, 'children')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedPropertyName).toBe('childrenMap'); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.typeValue).toBe(SubModel); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.map).toBe(true); + expect(getResolvedReflection(SimpleModel, 'id')!.resolvedClassType).toBe( + SimpleModel + ); + expect(getResolvedReflection(SimpleModel, 'id')!.resolvedPropertyName).toBe( + 'id' + ); + expect(getResolvedReflection(SimpleModel, 'id')!.type).toBe('uuid'); + expect(getResolvedReflection(SimpleModel, 'id')!.typeValue).toBeNull(); + expect(getResolvedReflection(SimpleModel, 'id')!.array).toBe(false); + expect(getResolvedReflection(SimpleModel, 'id')!.map).toBe(false); + + expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedClassType).toBe( + SimpleModel + ); + expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedPropertyName).toBe( + 'plan' + ); + expect(getResolvedReflection(SimpleModel, 'plan')!.type).toBe('enum'); + expect(getResolvedReflection(SimpleModel, 'plan')!.typeValue).toBe(Plan); + expect(getResolvedReflection(SimpleModel, 'plan')!.array).toBe(false); + expect(getResolvedReflection(SimpleModel, 'plan')!.map).toBe(false); + + expect( + getResolvedReflection(SimpleModel, 'children')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'children')!.resolvedPropertyName + ).toBe('children'); + expect(getResolvedReflection(SimpleModel, 'children')!.type).toBe('class'); + expect(getResolvedReflection(SimpleModel, 'children')!.typeValue).toBe( + SubModel + ); + expect(getResolvedReflection(SimpleModel, 'children')!.array).toBe(true); + expect(getResolvedReflection(SimpleModel, 'children')!.map).toBe(false); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedPropertyName + ).toBe('childrenMap'); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.type).toBe('class'); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.typeValue).toBe( + SubModel + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.array).toBe(false); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.map).toBe(true); }); test('getResolvedReflection deep', () => { - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedClassType).toBe(SubModel); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedPropertyName).toBe('label'); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.type).toBe('string'); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.typeValue).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.resolvedClassType).toBe(SubModel); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.resolvedPropertyName).toBe('label'); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.type).toBe('string'); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.typeValue).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.unknown')).toBeNull(); - - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedPropertyName).toBe('childrenMap'); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.typeValue).toBe(SubModel); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.map).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedClassType + ).toBe(SubModel); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedPropertyName + ).toBe('label'); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.type).toBe( + 'string' + ); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')!.typeValue + ).toBeNull(); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.array).toBe( + false + ); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.map).toBe( + false + ); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! + .resolvedClassType + ).toBe(SubModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! + .resolvedPropertyName + ).toBe('label'); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.type + ).toBe('string'); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.typeValue + ).toBeNull(); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.array + ).toBe(false); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.map).toBe( + false + ); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.unknown') + ).toBeNull(); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedPropertyName + ).toBe('childrenMap'); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.type).toBe( + 'class' + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.typeValue).toBe( + SubModel + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.array).toBe( + false + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.map).toBe( + false + ); }); - test('getResolvedReflection deep decorator', () => { - expect(getResolvedReflection(SimpleModel, 'childrenCollection.0')!).toEqual({ - resolvedClassType: SimpleModel, - resolvedPropertyName: 'childrenCollection', - type: 'class', - typeValue: SubModel, - array: false, - map: false, - }); - - expect(getResolvedReflection(SimpleModel, 'childrenCollection.0.label')!).toEqual({ - resolvedClassType: SubModel, - resolvedPropertyName: 'label', - type: 'string', - typeValue: null, - array: false, - map: false, - }); + expect(getResolvedReflection(SimpleModel, 'childrenCollection.0')!).toEqual({ + resolvedClassType: SimpleModel, + resolvedPropertyName: 'childrenCollection', + type: 'class', + typeValue: SubModel, + array: false, + map: false, + }); + + expect( + getResolvedReflection(SimpleModel, 'childrenCollection.0.label')! + ).toEqual({ + resolvedClassType: SubModel, + resolvedPropertyName: 'label', + type: 'string', + typeValue: null, + array: false, + map: false, + }); }); test('getResolvedReflection decorator string', () => { - expect(getReflectionType(SimpleModel, 'stringChildrenCollection')).toEqual({type: 'class', typeValue: StringCollectionWrapper}); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.resolvedPropertyName).toBe('stringChildrenCollection'); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.typeValue).toBe(StringCollectionWrapper); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.map).toBe(false); + expect(getReflectionType(SimpleModel, 'stringChildrenCollection')).toEqual({ + type: 'class', + typeValue: StringCollectionWrapper, + }); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')! + .resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')! + .resolvedPropertyName + ).toBe('stringChildrenCollection'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.type + ).toBe('class'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.typeValue + ).toBe(StringCollectionWrapper); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.array + ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.map + ).toBe(false); }); test('getResolvedReflection deep decorator string', () => { - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.resolvedClassType).toBe(SimpleModel); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.resolvedPropertyName).toBe('stringChildrenCollection'); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.type).toBe('string'); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.typeValue).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.map).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! + .resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! + .resolvedPropertyName + ).toBe('stringChildrenCollection'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.type + ).toBe('string'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.typeValue + ).toBeNull(); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.array + ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.map + ).toBe(false); }); test('plain-to test simple model', () => { - - const instance = plainToClass(SimpleModel, { - //todo, this should throw an error - id: '21313', - name: 'Hi' - }); - - expect(instance.id).toBe('21313'); - expect(instance.name).toBe('Hi'); + const instance = plainToClass(SimpleModel, { + //todo, this should throw an error + id: '21313', + name: 'Hi', + }); + + expect(instance.id).toBe('21313'); + expect(instance.name).toBe('Hi'); }); - test('partial', () => { - const instance = partialPlainToClass(SimpleModel, { - name: 'Hi', - children: [ - {label: 'Foo'} - ] - }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance.children[0]).toBeInstanceOf(SubModel); - expect(instance.children[0].label).toBe('Foo'); + const instance = partialPlainToClass(SimpleModel, { + name: 'Hi', + children: [{ label: 'Foo' }], + }); + + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance.children[0]).toBeInstanceOf(SubModel); + expect(instance.children[0].label).toBe('Foo'); }); - test('partial 2', () => { - const instance = partialPlainToClass(SimpleModel, { - name: 'Hi', - 'children.0.label': 'Foo' - }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance['children.0.label']).toBe('Foo'); - - expect(partialPlainToClass(SimpleModel, { - 'children.0.label': 2 - })).toEqual({'children.0.label': '2'}); - - const i2 = partialPlainToClass(SimpleModel, { - 'children.0': {'label': 3} - }); - expect(i2['children.0']).toBeInstanceOf(SubModel); - expect(i2['children.0'].label).toBe('3'); + const instance = partialPlainToClass(SimpleModel, { + name: 'Hi', + 'children.0.label': 'Foo', + }); + + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance['children.0.label']).toBe('Foo'); + + expect( + partialPlainToClass(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i2 = partialPlainToClass(SimpleModel, { + 'children.0': { label: 3 }, + }); + expect(i2['children.0']).toBeInstanceOf(SubModel); + expect(i2['children.0'].label).toBe('3'); }); - test('partial 3', () => { - const i2 = partialPlainToClass(SimpleModel, { - 'children': [{'label': 3}] - }); - expect(i2['children'][0]).toBeInstanceOf(SubModel); - expect(i2['children'][0].label).toBe('3'); + const i2 = partialPlainToClass(SimpleModel, { + children: [{ label: 3 }], + }); + expect(i2['children'][0]).toBeInstanceOf(SubModel); + expect(i2['children'][0].label).toBe('3'); }); - test('partial 4', () => { - const i2 = partialPlainToClass(SimpleModel, { - 'stringChildrenCollection.0': 4 - }); - expect(i2['stringChildrenCollection.0']).toBe('4'); + const i2 = partialPlainToClass(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i2['stringChildrenCollection.0']).toBe('4'); }); test('partial 5', () => { - const i2 = partialPlainToClass(SimpleModel, { - 'childrenMap.foo.label': 5 - }); - expect(i2['childrenMap.foo.label']).toBe('5'); + const i2 = partialPlainToClass(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i2['childrenMap.foo.label']).toBe('5'); }); - test('partial 6', () => { - const i = partialPlainToClass(SimpleModel, { - 'types': [6, 7] - }); - expect(i['types']).toEqual(['6', '7']); + const i = partialPlainToClass(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); }); test('partial 7', () => { - const i = partialPlainToClass(SimpleModel, { - 'types.0': [7] - }); - expect(i['types.0']).toEqual('7'); + const i = partialPlainToClass(SimpleModel, { + 'types.0': [7], + }); + expect(i['types.0']).toEqual('7'); }); test('partial document', () => { - const docParent = new DocumentClass(); - const document = partialPlainToClass(DocumentClass, { - 'pages.0.name': 5, - 'pages.0.children.0.name': 6, - 'pages.0.children': [{name: 7}], - }, [docParent]); - expect(document['pages.0.name']).toBe('5'); - expect(document['pages.0.children.0.name']).toBe('6'); - expect(document['pages.0.children']).toBeInstanceOf(PageCollection); - expect(document['pages.0.children'].get(0).name).toBe('7'); - - expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); - - expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'children', - type: 'class', - typeValue: PageCollection, - array: false, - map: false, - }); - - expect(getResolvedReflection(DocumentClass, 'pages.0.children.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); + const docParent = new DocumentClass(); + const document = partialPlainToClass( + DocumentClass, + { + 'pages.0.name': 5, + 'pages.0.children.0.name': 6, + 'pages.0.children': [{ name: 7 }], + }, + [docParent] + ); + expect(document['pages.0.name']).toBe('5'); + expect(document['pages.0.children.0.name']).toBe('6'); + expect(document['pages.0.children']).toBeInstanceOf(PageCollection); + expect(document['pages.0.children'].get(0).name).toBe('7'); + + expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); + + expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'children', + type: 'class', + typeValue: PageCollection, + array: false, + map: false, + }); + + expect( + getResolvedReflection(DocumentClass, 'pages.0.children.0.name') + ).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); }); test('test enum labels', () => { - - enum MyEnum { - first, - second, - third, - } - - class Model { - @EnumType(MyEnum) - enum: MyEnum = MyEnum.third; - } - - expect(plainToClass(Model, {enum: MyEnum.first}).enum).toBe(MyEnum.first); - expect(plainToClass(Model, {enum: MyEnum.second}).enum).toBe(MyEnum.second); - expect(plainToClass(Model, {enum: 0}).enum).toBe(MyEnum.first); - expect(plainToClass(Model, {enum: 1}).enum).toBe(MyEnum.second); - expect(plainToClass(Model, {enum: 2}).enum).toBe(MyEnum.third); - - expect(() => { - expect(plainToClass(Model, {enum: 'first'}).enum).toBe(MyEnum.first); - }).toThrow('Invalid ENUM given in property enum: first, valid: 0,1,2'); - - - class ModelWithLabels { - @EnumType(MyEnum, true) - enum: MyEnum = MyEnum.third; - } - expect(plainToClass(ModelWithLabels, {enum: MyEnum.first}).enum).toBe(MyEnum.first); - expect(plainToClass(ModelWithLabels, {enum: MyEnum.second}).enum).toBe(MyEnum.second); - expect(plainToClass(ModelWithLabels, {enum: 0}).enum).toBe(MyEnum.first); - expect(plainToClass(ModelWithLabels, {enum: 1}).enum).toBe(MyEnum.second); - expect(plainToClass(ModelWithLabels, {enum: 2}).enum).toBe(MyEnum.third); - - expect(plainToClass(ModelWithLabels, {enum: 'first'}).enum).toBe(MyEnum.first); - expect(plainToClass(ModelWithLabels, {enum: 'second'}).enum).toBe(MyEnum.second); - expect(plainToClass(ModelWithLabels, {enum: 'third'}).enum).toBe(MyEnum.third); - - expect(() => { - expect(plainToClass(ModelWithLabels, {enum: 'Hi'}).enum).toBe(MyEnum.first); - }).toThrow('Invalid ENUM given in property enum: Hi, valid: 0,1,2,first,second,third'); - -}); \ No newline at end of file + enum MyEnum { + first, + second, + third, + } + + class Model { + @EnumType(MyEnum) + enum: MyEnum = MyEnum.third; + } + + expect(plainToClass(Model, { enum: MyEnum.first }).enum).toBe(MyEnum.first); + expect(plainToClass(Model, { enum: MyEnum.second }).enum).toBe(MyEnum.second); + expect(plainToClass(Model, { enum: 0 }).enum).toBe(MyEnum.first); + expect(plainToClass(Model, { enum: 1 }).enum).toBe(MyEnum.second); + expect(plainToClass(Model, { enum: 2 }).enum).toBe(MyEnum.third); + + expect(() => { + expect(plainToClass(Model, { enum: 'first' }).enum).toBe(MyEnum.first); + }).toThrow('Invalid ENUM given in property enum: first, valid: 0,1,2'); + + class ModelWithLabels { + @EnumType(MyEnum, true) + enum: MyEnum = MyEnum.third; + } + expect(plainToClass(ModelWithLabels, { enum: MyEnum.first }).enum).toBe( + MyEnum.first + ); + expect(plainToClass(ModelWithLabels, { enum: MyEnum.second }).enum).toBe( + MyEnum.second + ); + expect(plainToClass(ModelWithLabels, { enum: 0 }).enum).toBe(MyEnum.first); + expect(plainToClass(ModelWithLabels, { enum: 1 }).enum).toBe(MyEnum.second); + expect(plainToClass(ModelWithLabels, { enum: 2 }).enum).toBe(MyEnum.third); + + expect(plainToClass(ModelWithLabels, { enum: 'first' }).enum).toBe( + MyEnum.first + ); + expect(plainToClass(ModelWithLabels, { enum: 'second' }).enum).toBe( + MyEnum.second + ); + expect(plainToClass(ModelWithLabels, { enum: 'third' }).enum).toBe( + MyEnum.third + ); + + expect(() => { + expect(plainToClass(ModelWithLabels, { enum: 'Hi' }).enum).toBe( + MyEnum.first + ); + }).toThrow( + 'Invalid ENUM given in property enum: Hi, valid: 0,1,2,first,second,third' + ); +}); diff --git a/packages/core/tests/speed.spec.ts b/packages/core/tests/speed.spec.ts index 4c13972b3..ac412d19f 100644 --- a/packages/core/tests/speed.spec.ts +++ b/packages/core/tests/speed.spec.ts @@ -1,164 +1,164 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {classToPlain, getCachedParameterNames, getRegisteredProperties, plainToClass} from "../src/mapper"; -import {Job} from "./big-entity"; -import {SimpleModel, SuperSimple} from "./entities"; +import { + classToPlain, + getCachedParameterNames, + getRegisteredProperties, + plainToClass, +} from '../src/mapper'; +import { Job } from './big-entity'; +import { SimpleModel, SuperSimple } from './entities'; function benchTime(title: string): () => void { - const started = performance.now(); - return () => { - console.log(title, performance.now() - started, 'ms'); - } + const started = performance.now(); + return () => { + console.log(title, performance.now() - started, 'ms'); + }; } function bench(title: string, exec: () => void) { - const b = benchTime(title); - exec(); - b(); + const b = benchTime(title); + exec(); + b(); } test('speed ', () => { - //warm up - const mainInstance = plainToClass(Job, { - id: '1', + //warm up + const mainInstance = plainToClass(Job, { + id: '1', + project: '2', + title: 'Foo', + config: { + parameters: { + lr: 0.05, + optimizer: 'sgd', + }, + }, + tasks: { + meiner: { + commands: [{ name: 'erster', command: 'ls -al' }], + }, + }, + created: '2018-11-25 23:38:24.339Z', + updated: '2018-11-25 23:38:24.339Z', + status: 50, + }); + plainToClass(Job, {}); + + // bench('single create', () => { + // const job = new Job('21313', '2'); + // job.title = 'Foo'; + // }); + // + // bench('single create2', () => { + // const job = new Job('21313', '2'); + // job.title = 'Foo'; + // }); + // + // bench('single plainToClass', () => { + // const instance = plainToClass(Job, { + // id: '21313', + // project: '2', + // title: 'Foo', + // }); + // }); + // + // bench('single plainToClass 2', () => { + // const instance = plainToClass(Job, { + // id: '21313', + // project: '2', + // title: 'Foo', + // }); + // }); + // + // bench('100x plainToClass', () => { + // for (let i = 0; i < 100; i++) { + // const instance = plainToClass(Job, { + // id: i, + // project: '2', + // title: 'Foo', + // }); + // } + // }); + // + // bench('100x plainToClass', () => { + // for (let i = 0; i < 100; i++) { + // const instance = plainToClass(Job, { + // id: i, + // project: '2', + // title: 'Foo', + // }); + // } + // }); + + // bench('10000x getRegisteredProperties', () => { + // for (let i = 0; i < 10000; i++) { + // getRegisteredProperties(Job); + // } + // }); + // + // bench('10000x getRegisteredProperties', () => { + // for (let i = 0; i < 10000; i++) { + // const parentReferences = {}; + // const propertyNames = getRegisteredProperties(Job); + // for (const propertyName of propertyNames) { + // parentReferences[propertyName] = true; + // } + // } + // }); + + // bench('10000x getCachedParameterNames', () => { + // for (let i = 0; i < 10000; i++) { + // getCachedParameterNames(Job); + // } + // }); + bench('10000x plainToClass big', () => { + for (let i = 0; i < 10000; i++) { + plainToClass(Job, { + id: i, project: '2', title: 'Foo', config: { - parameters: { - lr: 0.05, - optimizer: 'sgd', - } + parameters: { + lr: 0.05, + optimizer: 'sgd', + }, }, tasks: { - meiner: { - commands: [ - {name: 'erster', command: 'ls -al'} - ] - } + meiner: { + commands: [{ name: 'erster', command: 'ls -al' }], + }, }, created: '2018-11-25 23:38:24.339Z', updated: '2018-11-25 23:38:24.339Z', status: 50, - }); - plainToClass(Job, {}); - - // bench('single create', () => { - // const job = new Job('21313', '2'); - // job.title = 'Foo'; - // }); - // - // bench('single create2', () => { - // const job = new Job('21313', '2'); - // job.title = 'Foo'; - // }); - // - // bench('single plainToClass', () => { - // const instance = plainToClass(Job, { - // id: '21313', - // project: '2', - // title: 'Foo', - // }); - // }); - // - // bench('single plainToClass 2', () => { - // const instance = plainToClass(Job, { - // id: '21313', - // project: '2', - // title: 'Foo', - // }); - // }); - // - // bench('100x plainToClass', () => { - // for (let i = 0; i < 100; i++) { - // const instance = plainToClass(Job, { - // id: i, - // project: '2', - // title: 'Foo', - // }); - // } - // }); - // - // bench('100x plainToClass', () => { - // for (let i = 0; i < 100; i++) { - // const instance = plainToClass(Job, { - // id: i, - // project: '2', - // title: 'Foo', - // }); - // } - // }); - - // bench('10000x getRegisteredProperties', () => { - // for (let i = 0; i < 10000; i++) { - // getRegisteredProperties(Job); - // } - // }); - // - // bench('10000x getRegisteredProperties', () => { - // for (let i = 0; i < 10000; i++) { - // const parentReferences = {}; - // const propertyNames = getRegisteredProperties(Job); - // for (const propertyName of propertyNames) { - // parentReferences[propertyName] = true; - // } - // } - // }); - - // bench('10000x getCachedParameterNames', () => { - // for (let i = 0; i < 10000; i++) { - // getCachedParameterNames(Job); - // } - // }); - bench('10000x plainToClass big', () => { - for (let i = 0; i < 10000; i++) { - plainToClass(Job, { - id: i, - project: '2', - title: 'Foo', - config: { - parameters: { - lr: 0.05, - optimizer: 'sgd', - } - }, - tasks: { - meiner: { - commands: [ - {name: 'erster', command: 'ls -al'} - ] - } - }, - created: '2018-11-25 23:38:24.339Z', - updated: '2018-11-25 23:38:24.339Z', - status: 50, - }); - } - }); - - bench('10000x classToPlain big', () => { - for (let i = 0; i < 10000; i++) { - classToPlain(Job, mainInstance); - } - }); + }); + } + }); - bench('10000x plainToClass SuperSimple', () => { - for (let i = 0; i < 10000; i++) { - plainToClass(SuperSimple, { - name: i, - }); - } - }); + bench('10000x classToPlain big', () => { + for (let i = 0; i < 10000; i++) { + classToPlain(Job, mainInstance); + } + }); + bench('10000x plainToClass SuperSimple', () => { + for (let i = 0; i < 10000; i++) { + plainToClass(SuperSimple, { + name: i, + }); + } + }); - const base = plainToClass(SuperSimple, { - name: '1', - }); + const base = plainToClass(SuperSimple, { + name: '1', + }); - bench('10000x classToPlain SuperSimple', () => { - for (let i = 0; i < 10000; i++) { - classToPlain(SuperSimple, base); - } - }); + bench('10000x classToPlain SuperSimple', () => { + for (let i = 0; i < 10000; i++) { + classToPlain(SuperSimple, base); + } + }); - console.log('done'); + console.log('done'); }); diff --git a/packages/core/tests/to-plain.spec.ts b/packages/core/tests/to-plain.spec.ts index d793c0442..e8ee70b47 100644 --- a/packages/core/tests/to-plain.spec.ts +++ b/packages/core/tests/to-plain.spec.ts @@ -1,44 +1,43 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {classToPlain} from "../"; -import {Plan, SimpleModel, SubModel} from "./entities"; +import { classToPlain } from '../'; +import { Plan, SimpleModel, SubModel } from './entities'; test('test simple model', () => { - const instance = new SimpleModel('myName'); - const json = classToPlain(SimpleModel, instance); - - expect(json['id']).toBeString(); - expect(json['name']).toBe('myName'); + const instance = new SimpleModel('myName'); + const json = classToPlain(SimpleModel, instance); + expect(json['id']).toBeString(); + expect(json['name']).toBe('myName'); }); test('test simple model all fields', () => { - const instance = new SimpleModel('myName'); - instance.plan = Plan.PRO; - instance.type = 5; - instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); - instance.children.push(new SubModel('fooo')); - instance.children.push(new SubModel('barr')); - - instance.childrenMap.foo = new SubModel('bar'); - instance.childrenMap.foo2 = new SubModel('bar2'); - - const json = classToPlain(SimpleModel, instance); - - console.log('json', json); - - expect(json['id']).toBeString(); - expect(json['name']).toBe('myName'); - expect(json['type']).toBe(5); - expect(json['plan']).toBe(Plan.PRO); - expect(json['created']).toBe('2018-10-13T12:17:35.000Z'); - expect(json['children']).toBeArrayOfSize(2); - expect(json['children'][0]).toBeObject(); - expect(json['children'][0].label).toBe('fooo'); - expect(json['children'][1].label).toBe('barr'); - - expect(json['childrenMap']).toBeObject(); - expect(json['childrenMap'].foo).toBeObject(); - expect(json['childrenMap'].foo.label).toBe('bar'); - expect(json['childrenMap'].foo2.label).toBe('bar2'); + const instance = new SimpleModel('myName'); + instance.plan = Plan.PRO; + instance.type = 5; + instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); + instance.children.push(new SubModel('fooo')); + instance.children.push(new SubModel('barr')); + + instance.childrenMap.foo = new SubModel('bar'); + instance.childrenMap.foo2 = new SubModel('bar2'); + + const json = classToPlain(SimpleModel, instance); + + console.log('json', json); + + expect(json['id']).toBeString(); + expect(json['name']).toBe('myName'); + expect(json['type']).toBe(5); + expect(json['plan']).toBe(Plan.PRO); + expect(json['created']).toBe('2018-10-13T12:17:35.000Z'); + expect(json['children']).toBeArrayOfSize(2); + expect(json['children'][0]).toBeObject(); + expect(json['children'][0].label).toBe('fooo'); + expect(json['children'][1].label).toBe('barr'); + + expect(json['childrenMap']).toBeObject(); + expect(json['childrenMap'].foo).toBeObject(); + expect(json['childrenMap'].foo.label).toBe('bar'); + expect(json['childrenMap'].foo2.label).toBe('bar2'); }); diff --git a/packages/core/tests/utils.spec.ts b/packages/core/tests/utils.spec.ts index 217ac7ad6..803950234 100644 --- a/packages/core/tests/utils.spec.ts +++ b/packages/core/tests/utils.spec.ts @@ -1,54 +1,53 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {isArray, isObject, isUndefined} from ".."; +import { isArray, isObject, isUndefined } from '..'; class SimpleClass { - public label: string; + public label: string; - constructor(label: string) { - this.label = label; - } + constructor(label: string) { + this.label = label; + } } test('helper is Object', () => { - expect(isObject([])).toBeFalse(); - expect(isObject(false)).toBeFalse(); - expect(isObject(true)).toBeFalse(); - expect(isObject(null)).toBeFalse(); - expect(isObject(undefined)).toBeFalse(); - expect(isObject(1)).toBeFalse(); - expect(isObject('1')).toBeFalse(); - - expect(isObject({})).toBeTrue(); - expect(isObject(new Date())).toBeTrue(); - expect(isObject(new SimpleClass('asd'))).toBeTrue(); + expect(isObject([])).toBeFalse(); + expect(isObject(false)).toBeFalse(); + expect(isObject(true)).toBeFalse(); + expect(isObject(null)).toBeFalse(); + expect(isObject(undefined)).toBeFalse(); + expect(isObject(1)).toBeFalse(); + expect(isObject('1')).toBeFalse(); + + expect(isObject({})).toBeTrue(); + expect(isObject(new Date())).toBeTrue(); + expect(isObject(new SimpleClass('asd'))).toBeTrue(); }); test('helper is array', () => { - expect(isArray({})).toBeFalse(); - expect(isArray(new Date())).toBeFalse(); - expect(isArray(new SimpleClass('asd'))).toBeFalse(); - expect(isArray(false)).toBeFalse(); - expect(isArray(true)).toBeFalse(); - expect(isArray(null)).toBeFalse(); - expect(isArray(undefined)).toBeFalse(); - expect(isArray(1)).toBeFalse(); - expect(isArray('1')).toBeFalse(); - - expect(isArray([])).toBeTrue(); + expect(isArray({})).toBeFalse(); + expect(isArray(new Date())).toBeFalse(); + expect(isArray(new SimpleClass('asd'))).toBeFalse(); + expect(isArray(false)).toBeFalse(); + expect(isArray(true)).toBeFalse(); + expect(isArray(null)).toBeFalse(); + expect(isArray(undefined)).toBeFalse(); + expect(isArray(1)).toBeFalse(); + expect(isArray('1')).toBeFalse(); + + expect(isArray([])).toBeTrue(); }); test('helper is isUndefined', () => { - expect(isUndefined({})).toBeFalse(); - expect(isUndefined(new Date())).toBeFalse(); - expect(isUndefined(new SimpleClass('asd'))).toBeFalse(); - expect(isUndefined(false)).toBeFalse(); - expect(isUndefined(true)).toBeFalse(); - expect(isUndefined(null)).toBeFalse(); - expect(isUndefined(1)).toBeFalse(); - expect(isUndefined('1')).toBeFalse(); - expect(isUndefined([])).toBeFalse(); - - expect(isUndefined(undefined)).toBeTrue(); + expect(isUndefined({})).toBeFalse(); + expect(isUndefined(new Date())).toBeFalse(); + expect(isUndefined(new SimpleClass('asd'))).toBeFalse(); + expect(isUndefined(false)).toBeFalse(); + expect(isUndefined(true)).toBeFalse(); + expect(isUndefined(null)).toBeFalse(); + expect(isUndefined(1)).toBeFalse(); + expect(isUndefined('1')).toBeFalse(); + expect(isUndefined([])).toBeFalse(); + + expect(isUndefined(undefined)).toBeTrue(); }); - diff --git a/packages/core/tests/validation.spec.ts b/packages/core/tests/validation.spec.ts index c7f0e3d28..f77f062c3 100644 --- a/packages/core/tests/validation.spec.ts +++ b/packages/core/tests/validation.spec.ts @@ -1,190 +1,243 @@ import 'reflect-metadata'; -import 'jest-extended' -import {Class, ClassArray, NumberType, StringType, ClassMap, ArrayType, MapType, Optional, validate} from "../"; +import 'jest-extended'; +import { + Class, + ClassArray, + NumberType, + StringType, + ClassMap, + ArrayType, + MapType, + Optional, + validate, +} from '../'; test('test required', async () => { - - class Model { - @StringType() - id: string = '1'; - - @StringType() - name?: string; - - @Optional() - optional?: string; - - @Optional() - @MapType() - map?: { [name: string]: string }; - - @Optional() - @ArrayType() - array?: string[]; - } - - const instance = new Model; - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'name'}]); - - expect(await validate(Model, {name: 'foo', map: true})).toEqual([{message: "Invalid type. Expected object, but got boolean", path: 'map'}]); - expect(await validate(Model, {name: 'foo', array: 233})).toEqual([{message: "Invalid type. Expected array, but got number", path: 'array'}]); - - instance.name = 'Pete'; - expect(await validate(Model, instance)).toEqual([]); + class Model { + @StringType() + id: string = '1'; + + @StringType() + name?: string; + + @Optional() + optional?: string; + + @Optional() + @MapType() + map?: { [name: string]: string }; + + @Optional() + @ArrayType() + array?: string[]; + } + + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); + + expect(await validate(Model, { name: 'foo', map: true })).toEqual([ + { message: 'Invalid type. Expected object, but got boolean', path: 'map' }, + ]); + expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ + { message: 'Invalid type. Expected array, but got number', path: 'array' }, + ]); + + instance.name = 'Pete'; + expect(await validate(Model, instance)).toEqual([]); }); - test('test deep', async () => { - - class Deep { - @StringType() - name?: string; - } - - class Model { - @StringType() - id: string = '2'; - - @Class(Deep) - deep?: Deep; - - @ClassArray(Deep) - deepArray: Deep[] = []; - - @ClassMap(Deep) - deepMap: { [name: string]: Deep } = {}; - } - - const instance = new Model; - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'deep'}]); - - instance.deep = new Deep(); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'deep.name'}]); - - instance.deep.name = 'defined'; - instance.deepArray.push(new Deep()); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'deepArray.0.name'}]); - - instance.deepArray[0].name = 'defined'; - instance.deepMap.foo = new Deep(); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'deepMap.foo.name'}]); - - instance.deepMap.foo.name = 'defined'; - expect(await validate(Model, instance)).toEqual([]); + class Deep { + @StringType() + name?: string; + } + + class Model { + @StringType() + id: string = '2'; + + @Class(Deep) + deep?: Deep; + + @ClassArray(Deep) + deepArray: Deep[] = []; + + @ClassMap(Deep) + deepMap: { [name: string]: Deep } = {}; + } + + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deep' }, + ]); + + instance.deep = new Deep(); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deep.name' }, + ]); + + instance.deep.name = 'defined'; + instance.deepArray.push(new Deep()); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deepArray.0.name' }, + ]); + + instance.deepArray[0].name = 'defined'; + instance.deepMap.foo = new Deep(); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deepMap.foo.name' }, + ]); + + instance.deepMap.foo.name = 'defined'; + expect(await validate(Model, instance)).toEqual([]); }); test('test string', async () => { - class Model { - @StringType() - id: string = '2'; - } - - expect(await validate(Model, {id: '2'})).toEqual([]); - expect(await validate(Model, {id: 2})).toEqual([{message: "No String given", path: 'id'}]); - expect(await validate(Model, {id: null})).toEqual([{message: "No String given", path: 'id'}]); - expect(await validate(Model, {id: undefined})).toEqual([]); //because defaults are applied - expect(await validate(Model, {})).toEqual([]); //because defaults are applied - - class ModelOptional { - @StringType() - @Optional() - id?: string; - } - - expect(await validate(ModelOptional, {id: '2'})).toEqual([]); - expect(await validate(ModelOptional, {id: 2})).toEqual([{message: "No String given", path: 'id'}]); - expect(await validate(ModelOptional, {id: null})).toEqual([{message: "No String given", path: 'id'}]); - expect(await validate(ModelOptional, {id: undefined})).toEqual([]); - expect(await validate(ModelOptional, {})).toEqual([]); + class Model { + @StringType() + id: string = '2'; + } + + expect(await validate(Model, { id: '2' })).toEqual([]); + expect(await validate(Model, { id: 2 })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(Model, { id: null })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied + expect(await validate(Model, {})).toEqual([]); //because defaults are applied + + class ModelOptional { + @StringType() + @Optional() + id?: string; + } + + expect(await validate(ModelOptional, { id: '2' })).toEqual([]); + expect(await validate(ModelOptional, { id: 2 })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: null })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: undefined })).toEqual([]); + expect(await validate(ModelOptional, {})).toEqual([]); }); test('test number', async () => { - class Model { - @NumberType() - id: number = 2; - } - - expect(await validate(Model, {id: 3})).toEqual([]); - expect(await validate(Model, {id: '3'})).toEqual([]); - expect(await validate(Model, {id: 'a'})).toEqual([{message: "No Number given", path: 'id'}]); - expect(await validate(Model, {id: null})).toEqual([{message: "No Number given", path: 'id'}]); - expect(await validate(Model, {id: undefined})).toEqual([]); //because defaults are applied - expect(await validate(Model, {})).toEqual([]); //because defaults are applied - - class ModelOptional { - @NumberType() - @Optional() - id?: number; - } - - expect(await validate(ModelOptional, {id: 3})).toEqual([]); - expect(await validate(ModelOptional, {id: '3'})).toEqual([]); - expect(await validate(ModelOptional, {id: 'a'})).toEqual([{message: "No Number given", path: 'id'}]); - expect(await validate(ModelOptional, {id: null})).toEqual([{message: "No Number given", path: 'id'}]); - expect(await validate(ModelOptional, {id: undefined})).toEqual([]); - expect(await validate(ModelOptional, {})).toEqual([]); + class Model { + @NumberType() + id: number = 2; + } + + expect(await validate(Model, { id: 3 })).toEqual([]); + expect(await validate(Model, { id: '3' })).toEqual([]); + expect(await validate(Model, { id: 'a' })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(Model, { id: null })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied + expect(await validate(Model, {})).toEqual([]); //because defaults are applied + + class ModelOptional { + @NumberType() + @Optional() + id?: number; + } + + expect(await validate(ModelOptional, { id: 3 })).toEqual([]); + expect(await validate(ModelOptional, { id: '3' })).toEqual([]); + expect(await validate(ModelOptional, { id: 'a' })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: null })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: undefined })).toEqual([]); + expect(await validate(ModelOptional, {})).toEqual([]); }); test('test nested validation', async () => { - // Class definition with validation rules - class A { - @StringType() - public x!: string; - } - - class B { - @StringType() - public type!: string; - - @Class(A) - public nested!: A; - - @ClassMap(A) - public nestedMap!: {[name: string]: A}; - - @ClassArray(A) - public nesteds!: A[]; - } - - expect(await validate(B, { - type: "test type", - })).toEqual([ - {'message': 'Required value is undefined', 'path': 'nested'}, - {'message': 'Required value is undefined', 'path': 'nestedMap'}, - {'message': 'Required value is undefined', 'path': 'nesteds'}, - ]); - - expect(await validate(B, { - type: "test type", - nested: [{x: "test x"}], - nestedMap: [{x: "test x"}], - nesteds: {x: "test x"}, - })).toEqual([ - {'message': 'Invalid type. Expected object, but got array', 'path': 'nested'}, - {'message': 'Invalid type. Expected object, but got array', 'path': 'nestedMap'}, - {'message': 'Invalid type. Expected array, but got object', 'path': 'nesteds'}, - ]); - - class BOptional { - @StringType() - public type!: string; - - @Class(A) - @Optional() - public nested!: A; - } - - expect(await validate(BOptional, { - type: "test type", - })).toEqual([]); - - expect(await validate(BOptional, { - type: "test type", - nested: false, - })).toEqual([ - {'message': 'Invalid type. Expected object, but got boolean', 'path': 'nested'}, - ]); - -}); \ No newline at end of file + // Class definition with validation rules + class A { + @StringType() + public x!: string; + } + + class B { + @StringType() + public type!: string; + + @Class(A) + public nested!: A; + + @ClassMap(A) + public nestedMap!: { [name: string]: A }; + + @ClassArray(A) + public nesteds!: A[]; + } + + expect( + await validate(B, { + type: 'test type', + }) + ).toEqual([ + { message: 'Required value is undefined', path: 'nested' }, + { message: 'Required value is undefined', path: 'nestedMap' }, + { message: 'Required value is undefined', path: 'nesteds' }, + ]); + + expect( + await validate(B, { + type: 'test type', + nested: [{ x: 'test x' }], + nestedMap: [{ x: 'test x' }], + nesteds: { x: 'test x' }, + }) + ).toEqual([ + { message: 'Invalid type. Expected object, but got array', path: 'nested' }, + { + message: 'Invalid type. Expected object, but got array', + path: 'nestedMap', + }, + { + message: 'Invalid type. Expected array, but got object', + path: 'nesteds', + }, + ]); + + class BOptional { + @StringType() + public type!: string; + + @Class(A) + @Optional() + public nested!: A; + } + + expect( + await validate(BOptional, { + type: 'test type', + }) + ).toEqual([]); + + expect( + await validate(BOptional, { + type: 'test type', + nested: false, + }) + ).toEqual([ + { + message: 'Invalid type. Expected object, but got boolean', + path: 'nested', + }, + ]); +}); diff --git a/packages/mongo/src/database.ts b/packages/mongo/src/database.ts index dcd416b84..f8e6c55c5 100644 --- a/packages/mongo/src/database.ts +++ b/packages/mongo/src/database.ts @@ -1,17 +1,22 @@ import { - ClassType, - getCollectionName, - getDatabaseName, - getIdField, - getClassName, - getReflectionType + ClassType, + getCollectionName, + getDatabaseName, + getIdField, + getClassName, + getReflectionType, } from '@marcj/marshal'; -import {MongoClient, Collection, Cursor} from 'mongodb'; -import {classToMongo, mongoToClass, partialClassToMongo, partialMongoToPlain, propertyClassToMongo} from "./mapping"; +import { MongoClient, Collection, Cursor } from 'mongodb'; +import { + classToMongo, + mongoToClass, + partialClassToMongo, + partialMongoToPlain, + propertyClassToMongo, +} from './mapping'; -export class NoIDDefinedError extends Error { -} +export class NoIDDefinedError extends Error {} export type MongoClientFactory = () => Promise; @@ -22,236 +27,294 @@ export type MongoClientFactory = () => Promise; * if you want to pass values from JSON/HTTP-Request. */ export class Database { - constructor(private mongoClient: MongoClient | MongoClientFactory, private defaultDatabaseName = 'app') { - } - - private async getMongoClient(): Promise { - if ('function' === typeof this.mongoClient) { - const f = (this.mongoClient as MongoClientFactory); - return await f(); - } - - return this.mongoClient; - } - - private async getCollection(classType: ClassType): Promise> { - return (await this.getMongoClient()) - .db(getDatabaseName(classType) || this.defaultDatabaseName) - .collection(getCollectionName(classType)); - } - - /** - * Returns one instance based on given filter, or null when not found. - */ - public async get( - classType: ClassType, - filter: { [field: string]: any } - ): Promise { - const collection = await this.getCollection(classType); - - const item = await collection.findOne(partialClassToMongo(classType, filter)); - - if (item) { - return mongoToClass(classType, item); - } - - return null; - } - - /** - * Returns all available documents for given filter as instance classes. - * - * Use toClass=false to return the raw documents. Use find().map(v => mongoToPlain(classType, v)) so you can - * easily return that values back to the HTTP client very fast. - */ - public async find( - classType: ClassType, - filter?: { [field: string]: any }, - toClass: boolean = true, - ): Promise { - const collection = await this.getCollection(classType); - - const items = await collection.find(filter ? partialClassToMongo(classType, filter) : undefined).toArray(); - - const converter = toClass ? mongoToClass : partialMongoToPlain; - - return items.map(v => { - return converter(classType, v); - }) as T[]; - } - - /** - * Returns a mongodb cursor, which you can further modify and then call toArray() to retrieve the documents. - * - * Use toClass=false to return the raw documents. - */ - public async cursor( - classType: ClassType, - filter?: { [field: string]: any }, - toClass: boolean = true, - ): Promise> { - const collection = await this.getCollection(classType); - - const cursor = collection.find(filter ? partialClassToMongo(classType, filter) : undefined); - const converter = toClass ? mongoToClass : partialMongoToPlain; - cursor.map(v => converter(classType, v)); - - return cursor; - } - - /** - * Removes ONE item from the database that has the given id. You need to use @ID() decorator - * for at least and max one property at your entity to use this method. - */ - public async remove(classType: ClassType, id: string): Promise { - const collection = await this.getCollection(classType); - const idName = getIdField(classType); - if (!idName) return false; - - const filter: { [name: string]: any } = {}; - filter[idName] = id; - - const result = await collection.deleteOne(partialClassToMongo(classType, filter)); - - return result.deletedCount ? result.deletedCount > 0 : false; - } - - /** - * Removes ONE item from the database that matches given filter. - */ - public async deleteOne(classType: ClassType, filter: { [field: string]: any }) { - const collection = await this.getCollection(classType); - await collection.deleteOne(partialClassToMongo(classType, filter)); - } - - /** - * Removes ALL items from the database that matches given filter. - */ - public async deleteMany(classType: ClassType, filter: { [field: string]: any }) { - const collection = await this.getCollection(classType); - await collection.deleteMany(partialClassToMongo(classType, filter)); + constructor( + private mongoClient: MongoClient | MongoClientFactory, + private defaultDatabaseName = 'app' + ) {} + + private async getMongoClient(): Promise { + if ('function' === typeof this.mongoClient) { + const f = this.mongoClient as MongoClientFactory; + return await f(); } - /** - * Adds a new item to the database. Sets _id if defined at your entity. - */ - public async add(classType: ClassType, item: T): Promise { - const collection = await this.getCollection(classType); - - const id = getIdField(classType); - (item)['version'] = 1; - - const obj = classToMongo(classType, item); - obj['version'] = 1; - - const result = await collection.insertOne(obj); - - if (id === '_id' && result.insertedId) { - const {type} = getReflectionType(classType, id); - - if (type === 'objectId' && result.insertedId && result.insertedId.toHexString) { - (item)['_id'] = result.insertedId.toHexString(); - } - } - - return true; + return this.mongoClient; + } + + private async getCollection( + classType: ClassType + ): Promise> { + return (await this.getMongoClient()) + .db(getDatabaseName(classType) || this.defaultDatabaseName) + .collection(getCollectionName(classType)); + } + + /** + * Returns one instance based on given filter, or null when not found. + */ + public async get( + classType: ClassType, + filter: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + + const item = await collection.findOne( + partialClassToMongo(classType, filter) + ); + + if (item) { + return mongoToClass(classType, item); } - /** - * Returns the count of items in the database, that fit that given filter. - */ - public async count(classType: ClassType, filter?: { [field: string]: any }): Promise { - const collection = await this.getCollection(classType); - return await collection.countDocuments(partialClassToMongo(classType, filter)); + return null; + } + + /** + * Returns all available documents for given filter as instance classes. + * + * Use toClass=false to return the raw documents. Use find().map(v => mongoToPlain(classType, v)) so you can + * easily return that values back to the HTTP client very fast. + */ + public async find( + classType: ClassType, + filter?: { [field: string]: any }, + toClass: boolean = true + ): Promise { + const collection = await this.getCollection(classType); + + const items = await collection + .find(filter ? partialClassToMongo(classType, filter) : undefined) + .toArray(); + + const converter = toClass ? mongoToClass : partialMongoToPlain; + + return items.map((v) => { + return converter(classType, v); + }) as T[]; + } + + /** + * Returns a mongodb cursor, which you can further modify and then call toArray() to retrieve the documents. + * + * Use toClass=false to return the raw documents. + */ + public async cursor( + classType: ClassType, + filter?: { [field: string]: any }, + toClass: boolean = true + ): Promise> { + const collection = await this.getCollection(classType); + + const cursor = collection.find( + filter ? partialClassToMongo(classType, filter) : undefined + ); + const converter = toClass ? mongoToClass : partialMongoToPlain; + cursor.map((v) => converter(classType, v)); + + return cursor; + } + + /** + * Removes ONE item from the database that has the given id. You need to use @ID() decorator + * for at least and max one property at your entity to use this method. + */ + public async remove( + classType: ClassType, + id: string + ): Promise { + const collection = await this.getCollection(classType); + const idName = getIdField(classType); + if (!idName) return false; + + const filter: { [name: string]: any } = {}; + filter[idName] = id; + + const result = await collection.deleteOne( + partialClassToMongo(classType, filter) + ); + + return result.deletedCount ? result.deletedCount > 0 : false; + } + + /** + * Removes ONE item from the database that matches given filter. + */ + public async deleteOne( + classType: ClassType, + filter: { [field: string]: any } + ) { + const collection = await this.getCollection(classType); + await collection.deleteOne(partialClassToMongo(classType, filter)); + } + + /** + * Removes ALL items from the database that matches given filter. + */ + public async deleteMany( + classType: ClassType, + filter: { [field: string]: any } + ) { + const collection = await this.getCollection(classType); + await collection.deleteMany(partialClassToMongo(classType, filter)); + } + + /** + * Adds a new item to the database. Sets _id if defined at your entity. + */ + public async add(classType: ClassType, item: T): Promise { + const collection = await this.getCollection(classType); + + const id = getIdField(classType); + (item)['version'] = 1; + + const obj = classToMongo(classType, item); + obj['version'] = 1; + + const result = await collection.insertOne(obj); + + if (id === '_id' && result.insertedId) { + const { type } = getReflectionType(classType, id); + + if ( + type === 'objectId' && + result.insertedId && + result.insertedId.toHexString + ) { + (item)['_id'] = result.insertedId.toHexString(); + } } - /** - * Returns true when at least one item in the database is found that fits given filter. - */ - public async has(classType: ClassType, filter?: { [field: string]: any }): Promise { - return (await this.count(classType, partialClassToMongo(classType, filter))) > 0; + return true; + } + + /** + * Returns the count of items in the database, that fit that given filter. + */ + public async count( + classType: ClassType, + filter?: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + return await collection.countDocuments( + partialClassToMongo(classType, filter) + ); + } + + /** + * Returns true when at least one item in the database is found that fits given filter. + */ + public async has( + classType: ClassType, + filter?: { [field: string]: any } + ): Promise { + return ( + (await this.count(classType, partialClassToMongo(classType, filter))) > 0 + ); + } + + /** + * Updates an entity in the database and returns the new version number if successful, or null if not successful. + * + * If no filter is given, the ID of `update` is used. + */ + public async update( + classType: ClassType, + update: T, + filter?: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + + const updateStatement: { [name: string]: any } = { + $inc: { version: +1 }, + }; + + updateStatement['$set'] = classToMongo(classType, update); + delete updateStatement['$set']['version']; + + const filterQuery = filter + ? partialClassToMongo(classType, filter) + : this.buildFindCriteria(classType, update); + + const response = await collection.findOneAndUpdate( + filterQuery, + updateStatement, + { + projection: { version: 1 }, + returnOriginal: false, + } + ); + + const doc = response.value; + + if (!doc) { + return null; } - /** - * Updates an entity in the database and returns the new version number if successful, or null if not successful. - * - * If no filter is given, the ID of `update` is used. - */ - public async update(classType: ClassType, update: T, filter?: { [field: string]: any }): Promise { - const collection = await this.getCollection(classType); - - const updateStatement: { [name: string]: any } = { - $inc: {version: +1}, - }; - - updateStatement['$set'] = classToMongo(classType, update); - delete updateStatement['$set']['version']; - - const filterQuery = filter ? partialClassToMongo(classType, filter) : this.buildFindCriteria(classType, update); - - const response = await collection.findOneAndUpdate(filterQuery, updateStatement, { - projection: {version: 1}, - returnOriginal: false - }); + (update)['version'] = (doc)['version']; - const doc = response.value; + return (update)['version']; + } - if (!doc) { - return null; - } + private buildFindCriteria( + classType: ClassType, + data: T + ): { [name: string]: any } { + const criteria: { [name: string]: any } = {}; + const id = getIdField(classType); - (update)['version'] = (doc)['version']; - - return (update)['version']; + if (!id) { + throw new NoIDDefinedError( + `Class ${getClassName(classType)} has no @ID() defined.` + ); } - private buildFindCriteria(classType: ClassType, data: T): { [name: string]: any } { - const criteria: { [name: string]: any } = {}; - const id = getIdField(classType); - - if (!id) { - throw new NoIDDefinedError(`Class ${getClassName(classType)} has no @ID() defined.`); - } - - criteria[id] = propertyClassToMongo(classType, id, (data)[id]); - - return criteria; + criteria[id] = propertyClassToMongo(classType, id, (data)[id]); + + return criteria; + } + + /** + * Patches an entity in the database and returns the new version number if successful, or null if not successful. + * It's possible to provide nested key-value pairs, where the path should be based on dot symbol separation. + * + * Example + * + * await patch(SimpleEntity, { + * ['children.0.label']: 'Changed label' + * }); + */ + public async patch( + classType: ClassType, + filter: { [field: string]: any }, + patch: Partial + ): Promise { + const collection = await this.getCollection(classType); + + const patchStatement: { [name: string]: any } = { + $inc: { version: +1 }, + }; + + delete (patch)['id']; + delete (patch)['_id']; + delete (patch)['version']; + + patchStatement['$set'] = partialClassToMongo(classType, patch); + + const response = await collection.findOneAndUpdate( + partialClassToMongo(classType, filter), + patchStatement, + { + projection: { version: 1 }, + returnOriginal: false, + } + ); + + const doc = response.value; + + if (!doc) { + return null; } - /** - * Patches an entity in the database and returns the new version number if successful, or null if not successful. - * It's possible to provide nested key-value pairs, where the path should be based on dot symbol separation. - * - * Example - * - * await patch(SimpleEntity, { - * ['children.0.label']: 'Changed label' - * }); - */ - public async patch(classType: ClassType, filter: { [field: string]: any }, patch: Partial): Promise { - const collection = await this.getCollection(classType); - - const patchStatement: { [name: string]: any } = { - $inc: {version: +1} - }; - - delete (patch)['id']; - delete (patch)['_id']; - delete (patch)['version']; - - patchStatement['$set'] = partialClassToMongo(classType, patch); - - const response = await collection.findOneAndUpdate(partialClassToMongo(classType, filter), patchStatement, { - projection: {version: 1}, - returnOriginal: false - }); - - const doc = response.value; - - if (!doc) { - return null; - } - - return (doc)['version']; - } + return (doc)['version']; + } } diff --git a/packages/mongo/src/mapping.ts b/packages/mongo/src/mapping.ts index dd2a1bd4d..8c23c9740 100644 --- a/packages/mongo/src/mapping.ts +++ b/packages/mongo/src/mapping.ts @@ -1,525 +1,661 @@ -import {Binary, ObjectID} from "mongodb"; +import { Binary, ObjectID } from 'mongodb'; import { - classToPlain, - ClassType, - deleteExcludedPropertiesFor, - getClassName, - getClassPropertyName, - getDecorator, getEnumKeys, - getParentReferenceClass, - getRegisteredProperties, getResolvedReflection, - getValidEnumValue, - isArray, - isEnumAllowLabelsAsValue, - isObject, isOptional, - isUndefined, - isValidEnumValue, - toClass, - ToClassState -} from "@marcj/marshal"; -import * as clone from "clone"; -import * as mongoUuid from "mongo-uuid"; + classToPlain, + ClassType, + deleteExcludedPropertiesFor, + getClassName, + getClassPropertyName, + getDecorator, + getEnumKeys, + getParentReferenceClass, + getRegisteredProperties, + getResolvedReflection, + getValidEnumValue, + isArray, + isEnumAllowLabelsAsValue, + isObject, + isOptional, + isUndefined, + isValidEnumValue, + toClass, + ToClassState, +} from '@marcj/marshal'; +import * as clone from 'clone'; +import * as mongoUuid from 'mongo-uuid'; export function uuid4Binary(u?: string): Binary { - return mongoUuid(Binary, u); + return mongoUuid(Binary, u); } export function uuid4Stringify(u: Binary | string): string { - return 'string' === typeof u ? u : mongoUuid.stringify(u); + return 'string' === typeof u ? u : mongoUuid.stringify(u); } export function partialClassToMongo( - classType: ClassType, - target?: { [path: string]: any }, + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - if (target[i] as any instanceof RegExp) { - continue; - } - - result[i] = propertyClassToMongo(classType, i, target[i]); + if ((target[i] as any) instanceof RegExp) { + continue; } - return result; + result[i] = propertyClassToMongo(classType, i, target[i]); + } + + return result; } export function partialPlainToMongo( - classType: ClassType, - target?: { [path: string]: any }, + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - result[i] = propertyPlainToMongo(classType, i, target[i]); - } + result[i] = propertyPlainToMongo(classType, i, target[i]); + } - return result; + return result; } export function partialMongoToPlain( - classType: ClassType, - target?: { [path: string]: any }, + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - result[i] = propertyMongoToPlain(classType, i, target[i]); - } + result[i] = propertyMongoToPlain(classType, i, target[i]); + } - return result; + return result; } export function propertyMongoToPlain( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - const {type} = reflection; + const { type } = reflection; - if (isUndefined(propertyValue)) { - return undefined; - } - - if (null === propertyValue) { - return null; - } + if (isUndefined(propertyValue)) { + return undefined; + } - function convert(value: any) { - if (value && 'uuid' === type && 'string' !== typeof value) { - return uuid4Stringify(value); - } + if (null === propertyValue) { + return null; + } - if ('objectId' === type && 'string' !== typeof value && value.toHexString()) { - return (value).toHexString(); - } + function convert(value: any) { + if (value && 'uuid' === type && 'string' !== typeof value) { + return uuid4Stringify(value); + } - if ('date' === type && value instanceof Date) { - return value.toJSON(); - } + if ( + 'objectId' === type && + 'string' !== typeof value && + value.toHexString() + ) { + return (value).toHexString(); + } - return value; + if ('date' === type && value instanceof Date) { + return value.toJSON(); } - return convert(propertyValue); + return value; + } + + return convert(propertyValue); } export function propertyClassToMongo( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const {resolvedClassType, resolvedPropertyName, type, typeValue, array, map} = reflection; - - if (isUndefined(propertyValue)) { - return undefined; + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; + + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + if (isUndefined(propertyValue)) { + return undefined; + } + + if (null === propertyValue) { + return null; + } + + function convert(value: any) { + if (value && 'objectId' === type && 'string' === typeof value) { + try { + return new ObjectID(value); + } catch (e) { + throw new Error( + `Invalid ObjectID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } } - if (null === propertyValue) { - return null; + if (value && 'uuid' === type && 'string' === typeof value) { + try { + return uuid4Binary(value); + } catch (e) { + throw new Error( + `Invalid UUID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } } - function convert(value: any) { - if (value && 'objectId' === type && 'string' === typeof value) { - try { - return new ObjectID(value); - } catch (e) { - throw new Error(`Invalid ObjectID given in property ${getClassPropertyName(resolvedClassType, resolvedPropertyName)}: '${value}'`); - } - } - - if (value && 'uuid' === type && 'string' === typeof value) { - try { - return uuid4Binary(value); - } catch (e) { - throw new Error(`Invalid UUID given in property ${getClassPropertyName(resolvedClassType, resolvedPropertyName)}: '${value}'`); - } - } - - if ('string' === type) { - return String(value); - } - - if ('number' === type) { - return Number(value); - } - - if ('enum' === type) { - //the class instance itself can only have the actual value which can be used in plain as well - return value; - } + if ('string' === type) { + return String(value); + } - if ('binary' === type) { - return new Binary(value); - } + if ('number' === type) { + return Number(value); + } - if (type === 'class') { - return classToMongo(typeValue, value); - } + if ('enum' === type) { + //the class instance itself can only have the actual value which can be used in plain as well + return value; + } - return value; + if ('binary' === type) { + return new Binary(value); } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map(v => convert(v)); - } - return []; + if (type === 'class') { + return classToMongo(typeValue, value); } - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } - } - return result; + return value; + } + + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); } + return []; + } + + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; + } - return convert(propertyValue); + return convert(propertyValue); } export function propertyPlainToMongo( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const {resolvedClassType, resolvedPropertyName, type, typeValue, array, map} = reflection; - - if (isUndefined(propertyValue)) { - return undefined; + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; + + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + if (isUndefined(propertyValue)) { + return undefined; + } + + if (null === propertyValue) { + return null; + } + + function convert(value: any) { + if (value && 'objectId' === type && 'string' === typeof value) { + try { + return new ObjectID(value); + } catch (e) { + throw new Error( + `Invalid ObjectID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } } - if (null === propertyValue) { - return null; + if (value && 'uuid' === type && 'string' === typeof value) { + try { + return uuid4Binary(value); + } catch (e) { + throw new Error( + `Invalid UUID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } + } + if ( + 'date' === type && + ('string' === typeof value || 'number' === typeof value) + ) { + return new Date(value); } - function convert(value: any) { - if (value && 'objectId' === type && 'string' === typeof value) { - try { - return new ObjectID(value); - } catch (e) { - throw new Error(`Invalid ObjectID given in property ${getClassPropertyName(resolvedClassType, resolvedPropertyName)}: '${value}'`); - } - } - - if (value && 'uuid' === type && 'string' === typeof value) { - try { - return uuid4Binary(value); - } catch (e) { - throw new Error(`Invalid UUID given in property ${getClassPropertyName(resolvedClassType, resolvedPropertyName)}: '${value}'`); - } - } - if ('date' === type && ('string' === typeof value || 'number' === typeof value)) { - return new Date(value); - } - - if ('string' === type && 'string' !== typeof value) { - return String(value); - } - - if ('number' === type && 'number' !== typeof value) { - return +value; - } - - if ('binary' === type && 'string' === typeof value) { - return new Buffer(value, 'base64'); - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + if ('number' === type && 'number' !== typeof value) { + return +value; + } - return true === value; - } + if ('binary' === type && 'string' === typeof value) { + return new Buffer(value, 'base64'); + } - if ('any' === type) { - return clone(value, false); - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - if ('binary' === type && 'string' === typeof value) { - return new Binary(new Buffer(value, 'base64')); - } + return true === value; + } - if (type === 'class') { - //we need to check if value has all properties set, if one not-optional is missing, we throw an error - for (const property of getRegisteredProperties(typeValue)) { - if (value[property] === undefined) { - if (isOptional(typeValue, propertyName)) { - continue; - } - throw new Error(`Missing value for ${getClassPropertyName(typeValue, propertyName)}. Can not convert to mongo.`); - } - - value[property] = propertyPlainToMongo(typeValue, property, value[property]); - } - } + if ('any' === type) { + return clone(value, false); + } - return value; + if ('binary' === type && 'string' === typeof value) { + return new Binary(new Buffer(value, 'base64')); } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map(v => convert(v)); - } - return []; + if (type === 'class') { + //we need to check if value has all properties set, if one not-optional is missing, we throw an error + for (const property of getRegisteredProperties(typeValue)) { + if (value[property] === undefined) { + if (isOptional(typeValue, propertyName)) { + continue; + } + throw new Error( + `Missing value for ${getClassPropertyName( + typeValue, + propertyName + )}. Can not convert to mongo.` + ); + } + + value[property] = propertyPlainToMongo( + typeValue, + property, + value[property] + ); + } } - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } - } - return result; + return value; + } + + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); } + return []; + } + + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; + } - return convert(propertyValue); + return convert(propertyValue); } export function propertyMongoToClass( - classType: ClassType, - propertyName: string, - propertyValue: any, - parents: any[], - incomingLevel: number, - state: ToClassState + classType: ClassType, + propertyName: string, + propertyValue: any, + parents: any[], + incomingLevel: number, + state: ToClassState ) { - if (isUndefined(propertyValue)) { - return undefined; + if (isUndefined(propertyValue)) { + return undefined; + } + + if (null === propertyValue) { + return null; + } + + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; + + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + function convert(value: any) { + if (value && 'uuid' === type && 'string' !== typeof value) { + return uuid4Stringify(value); } - if (null === propertyValue) { - return null; + if ( + 'objectId' === type && + 'string' !== typeof value && + value.toHexString() + ) { + return (value).toHexString(); } - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const {resolvedClassType, resolvedPropertyName, type, typeValue, array, map} = reflection; - - function convert(value: any) { - - if (value && 'uuid' === type && 'string' !== typeof value) { - return uuid4Stringify(value); - } - - if ('objectId' === type && 'string' !== typeof value && value.toHexString()) { - return (value).toHexString(); - } - - if ('date' === type && !(value instanceof Date)) { - return new Date(value); - } - - if ('binary' === type && value instanceof Binary) { - return value.buffer; - } - - if ('any' === type) { - return clone(value, false); - } - - if ('string' === type && 'string' !== typeof value) { - return String(value); - } - - if ('number' === type && 'number' !== typeof value) { - return +value; - } - - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + if ('date' === type && !(value instanceof Date)) { + return new Date(value); + } - return true === value; - } + if ('binary' === type && value instanceof Binary) { + return value.buffer; + } - if ('enum' === type) { - const allowLabelsAsValue = isEnumAllowLabelsAsValue(resolvedClassType, resolvedPropertyName); - if (undefined !== value && !isValidEnumValue(typeValue, value, allowLabelsAsValue)) { - throw new Error(`Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${getEnumKeys(typeValue).join(',')}`); - } + if ('any' === type) { + return clone(value, false); + } - return getValidEnumValue(typeValue, value, allowLabelsAsValue); - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - if (type === 'class') { - if (value instanceof typeValue) { - //already the target type, this is an error - throw new Error(`${getClassPropertyName(resolvedClassType, resolvedPropertyName)} is already in target format. Are you calling plainToClass() with an class instance?`); - } + if ('number' === type && 'number' !== typeof value) { + return +value; + } - return toClass(typeValue, clone(value, false, 1), propertyMongoToClass, parents, incomingLevel, state); - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - return value; + return true === value; } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map(v => convert(v)); - } - return []; + if ('enum' === type) { + const allowLabelsAsValue = isEnumAllowLabelsAsValue( + resolvedClassType, + resolvedPropertyName + ); + if ( + undefined !== value && + !isValidEnumValue(typeValue, value, allowLabelsAsValue) + ) { + throw new Error( + `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${getEnumKeys( + typeValue + ).join(',')}` + ); + } + + return getValidEnumValue(typeValue, value, allowLabelsAsValue); } - if (map) { - const result: any = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue as any)[i]); - } - } - return result; + if (type === 'class') { + if (value instanceof typeValue) { + //already the target type, this is an error + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} is already in target format. Are you calling plainToClass() with an class instance?` + ); + } + + return toClass( + typeValue, + clone(value, false, 1), + propertyMongoToClass, + parents, + incomingLevel, + state + ); } - return convert(propertyValue); -} + return value; + } -export function mongoToClass(classType: ClassType, target: any, parents?: any[]): T { - const state = new ToClassState(); - const item = toClass(classType, clone(target, false, 1), propertyMongoToClass, parents || [], 1, state); + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } + return []; + } - for (const callback of state.onFullLoadCallbacks) { - callback(); + if (map) { + const result: any = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue as any)[i]); + } } + return result; + } - return item; + return convert(propertyValue); } -export function mongoToPlain(classType: ClassType, target: any) { - return classToPlain(classType, mongoToClass(classType, target)); +export function mongoToClass( + classType: ClassType, + target: any, + parents?: any[] +): T { + const state = new ToClassState(); + const item = toClass( + classType, + clone(target, false, 1), + propertyMongoToClass, + parents || [], + 1, + state + ); + + for (const callback of state.onFullLoadCallbacks) { + callback(); + } + + return item; } -export function plainToMongo(classType: ClassType, target: {[k: string]: any}): any { - const result: any = {}; +export function mongoToPlain(classType: ClassType, target: any) { + return classToPlain(classType, mongoToClass(classType, target)); +} - if (target instanceof classType) { - throw new Error(`Could not plainToMongo since target is a class instance of ${getClassName(classType)}`); +export function plainToMongo( + classType: ClassType, + target: { [k: string]: any } +): any { + const result: any = {}; + + if (target instanceof classType) { + throw new Error( + `Could not plainToMongo since target is a class instance of ${getClassName( + classType + )}` + ); + } + + for (const propertyName of getRegisteredProperties(classType)) { + if (undefined === (target as any)[propertyName]) { + continue; } - for (const propertyName of getRegisteredProperties(classType)) { - if (undefined === (target as any)[propertyName]) { - continue; - } - - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; - } - - result[propertyName] = propertyPlainToMongo(classType, propertyName, (target as any)[propertyName]); + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; } - deleteExcludedPropertiesFor(classType, result, 'mongo'); - return result; + result[propertyName] = propertyPlainToMongo( + classType, + propertyName, + (target as any)[propertyName] + ); + } + + deleteExcludedPropertiesFor(classType, result, 'mongo'); + return result; } export function classToMongo(classType: ClassType, target: T): any { - const result: any = {}; - - if (!(target instanceof classType)) { - throw new Error(`Could not classToMongo since target is not a class instance of ${getClassName(classType)}`); + const result: any = {}; + + if (!(target instanceof classType)) { + throw new Error( + `Could not classToMongo since target is not a class instance of ${getClassName( + classType + )}` + ); + } + + const decoratorName = getDecorator(classType); + if (decoratorName) { + return propertyClassToMongo( + classType, + decoratorName, + (target as any)[decoratorName] + ); + } + + for (const propertyName of getRegisteredProperties(classType)) { + if (undefined === (target as any)[propertyName]) { + continue; } - const decoratorName = getDecorator(classType); - if (decoratorName) { - return propertyClassToMongo(classType, decoratorName, (target as any)[decoratorName]); + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; } - for (const propertyName of getRegisteredProperties(classType)) { - if (undefined === (target as any)[propertyName]) { - continue; - } - - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; - } - - result[propertyName] = propertyClassToMongo(classType, propertyName, (target as any)[propertyName]); - } + result[propertyName] = propertyClassToMongo( + classType, + propertyName, + (target as any)[propertyName] + ); + } - deleteExcludedPropertiesFor(classType, result, 'mongo'); - return result; + deleteExcludedPropertiesFor(classType, result, 'mongo'); + return result; } - /** * Takes a mongo filter query and converts its plain values to classType's mongo types, so you * can use it to send it to mongo. */ export function convertPlainQueryToMongo( - classType: ClassType, - target: { [path: string]: any }, - fieldNamesMap: {[name: string]: boolean} = {} + classType: ClassType, + target: { [path: string]: any }, + fieldNamesMap: { [name: string]: boolean } = {} ): { [path: string]: any } { - const result: { [i: string]: any } = {}; + const result: { [i: string]: any } = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - const fieldValue: any = target[i]; + const fieldValue: any = target[i]; - if (i[0] === '$') { - result[i] = (fieldValue as any[]).map(v => convertPlainQueryToMongo(classType, v, fieldNamesMap)); - continue; - } + if (i[0] === '$') { + result[i] = (fieldValue as any[]).map((v) => + convertPlainQueryToMongo(classType, v, fieldNamesMap) + ); + continue; + } + + if (isObject(fieldValue)) { + for (const j in fieldValue) { + if (!fieldValue.hasOwnProperty(j)) continue; - if (isObject(fieldValue)) { - for (const j in fieldValue) { - if (!fieldValue.hasOwnProperty(j)) continue; - - const queryValue: any = (fieldValue as any)[j]; - - if (j[0] !== '$') { - result[i] = propertyClassToMongo(classType, i, fieldValue); - break; - } else { - if (j === '$and' || j === '$or' || j === '$nor' || j === '$not') { - (fieldValue as any)[j] = (queryValue as any[]).map(v => convertPlainQueryToMongo(classType, v, fieldNamesMap)); - } else if (j === '$in' || j === '$nin' || j === '$all') { - fieldNamesMap[i] = true; - (fieldValue as any)[j] = (queryValue as any[]).map(v => propertyClassToMongo(classType, i, v)); - } else if (j === '$text' || j === '$exists' || j === '$mod' || j === '$size' || j === '$type' || j === '$regex' || j === '$where') { - //don't transform - } else { - fieldNamesMap[i] = true; - (fieldValue as any)[j] = propertyClassToMongo(classType, i, queryValue); - } - } - } - - result[i] = fieldValue; + const queryValue: any = (fieldValue as any)[j]; + + if (j[0] !== '$') { + result[i] = propertyClassToMongo(classType, i, fieldValue); + break; } else { + if (j === '$and' || j === '$or' || j === '$nor' || j === '$not') { + (fieldValue as any)[j] = (queryValue as any[]).map((v) => + convertPlainQueryToMongo(classType, v, fieldNamesMap) + ); + } else if (j === '$in' || j === '$nin' || j === '$all') { fieldNamesMap[i] = true; - result[i] = propertyClassToMongo(classType, i, fieldValue); - } + (fieldValue as any)[j] = (queryValue as any[]).map((v) => + propertyClassToMongo(classType, i, v) + ); + } else if ( + j === '$text' || + j === '$exists' || + j === '$mod' || + j === '$size' || + j === '$type' || + j === '$regex' || + j === '$where' + ) { + //don't transform + } else { + fieldNamesMap[i] = true; + (fieldValue as any)[j] = propertyClassToMongo( + classType, + i, + queryValue + ); + } + } + } + + result[i] = fieldValue; + } else { + fieldNamesMap[i] = true; + result[i] = propertyClassToMongo(classType, i, fieldValue); } + } - return result; + return result; } diff --git a/packages/mongo/tests/class-to.spec.ts b/packages/mongo/tests/class-to.spec.ts index aa3c2c8f7..8778ab708 100644 --- a/packages/mongo/tests/class-to.spec.ts +++ b/packages/mongo/tests/class-to.spec.ts @@ -1,13 +1,13 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {classToMongo} from "../src/mapping"; -import {SimpleModel} from "@marcj/marshal/tests/entities"; +import { classToMongo } from '../src/mapping'; +import { SimpleModel } from '@marcj/marshal/tests/entities'; test('class-to test simple model', () => { - expect(() => { - const instance = classToMongo(SimpleModel, { - id: '21313', - name: 'Hi' - }); - }).toThrow(`Could not classToMongo since target is not a class instance`); + expect(() => { + const instance = classToMongo(SimpleModel, { + id: '21313', + name: 'Hi', + }); + }).toThrow(`Could not classToMongo since target is not a class instance`); }); diff --git a/packages/mongo/tests/mongo-query.spec.ts b/packages/mongo/tests/mongo-query.spec.ts index bf2f94df7..12d691bf8 100644 --- a/packages/mongo/tests/mongo-query.spec.ts +++ b/packages/mongo/tests/mongo-query.spec.ts @@ -1,81 +1,105 @@ import 'jest'; -import {NumberType, StringType} from "@marcj/marshal"; -import {convertPlainQueryToMongo} from ".."; +import { NumberType, StringType } from '@marcj/marshal'; +import { convertPlainQueryToMongo } from '..'; class Simple { - @NumberType() - public id!: number; + @NumberType() + public id!: number; - @NumberType() - public price!: number; + @NumberType() + public price!: number; - @StringType() - public label!: string; + @StringType() + public label!: string; } test('simple', () => { - const m = convertPlainQueryToMongo(Simple, { - id: {$qt: '1'} - }); + const m = convertPlainQueryToMongo(Simple, { + id: { $qt: '1' }, + }); - expect(m['id']['$qt']).toBe(1); + expect(m['id']['$qt']).toBe(1); }); test('simple 2', () => { - const m = convertPlainQueryToMongo(Simple, {id: {dif: 1}}); - expect(m).toEqual({id: {dif: 1}}); + const m = convertPlainQueryToMongo(Simple, { id: { dif: 1 } }); + expect(m).toEqual({ id: { dif: 1 } }); }); test('and', () => { - const m = convertPlainQueryToMongo(Simple, {$and: [{id: '1'}, {id: '2'}]}); - expect(m).toEqual({$and: [{id: 1}, {id: 2}]}); + const m = convertPlainQueryToMongo(Simple, { + $and: [{ id: '1' }, { id: '2' }], + }); + expect(m).toEqual({ $and: [{ id: 1 }, { id: 2 }] }); - expect(m['$and'][0]['id']).toBe(1); - expect(m['$and'][1]['id']).toBe(2); + expect(m['$and'][0]['id']).toBe(1); + expect(m['$and'][1]['id']).toBe(2); }); test('in', () => { - const m = convertPlainQueryToMongo(Simple, {id: {$in: ['1', '2']}}); - expect(m).toEqual({id: {$in: [1, 2]}}); + const m = convertPlainQueryToMongo(Simple, { id: { $in: ['1', '2'] } }); + expect(m).toEqual({ id: { $in: [1, 2] } }); - const m2 = convertPlainQueryToMongo(Simple, {id: {$nin: ['1', '2']}}); - expect(m2).toEqual({id: {$nin: [1, 2]}}); + const m2 = convertPlainQueryToMongo(Simple, { id: { $nin: ['1', '2'] } }); + expect(m2).toEqual({ id: { $nin: [1, 2] } }); - const m3 = convertPlainQueryToMongo(Simple, {label: {$all: [1, '2']}}); - expect(m3).toEqual({label: {$all: ['1', '2']}}); + const m3 = convertPlainQueryToMongo(Simple, { label: { $all: [1, '2'] } }); + expect(m3).toEqual({ label: { $all: ['1', '2'] } }); }); test('complex', () => { - const names = {}; - const m = convertPlainQueryToMongo(Simple, {$and: [{price: {$ne: '1.99'}}, {price: {$exists: true}}, {id: {$gt: '0'}}]}, names); - - expect(m).toEqual({$and: [{price: {$ne: 1.99}}, {price: {$exists: true}}, {id: {$gt: 0}}]}); - expect(Object.keys(names)).toEqual(['price', 'id']); + const names = {}; + const m = convertPlainQueryToMongo( + Simple, + { + $and: [ + { price: { $ne: '1.99' } }, + { price: { $exists: true } }, + { id: { $gt: '0' } }, + ], + }, + names + ); + + expect(m).toEqual({ + $and: [ + { price: { $ne: 1.99 } }, + { price: { $exists: true } }, + { id: { $gt: 0 } }, + ], + }); + expect(Object.keys(names)).toEqual(['price', 'id']); }); test('$or', () => { - const m = convertPlainQueryToMongo(Simple, {$and: [{$or: [{id: 1}]}]}); - expect(m).toEqual({$and: [{$or: [{id: 1}]}]}); + const m = convertPlainQueryToMongo(Simple, { $and: [{ $or: [{ id: 1 }] }] }); + expect(m).toEqual({ $and: [{ $or: [{ id: 1 }] }] }); }); test('nested $or', () => { - const m = convertPlainQueryToMongo(Simple, { $or: [ { id: { $lt: 20 } }, { price: 10 } ] } ); - expect(m).toEqual({ $or: [ { id: { $lt: 20 } }, { price: 10 } ] } ); + const m = convertPlainQueryToMongo(Simple, { + $or: [{ id: { $lt: 20 } }, { price: 10 }], + }); + expect(m).toEqual({ $or: [{ id: { $lt: 20 } }, { price: 10 }] }); }); test('not', () => { - const m = convertPlainQueryToMongo(Simple, {$not: [{price: {$ne: '1.99'}}, {price: {$exists: true}}]}); + const m = convertPlainQueryToMongo(Simple, { + $not: [{ price: { $ne: '1.99' } }, { price: { $exists: true } }], + }); - expect(m).toEqual({$not: [{price: {$ne: 1.99}}, {price: {$exists: true}}]}); + expect(m).toEqual({ + $not: [{ price: { $ne: 1.99 } }, { price: { $exists: true } }], + }); }); test('excluded', () => { - const excluded = ['$exists', '$mod', '$size', '$type', '$regex', '$where']; - - for (const e of excluded) { - const obj = {label: {}}; - obj['label'][e] = true; - const m = convertPlainQueryToMongo(Simple, obj); - expect(m).toEqual(obj); - } -}); \ No newline at end of file + const excluded = ['$exists', '$mod', '$size', '$type', '$regex', '$where']; + + for (const e of excluded) { + const obj = { label: {} }; + obj['label'][e] = true; + const m = convertPlainQueryToMongo(Simple, obj); + expect(m).toEqual(obj); + } +}); diff --git a/packages/mongo/tests/mongo.spec.ts b/packages/mongo/tests/mongo.spec.ts index ad01d41ad..a78c80306 100644 --- a/packages/mongo/tests/mongo.spec.ts +++ b/packages/mongo/tests/mongo.spec.ts @@ -1,333 +1,378 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; import { - BinaryType, - DatabaseName, Entity, getCollectionName, getDatabaseName, getEntityName, ID, MongoIdType, - plainToClass, StringType, -} from "@marcj/marshal"; -import {Binary, ObjectID, MongoClient} from "mongodb"; -import {Database} from "../src/database"; -import {SimpleModel, SuperSimple} from "@marcj/marshal/tests/entities"; -import {uuid4Stringify} from "../src/mapping"; -import {Buffer} from "buffer"; + BinaryType, + DatabaseName, + Entity, + getCollectionName, + getDatabaseName, + getEntityName, + ID, + MongoIdType, + plainToClass, + StringType, +} from '@marcj/marshal'; +import { Binary, ObjectID, MongoClient } from 'mongodb'; +import { Database } from '../src/database'; +import { SimpleModel, SuperSimple } from '@marcj/marshal/tests/entities'; +import { uuid4Stringify } from '../src/mapping'; +import { Buffer } from 'buffer'; let connection: MongoClient; afterEach(async () => { - await connection.close(true); + await connection.close(true); }); test('test save model', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - - const instance = plainToClass(SimpleModel, { - name: 'myName', - }); - - await database.add(SimpleModel, instance); - expect((instance)['version']).toBe(1); - - expect(await database.count(SimpleModel)).toBe(1); - expect(await database.count(SimpleModel, {name: 'myName'})).toBe(1); - expect(await database.count(SimpleModel, {name: 'MyNameNOTEXIST'})).toBe(0); - - expect(await database.has(SimpleModel)).toBeTrue(); - expect(await database.has(SimpleModel, {name: 'myName'})).toBeTrue(); - expect(await database.has(SimpleModel, {name: 'myNameNOTEXIST'})).toBeFalse(); - - expect(await database.get(SimpleModel, {name: 'myName'})).not.toBeNull(); - expect(await database.get(SimpleModel, {name: 'myNameNOTEXIST'})).toBeNull(); - - const collection = connection.db('testing').collection(getCollectionName(SimpleModel)); - const mongoItem = await collection.find().toArray(); - expect(mongoItem).toBeArrayOfSize(1); - expect(mongoItem[0].name).toBe('myName'); - expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); - expect(mongoItem[0].id).toBeInstanceOf(Binary); - expect(uuid4Stringify(mongoItem[0].id)).toBe(instance.id); - - const found = await database.get(SimpleModel, {id: instance.id}); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + + const instance = plainToClass(SimpleModel, { + name: 'myName', + }); + + await database.add(SimpleModel, instance); + expect((instance)['version']).toBe(1); + + expect(await database.count(SimpleModel)).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'MyNameNOTEXIST' })).toBe(0); + + expect(await database.has(SimpleModel)).toBeTrue(); + expect(await database.has(SimpleModel, { name: 'myName' })).toBeTrue(); + expect( + await database.has(SimpleModel, { name: 'myNameNOTEXIST' }) + ).toBeFalse(); + + expect(await database.get(SimpleModel, { name: 'myName' })).not.toBeNull(); + expect( + await database.get(SimpleModel, { name: 'myNameNOTEXIST' }) + ).toBeNull(); + + const collection = connection + .db('testing') + .collection(getCollectionName(SimpleModel)); + const mongoItem = await collection.find().toArray(); + expect(mongoItem).toBeArrayOfSize(1); + expect(mongoItem[0].name).toBe('myName'); + expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); + expect(mongoItem[0].id).toBeInstanceOf(Binary); + expect(uuid4Stringify(mongoItem[0].id)).toBe(instance.id); + + const found = await database.get(SimpleModel, { id: instance.id }); + expect(found).toBeInstanceOf(SimpleModel); + expect(found!.name).toBe('myName'); + expect(found!.id).toBe(instance.id); + + const list = await database.find(SimpleModel, { id: instance.id }); + expect(list[0]).toBeInstanceOf(SimpleModel); + expect(list[0].name).toBe('myName'); + expect(list[0].id).toBe(instance.id); + + const listAll = await database.find(SimpleModel); + expect(listAll[0]).toBeInstanceOf(SimpleModel); + expect(listAll[0].name).toBe('myName'); + expect(listAll[0].id).toBe(instance.id); + + expect( + await database.patch( + SimpleModel, + { name: 'noneExisting' }, + { name: 'myName2' } + ) + ).toBeNull(); + + const notExisting = new SimpleModel('Hi'); + expect(await database.update(SimpleModel, notExisting)).toBeNull(); + + expect( + await database.patch(SimpleModel, { id: instance.id }, { name: 'myName2' }) + ).toBe((instance)['version'] + 1); + + { + const found = await database.get(SimpleModel, { id: instance.id }); expect(found).toBeInstanceOf(SimpleModel); - expect(found!.name).toBe('myName'); - expect(found!.id).toBe(instance.id); - - const list = await database.find(SimpleModel, {id: instance.id}); - expect(list[0]).toBeInstanceOf(SimpleModel); - expect(list[0].name).toBe('myName'); - expect(list[0].id).toBe(instance.id); - - const listAll = await database.find(SimpleModel); - expect(listAll[0]).toBeInstanceOf(SimpleModel); - expect(listAll[0].name).toBe('myName'); - expect(listAll[0].id).toBe(instance.id); - - expect(await database.patch(SimpleModel, {name: 'noneExisting'}, {name: 'myName2'})).toBeNull(); - - const notExisting = new SimpleModel('Hi'); - expect(await database.update(SimpleModel, notExisting)).toBeNull(); - - expect(await database.patch(SimpleModel, {id: instance.id}, {name: 'myName2'})).toBe((instance)['version'] + 1); - - { - const found = await database.get(SimpleModel, {id: instance.id}); - expect(found).toBeInstanceOf(SimpleModel); - expect(found!.name).toBe('myName2'); - } - - instance.name = 'New Name'; - await database.update(SimpleModel, instance); - expect(await database.has(SimpleModel, {name: 'MyName'})).toBeFalse(); - expect(await database.has(SimpleModel, {name: 'New Name'})).toBeTrue(); - - instance.name = 'New Name 2'; - await database.update(SimpleModel, instance, {noResult: '2132'}); - expect(await database.has(SimpleModel, {name: 'MyName'})).toBeFalse(); - expect(await database.has(SimpleModel, {name: 'MyName 2'})).toBeFalse(); - expect(await database.has(SimpleModel, {name: 'New Name'})).toBeTrue(); - - await database.update(SimpleModel, instance, {id: instance.id}); - expect(await database.has(SimpleModel, {name: 'MyName'})).toBeFalse(); - expect(await database.has(SimpleModel, {name: 'New Name'})).toBeFalse(); - expect(await database.has(SimpleModel, {name: 'New Name 2'})).toBeTrue(); + expect(found!.name).toBe('myName2'); + } + + instance.name = 'New Name'; + await database.update(SimpleModel, instance); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); + + instance.name = 'New Name 2'; + await database.update(SimpleModel, instance, { noResult: '2132' }); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'MyName 2' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); + + await database.update(SimpleModel, instance, { id: instance.id }); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name 2' })).toBeTrue(); }); test('test delete', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); - const instance1 = plainToClass(SimpleModel, { - name: 'myName1', - }); + const instance1 = plainToClass(SimpleModel, { + name: 'myName1', + }); - const instance2 = plainToClass(SimpleModel, { - name: 'myName2', - }); + const instance2 = plainToClass(SimpleModel, { + name: 'myName2', + }); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); - expect(await database.count(SimpleModel, {name: 'myName1'})).toBe(1); - expect(await database.count(SimpleModel, {name: 'myName2'})).toBe(1); - expect(await database.count(SimpleModel, {name: 'myName3'})).toBe(0); + expect(await database.count(SimpleModel)).toBe(2); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.remove(SimpleModel, instance1.id); + await database.remove(SimpleModel, instance1.id); - expect(await database.count(SimpleModel)).toBe(1); - expect(await database.count(SimpleModel, {name: 'myName1'})).toBe(0); - expect(await database.count(SimpleModel, {name: 'myName2'})).toBe(1); - expect(await database.count(SimpleModel, {name: 'myName3'})).toBe(0); + expect(await database.count(SimpleModel)).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.remove(SimpleModel, instance2.id); + await database.remove(SimpleModel, instance2.id); - expect(await database.count(SimpleModel)).toBe(0); - expect(await database.count(SimpleModel, {name: 'myName1'})).toBe(0); - expect(await database.count(SimpleModel, {name: 'myName2'})).toBe(0); - expect(await database.count(SimpleModel, {name: 'myName3'})).toBe(0); + expect(await database.count(SimpleModel)).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); + expect(await database.count(SimpleModel)).toBe(2); - await database.deleteMany(SimpleModel, {name: /myName[0-9]/}); - expect(await database.count(SimpleModel)).toBe(0); + await database.deleteMany(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(0); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); + expect(await database.count(SimpleModel)).toBe(2); - await database.deleteOne(SimpleModel, {name: /myName[0-9]/}); - expect(await database.count(SimpleModel)).toBe(1); + await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(1); - await database.deleteOne(SimpleModel, {name: /myName[0-9]/}); - expect(await database.count(SimpleModel)).toBe(0); + await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(0); }); test('test super simple model', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - const instance = plainToClass(SuperSimple, { - name: 'myName', - }); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + const instance = plainToClass(SuperSimple, { + name: 'myName', + }); + + expect(instance._id).toBeUndefined(); + await database.add(SuperSimple, instance); + expect(instance._id).not.toBeUndefined(); + + { + const items = await database.find(SuperSimple); + expect(items[0]).toBeInstanceOf(SuperSimple); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); + } - expect(instance._id).toBeUndefined(); - await database.add(SuperSimple, instance); - expect(instance._id).not.toBeUndefined(); - - { - const items = await database.find(SuperSimple); - expect(items[0]).toBeInstanceOf(SuperSimple); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); - } - - { - const items = await (await database.cursor(SuperSimple)).toArray(); - expect(items[0]).toBeInstanceOf(SuperSimple); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); - } + { + const items = await (await database.cursor(SuperSimple)).toArray(); + expect(items[0]).toBeInstanceOf(SuperSimple); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); + } }); test('test databaseName', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - - await connection.db('testing2').dropDatabase(); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing2') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - const instance = plainToClass(DifferentDataBase, { - name: 'myName', - }); - - expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(instance._id).toBeUndefined(); - await database.add(DifferentDataBase, instance); - expect(instance._id).not.toBeUndefined(); - - const collection = connection.db('testing2').collection('differentCollection'); - expect(await collection.countDocuments()).toBe(1); - - const items = await database.find(DifferentDataBase); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + + await connection.db('testing2').dropDatabase(); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing2') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + const instance = plainToClass(DifferentDataBase, { + name: 'myName', + }); + + expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(instance._id).toBeUndefined(); + await database.add(DifferentDataBase, instance); + expect(instance._id).not.toBeUndefined(); + + const collection = connection + .db('testing2') + .collection('differentCollection'); + expect(await collection.countDocuments()).toBe(1); + + const items = await database.find(DifferentDataBase); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); }); test('no id', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); - @Entity('NoId') - class NoId { - @MongoIdType() - _id?: string; + @Entity('NoId') + class NoId { + @MongoIdType() + _id?: string; - @StringType() - name?: string; - } + @StringType() + name?: string; + } - const instance = plainToClass(NoId, { - name: 'myName', - }); + const instance = plainToClass(NoId, { + name: 'myName', + }); - await database.add(NoId, instance); - expect(instance._id).toBeUndefined(); + await database.add(NoId, instance); + expect(instance._id).toBeUndefined(); - const dbItem = await database.get(NoId, {name: 'myName'}); - expect(dbItem!.name).toBe('myName'); + const dbItem = await database.get(NoId, { name: 'myName' }); + expect(dbItem!.name).toBe('myName'); - dbItem!.name = 'Changed'; + dbItem!.name = 'Changed'; - await expect(database.update(NoId, dbItem)).rejects.toThrow('Class NoId has no @ID() defined') + await expect(database.update(NoId, dbItem)).rejects.toThrow( + 'Class NoId has no @ID() defined' + ); }); test('test factory', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing2').dropDatabase(); - await connection.db('testing').dropDatabase(); - - const database = new Database(async () => { - return await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}) - }, 'testing'); - - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing2') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - const instance = plainToClass(DifferentDataBase, { - name: 'myName', + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing2').dropDatabase(); + await connection.db('testing').dropDatabase(); + + const database = new Database(async () => { + return await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, }); - - expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(instance._id).toBeUndefined(); - await database.add(DifferentDataBase, instance); - expect(instance._id).not.toBeUndefined(); - - const collection = connection.db('testing2').collection('differentCollection'); - expect(await collection.countDocuments()).toBe(1); - - const items = await database.find(DifferentDataBase); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); + }, 'testing'); + + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing2') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + const instance = plainToClass(DifferentDataBase, { + name: 'myName', + }); + + expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(instance._id).toBeUndefined(); + await database.add(DifferentDataBase, instance); + expect(instance._id).not.toBeUndefined(); + + const collection = connection + .db('testing2') + .collection('differentCollection'); + expect(await collection.countDocuments()).toBe(1); + + const items = await database.find(DifferentDataBase); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); }); - test('second object id', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser: true}); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - @Entity('SecondObjectId') - class SecondObjectId { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - - @MongoIdType() - secondId?: string; - } - - const instance = plainToClass(SecondObjectId, { - name: 'myName', - secondId: '5bf4a1ccce060e0b38864c9e', - preview: 'QmFhcg==', //Baar - }); - - await database.add(SecondObjectId, instance); - - const dbItem = await database.get(SecondObjectId, {name: 'myName'}); - expect(dbItem!.name).toBe('myName'); - - const dbItemBySecondId = await database.get(SecondObjectId, {secondId: '5bf4a1ccce060e0b38864c9e'}); - expect(dbItemBySecondId!.name).toBe('myName'); - - const collection = connection.db('testing').collection(getCollectionName(SecondObjectId)); - const mongoItem = await collection.find().toArray(); - expect(mongoItem).toBeArrayOfSize(1); - expect(mongoItem[0].name).toBe('myName'); - expect(mongoItem[0].preview).toBeInstanceOf(Binary); - expect(mongoItem[0].preview.buffer.toString('utf8')).toBe('Baar'); - - console.log(mongoItem[0]); - expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); - expect(mongoItem[0].secondId).toBeInstanceOf(ObjectID); - expect(mongoItem[0]._id.toHexString()).toBe(instance._id); - expect(mongoItem[0].secondId.toHexString()).toBe(instance.secondId); - + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + @Entity('SecondObjectId') + class SecondObjectId { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + + @MongoIdType() + secondId?: string; + } + + const instance = plainToClass(SecondObjectId, { + name: 'myName', + secondId: '5bf4a1ccce060e0b38864c9e', + preview: 'QmFhcg==', //Baar + }); + + await database.add(SecondObjectId, instance); + + const dbItem = await database.get(SecondObjectId, { name: 'myName' }); + expect(dbItem!.name).toBe('myName'); + + const dbItemBySecondId = await database.get(SecondObjectId, { + secondId: '5bf4a1ccce060e0b38864c9e', + }); + expect(dbItemBySecondId!.name).toBe('myName'); + + const collection = connection + .db('testing') + .collection(getCollectionName(SecondObjectId)); + const mongoItem = await collection.find().toArray(); + expect(mongoItem).toBeArrayOfSize(1); + expect(mongoItem[0].name).toBe('myName'); + expect(mongoItem[0].preview).toBeInstanceOf(Binary); + expect(mongoItem[0].preview.buffer.toString('utf8')).toBe('Baar'); + + console.log(mongoItem[0]); + expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); + expect(mongoItem[0].secondId).toBeInstanceOf(ObjectID); + expect(mongoItem[0]._id.toHexString()).toBe(instance._id); + expect(mongoItem[0].secondId.toHexString()).toBe(instance.secondId); }); diff --git a/packages/mongo/tests/to-class.spec.ts b/packages/mongo/tests/to-class.spec.ts index fdea0e34f..965c3ef45 100644 --- a/packages/mongo/tests/to-class.spec.ts +++ b/packages/mongo/tests/to-class.spec.ts @@ -1,152 +1,97 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; import { - AnyType, - ClassArray, - classToPlain, - cloneClass, - EnumType, - Exclude, - getEntityName, - getEnumKeys, - getEnumLabels, - getIdField, - getIdFieldValue, - getValidEnumValue, - isExcluded, - isValidEnumValue, - NumberType, - plainToClass, - StringType, - uuid, - getReflectionType, - getParentReferenceClass, - ParentReference, - ClassCircular, BooleanType, - Optional,Class, OnLoad -} from "@marcj/marshal"; + AnyType, + ClassArray, + classToPlain, + cloneClass, + EnumType, + Exclude, + getEntityName, + getEnumKeys, + getEnumLabels, + getIdField, + getIdFieldValue, + getValidEnumValue, + isExcluded, + isValidEnumValue, + NumberType, + plainToClass, + StringType, + uuid, + getReflectionType, + getParentReferenceClass, + ParentReference, + ClassCircular, + BooleanType, + Optional, + Class, + OnLoad, +} from '@marcj/marshal'; import { - now, - SimpleModel, - Plan, - SubModel, - CollectionWrapper, - StringCollectionWrapper, -} from "@marcj/marshal/tests/entities"; -import {Binary} from "mongodb"; -import {ClassWithUnmetParent, DocumentClass, ImpossibleToMetDocumentClass} from "@marcj/marshal/tests/document-scenario/DocumentClass"; -import {PageCollection} from "@marcj/marshal/tests/document-scenario/PageCollection"; -import {PageClass} from "@marcj/marshal/tests/document-scenario/PageClass"; -import {classToMongo, mongoToClass, plainToMongo} from "../src/mapping"; + now, + SimpleModel, + Plan, + SubModel, + CollectionWrapper, + StringCollectionWrapper, +} from '@marcj/marshal/tests/entities'; +import { Binary } from 'mongodb'; +import { + ClassWithUnmetParent, + DocumentClass, + ImpossibleToMetDocumentClass, +} from '@marcj/marshal/tests/document-scenario/DocumentClass'; +import { PageCollection } from '@marcj/marshal/tests/document-scenario/PageCollection'; +import { PageClass } from '@marcj/marshal/tests/document-scenario/PageClass'; +import { classToMongo, mongoToClass, plainToMongo } from '../src/mapping'; test('test simple model', () => { - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - expect(getIdField(SimpleModel)).toBe('id'); - - expect(getIdField(SubModel)).toBe(null); - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - id: 'my-super-id', - name: 'myName', - }); - - expect(instance).toBeInstanceOf(SimpleModel); - expect(instance.id).toBe('my-super-id'); - expect(instance.name).toBe('myName'); - expect(instance.type).toBe(0); - expect(instance.plan).toBe(Plan.DEFAULT); - expect(instance.created).toBeDate(); - expect(instance.created).toBe(now); - - expect(getIdFieldValue(SimpleModel, instance)).toBe('my-super-id'); - } -}); + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + expect(getIdField(SimpleModel)).toBe('id'); -test('test simple model all fields', () => { - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - expect(getIdField(SimpleModel)).toBe('id'); - - expect(getIdField(SubModel)).toBe(null); - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - type: 5, - plan: 1, - yesNo: '1', - created: 'Sat Oct 13 2018 14:17:35 GMT+0200', - children: [ - {label: 'fooo'}, - {label: 'barr'}, - ], - childrenMap: { - foo: { - label: 'bar' - }, - foo2: { - label: 'bar2' - } - } - }); - - expect(instance).toBeInstanceOf(SimpleModel); - expect(instance.id).toBeString(); - expect(instance.name).toBe('myName'); - expect(instance.type).toBe(5); - expect(instance.yesNo).toBe(true); - expect(instance.plan).toBe(Plan.PRO); - expect(instance.created).toBeDate(); - expect(instance.created).toEqual(new Date('Sat Oct 13 2018 14:17:35 GMT+0200')); - - expect(instance.children).toBeArrayOfSize(2); - - expect(instance.children[0]).toBeInstanceOf(SubModel); - expect(instance.children[1]).toBeInstanceOf(SubModel); - - expect(instance.children[0].label).toBe('fooo'); - expect(instance.children[1].label).toBe('barr'); - - expect(instance.childrenMap).toBeObject(); - expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); - expect(instance.childrenMap.foo2).toBeInstanceOf(SubModel); - - expect(instance.childrenMap.foo.label).toBe('bar'); - expect(instance.childrenMap.foo2.label).toBe('bar2'); - - expect(getIdFieldValue(SimpleModel, instance)).toBeString(); - - const plain = classToPlain(SimpleModel, instance); - expect(plain.yesNo).toBeTrue(); - expect(plain.plan).toBe(1); - - const copy = cloneClass(instance); - expect(instance !== copy).toBeTrue(); - expect(instance.children[0] !== copy.children[0]).toBeTrue(); - expect(instance.children[1] !== copy.children[1]).toBeTrue(); - expect(instance.childrenMap.foo !== copy.childrenMap.foo).toBeTrue(); - expect(instance.childrenMap.foo2 !== copy.childrenMap.foo2).toBeTrue(); - expect(instance.created !== copy.created).toBeTrue(); - - expect(plain).toEqual(classToPlain(SimpleModel, copy)); - } -}); + expect(getIdField(SubModel)).toBe(null); -test('test simple model with not mapped fields', () => { - expect(isExcluded(SimpleModel, 'excluded', 'mongo')).toBeTrue(); - expect(isExcluded(SimpleModel, 'excluded', 'plain')).toBeTrue(); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + id: 'my-super-id', + name: 'myName', + }); - expect(isExcluded(SimpleModel, 'excludedForPlain', 'mongo')).toBeFalse(); - expect(isExcluded(SimpleModel, 'excludedForPlain', 'plain')).toBeTrue(); + expect(instance).toBeInstanceOf(SimpleModel); + expect(instance.id).toBe('my-super-id'); + expect(instance.name).toBe('myName'); + expect(instance.type).toBe(0); + expect(instance.plan).toBe(Plan.DEFAULT); + expect(instance.created).toBeDate(); + expect(instance.created).toBe(now); - expect(isExcluded(SimpleModel, 'excludedForMongo', 'mongo')).toBeTrue(); - expect(isExcluded(SimpleModel, 'excludedForMongo', 'plain')).toBeFalse(); + expect(getIdFieldValue(SimpleModel, instance)).toBe('my-super-id'); + } +}); - const instance = plainToClass(SimpleModel, { - name: 'myName', - type: 5, - yesNo: '1', - notMapped: {a: 'foo'} +test('test simple model all fields', () => { + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + expect(getIdField(SimpleModel)).toBe('id'); + + expect(getIdField(SubModel)).toBe(null); + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + type: 5, + plan: 1, + yesNo: '1', + created: 'Sat Oct 13 2018 14:17:35 GMT+0200', + children: [{ label: 'fooo' }, { label: 'barr' }], + childrenMap: { + foo: { + label: 'bar', + }, + foo2: { + label: 'bar2', + }, + }, }); expect(instance).toBeInstanceOf(SimpleModel); @@ -154,655 +99,743 @@ test('test simple model with not mapped fields', () => { expect(instance.name).toBe('myName'); expect(instance.type).toBe(5); expect(instance.yesNo).toBe(true); - expect(instance.notMapped).toEqual({}); - expect(instance.excluded).toBe('default'); - expect(instance.excludedForPlain).toBe('excludedForPlain'); - expect(instance.excludedForMongo).toBe('excludedForMongo'); + expect(instance.plan).toBe(Plan.PRO); + expect(instance.created).toBeDate(); + expect(instance.created).toEqual( + new Date('Sat Oct 13 2018 14:17:35 GMT+0200') + ); - const mongoEntry = plainToMongo(SimpleModel, { - id: uuid(), - name: 'myName', - type: 5, - yesNo: 'eads', - notMapped: {a: 'foo'}, - excludedForPlain: 'excludedForPlain' - }); + expect(instance.children).toBeArrayOfSize(2); - expect(mongoEntry.id).toBeInstanceOf(Binary); - expect(mongoEntry.name).toBe('myName'); - expect(mongoEntry.type).toBe(5); - expect(mongoEntry.yesNo).toBe(false); - expect(mongoEntry.notMapped).toBeUndefined(); - expect(mongoEntry.excluded).toBeUndefined(); - expect(mongoEntry.excludedForPlain).toBe('excludedForPlain'); - expect(mongoEntry.excludedForMongo).toBeUndefined(); - - const plainObject = classToPlain(SimpleModel, instance); - - expect(plainObject.id).toBeString(); - expect(plainObject.name).toBe('myName'); - expect(plainObject.type).toBe(5); - expect(plainObject.notMapped).toBeUndefined(); - expect(plainObject.excluded).toBeUndefined(); - expect(plainObject.excludedForPlain).toBeUndefined(); - expect(plainObject.excludedForMongo).toBe('excludedForMongo'); -}); + expect(instance.children[0]).toBeInstanceOf(SubModel); + expect(instance.children[1]).toBeInstanceOf(SubModel); -test('test @decorator', async () => { - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - stringChildrenCollection: ['Foo', 'Bar'] - }); - - expect(instance.name).toBe('myName'); - expect(instance.stringChildrenCollection).toBeInstanceOf(StringCollectionWrapper); - expect(instance.stringChildrenCollection.items).toEqual(['Foo', 'Bar']); - - instance.stringChildrenCollection.add('Bar2'); - expect(instance.stringChildrenCollection.items[2]).toBe('Bar2'); - - const plain = classToPlain(SimpleModel, instance); - expect(plain.name).toBe('myName'); - expect(plain.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); - - const mongo = classToMongo(SimpleModel, instance); - expect(mongo.name).toBe('myName'); - expect(mongo.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); - - const instance2 = toClass(SimpleModel, { - name: 'myName', - stringChildrenCollection: false - }); - - expect(instance2.name).toBe('myName'); - expect(instance2.stringChildrenCollection).toBeInstanceOf(StringCollectionWrapper); - expect(instance2.stringChildrenCollection.items).toEqual([]); - } -}); + expect(instance.children[0].label).toBe('fooo'); + expect(instance.children[1].label).toBe('barr'); -test('test childrenMap', async () => { + expect(instance.childrenMap).toBeObject(); + expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); + expect(instance.childrenMap.foo2).toBeInstanceOf(SubModel); - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - childrenMap: {foo: {label: 'Foo'}, bar: {label: 'Bar'}} - }); + expect(instance.childrenMap.foo.label).toBe('bar'); + expect(instance.childrenMap.foo2.label).toBe('bar2'); - expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); - expect(instance.childrenMap.bar).toBeInstanceOf(SubModel); + expect(getIdFieldValue(SimpleModel, instance)).toBeString(); - expect(instance.childrenMap.foo.label).toBe('Foo'); - expect(instance.childrenMap.bar.label).toBe('Bar'); - } -}); + const plain = classToPlain(SimpleModel, instance); + expect(plain.yesNo).toBeTrue(); + expect(plain.plan).toBe(1); -test('test allowNull', async () => { - class Model { - @StringType() - @Optional() - name: string | null = null; - } + const copy = cloneClass(instance); + expect(instance !== copy).toBeTrue(); + expect(instance.children[0] !== copy.children[0]).toBeTrue(); + expect(instance.children[1] !== copy.children[1]).toBeTrue(); + expect(instance.childrenMap.foo !== copy.childrenMap.foo).toBeTrue(); + expect(instance.childrenMap.foo2 !== copy.childrenMap.foo2).toBeTrue(); + expect(instance.created !== copy.created).toBeTrue(); - for (const toClass of [plainToClass, mongoToClass]) { - expect(toClass(Model, {}).name).toBe(null); - expect(toClass(Model, {name: null}).name).toBe(null); - expect(toClass(Model, {name: undefined}).name).toBe(null); - } + expect(plain).toEqual(classToPlain(SimpleModel, copy)); + } }); -test('test OnLoad', async () => { - let ModelRef; - - class Sub { - @StringType() - name?: string; - - @AnyType() - onLoadCallback: (item: Sub) => void; - - @AnyType() - onFullLoadCallback: (item: Sub) => void; +test('test simple model with not mapped fields', () => { + expect(isExcluded(SimpleModel, 'excluded', 'mongo')).toBeTrue(); + expect(isExcluded(SimpleModel, 'excluded', 'plain')).toBeTrue(); + + expect(isExcluded(SimpleModel, 'excludedForPlain', 'mongo')).toBeFalse(); + expect(isExcluded(SimpleModel, 'excludedForPlain', 'plain')).toBeTrue(); + + expect(isExcluded(SimpleModel, 'excludedForMongo', 'mongo')).toBeTrue(); + expect(isExcluded(SimpleModel, 'excludedForMongo', 'plain')).toBeFalse(); + + const instance = plainToClass(SimpleModel, { + name: 'myName', + type: 5, + yesNo: '1', + notMapped: { a: 'foo' }, + }); + + expect(instance).toBeInstanceOf(SimpleModel); + expect(instance.id).toBeString(); + expect(instance.name).toBe('myName'); + expect(instance.type).toBe(5); + expect(instance.yesNo).toBe(true); + expect(instance.notMapped).toEqual({}); + expect(instance.excluded).toBe('default'); + expect(instance.excludedForPlain).toBe('excludedForPlain'); + expect(instance.excludedForMongo).toBe('excludedForMongo'); + + const mongoEntry = plainToMongo(SimpleModel, { + id: uuid(), + name: 'myName', + type: 5, + yesNo: 'eads', + notMapped: { a: 'foo' }, + excludedForPlain: 'excludedForPlain', + }); + + expect(mongoEntry.id).toBeInstanceOf(Binary); + expect(mongoEntry.name).toBe('myName'); + expect(mongoEntry.type).toBe(5); + expect(mongoEntry.yesNo).toBe(false); + expect(mongoEntry.notMapped).toBeUndefined(); + expect(mongoEntry.excluded).toBeUndefined(); + expect(mongoEntry.excludedForPlain).toBe('excludedForPlain'); + expect(mongoEntry.excludedForMongo).toBeUndefined(); + + const plainObject = classToPlain(SimpleModel, instance); + + expect(plainObject.id).toBeString(); + expect(plainObject.name).toBe('myName'); + expect(plainObject.type).toBe(5); + expect(plainObject.notMapped).toBeUndefined(); + expect(plainObject.excluded).toBeUndefined(); + expect(plainObject.excludedForPlain).toBeUndefined(); + expect(plainObject.excludedForMongo).toBe('excludedForMongo'); +}); - @ClassCircular(() => ModelRef) - @ParentReference() - parent?: any; +test('test @decorator', async () => { + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + stringChildrenCollection: ['Foo', 'Bar'], + }); - constructor(name: string, onLoadCallback: (item: Sub) => void, onFullLoadCallback: (item: Sub) => void) { - this.name = name; - this.onLoadCallback = onLoadCallback; - this.onFullLoadCallback = onFullLoadCallback; - } + expect(instance.name).toBe('myName'); + expect(instance.stringChildrenCollection).toBeInstanceOf( + StringCollectionWrapper + ); + expect(instance.stringChildrenCollection.items).toEqual(['Foo', 'Bar']); - @OnLoad() - onLoad() { - this.onLoadCallback(this); - } + instance.stringChildrenCollection.add('Bar2'); + expect(instance.stringChildrenCollection.items[2]).toBe('Bar2'); - @OnLoad({fullLoad: true}) - onFullLoad() { - this.onFullLoadCallback(this); - } - } + const plain = classToPlain(SimpleModel, instance); + expect(plain.name).toBe('myName'); + expect(plain.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); - class Model { - @StringType() - @Optional() - name: string | null = null; + const mongo = classToMongo(SimpleModel, instance); + expect(mongo.name).toBe('myName'); + expect(mongo.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); - @Class(Sub) - sub?: Sub; + const instance2 = toClass(SimpleModel, { + name: 'myName', + stringChildrenCollection: false, + }); - @Class(Sub) - sub2?: Sub; - } + expect(instance2.name).toBe('myName'); + expect(instance2.stringChildrenCollection).toBeInstanceOf( + StringCollectionWrapper + ); + expect(instance2.stringChildrenCollection.items).toEqual([]); + } +}); - ModelRef = Model; +test('test childrenMap', async () => { + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + childrenMap: { foo: { label: 'Foo' }, bar: { label: 'Bar' } }, + }); - for (const toClass of [plainToClass, mongoToClass]) { - let onLoadedTriggered = false; - let onFullLoadedTriggered = false; + expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); + expect(instance.childrenMap.bar).toBeInstanceOf(SubModel); - const instance = toClass(Model, { - name: 'Root', + expect(instance.childrenMap.foo.label).toBe('Foo'); + expect(instance.childrenMap.bar.label).toBe('Bar'); + } +}); - sub: { - name: 'Hi', +test('test allowNull', async () => { + class Model { + @StringType() + @Optional() + name: string | null = null; + } + + for (const toClass of [plainToClass, mongoToClass]) { + expect(toClass(Model, {}).name).toBe(null); + expect(toClass(Model, { name: null }).name).toBe(null); + expect(toClass(Model, { name: undefined }).name).toBe(null); + } +}); - //on regular "OnLoad()" parent-references are not loaded yet - onLoadCallback: (item: Sub) => { - expect(item.parent.sub2).toBeUndefined(); - onLoadedTriggered = true; - }, - //on full "OnLoad({fullLoad: true})" all parent-references are loaded - onFullLoadCallback: (item: Sub) => { - expect(item.parent.sub2).toBeInstanceOf(Sub); - onFullLoadedTriggered = true; - }, - }, +test('test OnLoad', async () => { + let ModelRef; + + class Sub { + @StringType() + name?: string; + + @AnyType() + onLoadCallback: (item: Sub) => void; + + @AnyType() + onFullLoadCallback: (item: Sub) => void; + + @ClassCircular(() => ModelRef) + @ParentReference() + parent?: any; + + constructor( + name: string, + onLoadCallback: (item: Sub) => void, + onFullLoadCallback: (item: Sub) => void + ) { + this.name = name; + this.onLoadCallback = onLoadCallback; + this.onFullLoadCallback = onFullLoadCallback; + } - sub2: { - name: 'Hi2', - onLoadCallback: (item: Sub) => { - }, - onFullLoadCallback: (item: Sub) => { - }, - }, - }); + @OnLoad() + onLoad() { + this.onLoadCallback(this); + } - expect(instance.name).toBe('Root'); - expect(onLoadedTriggered).toBe(true); - expect(onFullLoadedTriggered).toBe(true); + @OnLoad({ fullLoad: true }) + onFullLoad() { + this.onFullLoadCallback(this); } + } + + class Model { + @StringType() + @Optional() + name: string | null = null; + + @Class(Sub) + sub?: Sub; + + @Class(Sub) + sub2?: Sub; + } + + ModelRef = Model; + + for (const toClass of [plainToClass, mongoToClass]) { + let onLoadedTriggered = false; + let onFullLoadedTriggered = false; + + const instance = toClass(Model, { + name: 'Root', + + sub: { + name: 'Hi', + + //on regular "OnLoad()" parent-references are not loaded yet + onLoadCallback: (item: Sub) => { + expect(item.parent.sub2).toBeUndefined(); + onLoadedTriggered = true; + }, + //on full "OnLoad({fullLoad: true})" all parent-references are loaded + onFullLoadCallback: (item: Sub) => { + expect(item.parent.sub2).toBeInstanceOf(Sub); + onFullLoadedTriggered = true; + }, + }, + + sub2: { + name: 'Hi2', + onLoadCallback: (item: Sub) => {}, + onFullLoadCallback: (item: Sub) => {}, + }, + }); + + expect(instance.name).toBe('Root'); + expect(onLoadedTriggered).toBe(true); + expect(onFullLoadedTriggered).toBe(true); + } }); test('test setter/getter', async () => { - class Font { - name?: string; - } + class Font { + name?: string; + } - class Model { - @Exclude() - private _fonts?: Font[]; + class Model { + @Exclude() + private _fonts?: Font[]; - get test() { - return true; - } + get test() { + return true; + } - @ClassArray(Font) - get fonts() { - return this._fonts; - } + @ClassArray(Font) + get fonts() { + return this._fonts; + } - set fonts(v) { - this._fonts = v; - } + set fonts(v) { + this._fonts = v; } + } - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - fonts: [{name: 'Arial'}, {name: 'Verdana'}] - }); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + fonts: [{ name: 'Arial' }, { name: 'Verdana' }], + }); - expect(instance.test).toBeTrue(); - expect(instance.fonts!.length).toBe(2); + expect(instance.test).toBeTrue(); + expect(instance.fonts!.length).toBe(2); - const plain = classToPlain(Model, instance); - expect(plain._fonts).toBeUndefined(); - expect(plain.fonts).toBeArrayOfSize(2); - - const mongo = classToMongo(Model, instance); - expect(mongo._fonts).toBeUndefined(); - expect(mongo.fonts).toBeArrayOfSize(2); - } + const plain = classToPlain(Model, instance); + expect(plain._fonts).toBeUndefined(); + expect(plain.fonts).toBeArrayOfSize(2); + const mongo = classToMongo(Model, instance); + expect(mongo._fonts).toBeUndefined(); + expect(mongo.fonts).toBeArrayOfSize(2); + } }); test('test decorator complex', async () => { - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - childrenCollection: [{label: 'Foo'}, {label: 'Bar'}] - }); - - expect(instance.name).toBe('myName'); - expect(instance.childrenCollection).toBeInstanceOf(CollectionWrapper); - expect(instance.childrenCollection.items[0]).toBeInstanceOf(SubModel); - expect(instance.childrenCollection.items[1]).toBeInstanceOf(SubModel); - expect(instance.childrenCollection.items[0].label).toBe('Foo'); - expect(instance.childrenCollection.items[1].label).toBe('Bar'); - expect(instance.childrenCollection.items[1].constructorUsed).toBeTrue(); - - instance.childrenCollection.add(new SubModel('Bar2')); - expect(instance.childrenCollection.items[2].label).toEqual('Bar2'); - - const plain = classToPlain(SimpleModel, instance); - - expect(plain.name).toBe('myName'); - expect(plain.childrenCollection).toEqual([{label: 'Foo'}, {label: 'Bar'}, {label: 'Bar2'}]); - - const mongo = classToMongo(SimpleModel, instance); - expect(mongo.name).toBe('myName'); - expect(mongo.childrenCollection).toEqual([{label: 'Foo'}, {label: 'Bar'}, {label: 'Bar2'}]); - } + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + childrenCollection: [{ label: 'Foo' }, { label: 'Bar' }], + }); + + expect(instance.name).toBe('myName'); + expect(instance.childrenCollection).toBeInstanceOf(CollectionWrapper); + expect(instance.childrenCollection.items[0]).toBeInstanceOf(SubModel); + expect(instance.childrenCollection.items[1]).toBeInstanceOf(SubModel); + expect(instance.childrenCollection.items[0].label).toBe('Foo'); + expect(instance.childrenCollection.items[1].label).toBe('Bar'); + expect(instance.childrenCollection.items[1].constructorUsed).toBeTrue(); + + instance.childrenCollection.add(new SubModel('Bar2')); + expect(instance.childrenCollection.items[2].label).toEqual('Bar2'); + + const plain = classToPlain(SimpleModel, instance); + + expect(plain.name).toBe('myName'); + expect(plain.childrenCollection).toEqual([ + { label: 'Foo' }, + { label: 'Bar' }, + { label: 'Bar2' }, + ]); + + const mongo = classToMongo(SimpleModel, instance); + expect(mongo.name).toBe('myName'); + expect(mongo.childrenCollection).toEqual([ + { label: 'Foo' }, + { label: 'Bar' }, + { label: 'Bar2' }, + ]); + } }); test('test @decorator with parent', async () => { - expect(getReflectionType(DocumentClass, 'pages')).toEqual({type: 'class', typeValue: PageCollection}); - expect(getReflectionType(PageCollection, 'pages')).toEqual({type: 'class', typeValue: PageClass}); - expect(getReflectionType(PageClass, 'parent')).toEqual({type: 'class', typeValue: PageClass}); - expect(getReflectionType(PageClass, 'document')).toEqual({type: 'class', typeValue: DocumentClass}); - expect(getReflectionType(PageClass, 'children')).toEqual({type: 'class', typeValue: PageCollection}); + expect(getReflectionType(DocumentClass, 'pages')).toEqual({ + type: 'class', + typeValue: PageCollection, + }); + expect(getReflectionType(PageCollection, 'pages')).toEqual({ + type: 'class', + typeValue: PageClass, + }); + expect(getReflectionType(PageClass, 'parent')).toEqual({ + type: 'class', + typeValue: PageClass, + }); + expect(getReflectionType(PageClass, 'document')).toEqual({ + type: 'class', + typeValue: DocumentClass, + }); + expect(getReflectionType(PageClass, 'children')).toEqual({ + type: 'class', + typeValue: PageCollection, + }); + + expect(getParentReferenceClass(PageClass, 'parent')).toBe(PageClass); + + expect(() => { + const instance = mongoToClass(ClassWithUnmetParent, {}); + }).toThrow('ClassWithUnmetParent::parent is defined as'); + + expect(() => { + const instance = mongoToClass(PageClass, { + name: 'myName', + }); + }).toThrow('PageClass::document is in constructor has'); - expect(getParentReferenceClass(PageClass, 'parent')).toBe(PageClass); + { + const doc = new DocumentClass(); - expect(() => { - const instance = mongoToClass(ClassWithUnmetParent, { - }); - }).toThrow('ClassWithUnmetParent::parent is defined as'); + const instance = mongoToClass( + PageClass, + { + name: 'myName', + }, + [doc] + ); - expect(() => { - const instance = mongoToClass(PageClass, { - name: 'myName' - }); - }).toThrow('PageClass::document is in constructor has'); + expect(instance.document).toBe(doc); + } - { - const doc = new DocumentClass(); + expect(() => { + const instance = mongoToClass(ImpossibleToMetDocumentClass, { + name: 'myName', + pages: [ + { + name: 'Foo', + children: [ + { + name: 'Foo.1', + }, + ], + }, + { name: 'Bar' }, + ], + }); + }).toThrow('PageClass::document is in constructor has'); + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(DocumentClass, { + name: 'myName', + page: { + name: 'RootPage', + children: [ + { name: 'RootPage.1' }, + { name: 'RootPage.2' }, + { name: 'RootPage.3' }, + ], + }, + pages: [ + { + name: 'Foo', + children: [ + { + name: 'Foo.1', + children: [ + { name: 'Foo.1.1' }, + { name: 'Foo.1.2' }, + { name: 'Foo.1.3' }, + ], + }, + ], + }, + { name: 'Bar' }, + ], + }); - const instance = mongoToClass(PageClass, { - name: 'myName' - }, [doc]); + expect(instance.name).toBe('myName'); - expect(instance.document).toBe(doc); - } + expect(instance.page).toBeInstanceOf(PageClass); + expect(instance.page!.children.get(0)!.parent).toBe(instance.page); + expect(instance.page!.children.get(1)!.parent).toBe(instance.page); + expect(instance.page!.children.get(2)!.parent).toBe(instance.page); - expect(() => { - const instance = mongoToClass(ImpossibleToMetDocumentClass, { - name: 'myName', - pages: [ - { - name: 'Foo', - children: [ - { - name: 'Foo.1' - } - ] - }, - {name: 'Bar'} - ] - }); - }).toThrow('PageClass::document is in constructor has'); + expect(instance.pages).toBeInstanceOf(PageCollection); + expect(instance.pages.count()).toBe(2); + expect(instance.pages.get(0)).toBeInstanceOf(PageClass); + expect(instance.pages.get(1)).toBeInstanceOf(PageClass); - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(DocumentClass, { - name: 'myName', - page: { - name: 'RootPage', - children: [ - {name: 'RootPage.1'}, - {name: 'RootPage.2'}, - {name: 'RootPage.3'}, - ] - }, - pages: [ - { - name: 'Foo', - children: [ - { - name: 'Foo.1', - children: [ - {name: 'Foo.1.1'}, - {name: 'Foo.1.2'}, - {name: 'Foo.1.3'}, - ] - } - ] - }, - {name: 'Bar'} - ] - }); - - expect(instance.name).toBe('myName'); - - expect(instance.page).toBeInstanceOf(PageClass); - expect(instance.page!.children.get(0)!.parent).toBe(instance.page); - expect(instance.page!.children.get(1)!.parent).toBe(instance.page); - expect(instance.page!.children.get(2)!.parent).toBe(instance.page); - - expect(instance.pages).toBeInstanceOf(PageCollection); - expect(instance.pages.count()).toBe(2); - expect(instance.pages.get(0)).toBeInstanceOf(PageClass); - expect(instance.pages.get(1)).toBeInstanceOf(PageClass); - - expect(instance.pages.get(0)!.name).toBe('Foo'); - expect(instance.pages.get(1)!.name).toBe('Bar'); - expect(instance.pages.get(0)!.parent).toBeUndefined(); - expect(instance.pages.get(1)!.parent).toBeUndefined(); - - expect(instance.pages.get(0)!.document).toBe(instance); - expect(instance.pages.get(1)!.document).toBe(instance); - - expect(instance.pages.get(0)!.children).toBeInstanceOf(PageCollection); - - const foo_1 = instance.pages.get(0)!.children.get(0); - expect(foo_1).toBeInstanceOf(PageClass); - expect(foo_1!.name).toBe('Foo.1'); - expect(foo_1!.document).toBe(instance); - - expect(foo_1!.parent).not.toBeUndefined(); - expect(foo_1!.parent!.name).toBe('Foo'); - expect(foo_1!.parent).toBe(instance.pages.get(0)); - - expect(foo_1!.children.count()).toBe(3); - const foo_1_1 = foo_1!.children.get(0); - const foo_1_2 = foo_1!.children.get(1); - const foo_1_3 = foo_1!.children.get(2); - - expect(foo_1_1).toBeInstanceOf(PageClass); - expect(foo_1_2).toBeInstanceOf(PageClass); - expect(foo_1_3).toBeInstanceOf(PageClass); - - expect(foo_1_1!.parent).toBeInstanceOf(PageClass); - expect(foo_1_2!.parent).toBeInstanceOf(PageClass); - expect(foo_1_3!.parent).toBeInstanceOf(PageClass); - - expect(foo_1_1!.parent).toBe(foo_1); - expect(foo_1_2!.parent).toBe(foo_1); - expect(foo_1_3!.parent).toBe(foo_1); - - expect(foo_1_1!.document).toBe(instance); - expect(foo_1_2!.document).toBe(instance); - expect(foo_1_3!.document).toBe(instance); - - expect(foo_1_1!.parent!.name).toBe('Foo.1'); - expect(foo_1_2!.parent!.name).toBe('Foo.1'); - expect(foo_1_3!.parent!.name).toBe('Foo.1'); - - expect(() => { - const clone = cloneClass(instance.page); - }).toThrow('PageClass::document is in constructor has'); - - const clone = cloneClass(instance.page, [instance]); - expect(clone).toBeInstanceOf(PageClass); - expect(clone!.document).toBe(instance); - expect(clone!.parent).toBeUndefined(); - - for (const toPlain of [classToPlain, classToMongo]) { - const plain = toPlain(DocumentClass, instance); - - expect(plain.name).toBe('myName'); - expect(plain.pages[0].name).toEqual('Foo'); - expect(plain.pages[1].name).toEqual('Bar'); - expect(plain.pages[0].children[0].name).toEqual('Foo.1'); - expect(plain.pages[0].parent).toBeUndefined(); - - expect(plain.pages[0].parent).toBeUndefined(); - } - } -}); + expect(instance.pages.get(0)!.name).toBe('Foo'); + expect(instance.pages.get(1)!.name).toBe('Bar'); + expect(instance.pages.get(0)!.parent).toBeUndefined(); + expect(instance.pages.get(1)!.parent).toBeUndefined(); + expect(instance.pages.get(0)!.document).toBe(instance); + expect(instance.pages.get(1)!.document).toBe(instance); -test('simple string + number + boolean', () => { + expect(instance.pages.get(0)!.children).toBeInstanceOf(PageCollection); - class Model { - @StringType() - name?: string; + const foo_1 = instance.pages.get(0)!.children.get(0); + expect(foo_1).toBeInstanceOf(PageClass); + expect(foo_1!.name).toBe('Foo.1'); + expect(foo_1!.document).toBe(instance); - @NumberType() - age?: string; + expect(foo_1!.parent).not.toBeUndefined(); + expect(foo_1!.parent!.name).toBe('Foo'); + expect(foo_1!.parent).toBe(instance.pages.get(0)); - @BooleanType() - yesNo?: boolean; - } + expect(foo_1!.children.count()).toBe(3); + const foo_1_1 = foo_1!.children.get(0); + const foo_1_2 = foo_1!.children.get(1); + const foo_1_3 = foo_1!.children.get(2); - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - name: 1, - age: '2', - yesNo: 'false' - }); - expect(instance.name).toBe('1'); - expect(instance.age).toBe(2); - - expect(toClass(Model, {yesNo: 'false'}).yesNo).toBeFalse(); - expect(toClass(Model, {yesNo: '0'}).yesNo).toBeFalse(); - expect(toClass(Model, {yesNo: false}).yesNo).toBeFalse(); - expect(toClass(Model, {yesNo: 0}).yesNo).toBeFalse(); - expect(toClass(Model, {yesNo: 'nothing'}).yesNo).toBeFalse(); - expect(toClass(Model, {yesNo: null}).yesNo).toBeNull(); - - expect(toClass(Model, {yesNo: 'true'}).yesNo).toBeTrue(); - expect(toClass(Model, {yesNo: '1'}).yesNo).toBeTrue(); - expect(toClass(Model, {yesNo: true}).yesNo).toBeTrue(); - expect(toClass(Model, {yesNo: 1}).yesNo).toBeTrue(); - expect(toClass(Model, {yesNo: null}).yesNo).toBeNull(); - } -}); + expect(foo_1_1).toBeInstanceOf(PageClass); + expect(foo_1_2).toBeInstanceOf(PageClass); + expect(foo_1_3).toBeInstanceOf(PageClass); + expect(foo_1_1!.parent).toBeInstanceOf(PageClass); + expect(foo_1_2!.parent).toBeInstanceOf(PageClass); + expect(foo_1_3!.parent).toBeInstanceOf(PageClass); -test('cloneClass', () => { - class SubModel { - @StringType() - name?: string; - } + expect(foo_1_1!.parent).toBe(foo_1); + expect(foo_1_2!.parent).toBe(foo_1); + expect(foo_1_3!.parent).toBe(foo_1); - class DataStruct { - @StringType() - name?: string; - } + expect(foo_1_1!.document).toBe(instance); + expect(foo_1_2!.document).toBe(instance); + expect(foo_1_3!.document).toBe(instance); + + expect(foo_1_1!.parent!.name).toBe('Foo.1'); + expect(foo_1_2!.parent!.name).toBe('Foo.1'); + expect(foo_1_3!.parent!.name).toBe('Foo.1'); - class Model { - @AnyType() - data: any; + expect(() => { + const clone = cloneClass(instance.page); + }).toThrow('PageClass::document is in constructor has'); - @Class(DataStruct) - dataStruct?: DataStruct; + const clone = cloneClass(instance.page, [instance]); + expect(clone).toBeInstanceOf(PageClass); + expect(clone!.document).toBe(instance); + expect(clone!.parent).toBeUndefined(); - @ClassArray(SubModel) - subs?: SubModel[]; - } + for (const toPlain of [classToPlain, classToMongo]) { + const plain = toPlain(DocumentClass, instance); + + expect(plain.name).toBe('myName'); + expect(plain.pages[0].name).toEqual('Foo'); + expect(plain.pages[1].name).toEqual('Bar'); + expect(plain.pages[0].children[0].name).toEqual('Foo.1'); + expect(plain.pages[0].parent).toBeUndefined(); - const data = { - a: 'true' - }; - - const dataStruct = { - name: 'Foo' - }; - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - data: data, - dataStruct: dataStruct, - subs: [{name: 'foo'}], - }); - - expect(instance.data).toEqual({a: 'true'}); - expect(instance.data).not.toBe(data); - expect(instance.dataStruct!.name).toBe('Foo'); - expect(instance.dataStruct).toEqual(dataStruct); - expect(instance.dataStruct).not.toBe(dataStruct); - - const cloned = cloneClass(instance); - expect(cloned.data).toEqual({a: 'true'}); - expect(cloned.data).not.toBe(data); - expect(cloned.dataStruct!.name).toBe('Foo'); - expect(cloned.dataStruct).toEqual(dataStruct); - expect(cloned.dataStruct).not.toBe(dataStruct); - - expect(cloned.subs![0]).not.toBe(instance.subs![0]); + expect(plain.pages[0].parent).toBeUndefined(); } + } }); -test('enums', () => { - enum Enum1 { - first, - second, - } +test('simple string + number + boolean', () => { + class Model { + @StringType() + name?: string; + + @NumberType() + age?: string; + + @BooleanType() + yesNo?: boolean; + } + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + name: 1, + age: '2', + yesNo: 'false', + }); + expect(instance.name).toBe('1'); + expect(instance.age).toBe(2); + + expect(toClass(Model, { yesNo: 'false' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: '0' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: false }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: 0 }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: 'nothing' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); + + expect(toClass(Model, { yesNo: 'true' }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: '1' }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: true }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: 1 }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); + } +}); - enum Enum2 { - first = 'z', - second = 'x', - } +test('cloneClass', () => { + class SubModel { + @StringType() + name?: string; + } + + class DataStruct { + @StringType() + name?: string; + } + + class Model { + @AnyType() + data: any; + + @Class(DataStruct) + dataStruct?: DataStruct; + + @ClassArray(SubModel) + subs?: SubModel[]; + } + + const data = { + a: 'true', + }; + + const dataStruct = { + name: 'Foo', + }; + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + data: data, + dataStruct: dataStruct, + subs: [{ name: 'foo' }], + }); - enum Enum3 { - first = 200, - second = 100, - } + expect(instance.data).toEqual({ a: 'true' }); + expect(instance.data).not.toBe(data); + expect(instance.dataStruct!.name).toBe('Foo'); + expect(instance.dataStruct).toEqual(dataStruct); + expect(instance.dataStruct).not.toBe(dataStruct); + + const cloned = cloneClass(instance); + expect(cloned.data).toEqual({ a: 'true' }); + expect(cloned.data).not.toBe(data); + expect(cloned.dataStruct!.name).toBe('Foo'); + expect(cloned.dataStruct).toEqual(dataStruct); + expect(cloned.dataStruct).not.toBe(dataStruct); + + expect(cloned.subs![0]).not.toBe(instance.subs![0]); + } +}); - enum Enum4 { - first = '200', - second = 'x', +test('enums', () => { + enum Enum1 { + first, + second, + } + + enum Enum2 { + first = 'z', + second = 'x', + } + + enum Enum3 { + first = 200, + second = 100, + } + + enum Enum4 { + first = '200', + second = 'x', + } + + class Model { + @EnumType(Enum1) + enum1?: Enum1; + + @EnumType(Enum2) + enum2?: Enum2; + + @EnumType(Enum3) + enum3?: Enum3; + + @EnumType(Enum4) + enum4?: Enum4; + + @EnumType(Enum4, true) + enumLabels?: Enum4; + } + + expect(getEnumLabels(Enum1)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum2)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum3)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum4)).toEqual(['first', 'second']); + + expect(getEnumKeys(Enum1)).toEqual([0, 1]); + expect(getEnumKeys(Enum2)).toEqual(['z', 'x']); + expect(getEnumKeys(Enum3)).toEqual([200, 100]); + expect(getEnumKeys(Enum4)).toEqual(['200', 'x']); + + expect(isValidEnumValue(Enum1, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum1, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum1, 0)).toBeTrue(); + expect(isValidEnumValue(Enum1, 1)).toBeTrue(); + expect(isValidEnumValue(Enum1, '0')).toBeTrue(); + expect(isValidEnumValue(Enum1, '1')).toBeTrue(); + expect(isValidEnumValue(Enum1, 2)).toBeFalse(); + expect(isValidEnumValue(Enum1, '2')).toBeFalse(); + + expect(getValidEnumValue(Enum1, 1)).toBe(1); + expect(getValidEnumValue(Enum1, '1')).toBe(1); + expect(getValidEnumValue(Enum1, '2')).toBeUndefined(); + + expect(isValidEnumValue(Enum2, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum2, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum2, 'z')).toBeTrue(); + expect(isValidEnumValue(Enum2, 'x')).toBeTrue(); + + expect(getValidEnumValue(Enum2, 1)).toBeUndefined(); + expect(getValidEnumValue(Enum2, 'x')).toBe('x'); + expect(getValidEnumValue(Enum2, '2')).toBeUndefined(); + + expect(isValidEnumValue(Enum3, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum3, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum3, '200')).toBeTrue(); + expect(isValidEnumValue(Enum3, '100')).toBeTrue(); + expect(isValidEnumValue(Enum3, 200)).toBeTrue(); + expect(isValidEnumValue(Enum3, 100)).toBeTrue(); + + expect(isValidEnumValue(Enum4, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum4, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum4, '200')).toBeTrue(); + expect(isValidEnumValue(Enum4, 200)).toBeTrue(); + expect(isValidEnumValue(Enum4, 'x')).toBeTrue(); + + expect(isValidEnumValue(Enum4, 'first', true)).toBeTrue(); + expect(isValidEnumValue(Enum4, 'second', true)).toBeTrue(); + + expect(getValidEnumValue(Enum4, 1)).toBeUndefined(); + expect(getValidEnumValue(Enum4, 200)).toBe('200'); + expect(getValidEnumValue(Enum4, '200')).toBe('200'); + expect(getValidEnumValue(Enum4, '2')).toBeUndefined(); + expect(getValidEnumValue(Enum4, 'first', true)).toBe('200'); + expect(getValidEnumValue(Enum4, 'second', true)).toBe('x'); + + for (const toClass of [plainToClass, mongoToClass]) { + { + const instance = toClass(Model, { + enum1: 1, + enum2: 'x', + enum3: 100, + enum4: 'x', + enumLabels: 'x', + }); + + expect(instance.enum1).toBe(Enum1.second); + expect(instance.enum2).toBe(Enum2.second); + expect(instance.enum3).toBe(Enum3.second); + expect(instance.enum4).toBe(Enum4.second); + expect(instance.enumLabels).toBe(Enum4.second); } - class Model { - @EnumType(Enum1) - enum1?: Enum1; - - @EnumType(Enum2) - enum2?: Enum2; - - @EnumType(Enum3) - enum3?: Enum3; + { + const instance = toClass(Model, { + enum1: '1', + enum2: 'x', + enum3: '100', + enum4: 'x', + enumLabels: 'second', + }); + + expect(instance.enum1).toBe(Enum1.second); + expect(instance.enum2).toBe(Enum2.second); + expect(instance.enum3).toBe(Enum3.second); + expect(instance.enum4).toBe(Enum4.second); + expect(instance.enumLabels).toBe(Enum4.second); + } - @EnumType(Enum4) - enum4?: Enum4; + expect(() => { + const instance = toClass(Model, { + enum1: 2, + }); + }).toThrow('Invalid ENUM given in property'); - @EnumType(Enum4, true) - enumLabels?: Enum4; - } + expect(() => { + const instance = plainToClass(Model, { + enum2: 2, + }); + }).toThrow('Invalid ENUM given in property'); - expect(getEnumLabels(Enum1)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum2)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum3)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum4)).toEqual(['first', 'second']); - - expect(getEnumKeys(Enum1)).toEqual([0, 1]); - expect(getEnumKeys(Enum2)).toEqual(['z', 'x']); - expect(getEnumKeys(Enum3)).toEqual([200, 100]); - expect(getEnumKeys(Enum4)).toEqual(['200', 'x']); - - expect(isValidEnumValue(Enum1, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum1, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum1, 0)).toBeTrue(); - expect(isValidEnumValue(Enum1, 1)).toBeTrue(); - expect(isValidEnumValue(Enum1, '0')).toBeTrue(); - expect(isValidEnumValue(Enum1, '1')).toBeTrue(); - expect(isValidEnumValue(Enum1, 2)).toBeFalse(); - expect(isValidEnumValue(Enum1, '2')).toBeFalse(); - - expect(getValidEnumValue(Enum1, 1)).toBe(1); - expect(getValidEnumValue(Enum1, '1')).toBe(1); - expect(getValidEnumValue(Enum1, '2')).toBeUndefined(); - - expect(isValidEnumValue(Enum2, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum2, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum2, 'z')).toBeTrue(); - expect(isValidEnumValue(Enum2, 'x')).toBeTrue(); - - expect(getValidEnumValue(Enum2, 1)).toBeUndefined(); - expect(getValidEnumValue(Enum2, 'x')).toBe('x'); - expect(getValidEnumValue(Enum2, '2')).toBeUndefined(); - - expect(isValidEnumValue(Enum3, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum3, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum3, '200')).toBeTrue(); - expect(isValidEnumValue(Enum3, '100')).toBeTrue(); - expect(isValidEnumValue(Enum3, 200)).toBeTrue(); - expect(isValidEnumValue(Enum3, 100)).toBeTrue(); - - expect(isValidEnumValue(Enum4, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum4, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum4, '200')).toBeTrue(); - expect(isValidEnumValue(Enum4, 200)).toBeTrue(); - expect(isValidEnumValue(Enum4, 'x')).toBeTrue(); - - expect(isValidEnumValue(Enum4, 'first', true)).toBeTrue(); - expect(isValidEnumValue(Enum4, 'second', true)).toBeTrue(); - - expect(getValidEnumValue(Enum4, 1)).toBeUndefined(); - expect(getValidEnumValue(Enum4, 200)).toBe('200'); - expect(getValidEnumValue(Enum4, '200')).toBe('200'); - expect(getValidEnumValue(Enum4, '2')).toBeUndefined(); - expect(getValidEnumValue(Enum4, 'first', true)).toBe('200'); - expect(getValidEnumValue(Enum4, 'second', true)).toBe('x'); - - for (const toClass of [plainToClass, mongoToClass]) { - { - const instance = toClass(Model, { - enum1: 1, - enum2: 'x', - enum3: 100, - enum4: 'x', - enumLabels: 'x' - }); - - expect(instance.enum1).toBe(Enum1.second); - expect(instance.enum2).toBe(Enum2.second); - expect(instance.enum3).toBe(Enum3.second); - expect(instance.enum4).toBe(Enum4.second); - expect(instance.enumLabels).toBe(Enum4.second); - } + expect(() => { + const instance = toClass(Model, { + enum3: 2, + }); + }).toThrow('Invalid ENUM given in property'); - { - const instance = toClass(Model, { - enum1: '1', - enum2: 'x', - enum3: '100', - enum4: 'x', - enumLabels: 'second', - }); - - expect(instance.enum1).toBe(Enum1.second); - expect(instance.enum2).toBe(Enum2.second); - expect(instance.enum3).toBe(Enum3.second); - expect(instance.enum4).toBe(Enum4.second); - expect(instance.enumLabels).toBe(Enum4.second); - } - - expect(() => { - const instance = toClass(Model, { - enum1: 2 - }); - }).toThrow('Invalid ENUM given in property'); - - expect(() => { - const instance = plainToClass(Model, { - enum2: 2 - }); - }).toThrow('Invalid ENUM given in property'); - - expect(() => { - const instance = toClass(Model, { - enum3: 2 - }); - }).toThrow('Invalid ENUM given in property'); - - expect(() => { - const instance = toClass(Model, { - enum3: 4 - }); - }).toThrow('Invalid ENUM given in property'); - } -}); \ No newline at end of file + expect(() => { + const instance = toClass(Model, { + enum3: 4, + }); + }).toThrow('Invalid ENUM given in property'); + } +}); diff --git a/packages/mongo/tests/to-mongo.spec.ts b/packages/mongo/tests/to-mongo.spec.ts index 5b3191075..7d1f578cb 100644 --- a/packages/mongo/tests/to-mongo.spec.ts +++ b/packages/mongo/tests/to-mongo.spec.ts @@ -1,286 +1,295 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; import { - BinaryType, - classToPlain, - EnumType, - getReflectionType, getResolvedReflection, - MongoIdType, - plainToClass, - UUIDType -} from "@marcj/marshal"; -import {Plan, SimpleModel, SubModel} from "@marcj/marshal/tests/entities"; -import {Binary, ObjectID} from "mongodb"; -import {classToMongo, mongoToClass, partialClassToMongo, partialPlainToMongo} from "../src/mapping"; -import {Buffer} from "buffer"; -import {DocumentClass} from "@marcj/marshal/tests/document-scenario/DocumentClass"; -import {PageCollection} from "@marcj/marshal/tests/document-scenario/PageCollection"; -import {PageClass} from "@marcj/marshal/tests/document-scenario/PageClass"; + BinaryType, + classToPlain, + EnumType, + getReflectionType, + getResolvedReflection, + MongoIdType, + plainToClass, + UUIDType, +} from '@marcj/marshal'; +import { Plan, SimpleModel, SubModel } from '@marcj/marshal/tests/entities'; +import { Binary, ObjectID } from 'mongodb'; +import { + classToMongo, + mongoToClass, + partialClassToMongo, + partialPlainToMongo, +} from '../src/mapping'; +import { Buffer } from 'buffer'; +import { DocumentClass } from '@marcj/marshal/tests/document-scenario/DocumentClass'; +import { PageCollection } from '@marcj/marshal/tests/document-scenario/PageCollection'; +import { PageClass } from '@marcj/marshal/tests/document-scenario/PageClass'; test('test simple model', () => { - const instance = new SimpleModel('myName'); - const mongo = classToMongo(SimpleModel, instance); - - expect(mongo['id']).toBeInstanceOf(Binary); - expect(mongo['name']).toBe('myName'); + const instance = new SimpleModel('myName'); + const mongo = classToMongo(SimpleModel, instance); + expect(mongo['id']).toBeInstanceOf(Binary); + expect(mongo['name']).toBe('myName'); }); test('test simple model all fields', () => { - const instance = new SimpleModel('myName'); - instance.plan = Plan.PRO; - instance.type = 5; - instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); - instance.children.push(new SubModel('fooo')); - instance.children.push(new SubModel('barr')); - - instance.childrenMap.foo = new SubModel('bar'); - instance.childrenMap.foo2 = new SubModel('bar2'); - - const mongo = classToMongo(SimpleModel, instance); - - expect(mongo['id']).toBeInstanceOf(Binary); - expect(mongo['name']).toBe('myName'); - expect(mongo['type']).toBe(5); - expect(mongo['plan']).toBe(Plan.PRO); - expect(mongo['created']).toBeDate(); - expect(mongo['children']).toBeArrayOfSize(2); - expect(mongo['children'][0]).toBeObject(); - expect(mongo['children'][0].label).toBe('fooo'); - expect(mongo['children'][1].label).toBe('barr'); - - expect(mongo['childrenMap']).toBeObject(); - expect(mongo['childrenMap'].foo).toBeObject(); - expect(mongo['childrenMap'].foo.label).toBe('bar'); - expect(mongo['childrenMap'].foo2.label).toBe('bar2'); + const instance = new SimpleModel('myName'); + instance.plan = Plan.PRO; + instance.type = 5; + instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); + instance.children.push(new SubModel('fooo')); + instance.children.push(new SubModel('barr')); + + instance.childrenMap.foo = new SubModel('bar'); + instance.childrenMap.foo2 = new SubModel('bar2'); + + const mongo = classToMongo(SimpleModel, instance); + + expect(mongo['id']).toBeInstanceOf(Binary); + expect(mongo['name']).toBe('myName'); + expect(mongo['type']).toBe(5); + expect(mongo['plan']).toBe(Plan.PRO); + expect(mongo['created']).toBeDate(); + expect(mongo['children']).toBeArrayOfSize(2); + expect(mongo['children'][0]).toBeObject(); + expect(mongo['children'][0].label).toBe('fooo'); + expect(mongo['children'][1].label).toBe('barr'); + + expect(mongo['childrenMap']).toBeObject(); + expect(mongo['childrenMap'].foo).toBeObject(); + expect(mongo['childrenMap'].foo.label).toBe('bar'); + expect(mongo['childrenMap'].foo2.label).toBe('bar2'); }); test('convert IDs and invalid values', () => { - enum Enum { - first, - second, - } + enum Enum { + first, + second, + } - class Model { - @MongoIdType() - id2?: string; + class Model { + @MongoIdType() + id2?: string; - @UUIDType() - uuid?: string; + @UUIDType() + uuid?: string; - @EnumType(Enum) - enum?: Enum; - } + @EnumType(Enum) + enum?: Enum; + } - const instance = new Model(); - instance.id2 = '5be340cb2ffb5e901a9b62e4'; + const instance = new Model(); + instance.id2 = '5be340cb2ffb5e901a9b62e4'; - const mongo = classToMongo(Model, instance); - expect(mongo.id2).toBeInstanceOf(ObjectID); - expect(mongo.id2.toHexString()).toBe('5be340cb2ffb5e901a9b62e4'); + const mongo = classToMongo(Model, instance); + expect(mongo.id2).toBeInstanceOf(ObjectID); + expect(mongo.id2.toHexString()).toBe('5be340cb2ffb5e901a9b62e4'); - expect(() => { - const instance = new Model(); - instance.id2 = 'notavalidId'; - const mongo = classToMongo(Model, instance); - }).toThrow('Invalid ObjectID given in property'); + expect(() => { + const instance = new Model(); + instance.id2 = 'notavalidId'; + const mongo = classToMongo(Model, instance); + }).toThrow('Invalid ObjectID given in property'); - expect(() => { - const instance = new Model(); - instance.uuid = 'notavalidId'; - const mongo = classToMongo(Model, instance); - }).toThrow('Invalid UUID given in property'); + expect(() => { + const instance = new Model(); + instance.uuid = 'notavalidId'; + const mongo = classToMongo(Model, instance); + }).toThrow('Invalid UUID given in property'); }); - test('binary', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } - const i = new Model(); - expect(i.preview.toString('utf8')).toBe('FooBar'); + const i = new Model(); + expect(i.preview.toString('utf8')).toBe('FooBar'); - const mongo = classToMongo(Model, i); - expect(mongo.preview).toBeInstanceOf(Binary); - expect((mongo.preview as Binary).length()).toBe(6); + const mongo = classToMongo(Model, i); + expect(mongo.preview).toBeInstanceOf(Binary); + expect((mongo.preview as Binary).length()).toBe(6); }); - test('binary from mongo', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } - const i = mongoToClass(Model, { - preview: new Binary(new Buffer('FooBar', 'utf8')) - }); + const i = mongoToClass(Model, { + preview: new Binary(new Buffer('FooBar', 'utf8')), + }); - expect(i.preview.length).toBe(6); - expect(i.preview.toString('utf8')).toBe('FooBar'); + expect(i.preview.length).toBe(6); + expect(i.preview.toString('utf8')).toBe('FooBar'); }); - test('partial 2', () => { - const instance = partialClassToMongo(SimpleModel, { - name: 'Hi', - 'children.0.label': 'Foo' + const instance = partialClassToMongo(SimpleModel, { + name: 'Hi', + 'children.0.label': 'Foo', + }); + + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance['children.0.label']).toBe('Foo'); + + { + expect( + partialClassToMongo(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i = partialClassToMongo(SimpleModel, { + 'children.0': new SubModel('3'), }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance['children.0.label']).toBe('Foo'); - - { - expect(partialClassToMongo(SimpleModel, { - 'children.0.label': 2 - })).toEqual({'children.0.label': '2'}); - - const i = partialClassToMongo(SimpleModel, { - 'children.0': new SubModel('3') - }); - expect(i['children.0'].label).toBe('3'); - } - - { - expect(partialPlainToMongo(SimpleModel, { - 'children.0.label': 2 - })).toEqual({'children.0.label': '2'}); - - const i = partialPlainToMongo(SimpleModel, { - 'children.0': {label: 3} - }); - expect(i['children.0'].label).toBe('3'); - } + expect(i['children.0'].label).toBe('3'); + } + + { + expect( + partialPlainToMongo(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i = partialPlainToMongo(SimpleModel, { + 'children.0': { label: 3 }, + }); + expect(i['children.0'].label).toBe('3'); + } }); - test('partial 3', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'children': [new SubModel('3')] - }); - expect(i['children'][0].label).toBe('3'); - } - - { - const i = partialPlainToMongo(SimpleModel, { - 'children': [{label: 3}] - }); - expect(i['children'][0].label).toBe('3'); - } + { + const i = partialClassToMongo(SimpleModel, { + children: [new SubModel('3')], + }); + expect(i['children'][0].label).toBe('3'); + } + + { + const i = partialPlainToMongo(SimpleModel, { + children: [{ label: 3 }], + }); + expect(i['children'][0].label).toBe('3'); + } }); test('partial with required', () => { - { - expect(() => {partialPlainToMongo(SimpleModel, { - 'children': [{}] - })}).toThrow('Missing value for SubModel::children. Can not convert to mongo'); - } + { + expect(() => { + partialPlainToMongo(SimpleModel, { + children: [{}], + }); + }).toThrow( + 'Missing value for SubModel::children. Can not convert to mongo' + ); + } }); - test('partial 4', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'stringChildrenCollection.0': 4 - }); - expect(i['stringChildrenCollection.0']).toBe('4'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'stringChildrenCollection.0': 4 - }); - expect(i['stringChildrenCollection.0']).toBe('4'); - } + { + const i = partialClassToMongo(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i['stringChildrenCollection.0']).toBe('4'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i['stringChildrenCollection.0']).toBe('4'); + } }); test('partial 5', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'childrenMap.foo.label': 5 - }); - expect(i['childrenMap.foo.label']).toBe('5'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'childrenMap.foo.label': 5 - }); - expect(i['childrenMap.foo.label']).toBe('5'); - } + { + const i = partialClassToMongo(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i['childrenMap.foo.label']).toBe('5'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i['childrenMap.foo.label']).toBe('5'); + } }); - test('partial 6', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'types': [6, 7] - }); - expect(i['types']).toEqual(['6', '7']); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'types': [6, 7] - }); - expect(i['types']).toEqual(['6', '7']); - } + { + const i = partialClassToMongo(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); + } + { + const i = partialPlainToMongo(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); + } }); test('partial 7', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'types.0': [7] - }); - expect(i['types.0']).toEqual('7'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'types.0': [7] - }); - expect(i['types.0']).toEqual('7'); - } -}); - -test('partial document', () => { - const doc = new DocumentClass; - const document = partialClassToMongo(DocumentClass, { - 'pages.0.name': 5, - 'pages.0.children.0.name': 6, - 'pages.0.children': new PageCollection([new PageClass(doc, '7')]) + { + const i = partialClassToMongo(SimpleModel, { + 'types.0': [7], }); - expect(document['pages.0.name']).toBe('5'); - expect(document['pages.0.children.0.name']).toBe('6'); - expect(document['pages.0.children']).toBeInstanceOf(Array); - expect(document['pages.0.children'][0].name).toBe('7'); - - expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); - - expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'children', - type: 'class', - typeValue: PageCollection, - array: false, - map: false, + expect(i['types.0']).toEqual('7'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'types.0': [7], }); + expect(i['types.0']).toEqual('7'); + } +}); - expect(getResolvedReflection(DocumentClass, 'pages.0.children.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); -}); \ No newline at end of file +test('partial document', () => { + const doc = new DocumentClass(); + const document = partialClassToMongo(DocumentClass, { + 'pages.0.name': 5, + 'pages.0.children.0.name': 6, + 'pages.0.children': new PageCollection([new PageClass(doc, '7')]), + }); + expect(document['pages.0.name']).toBe('5'); + expect(document['pages.0.children.0.name']).toBe('6'); + expect(document['pages.0.children']).toBeInstanceOf(Array); + expect(document['pages.0.children'][0].name).toBe('7'); + + expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); + + expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'children', + type: 'class', + typeValue: PageCollection, + array: false, + map: false, + }); + + expect( + getResolvedReflection(DocumentClass, 'pages.0.children.0.name') + ).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); +}); diff --git a/packages/mongo/tests/to-plain.spec.ts b/packages/mongo/tests/to-plain.spec.ts index dd056a7f7..8e5814338 100644 --- a/packages/mongo/tests/to-plain.spec.ts +++ b/packages/mongo/tests/to-plain.spec.ts @@ -1,49 +1,48 @@ -import 'jest-extended' +import 'jest-extended'; import 'reflect-metadata'; -import {ObjectID} from "mongodb"; -import {DateType, ID, MongoIdType, UUIDType} from "@marcj/marshal"; -import {mongoToPlain, partialMongoToPlain, uuid4Binary} from "../src/mapping"; +import { ObjectID } from 'mongodb'; +import { DateType, ID, MongoIdType, UUIDType } from '@marcj/marshal'; +import { mongoToPlain, partialMongoToPlain, uuid4Binary } from '../src/mapping'; test('mongo to plain', () => { - class Model { - @ID() - @MongoIdType() - _id?: string; - - @DateType() - date?: Date; - } - - const plain = mongoToPlain(Model, { - _id: new ObjectID("5be340cb2ffb5e901a9b62e4"), - date: new Date('2018-11-07 19:45:15.805Z'), - }); - - expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); - expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); + class Model { + @ID() + @MongoIdType() + _id?: string; + + @DateType() + date?: Date; + } + + const plain = mongoToPlain(Model, { + _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), + date: new Date('2018-11-07 19:45:15.805Z'), + }); + + expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); + expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); }); test('mongo to plain partial', () => { - class Model { - @ID() - @MongoIdType() - _id?: string; - - @UUIDType() - uuid?: string; - - @DateType() - date?: Date; - } - - const plain = partialMongoToPlain(Model, { - uuid: uuid4Binary("12345678-1234-5678-1234-567812345678"), - _id: new ObjectID("5be340cb2ffb5e901a9b62e4"), - date: new Date('2018-11-07 19:45:15.805Z'), - }); - - expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); - expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); - expect(plain.uuid).toBe('12345678-1234-5678-1234-567812345678'); + class Model { + @ID() + @MongoIdType() + _id?: string; + + @UUIDType() + uuid?: string; + + @DateType() + date?: Date; + } + + const plain = partialMongoToPlain(Model, { + uuid: uuid4Binary('12345678-1234-5678-1234-567812345678'), + _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), + date: new Date('2018-11-07 19:45:15.805Z'), + }); + + expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); + expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); + expect(plain.uuid).toBe('12345678-1234-5678-1234-567812345678'); }); - diff --git a/packages/nest/src/nest.ts b/packages/nest/src/nest.ts index ec54862cc..dd412bd59 100644 --- a/packages/nest/src/nest.ts +++ b/packages/nest/src/nest.ts @@ -1,26 +1,33 @@ -import {PipeTransform, ArgumentMetadata, BadRequestException} from '@nestjs/common'; -import {validate, plainToClass, applyDefaultValues} from "@marcj/marshal"; +import { + PipeTransform, + ArgumentMetadata, + BadRequestException, +} from '@nestjs/common'; +import { validate, plainToClass, applyDefaultValues } from '@marcj/marshal'; export class ValidationPipe implements PipeTransform { - constructor(private options?: { transform?: boolean, disableErrorMessages?: boolean }) { - } - - async transform(value: any, metadata: ArgumentMetadata): Promise { - if (!metadata.metatype) { - return; - } + constructor( + private options?: { transform?: boolean; disableErrorMessages?: boolean } + ) {} - const valueWithDefaults = applyDefaultValues(metadata.metatype, value); - const errors = await validate(metadata.metatype, valueWithDefaults); + async transform(value: any, metadata: ArgumentMetadata): Promise { + if (!metadata.metatype) { + return; + } - if (errors.length > 0) { - throw new BadRequestException(this.options && this.options.disableErrorMessages ? undefined : errors); - } + const valueWithDefaults = applyDefaultValues(metadata.metatype, value); + const errors = await validate(metadata.metatype, valueWithDefaults); - if (this.options && this.options.transform) { - return plainToClass(metadata.metatype, value); - } + if (errors.length > 0) { + throw new BadRequestException( + this.options && this.options.disableErrorMessages ? undefined : errors + ); + } - return valueWithDefaults; + if (this.options && this.options.transform) { + return plainToClass(metadata.metatype, value); } + + return valueWithDefaults; + } } diff --git a/packages/nest/tests/validation.spec.ts b/packages/nest/tests/validation.spec.ts index 8726b3262..d987b4791 100644 --- a/packages/nest/tests/validation.spec.ts +++ b/packages/nest/tests/validation.spec.ts @@ -1,86 +1,116 @@ import 'reflect-metadata'; -import 'jest-extended' -import {Optional, validate, StringType, ArrayType, MapType} from "@marcj/marshal"; -import {ValidationPipe} from '../'; -import {BadRequestException} from '@nestjs/common'; +import 'jest-extended'; +import { + Optional, + validate, + StringType, + ArrayType, + MapType, +} from '@marcj/marshal'; +import { ValidationPipe } from '../'; +import { BadRequestException } from '@nestjs/common'; test('test required', async () => { - - class Model { - @StringType() - id: string = '1'; - - @StringType() - name?: string; - - @Optional() - optional?: string; - - @Optional() - @MapType() - map?: {[name: string]: string}; - - @Optional() - @ArrayType() - array?: string[]; + class Model { + @StringType() + id: string = '1'; + + @StringType() + name?: string; + + @Optional() + optional?: string; + + @Optional() + @MapType() + map?: { [name: string]: string }; + + @Optional() + @ArrayType() + array?: string[]; + } + + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); + + expect(await validate(Model, { name: 'foo', map: true })).toEqual([ + { message: 'Invalid type. Expected object, but got boolean', path: 'map' }, + ]); + expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ + { message: 'Invalid type. Expected array, but got number', path: 'array' }, + ]); + + { + const pipe = new ValidationPipe(); + const result = await pipe.transform({ name: 'Foo' }, { type: 'body' }); + expect(result).toBeUndefined(); + } + + { + const pipe = new ValidationPipe(); + const result = await pipe.transform( + { name: 'Foo' }, + { type: 'body', metatype: Model } + ); + expect(result).not.toBeInstanceOf(Model); + expect(result.id).toBe('1'); //because ValidationPipe is reading default values + } + + { + const pipe = new ValidationPipe(); + const result = await pipe.transform( + { name: 'Foo', optional: 'two' }, + { type: 'body', metatype: Model } + ); + expect(result).not.toBeInstanceOf(Model); + expect(result.id).toBe('1'); //because ValidationPipe is reading default values + } + + { + const pipe = new ValidationPipe({ transform: true }); + const result = await pipe.transform( + { name: 'Foo', optional: 'two' }, + { type: 'body', metatype: Model } + ); + expect(result).toBeInstanceOf(Model); + } + + { + const pipe = new ValidationPipe({ transform: true }); + try { + const result = await pipe.transform( + { optional: 'two' }, + { type: 'body', metatype: Model } + ); + fail('no exception thrown'); + } catch (error) { + expect(error).toBeInstanceOf(BadRequestException); + expect(error.message).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); } - - const instance = new Model; - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([{message: "Required value is undefined", path: 'name'}]); - - expect(await validate(Model, {name: 'foo', map: true})).toEqual([{message: "Invalid type. Expected object, but got boolean", path: 'map'}]); - expect(await validate(Model, {name: 'foo', array: 233})).toEqual([{message: "Invalid type. Expected array, but got number", path: 'array'}]); - - { - const pipe = new ValidationPipe(); - const result = await pipe.transform({name: 'Foo'}, {type: 'body'}); - expect(result).toBeUndefined(); - } - - { - const pipe = new ValidationPipe(); - const result = await pipe.transform({name: 'Foo'}, {type: 'body', metatype: Model}); - expect(result).not.toBeInstanceOf(Model); - expect(result.id).toBe('1'); //because ValidationPipe is reading default values + } + + { + const pipe = new ValidationPipe({ + transform: true, + disableErrorMessages: true, + }); + try { + const result = await pipe.transform( + { optional: 'two' }, + { type: 'body', metatype: Model } + ); + fail('no exception thrown'); + } catch (error) { + expect(error).toBeInstanceOf(BadRequestException); + expect(error.message).toEqual({ error: 'Bad Request', statusCode: 400 }); } + } - { - const pipe = new ValidationPipe(); - const result = await pipe.transform({name: 'Foo', optional: 'two'}, {type: 'body', metatype: Model}); - expect(result).not.toBeInstanceOf(Model); - expect(result.id).toBe('1'); //because ValidationPipe is reading default values - } - - { - const pipe = new ValidationPipe({transform: true}); - const result = await pipe.transform({name: 'Foo', optional: 'two'}, {type: 'body', metatype: Model}); - expect(result).toBeInstanceOf(Model); - } - - { - const pipe = new ValidationPipe({transform: true}); - try { - const result = await pipe.transform({optional: 'two'}, {type: 'body', metatype: Model}); - fail('no exception thrown') - } catch (error) { - expect(error).toBeInstanceOf(BadRequestException); - expect(error.message).toEqual([{"message": "Required value is undefined", "path": "name"}]); - } - } - - { - const pipe = new ValidationPipe({transform: true, disableErrorMessages: true}); - try { - const result = await pipe.transform({optional: 'two'}, {type: 'body', metatype: Model}); - fail('no exception thrown') - } catch (error) { - expect(error).toBeInstanceOf(BadRequestException); - expect(error.message).toEqual({"error": "Bad Request", "statusCode": 400}); - } - } - - instance.name = 'Pete'; - expect(await validate(Model, instance)).toEqual([]); + instance.name = 'Pete'; + expect(await validate(Model, instance)).toEqual([]); }); - diff --git a/tslint.json b/tslint.json index 128bb7293..0ef2d408c 100644 --- a/tslint.json +++ b/tslint.json @@ -2,7 +2,12 @@ // "rulesDirectory": [ // "node_modules/codelyzer" // ], + "extends": [ + "tslint-config-prettier", + "tslint-plugin-prettier" + ], "rules": { + "prettier": true, "arrow-return-shorthand": true, "callable-types": true, "class-name": true, From d403ca101be38c917f187f7f1f224c02396641de Mon Sep 17 00:00:00 2001 From: sebastianhoitz Date: Thu, 7 Feb 2019 15:45:57 +0100 Subject: [PATCH 2/5] Transform to 4 spaces --- .prettierrc | 2 +- packages/core/src/decorators.ts | 337 ++--- packages/core/src/mapper.ts | 1415 ++++++++++----------- packages/core/src/utils.ts | 144 +-- packages/core/src/validation.ts | 331 ++--- packages/core/tests/big-entity.ts | 744 +++++------ packages/core/tests/class-to.spec.ts | 74 +- packages/core/tests/decorator.spec.ts | 530 ++++---- packages/core/tests/entities.ts | 196 +-- packages/core/tests/plain-to.spec.ts | 687 +++++----- packages/core/tests/speed.spec.ts | 270 ++-- packages/core/tests/to-plain.spec.ts | 64 +- packages/core/tests/utils.spec.ts | 74 +- packages/core/tests/validation.spec.ts | 457 +++---- packages/mongo/src/database.ts | 575 ++++----- packages/mongo/src/mapping.ts | 1044 +++++++-------- packages/mongo/tests/class-to.spec.ts | 12 +- packages/mongo/tests/mongo-query.spec.ts | 132 +- packages/mongo/tests/mongo.spec.ts | 644 +++++----- packages/mongo/tests/to-class.spec.ts | 1472 +++++++++++----------- packages/mongo/tests/to-mongo.spec.ts | 460 +++---- packages/mongo/tests/to-plain.spec.ts | 74 +- packages/nest/src/nest.ts | 49 +- packages/nest/tests/validation.spec.ts | 193 +-- 24 files changed, 5028 insertions(+), 4952 deletions(-) diff --git a/.prettierrc b/.prettierrc index 1e43a30a1..875e829ef 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "trailingComma": "es5", - "tabWidth": 2, + "tabWidth": 4, "semi": true, "singleQuote": true, "arrowParens": "always" diff --git a/packages/core/src/decorators.ts b/packages/core/src/decorators.ts index cebcd64a2..f2b68ba7a 100644 --- a/packages/core/src/decorators.ts +++ b/packages/core/src/decorators.ts @@ -1,271 +1,276 @@ import { Types } from './mapper'; import { ClassType, getClassName } from './utils'; import { - AddValidator, - PropertyValidator, - PropertyValidatorError, + AddValidator, + PropertyValidator, + PropertyValidatorError, } from './validation'; export function Entity(name: string, collectionName?: string) { - return (target: Object) => { - Reflect.defineMetadata('marshal:entityName', name, target); - Reflect.defineMetadata( - 'marshal:collectionName', - collectionName || name + 's', - target - ); - }; + return (target: Object) => { + Reflect.defineMetadata('marshal:entityName', name, target); + Reflect.defineMetadata( + 'marshal:collectionName', + collectionName || name + 's', + target + ); + }; } export function DatabaseName(name: string) { - return (target: Object) => { - Reflect.defineMetadata('marshal:databaseName', name, target); - }; + return (target: Object) => { + Reflect.defineMetadata('marshal:databaseName', name, target); + }; } export function Decorator() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:dataDecorator', property, target); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:dataDecorator', property, target); + }; } export function ID() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:idField', property, target); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:idField', property, target); + }; } export function ParentReference() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:parentReference', true, target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata( + 'marshal:parentReference', + true, + target, + property + ); + }; } function addMetadataArray(metadataKey: string, target: Object, item: any) { - const array = Reflect.getMetadata(metadataKey, target) || []; - if (-1 === array.indexOf(item)) { - array.push(item); - } + const array = Reflect.getMetadata(metadataKey, target) || []; + if (-1 === array.indexOf(item)) { + array.push(item); + } - Reflect.defineMetadata(metadataKey, array, target); + Reflect.defineMetadata(metadataKey, array, target); } /** * Executes the method when the current class is instantiated and populated. */ export function OnLoad(options: { fullLoad?: boolean } = {}) { - return (target: Object, property: string) => { - addMetadataArray('marshal:onLoad', target, { - property: property, - options: options, - }); - }; + return (target: Object, property: string) => { + addMetadataArray('marshal:onLoad', target, { + property: property, + options: options, + }); + }; } /** * Exclude in *toMongo and *toPlain. */ export function Exclude() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'all', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'all', target, property); + }; } export function ExcludeToMongo() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'mongo', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'mongo', target, property); + }; } export function ExcludeToPlain() { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:exclude', 'plain', target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:exclude', 'plain', target, property); + }; } export function registerProperty(target: Object, property: string) { - addMetadataArray('marshal:properties', target, property); + addMetadataArray('marshal:properties', target, property); } export function Type(type: Types) { - return (target: Object, property: string) => { - Reflect.defineMetadata('marshal:dataType', type, target, property); - registerProperty(target, property); - }; + return (target: Object, property: string) => { + Reflect.defineMetadata('marshal:dataType', type, target, property); + registerProperty(target, property); + }; } export function ArrayType() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:isArray', true, target, property); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:isArray', true, target, property); + }; } export function MapType() { - return (target: Object, property: string) => { - registerProperty(target, property); - Reflect.defineMetadata('marshal:isMap', true, target, property); - }; + return (target: Object, property: string) => { + registerProperty(target, property); + Reflect.defineMetadata('marshal:isMap', true, target, property); + }; } export function ClassCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - Type('class')(target, property); - Reflect.defineMetadata( - 'marshal:dataTypeValue', - classType, - target, - property - ); - Reflect.defineMetadata( - 'marshal:dataTypeValueCircular', - true, - target, - property - ); - }; + return (target: Object, property: string) => { + Type('class')(target, property); + Reflect.defineMetadata( + 'marshal:dataTypeValue', + classType, + target, + property + ); + Reflect.defineMetadata( + 'marshal:dataTypeValueCircular', + true, + target, + property + ); + }; } export function Class(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error( - `${getClassName( - target - )}::${property} has @Class but argument is empty. Use @ClassCircular(() => YourClass) to work around circular dependencies.` - ); - } - - Type('class')(target, property); - Reflect.defineMetadata( - 'marshal:dataTypeValue', - classType, - target, - property - ); - }; + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @Class but argument is empty. Use @ClassCircular(() => YourClass) to work around circular dependencies.` + ); + } + + Type('class')(target, property); + Reflect.defineMetadata( + 'marshal:dataTypeValue', + classType, + target, + property + ); + }; } export function ClassMap(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error( - `${getClassName( - target - )}::${property} has @ClassMap but argument is empty. Use @ClassMap(() => YourClass) to work around circular dependencies.` - ); - } - - Class(classType)(target, property); - MapType()(target, property); - }; + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @ClassMap but argument is empty. Use @ClassMap(() => YourClass) to work around circular dependencies.` + ); + } + + Class(classType)(target, property); + MapType()(target, property); + }; } export function ClassMapCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - ClassCircular(classType)(target, property); - MapType()(target, property); - }; + return (target: Object, property: string) => { + ClassCircular(classType)(target, property); + MapType()(target, property); + }; } export function ClassArray(classType: ClassType) { - return (target: Object, property: string) => { - if (!classType) { - throw new Error( - `${getClassName( - target - )}::${property} has @ClassArray but argument is empty. Use @ClassArrayCircular(() => YourClass) to work around circular dependencies.` - ); - } - - Class(classType)(target, property); - ArrayType()(target, property); - }; + return (target: Object, property: string) => { + if (!classType) { + throw new Error( + `${getClassName( + target + )}::${property} has @ClassArray but argument is empty. Use @ClassArrayCircular(() => YourClass) to work around circular dependencies.` + ); + } + + Class(classType)(target, property); + ArrayType()(target, property); + }; } export function ClassArrayCircular(classType: () => ClassType) { - return (target: Object, property: string) => { - ClassCircular(classType)(target, property); - ArrayType()(target, property); - }; + return (target: Object, property: string) => { + ClassCircular(classType)(target, property); + ArrayType()(target, property); + }; } function concat(...decorators: ((target: Object, property: string) => void)[]) { - return (target: Object, property: string) => { - for (const decorator of decorators) { - decorator(target, property); - } - }; + return (target: Object, property: string) => { + for (const decorator of decorators) { + decorator(target, property); + } + }; } export function MongoIdType() { - return Type('objectId'); + return Type('objectId'); } export function UUIDType() { - return Type('uuid'); + return Type('uuid'); } export function DateType() { - return Type('date'); + return Type('date'); } export function BinaryType() { - return Type('binary'); + return Type('binary'); } export function StringType() { - class Validator implements PropertyValidator { - async validate( - value: any, - target: ClassType, - property: string - ): Promise { - if ('string' !== typeof value) { - return new PropertyValidatorError('No String given'); - } + class Validator implements PropertyValidator { + async validate( + value: any, + target: ClassType, + property: string + ): Promise { + if ('string' !== typeof value) { + return new PropertyValidatorError('No String given'); + } + } } - } - return concat(AddValidator(Validator), Type('string')); + return concat(AddValidator(Validator), Type('string')); } export function AnyType() { - return Type('any'); + return Type('any'); } export function NumberType() { - class Validator implements PropertyValidator { - async validate( - value: any, - target: ClassType, - property: string - ): Promise { - value = parseFloat(value); - - if (!Number.isFinite(value)) { - return new PropertyValidatorError('No Number given'); - } + class Validator implements PropertyValidator { + async validate( + value: any, + target: ClassType, + property: string + ): Promise { + value = parseFloat(value); + + if (!Number.isFinite(value)) { + return new PropertyValidatorError('No Number given'); + } + } } - } - return concat(AddValidator(Validator), Type('number')); + return concat(AddValidator(Validator), Type('number')); } export function BooleanType() { - return Type('boolean'); + return Type('boolean'); } export function EnumType(type: any, allowLabelsAsValue = false) { - return (target: Object, property: string) => { - Type('enum')(target, property); - Reflect.defineMetadata('marshal:dataTypeValue', type, target, property); - Reflect.defineMetadata( - 'marshal:enum:allowLabelsAsValue', - allowLabelsAsValue, - target, - property - ); - }; + return (target: Object, property: string) => { + Type('enum')(target, property); + Reflect.defineMetadata('marshal:dataTypeValue', type, target, property); + Reflect.defineMetadata( + 'marshal:enum:allowLabelsAsValue', + allowLabelsAsValue, + target, + property + ); + }; } diff --git a/packages/core/src/mapper.ts b/packages/core/src/mapper.ts index f534d3f9c..1c90391ef 100644 --- a/packages/core/src/mapper.ts +++ b/packages/core/src/mapper.ts @@ -1,14 +1,14 @@ import { - ClassType, - isArray, - isObject, - isUndefined, - getEnumKeys, - isValidEnumValue, - getValidEnumValue, - getClassPropertyName, - getClassName, - getEnumLabels, + ClassType, + isArray, + isObject, + isUndefined, + getEnumKeys, + isValidEnumValue, + getValidEnumValue, + getClassPropertyName, + getClassName, + getEnumLabels, } from './utils'; import { isOptional } from './validation'; import * as clone from 'clone'; @@ -16,735 +16,738 @@ import * as getParameterNames from 'get-parameter-names'; import { Buffer } from 'buffer'; export type Types = - | 'objectId' - | 'uuid' - | 'binary' - | 'class' - | 'date' - | 'string' - | 'boolean' - | 'number' - | 'enum' - | 'any'; + | 'objectId' + | 'uuid' + | 'binary' + | 'class' + | 'date' + | 'string' + | 'boolean' + | 'number' + | 'enum' + | 'any'; const cache = new Map>(); function getCachedMetaData( - key: string, - target: Object, - propertyName?: string + key: string, + target: Object, + propertyName?: string ): any { - let valueMap = cache.get(target); - if (!valueMap) { - valueMap = new Map(); - cache.set(target, valueMap); - } + let valueMap = cache.get(target); + if (!valueMap) { + valueMap = new Map(); + cache.set(target, valueMap); + } - const cacheKey = key + '::' + propertyName; - let value = valueMap.get(cacheKey); + const cacheKey = key + '::' + propertyName; + let value = valueMap.get(cacheKey); - if (undefined === value) { - if (propertyName) { - value = Reflect.getMetadata(key, target, propertyName); - } else { - value = Reflect.getMetadata(key, target); + if (undefined === value) { + if (propertyName) { + value = Reflect.getMetadata(key, target, propertyName); + } else { + value = Reflect.getMetadata(key, target); + } + valueMap.set(cacheKey, value || ''); } - valueMap.set(cacheKey, value || ''); - } - return value || ''; + return value || ''; } export function getCachedParameterNames(classType: ClassType): string[] { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); - } - - let value = valueMap.get('parameter_names'); - if (!value) { - value = getParameterNames(classType.prototype.constructor); - valueMap.set('parameter_names', value); - } - - return value; + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } + + let value = valueMap.get('parameter_names'); + if (!value) { + value = getParameterNames(classType.prototype.constructor); + valueMap.set('parameter_names', value); + } + + return value; } export function isCircularDataType( - classType: ClassType, - propertyName: string + classType: ClassType, + propertyName: string ): boolean { - return ( - getCachedMetaData( - 'marshal:dataTypeValueCircular', - classType.prototype, - propertyName - ) || false - ); + return ( + getCachedMetaData( + 'marshal:dataTypeValueCircular', + classType.prototype, + propertyName + ) || false + ); } export function getOnLoad( - classType: ClassType + classType: ClassType ): { property: string; options: { fullLoad?: false } }[] { - return getCachedMetaData('marshal:onLoad', classType.prototype) || []; + return getCachedMetaData('marshal:onLoad', classType.prototype) || []; } export interface ResolvedReflectionFound { - resolvedClassType: ClassType; - resolvedPropertyName: string; - type: Types; - typeValue: any; - array: boolean; - map: boolean; + resolvedClassType: ClassType; + resolvedPropertyName: string; + type: Types; + typeValue: any; + array: boolean; + map: boolean; } export type ResolvedReflection = ResolvedReflectionFound | null; export function getResolvedReflection( - classType: ClassType, - propertyPath: string + classType: ClassType, + propertyPath: string ): ResolvedReflection { - const names = propertyPath.split('.'); - let resolvedClassType: ClassType = classType; - let resolvedTypeCandidate: Types | undefined; - let resolvedClassTypeCandidate: ClassType | undefined; - let resolvedPropertyName: string = ''; - let inArrayOrMap = false; - let inClassField = false; - let isArray = false; - let isMap = false; - - for (let i = 0; i < names.length; i++) { - const name = names[i]; - - if (inArrayOrMap) { - if (inClassField && resolvedClassTypeCandidate) { - const { type } = getReflectionType(resolvedClassTypeCandidate, name); - if (!type) { - return null; + const names = propertyPath.split('.'); + let resolvedClassType: ClassType = classType; + let resolvedTypeCandidate: Types | undefined; + let resolvedClassTypeCandidate: ClassType | undefined; + let resolvedPropertyName: string = ''; + let inArrayOrMap = false; + let inClassField = false; + let isArray = false; + let isMap = false; + + for (let i = 0; i < names.length; i++) { + const name = names[i]; + + if (inArrayOrMap) { + if (inClassField && resolvedClassTypeCandidate) { + const { type } = getReflectionType( + resolvedClassTypeCandidate, + name + ); + if (!type) { + return null; + } + inClassField = false; + inArrayOrMap = false; + resolvedClassType = resolvedClassTypeCandidate; + } else { + inClassField = true; + continue; + } } - inClassField = false; - inArrayOrMap = false; - resolvedClassType = resolvedClassTypeCandidate; - } else { - inClassField = true; - continue; - } - } - const { type, typeValue } = getReflectionType(resolvedClassType, name); + const { type, typeValue } = getReflectionType(resolvedClassType, name); - if (!type) { - return null; - } - - resolvedPropertyName = name; - isArray = isArrayType(resolvedClassType, resolvedPropertyName); - isMap = isMapType(resolvedClassType, resolvedPropertyName); - if (isArray || isMap) { - inArrayOrMap = true; - } - - if (type === 'class') { - resolvedClassTypeCandidate = typeValue; - if (resolvedClassTypeCandidate) { - const decorator = getDecorator(resolvedClassTypeCandidate); - if (decorator) { - const { type, typeValue } = getReflectionType( - resolvedClassTypeCandidate, - decorator - ); + if (!type) { + return null; + } - if ( - isArrayType(resolvedClassTypeCandidate, decorator) || - isMapType(resolvedClassTypeCandidate, decorator) - ) { + resolvedPropertyName = name; + isArray = isArrayType(resolvedClassType, resolvedPropertyName); + isMap = isMapType(resolvedClassType, resolvedPropertyName); + if (isArray || isMap) { inArrayOrMap = true; - } + } - if (type === 'class') { - if (!typeValue) { - throw new Error( - `${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )} has no class defined. Use Circular decorator if that class really exists.` - ); - } - resolvedTypeCandidate = type; + if (type === 'class') { resolvedClassTypeCandidate = typeValue; - } else if (type) { - if (names[i + 1]) { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: type, - typeValue: typeValue, - array: false, - map: false, - }; + if (resolvedClassTypeCandidate) { + const decorator = getDecorator(resolvedClassTypeCandidate); + if (decorator) { + const { type, typeValue } = getReflectionType( + resolvedClassTypeCandidate, + decorator + ); + + if ( + isArrayType(resolvedClassTypeCandidate, decorator) || + isMapType(resolvedClassTypeCandidate, decorator) + ) { + inArrayOrMap = true; + } + + if (type === 'class') { + if (!typeValue) { + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} has no class defined. Use Circular decorator if that class really exists.` + ); + } + resolvedTypeCandidate = type; + resolvedClassTypeCandidate = typeValue; + } else if (type) { + if (names[i + 1]) { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: type, + typeValue: typeValue, + array: false, + map: false, + }; + } else { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: 'class', + typeValue: resolvedClassTypeCandidate, + array: isArray, + map: isMap, + }; + } + } else { + return null; + } + } } else { - return { + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} has no class defined. Use Circular decorator if that class really exists.` + ); + } + } + } + + if (inClassField) { + isArray = false; + isMap = false; + + if (resolvedTypeCandidate) { + return { resolvedClassType: resolvedClassType, resolvedPropertyName: resolvedPropertyName, - type: 'class', + type: resolvedTypeCandidate, typeValue: resolvedClassTypeCandidate, array: isArray, map: isMap, - }; - } - } else { - return null; - } + }; } - } else { - throw new Error( - `${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )} has no class defined. Use Circular decorator if that class really exists.` - ); - } - } - } - - if (inClassField) { - isArray = false; - isMap = false; - - if (resolvedTypeCandidate) { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: resolvedTypeCandidate, - typeValue: resolvedClassTypeCandidate, - array: isArray, - map: isMap, - }; - } - } - - const { type, typeValue } = getReflectionType( - resolvedClassType, - resolvedPropertyName - ); - if (type) { - return { - resolvedClassType: resolvedClassType, - resolvedPropertyName: resolvedPropertyName, - type: type, - typeValue: typeValue, - array: isArray, - map: isMap, - }; - } - - return null; + } + + const { type, typeValue } = getReflectionType( + resolvedClassType, + resolvedPropertyName + ); + if (type) { + return { + resolvedClassType: resolvedClassType, + resolvedPropertyName: resolvedPropertyName, + type: type, + typeValue: typeValue, + array: isArray, + map: isMap, + }; + } + + return null; } export function getReflectionType( - classType: ClassType, - propertyName: string + classType: ClassType, + propertyName: string ): { type: Types | null; typeValue: any | null } { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); - } - - let value = valueMap.get('getReflectionType::' + propertyName); - - if (undefined === value) { - const type = - Reflect.getMetadata( - 'marshal:dataType', - classType.prototype, - propertyName - ) || null; - let typeValue = - Reflect.getMetadata( - 'marshal:dataTypeValue', - classType.prototype, - propertyName - ) || null; + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } + + let value = valueMap.get('getReflectionType::' + propertyName); + + if (undefined === value) { + const type = + Reflect.getMetadata( + 'marshal:dataType', + classType.prototype, + propertyName + ) || null; + let typeValue = + Reflect.getMetadata( + 'marshal:dataTypeValue', + classType.prototype, + propertyName + ) || null; + + if (isCircularDataType(classType, propertyName)) { + typeValue = typeValue(); + } + value = { + type: type, + typeValue: typeValue, + }; - if (isCircularDataType(classType, propertyName)) { - typeValue = typeValue(); + valueMap.set('getReflectionType::' + propertyName, value); } - value = { - type: type, - typeValue: typeValue, - }; - valueMap.set('getReflectionType::' + propertyName, value); - } - - return value; + return value; } export function getParentReferenceClass( - classType: ClassType, - propertyName: string + classType: ClassType, + propertyName: string ): any { - let valueMap = cache.get(classType.prototype); - if (!valueMap) { - valueMap = new Map(); - cache.set(classType.prototype, valueMap); - } - - let value = valueMap.get('ParentReferenceClass::' + propertyName); - if (undefined === value) { - const parentReference = - Reflect.getMetadata( - 'marshal:parentReference', - classType.prototype, - propertyName - ) || false; + let valueMap = cache.get(classType.prototype); + if (!valueMap) { + valueMap = new Map(); + cache.set(classType.prototype, valueMap); + } - if (parentReference) { - const { typeValue } = getReflectionType(classType, propertyName); + let value = valueMap.get('ParentReferenceClass::' + propertyName); + if (undefined === value) { + const parentReference = + Reflect.getMetadata( + 'marshal:parentReference', + classType.prototype, + propertyName + ) || false; - if (!typeValue) { - throw new Error( - `${getClassPropertyName( - classType, - propertyName - )} has @ParentReference but no @Class defined.` - ); - } - value = typeValue; + if (parentReference) { + const { typeValue } = getReflectionType(classType, propertyName); + + if (!typeValue) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} has @ParentReference but no @Class defined.` + ); + } + value = typeValue; + } + valueMap.set('ParentReferenceClass::' + propertyName, value || ''); } - valueMap.set('ParentReferenceClass::' + propertyName, value || ''); - } - return value; + return value; } export function propertyClassToPlain( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - if (undefined === propertyValue) { - return undefined; - } + if (undefined === propertyValue) { + return undefined; + } - if (null === propertyValue) { - return null; - } - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; + if (null === propertyValue) { + return null; + } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - const { - resolvedClassType, - resolvedPropertyName, - type, - typeValue, - array, - map, - } = reflection; + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; - function convert(value: any) { - if ('date' === type && value instanceof Date) { - return value.toJSON(); - } + function convert(value: any) { + if ('date' === type && value instanceof Date) { + return value.toJSON(); + } - if ('string' === type) { - return String(value); - } + if ('string' === type) { + return String(value); + } - if ('enum' === type) { - //the class instance itself can only have the actual value which can be used in plain as well - return value; - } + if ('enum' === type) { + //the class instance itself can only have the actual value which can be used in plain as well + return value; + } - if ('binary' === type && value.toString) { - return value.toString('base64'); - } + if ('binary' === type && value.toString) { + return value.toString('base64'); + } - if ('any' === type) { - return clone(value, false); - } + if ('any' === type) { + return clone(value, false); + } - if (type === 'class') { - if (!(value instanceof typeValue)) { - throw new Error( - `Could not convert ${getClassPropertyName( - classType, - propertyName - )} since target is not a ` + - `class instance of ${getClassName(typeValue)}. Got ${getClassName( - value - )}` - ); - } + if (type === 'class') { + if (!(value instanceof typeValue)) { + throw new Error( + `Could not convert ${getClassPropertyName( + classType, + propertyName + )} since target is not a ` + + `class instance of ${getClassName( + typeValue + )}. Got ${getClassName(value)}` + ); + } + + return classToPlain(typeValue, value); + } - return classToPlain(typeValue, value); + return value; } - return value; - } + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map((v) => convert(v)); + return []; } - return []; - } - - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; } - return result; - } - return convert(propertyValue); + return convert(propertyValue); } export function propertyPlainToClass( - classType: ClassType, - propertyName: string, - propertyValue: any, - parents: any[], - incomingLevel: number, - state: ToClassState + classType: ClassType, + propertyName: string, + propertyValue: any, + parents: any[], + incomingLevel: number, + state: ToClassState ) { - if (isUndefined(propertyValue)) { - return undefined; - } + if (isUndefined(propertyValue)) { + return undefined; + } - if (null === propertyValue) { - return null; - } + if (null === propertyValue) { + return null; + } - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - const { - resolvedClassType, - resolvedPropertyName, - type, - typeValue, - array, - map, - } = reflection; + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + function convert(value: any) { + if ( + 'date' === type && + ('string' === typeof value || 'number' === typeof value) + ) { + return new Date(value); + } - function convert(value: any) { - if ( - 'date' === type && - ('string' === typeof value || 'number' === typeof value) - ) { - return new Date(value); - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - if ('string' === type && 'string' !== typeof value) { - return String(value); - } + if ('number' === type && 'number' !== typeof value) { + return +value; + } - if ('number' === type && 'number' !== typeof value) { - return +value; - } + if ('binary' === type && 'string' === typeof value) { + return new Buffer(value, 'base64'); + } - if ('binary' === type && 'string' === typeof value) { - return new Buffer(value, 'base64'); - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + return true === value; + } - return true === value; - } + if ('any' === type) { + return clone(value, false); + } - if ('any' === type) { - return clone(value, false); - } + if ('enum' === type) { + const allowLabelsAsValue = isEnumAllowLabelsAsValue( + resolvedClassType, + resolvedPropertyName + ); + if ( + undefined !== value && + !isValidEnumValue(typeValue, value, allowLabelsAsValue) + ) { + const valids = getEnumKeys(typeValue); + if (allowLabelsAsValue) { + for (const label of getEnumLabels(typeValue)) { + valids.push(label); + } + } + throw new Error( + `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${valids.join( + ',' + )}` + ); + } - if ('enum' === type) { - const allowLabelsAsValue = isEnumAllowLabelsAsValue( - resolvedClassType, - resolvedPropertyName - ); - if ( - undefined !== value && - !isValidEnumValue(typeValue, value, allowLabelsAsValue) - ) { - const valids = getEnumKeys(typeValue); - if (allowLabelsAsValue) { - for (const label of getEnumLabels(typeValue)) { - valids.push(label); - } + return getValidEnumValue(typeValue, value, allowLabelsAsValue); } - throw new Error( - `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${valids.join( - ',' - )}` - ); - } - return getValidEnumValue(typeValue, value, allowLabelsAsValue); - } + if (type === 'class') { + if (value instanceof typeValue) { + //already the target type, this is an error + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} is already in target format. Are you calling plainToClass() with an class instance?` + ); + } - if (type === 'class') { - if (value instanceof typeValue) { - //already the target type, this is an error - throw new Error( - `${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )} is already in target format. Are you calling plainToClass() with an class instance?` - ); - } + return toClass( + typeValue, + value, + propertyPlainToClass, + parents, + incomingLevel, + state + ); + } - return toClass( - typeValue, - value, - propertyPlainToClass, - parents, - incomingLevel, - state - ); + return value; } - return value; - } + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map((v) => convert(v)); + return []; } - return []; - } - - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; } - return result; - } - return convert(propertyValue); + return convert(propertyValue); } export function cloneClass(target: T, parents?: any[]): T { - return plainToClass( - target.constructor as ClassType, - classToPlain(target.constructor as ClassType, target), - parents - ); + return plainToClass( + target.constructor as ClassType, + classToPlain(target.constructor as ClassType, target), + parents + ); } export function classToPlain(classType: ClassType, target: T): any { - const result: any = {}; - - if (!(target instanceof classType)) { - throw new Error( - `Could not classToPlain since target is not a class instance of ${getClassName( - classType - )}` - ); - } - - const decoratorName = getDecorator(classType); - if (decoratorName) { - return propertyClassToPlain( - classType, - decoratorName, - (target as any)[decoratorName] - ); - } + const result: any = {}; - const propertyNames = getRegisteredProperties(classType); - - for (const propertyName of propertyNames) { - if (undefined === (target as any)[propertyName]) { - continue; + if (!(target instanceof classType)) { + throw new Error( + `Could not classToPlain since target is not a class instance of ${getClassName( + classType + )}` + ); } - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; + const decoratorName = getDecorator(classType); + if (decoratorName) { + return propertyClassToPlain( + classType, + decoratorName, + (target as any)[decoratorName] + ); } - if (isExcluded(classType, propertyName, 'plain')) { - continue; - } + const propertyNames = getRegisteredProperties(classType); - result[propertyName] = propertyClassToPlain( - classType, - propertyName, - (target as any)[propertyName] - ); - } + for (const propertyName of propertyNames) { + if (undefined === (target as any)[propertyName]) { + continue; + } + + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; + } - return result; + if (isExcluded(classType, propertyName, 'plain')) { + continue; + } + + result[propertyName] = propertyClassToPlain( + classType, + propertyName, + (target as any)[propertyName] + ); + } + + return result; } export class ToClassState { - onFullLoadCallbacks: (() => void)[] = []; + onFullLoadCallbacks: (() => void)[] = []; } const propertyNamesCache = new Map, string[]>(); const parentReferencesCache = new Map< - ClassType, - { [propertyName: string]: any } + ClassType, + { [propertyName: string]: any } >(); function findParent(parents: any[], parentType: ClassType): T | null { - for (let i = parents.length - 1; i >= 0; i--) { - if (parents[i] instanceof parentType) { - return parents[i]; + for (let i = parents.length - 1; i >= 0; i--) { + if (parents[i] instanceof parentType) { + return parents[i]; + } } - } - return null; + return null; } export function toClass( - classType: ClassType, - cloned: object, - converter: ( classType: ClassType, - propertyName: string, - propertyValue: any, + cloned: object, + converter: ( + classType: ClassType, + propertyName: string, + propertyValue: any, + parents: any[], + incomingLevel: number, + state: ToClassState + ) => any, parents: any[], - incomingLevel: number, + incomingLevel, state: ToClassState - ) => any, - parents: any[], - incomingLevel, - state: ToClassState ): T { - const assignedViaConstructor: { [propertyName: string]: boolean } = {}; - - let propertyNames = propertyNamesCache.get(classType); - if (!propertyNames) { - propertyNames = getRegisteredProperties(classType); - propertyNamesCache.set(classType, propertyNames); - } - - let parentReferences = parentReferencesCache.get(classType); - if (!parentReferences) { - parentReferences = {}; - for (const propertyName of propertyNames) { - parentReferences[propertyName] = getParentReferenceClass( - classType, - propertyName - ); - } - parentReferencesCache.set(classType, parentReferences); - } - - const parameterNames = getCachedParameterNames(classType); - - const decoratorName = getDecorator(classType); - const backupedClone = cloned; - - if (!isObject(cloned)) { - cloned = {}; - } + const assignedViaConstructor: { [propertyName: string]: boolean } = {}; + + let propertyNames = propertyNamesCache.get(classType); + if (!propertyNames) { + propertyNames = getRegisteredProperties(classType); + propertyNamesCache.set(classType, propertyNames); + } + + let parentReferences = parentReferencesCache.get(classType); + if (!parentReferences) { + parentReferences = {}; + for (const propertyName of propertyNames) { + parentReferences[propertyName] = getParentReferenceClass( + classType, + propertyName + ); + } + parentReferencesCache.set(classType, parentReferences); + } + + const parameterNames = getCachedParameterNames(classType); + + const decoratorName = getDecorator(classType); + const backupedClone = cloned; + + if (!isObject(cloned)) { + cloned = {}; + } + + const args: any[] = []; + for (const propertyName of parameterNames) { + if (decoratorName && propertyName === decoratorName) { + cloned[propertyName] = converter( + classType, + decoratorName, + backupedClone, + parents, + incomingLevel, + state + ); + } else if (parentReferences[propertyName]) { + const parent = findParent(parents, parentReferences[propertyName]); + if (parent) { + cloned[propertyName] = parent; + } else if (!isOptional(classType, propertyName)) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} is in constructor ` + + `has @ParentReference() and NOT @Optional(), but no parent of type ${getClassName( + parentReferences[propertyName] + )} found. ` + + `In case of circular reference, remove '${propertyName}' from constructor, or make sure you provided all parents.` + ); + } + } else { + cloned[propertyName] = converter( + classType, + propertyName, + cloned[propertyName], + parents, + incomingLevel + 1, + state + ); + } - const args: any[] = []; - for (const propertyName of parameterNames) { - if (decoratorName && propertyName === decoratorName) { - cloned[propertyName] = converter( - classType, - decoratorName, - backupedClone, - parents, - incomingLevel, - state - ); - } else if (parentReferences[propertyName]) { - const parent = findParent(parents, parentReferences[propertyName]); - if (parent) { - cloned[propertyName] = parent; - } else if (!isOptional(classType, propertyName)) { - throw new Error( - `${getClassPropertyName( - classType, - propertyName - )} is in constructor ` + - `has @ParentReference() and NOT @Optional(), but no parent of type ${getClassName( - parentReferences[propertyName] - )} found. ` + - `In case of circular reference, remove '${propertyName}' from constructor, or make sure you provided all parents.` - ); - } - } else { - cloned[propertyName] = converter( - classType, - propertyName, - cloned[propertyName], - parents, - incomingLevel + 1, - state - ); + assignedViaConstructor[propertyName] = true; + args.push(cloned[propertyName]); } - assignedViaConstructor[propertyName] = true; - args.push(cloned[propertyName]); - } - - const item = new classType(...args); + const item = new classType(...args); - const parentsWithItem = parents.slice(0); - parentsWithItem.push(item); + const parentsWithItem = parents.slice(0); + parentsWithItem.push(item); - for (const propertyName of propertyNames) { - if (assignedViaConstructor[propertyName]) { - //already given via constructor - continue; - } + for (const propertyName of propertyNames) { + if (assignedViaConstructor[propertyName]) { + //already given via constructor + continue; + } - if (parentReferences[propertyName]) { - const parent = findParent(parents, parentReferences[propertyName]); - if (parent) { - item[propertyName] = parent; - } else if (!isOptional(classType, propertyName)) { - throw new Error( - `${getClassPropertyName( - classType, - propertyName - )} is defined as @ParentReference() and ` + - `NOT @Optional(), but no parent found. Add @Optional() or provide ${propertyName} in parents to fix that.` - ); - } - } else if (undefined !== cloned[propertyName]) { - item[propertyName] = converter( - classType, - propertyName, - cloned[propertyName], - parentsWithItem, - incomingLevel + 1, - state - ); + if (parentReferences[propertyName]) { + const parent = findParent(parents, parentReferences[propertyName]); + if (parent) { + item[propertyName] = parent; + } else if (!isOptional(classType, propertyName)) { + throw new Error( + `${getClassPropertyName( + classType, + propertyName + )} is defined as @ParentReference() and ` + + `NOT @Optional(), but no parent found. Add @Optional() or provide ${propertyName} in parents to fix that.` + ); + } + } else if (undefined !== cloned[propertyName]) { + item[propertyName] = converter( + classType, + propertyName, + cloned[propertyName], + parentsWithItem, + incomingLevel + 1, + state + ); + } } - } - const onLoads = getOnLoad(classType); - for (const onLoad of onLoads) { - if (onLoad.options.fullLoad) { - state.onFullLoadCallbacks.push(() => { - item[onLoad.property](); - }); - } else { - item[onLoad.property](); + const onLoads = getOnLoad(classType); + for (const onLoad of onLoads) { + if (onLoad.options.fullLoad) { + state.onFullLoadCallbacks.push(() => { + item[onLoad.property](); + }); + } else { + item[onLoad.property](); + } } - } - return item; + return item; } /** @@ -753,26 +756,26 @@ export function toClass( * Returns a new regular object again. */ export function partialPlainToClass( - classType: ClassType, - target: { [path: string]: any }, - parents?: any[] + classType: ClassType, + target: { [path: string]: any }, + parents?: any[] ): Partial<{ [F in K]: any }> { - const result: Partial<{ [F in K]: any }> = {}; - const state = new ToClassState(); - - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; - result[i] = propertyPlainToClass( - classType, - i, - target[i], - parents || [], - 1, - state - ); - } + const result: Partial<{ [F in K]: any }> = {}; + const state = new ToClassState(); - return result; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; + result[i] = propertyPlainToClass( + classType, + i, + target[i], + parents || [], + 1, + state + ); + } + + return result; } /** @@ -781,17 +784,17 @@ export function partialPlainToClass( * Returns a new regular object again. */ export function partialClassToPlain( - classType: ClassType, - target: { [path: string]: any } + classType: ClassType, + target: { [path: string]: any } ): Partial<{ [F in K]: any }> { - const result: Partial<{ [F in K]: any }> = {}; + const result: Partial<{ [F in K]: any }> = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; - result[i] = propertyClassToPlain(classType, i, target[i]); - } + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; + result[i] = propertyClassToPlain(classType, i, target[i]); + } - return result; + return result; } /** @@ -799,146 +802,148 @@ export function partialClassToPlain( * and returns an instance of classType. */ export function plainToClass( - classType: ClassType, - target: object, - parents?: any[] + classType: ClassType, + target: object, + parents?: any[] ): T { - const state = new ToClassState(); - const item = toClass( - classType, - target, - propertyPlainToClass, - parents || [], - 1, - state - ); - - for (const callback of state.onFullLoadCallbacks) { - callback(); - } - - return item; + const state = new ToClassState(); + const item = toClass( + classType, + target, + propertyPlainToClass, + parents || [], + 1, + state + ); + + for (const callback of state.onFullLoadCallbacks) { + callback(); + } + + return item; } export function deleteExcludedPropertiesFor( - classType: ClassType, - item: any, - target: 'mongo' | 'plain' + classType: ClassType, + item: any, + target: 'mongo' | 'plain' ) { - for (const propertyName in item) { - if (!item.hasOwnProperty(propertyName)) continue; - if (isExcluded(classType, propertyName, target)) { - delete item[propertyName]; + for (const propertyName in item) { + if (!item.hasOwnProperty(propertyName)) continue; + if (isExcluded(classType, propertyName, target)) { + delete item[propertyName]; + } } - } } export function getIdField(classType: ClassType): string | null { - return getCachedMetaData('marshal:idField', classType.prototype) || null; + return getCachedMetaData('marshal:idField', classType.prototype) || null; } export function getIdFieldValue(classType: ClassType, target: any): any { - const id = getIdField(classType); - return id ? target[id] : null; + const id = getIdField(classType); + return id ? target[id] : null; } export function getDecorator(classType: ClassType): string | null { - return ( - getCachedMetaData('marshal:dataDecorator', classType.prototype) || null - ); + return ( + getCachedMetaData('marshal:dataDecorator', classType.prototype) || null + ); } export function getRegisteredProperties(classType: ClassType): string[] { - return getCachedMetaData('marshal:properties', classType.prototype) || []; + return getCachedMetaData('marshal:properties', classType.prototype) || []; } export function isArrayType( - classType: ClassType, - property: string + classType: ClassType, + property: string ): boolean { - return ( - getCachedMetaData('marshal:isArray', classType.prototype, property) || false - ); + return ( + getCachedMetaData('marshal:isArray', classType.prototype, property) || + false + ); } export function isMapType( - classType: ClassType, - property: string + classType: ClassType, + property: string ): boolean { - return ( - getCachedMetaData('marshal:isMap', classType.prototype, property) || false - ); + return ( + getCachedMetaData('marshal:isMap', classType.prototype, property) || + false + ); } export function isEnumAllowLabelsAsValue( - classType: ClassType, - property: string + classType: ClassType, + property: string ): boolean { - return ( - getCachedMetaData( - 'marshal:enum:allowLabelsAsValue', - classType.prototype, - property - ) || false - ); + return ( + getCachedMetaData( + 'marshal:enum:allowLabelsAsValue', + classType.prototype, + property + ) || false + ); } export function isExcluded( - classType: ClassType, - property: string, - wantedTarget: 'mongo' | 'plain' + classType: ClassType, + property: string, + wantedTarget: 'mongo' | 'plain' ): boolean { - const mode = getCachedMetaData( - 'marshal:exclude', - classType.prototype, - property - ); + const mode = getCachedMetaData( + 'marshal:exclude', + classType.prototype, + property + ); - if ('all' === mode) { - return true; - } + if ('all' === mode) { + return true; + } - return mode === wantedTarget; + return mode === wantedTarget; } export function getEntityName(classType: ClassType): string { - const name = getCachedMetaData('marshal:entityName', classType); + const name = getCachedMetaData('marshal:entityName', classType); - if (!name) { - throw new Error('No @Entity() defined for class ' + classType); - } + if (!name) { + throw new Error('No @Entity() defined for class ' + classType); + } - return name; + return name; } export function getDatabaseName(classType: ClassType): string | null { - return getCachedMetaData('marshal:databaseName', classType) || null; + return getCachedMetaData('marshal:databaseName', classType) || null; } export function getCollectionName(classType: ClassType): string { - const name = getCachedMetaData('marshal:collectionName', classType); + const name = getCachedMetaData('marshal:collectionName', classType); - if (!name) { - throw new Error('No @Entity() defined for class ' + classType); - } + if (!name) { + throw new Error('No @Entity() defined for class ' + classType); + } - return name; + return name; } export function applyDefaultValues( - classType: ClassType, - value: { [name: string]: any } + classType: ClassType, + value: { [name: string]: any } ): object { - if (!isObject(value)) return {}; + if (!isObject(value)) return {}; - const valueWithDefaults = value; - const instance = plainToClass(classType, value); + const valueWithDefaults = value; + const instance = plainToClass(classType, value); - for (const i of getRegisteredProperties(classType)) { - if (undefined === value[i]) { - valueWithDefaults[i] = (instance as any)[i]; + for (const i of getRegisteredProperties(classType)) { + if (undefined === value[i]) { + valueWithDefaults[i] = (instance as any)[i]; + } } - } - return valueWithDefaults; + return valueWithDefaults; } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 0894c6ca2..8f66cd24c 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,121 +1,121 @@ import { v4 } from 'uuid'; export function getClassName(classType: ClassType | Object): string { - return ( - classType['name'] || - (classType.constructor ? classType.constructor.name : '') - ); + return ( + classType['name'] || + (classType.constructor ? classType.constructor.name : '') + ); } export function getClassPropertyName( - classType: ClassType | Object, - propertyName: string + classType: ClassType | Object, + propertyName: string ): string { - const name = getClassName(classType); + const name = getClassName(classType); - return `${name}::${propertyName}`; + return `${name}::${propertyName}`; } export function uuid(): string { - return v4(); + return v4(); } export type ClassType = new (...args: any[]) => T; export function typeOf(obj: any) { - return {}.toString - .call(obj) - .match(/\s([a-zA-Z]+)/)[1] - .toLowerCase(); + return {}.toString + .call(obj) + .match(/\s([a-zA-Z]+)/)[1] + .toLowerCase(); } export function isObject(obj: any): obj is object { - if (obj === null) { - return false; - } - return ( - typeof obj === 'function' || (typeof obj === 'object' && !isArray(obj)) - ); + if (obj === null) { + return false; + } + return ( + typeof obj === 'function' || (typeof obj === 'object' && !isArray(obj)) + ); } export function isArray(obj: any): obj is any[] { - return Array.isArray(obj); + return Array.isArray(obj); } export function isUndefined(obj: any): obj is undefined { - return undefined === obj; + return undefined === obj; } const cacheEnumLabels = new Map(); export function getEnumLabels(enumDefinition: any) { - let value = cacheEnumLabels.get(enumDefinition); - if (!value) { - value = Object.keys(enumDefinition).filter( - (v) => !Number.isFinite(parseInt(v)) - ); - cacheEnumLabels.set(enumDefinition, value); - } + let value = cacheEnumLabels.get(enumDefinition); + if (!value) { + value = Object.keys(enumDefinition).filter( + (v) => !Number.isFinite(parseInt(v)) + ); + cacheEnumLabels.set(enumDefinition, value); + } - return value; + return value; } const cacheEnumKeys = new Map(); export function getEnumKeys(enumDefinition: any): any[] { - let value = cacheEnumKeys.get(enumDefinition); - if (!value) { - const labels = getEnumLabels(enumDefinition); - value = Object.values(enumDefinition).filter( - (v) => -1 === labels.indexOf(v as string) - ) as any[]; - - cacheEnumKeys.set(enumDefinition, value); - } + let value = cacheEnumKeys.get(enumDefinition); + if (!value) { + const labels = getEnumLabels(enumDefinition); + value = Object.values(enumDefinition).filter( + (v) => -1 === labels.indexOf(v as string) + ) as any[]; + + cacheEnumKeys.set(enumDefinition, value); + } - return value; + return value; } export function isValidEnumValue( - enumDefinition: any, - value: any, - allowLabelsAsValue = false + enumDefinition: any, + value: any, + allowLabelsAsValue = false ) { - if (allowLabelsAsValue) { - const labels = getEnumLabels(enumDefinition); - if (-1 !== labels.indexOf(String(value))) { - return true; + if (allowLabelsAsValue) { + const labels = getEnumLabels(enumDefinition); + if (-1 !== labels.indexOf(String(value))) { + return true; + } } - } - - const keys = getEnumKeys(enumDefinition); - return ( - -1 !== keys.indexOf(+value) || - -1 !== keys.indexOf(value) || - -1 !== keys.indexOf(String(value)) - ); + + const keys = getEnumKeys(enumDefinition); + return ( + -1 !== keys.indexOf(+value) || + -1 !== keys.indexOf(value) || + -1 !== keys.indexOf(String(value)) + ); } export function getValidEnumValue( - enumDefinition: any, - value: any, - allowLabelsAsValue = false + enumDefinition: any, + value: any, + allowLabelsAsValue = false ) { - if (allowLabelsAsValue) { - const labels = getEnumLabels(enumDefinition); - if (-1 !== labels.indexOf(String(value))) { - return enumDefinition[String(value)]; + if (allowLabelsAsValue) { + const labels = getEnumLabels(enumDefinition); + if (-1 !== labels.indexOf(String(value))) { + return enumDefinition[String(value)]; + } } - } - const keys = getEnumKeys(enumDefinition); - if (-1 !== keys.indexOf(value)) { - return value; - } - if (-1 !== keys.indexOf(+value)) { - return +value; - } - if (-1 !== keys.indexOf(String(value))) { - return String(value); - } + const keys = getEnumKeys(enumDefinition); + if (-1 !== keys.indexOf(value)) { + return value; + } + if (-1 !== keys.indexOf(+value)) { + return +value; + } + if (-1 !== keys.indexOf(String(value))) { + return String(value); + } } diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index b1ef467d4..ed1ba3dda 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -1,213 +1,222 @@ import { ClassType, isArray, isObject, typeOf } from './utils'; import { - applyDefaultValues, - getReflectionType, - getRegisteredProperties, - isArrayType, - isMapType, + applyDefaultValues, + getReflectionType, + getRegisteredProperties, + isArrayType, + isMapType, } from './mapper'; export function addValidator( - target: Object, - property: string, - validator: ClassType + target: Object, + property: string, + validator: ClassType ) { - const validators = - Reflect.getMetadata('marshal:validators', target, property) || []; - if (-1 === validators.indexOf(validator)) { - validators.push(validator); - } + const validators = + Reflect.getMetadata('marshal:validators', target, property) || []; + if (-1 === validators.indexOf(validator)) { + validators.push(validator); + } - Reflect.defineMetadata('marshal:validators', validators, target, property); + Reflect.defineMetadata('marshal:validators', validators, target, property); } export class PropertyValidatorError { - message: string; + message: string; - constructor(message: string) { - this.message = message; - } + constructor(message: string) { + this.message = message; + } } export interface PropertyValidator { - validate( - value: any, - target: ClassType, - propertyName: string - ): Promise; + validate( + value: any, + target: ClassType, + propertyName: string + ): Promise; } export function getValidators( - classType: ClassType, - propertyName: string + classType: ClassType, + propertyName: string ): ClassType[] { - return ( - Reflect.getMetadata( - 'marshal:validators', - classType.prototype, - propertyName - ) || [] - ); + return ( + Reflect.getMetadata( + 'marshal:validators', + classType.prototype, + propertyName + ) || [] + ); } export function AddValidator(validator: ClassType) { - return (target: Object, property: string) => { - addValidator(target, property, validator); - }; + return (target: Object, property: string) => { + addValidator(target, property, validator); + }; } export class RequiredValidator implements PropertyValidator { - async validate( - value: any, - target: ClassType, - propertyName: string - ): Promise { - if (undefined === value) { - return new PropertyValidatorError('Required value is undefined'); + async validate( + value: any, + target: ClassType, + propertyName: string + ): Promise { + if (undefined === value) { + return new PropertyValidatorError('Required value is undefined'); + } } - } } export function Optional() { - return (target: Object, propertyName: string) => { - Reflect.defineMetadata('marshal:isOptional', true, target, propertyName); - }; + return (target: Object, propertyName: string) => { + Reflect.defineMetadata( + 'marshal:isOptional', + true, + target, + propertyName + ); + }; } export function isOptional( - classType: ClassType, - propertyName: string + classType: ClassType, + propertyName: string ): boolean { - return ( - Reflect.getMetadata( - 'marshal:isOptional', - classType.prototype, - propertyName - ) || false - ); + return ( + Reflect.getMetadata( + 'marshal:isOptional', + classType.prototype, + propertyName + ) || false + ); } export class ValidationError { - path: string; - message: string; - - constructor(path: string, message: string) { - this.path = path; - this.message = message; - } - - static createInvalidType(path: string, expectedType: string, actual: any) { - return new ValidationError( - path, - `Invalid type. Expected ${expectedType}, but got ${typeOf(actual)}` - ); - } + path: string; + message: string; + + constructor(path: string, message: string) { + this.path = path; + this.message = message; + } + + static createInvalidType(path: string, expectedType: string, actual: any) { + return new ValidationError( + path, + `Invalid type. Expected ${expectedType}, but got ${typeOf(actual)}` + ); + } } export async function validate( - classType: ClassType, - item: { [name: string]: any }, - path?: string + classType: ClassType, + item: { [name: string]: any }, + path?: string ): Promise { - const properties = getRegisteredProperties(classType); - const errors: ValidationError[] = []; - - if (!(item instanceof classType)) { - item = applyDefaultValues(classType, item as object); - } - - async function handleValidator( - validatorType: ClassType, - value: any, - propertyName: string, - propertyPath: string - ): Promise { - const instance = new validatorType(); - const result = await instance.validate(value, classType, propertyName); - if (result instanceof PropertyValidatorError) { - errors.push(new ValidationError(propertyPath, result.message)); - return true; - } + const properties = getRegisteredProperties(classType); + const errors: ValidationError[] = []; - return false; - } - - for (const propertyName of properties) { - const { type, typeValue } = getReflectionType(classType, propertyName); - const propertyPath = path ? path + '.' + propertyName : propertyName; - const validators = getValidators(classType, propertyName); - const propertyValue: any = item[propertyName]; - const array = isArrayType(classType, propertyName); - const map = isMapType(classType, propertyName); - - if (!isOptional(classType, propertyName)) { - await handleValidator( - RequiredValidator, - propertyValue, - propertyName, - propertyPath - ); + if (!(item instanceof classType)) { + item = applyDefaultValues(classType, item as object); } - if (undefined === propertyValue) { - //there's no need to continue validation without a value. - continue; + async function handleValidator( + validatorType: ClassType, + value: any, + propertyName: string, + propertyPath: string + ): Promise { + const instance = new validatorType(); + const result = await instance.validate(value, classType, propertyName); + if (result instanceof PropertyValidatorError) { + errors.push(new ValidationError(propertyPath, result.message)); + return true; + } + + return false; } - if (array) { - if (!isArray(propertyValue)) { - errors.push( - ValidationError.createInvalidType( - propertyPath, - 'array', - propertyValue - ) - ); - continue; - } - } else { - if (type === 'class' || map) { - if (!isObject(propertyValue)) { - errors.push( - ValidationError.createInvalidType( - propertyPath, - 'object', - propertyValue - ) - ); - continue; + for (const propertyName of properties) { + const { type, typeValue } = getReflectionType(classType, propertyName); + const propertyPath = path ? path + '.' + propertyName : propertyName; + const validators = getValidators(classType, propertyName); + const propertyValue: any = item[propertyName]; + const array = isArrayType(classType, propertyName); + const map = isMapType(classType, propertyName); + + if (!isOptional(classType, propertyName)) { + await handleValidator( + RequiredValidator, + propertyValue, + propertyName, + propertyPath + ); } - } - } - for (const validatorType of validators) { - await handleValidator( - validatorType, - propertyValue, - propertyName, - propertyPath - ); - } + if (undefined === propertyValue) { + //there's no need to continue validation without a value. + continue; + } + + if (array) { + if (!isArray(propertyValue)) { + errors.push( + ValidationError.createInvalidType( + propertyPath, + 'array', + propertyValue + ) + ); + continue; + } + } else { + if (type === 'class' || map) { + if (!isObject(propertyValue)) { + errors.push( + ValidationError.createInvalidType( + propertyPath, + 'object', + propertyValue + ) + ); + continue; + } + } + } - if (type === 'class') { - if (map || array) { - if (array && !isArray(propertyValue)) continue; - if (map && !isObject(propertyValue)) continue; + for (const validatorType of validators) { + await handleValidator( + validatorType, + propertyValue, + propertyName, + propertyPath + ); + } - for (const i in propertyValue) { - const deepPropertyPath = propertyPath + '.' + i; - errors.push( - ...(await validate(typeValue, propertyValue[i], deepPropertyPath)) - ); + if (type === 'class') { + if (map || array) { + if (array && !isArray(propertyValue)) continue; + if (map && !isObject(propertyValue)) continue; + + for (const i in propertyValue) { + const deepPropertyPath = propertyPath + '.' + i; + errors.push( + ...(await validate( + typeValue, + propertyValue[i], + deepPropertyPath + )) + ); + } + } else { + //deep validation + errors.push( + ...(await validate(typeValue, propertyValue, propertyPath)) + ); + } } - } else { - //deep validation - errors.push( - ...(await validate(typeValue, propertyValue, propertyPath)) - ); - } } - } - return errors; + return errors; } diff --git a/packages/core/tests/big-entity.ts b/packages/core/tests/big-entity.ts index c11bdf04b..15f99e7cf 100644 --- a/packages/core/tests/big-entity.ts +++ b/packages/core/tests/big-entity.ts @@ -1,562 +1,562 @@ import { - AnyType, - ArrayType, - BooleanType, - Class, - ClassArray, - ClassMap, - DateType, - Entity, - EnumType, - ID, - MapType, - NumberType, - Optional, - StringType, - UUIDType, + AnyType, + ArrayType, + BooleanType, + Class, + ClassArray, + ClassMap, + DateType, + Entity, + EnumType, + ID, + MapType, + NumberType, + Optional, + StringType, + UUIDType, } from '../'; export class JobConfigDocker { - @StringType() - @ArrayType() - env: string[] = []; //e.g. ["PATH=bla"] + @StringType() + @ArrayType() + env: string[] = []; //e.g. ["PATH=bla"] - @StringType() - @ArrayType() - binds: string[] = []; //e.g. ["/tmp:/tmp"] + @StringType() + @ArrayType() + binds: string[] = []; //e.g. ["/tmp:/tmp"] - @StringType() - @ArrayType() - links: string[] = []; //e.g. ["redis3:redis"] + @StringType() + @ArrayType() + links: string[] = []; //e.g. ["redis3:redis"] } export class JobResources { - @NumberType() - cpu: number = 1; + @NumberType() + cpu: number = 1; - @NumberType() - memory: number = 1; + @NumberType() + memory: number = 1; - @NumberType() - gpu: number = 0; + @NumberType() + gpu: number = 0; - @NumberType() - gpuMemory: number = 0; + @NumberType() + gpuMemory: number = 0; } export class JobTaskCommand { - @StringType() - name: string = ''; + @StringType() + name: string = ''; - @StringType() - command: string = ''; + @StringType() + command: string = ''; - constructor(name: string, command: string) { - this.name = name; - this.command = command; - } + constructor(name: string, command: string) { + this.name = name; + this.command = command; + } } export class JobTaskConfigBase { - @StringType() - @ArrayType() - install: string[] | null = null; + @StringType() + @ArrayType() + install: string[] | null = null; - @StringType() - dockerfile: string = ''; + @StringType() + dockerfile: string = ''; - @StringType() - @ArrayType() - install_files: string[] | null = null; + @StringType() + @ArrayType() + install_files: string[] | null = null; - @StringType() - image: string = ''; + @StringType() + image: string = ''; - @StringType() - @ArrayType() - environmentVariables: string[] = []; + @StringType() + @ArrayType() + environmentVariables: string[] = []; - @StringType() - @ArrayType() - servers: string[] = []; + @StringType() + @ArrayType() + servers: string[] = []; - @ClassArray(JobTaskCommand) - commands: JobTaskCommand[] = []; + @ClassArray(JobTaskCommand) + commands: JobTaskCommand[] = []; - @StringType() - @ArrayType() - args: null | string[] = null; + @StringType() + @ArrayType() + args: null | string[] = null; - @Class(JobResources) - resources: JobResources = new JobResources(); + @Class(JobResources) + resources: JobResources = new JobResources(); - @Class(JobConfigDocker) - docker: JobConfigDocker = new JobConfigDocker(); + @Class(JobConfigDocker) + docker: JobConfigDocker = new JobConfigDocker(); - public isDockerImage(): boolean { - return !!this.image; - } + public isDockerImage(): boolean { + return !!this.image; + } - public hasCommand() { - return this.commands.length > 0; - } + public hasCommand() { + return this.commands.length > 0; + } } export class JobTaskConfig extends JobTaskConfigBase { - /** - * Will be set by config loading. - */ - @StringType() - name: string = ''; - - @NumberType() - replicas: number = 1; - - @StringType() - @ArrayType() - depends_on: string[] = []; + /** + * Will be set by config loading. + */ + @StringType() + name: string = ''; + + @NumberType() + replicas: number = 1; + + @StringType() + @ArrayType() + depends_on: string[] = []; } export class JobConfig extends JobTaskConfigBase { - public static readonly inheritTaskProperties: string[] = [ - 'install', - 'dockerfile', - 'install_files', - 'image', - 'environmentVariables', - 'servers', - 'resources', - 'commands', - 'args', - 'docker', - ]; - - @AnyType() - parameters: { [name: string]: any } = {}; - - @StringType() - @ArrayType() - ignore: string[] = []; - - @NumberType() - priority: number = 0; - - @StringType() - import: string = ''; - - @ClassMap(JobTaskConfig) - tasks: { [name: string]: JobTaskConfig } = {}; + public static readonly inheritTaskProperties: string[] = [ + 'install', + 'dockerfile', + 'install_files', + 'image', + 'environmentVariables', + 'servers', + 'resources', + 'commands', + 'args', + 'docker', + ]; + + @AnyType() + parameters: { [name: string]: any } = {}; + + @StringType() + @ArrayType() + ignore: string[] = []; + + @NumberType() + priority: number = 0; + + @StringType() + import: string = ''; + + @ClassMap(JobTaskConfig) + tasks: { [name: string]: JobTaskConfig } = {}; } export class JobEnvironmentPython { - @StringType() - version?: string; + @StringType() + version?: string; - @StringType() - binary?: string; + @StringType() + binary?: string; - @StringType() - sdkVersion?: string; + @StringType() + sdkVersion?: string; - @StringType() - @MapType() - pipPackages: { [name: string]: string } = {}; + @StringType() + @MapType() + pipPackages: { [name: string]: string } = {}; } export class JobEnvironment { - @StringType() - hostname?: string; + @StringType() + hostname?: string; - @StringType() - username?: string; + @StringType() + username?: string; - @StringType() - platform?: string; + @StringType() + platform?: string; - @StringType() - release?: string; + @StringType() + release?: string; - @StringType() - arch?: string; + @StringType() + arch?: string; - @NumberType() - uptime?: number; + @NumberType() + uptime?: number; - @StringType() - nodeVersion?: string; + @StringType() + nodeVersion?: string; - @StringType() - @MapType() - environmentVariables?: { [name: string]: string }; + @StringType() + @MapType() + environmentVariables?: { [name: string]: string }; - @Class(JobEnvironmentPython) - python?: JobEnvironmentPython; + @Class(JobEnvironmentPython) + python?: JobEnvironmentPython; } export class JobGit { - @StringType() - author?: string; + @StringType() + author?: string; - @StringType() - branch?: string; + @StringType() + branch?: string; - @StringType() - commit?: string; + @StringType() + commit?: string; - @StringType() - message?: string; + @StringType() + message?: string; - @StringType() - origin?: string; + @StringType() + origin?: string; } export class JobDocker { - runOnVersion?: string; + runOnVersion?: string; } export class JobDockerImage { - @StringType() - name?: string; + @StringType() + name?: string; - @StringType() - id?: string; + @StringType() + id?: string; - @NumberType() - size?: number; + @NumberType() + size?: number; - @StringType() - os?: string; + @StringType() + os?: string; - @StringType() - arch?: string; + @StringType() + arch?: string; - @DateType() - created?: Date; + @DateType() + created?: Date; - @StringType() - builtWithDockerVersion?: string; + @StringType() + builtWithDockerVersion?: string; } export enum JobStatus { - creating = 0, - created = 50, //when all files are attached + creating = 0, + created = 50, //when all files are attached - started = 100, + started = 100, - done = 150, //when all tasks are done - aborted = 200, //when at least one task aborted + done = 150, //when all tasks are done + aborted = 200, //when at least one task aborted - failed = 250, //when at least one task failed - crashed = 300, //when at least one task crashed + failed = 250, //when at least one task failed + crashed = 300, //when at least one task crashed } export enum JobTaskStatus { - pending = 0, + pending = 0, - queued = 100, //when the job got a queue position assigned and queue results - assigned = 150, //when a server or multiple servers are assigned and at least one replica is about to start + queued = 100, //when the job got a queue position assigned and queue results + assigned = 150, //when a server or multiple servers are assigned and at least one replica is about to start - started = 300, + started = 300, - //beginning with that ended - done = 500, - aborted = 550, - failed = 600, - crashed = 650, + //beginning with that ended + done = 500, + aborted = 550, + failed = 600, + crashed = 650, } export enum JobTaskInstanceStatus { - pending = 0, + pending = 0, - booting = 200, //is starting the job's task - docker_pull = 220, //joining docker's network - docker_build = 230, //joining docker's network - joining_network = 250, //joining docker's network + booting = 200, //is starting the job's task + docker_pull = 220, //joining docker's network + docker_build = 230, //joining docker's network + joining_network = 250, //joining docker's network - started = 300, + started = 300, - //beginning with that ended - done = 500, - aborted = 550, - failed = 600, - crashed = 650, + //beginning with that ended + done = 500, + aborted = 550, + failed = 600, + crashed = 650, } export class Channel { - @StringType() - @ArrayType() - traces: string[] = []; + @StringType() + @ArrayType() + traces: string[] = []; - @BooleanType() - main?: boolean; + @BooleanType() + main?: boolean; - @BooleanType() - kpi?: boolean; + @BooleanType() + kpi?: boolean; - @NumberType() - kpiTrace: number = 0; + @NumberType() + kpiTrace: number = 0; - @BooleanType() - maxOptimization: boolean = true; + @BooleanType() + maxOptimization: boolean = true; - @AnyType() - @ArrayType() - lastValue: any[] = []; + @AnyType() + @ArrayType() + lastValue: any[] = []; - @AnyType() - xaxis?: object; + @AnyType() + xaxis?: object; - @AnyType() - yaxis?: object; + @AnyType() + yaxis?: object; - @AnyType() - layout?: object; + @AnyType() + layout?: object; } export class JobAssignedResourcesGpu { - @NumberType() - id: number; + @NumberType() + id: number; - @NumberType() - memory: number; + @NumberType() + memory: number; - constructor(id: number, memory: number) { - this.id = id; - this.memory = memory; - } + constructor(id: number, memory: number) { + this.id = id; + this.memory = memory; + } } export class JobAssignedResources { - @NumberType() - cpu: number = 0; + @NumberType() + cpu: number = 0; + + @NumberType() + memory: number = 0; - @NumberType() - memory: number = 0; + @ClassArray(JobAssignedResourcesGpu) + gpus: JobAssignedResourcesGpu[] = []; - @ClassArray(JobAssignedResourcesGpu) - gpus: JobAssignedResourcesGpu[] = []; + public getMinGpuMemory(): number { + let minGpuMemory = 0; - public getMinGpuMemory(): number { - let minGpuMemory = 0; + for (const gpu of this.gpus) { + if (gpu.memory < minGpuMemory) minGpuMemory = gpu.memory; + } - for (const gpu of this.gpus) { - if (gpu.memory < minGpuMemory) minGpuMemory = gpu.memory; + return minGpuMemory; } - return minGpuMemory; - } + public getMaxGpuMemory(): number { + let maxGpuMemory = 0; - public getMaxGpuMemory(): number { - let maxGpuMemory = 0; + for (const gpu of this.gpus) { + if (gpu.memory > maxGpuMemory) maxGpuMemory = gpu.memory; + } - for (const gpu of this.gpus) { - if (gpu.memory > maxGpuMemory) maxGpuMemory = gpu.memory; + return maxGpuMemory; } - return maxGpuMemory; - } - - public getGpuMemoryRange(): string { - const min = this.getMinGpuMemory(); - const max = this.getMaxGpuMemory(); + public getGpuMemoryRange(): string { + const min = this.getMinGpuMemory(); + const max = this.getMaxGpuMemory(); - if (min === max) return `${min}`; + if (min === max) return `${min}`; - return `${min}-${max}`; - } + return `${min}-${max}`; + } } export class JobTaskInstance { - @NumberType() - id: number; + @NumberType() + id: number; - @EnumType(JobTaskInstanceStatus) - status: JobTaskInstanceStatus = JobTaskInstanceStatus.pending; + @EnumType(JobTaskInstanceStatus) + status: JobTaskInstanceStatus = JobTaskInstanceStatus.pending; - @Class(JobEnvironment) - environment: JobEnvironment = new JobEnvironment(); + @Class(JobEnvironment) + environment: JobEnvironment = new JobEnvironment(); - @UUIDType() - server?: string; + @UUIDType() + server?: string; - @Class(JobAssignedResources) - assignedResources: JobAssignedResources = new JobAssignedResources(); + @Class(JobAssignedResources) + assignedResources: JobAssignedResources = new JobAssignedResources(); - constructor(id: number) { - this.id = id; - } + constructor(id: number) { + this.id = id; + } - public isStarted() { - return this.status >= JobTaskInstanceStatus.booting; - } + public isStarted() { + return this.status >= JobTaskInstanceStatus.booting; + } - public isRunning() { - return this.isStarted() && !this.isEnded(); - } + public isRunning() { + return this.isStarted() && !this.isEnded(); + } - public isEnded() { - return this.status >= JobTaskInstanceStatus.done; - } + public isEnded() { + return this.status >= JobTaskInstanceStatus.done; + } } export class JobTaskQueue { - @NumberType() - position: number = 0; + @NumberType() + position: number = 0; - @NumberType() - tries: number = 0; + @NumberType() + tries: number = 0; - @StringType() - result: string = ''; + @StringType() + result: string = ''; - @DateType() - added: Date = new Date(); + @DateType() + added: Date = new Date(); } export class JobTask { - @Class(JobTaskQueue) - queue: JobTaskQueue = new JobTaskQueue(); + @Class(JobTaskQueue) + queue: JobTaskQueue = new JobTaskQueue(); - @StringType() - name: string; + @StringType() + name: string; - @Class(JobDocker) - docker: JobDocker = new JobDocker(); + @Class(JobDocker) + docker: JobDocker = new JobDocker(); - @Class(JobDockerImage) - dockerImage: JobDockerImage = new JobDockerImage(); + @Class(JobDockerImage) + dockerImage: JobDockerImage = new JobDockerImage(); - // exitCodes: { [key: string]: number } = {}; + // exitCodes: { [key: string]: number } = {}; - // @UUIDType() - // server: string | null = null; + // @UUIDType() + // server: string | null = null; - @EnumType(JobTaskStatus) - status: JobTaskStatus = JobTaskStatus.pending; + @EnumType(JobTaskStatus) + status: JobTaskStatus = JobTaskStatus.pending; - // @Class(JobAssignedResources) - // assignedResources: JobAssignedResources = new JobAssignedResources; + // @Class(JobAssignedResources) + // assignedResources: JobAssignedResources = new JobAssignedResources; - @DateType() - assigned: Date | null = null; + @DateType() + assigned: Date | null = null; - @DateType() - started: Date | null = null; + @DateType() + started: Date | null = null; - @DateType() - ended: Date | null = null; + @DateType() + ended: Date | null = null; - @ClassMap(JobTaskInstance) - private instances: { [name: string]: JobTaskInstance } = {}; + @ClassMap(JobTaskInstance) + private instances: { [name: string]: JobTaskInstance } = {}; - constructor(name: string, replicas: number) { - this.name = name; + constructor(name: string, replicas: number) { + this.name = name; - for (let i = 1; i <= replicas; i++) { - this.instances[this.name + '_' + i] = new JobTaskInstance(i); + for (let i = 1; i <= replicas; i++) { + this.instances[this.name + '_' + i] = new JobTaskInstance(i); + } } - } } @Entity('job', 'jobs') export class Job { - @ID() - @UUIDType() - id: string; + @ID() + @UUIDType() + id: string; - @UUIDType() - project: string; + @UUIDType() + project: string; - @NumberType() - number: number = 0; + @NumberType() + number: number = 0; - @NumberType() - version: number = 1; + @NumberType() + version: number = 1; - //obsolete - alive: boolean = false; + //obsolete + alive: boolean = false; - @DateType() - created: Date = new Date(); + @DateType() + created: Date = new Date(); - @DateType() - updated: Date = new Date(); + @DateType() + updated: Date = new Date(); - // exitCode = 0; - @StringType() - author?: string; + // exitCode = 0; + @StringType() + author?: string; - @Class(JobConfig) - config: JobConfig = new JobConfig(); + @Class(JobConfig) + config: JobConfig = new JobConfig(); - @Class(JobGit) - @Optional() - git?: JobGit; + @Class(JobGit) + @Optional() + git?: JobGit; - @StringType() - configFile?: string; + @StringType() + configFile?: string; - @EnumType(JobStatus) - status: JobStatus = JobStatus.creating; + @EnumType(JobStatus) + status: JobStatus = JobStatus.creating; - @StringType() - title: string = ''; + @StringType() + title: string = ''; - @ClassMap(JobTask) - tasks: { [name: string]: JobTask } = {}; + @ClassMap(JobTask) + tasks: { [name: string]: JobTask } = {}; - @BooleanType() - runOnCluster: boolean = false; + @BooleanType() + runOnCluster: boolean = false; - @DateType() - assigned: Date | null = null; + @DateType() + assigned: Date | null = null; - @DateType() - started: Date | null = null; + @DateType() + started: Date | null = null; - @DateType() - ended: Date | null = null; + @DateType() + ended: Date | null = null; - @DateType() - ping: Date | null = null; + @DateType() + ping: Date | null = null; - //aka epochs - @NumberType() - iteration: number = 0; + //aka epochs + @NumberType() + iteration: number = 0; - @NumberType() - iterations: number = 0; + @NumberType() + iterations: number = 0; - @NumberType() - secondsPerIteration: number = 0; + @NumberType() + secondsPerIteration: number = 0; - //aka batches - @NumberType() - step: number = 0; + //aka batches + @NumberType() + step: number = 0; - @NumberType() - steps: number = 0; + @NumberType() + steps: number = 0; - @StringType() - stepLabel: string = 'step'; + @StringType() + stepLabel: string = 'step'; - /** - * ETA in seconds. Time left. - */ - @NumberType() - eta: number = 0; + /** + * ETA in seconds. Time left. + */ + @NumberType() + eta: number = 0; - @NumberType() - speed: number = 0; + @NumberType() + speed: number = 0; - @StringType() - speedLabel: string = 'sample/s'; + @StringType() + speedLabel: string = 'sample/s'; - @ClassMap(Channel) - channels: { [name: string]: Channel } = {}; + @ClassMap(Channel) + channels: { [name: string]: Channel } = {}; - constructor(id: string, project: string) { - this.id = id; - this.project = project; - } + constructor(id: string, project: string) { + this.id = id; + this.project = project; + } } diff --git a/packages/core/tests/class-to.spec.ts b/packages/core/tests/class-to.spec.ts index 112bd2bdd..04e5a3e9d 100644 --- a/packages/core/tests/class-to.spec.ts +++ b/packages/core/tests/class-to.spec.ts @@ -1,55 +1,55 @@ import 'jest-extended'; import 'reflect-metadata'; import { - CollectionWrapper, - SimpleModel, - StringCollectionWrapper, - SubModel, + CollectionWrapper, + SimpleModel, + StringCollectionWrapper, + SubModel, } from './entities'; import { classToPlain, partialClassToPlain } from '../src/mapper'; test('class-to test simple model', () => { - expect(() => { - const instance = classToPlain(SimpleModel, { - id: '21313', - name: 'Hi', - }); - }).toThrow(`Could not classToPlain since target is not a class instance`); + expect(() => { + const instance = classToPlain(SimpleModel, { + id: '21313', + name: 'Hi', + }); + }).toThrow(`Could not classToPlain since target is not a class instance`); }); test('partial', () => { - const i = new SimpleModel('Hi'); - i.children.push(new SubModel('Foo')); + const i = new SimpleModel('Hi'); + i.children.push(new SubModel('Foo')); - const plain = partialClassToPlain(SimpleModel, { - name: i.name, - children: i.children, - }); + const plain = partialClassToPlain(SimpleModel, { + name: i.name, + children: i.children, + }); - expect(plain).not.toBeInstanceOf(SimpleModel); - expect(plain['id']).toBeUndefined(); - expect(plain['type']).toBeUndefined(); - expect(plain.name).toBe('Hi'); - expect(plain.children[0].label).toBe('Foo'); + expect(plain).not.toBeInstanceOf(SimpleModel); + expect(plain['id']).toBeUndefined(); + expect(plain['type']).toBeUndefined(); + expect(plain.name).toBe('Hi'); + expect(plain.children[0].label).toBe('Foo'); }); test('partial 2', () => { - const i = new SimpleModel('Hi'); - i.children.push(new SubModel('Foo')); + const i = new SimpleModel('Hi'); + i.children.push(new SubModel('Foo')); - const plain = partialClassToPlain(SimpleModel, { - 'children.0': i.children[0], - stringChildrenCollection: new StringCollectionWrapper(['Foo', 'Bar']), - childrenCollection: new CollectionWrapper([new SubModel('Bar3')]), - 'childrenCollection.1': new SubModel('Bar4'), - 'stringChildrenCollection.0': 'Bar2', - 'childrenCollection.2.label': 'Bar5', - }); + const plain = partialClassToPlain(SimpleModel, { + 'children.0': i.children[0], + stringChildrenCollection: new StringCollectionWrapper(['Foo', 'Bar']), + childrenCollection: new CollectionWrapper([new SubModel('Bar3')]), + 'childrenCollection.1': new SubModel('Bar4'), + 'stringChildrenCollection.0': 'Bar2', + 'childrenCollection.2.label': 'Bar5', + }); - expect(plain['children.0'].label).toBe('Foo'); - expect(plain['stringChildrenCollection']).toEqual(['Foo', 'Bar']); - expect(plain['stringChildrenCollection.0']).toEqual('Bar2'); - expect(plain['childrenCollection']).toEqual([{ label: 'Bar3' }]); - expect(plain['childrenCollection.1']).toEqual({ label: 'Bar4' }); - expect(plain['childrenCollection.2.label']).toEqual('Bar5'); + expect(plain['children.0'].label).toBe('Foo'); + expect(plain['stringChildrenCollection']).toEqual(['Foo', 'Bar']); + expect(plain['stringChildrenCollection.0']).toEqual('Bar2'); + expect(plain['childrenCollection']).toEqual([{ label: 'Bar3' }]); + expect(plain['childrenCollection.1']).toEqual({ label: 'Bar4' }); + expect(plain['childrenCollection.2.label']).toEqual('Bar5'); }); diff --git a/packages/core/tests/decorator.spec.ts b/packages/core/tests/decorator.spec.ts index 887d8f209..ead57bff2 100644 --- a/packages/core/tests/decorator.spec.ts +++ b/packages/core/tests/decorator.spec.ts @@ -1,307 +1,307 @@ import 'jest-extended'; import { - AnyType, - ArrayType, - BooleanType, - Class, - DatabaseName, - Entity, - ID, - MapType, - MongoIdType, - StringType, - getCollectionName, - getDatabaseName, - getEntityName, - getReflectionType, - isArrayType, - isMapType, - plainToClass, - ClassMap, - ClassArray, - ClassCircular, - ClassArrayCircular, - ClassMapCircular, - ParentReference, - getParentReferenceClass, - BinaryType, - classToPlain, + AnyType, + ArrayType, + BooleanType, + Class, + DatabaseName, + Entity, + ID, + MapType, + MongoIdType, + StringType, + getCollectionName, + getDatabaseName, + getEntityName, + getReflectionType, + isArrayType, + isMapType, + plainToClass, + ClassMap, + ClassArray, + ClassCircular, + ClassArrayCircular, + ClassMapCircular, + ParentReference, + getParentReferenceClass, + BinaryType, + classToPlain, } from '../'; import { Buffer } from 'buffer'; test('test entity database', async () => { - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing1') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - class Child extends DifferentDataBase {} - - @Entity('DifferentDataBase2', 'differentCollection2') - @DatabaseName('testing2') - class Child2 extends DifferentDataBase {} - - expect(getDatabaseName(DifferentDataBase)).toBe('testing1'); - expect(getEntityName(DifferentDataBase)).toBe('DifferentDataBase'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(getDatabaseName(Child2)).toBe('testing2'); - expect(getEntityName(Child2)).toBe('DifferentDataBase2'); - expect(getCollectionName(Child2)).toBe('differentCollection2'); - - expect(getDatabaseName(Child)).toBe('testing1'); - expect(getEntityName(Child)).toBe('DifferentDataBase'); - expect(getCollectionName(Child)).toBe('differentCollection'); + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing1') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + class Child extends DifferentDataBase {} + + @Entity('DifferentDataBase2', 'differentCollection2') + @DatabaseName('testing2') + class Child2 extends DifferentDataBase {} + + expect(getDatabaseName(DifferentDataBase)).toBe('testing1'); + expect(getEntityName(DifferentDataBase)).toBe('DifferentDataBase'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(getDatabaseName(Child2)).toBe('testing2'); + expect(getEntityName(Child2)).toBe('DifferentDataBase2'); + expect(getCollectionName(Child2)).toBe('differentCollection2'); + + expect(getDatabaseName(Child)).toBe('testing1'); + expect(getEntityName(Child)).toBe('DifferentDataBase'); + expect(getCollectionName(Child)).toBe('differentCollection'); }); test('test no entity throw error', () => { - expect(() => { - class Model {} - getEntityName(Model); - }).toThrowError('No @Entity() defined for class class Model'); - - expect(() => { - class Model {} - getCollectionName(Model); - }).toThrowError('No @Entity() defined for class class Model'); + expect(() => { + class Model {} + getEntityName(Model); + }).toThrowError('No @Entity() defined for class class Model'); + + expect(() => { + class Model {} + getCollectionName(Model); + }).toThrowError('No @Entity() defined for class class Model'); }); test('test decorator errors', () => { - class Sub {} + class Sub {} + + expect(() => { + class Model { + @Class(undefined) + sub?: Sub; + } + }).toThrowError('Model::sub has @Class but argument is empty.'); + + expect(() => { + class Model { + @ClassMap(undefined) + sub?: Sub; + } + }).toThrowError('Model::sub has @ClassMap but argument is empty.'); + + expect(() => { + class Model { + @ClassArray(undefined) + sub?: Sub; + } + }).toThrowError('Model::sub has @ClassArray but argument is empty.'); +}); - expect(() => { - class Model { - @Class(undefined) - sub?: Sub; +test('test decorator ParentReference without class', () => { + class Sub {} + + expect(() => { + class Model { + @ParentReference() + sub?: Sub; + } + getParentReferenceClass(Model, 'sub'); + }).toThrowError('Model::sub has @ParentReference but no @Class defined.'); +}); + +test('test decorator circular', () => { + class Sub {} + + { + class Model { + @ClassCircular(() => Sub) + sub?: Sub; + } + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); } - }).toThrowError('Model::sub has @Class but argument is empty.'); - expect(() => { - class Model { - @ClassMap(undefined) - sub?: Sub; + { + class Model { + @ClassMapCircular(() => Sub) + sub?: Sub; + } + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); + expect(isMapType(Model, 'sub')).toBeTrue(); } - }).toThrowError('Model::sub has @ClassMap but argument is empty.'); - expect(() => { - class Model { - @ClassArray(undefined) - sub?: Sub; + { + class Model { + @ClassArrayCircular(() => Sub) + sub?: Sub; + } + expect(getReflectionType(Model, 'sub')).toEqual({ + type: 'class', + typeValue: Sub, + }); + expect(isArrayType(Model, 'sub')).toBeTrue(); } - }).toThrowError('Model::sub has @ClassArray but argument is empty.'); }); -test('test decorator ParentReference without class', () => { - class Sub {} +test('test properties', () => { + class DataValue {} + class DataValue2 {} - expect(() => { + @Entity('Model') class Model { - @ParentReference() - sub?: Sub; + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + + @Class(DataValue) + data?: DataValue; } - getParentReferenceClass(Model, 'sub'); - }).toThrowError('Model::sub has @ParentReference but no @Class defined.'); -}); -test('test decorator circular', () => { - class Sub {} + @Entity('SubModel') + class SubModel extends Model { + @Class(DataValue2) + data2?: DataValue2; + } - { - class Model { - @ClassCircular(() => Sub) - sub?: Sub; + { + const { type, typeValue } = getReflectionType(Model, '_id'); + expect(type).toBe('objectId'); + expect(typeValue).toBeNull(); } - expect(getReflectionType(Model, 'sub')).toEqual({ - type: 'class', - typeValue: Sub, - }); - } - { - class Model { - @ClassMapCircular(() => Sub) - sub?: Sub; + { + const { type, typeValue } = getReflectionType(Model, 'data'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue); } - expect(getReflectionType(Model, 'sub')).toEqual({ - type: 'class', - typeValue: Sub, - }); - expect(isMapType(Model, 'sub')).toBeTrue(); - } - - { - class Model { - @ClassArrayCircular(() => Sub) - sub?: Sub; + + { + const { type, typeValue } = getReflectionType(Model, 'data2'); + expect(type).toBeNull(); + expect(typeValue).toBeNull(); + } + + { + const { type, typeValue } = getReflectionType(SubModel, '_id'); + expect(type).toBe('objectId'); + expect(typeValue).toBeNull(); + } + { + const { type, typeValue } = getReflectionType(SubModel, 'data'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue); + } + { + const { type, typeValue } = getReflectionType(SubModel, 'data2'); + expect(type).toBe('class'); + expect(typeValue).toBe(DataValue2); } - expect(getReflectionType(Model, 'sub')).toEqual({ - type: 'class', - typeValue: Sub, - }); - expect(isArrayType(Model, 'sub')).toBeTrue(); - } }); -test('test properties', () => { - class DataValue {} - class DataValue2 {} - - @Entity('Model') - class Model { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - - @Class(DataValue) - data?: DataValue; - } - - @Entity('SubModel') - class SubModel extends Model { - @Class(DataValue2) - data2?: DataValue2; - } - - { - const { type, typeValue } = getReflectionType(Model, '_id'); - expect(type).toBe('objectId'); - expect(typeValue).toBeNull(); - } +test('more decorator', () => { + class Model { + @BooleanType() + bool: boolean = false; - { - const { type, typeValue } = getReflectionType(Model, 'data'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue); - } + @AnyType() + whatever: any; + } - { - const { type, typeValue } = getReflectionType(Model, 'data2'); - expect(type).toBeNull(); - expect(typeValue).toBeNull(); - } + { + const instance = plainToClass(Model, { + bool: 'wow', + whatever: { any: false }, + }); - { - const { type, typeValue } = getReflectionType(SubModel, '_id'); - expect(type).toBe('objectId'); - expect(typeValue).toBeNull(); - } - { - const { type, typeValue } = getReflectionType(SubModel, 'data'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue); - } - { - const { type, typeValue } = getReflectionType(SubModel, 'data2'); - expect(type).toBe('class'); - expect(typeValue).toBe(DataValue2); - } -}); + expect(instance.bool).toBeFalse(); + expect(instance.whatever).toEqual({ any: false }); + } -test('more decorator', () => { - class Model { - @BooleanType() - bool: boolean = false; - - @AnyType() - whatever: any; - } - - { - const instance = plainToClass(Model, { - bool: 'wow', - whatever: { any: false }, - }); - - expect(instance.bool).toBeFalse(); - expect(instance.whatever).toEqual({ any: false }); - } - - { - const instance = plainToClass(Model, { - bool: 'true', - }); - expect(instance.bool).toBeTrue(); - } - - { - const instance = plainToClass(Model, { - bool: '1', - }); - expect(instance.bool).toBeTrue(); - } - - { - const instance = plainToClass(Model, { - bool: 1, - }); - expect(instance.bool).toBeTrue(); - } - - { - const instance = plainToClass(Model, { - bool: 'false', - }); - expect(instance.bool).toBeFalse(); - } - - { - const instance = plainToClass(Model, { - bool: '0', - }); - expect(instance.bool).toBeFalse(); - } - - { - const instance = plainToClass(Model, { - bool: 0, - }); - expect(instance.bool).toBeFalse(); - } + { + const instance = plainToClass(Model, { + bool: 'true', + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: '1', + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: 1, + }); + expect(instance.bool).toBeTrue(); + } + + { + const instance = plainToClass(Model, { + bool: 'false', + }); + expect(instance.bool).toBeFalse(); + } + + { + const instance = plainToClass(Model, { + bool: '0', + }); + expect(instance.bool).toBeFalse(); + } + + { + const instance = plainToClass(Model, { + bool: 0, + }); + expect(instance.bool).toBeFalse(); + } }); test('more array/map', () => { - class Model { - @BooleanType() - @ArrayType() - bools?: boolean[]; - - @AnyType() - @MapType() - whatever?: any[]; - } - - expect(isArrayType(Model, 'bools')).toBeTrue(); - expect(isMapType(Model, 'whatever')).toBeTrue(); + class Model { + @BooleanType() + @ArrayType() + bools?: boolean[]; + + @AnyType() + @MapType() + whatever?: any[]; + } + + expect(isArrayType(Model, 'bools')).toBeTrue(); + expect(isMapType(Model, 'whatever')).toBeTrue(); }); test('binary', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } - - const { type, typeValue } = getReflectionType(Model, 'preview'); - expect(type).toBe('binary'); - expect(typeValue).toBeNull(); - - const i = new Model(); - expect(i.preview.toString('utf8')).toBe('FooBar'); - - const plain = classToPlain(Model, i); - expect(plain.preview).toBe('Rm9vQmFy'); - expect(plain.preview).toBe(new Buffer('FooBar', 'utf8').toString('base64')); - - const back = plainToClass(Model, plain); - expect(back.preview).toBeInstanceOf(Buffer); - expect(back.preview.toString('utf8')).toBe('FooBar'); - expect(back.preview.length).toBe(6); + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } + + const { type, typeValue } = getReflectionType(Model, 'preview'); + expect(type).toBe('binary'); + expect(typeValue).toBeNull(); + + const i = new Model(); + expect(i.preview.toString('utf8')).toBe('FooBar'); + + const plain = classToPlain(Model, i); + expect(plain.preview).toBe('Rm9vQmFy'); + expect(plain.preview).toBe(new Buffer('FooBar', 'utf8').toString('base64')); + + const back = plainToClass(Model, plain); + expect(back.preview).toBeInstanceOf(Buffer); + expect(back.preview.toString('utf8')).toBe('FooBar'); + expect(back.preview.length).toBe(6); }); diff --git a/packages/core/tests/entities.ts b/packages/core/tests/entities.ts index 072348752..56eced63c 100644 --- a/packages/core/tests/entities.ts +++ b/packages/core/tests/entities.ts @@ -1,151 +1,151 @@ import { - DateType, - Entity, - EnumType, - ID, - NumberType, - StringType, - ClassArray, - ClassMap, - UUIDType, - uuid, - Exclude, - MongoIdType, - Decorator, - Class, - ExcludeToMongo, - ExcludeToPlain, - ArrayType, - BooleanType, + DateType, + Entity, + EnumType, + ID, + NumberType, + StringType, + ClassArray, + ClassMap, + UUIDType, + uuid, + Exclude, + MongoIdType, + Decorator, + Class, + ExcludeToMongo, + ExcludeToPlain, + ArrayType, + BooleanType, } from '..'; @Entity('sub') export class SubModel { - @StringType() - label: string; + @StringType() + label: string; - constructorUsed = false; + constructorUsed = false; - constructor(label: string) { - this.label = label; - this.constructorUsed = true; - } + constructor(label: string) { + this.label = label; + this.constructorUsed = true; + } } export enum Plan { - DEFAULT, - PRO, - ENTERPRISE, + DEFAULT, + PRO, + ENTERPRISE, } export const now = new Date(); export class CollectionWrapper { - @ClassArray(SubModel) - @Decorator() - public items: SubModel[]; + @ClassArray(SubModel) + @Decorator() + public items: SubModel[]; - constructor(items: SubModel[]) { - this.items = items; - } + constructor(items: SubModel[]) { + this.items = items; + } - public add(item: SubModel) { - this.items.push(item); - } + public add(item: SubModel) { + this.items.push(item); + } } export class StringCollectionWrapper { - @Decorator() - @StringType() - @ArrayType() - public items: string[]; - - constructor(items: string[]) { - this.items = items; - } - - public add(item: string) { - this.items.push(item); - } + @Decorator() + @StringType() + @ArrayType() + public items: string[]; + + constructor(items: string[]) { + this.items = items; + } + + public add(item: string) { + this.items.push(item); + } } @Entity('SimpleModel') export class SimpleModel { - @ID() - @UUIDType() - id: string = uuid(); + @ID() + @UUIDType() + id: string = uuid(); - @StringType() - name: string; + @StringType() + name: string; - @NumberType() - type: number = 0; + @NumberType() + type: number = 0; - @BooleanType() - yesNo: boolean = false; + @BooleanType() + yesNo: boolean = false; - @EnumType(Plan) - plan: Plan = Plan.DEFAULT; + @EnumType(Plan) + plan: Plan = Plan.DEFAULT; - @DateType() - created: Date = now; + @DateType() + created: Date = now; - @ArrayType() - @StringType() - types: string[] = []; + @ArrayType() + @StringType() + types: string[] = []; - @ClassArray(SubModel) - children: SubModel[] = []; + @ClassArray(SubModel) + children: SubModel[] = []; - @ClassMap(SubModel) - childrenMap: { [key: string]: SubModel } = {}; + @ClassMap(SubModel) + childrenMap: { [key: string]: SubModel } = {}; - @Class(CollectionWrapper) - childrenCollection: CollectionWrapper = new CollectionWrapper([]); + @Class(CollectionWrapper) + childrenCollection: CollectionWrapper = new CollectionWrapper([]); - @Class(StringCollectionWrapper) - stringChildrenCollection: StringCollectionWrapper = new StringCollectionWrapper( - [] - ); + @Class(StringCollectionWrapper) + stringChildrenCollection: StringCollectionWrapper = new StringCollectionWrapper( + [] + ); - notMapped: { [key: string]: any } = {}; + notMapped: { [key: string]: any } = {}; - @Exclude() - @StringType() - excluded: string = 'default'; + @Exclude() + @StringType() + excluded: string = 'default'; - @ExcludeToMongo() - @StringType() - excludedForMongo: string = 'excludedForMongo'; + @ExcludeToMongo() + @StringType() + excludedForMongo: string = 'excludedForMongo'; - @ExcludeToPlain() - @StringType() - excludedForPlain: string = 'excludedForPlain'; + @ExcludeToPlain() + @StringType() + excludedForPlain: string = 'excludedForPlain'; - constructor(name: string) { - this.name = name; - } + constructor(name: string) { + this.name = name; + } } @Entity('SuperSimple') export class SuperSimple { - @ID() - @MongoIdType() - _id?: string; + @ID() + @MongoIdType() + _id?: string; - @StringType() - name?: string; + @StringType() + name?: string; } @Entity('BaseClass') export class BaseClass { - @ID() - @MongoIdType() - _id?: string; + @ID() + @MongoIdType() + _id?: string; } @Entity('ChildClass') export class ChildClass extends BaseClass { - @StringType() - name?: string; + @StringType() + name?: string; } diff --git a/packages/core/tests/plain-to.spec.ts b/packages/core/tests/plain-to.spec.ts index 16e9e652f..46321de42 100644 --- a/packages/core/tests/plain-to.spec.ts +++ b/packages/core/tests/plain-to.spec.ts @@ -1,17 +1,17 @@ import 'jest-extended'; import 'reflect-metadata'; import { - Plan, - SimpleModel, - StringCollectionWrapper, - SubModel, + Plan, + SimpleModel, + StringCollectionWrapper, + SubModel, } from './entities'; import { - getReflectionType, - getResolvedReflection, - partialPlainToClass, - plainToClass, - Types, + getReflectionType, + getResolvedReflection, + partialPlainToClass, + plainToClass, + Types, } from '../src/mapper'; import { ClassType, EnumType } from '..'; import { DocumentClass } from './document-scenario/DocumentClass'; @@ -19,375 +19,388 @@ import { PageClass } from './document-scenario/PageClass'; import { PageCollection } from './document-scenario/PageCollection'; test('getResolvedReflection simple', () => { - expect(getResolvedReflection(SimpleModel, 'id')!.resolvedClassType).toBe( - SimpleModel - ); - expect(getResolvedReflection(SimpleModel, 'id')!.resolvedPropertyName).toBe( - 'id' - ); - expect(getResolvedReflection(SimpleModel, 'id')!.type).toBe('uuid'); - expect(getResolvedReflection(SimpleModel, 'id')!.typeValue).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'id')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'id')!.map).toBe(false); - - expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedClassType).toBe( - SimpleModel - ); - expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedPropertyName).toBe( - 'plan' - ); - expect(getResolvedReflection(SimpleModel, 'plan')!.type).toBe('enum'); - expect(getResolvedReflection(SimpleModel, 'plan')!.typeValue).toBe(Plan); - expect(getResolvedReflection(SimpleModel, 'plan')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'plan')!.map).toBe(false); - - expect( - getResolvedReflection(SimpleModel, 'children')!.resolvedClassType - ).toBe(SimpleModel); - expect( - getResolvedReflection(SimpleModel, 'children')!.resolvedPropertyName - ).toBe('children'); - expect(getResolvedReflection(SimpleModel, 'children')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'children')!.typeValue).toBe( - SubModel - ); - expect(getResolvedReflection(SimpleModel, 'children')!.array).toBe(true); - expect(getResolvedReflection(SimpleModel, 'children')!.map).toBe(false); - - expect( - getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedClassType - ).toBe(SimpleModel); - expect( - getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedPropertyName - ).toBe('childrenMap'); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.type).toBe('class'); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.typeValue).toBe( - SubModel - ); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.array).toBe(false); - expect(getResolvedReflection(SimpleModel, 'childrenMap')!.map).toBe(true); + expect(getResolvedReflection(SimpleModel, 'id')!.resolvedClassType).toBe( + SimpleModel + ); + expect(getResolvedReflection(SimpleModel, 'id')!.resolvedPropertyName).toBe( + 'id' + ); + expect(getResolvedReflection(SimpleModel, 'id')!.type).toBe('uuid'); + expect(getResolvedReflection(SimpleModel, 'id')!.typeValue).toBeNull(); + expect(getResolvedReflection(SimpleModel, 'id')!.array).toBe(false); + expect(getResolvedReflection(SimpleModel, 'id')!.map).toBe(false); + + expect(getResolvedReflection(SimpleModel, 'plan')!.resolvedClassType).toBe( + SimpleModel + ); + expect( + getResolvedReflection(SimpleModel, 'plan')!.resolvedPropertyName + ).toBe('plan'); + expect(getResolvedReflection(SimpleModel, 'plan')!.type).toBe('enum'); + expect(getResolvedReflection(SimpleModel, 'plan')!.typeValue).toBe(Plan); + expect(getResolvedReflection(SimpleModel, 'plan')!.array).toBe(false); + expect(getResolvedReflection(SimpleModel, 'plan')!.map).toBe(false); + + expect( + getResolvedReflection(SimpleModel, 'children')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'children')!.resolvedPropertyName + ).toBe('children'); + expect(getResolvedReflection(SimpleModel, 'children')!.type).toBe('class'); + expect(getResolvedReflection(SimpleModel, 'children')!.typeValue).toBe( + SubModel + ); + expect(getResolvedReflection(SimpleModel, 'children')!.array).toBe(true); + expect(getResolvedReflection(SimpleModel, 'children')!.map).toBe(false); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap')!.resolvedPropertyName + ).toBe('childrenMap'); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.type).toBe( + 'class' + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.typeValue).toBe( + SubModel + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.array).toBe( + false + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap')!.map).toBe(true); }); test('getResolvedReflection deep', () => { - expect( - getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedClassType - ).toBe(SubModel); - expect( - getResolvedReflection(SimpleModel, 'children.0.label')!.resolvedPropertyName - ).toBe('label'); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.type).toBe( - 'string' - ); - expect( - getResolvedReflection(SimpleModel, 'children.0.label')!.typeValue - ).toBeNull(); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.array).toBe( - false - ); - expect(getResolvedReflection(SimpleModel, 'children.0.label')!.map).toBe( - false - ); - - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! - .resolvedClassType - ).toBe(SubModel); - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! - .resolvedPropertyName - ).toBe('label'); - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.type - ).toBe('string'); - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.typeValue - ).toBeNull(); - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.array - ).toBe(false); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.map).toBe( - false - ); - - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo.unknown') - ).toBeNull(); - - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedClassType - ).toBe(SimpleModel); - expect( - getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedPropertyName - ).toBe('childrenMap'); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.type).toBe( - 'class' - ); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.typeValue).toBe( - SubModel - ); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.array).toBe( - false - ); - expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.map).toBe( - false - ); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')! + .resolvedClassType + ).toBe(SubModel); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')! + .resolvedPropertyName + ).toBe('label'); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.type).toBe( + 'string' + ); + expect( + getResolvedReflection(SimpleModel, 'children.0.label')!.typeValue + ).toBeNull(); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.array).toBe( + false + ); + expect(getResolvedReflection(SimpleModel, 'children.0.label')!.map).toBe( + false + ); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! + .resolvedClassType + ).toBe(SubModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')! + .resolvedPropertyName + ).toBe('label'); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.type + ).toBe('string'); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.typeValue + ).toBeNull(); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.array + ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.label')!.map + ).toBe(false); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo.unknown') + ).toBeNull(); + + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo')!.resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo')! + .resolvedPropertyName + ).toBe('childrenMap'); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.type).toBe( + 'class' + ); + expect( + getResolvedReflection(SimpleModel, 'childrenMap.foo')!.typeValue + ).toBe(SubModel); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.array).toBe( + false + ); + expect(getResolvedReflection(SimpleModel, 'childrenMap.foo')!.map).toBe( + false + ); }); test('getResolvedReflection deep decorator', () => { - expect(getResolvedReflection(SimpleModel, 'childrenCollection.0')!).toEqual({ - resolvedClassType: SimpleModel, - resolvedPropertyName: 'childrenCollection', - type: 'class', - typeValue: SubModel, - array: false, - map: false, - }); - - expect( - getResolvedReflection(SimpleModel, 'childrenCollection.0.label')! - ).toEqual({ - resolvedClassType: SubModel, - resolvedPropertyName: 'label', - type: 'string', - typeValue: null, - array: false, - map: false, - }); + expect(getResolvedReflection(SimpleModel, 'childrenCollection.0')!).toEqual( + { + resolvedClassType: SimpleModel, + resolvedPropertyName: 'childrenCollection', + type: 'class', + typeValue: SubModel, + array: false, + map: false, + } + ); + + expect( + getResolvedReflection(SimpleModel, 'childrenCollection.0.label')! + ).toEqual({ + resolvedClassType: SubModel, + resolvedPropertyName: 'label', + type: 'string', + typeValue: null, + array: false, + map: false, + }); }); test('getResolvedReflection decorator string', () => { - expect(getReflectionType(SimpleModel, 'stringChildrenCollection')).toEqual({ - type: 'class', - typeValue: StringCollectionWrapper, - }); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')! - .resolvedClassType - ).toBe(SimpleModel); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')! - .resolvedPropertyName - ).toBe('stringChildrenCollection'); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.type - ).toBe('class'); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.typeValue - ).toBe(StringCollectionWrapper); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.array - ).toBe(false); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.map - ).toBe(false); + expect(getReflectionType(SimpleModel, 'stringChildrenCollection')).toEqual({ + type: 'class', + typeValue: StringCollectionWrapper, + }); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')! + .resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')! + .resolvedPropertyName + ).toBe('stringChildrenCollection'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.type + ).toBe('class'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')! + .typeValue + ).toBe(StringCollectionWrapper); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.array + ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection')!.map + ).toBe(false); }); test('getResolvedReflection deep decorator string', () => { - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! - .resolvedClassType - ).toBe(SimpleModel); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! - .resolvedPropertyName - ).toBe('stringChildrenCollection'); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.type - ).toBe('string'); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.typeValue - ).toBeNull(); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.array - ).toBe(false); - expect( - getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.map - ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! + .resolvedClassType + ).toBe(SimpleModel); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! + .resolvedPropertyName + ).toBe('stringChildrenCollection'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.type + ).toBe('string'); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')! + .typeValue + ).toBeNull(); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.array + ).toBe(false); + expect( + getResolvedReflection(SimpleModel, 'stringChildrenCollection.0')!.map + ).toBe(false); }); test('plain-to test simple model', () => { - const instance = plainToClass(SimpleModel, { - //todo, this should throw an error - id: '21313', - name: 'Hi', - }); - - expect(instance.id).toBe('21313'); - expect(instance.name).toBe('Hi'); + const instance = plainToClass(SimpleModel, { + //todo, this should throw an error + id: '21313', + name: 'Hi', + }); + + expect(instance.id).toBe('21313'); + expect(instance.name).toBe('Hi'); }); test('partial', () => { - const instance = partialPlainToClass(SimpleModel, { - name: 'Hi', - children: [{ label: 'Foo' }], - }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance.children[0]).toBeInstanceOf(SubModel); - expect(instance.children[0].label).toBe('Foo'); + const instance = partialPlainToClass(SimpleModel, { + name: 'Hi', + children: [{ label: 'Foo' }], + }); + + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance.children[0]).toBeInstanceOf(SubModel); + expect(instance.children[0].label).toBe('Foo'); }); test('partial 2', () => { - const instance = partialPlainToClass(SimpleModel, { - name: 'Hi', - 'children.0.label': 'Foo', - }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance['children.0.label']).toBe('Foo'); - - expect( - partialPlainToClass(SimpleModel, { - 'children.0.label': 2, - }) - ).toEqual({ 'children.0.label': '2' }); - - const i2 = partialPlainToClass(SimpleModel, { - 'children.0': { label: 3 }, - }); - expect(i2['children.0']).toBeInstanceOf(SubModel); - expect(i2['children.0'].label).toBe('3'); + const instance = partialPlainToClass(SimpleModel, { + name: 'Hi', + 'children.0.label': 'Foo', + }); + + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance['children.0.label']).toBe('Foo'); + + expect( + partialPlainToClass(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i2 = partialPlainToClass(SimpleModel, { + 'children.0': { label: 3 }, + }); + expect(i2['children.0']).toBeInstanceOf(SubModel); + expect(i2['children.0'].label).toBe('3'); }); test('partial 3', () => { - const i2 = partialPlainToClass(SimpleModel, { - children: [{ label: 3 }], - }); - expect(i2['children'][0]).toBeInstanceOf(SubModel); - expect(i2['children'][0].label).toBe('3'); + const i2 = partialPlainToClass(SimpleModel, { + children: [{ label: 3 }], + }); + expect(i2['children'][0]).toBeInstanceOf(SubModel); + expect(i2['children'][0].label).toBe('3'); }); test('partial 4', () => { - const i2 = partialPlainToClass(SimpleModel, { - 'stringChildrenCollection.0': 4, - }); - expect(i2['stringChildrenCollection.0']).toBe('4'); + const i2 = partialPlainToClass(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i2['stringChildrenCollection.0']).toBe('4'); }); test('partial 5', () => { - const i2 = partialPlainToClass(SimpleModel, { - 'childrenMap.foo.label': 5, - }); - expect(i2['childrenMap.foo.label']).toBe('5'); + const i2 = partialPlainToClass(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i2['childrenMap.foo.label']).toBe('5'); }); test('partial 6', () => { - const i = partialPlainToClass(SimpleModel, { - types: [6, 7], - }); - expect(i['types']).toEqual(['6', '7']); + const i = partialPlainToClass(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); }); test('partial 7', () => { - const i = partialPlainToClass(SimpleModel, { - 'types.0': [7], - }); - expect(i['types.0']).toEqual('7'); + const i = partialPlainToClass(SimpleModel, { + 'types.0': [7], + }); + expect(i['types.0']).toEqual('7'); }); test('partial document', () => { - const docParent = new DocumentClass(); - const document = partialPlainToClass( - DocumentClass, - { - 'pages.0.name': 5, - 'pages.0.children.0.name': 6, - 'pages.0.children': [{ name: 7 }], - }, - [docParent] - ); - expect(document['pages.0.name']).toBe('5'); - expect(document['pages.0.children.0.name']).toBe('6'); - expect(document['pages.0.children']).toBeInstanceOf(PageCollection); - expect(document['pages.0.children'].get(0).name).toBe('7'); - - expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); - - expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'children', - type: 'class', - typeValue: PageCollection, - array: false, - map: false, - }); - - expect( - getResolvedReflection(DocumentClass, 'pages.0.children.0.name') - ).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); + const docParent = new DocumentClass(); + const document = partialPlainToClass( + DocumentClass, + { + 'pages.0.name': 5, + 'pages.0.children.0.name': 6, + 'pages.0.children': [{ name: 7 }], + }, + [docParent] + ); + expect(document['pages.0.name']).toBe('5'); + expect(document['pages.0.children.0.name']).toBe('6'); + expect(document['pages.0.children']).toBeInstanceOf(PageCollection); + expect(document['pages.0.children'].get(0).name).toBe('7'); + + expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); + + expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'children', + type: 'class', + typeValue: PageCollection, + array: false, + map: false, + }); + + expect( + getResolvedReflection(DocumentClass, 'pages.0.children.0.name') + ).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); }); test('test enum labels', () => { - enum MyEnum { - first, - second, - third, - } - - class Model { - @EnumType(MyEnum) - enum: MyEnum = MyEnum.third; - } - - expect(plainToClass(Model, { enum: MyEnum.first }).enum).toBe(MyEnum.first); - expect(plainToClass(Model, { enum: MyEnum.second }).enum).toBe(MyEnum.second); - expect(plainToClass(Model, { enum: 0 }).enum).toBe(MyEnum.first); - expect(plainToClass(Model, { enum: 1 }).enum).toBe(MyEnum.second); - expect(plainToClass(Model, { enum: 2 }).enum).toBe(MyEnum.third); - - expect(() => { - expect(plainToClass(Model, { enum: 'first' }).enum).toBe(MyEnum.first); - }).toThrow('Invalid ENUM given in property enum: first, valid: 0,1,2'); - - class ModelWithLabels { - @EnumType(MyEnum, true) - enum: MyEnum = MyEnum.third; - } - expect(plainToClass(ModelWithLabels, { enum: MyEnum.first }).enum).toBe( - MyEnum.first - ); - expect(plainToClass(ModelWithLabels, { enum: MyEnum.second }).enum).toBe( - MyEnum.second - ); - expect(plainToClass(ModelWithLabels, { enum: 0 }).enum).toBe(MyEnum.first); - expect(plainToClass(ModelWithLabels, { enum: 1 }).enum).toBe(MyEnum.second); - expect(plainToClass(ModelWithLabels, { enum: 2 }).enum).toBe(MyEnum.third); - - expect(plainToClass(ModelWithLabels, { enum: 'first' }).enum).toBe( - MyEnum.first - ); - expect(plainToClass(ModelWithLabels, { enum: 'second' }).enum).toBe( - MyEnum.second - ); - expect(plainToClass(ModelWithLabels, { enum: 'third' }).enum).toBe( - MyEnum.third - ); - - expect(() => { - expect(plainToClass(ModelWithLabels, { enum: 'Hi' }).enum).toBe( - MyEnum.first + enum MyEnum { + first, + second, + third, + } + + class Model { + @EnumType(MyEnum) + enum: MyEnum = MyEnum.third; + } + + expect(plainToClass(Model, { enum: MyEnum.first }).enum).toBe(MyEnum.first); + expect(plainToClass(Model, { enum: MyEnum.second }).enum).toBe( + MyEnum.second + ); + expect(plainToClass(Model, { enum: 0 }).enum).toBe(MyEnum.first); + expect(plainToClass(Model, { enum: 1 }).enum).toBe(MyEnum.second); + expect(plainToClass(Model, { enum: 2 }).enum).toBe(MyEnum.third); + + expect(() => { + expect(plainToClass(Model, { enum: 'first' }).enum).toBe(MyEnum.first); + }).toThrow('Invalid ENUM given in property enum: first, valid: 0,1,2'); + + class ModelWithLabels { + @EnumType(MyEnum, true) + enum: MyEnum = MyEnum.third; + } + expect(plainToClass(ModelWithLabels, { enum: MyEnum.first }).enum).toBe( + MyEnum.first + ); + expect(plainToClass(ModelWithLabels, { enum: MyEnum.second }).enum).toBe( + MyEnum.second + ); + expect(plainToClass(ModelWithLabels, { enum: 0 }).enum).toBe(MyEnum.first); + expect(plainToClass(ModelWithLabels, { enum: 1 }).enum).toBe(MyEnum.second); + expect(plainToClass(ModelWithLabels, { enum: 2 }).enum).toBe(MyEnum.third); + + expect(plainToClass(ModelWithLabels, { enum: 'first' }).enum).toBe( + MyEnum.first + ); + expect(plainToClass(ModelWithLabels, { enum: 'second' }).enum).toBe( + MyEnum.second + ); + expect(plainToClass(ModelWithLabels, { enum: 'third' }).enum).toBe( + MyEnum.third + ); + + expect(() => { + expect(plainToClass(ModelWithLabels, { enum: 'Hi' }).enum).toBe( + MyEnum.first + ); + }).toThrow( + 'Invalid ENUM given in property enum: Hi, valid: 0,1,2,first,second,third' ); - }).toThrow( - 'Invalid ENUM given in property enum: Hi, valid: 0,1,2,first,second,third' - ); }); diff --git a/packages/core/tests/speed.spec.ts b/packages/core/tests/speed.spec.ts index ac412d19f..75f5eca11 100644 --- a/packages/core/tests/speed.spec.ts +++ b/packages/core/tests/speed.spec.ts @@ -1,164 +1,164 @@ import 'jest-extended'; import 'reflect-metadata'; import { - classToPlain, - getCachedParameterNames, - getRegisteredProperties, - plainToClass, + classToPlain, + getCachedParameterNames, + getRegisteredProperties, + plainToClass, } from '../src/mapper'; import { Job } from './big-entity'; import { SimpleModel, SuperSimple } from './entities'; function benchTime(title: string): () => void { - const started = performance.now(); - return () => { - console.log(title, performance.now() - started, 'ms'); - }; + const started = performance.now(); + return () => { + console.log(title, performance.now() - started, 'ms'); + }; } function bench(title: string, exec: () => void) { - const b = benchTime(title); - exec(); - b(); + const b = benchTime(title); + exec(); + b(); } test('speed ', () => { - //warm up - const mainInstance = plainToClass(Job, { - id: '1', - project: '2', - title: 'Foo', - config: { - parameters: { - lr: 0.05, - optimizer: 'sgd', - }, - }, - tasks: { - meiner: { - commands: [{ name: 'erster', command: 'ls -al' }], - }, - }, - created: '2018-11-25 23:38:24.339Z', - updated: '2018-11-25 23:38:24.339Z', - status: 50, - }); - plainToClass(Job, {}); - - // bench('single create', () => { - // const job = new Job('21313', '2'); - // job.title = 'Foo'; - // }); - // - // bench('single create2', () => { - // const job = new Job('21313', '2'); - // job.title = 'Foo'; - // }); - // - // bench('single plainToClass', () => { - // const instance = plainToClass(Job, { - // id: '21313', - // project: '2', - // title: 'Foo', - // }); - // }); - // - // bench('single plainToClass 2', () => { - // const instance = plainToClass(Job, { - // id: '21313', - // project: '2', - // title: 'Foo', - // }); - // }); - // - // bench('100x plainToClass', () => { - // for (let i = 0; i < 100; i++) { - // const instance = plainToClass(Job, { - // id: i, - // project: '2', - // title: 'Foo', - // }); - // } - // }); - // - // bench('100x plainToClass', () => { - // for (let i = 0; i < 100; i++) { - // const instance = plainToClass(Job, { - // id: i, - // project: '2', - // title: 'Foo', - // }); - // } - // }); - - // bench('10000x getRegisteredProperties', () => { - // for (let i = 0; i < 10000; i++) { - // getRegisteredProperties(Job); - // } - // }); - // - // bench('10000x getRegisteredProperties', () => { - // for (let i = 0; i < 10000; i++) { - // const parentReferences = {}; - // const propertyNames = getRegisteredProperties(Job); - // for (const propertyName of propertyNames) { - // parentReferences[propertyName] = true; - // } - // } - // }); - - // bench('10000x getCachedParameterNames', () => { - // for (let i = 0; i < 10000; i++) { - // getCachedParameterNames(Job); - // } - // }); - bench('10000x plainToClass big', () => { - for (let i = 0; i < 10000; i++) { - plainToClass(Job, { - id: i, + //warm up + const mainInstance = plainToClass(Job, { + id: '1', project: '2', title: 'Foo', config: { - parameters: { - lr: 0.05, - optimizer: 'sgd', - }, + parameters: { + lr: 0.05, + optimizer: 'sgd', + }, }, tasks: { - meiner: { - commands: [{ name: 'erster', command: 'ls -al' }], - }, + meiner: { + commands: [{ name: 'erster', command: 'ls -al' }], + }, }, created: '2018-11-25 23:38:24.339Z', updated: '2018-11-25 23:38:24.339Z', status: 50, - }); - } - }); + }); + plainToClass(Job, {}); + + // bench('single create', () => { + // const job = new Job('21313', '2'); + // job.title = 'Foo'; + // }); + // + // bench('single create2', () => { + // const job = new Job('21313', '2'); + // job.title = 'Foo'; + // }); + // + // bench('single plainToClass', () => { + // const instance = plainToClass(Job, { + // id: '21313', + // project: '2', + // title: 'Foo', + // }); + // }); + // + // bench('single plainToClass 2', () => { + // const instance = plainToClass(Job, { + // id: '21313', + // project: '2', + // title: 'Foo', + // }); + // }); + // + // bench('100x plainToClass', () => { + // for (let i = 0; i < 100; i++) { + // const instance = plainToClass(Job, { + // id: i, + // project: '2', + // title: 'Foo', + // }); + // } + // }); + // + // bench('100x plainToClass', () => { + // for (let i = 0; i < 100; i++) { + // const instance = plainToClass(Job, { + // id: i, + // project: '2', + // title: 'Foo', + // }); + // } + // }); + + // bench('10000x getRegisteredProperties', () => { + // for (let i = 0; i < 10000; i++) { + // getRegisteredProperties(Job); + // } + // }); + // + // bench('10000x getRegisteredProperties', () => { + // for (let i = 0; i < 10000; i++) { + // const parentReferences = {}; + // const propertyNames = getRegisteredProperties(Job); + // for (const propertyName of propertyNames) { + // parentReferences[propertyName] = true; + // } + // } + // }); + + // bench('10000x getCachedParameterNames', () => { + // for (let i = 0; i < 10000; i++) { + // getCachedParameterNames(Job); + // } + // }); + bench('10000x plainToClass big', () => { + for (let i = 0; i < 10000; i++) { + plainToClass(Job, { + id: i, + project: '2', + title: 'Foo', + config: { + parameters: { + lr: 0.05, + optimizer: 'sgd', + }, + }, + tasks: { + meiner: { + commands: [{ name: 'erster', command: 'ls -al' }], + }, + }, + created: '2018-11-25 23:38:24.339Z', + updated: '2018-11-25 23:38:24.339Z', + status: 50, + }); + } + }); - bench('10000x classToPlain big', () => { - for (let i = 0; i < 10000; i++) { - classToPlain(Job, mainInstance); - } - }); + bench('10000x classToPlain big', () => { + for (let i = 0; i < 10000; i++) { + classToPlain(Job, mainInstance); + } + }); - bench('10000x plainToClass SuperSimple', () => { - for (let i = 0; i < 10000; i++) { - plainToClass(SuperSimple, { - name: i, - }); - } - }); + bench('10000x plainToClass SuperSimple', () => { + for (let i = 0; i < 10000; i++) { + plainToClass(SuperSimple, { + name: i, + }); + } + }); - const base = plainToClass(SuperSimple, { - name: '1', - }); + const base = plainToClass(SuperSimple, { + name: '1', + }); - bench('10000x classToPlain SuperSimple', () => { - for (let i = 0; i < 10000; i++) { - classToPlain(SuperSimple, base); - } - }); + bench('10000x classToPlain SuperSimple', () => { + for (let i = 0; i < 10000; i++) { + classToPlain(SuperSimple, base); + } + }); - console.log('done'); + console.log('done'); }); diff --git a/packages/core/tests/to-plain.spec.ts b/packages/core/tests/to-plain.spec.ts index e8ee70b47..e2020b2bd 100644 --- a/packages/core/tests/to-plain.spec.ts +++ b/packages/core/tests/to-plain.spec.ts @@ -4,40 +4,40 @@ import { classToPlain } from '../'; import { Plan, SimpleModel, SubModel } from './entities'; test('test simple model', () => { - const instance = new SimpleModel('myName'); - const json = classToPlain(SimpleModel, instance); + const instance = new SimpleModel('myName'); + const json = classToPlain(SimpleModel, instance); - expect(json['id']).toBeString(); - expect(json['name']).toBe('myName'); + expect(json['id']).toBeString(); + expect(json['name']).toBe('myName'); }); test('test simple model all fields', () => { - const instance = new SimpleModel('myName'); - instance.plan = Plan.PRO; - instance.type = 5; - instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); - instance.children.push(new SubModel('fooo')); - instance.children.push(new SubModel('barr')); - - instance.childrenMap.foo = new SubModel('bar'); - instance.childrenMap.foo2 = new SubModel('bar2'); - - const json = classToPlain(SimpleModel, instance); - - console.log('json', json); - - expect(json['id']).toBeString(); - expect(json['name']).toBe('myName'); - expect(json['type']).toBe(5); - expect(json['plan']).toBe(Plan.PRO); - expect(json['created']).toBe('2018-10-13T12:17:35.000Z'); - expect(json['children']).toBeArrayOfSize(2); - expect(json['children'][0]).toBeObject(); - expect(json['children'][0].label).toBe('fooo'); - expect(json['children'][1].label).toBe('barr'); - - expect(json['childrenMap']).toBeObject(); - expect(json['childrenMap'].foo).toBeObject(); - expect(json['childrenMap'].foo.label).toBe('bar'); - expect(json['childrenMap'].foo2.label).toBe('bar2'); + const instance = new SimpleModel('myName'); + instance.plan = Plan.PRO; + instance.type = 5; + instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); + instance.children.push(new SubModel('fooo')); + instance.children.push(new SubModel('barr')); + + instance.childrenMap.foo = new SubModel('bar'); + instance.childrenMap.foo2 = new SubModel('bar2'); + + const json = classToPlain(SimpleModel, instance); + + console.log('json', json); + + expect(json['id']).toBeString(); + expect(json['name']).toBe('myName'); + expect(json['type']).toBe(5); + expect(json['plan']).toBe(Plan.PRO); + expect(json['created']).toBe('2018-10-13T12:17:35.000Z'); + expect(json['children']).toBeArrayOfSize(2); + expect(json['children'][0]).toBeObject(); + expect(json['children'][0].label).toBe('fooo'); + expect(json['children'][1].label).toBe('barr'); + + expect(json['childrenMap']).toBeObject(); + expect(json['childrenMap'].foo).toBeObject(); + expect(json['childrenMap'].foo.label).toBe('bar'); + expect(json['childrenMap'].foo2.label).toBe('bar2'); }); diff --git a/packages/core/tests/utils.spec.ts b/packages/core/tests/utils.spec.ts index 803950234..aeefc7b02 100644 --- a/packages/core/tests/utils.spec.ts +++ b/packages/core/tests/utils.spec.ts @@ -3,51 +3,51 @@ import 'reflect-metadata'; import { isArray, isObject, isUndefined } from '..'; class SimpleClass { - public label: string; + public label: string; - constructor(label: string) { - this.label = label; - } + constructor(label: string) { + this.label = label; + } } test('helper is Object', () => { - expect(isObject([])).toBeFalse(); - expect(isObject(false)).toBeFalse(); - expect(isObject(true)).toBeFalse(); - expect(isObject(null)).toBeFalse(); - expect(isObject(undefined)).toBeFalse(); - expect(isObject(1)).toBeFalse(); - expect(isObject('1')).toBeFalse(); - - expect(isObject({})).toBeTrue(); - expect(isObject(new Date())).toBeTrue(); - expect(isObject(new SimpleClass('asd'))).toBeTrue(); + expect(isObject([])).toBeFalse(); + expect(isObject(false)).toBeFalse(); + expect(isObject(true)).toBeFalse(); + expect(isObject(null)).toBeFalse(); + expect(isObject(undefined)).toBeFalse(); + expect(isObject(1)).toBeFalse(); + expect(isObject('1')).toBeFalse(); + + expect(isObject({})).toBeTrue(); + expect(isObject(new Date())).toBeTrue(); + expect(isObject(new SimpleClass('asd'))).toBeTrue(); }); test('helper is array', () => { - expect(isArray({})).toBeFalse(); - expect(isArray(new Date())).toBeFalse(); - expect(isArray(new SimpleClass('asd'))).toBeFalse(); - expect(isArray(false)).toBeFalse(); - expect(isArray(true)).toBeFalse(); - expect(isArray(null)).toBeFalse(); - expect(isArray(undefined)).toBeFalse(); - expect(isArray(1)).toBeFalse(); - expect(isArray('1')).toBeFalse(); - - expect(isArray([])).toBeTrue(); + expect(isArray({})).toBeFalse(); + expect(isArray(new Date())).toBeFalse(); + expect(isArray(new SimpleClass('asd'))).toBeFalse(); + expect(isArray(false)).toBeFalse(); + expect(isArray(true)).toBeFalse(); + expect(isArray(null)).toBeFalse(); + expect(isArray(undefined)).toBeFalse(); + expect(isArray(1)).toBeFalse(); + expect(isArray('1')).toBeFalse(); + + expect(isArray([])).toBeTrue(); }); test('helper is isUndefined', () => { - expect(isUndefined({})).toBeFalse(); - expect(isUndefined(new Date())).toBeFalse(); - expect(isUndefined(new SimpleClass('asd'))).toBeFalse(); - expect(isUndefined(false)).toBeFalse(); - expect(isUndefined(true)).toBeFalse(); - expect(isUndefined(null)).toBeFalse(); - expect(isUndefined(1)).toBeFalse(); - expect(isUndefined('1')).toBeFalse(); - expect(isUndefined([])).toBeFalse(); - - expect(isUndefined(undefined)).toBeTrue(); + expect(isUndefined({})).toBeFalse(); + expect(isUndefined(new Date())).toBeFalse(); + expect(isUndefined(new SimpleClass('asd'))).toBeFalse(); + expect(isUndefined(false)).toBeFalse(); + expect(isUndefined(true)).toBeFalse(); + expect(isUndefined(null)).toBeFalse(); + expect(isUndefined(1)).toBeFalse(); + expect(isUndefined('1')).toBeFalse(); + expect(isUndefined([])).toBeFalse(); + + expect(isUndefined(undefined)).toBeTrue(); }); diff --git a/packages/core/tests/validation.spec.ts b/packages/core/tests/validation.spec.ts index f77f062c3..a4273bdcf 100644 --- a/packages/core/tests/validation.spec.ts +++ b/packages/core/tests/validation.spec.ts @@ -1,243 +1,252 @@ import 'reflect-metadata'; import 'jest-extended'; import { - Class, - ClassArray, - NumberType, - StringType, - ClassMap, - ArrayType, - MapType, - Optional, - validate, + Class, + ClassArray, + NumberType, + StringType, + ClassMap, + ArrayType, + MapType, + Optional, + validate, } from '../'; test('test required', async () => { - class Model { - @StringType() - id: string = '1'; - - @StringType() - name?: string; - - @Optional() - optional?: string; - - @Optional() - @MapType() - map?: { [name: string]: string }; - - @Optional() - @ArrayType() - array?: string[]; - } - - const instance = new Model(); - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'name' }, - ]); - - expect(await validate(Model, { name: 'foo', map: true })).toEqual([ - { message: 'Invalid type. Expected object, but got boolean', path: 'map' }, - ]); - expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ - { message: 'Invalid type. Expected array, but got number', path: 'array' }, - ]); - - instance.name = 'Pete'; - expect(await validate(Model, instance)).toEqual([]); + class Model { + @StringType() + id: string = '1'; + + @StringType() + name?: string; + + @Optional() + optional?: string; + + @Optional() + @MapType() + map?: { [name: string]: string }; + + @Optional() + @ArrayType() + array?: string[]; + } + + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); + + expect(await validate(Model, { name: 'foo', map: true })).toEqual([ + { + message: 'Invalid type. Expected object, but got boolean', + path: 'map', + }, + ]); + expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ + { + message: 'Invalid type. Expected array, but got number', + path: 'array', + }, + ]); + + instance.name = 'Pete'; + expect(await validate(Model, instance)).toEqual([]); }); test('test deep', async () => { - class Deep { - @StringType() - name?: string; - } - - class Model { - @StringType() - id: string = '2'; - - @Class(Deep) - deep?: Deep; - - @ClassArray(Deep) - deepArray: Deep[] = []; - - @ClassMap(Deep) - deepMap: { [name: string]: Deep } = {}; - } - - const instance = new Model(); - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'deep' }, - ]); - - instance.deep = new Deep(); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'deep.name' }, - ]); - - instance.deep.name = 'defined'; - instance.deepArray.push(new Deep()); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'deepArray.0.name' }, - ]); - - instance.deepArray[0].name = 'defined'; - instance.deepMap.foo = new Deep(); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'deepMap.foo.name' }, - ]); - - instance.deepMap.foo.name = 'defined'; - expect(await validate(Model, instance)).toEqual([]); + class Deep { + @StringType() + name?: string; + } + + class Model { + @StringType() + id: string = '2'; + + @Class(Deep) + deep?: Deep; + + @ClassArray(Deep) + deepArray: Deep[] = []; + + @ClassMap(Deep) + deepMap: { [name: string]: Deep } = {}; + } + + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deep' }, + ]); + + instance.deep = new Deep(); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deep.name' }, + ]); + + instance.deep.name = 'defined'; + instance.deepArray.push(new Deep()); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deepArray.0.name' }, + ]); + + instance.deepArray[0].name = 'defined'; + instance.deepMap.foo = new Deep(); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'deepMap.foo.name' }, + ]); + + instance.deepMap.foo.name = 'defined'; + expect(await validate(Model, instance)).toEqual([]); }); test('test string', async () => { - class Model { - @StringType() - id: string = '2'; - } - - expect(await validate(Model, { id: '2' })).toEqual([]); - expect(await validate(Model, { id: 2 })).toEqual([ - { message: 'No String given', path: 'id' }, - ]); - expect(await validate(Model, { id: null })).toEqual([ - { message: 'No String given', path: 'id' }, - ]); - expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied - expect(await validate(Model, {})).toEqual([]); //because defaults are applied - - class ModelOptional { - @StringType() - @Optional() - id?: string; - } - - expect(await validate(ModelOptional, { id: '2' })).toEqual([]); - expect(await validate(ModelOptional, { id: 2 })).toEqual([ - { message: 'No String given', path: 'id' }, - ]); - expect(await validate(ModelOptional, { id: null })).toEqual([ - { message: 'No String given', path: 'id' }, - ]); - expect(await validate(ModelOptional, { id: undefined })).toEqual([]); - expect(await validate(ModelOptional, {})).toEqual([]); + class Model { + @StringType() + id: string = '2'; + } + + expect(await validate(Model, { id: '2' })).toEqual([]); + expect(await validate(Model, { id: 2 })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(Model, { id: null })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied + expect(await validate(Model, {})).toEqual([]); //because defaults are applied + + class ModelOptional { + @StringType() + @Optional() + id?: string; + } + + expect(await validate(ModelOptional, { id: '2' })).toEqual([]); + expect(await validate(ModelOptional, { id: 2 })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: null })).toEqual([ + { message: 'No String given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: undefined })).toEqual([]); + expect(await validate(ModelOptional, {})).toEqual([]); }); test('test number', async () => { - class Model { - @NumberType() - id: number = 2; - } - - expect(await validate(Model, { id: 3 })).toEqual([]); - expect(await validate(Model, { id: '3' })).toEqual([]); - expect(await validate(Model, { id: 'a' })).toEqual([ - { message: 'No Number given', path: 'id' }, - ]); - expect(await validate(Model, { id: null })).toEqual([ - { message: 'No Number given', path: 'id' }, - ]); - expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied - expect(await validate(Model, {})).toEqual([]); //because defaults are applied - - class ModelOptional { - @NumberType() - @Optional() - id?: number; - } - - expect(await validate(ModelOptional, { id: 3 })).toEqual([]); - expect(await validate(ModelOptional, { id: '3' })).toEqual([]); - expect(await validate(ModelOptional, { id: 'a' })).toEqual([ - { message: 'No Number given', path: 'id' }, - ]); - expect(await validate(ModelOptional, { id: null })).toEqual([ - { message: 'No Number given', path: 'id' }, - ]); - expect(await validate(ModelOptional, { id: undefined })).toEqual([]); - expect(await validate(ModelOptional, {})).toEqual([]); + class Model { + @NumberType() + id: number = 2; + } + + expect(await validate(Model, { id: 3 })).toEqual([]); + expect(await validate(Model, { id: '3' })).toEqual([]); + expect(await validate(Model, { id: 'a' })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(Model, { id: null })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(Model, { id: undefined })).toEqual([]); //because defaults are applied + expect(await validate(Model, {})).toEqual([]); //because defaults are applied + + class ModelOptional { + @NumberType() + @Optional() + id?: number; + } + + expect(await validate(ModelOptional, { id: 3 })).toEqual([]); + expect(await validate(ModelOptional, { id: '3' })).toEqual([]); + expect(await validate(ModelOptional, { id: 'a' })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: null })).toEqual([ + { message: 'No Number given', path: 'id' }, + ]); + expect(await validate(ModelOptional, { id: undefined })).toEqual([]); + expect(await validate(ModelOptional, {})).toEqual([]); }); test('test nested validation', async () => { - // Class definition with validation rules - class A { - @StringType() - public x!: string; - } - - class B { - @StringType() - public type!: string; - - @Class(A) - public nested!: A; - - @ClassMap(A) - public nestedMap!: { [name: string]: A }; - - @ClassArray(A) - public nesteds!: A[]; - } - - expect( - await validate(B, { - type: 'test type', - }) - ).toEqual([ - { message: 'Required value is undefined', path: 'nested' }, - { message: 'Required value is undefined', path: 'nestedMap' }, - { message: 'Required value is undefined', path: 'nesteds' }, - ]); - - expect( - await validate(B, { - type: 'test type', - nested: [{ x: 'test x' }], - nestedMap: [{ x: 'test x' }], - nesteds: { x: 'test x' }, - }) - ).toEqual([ - { message: 'Invalid type. Expected object, but got array', path: 'nested' }, - { - message: 'Invalid type. Expected object, but got array', - path: 'nestedMap', - }, - { - message: 'Invalid type. Expected array, but got object', - path: 'nesteds', - }, - ]); - - class BOptional { - @StringType() - public type!: string; - - @Class(A) - @Optional() - public nested!: A; - } - - expect( - await validate(BOptional, { - type: 'test type', - }) - ).toEqual([]); - - expect( - await validate(BOptional, { - type: 'test type', - nested: false, - }) - ).toEqual([ - { - message: 'Invalid type. Expected object, but got boolean', - path: 'nested', - }, - ]); + // Class definition with validation rules + class A { + @StringType() + public x!: string; + } + + class B { + @StringType() + public type!: string; + + @Class(A) + public nested!: A; + + @ClassMap(A) + public nestedMap!: { [name: string]: A }; + + @ClassArray(A) + public nesteds!: A[]; + } + + expect( + await validate(B, { + type: 'test type', + }) + ).toEqual([ + { message: 'Required value is undefined', path: 'nested' }, + { message: 'Required value is undefined', path: 'nestedMap' }, + { message: 'Required value is undefined', path: 'nesteds' }, + ]); + + expect( + await validate(B, { + type: 'test type', + nested: [{ x: 'test x' }], + nestedMap: [{ x: 'test x' }], + nesteds: { x: 'test x' }, + }) + ).toEqual([ + { + message: 'Invalid type. Expected object, but got array', + path: 'nested', + }, + { + message: 'Invalid type. Expected object, but got array', + path: 'nestedMap', + }, + { + message: 'Invalid type. Expected array, but got object', + path: 'nesteds', + }, + ]); + + class BOptional { + @StringType() + public type!: string; + + @Class(A) + @Optional() + public nested!: A; + } + + expect( + await validate(BOptional, { + type: 'test type', + }) + ).toEqual([]); + + expect( + await validate(BOptional, { + type: 'test type', + nested: false, + }) + ).toEqual([ + { + message: 'Invalid type. Expected object, but got boolean', + path: 'nested', + }, + ]); }); diff --git a/packages/mongo/src/database.ts b/packages/mongo/src/database.ts index f8e6c55c5..d38c0ed53 100644 --- a/packages/mongo/src/database.ts +++ b/packages/mongo/src/database.ts @@ -1,19 +1,19 @@ import { - ClassType, - getCollectionName, - getDatabaseName, - getIdField, - getClassName, - getReflectionType, + ClassType, + getCollectionName, + getDatabaseName, + getIdField, + getClassName, + getReflectionType, } from '@marcj/marshal'; import { MongoClient, Collection, Cursor } from 'mongodb'; import { - classToMongo, - mongoToClass, - partialClassToMongo, - partialMongoToPlain, - propertyClassToMongo, + classToMongo, + mongoToClass, + partialClassToMongo, + partialMongoToPlain, + propertyClassToMongo, } from './mapping'; export class NoIDDefinedError extends Error {} @@ -27,294 +27,297 @@ export type MongoClientFactory = () => Promise; * if you want to pass values from JSON/HTTP-Request. */ export class Database { - constructor( - private mongoClient: MongoClient | MongoClientFactory, - private defaultDatabaseName = 'app' - ) {} - - private async getMongoClient(): Promise { - if ('function' === typeof this.mongoClient) { - const f = this.mongoClient as MongoClientFactory; - return await f(); + constructor( + private mongoClient: MongoClient | MongoClientFactory, + private defaultDatabaseName = 'app' + ) {} + + private async getMongoClient(): Promise { + if ('function' === typeof this.mongoClient) { + const f = this.mongoClient as MongoClientFactory; + return await f(); + } + + return this.mongoClient; } - return this.mongoClient; - } - - private async getCollection( - classType: ClassType - ): Promise> { - return (await this.getMongoClient()) - .db(getDatabaseName(classType) || this.defaultDatabaseName) - .collection(getCollectionName(classType)); - } - - /** - * Returns one instance based on given filter, or null when not found. - */ - public async get( - classType: ClassType, - filter: { [field: string]: any } - ): Promise { - const collection = await this.getCollection(classType); - - const item = await collection.findOne( - partialClassToMongo(classType, filter) - ); - - if (item) { - return mongoToClass(classType, item); + private async getCollection( + classType: ClassType + ): Promise> { + return (await this.getMongoClient()) + .db(getDatabaseName(classType) || this.defaultDatabaseName) + .collection(getCollectionName(classType)); } - return null; - } - - /** - * Returns all available documents for given filter as instance classes. - * - * Use toClass=false to return the raw documents. Use find().map(v => mongoToPlain(classType, v)) so you can - * easily return that values back to the HTTP client very fast. - */ - public async find( - classType: ClassType, - filter?: { [field: string]: any }, - toClass: boolean = true - ): Promise { - const collection = await this.getCollection(classType); - - const items = await collection - .find(filter ? partialClassToMongo(classType, filter) : undefined) - .toArray(); - - const converter = toClass ? mongoToClass : partialMongoToPlain; - - return items.map((v) => { - return converter(classType, v); - }) as T[]; - } - - /** - * Returns a mongodb cursor, which you can further modify and then call toArray() to retrieve the documents. - * - * Use toClass=false to return the raw documents. - */ - public async cursor( - classType: ClassType, - filter?: { [field: string]: any }, - toClass: boolean = true - ): Promise> { - const collection = await this.getCollection(classType); - - const cursor = collection.find( - filter ? partialClassToMongo(classType, filter) : undefined - ); - const converter = toClass ? mongoToClass : partialMongoToPlain; - cursor.map((v) => converter(classType, v)); - - return cursor; - } - - /** - * Removes ONE item from the database that has the given id. You need to use @ID() decorator - * for at least and max one property at your entity to use this method. - */ - public async remove( - classType: ClassType, - id: string - ): Promise { - const collection = await this.getCollection(classType); - const idName = getIdField(classType); - if (!idName) return false; - - const filter: { [name: string]: any } = {}; - filter[idName] = id; - - const result = await collection.deleteOne( - partialClassToMongo(classType, filter) - ); - - return result.deletedCount ? result.deletedCount > 0 : false; - } - - /** - * Removes ONE item from the database that matches given filter. - */ - public async deleteOne( - classType: ClassType, - filter: { [field: string]: any } - ) { - const collection = await this.getCollection(classType); - await collection.deleteOne(partialClassToMongo(classType, filter)); - } - - /** - * Removes ALL items from the database that matches given filter. - */ - public async deleteMany( - classType: ClassType, - filter: { [field: string]: any } - ) { - const collection = await this.getCollection(classType); - await collection.deleteMany(partialClassToMongo(classType, filter)); - } - - /** - * Adds a new item to the database. Sets _id if defined at your entity. - */ - public async add(classType: ClassType, item: T): Promise { - const collection = await this.getCollection(classType); - - const id = getIdField(classType); - (item)['version'] = 1; - - const obj = classToMongo(classType, item); - obj['version'] = 1; - - const result = await collection.insertOne(obj); - - if (id === '_id' && result.insertedId) { - const { type } = getReflectionType(classType, id); - - if ( - type === 'objectId' && - result.insertedId && - result.insertedId.toHexString - ) { - (item)['_id'] = result.insertedId.toHexString(); - } + /** + * Returns one instance based on given filter, or null when not found. + */ + public async get( + classType: ClassType, + filter: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + + const item = await collection.findOne( + partialClassToMongo(classType, filter) + ); + + if (item) { + return mongoToClass(classType, item); + } + + return null; + } + + /** + * Returns all available documents for given filter as instance classes. + * + * Use toClass=false to return the raw documents. Use find().map(v => mongoToPlain(classType, v)) so you can + * easily return that values back to the HTTP client very fast. + */ + public async find( + classType: ClassType, + filter?: { [field: string]: any }, + toClass: boolean = true + ): Promise { + const collection = await this.getCollection(classType); + + const items = await collection + .find(filter ? partialClassToMongo(classType, filter) : undefined) + .toArray(); + + const converter = toClass ? mongoToClass : partialMongoToPlain; + + return items.map((v) => { + return converter(classType, v); + }) as T[]; + } + + /** + * Returns a mongodb cursor, which you can further modify and then call toArray() to retrieve the documents. + * + * Use toClass=false to return the raw documents. + */ + public async cursor( + classType: ClassType, + filter?: { [field: string]: any }, + toClass: boolean = true + ): Promise> { + const collection = await this.getCollection(classType); + + const cursor = collection.find( + filter ? partialClassToMongo(classType, filter) : undefined + ); + const converter = toClass ? mongoToClass : partialMongoToPlain; + cursor.map((v) => converter(classType, v)); + + return cursor; + } + + /** + * Removes ONE item from the database that has the given id. You need to use @ID() decorator + * for at least and max one property at your entity to use this method. + */ + public async remove( + classType: ClassType, + id: string + ): Promise { + const collection = await this.getCollection(classType); + const idName = getIdField(classType); + if (!idName) return false; + + const filter: { [name: string]: any } = {}; + filter[idName] = id; + + const result = await collection.deleteOne( + partialClassToMongo(classType, filter) + ); + + return result.deletedCount ? result.deletedCount > 0 : false; } - return true; - } - - /** - * Returns the count of items in the database, that fit that given filter. - */ - public async count( - classType: ClassType, - filter?: { [field: string]: any } - ): Promise { - const collection = await this.getCollection(classType); - return await collection.countDocuments( - partialClassToMongo(classType, filter) - ); - } - - /** - * Returns true when at least one item in the database is found that fits given filter. - */ - public async has( - classType: ClassType, - filter?: { [field: string]: any } - ): Promise { - return ( - (await this.count(classType, partialClassToMongo(classType, filter))) > 0 - ); - } - - /** - * Updates an entity in the database and returns the new version number if successful, or null if not successful. - * - * If no filter is given, the ID of `update` is used. - */ - public async update( - classType: ClassType, - update: T, - filter?: { [field: string]: any } - ): Promise { - const collection = await this.getCollection(classType); - - const updateStatement: { [name: string]: any } = { - $inc: { version: +1 }, - }; - - updateStatement['$set'] = classToMongo(classType, update); - delete updateStatement['$set']['version']; - - const filterQuery = filter - ? partialClassToMongo(classType, filter) - : this.buildFindCriteria(classType, update); - - const response = await collection.findOneAndUpdate( - filterQuery, - updateStatement, - { - projection: { version: 1 }, - returnOriginal: false, - } - ); - - const doc = response.value; - - if (!doc) { - return null; + /** + * Removes ONE item from the database that matches given filter. + */ + public async deleteOne( + classType: ClassType, + filter: { [field: string]: any } + ) { + const collection = await this.getCollection(classType); + await collection.deleteOne(partialClassToMongo(classType, filter)); } - (update)['version'] = (doc)['version']; + /** + * Removes ALL items from the database that matches given filter. + */ + public async deleteMany( + classType: ClassType, + filter: { [field: string]: any } + ) { + const collection = await this.getCollection(classType); + await collection.deleteMany(partialClassToMongo(classType, filter)); + } + + /** + * Adds a new item to the database. Sets _id if defined at your entity. + */ + public async add(classType: ClassType, item: T): Promise { + const collection = await this.getCollection(classType); + + const id = getIdField(classType); + (item)['version'] = 1; - return (update)['version']; - } + const obj = classToMongo(classType, item); + obj['version'] = 1; - private buildFindCriteria( - classType: ClassType, - data: T - ): { [name: string]: any } { - const criteria: { [name: string]: any } = {}; - const id = getIdField(classType); + const result = await collection.insertOne(obj); - if (!id) { - throw new NoIDDefinedError( - `Class ${getClassName(classType)} has no @ID() defined.` - ); + if (id === '_id' && result.insertedId) { + const { type } = getReflectionType(classType, id); + + if ( + type === 'objectId' && + result.insertedId && + result.insertedId.toHexString + ) { + (item)['_id'] = result.insertedId.toHexString(); + } + } + + return true; + } + + /** + * Returns the count of items in the database, that fit that given filter. + */ + public async count( + classType: ClassType, + filter?: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + return await collection.countDocuments( + partialClassToMongo(classType, filter) + ); + } + + /** + * Returns true when at least one item in the database is found that fits given filter. + */ + public async has( + classType: ClassType, + filter?: { [field: string]: any } + ): Promise { + return ( + (await this.count( + classType, + partialClassToMongo(classType, filter) + )) > 0 + ); } - criteria[id] = propertyClassToMongo(classType, id, (data)[id]); - - return criteria; - } - - /** - * Patches an entity in the database and returns the new version number if successful, or null if not successful. - * It's possible to provide nested key-value pairs, where the path should be based on dot symbol separation. - * - * Example - * - * await patch(SimpleEntity, { - * ['children.0.label']: 'Changed label' - * }); - */ - public async patch( - classType: ClassType, - filter: { [field: string]: any }, - patch: Partial - ): Promise { - const collection = await this.getCollection(classType); - - const patchStatement: { [name: string]: any } = { - $inc: { version: +1 }, - }; - - delete (patch)['id']; - delete (patch)['_id']; - delete (patch)['version']; - - patchStatement['$set'] = partialClassToMongo(classType, patch); - - const response = await collection.findOneAndUpdate( - partialClassToMongo(classType, filter), - patchStatement, - { - projection: { version: 1 }, - returnOriginal: false, - } - ); - - const doc = response.value; - - if (!doc) { - return null; + /** + * Updates an entity in the database and returns the new version number if successful, or null if not successful. + * + * If no filter is given, the ID of `update` is used. + */ + public async update( + classType: ClassType, + update: T, + filter?: { [field: string]: any } + ): Promise { + const collection = await this.getCollection(classType); + + const updateStatement: { [name: string]: any } = { + $inc: { version: +1 }, + }; + + updateStatement['$set'] = classToMongo(classType, update); + delete updateStatement['$set']['version']; + + const filterQuery = filter + ? partialClassToMongo(classType, filter) + : this.buildFindCriteria(classType, update); + + const response = await collection.findOneAndUpdate( + filterQuery, + updateStatement, + { + projection: { version: 1 }, + returnOriginal: false, + } + ); + + const doc = response.value; + + if (!doc) { + return null; + } + + (update)['version'] = (doc)['version']; + + return (update)['version']; } - return (doc)['version']; - } + private buildFindCriteria( + classType: ClassType, + data: T + ): { [name: string]: any } { + const criteria: { [name: string]: any } = {}; + const id = getIdField(classType); + + if (!id) { + throw new NoIDDefinedError( + `Class ${getClassName(classType)} has no @ID() defined.` + ); + } + + criteria[id] = propertyClassToMongo(classType, id, (data)[id]); + + return criteria; + } + + /** + * Patches an entity in the database and returns the new version number if successful, or null if not successful. + * It's possible to provide nested key-value pairs, where the path should be based on dot symbol separation. + * + * Example + * + * await patch(SimpleEntity, { + * ['children.0.label']: 'Changed label' + * }); + */ + public async patch( + classType: ClassType, + filter: { [field: string]: any }, + patch: Partial + ): Promise { + const collection = await this.getCollection(classType); + + const patchStatement: { [name: string]: any } = { + $inc: { version: +1 }, + }; + + delete (patch)['id']; + delete (patch)['_id']; + delete (patch)['version']; + + patchStatement['$set'] = partialClassToMongo(classType, patch); + + const response = await collection.findOneAndUpdate( + partialClassToMongo(classType, filter), + patchStatement, + { + projection: { version: 1 }, + returnOriginal: false, + } + ); + + const doc = response.value; + + if (!doc) { + return null; + } + + return (doc)['version']; + } } diff --git a/packages/mongo/src/mapping.ts b/packages/mongo/src/mapping.ts index 8c23c9740..23874b46f 100644 --- a/packages/mongo/src/mapping.ts +++ b/packages/mongo/src/mapping.ts @@ -1,590 +1,590 @@ import { Binary, ObjectID } from 'mongodb'; import { - classToPlain, - ClassType, - deleteExcludedPropertiesFor, - getClassName, - getClassPropertyName, - getDecorator, - getEnumKeys, - getParentReferenceClass, - getRegisteredProperties, - getResolvedReflection, - getValidEnumValue, - isArray, - isEnumAllowLabelsAsValue, - isObject, - isOptional, - isUndefined, - isValidEnumValue, - toClass, - ToClassState, + classToPlain, + ClassType, + deleteExcludedPropertiesFor, + getClassName, + getClassPropertyName, + getDecorator, + getEnumKeys, + getParentReferenceClass, + getRegisteredProperties, + getResolvedReflection, + getValidEnumValue, + isArray, + isEnumAllowLabelsAsValue, + isObject, + isOptional, + isUndefined, + isValidEnumValue, + toClass, + ToClassState, } from '@marcj/marshal'; import * as clone from 'clone'; import * as mongoUuid from 'mongo-uuid'; export function uuid4Binary(u?: string): Binary { - return mongoUuid(Binary, u); + return mongoUuid(Binary, u); } export function uuid4Stringify(u: Binary | string): string { - return 'string' === typeof u ? u : mongoUuid.stringify(u); + return 'string' === typeof u ? u : mongoUuid.stringify(u); } export function partialClassToMongo( - classType: ClassType, - target?: { [path: string]: any } + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - if ((target[i] as any) instanceof RegExp) { - continue; - } + if ((target[i] as any) instanceof RegExp) { + continue; + } - result[i] = propertyClassToMongo(classType, i, target[i]); - } + result[i] = propertyClassToMongo(classType, i, target[i]); + } - return result; + return result; } export function partialPlainToMongo( - classType: ClassType, - target?: { [path: string]: any } + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - result[i] = propertyPlainToMongo(classType, i, target[i]); - } + result[i] = propertyPlainToMongo(classType, i, target[i]); + } - return result; + return result; } export function partialMongoToPlain( - classType: ClassType, - target?: { [path: string]: any } + classType: ClassType, + target?: { [path: string]: any } ): { [path: string]: any } { - if (!target) return {}; + if (!target) return {}; - const result = {}; - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; + const result = {}; + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - result[i] = propertyMongoToPlain(classType, i, target[i]); - } + result[i] = propertyMongoToPlain(classType, i, target[i]); + } - return result; + return result; } export function propertyMongoToPlain( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const { type } = reflection; - - if (isUndefined(propertyValue)) { - return undefined; - } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - if (null === propertyValue) { - return null; - } + const { type } = reflection; - function convert(value: any) { - if (value && 'uuid' === type && 'string' !== typeof value) { - return uuid4Stringify(value); + if (isUndefined(propertyValue)) { + return undefined; } - if ( - 'objectId' === type && - 'string' !== typeof value && - value.toHexString() - ) { - return (value).toHexString(); + if (null === propertyValue) { + return null; } - if ('date' === type && value instanceof Date) { - return value.toJSON(); - } + function convert(value: any) { + if (value && 'uuid' === type && 'string' !== typeof value) { + return uuid4Stringify(value); + } + + if ( + 'objectId' === type && + 'string' !== typeof value && + value.toHexString() + ) { + return (value).toHexString(); + } + + if ('date' === type && value instanceof Date) { + return value.toJSON(); + } - return value; - } + return value; + } - return convert(propertyValue); + return convert(propertyValue); } export function propertyClassToMongo( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const { - resolvedClassType, - resolvedPropertyName, - type, - typeValue, - array, - map, - } = reflection; - - if (isUndefined(propertyValue)) { - return undefined; - } - - if (null === propertyValue) { - return null; - } - - function convert(value: any) { - if (value && 'objectId' === type && 'string' === typeof value) { - try { - return new ObjectID(value); - } catch (e) { - throw new Error( - `Invalid ObjectID given in property ${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )}: '${value}'` - ); - } - } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - if (value && 'uuid' === type && 'string' === typeof value) { - try { - return uuid4Binary(value); - } catch (e) { - throw new Error( - `Invalid UUID given in property ${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )}: '${value}'` - ); - } - } + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + if (isUndefined(propertyValue)) { + return undefined; + } + + if (null === propertyValue) { + return null; + } + + function convert(value: any) { + if (value && 'objectId' === type && 'string' === typeof value) { + try { + return new ObjectID(value); + } catch (e) { + throw new Error( + `Invalid ObjectID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } + } - if ('string' === type) { - return String(value); - } + if (value && 'uuid' === type && 'string' === typeof value) { + try { + return uuid4Binary(value); + } catch (e) { + throw new Error( + `Invalid UUID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } + } - if ('number' === type) { - return Number(value); - } + if ('string' === type) { + return String(value); + } - if ('enum' === type) { - //the class instance itself can only have the actual value which can be used in plain as well - return value; - } + if ('number' === type) { + return Number(value); + } - if ('binary' === type) { - return new Binary(value); - } + if ('enum' === type) { + //the class instance itself can only have the actual value which can be used in plain as well + return value; + } - if (type === 'class') { - return classToMongo(typeValue, value); - } + if ('binary' === type) { + return new Binary(value); + } - return value; - } + if (type === 'class') { + return classToMongo(typeValue, value); + } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map((v) => convert(v)); + return value; } - return []; - } - - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } + + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } + return []; } - return result; - } - return convert(propertyValue); + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; + } + + return convert(propertyValue); } export function propertyPlainToMongo( - classType: ClassType, - propertyName: string, - propertyValue: any + classType: ClassType, + propertyName: string, + propertyValue: any ) { - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const { - resolvedClassType, - resolvedPropertyName, - type, - typeValue, - array, - map, - } = reflection; - - if (isUndefined(propertyValue)) { - return undefined; - } - - if (null === propertyValue) { - return null; - } - - function convert(value: any) { - if (value && 'objectId' === type && 'string' === typeof value) { - try { - return new ObjectID(value); - } catch (e) { - throw new Error( - `Invalid ObjectID given in property ${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )}: '${value}'` - ); - } - } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - if (value && 'uuid' === type && 'string' === typeof value) { - try { - return uuid4Binary(value); - } catch (e) { - throw new Error( - `Invalid UUID given in property ${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )}: '${value}'` - ); - } - } - if ( - 'date' === type && - ('string' === typeof value || 'number' === typeof value) - ) { - return new Date(value); - } + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; + + if (isUndefined(propertyValue)) { + return undefined; + } + + if (null === propertyValue) { + return null; + } + + function convert(value: any) { + if (value && 'objectId' === type && 'string' === typeof value) { + try { + return new ObjectID(value); + } catch (e) { + throw new Error( + `Invalid ObjectID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } + } - if ('string' === type && 'string' !== typeof value) { - return String(value); - } + if (value && 'uuid' === type && 'string' === typeof value) { + try { + return uuid4Binary(value); + } catch (e) { + throw new Error( + `Invalid UUID given in property ${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )}: '${value}'` + ); + } + } + if ( + 'date' === type && + ('string' === typeof value || 'number' === typeof value) + ) { + return new Date(value); + } - if ('number' === type && 'number' !== typeof value) { - return +value; - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - if ('binary' === type && 'string' === typeof value) { - return new Buffer(value, 'base64'); - } + if ('number' === type && 'number' !== typeof value) { + return +value; + } - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + if ('binary' === type && 'string' === typeof value) { + return new Buffer(value, 'base64'); + } - return true === value; - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - if ('any' === type) { - return clone(value, false); - } + return true === value; + } - if ('binary' === type && 'string' === typeof value) { - return new Binary(new Buffer(value, 'base64')); - } + if ('any' === type) { + return clone(value, false); + } - if (type === 'class') { - //we need to check if value has all properties set, if one not-optional is missing, we throw an error - for (const property of getRegisteredProperties(typeValue)) { - if (value[property] === undefined) { - if (isOptional(typeValue, propertyName)) { - continue; - } - throw new Error( - `Missing value for ${getClassPropertyName( - typeValue, - propertyName - )}. Can not convert to mongo.` - ); - } - - value[property] = propertyPlainToMongo( - typeValue, - property, - value[property] - ); - } - } + if ('binary' === type && 'string' === typeof value) { + return new Binary(new Buffer(value, 'base64')); + } + + if (type === 'class') { + //we need to check if value has all properties set, if one not-optional is missing, we throw an error + for (const property of getRegisteredProperties(typeValue)) { + if (value[property] === undefined) { + if (isOptional(typeValue, propertyName)) { + continue; + } + throw new Error( + `Missing value for ${getClassPropertyName( + typeValue, + propertyName + )}. Can not convert to mongo.` + ); + } + + value[property] = propertyPlainToMongo( + typeValue, + property, + value[property] + ); + } + } - return value; - } + return value; + } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map((v) => convert(v)); + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } + return []; } - return []; - } - - if (map) { - const result: { [name: string]: any } = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue)[i]); - } + + if (map) { + const result: { [name: string]: any } = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue)[i]); + } + } + return result; } - return result; - } - return convert(propertyValue); + return convert(propertyValue); } export function propertyMongoToClass( - classType: ClassType, - propertyName: string, - propertyValue: any, - parents: any[], - incomingLevel: number, - state: ToClassState + classType: ClassType, + propertyName: string, + propertyValue: any, + parents: any[], + incomingLevel: number, + state: ToClassState ) { - if (isUndefined(propertyValue)) { - return undefined; - } - - if (null === propertyValue) { - return null; - } - - const reflection = getResolvedReflection(classType, propertyName); - if (!reflection) return propertyValue; - - const { - resolvedClassType, - resolvedPropertyName, - type, - typeValue, - array, - map, - } = reflection; - - function convert(value: any) { - if (value && 'uuid' === type && 'string' !== typeof value) { - return uuid4Stringify(value); + if (isUndefined(propertyValue)) { + return undefined; } - if ( - 'objectId' === type && - 'string' !== typeof value && - value.toHexString() - ) { - return (value).toHexString(); + if (null === propertyValue) { + return null; } - if ('date' === type && !(value instanceof Date)) { - return new Date(value); - } + const reflection = getResolvedReflection(classType, propertyName); + if (!reflection) return propertyValue; - if ('binary' === type && value instanceof Binary) { - return value.buffer; - } + const { + resolvedClassType, + resolvedPropertyName, + type, + typeValue, + array, + map, + } = reflection; - if ('any' === type) { - return clone(value, false); - } + function convert(value: any) { + if (value && 'uuid' === type && 'string' !== typeof value) { + return uuid4Stringify(value); + } - if ('string' === type && 'string' !== typeof value) { - return String(value); - } + if ( + 'objectId' === type && + 'string' !== typeof value && + value.toHexString() + ) { + return (value).toHexString(); + } - if ('number' === type && 'number' !== typeof value) { - return +value; - } + if ('date' === type && !(value instanceof Date)) { + return new Date(value); + } - if ('boolean' === type && 'boolean' !== typeof value) { - if ('true' === value || '1' === value || 1 === value) return true; - if ('false' === value || '0' === value || 0 === value) return false; + if ('binary' === type && value instanceof Binary) { + return value.buffer; + } - return true === value; - } + if ('any' === type) { + return clone(value, false); + } - if ('enum' === type) { - const allowLabelsAsValue = isEnumAllowLabelsAsValue( - resolvedClassType, - resolvedPropertyName - ); - if ( - undefined !== value && - !isValidEnumValue(typeValue, value, allowLabelsAsValue) - ) { - throw new Error( - `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${getEnumKeys( - typeValue - ).join(',')}` - ); - } + if ('string' === type && 'string' !== typeof value) { + return String(value); + } - return getValidEnumValue(typeValue, value, allowLabelsAsValue); - } + if ('number' === type && 'number' !== typeof value) { + return +value; + } - if (type === 'class') { - if (value instanceof typeValue) { - //already the target type, this is an error - throw new Error( - `${getClassPropertyName( - resolvedClassType, - resolvedPropertyName - )} is already in target format. Are you calling plainToClass() with an class instance?` - ); - } + if ('boolean' === type && 'boolean' !== typeof value) { + if ('true' === value || '1' === value || 1 === value) return true; + if ('false' === value || '0' === value || 0 === value) return false; - return toClass( - typeValue, - clone(value, false, 1), - propertyMongoToClass, - parents, - incomingLevel, - state - ); - } + return true === value; + } - return value; - } + if ('enum' === type) { + const allowLabelsAsValue = isEnumAllowLabelsAsValue( + resolvedClassType, + resolvedPropertyName + ); + if ( + undefined !== value && + !isValidEnumValue(typeValue, value, allowLabelsAsValue) + ) { + throw new Error( + `Invalid ENUM given in property ${resolvedPropertyName}: ${value}, valid: ${getEnumKeys( + typeValue + ).join(',')}` + ); + } + + return getValidEnumValue(typeValue, value, allowLabelsAsValue); + } + + if (type === 'class') { + if (value instanceof typeValue) { + //already the target type, this is an error + throw new Error( + `${getClassPropertyName( + resolvedClassType, + resolvedPropertyName + )} is already in target format. Are you calling plainToClass() with an class instance?` + ); + } + + return toClass( + typeValue, + clone(value, false, 1), + propertyMongoToClass, + parents, + incomingLevel, + state + ); + } - if (array) { - if (isArray(propertyValue)) { - return propertyValue.map((v) => convert(v)); + return value; } - return []; - } - if (map) { - const result: any = {}; - if (isObject(propertyValue)) { - for (const i in propertyValue) { - if (!propertyValue.hasOwnProperty(i)) continue; - result[i] = convert((propertyValue as any)[i]); - } + if (array) { + if (isArray(propertyValue)) { + return propertyValue.map((v) => convert(v)); + } + return []; + } + + if (map) { + const result: any = {}; + if (isObject(propertyValue)) { + for (const i in propertyValue) { + if (!propertyValue.hasOwnProperty(i)) continue; + result[i] = convert((propertyValue as any)[i]); + } + } + return result; } - return result; - } - return convert(propertyValue); + return convert(propertyValue); } export function mongoToClass( - classType: ClassType, - target: any, - parents?: any[] + classType: ClassType, + target: any, + parents?: any[] ): T { - const state = new ToClassState(); - const item = toClass( - classType, - clone(target, false, 1), - propertyMongoToClass, - parents || [], - 1, - state - ); - - for (const callback of state.onFullLoadCallbacks) { - callback(); - } - - return item; + const state = new ToClassState(); + const item = toClass( + classType, + clone(target, false, 1), + propertyMongoToClass, + parents || [], + 1, + state + ); + + for (const callback of state.onFullLoadCallbacks) { + callback(); + } + + return item; } export function mongoToPlain(classType: ClassType, target: any) { - return classToPlain(classType, mongoToClass(classType, target)); + return classToPlain(classType, mongoToClass(classType, target)); } export function plainToMongo( - classType: ClassType, - target: { [k: string]: any } + classType: ClassType, + target: { [k: string]: any } ): any { - const result: any = {}; - - if (target instanceof classType) { - throw new Error( - `Could not plainToMongo since target is a class instance of ${getClassName( - classType - )}` - ); - } + const result: any = {}; - for (const propertyName of getRegisteredProperties(classType)) { - if (undefined === (target as any)[propertyName]) { - continue; + if (target instanceof classType) { + throw new Error( + `Could not plainToMongo since target is a class instance of ${getClassName( + classType + )}` + ); } - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; - } + for (const propertyName of getRegisteredProperties(classType)) { + if (undefined === (target as any)[propertyName]) { + continue; + } - result[propertyName] = propertyPlainToMongo( - classType, - propertyName, - (target as any)[propertyName] - ); - } + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; + } + + result[propertyName] = propertyPlainToMongo( + classType, + propertyName, + (target as any)[propertyName] + ); + } - deleteExcludedPropertiesFor(classType, result, 'mongo'); - return result; + deleteExcludedPropertiesFor(classType, result, 'mongo'); + return result; } export function classToMongo(classType: ClassType, target: T): any { - const result: any = {}; - - if (!(target instanceof classType)) { - throw new Error( - `Could not classToMongo since target is not a class instance of ${getClassName( - classType - )}` - ); - } - - const decoratorName = getDecorator(classType); - if (decoratorName) { - return propertyClassToMongo( - classType, - decoratorName, - (target as any)[decoratorName] - ); - } + const result: any = {}; - for (const propertyName of getRegisteredProperties(classType)) { - if (undefined === (target as any)[propertyName]) { - continue; + if (!(target instanceof classType)) { + throw new Error( + `Could not classToMongo since target is not a class instance of ${getClassName( + classType + )}` + ); } - if (getParentReferenceClass(classType, propertyName)) { - //we do not export parent references, as this would lead to an circular reference - continue; + const decoratorName = getDecorator(classType); + if (decoratorName) { + return propertyClassToMongo( + classType, + decoratorName, + (target as any)[decoratorName] + ); } - result[propertyName] = propertyClassToMongo( - classType, - propertyName, - (target as any)[propertyName] - ); - } + for (const propertyName of getRegisteredProperties(classType)) { + if (undefined === (target as any)[propertyName]) { + continue; + } + + if (getParentReferenceClass(classType, propertyName)) { + //we do not export parent references, as this would lead to an circular reference + continue; + } + + result[propertyName] = propertyClassToMongo( + classType, + propertyName, + (target as any)[propertyName] + ); + } - deleteExcludedPropertiesFor(classType, result, 'mongo'); - return result; + deleteExcludedPropertiesFor(classType, result, 'mongo'); + return result; } /** @@ -592,70 +592,80 @@ export function classToMongo(classType: ClassType, target: T): any { * can use it to send it to mongo. */ export function convertPlainQueryToMongo( - classType: ClassType, - target: { [path: string]: any }, - fieldNamesMap: { [name: string]: boolean } = {} + classType: ClassType, + target: { [path: string]: any }, + fieldNamesMap: { [name: string]: boolean } = {} ): { [path: string]: any } { - const result: { [i: string]: any } = {}; - - for (const i in target) { - if (!target.hasOwnProperty(i)) continue; - - const fieldValue: any = target[i]; + const result: { [i: string]: any } = {}; - if (i[0] === '$') { - result[i] = (fieldValue as any[]).map((v) => - convertPlainQueryToMongo(classType, v, fieldNamesMap) - ); - continue; - } + for (const i in target) { + if (!target.hasOwnProperty(i)) continue; - if (isObject(fieldValue)) { - for (const j in fieldValue) { - if (!fieldValue.hasOwnProperty(j)) continue; + const fieldValue: any = target[i]; - const queryValue: any = (fieldValue as any)[j]; + if (i[0] === '$') { + result[i] = (fieldValue as any[]).map((v) => + convertPlainQueryToMongo(classType, v, fieldNamesMap) + ); + continue; + } - if (j[0] !== '$') { - result[i] = propertyClassToMongo(classType, i, fieldValue); - break; + if (isObject(fieldValue)) { + for (const j in fieldValue) { + if (!fieldValue.hasOwnProperty(j)) continue; + + const queryValue: any = (fieldValue as any)[j]; + + if (j[0] !== '$') { + result[i] = propertyClassToMongo(classType, i, fieldValue); + break; + } else { + if ( + j === '$and' || + j === '$or' || + j === '$nor' || + j === '$not' + ) { + (fieldValue as any)[j] = (queryValue as any[]).map( + (v) => + convertPlainQueryToMongo( + classType, + v, + fieldNamesMap + ) + ); + } else if (j === '$in' || j === '$nin' || j === '$all') { + fieldNamesMap[i] = true; + (fieldValue as any)[j] = (queryValue as any[]).map( + (v) => propertyClassToMongo(classType, i, v) + ); + } else if ( + j === '$text' || + j === '$exists' || + j === '$mod' || + j === '$size' || + j === '$type' || + j === '$regex' || + j === '$where' + ) { + //don't transform + } else { + fieldNamesMap[i] = true; + (fieldValue as any)[j] = propertyClassToMongo( + classType, + i, + queryValue + ); + } + } + } + + result[i] = fieldValue; } else { - if (j === '$and' || j === '$or' || j === '$nor' || j === '$not') { - (fieldValue as any)[j] = (queryValue as any[]).map((v) => - convertPlainQueryToMongo(classType, v, fieldNamesMap) - ); - } else if (j === '$in' || j === '$nin' || j === '$all') { fieldNamesMap[i] = true; - (fieldValue as any)[j] = (queryValue as any[]).map((v) => - propertyClassToMongo(classType, i, v) - ); - } else if ( - j === '$text' || - j === '$exists' || - j === '$mod' || - j === '$size' || - j === '$type' || - j === '$regex' || - j === '$where' - ) { - //don't transform - } else { - fieldNamesMap[i] = true; - (fieldValue as any)[j] = propertyClassToMongo( - classType, - i, - queryValue - ); - } + result[i] = propertyClassToMongo(classType, i, fieldValue); } - } - - result[i] = fieldValue; - } else { - fieldNamesMap[i] = true; - result[i] = propertyClassToMongo(classType, i, fieldValue); } - } - return result; + return result; } diff --git a/packages/mongo/tests/class-to.spec.ts b/packages/mongo/tests/class-to.spec.ts index 8778ab708..e446be2cb 100644 --- a/packages/mongo/tests/class-to.spec.ts +++ b/packages/mongo/tests/class-to.spec.ts @@ -4,10 +4,10 @@ import { classToMongo } from '../src/mapping'; import { SimpleModel } from '@marcj/marshal/tests/entities'; test('class-to test simple model', () => { - expect(() => { - const instance = classToMongo(SimpleModel, { - id: '21313', - name: 'Hi', - }); - }).toThrow(`Could not classToMongo since target is not a class instance`); + expect(() => { + const instance = classToMongo(SimpleModel, { + id: '21313', + name: 'Hi', + }); + }).toThrow(`Could not classToMongo since target is not a class instance`); }); diff --git a/packages/mongo/tests/mongo-query.spec.ts b/packages/mongo/tests/mongo-query.spec.ts index 12d691bf8..58c516136 100644 --- a/packages/mongo/tests/mongo-query.spec.ts +++ b/packages/mongo/tests/mongo-query.spec.ts @@ -3,103 +3,105 @@ import { NumberType, StringType } from '@marcj/marshal'; import { convertPlainQueryToMongo } from '..'; class Simple { - @NumberType() - public id!: number; + @NumberType() + public id!: number; - @NumberType() - public price!: number; + @NumberType() + public price!: number; - @StringType() - public label!: string; + @StringType() + public label!: string; } test('simple', () => { - const m = convertPlainQueryToMongo(Simple, { - id: { $qt: '1' }, - }); + const m = convertPlainQueryToMongo(Simple, { + id: { $qt: '1' }, + }); - expect(m['id']['$qt']).toBe(1); + expect(m['id']['$qt']).toBe(1); }); test('simple 2', () => { - const m = convertPlainQueryToMongo(Simple, { id: { dif: 1 } }); - expect(m).toEqual({ id: { dif: 1 } }); + const m = convertPlainQueryToMongo(Simple, { id: { dif: 1 } }); + expect(m).toEqual({ id: { dif: 1 } }); }); test('and', () => { - const m = convertPlainQueryToMongo(Simple, { - $and: [{ id: '1' }, { id: '2' }], - }); - expect(m).toEqual({ $and: [{ id: 1 }, { id: 2 }] }); + const m = convertPlainQueryToMongo(Simple, { + $and: [{ id: '1' }, { id: '2' }], + }); + expect(m).toEqual({ $and: [{ id: 1 }, { id: 2 }] }); - expect(m['$and'][0]['id']).toBe(1); - expect(m['$and'][1]['id']).toBe(2); + expect(m['$and'][0]['id']).toBe(1); + expect(m['$and'][1]['id']).toBe(2); }); test('in', () => { - const m = convertPlainQueryToMongo(Simple, { id: { $in: ['1', '2'] } }); - expect(m).toEqual({ id: { $in: [1, 2] } }); + const m = convertPlainQueryToMongo(Simple, { id: { $in: ['1', '2'] } }); + expect(m).toEqual({ id: { $in: [1, 2] } }); - const m2 = convertPlainQueryToMongo(Simple, { id: { $nin: ['1', '2'] } }); - expect(m2).toEqual({ id: { $nin: [1, 2] } }); + const m2 = convertPlainQueryToMongo(Simple, { id: { $nin: ['1', '2'] } }); + expect(m2).toEqual({ id: { $nin: [1, 2] } }); - const m3 = convertPlainQueryToMongo(Simple, { label: { $all: [1, '2'] } }); - expect(m3).toEqual({ label: { $all: ['1', '2'] } }); + const m3 = convertPlainQueryToMongo(Simple, { label: { $all: [1, '2'] } }); + expect(m3).toEqual({ label: { $all: ['1', '2'] } }); }); test('complex', () => { - const names = {}; - const m = convertPlainQueryToMongo( - Simple, - { - $and: [ - { price: { $ne: '1.99' } }, - { price: { $exists: true } }, - { id: { $gt: '0' } }, - ], - }, - names - ); - - expect(m).toEqual({ - $and: [ - { price: { $ne: 1.99 } }, - { price: { $exists: true } }, - { id: { $gt: 0 } }, - ], - }); - expect(Object.keys(names)).toEqual(['price', 'id']); + const names = {}; + const m = convertPlainQueryToMongo( + Simple, + { + $and: [ + { price: { $ne: '1.99' } }, + { price: { $exists: true } }, + { id: { $gt: '0' } }, + ], + }, + names + ); + + expect(m).toEqual({ + $and: [ + { price: { $ne: 1.99 } }, + { price: { $exists: true } }, + { id: { $gt: 0 } }, + ], + }); + expect(Object.keys(names)).toEqual(['price', 'id']); }); test('$or', () => { - const m = convertPlainQueryToMongo(Simple, { $and: [{ $or: [{ id: 1 }] }] }); - expect(m).toEqual({ $and: [{ $or: [{ id: 1 }] }] }); + const m = convertPlainQueryToMongo(Simple, { + $and: [{ $or: [{ id: 1 }] }], + }); + expect(m).toEqual({ $and: [{ $or: [{ id: 1 }] }] }); }); test('nested $or', () => { - const m = convertPlainQueryToMongo(Simple, { - $or: [{ id: { $lt: 20 } }, { price: 10 }], - }); - expect(m).toEqual({ $or: [{ id: { $lt: 20 } }, { price: 10 }] }); + const m = convertPlainQueryToMongo(Simple, { + $or: [{ id: { $lt: 20 } }, { price: 10 }], + }); + expect(m).toEqual({ $or: [{ id: { $lt: 20 } }, { price: 10 }] }); }); test('not', () => { - const m = convertPlainQueryToMongo(Simple, { - $not: [{ price: { $ne: '1.99' } }, { price: { $exists: true } }], - }); + const m = convertPlainQueryToMongo(Simple, { + $not: [{ price: { $ne: '1.99' } }, { price: { $exists: true } }], + }); - expect(m).toEqual({ - $not: [{ price: { $ne: 1.99 } }, { price: { $exists: true } }], - }); + expect(m).toEqual({ + $not: [{ price: { $ne: 1.99 } }, { price: { $exists: true } }], + }); }); test('excluded', () => { - const excluded = ['$exists', '$mod', '$size', '$type', '$regex', '$where']; - - for (const e of excluded) { - const obj = { label: {} }; - obj['label'][e] = true; - const m = convertPlainQueryToMongo(Simple, obj); - expect(m).toEqual(obj); - } + const excluded = ['$exists', '$mod', '$size', '$type', '$regex', '$where']; + + for (const e of excluded) { + const obj = { label: {} }; + obj['label'][e] = true; + const m = convertPlainQueryToMongo(Simple, obj); + expect(m).toEqual(obj); + } }); diff --git a/packages/mongo/tests/mongo.spec.ts b/packages/mongo/tests/mongo.spec.ts index a78c80306..dbf87a2ec 100644 --- a/packages/mongo/tests/mongo.spec.ts +++ b/packages/mongo/tests/mongo.spec.ts @@ -1,16 +1,16 @@ import 'jest-extended'; import 'reflect-metadata'; import { - BinaryType, - DatabaseName, - Entity, - getCollectionName, - getDatabaseName, - getEntityName, - ID, - MongoIdType, - plainToClass, - StringType, + BinaryType, + DatabaseName, + Entity, + getCollectionName, + getDatabaseName, + getEntityName, + ID, + MongoIdType, + plainToClass, + StringType, } from '@marcj/marshal'; import { Binary, ObjectID, MongoClient } from 'mongodb'; import { Database } from '../src/database'; @@ -21,358 +21,364 @@ import { Buffer } from 'buffer'; let connection: MongoClient; afterEach(async () => { - await connection.close(true); + await connection.close(true); }); test('test save model', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - - const instance = plainToClass(SimpleModel, { - name: 'myName', - }); - - await database.add(SimpleModel, instance); - expect((instance)['version']).toBe(1); - - expect(await database.count(SimpleModel)).toBe(1); - expect(await database.count(SimpleModel, { name: 'myName' })).toBe(1); - expect(await database.count(SimpleModel, { name: 'MyNameNOTEXIST' })).toBe(0); - - expect(await database.has(SimpleModel)).toBeTrue(); - expect(await database.has(SimpleModel, { name: 'myName' })).toBeTrue(); - expect( - await database.has(SimpleModel, { name: 'myNameNOTEXIST' }) - ).toBeFalse(); - - expect(await database.get(SimpleModel, { name: 'myName' })).not.toBeNull(); - expect( - await database.get(SimpleModel, { name: 'myNameNOTEXIST' }) - ).toBeNull(); - - const collection = connection - .db('testing') - .collection(getCollectionName(SimpleModel)); - const mongoItem = await collection.find().toArray(); - expect(mongoItem).toBeArrayOfSize(1); - expect(mongoItem[0].name).toBe('myName'); - expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); - expect(mongoItem[0].id).toBeInstanceOf(Binary); - expect(uuid4Stringify(mongoItem[0].id)).toBe(instance.id); - - const found = await database.get(SimpleModel, { id: instance.id }); - expect(found).toBeInstanceOf(SimpleModel); - expect(found!.name).toBe('myName'); - expect(found!.id).toBe(instance.id); - - const list = await database.find(SimpleModel, { id: instance.id }); - expect(list[0]).toBeInstanceOf(SimpleModel); - expect(list[0].name).toBe('myName'); - expect(list[0].id).toBe(instance.id); - - const listAll = await database.find(SimpleModel); - expect(listAll[0]).toBeInstanceOf(SimpleModel); - expect(listAll[0].name).toBe('myName'); - expect(listAll[0].id).toBe(instance.id); - - expect( - await database.patch( - SimpleModel, - { name: 'noneExisting' }, - { name: 'myName2' } - ) - ).toBeNull(); - - const notExisting = new SimpleModel('Hi'); - expect(await database.update(SimpleModel, notExisting)).toBeNull(); - - expect( - await database.patch(SimpleModel, { id: instance.id }, { name: 'myName2' }) - ).toBe((instance)['version'] + 1); - - { + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + + const instance = plainToClass(SimpleModel, { + name: 'myName', + }); + + await database.add(SimpleModel, instance); + expect((instance)['version']).toBe(1); + + expect(await database.count(SimpleModel)).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'MyNameNOTEXIST' })).toBe( + 0 + ); + + expect(await database.has(SimpleModel)).toBeTrue(); + expect(await database.has(SimpleModel, { name: 'myName' })).toBeTrue(); + expect( + await database.has(SimpleModel, { name: 'myNameNOTEXIST' }) + ).toBeFalse(); + + expect(await database.get(SimpleModel, { name: 'myName' })).not.toBeNull(); + expect( + await database.get(SimpleModel, { name: 'myNameNOTEXIST' }) + ).toBeNull(); + + const collection = connection + .db('testing') + .collection(getCollectionName(SimpleModel)); + const mongoItem = await collection.find().toArray(); + expect(mongoItem).toBeArrayOfSize(1); + expect(mongoItem[0].name).toBe('myName'); + expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); + expect(mongoItem[0].id).toBeInstanceOf(Binary); + expect(uuid4Stringify(mongoItem[0].id)).toBe(instance.id); + const found = await database.get(SimpleModel, { id: instance.id }); expect(found).toBeInstanceOf(SimpleModel); - expect(found!.name).toBe('myName2'); - } - - instance.name = 'New Name'; - await database.update(SimpleModel, instance); - expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); - expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); - - instance.name = 'New Name 2'; - await database.update(SimpleModel, instance, { noResult: '2132' }); - expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); - expect(await database.has(SimpleModel, { name: 'MyName 2' })).toBeFalse(); - expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); - - await database.update(SimpleModel, instance, { id: instance.id }); - expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); - expect(await database.has(SimpleModel, { name: 'New Name' })).toBeFalse(); - expect(await database.has(SimpleModel, { name: 'New Name 2' })).toBeTrue(); + expect(found!.name).toBe('myName'); + expect(found!.id).toBe(instance.id); + + const list = await database.find(SimpleModel, { id: instance.id }); + expect(list[0]).toBeInstanceOf(SimpleModel); + expect(list[0].name).toBe('myName'); + expect(list[0].id).toBe(instance.id); + + const listAll = await database.find(SimpleModel); + expect(listAll[0]).toBeInstanceOf(SimpleModel); + expect(listAll[0].name).toBe('myName'); + expect(listAll[0].id).toBe(instance.id); + + expect( + await database.patch( + SimpleModel, + { name: 'noneExisting' }, + { name: 'myName2' } + ) + ).toBeNull(); + + const notExisting = new SimpleModel('Hi'); + expect(await database.update(SimpleModel, notExisting)).toBeNull(); + + expect( + await database.patch( + SimpleModel, + { id: instance.id }, + { name: 'myName2' } + ) + ).toBe((instance)['version'] + 1); + + { + const found = await database.get(SimpleModel, { id: instance.id }); + expect(found).toBeInstanceOf(SimpleModel); + expect(found!.name).toBe('myName2'); + } + + instance.name = 'New Name'; + await database.update(SimpleModel, instance); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); + + instance.name = 'New Name 2'; + await database.update(SimpleModel, instance, { noResult: '2132' }); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'MyName 2' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeTrue(); + + await database.update(SimpleModel, instance, { id: instance.id }); + expect(await database.has(SimpleModel, { name: 'MyName' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name' })).toBeFalse(); + expect(await database.has(SimpleModel, { name: 'New Name 2' })).toBeTrue(); }); test('test delete', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); - const instance1 = plainToClass(SimpleModel, { - name: 'myName1', - }); + const instance1 = plainToClass(SimpleModel, { + name: 'myName1', + }); - const instance2 = plainToClass(SimpleModel, { - name: 'myName2', - }); + const instance2 = plainToClass(SimpleModel, { + name: 'myName2', + }); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); - expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(1); - expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); - expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); + expect(await database.count(SimpleModel)).toBe(2); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.remove(SimpleModel, instance1.id); + await database.remove(SimpleModel, instance1.id); - expect(await database.count(SimpleModel)).toBe(1); - expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); - expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); - expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); + expect(await database.count(SimpleModel)).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(1); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.remove(SimpleModel, instance2.id); + await database.remove(SimpleModel, instance2.id); - expect(await database.count(SimpleModel)).toBe(0); - expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); - expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(0); - expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); + expect(await database.count(SimpleModel)).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName1' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName2' })).toBe(0); + expect(await database.count(SimpleModel, { name: 'myName3' })).toBe(0); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); + expect(await database.count(SimpleModel)).toBe(2); - await database.deleteMany(SimpleModel, { name: /myName[0-9]/ }); - expect(await database.count(SimpleModel)).toBe(0); + await database.deleteMany(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(0); - await database.add(SimpleModel, instance1); - await database.add(SimpleModel, instance2); - expect(await database.count(SimpleModel)).toBe(2); + await database.add(SimpleModel, instance1); + await database.add(SimpleModel, instance2); + expect(await database.count(SimpleModel)).toBe(2); - await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); - expect(await database.count(SimpleModel)).toBe(1); + await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(1); - await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); - expect(await database.count(SimpleModel)).toBe(0); + await database.deleteOne(SimpleModel, { name: /myName[0-9]/ }); + expect(await database.count(SimpleModel)).toBe(0); }); test('test super simple model', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - const instance = plainToClass(SuperSimple, { - name: 'myName', - }); - - expect(instance._id).toBeUndefined(); - await database.add(SuperSimple, instance); - expect(instance._id).not.toBeUndefined(); - - { - const items = await database.find(SuperSimple); - expect(items[0]).toBeInstanceOf(SuperSimple); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); - } + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); - { - const items = await (await database.cursor(SuperSimple)).toArray(); - expect(items[0]).toBeInstanceOf(SuperSimple); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); - } + const instance = plainToClass(SuperSimple, { + name: 'myName', + }); + + expect(instance._id).toBeUndefined(); + await database.add(SuperSimple, instance); + expect(instance._id).not.toBeUndefined(); + + { + const items = await database.find(SuperSimple); + expect(items[0]).toBeInstanceOf(SuperSimple); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); + } + + { + const items = await (await database.cursor(SuperSimple)).toArray(); + expect(items[0]).toBeInstanceOf(SuperSimple); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); + } }); test('test databaseName', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - - await connection.db('testing2').dropDatabase(); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing2') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - const instance = plainToClass(DifferentDataBase, { - name: 'myName', - }); - - expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(instance._id).toBeUndefined(); - await database.add(DifferentDataBase, instance); - expect(instance._id).not.toBeUndefined(); - - const collection = connection - .db('testing2') - .collection('differentCollection'); - expect(await collection.countDocuments()).toBe(1); - - const items = await database.find(DifferentDataBase); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + + await connection.db('testing2').dropDatabase(); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing2') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + const instance = plainToClass(DifferentDataBase, { + name: 'myName', + }); + + expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(instance._id).toBeUndefined(); + await database.add(DifferentDataBase, instance); + expect(instance._id).not.toBeUndefined(); + + const collection = connection + .db('testing2') + .collection('differentCollection'); + expect(await collection.countDocuments()).toBe(1); + + const items = await database.find(DifferentDataBase); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); }); test('no id', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); - @Entity('NoId') - class NoId { - @MongoIdType() - _id?: string; + @Entity('NoId') + class NoId { + @MongoIdType() + _id?: string; - @StringType() - name?: string; - } + @StringType() + name?: string; + } - const instance = plainToClass(NoId, { - name: 'myName', - }); + const instance = plainToClass(NoId, { + name: 'myName', + }); - await database.add(NoId, instance); - expect(instance._id).toBeUndefined(); + await database.add(NoId, instance); + expect(instance._id).toBeUndefined(); - const dbItem = await database.get(NoId, { name: 'myName' }); - expect(dbItem!.name).toBe('myName'); + const dbItem = await database.get(NoId, { name: 'myName' }); + expect(dbItem!.name).toBe('myName'); - dbItem!.name = 'Changed'; + dbItem!.name = 'Changed'; - await expect(database.update(NoId, dbItem)).rejects.toThrow( - 'Class NoId has no @ID() defined' - ); + await expect(database.update(NoId, dbItem)).rejects.toThrow( + 'Class NoId has no @ID() defined' + ); }); test('test factory', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing2').dropDatabase(); - await connection.db('testing').dropDatabase(); - - const database = new Database(async () => { - return await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, }); - }, 'testing'); - - @Entity('DifferentDataBase', 'differentCollection') - @DatabaseName('testing2') - class DifferentDataBase { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - } - - const instance = plainToClass(DifferentDataBase, { - name: 'myName', - }); - - expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); - expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); - - expect(instance._id).toBeUndefined(); - await database.add(DifferentDataBase, instance); - expect(instance._id).not.toBeUndefined(); - - const collection = connection - .db('testing2') - .collection('differentCollection'); - expect(await collection.countDocuments()).toBe(1); - - const items = await database.find(DifferentDataBase); - expect(items[0]._id).toBe(instance._id); - expect(items[0].name).toBe(instance.name); + await connection.db('testing2').dropDatabase(); + await connection.db('testing').dropDatabase(); + + const database = new Database(async () => { + return await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + }, 'testing'); + + @Entity('DifferentDataBase', 'differentCollection') + @DatabaseName('testing2') + class DifferentDataBase { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + } + + const instance = plainToClass(DifferentDataBase, { + name: 'myName', + }); + + expect(getDatabaseName(DifferentDataBase)).toBe('testing2'); + expect(getCollectionName(DifferentDataBase)).toBe('differentCollection'); + + expect(instance._id).toBeUndefined(); + await database.add(DifferentDataBase, instance); + expect(instance._id).not.toBeUndefined(); + + const collection = connection + .db('testing2') + .collection('differentCollection'); + expect(await collection.countDocuments()).toBe(1); + + const items = await database.find(DifferentDataBase); + expect(items[0]._id).toBe(instance._id); + expect(items[0].name).toBe(instance.name); }); test('second object id', async () => { - connection = await MongoClient.connect('mongodb://localhost:27017', { - useNewUrlParser: true, - }); - await connection.db('testing').dropDatabase(); - const database = new Database(connection, 'testing'); - - @Entity('SecondObjectId') - class SecondObjectId { - @ID() - @MongoIdType() - _id?: string; - - @StringType() - name?: string; - - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - - @MongoIdType() - secondId?: string; - } - - const instance = plainToClass(SecondObjectId, { - name: 'myName', - secondId: '5bf4a1ccce060e0b38864c9e', - preview: 'QmFhcg==', //Baar - }); - - await database.add(SecondObjectId, instance); - - const dbItem = await database.get(SecondObjectId, { name: 'myName' }); - expect(dbItem!.name).toBe('myName'); - - const dbItemBySecondId = await database.get(SecondObjectId, { - secondId: '5bf4a1ccce060e0b38864c9e', - }); - expect(dbItemBySecondId!.name).toBe('myName'); - - const collection = connection - .db('testing') - .collection(getCollectionName(SecondObjectId)); - const mongoItem = await collection.find().toArray(); - expect(mongoItem).toBeArrayOfSize(1); - expect(mongoItem[0].name).toBe('myName'); - expect(mongoItem[0].preview).toBeInstanceOf(Binary); - expect(mongoItem[0].preview.buffer.toString('utf8')).toBe('Baar'); - - console.log(mongoItem[0]); - expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); - expect(mongoItem[0].secondId).toBeInstanceOf(ObjectID); - expect(mongoItem[0]._id.toHexString()).toBe(instance._id); - expect(mongoItem[0].secondId.toHexString()).toBe(instance.secondId); + connection = await MongoClient.connect('mongodb://localhost:27017', { + useNewUrlParser: true, + }); + await connection.db('testing').dropDatabase(); + const database = new Database(connection, 'testing'); + + @Entity('SecondObjectId') + class SecondObjectId { + @ID() + @MongoIdType() + _id?: string; + + @StringType() + name?: string; + + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + + @MongoIdType() + secondId?: string; + } + + const instance = plainToClass(SecondObjectId, { + name: 'myName', + secondId: '5bf4a1ccce060e0b38864c9e', + preview: 'QmFhcg==', //Baar + }); + + await database.add(SecondObjectId, instance); + + const dbItem = await database.get(SecondObjectId, { name: 'myName' }); + expect(dbItem!.name).toBe('myName'); + + const dbItemBySecondId = await database.get(SecondObjectId, { + secondId: '5bf4a1ccce060e0b38864c9e', + }); + expect(dbItemBySecondId!.name).toBe('myName'); + + const collection = connection + .db('testing') + .collection(getCollectionName(SecondObjectId)); + const mongoItem = await collection.find().toArray(); + expect(mongoItem).toBeArrayOfSize(1); + expect(mongoItem[0].name).toBe('myName'); + expect(mongoItem[0].preview).toBeInstanceOf(Binary); + expect(mongoItem[0].preview.buffer.toString('utf8')).toBe('Baar'); + + console.log(mongoItem[0]); + expect(mongoItem[0]._id).toBeInstanceOf(ObjectID); + expect(mongoItem[0].secondId).toBeInstanceOf(ObjectID); + expect(mongoItem[0]._id.toHexString()).toBe(instance._id); + expect(mongoItem[0].secondId.toHexString()).toBe(instance.secondId); }); diff --git a/packages/mongo/tests/to-class.spec.ts b/packages/mongo/tests/to-class.spec.ts index 965c3ef45..8839bb424 100644 --- a/packages/mongo/tests/to-class.spec.ts +++ b/packages/mongo/tests/to-class.spec.ts @@ -1,841 +1,841 @@ import 'jest-extended'; import 'reflect-metadata'; import { - AnyType, - ClassArray, - classToPlain, - cloneClass, - EnumType, - Exclude, - getEntityName, - getEnumKeys, - getEnumLabels, - getIdField, - getIdFieldValue, - getValidEnumValue, - isExcluded, - isValidEnumValue, - NumberType, - plainToClass, - StringType, - uuid, - getReflectionType, - getParentReferenceClass, - ParentReference, - ClassCircular, - BooleanType, - Optional, - Class, - OnLoad, + AnyType, + ClassArray, + classToPlain, + cloneClass, + EnumType, + Exclude, + getEntityName, + getEnumKeys, + getEnumLabels, + getIdField, + getIdFieldValue, + getValidEnumValue, + isExcluded, + isValidEnumValue, + NumberType, + plainToClass, + StringType, + uuid, + getReflectionType, + getParentReferenceClass, + ParentReference, + ClassCircular, + BooleanType, + Optional, + Class, + OnLoad, } from '@marcj/marshal'; import { - now, - SimpleModel, - Plan, - SubModel, - CollectionWrapper, - StringCollectionWrapper, + now, + SimpleModel, + Plan, + SubModel, + CollectionWrapper, + StringCollectionWrapper, } from '@marcj/marshal/tests/entities'; import { Binary } from 'mongodb'; import { - ClassWithUnmetParent, - DocumentClass, - ImpossibleToMetDocumentClass, + ClassWithUnmetParent, + DocumentClass, + ImpossibleToMetDocumentClass, } from '@marcj/marshal/tests/document-scenario/DocumentClass'; import { PageCollection } from '@marcj/marshal/tests/document-scenario/PageCollection'; import { PageClass } from '@marcj/marshal/tests/document-scenario/PageClass'; import { classToMongo, mongoToClass, plainToMongo } from '../src/mapping'; test('test simple model', () => { - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - expect(getIdField(SimpleModel)).toBe('id'); - - expect(getIdField(SubModel)).toBe(null); - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - id: 'my-super-id', - name: 'myName', - }); - - expect(instance).toBeInstanceOf(SimpleModel); - expect(instance.id).toBe('my-super-id'); - expect(instance.name).toBe('myName'); - expect(instance.type).toBe(0); - expect(instance.plan).toBe(Plan.DEFAULT); - expect(instance.created).toBeDate(); - expect(instance.created).toBe(now); - - expect(getIdFieldValue(SimpleModel, instance)).toBe('my-super-id'); - } + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + expect(getIdField(SimpleModel)).toBe('id'); + + expect(getIdField(SubModel)).toBe(null); + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + id: 'my-super-id', + name: 'myName', + }); + + expect(instance).toBeInstanceOf(SimpleModel); + expect(instance.id).toBe('my-super-id'); + expect(instance.name).toBe('myName'); + expect(instance.type).toBe(0); + expect(instance.plan).toBe(Plan.DEFAULT); + expect(instance.created).toBeDate(); + expect(instance.created).toBe(now); + + expect(getIdFieldValue(SimpleModel, instance)).toBe('my-super-id'); + } }); test('test simple model all fields', () => { - expect(getEntityName(SimpleModel)).toBe('SimpleModel'); - expect(getIdField(SimpleModel)).toBe('id'); - - expect(getIdField(SubModel)).toBe(null); - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - type: 5, - plan: 1, - yesNo: '1', - created: 'Sat Oct 13 2018 14:17:35 GMT+0200', - children: [{ label: 'fooo' }, { label: 'barr' }], - childrenMap: { - foo: { - label: 'bar', - }, - foo2: { - label: 'bar2', - }, - }, - }); + expect(getEntityName(SimpleModel)).toBe('SimpleModel'); + expect(getIdField(SimpleModel)).toBe('id'); + + expect(getIdField(SubModel)).toBe(null); + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + type: 5, + plan: 1, + yesNo: '1', + created: 'Sat Oct 13 2018 14:17:35 GMT+0200', + children: [{ label: 'fooo' }, { label: 'barr' }], + childrenMap: { + foo: { + label: 'bar', + }, + foo2: { + label: 'bar2', + }, + }, + }); - expect(instance).toBeInstanceOf(SimpleModel); - expect(instance.id).toBeString(); - expect(instance.name).toBe('myName'); - expect(instance.type).toBe(5); - expect(instance.yesNo).toBe(true); - expect(instance.plan).toBe(Plan.PRO); - expect(instance.created).toBeDate(); - expect(instance.created).toEqual( - new Date('Sat Oct 13 2018 14:17:35 GMT+0200') - ); + expect(instance).toBeInstanceOf(SimpleModel); + expect(instance.id).toBeString(); + expect(instance.name).toBe('myName'); + expect(instance.type).toBe(5); + expect(instance.yesNo).toBe(true); + expect(instance.plan).toBe(Plan.PRO); + expect(instance.created).toBeDate(); + expect(instance.created).toEqual( + new Date('Sat Oct 13 2018 14:17:35 GMT+0200') + ); - expect(instance.children).toBeArrayOfSize(2); + expect(instance.children).toBeArrayOfSize(2); - expect(instance.children[0]).toBeInstanceOf(SubModel); - expect(instance.children[1]).toBeInstanceOf(SubModel); + expect(instance.children[0]).toBeInstanceOf(SubModel); + expect(instance.children[1]).toBeInstanceOf(SubModel); - expect(instance.children[0].label).toBe('fooo'); - expect(instance.children[1].label).toBe('barr'); + expect(instance.children[0].label).toBe('fooo'); + expect(instance.children[1].label).toBe('barr'); - expect(instance.childrenMap).toBeObject(); - expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); - expect(instance.childrenMap.foo2).toBeInstanceOf(SubModel); + expect(instance.childrenMap).toBeObject(); + expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); + expect(instance.childrenMap.foo2).toBeInstanceOf(SubModel); - expect(instance.childrenMap.foo.label).toBe('bar'); - expect(instance.childrenMap.foo2.label).toBe('bar2'); + expect(instance.childrenMap.foo.label).toBe('bar'); + expect(instance.childrenMap.foo2.label).toBe('bar2'); - expect(getIdFieldValue(SimpleModel, instance)).toBeString(); + expect(getIdFieldValue(SimpleModel, instance)).toBeString(); - const plain = classToPlain(SimpleModel, instance); - expect(plain.yesNo).toBeTrue(); - expect(plain.plan).toBe(1); + const plain = classToPlain(SimpleModel, instance); + expect(plain.yesNo).toBeTrue(); + expect(plain.plan).toBe(1); - const copy = cloneClass(instance); - expect(instance !== copy).toBeTrue(); - expect(instance.children[0] !== copy.children[0]).toBeTrue(); - expect(instance.children[1] !== copy.children[1]).toBeTrue(); - expect(instance.childrenMap.foo !== copy.childrenMap.foo).toBeTrue(); - expect(instance.childrenMap.foo2 !== copy.childrenMap.foo2).toBeTrue(); - expect(instance.created !== copy.created).toBeTrue(); + const copy = cloneClass(instance); + expect(instance !== copy).toBeTrue(); + expect(instance.children[0] !== copy.children[0]).toBeTrue(); + expect(instance.children[1] !== copy.children[1]).toBeTrue(); + expect(instance.childrenMap.foo !== copy.childrenMap.foo).toBeTrue(); + expect(instance.childrenMap.foo2 !== copy.childrenMap.foo2).toBeTrue(); + expect(instance.created !== copy.created).toBeTrue(); - expect(plain).toEqual(classToPlain(SimpleModel, copy)); - } + expect(plain).toEqual(classToPlain(SimpleModel, copy)); + } }); test('test simple model with not mapped fields', () => { - expect(isExcluded(SimpleModel, 'excluded', 'mongo')).toBeTrue(); - expect(isExcluded(SimpleModel, 'excluded', 'plain')).toBeTrue(); - - expect(isExcluded(SimpleModel, 'excludedForPlain', 'mongo')).toBeFalse(); - expect(isExcluded(SimpleModel, 'excludedForPlain', 'plain')).toBeTrue(); - - expect(isExcluded(SimpleModel, 'excludedForMongo', 'mongo')).toBeTrue(); - expect(isExcluded(SimpleModel, 'excludedForMongo', 'plain')).toBeFalse(); - - const instance = plainToClass(SimpleModel, { - name: 'myName', - type: 5, - yesNo: '1', - notMapped: { a: 'foo' }, - }); - - expect(instance).toBeInstanceOf(SimpleModel); - expect(instance.id).toBeString(); - expect(instance.name).toBe('myName'); - expect(instance.type).toBe(5); - expect(instance.yesNo).toBe(true); - expect(instance.notMapped).toEqual({}); - expect(instance.excluded).toBe('default'); - expect(instance.excludedForPlain).toBe('excludedForPlain'); - expect(instance.excludedForMongo).toBe('excludedForMongo'); - - const mongoEntry = plainToMongo(SimpleModel, { - id: uuid(), - name: 'myName', - type: 5, - yesNo: 'eads', - notMapped: { a: 'foo' }, - excludedForPlain: 'excludedForPlain', - }); - - expect(mongoEntry.id).toBeInstanceOf(Binary); - expect(mongoEntry.name).toBe('myName'); - expect(mongoEntry.type).toBe(5); - expect(mongoEntry.yesNo).toBe(false); - expect(mongoEntry.notMapped).toBeUndefined(); - expect(mongoEntry.excluded).toBeUndefined(); - expect(mongoEntry.excludedForPlain).toBe('excludedForPlain'); - expect(mongoEntry.excludedForMongo).toBeUndefined(); - - const plainObject = classToPlain(SimpleModel, instance); - - expect(plainObject.id).toBeString(); - expect(plainObject.name).toBe('myName'); - expect(plainObject.type).toBe(5); - expect(plainObject.notMapped).toBeUndefined(); - expect(plainObject.excluded).toBeUndefined(); - expect(plainObject.excludedForPlain).toBeUndefined(); - expect(plainObject.excludedForMongo).toBe('excludedForMongo'); -}); - -test('test @decorator', async () => { - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - stringChildrenCollection: ['Foo', 'Bar'], - }); + expect(isExcluded(SimpleModel, 'excluded', 'mongo')).toBeTrue(); + expect(isExcluded(SimpleModel, 'excluded', 'plain')).toBeTrue(); - expect(instance.name).toBe('myName'); - expect(instance.stringChildrenCollection).toBeInstanceOf( - StringCollectionWrapper - ); - expect(instance.stringChildrenCollection.items).toEqual(['Foo', 'Bar']); + expect(isExcluded(SimpleModel, 'excludedForPlain', 'mongo')).toBeFalse(); + expect(isExcluded(SimpleModel, 'excludedForPlain', 'plain')).toBeTrue(); - instance.stringChildrenCollection.add('Bar2'); - expect(instance.stringChildrenCollection.items[2]).toBe('Bar2'); + expect(isExcluded(SimpleModel, 'excludedForMongo', 'mongo')).toBeTrue(); + expect(isExcluded(SimpleModel, 'excludedForMongo', 'plain')).toBeFalse(); - const plain = classToPlain(SimpleModel, instance); - expect(plain.name).toBe('myName'); - expect(plain.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); + const instance = plainToClass(SimpleModel, { + name: 'myName', + type: 5, + yesNo: '1', + notMapped: { a: 'foo' }, + }); - const mongo = classToMongo(SimpleModel, instance); - expect(mongo.name).toBe('myName'); - expect(mongo.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); + expect(instance).toBeInstanceOf(SimpleModel); + expect(instance.id).toBeString(); + expect(instance.name).toBe('myName'); + expect(instance.type).toBe(5); + expect(instance.yesNo).toBe(true); + expect(instance.notMapped).toEqual({}); + expect(instance.excluded).toBe('default'); + expect(instance.excludedForPlain).toBe('excludedForPlain'); + expect(instance.excludedForMongo).toBe('excludedForMongo'); - const instance2 = toClass(SimpleModel, { - name: 'myName', - stringChildrenCollection: false, + const mongoEntry = plainToMongo(SimpleModel, { + id: uuid(), + name: 'myName', + type: 5, + yesNo: 'eads', + notMapped: { a: 'foo' }, + excludedForPlain: 'excludedForPlain', }); - expect(instance2.name).toBe('myName'); - expect(instance2.stringChildrenCollection).toBeInstanceOf( - StringCollectionWrapper - ); - expect(instance2.stringChildrenCollection.items).toEqual([]); - } + expect(mongoEntry.id).toBeInstanceOf(Binary); + expect(mongoEntry.name).toBe('myName'); + expect(mongoEntry.type).toBe(5); + expect(mongoEntry.yesNo).toBe(false); + expect(mongoEntry.notMapped).toBeUndefined(); + expect(mongoEntry.excluded).toBeUndefined(); + expect(mongoEntry.excludedForPlain).toBe('excludedForPlain'); + expect(mongoEntry.excludedForMongo).toBeUndefined(); + + const plainObject = classToPlain(SimpleModel, instance); + + expect(plainObject.id).toBeString(); + expect(plainObject.name).toBe('myName'); + expect(plainObject.type).toBe(5); + expect(plainObject.notMapped).toBeUndefined(); + expect(plainObject.excluded).toBeUndefined(); + expect(plainObject.excludedForPlain).toBeUndefined(); + expect(plainObject.excludedForMongo).toBe('excludedForMongo'); +}); + +test('test @decorator', async () => { + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + stringChildrenCollection: ['Foo', 'Bar'], + }); + + expect(instance.name).toBe('myName'); + expect(instance.stringChildrenCollection).toBeInstanceOf( + StringCollectionWrapper + ); + expect(instance.stringChildrenCollection.items).toEqual(['Foo', 'Bar']); + + instance.stringChildrenCollection.add('Bar2'); + expect(instance.stringChildrenCollection.items[2]).toBe('Bar2'); + + const plain = classToPlain(SimpleModel, instance); + expect(plain.name).toBe('myName'); + expect(plain.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); + + const mongo = classToMongo(SimpleModel, instance); + expect(mongo.name).toBe('myName'); + expect(mongo.stringChildrenCollection).toEqual(['Foo', 'Bar', 'Bar2']); + + const instance2 = toClass(SimpleModel, { + name: 'myName', + stringChildrenCollection: false, + }); + + expect(instance2.name).toBe('myName'); + expect(instance2.stringChildrenCollection).toBeInstanceOf( + StringCollectionWrapper + ); + expect(instance2.stringChildrenCollection.items).toEqual([]); + } }); test('test childrenMap', async () => { - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - childrenMap: { foo: { label: 'Foo' }, bar: { label: 'Bar' } }, - }); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + childrenMap: { foo: { label: 'Foo' }, bar: { label: 'Bar' } }, + }); - expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); - expect(instance.childrenMap.bar).toBeInstanceOf(SubModel); + expect(instance.childrenMap.foo).toBeInstanceOf(SubModel); + expect(instance.childrenMap.bar).toBeInstanceOf(SubModel); - expect(instance.childrenMap.foo.label).toBe('Foo'); - expect(instance.childrenMap.bar.label).toBe('Bar'); - } + expect(instance.childrenMap.foo.label).toBe('Foo'); + expect(instance.childrenMap.bar.label).toBe('Bar'); + } }); test('test allowNull', async () => { - class Model { - @StringType() - @Optional() - name: string | null = null; - } - - for (const toClass of [plainToClass, mongoToClass]) { - expect(toClass(Model, {}).name).toBe(null); - expect(toClass(Model, { name: null }).name).toBe(null); - expect(toClass(Model, { name: undefined }).name).toBe(null); - } + class Model { + @StringType() + @Optional() + name: string | null = null; + } + + for (const toClass of [plainToClass, mongoToClass]) { + expect(toClass(Model, {}).name).toBe(null); + expect(toClass(Model, { name: null }).name).toBe(null); + expect(toClass(Model, { name: undefined }).name).toBe(null); + } }); test('test OnLoad', async () => { - let ModelRef; - - class Sub { - @StringType() - name?: string; - - @AnyType() - onLoadCallback: (item: Sub) => void; - - @AnyType() - onFullLoadCallback: (item: Sub) => void; - - @ClassCircular(() => ModelRef) - @ParentReference() - parent?: any; - - constructor( - name: string, - onLoadCallback: (item: Sub) => void, - onFullLoadCallback: (item: Sub) => void - ) { - this.name = name; - this.onLoadCallback = onLoadCallback; - this.onFullLoadCallback = onFullLoadCallback; + let ModelRef; + + class Sub { + @StringType() + name?: string; + + @AnyType() + onLoadCallback: (item: Sub) => void; + + @AnyType() + onFullLoadCallback: (item: Sub) => void; + + @ClassCircular(() => ModelRef) + @ParentReference() + parent?: any; + + constructor( + name: string, + onLoadCallback: (item: Sub) => void, + onFullLoadCallback: (item: Sub) => void + ) { + this.name = name; + this.onLoadCallback = onLoadCallback; + this.onFullLoadCallback = onFullLoadCallback; + } + + @OnLoad() + onLoad() { + this.onLoadCallback(this); + } + + @OnLoad({ fullLoad: true }) + onFullLoad() { + this.onFullLoadCallback(this); + } } - @OnLoad() - onLoad() { - this.onLoadCallback(this); - } + class Model { + @StringType() + @Optional() + name: string | null = null; + + @Class(Sub) + sub?: Sub; - @OnLoad({ fullLoad: true }) - onFullLoad() { - this.onFullLoadCallback(this); + @Class(Sub) + sub2?: Sub; } - } - - class Model { - @StringType() - @Optional() - name: string | null = null; - - @Class(Sub) - sub?: Sub; - - @Class(Sub) - sub2?: Sub; - } - - ModelRef = Model; - - for (const toClass of [plainToClass, mongoToClass]) { - let onLoadedTriggered = false; - let onFullLoadedTriggered = false; - - const instance = toClass(Model, { - name: 'Root', - - sub: { - name: 'Hi', - - //on regular "OnLoad()" parent-references are not loaded yet - onLoadCallback: (item: Sub) => { - expect(item.parent.sub2).toBeUndefined(); - onLoadedTriggered = true; - }, - //on full "OnLoad({fullLoad: true})" all parent-references are loaded - onFullLoadCallback: (item: Sub) => { - expect(item.parent.sub2).toBeInstanceOf(Sub); - onFullLoadedTriggered = true; - }, - }, - - sub2: { - name: 'Hi2', - onLoadCallback: (item: Sub) => {}, - onFullLoadCallback: (item: Sub) => {}, - }, - }); - expect(instance.name).toBe('Root'); - expect(onLoadedTriggered).toBe(true); - expect(onFullLoadedTriggered).toBe(true); - } -}); + ModelRef = Model; -test('test setter/getter', async () => { - class Font { - name?: string; - } + for (const toClass of [plainToClass, mongoToClass]) { + let onLoadedTriggered = false; + let onFullLoadedTriggered = false; + + const instance = toClass(Model, { + name: 'Root', + + sub: { + name: 'Hi', - class Model { - @Exclude() - private _fonts?: Font[]; + //on regular "OnLoad()" parent-references are not loaded yet + onLoadCallback: (item: Sub) => { + expect(item.parent.sub2).toBeUndefined(); + onLoadedTriggered = true; + }, + //on full "OnLoad({fullLoad: true})" all parent-references are loaded + onFullLoadCallback: (item: Sub) => { + expect(item.parent.sub2).toBeInstanceOf(Sub); + onFullLoadedTriggered = true; + }, + }, + + sub2: { + name: 'Hi2', + onLoadCallback: (item: Sub) => {}, + onFullLoadCallback: (item: Sub) => {}, + }, + }); - get test() { - return true; + expect(instance.name).toBe('Root'); + expect(onLoadedTriggered).toBe(true); + expect(onFullLoadedTriggered).toBe(true); } +}); - @ClassArray(Font) - get fonts() { - return this._fonts; +test('test setter/getter', async () => { + class Font { + name?: string; } - set fonts(v) { - this._fonts = v; + class Model { + @Exclude() + private _fonts?: Font[]; + + get test() { + return true; + } + + @ClassArray(Font) + get fonts() { + return this._fonts; + } + + set fonts(v) { + this._fonts = v; + } } - } - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - fonts: [{ name: 'Arial' }, { name: 'Verdana' }], - }); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + fonts: [{ name: 'Arial' }, { name: 'Verdana' }], + }); - expect(instance.test).toBeTrue(); - expect(instance.fonts!.length).toBe(2); + expect(instance.test).toBeTrue(); + expect(instance.fonts!.length).toBe(2); - const plain = classToPlain(Model, instance); - expect(plain._fonts).toBeUndefined(); - expect(plain.fonts).toBeArrayOfSize(2); + const plain = classToPlain(Model, instance); + expect(plain._fonts).toBeUndefined(); + expect(plain.fonts).toBeArrayOfSize(2); - const mongo = classToMongo(Model, instance); - expect(mongo._fonts).toBeUndefined(); - expect(mongo.fonts).toBeArrayOfSize(2); - } + const mongo = classToMongo(Model, instance); + expect(mongo._fonts).toBeUndefined(); + expect(mongo.fonts).toBeArrayOfSize(2); + } }); test('test decorator complex', async () => { - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(SimpleModel, { - name: 'myName', - childrenCollection: [{ label: 'Foo' }, { label: 'Bar' }], - }); - - expect(instance.name).toBe('myName'); - expect(instance.childrenCollection).toBeInstanceOf(CollectionWrapper); - expect(instance.childrenCollection.items[0]).toBeInstanceOf(SubModel); - expect(instance.childrenCollection.items[1]).toBeInstanceOf(SubModel); - expect(instance.childrenCollection.items[0].label).toBe('Foo'); - expect(instance.childrenCollection.items[1].label).toBe('Bar'); - expect(instance.childrenCollection.items[1].constructorUsed).toBeTrue(); - - instance.childrenCollection.add(new SubModel('Bar2')); - expect(instance.childrenCollection.items[2].label).toEqual('Bar2'); - - const plain = classToPlain(SimpleModel, instance); - - expect(plain.name).toBe('myName'); - expect(plain.childrenCollection).toEqual([ - { label: 'Foo' }, - { label: 'Bar' }, - { label: 'Bar2' }, - ]); - - const mongo = classToMongo(SimpleModel, instance); - expect(mongo.name).toBe('myName'); - expect(mongo.childrenCollection).toEqual([ - { label: 'Foo' }, - { label: 'Bar' }, - { label: 'Bar2' }, - ]); - } + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(SimpleModel, { + name: 'myName', + childrenCollection: [{ label: 'Foo' }, { label: 'Bar' }], + }); + + expect(instance.name).toBe('myName'); + expect(instance.childrenCollection).toBeInstanceOf(CollectionWrapper); + expect(instance.childrenCollection.items[0]).toBeInstanceOf(SubModel); + expect(instance.childrenCollection.items[1]).toBeInstanceOf(SubModel); + expect(instance.childrenCollection.items[0].label).toBe('Foo'); + expect(instance.childrenCollection.items[1].label).toBe('Bar'); + expect(instance.childrenCollection.items[1].constructorUsed).toBeTrue(); + + instance.childrenCollection.add(new SubModel('Bar2')); + expect(instance.childrenCollection.items[2].label).toEqual('Bar2'); + + const plain = classToPlain(SimpleModel, instance); + + expect(plain.name).toBe('myName'); + expect(plain.childrenCollection).toEqual([ + { label: 'Foo' }, + { label: 'Bar' }, + { label: 'Bar2' }, + ]); + + const mongo = classToMongo(SimpleModel, instance); + expect(mongo.name).toBe('myName'); + expect(mongo.childrenCollection).toEqual([ + { label: 'Foo' }, + { label: 'Bar' }, + { label: 'Bar2' }, + ]); + } }); test('test @decorator with parent', async () => { - expect(getReflectionType(DocumentClass, 'pages')).toEqual({ - type: 'class', - typeValue: PageCollection, - }); - expect(getReflectionType(PageCollection, 'pages')).toEqual({ - type: 'class', - typeValue: PageClass, - }); - expect(getReflectionType(PageClass, 'parent')).toEqual({ - type: 'class', - typeValue: PageClass, - }); - expect(getReflectionType(PageClass, 'document')).toEqual({ - type: 'class', - typeValue: DocumentClass, - }); - expect(getReflectionType(PageClass, 'children')).toEqual({ - type: 'class', - typeValue: PageCollection, - }); - - expect(getParentReferenceClass(PageClass, 'parent')).toBe(PageClass); - - expect(() => { - const instance = mongoToClass(ClassWithUnmetParent, {}); - }).toThrow('ClassWithUnmetParent::parent is defined as'); - - expect(() => { - const instance = mongoToClass(PageClass, { - name: 'myName', + expect(getReflectionType(DocumentClass, 'pages')).toEqual({ + type: 'class', + typeValue: PageCollection, }); - }).toThrow('PageClass::document is in constructor has'); - - { - const doc = new DocumentClass(); - - const instance = mongoToClass( - PageClass, - { - name: 'myName', - }, - [doc] - ); - - expect(instance.document).toBe(doc); - } - - expect(() => { - const instance = mongoToClass(ImpossibleToMetDocumentClass, { - name: 'myName', - pages: [ - { - name: 'Foo', - children: [ - { - name: 'Foo.1', - }, - ], - }, - { name: 'Bar' }, - ], + expect(getReflectionType(PageCollection, 'pages')).toEqual({ + type: 'class', + typeValue: PageClass, }); - }).toThrow('PageClass::document is in constructor has'); - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(DocumentClass, { - name: 'myName', - page: { - name: 'RootPage', - children: [ - { name: 'RootPage.1' }, - { name: 'RootPage.2' }, - { name: 'RootPage.3' }, - ], - }, - pages: [ - { - name: 'Foo', - children: [ - { - name: 'Foo.1', - children: [ - { name: 'Foo.1.1' }, - { name: 'Foo.1.2' }, - { name: 'Foo.1.3' }, - ], - }, - ], - }, - { name: 'Bar' }, - ], + expect(getReflectionType(PageClass, 'parent')).toEqual({ + type: 'class', + typeValue: PageClass, + }); + expect(getReflectionType(PageClass, 'document')).toEqual({ + type: 'class', + typeValue: DocumentClass, + }); + expect(getReflectionType(PageClass, 'children')).toEqual({ + type: 'class', + typeValue: PageCollection, }); - expect(instance.name).toBe('myName'); - - expect(instance.page).toBeInstanceOf(PageClass); - expect(instance.page!.children.get(0)!.parent).toBe(instance.page); - expect(instance.page!.children.get(1)!.parent).toBe(instance.page); - expect(instance.page!.children.get(2)!.parent).toBe(instance.page); - - expect(instance.pages).toBeInstanceOf(PageCollection); - expect(instance.pages.count()).toBe(2); - expect(instance.pages.get(0)).toBeInstanceOf(PageClass); - expect(instance.pages.get(1)).toBeInstanceOf(PageClass); + expect(getParentReferenceClass(PageClass, 'parent')).toBe(PageClass); - expect(instance.pages.get(0)!.name).toBe('Foo'); - expect(instance.pages.get(1)!.name).toBe('Bar'); - expect(instance.pages.get(0)!.parent).toBeUndefined(); - expect(instance.pages.get(1)!.parent).toBeUndefined(); + expect(() => { + const instance = mongoToClass(ClassWithUnmetParent, {}); + }).toThrow('ClassWithUnmetParent::parent is defined as'); - expect(instance.pages.get(0)!.document).toBe(instance); - expect(instance.pages.get(1)!.document).toBe(instance); + expect(() => { + const instance = mongoToClass(PageClass, { + name: 'myName', + }); + }).toThrow('PageClass::document is in constructor has'); - expect(instance.pages.get(0)!.children).toBeInstanceOf(PageCollection); + { + const doc = new DocumentClass(); - const foo_1 = instance.pages.get(0)!.children.get(0); - expect(foo_1).toBeInstanceOf(PageClass); - expect(foo_1!.name).toBe('Foo.1'); - expect(foo_1!.document).toBe(instance); + const instance = mongoToClass( + PageClass, + { + name: 'myName', + }, + [doc] + ); - expect(foo_1!.parent).not.toBeUndefined(); - expect(foo_1!.parent!.name).toBe('Foo'); - expect(foo_1!.parent).toBe(instance.pages.get(0)); + expect(instance.document).toBe(doc); + } - expect(foo_1!.children.count()).toBe(3); - const foo_1_1 = foo_1!.children.get(0); - const foo_1_2 = foo_1!.children.get(1); - const foo_1_3 = foo_1!.children.get(2); + expect(() => { + const instance = mongoToClass(ImpossibleToMetDocumentClass, { + name: 'myName', + pages: [ + { + name: 'Foo', + children: [ + { + name: 'Foo.1', + }, + ], + }, + { name: 'Bar' }, + ], + }); + }).toThrow('PageClass::document is in constructor has'); - expect(foo_1_1).toBeInstanceOf(PageClass); - expect(foo_1_2).toBeInstanceOf(PageClass); - expect(foo_1_3).toBeInstanceOf(PageClass); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(DocumentClass, { + name: 'myName', + page: { + name: 'RootPage', + children: [ + { name: 'RootPage.1' }, + { name: 'RootPage.2' }, + { name: 'RootPage.3' }, + ], + }, + pages: [ + { + name: 'Foo', + children: [ + { + name: 'Foo.1', + children: [ + { name: 'Foo.1.1' }, + { name: 'Foo.1.2' }, + { name: 'Foo.1.3' }, + ], + }, + ], + }, + { name: 'Bar' }, + ], + }); + + expect(instance.name).toBe('myName'); + + expect(instance.page).toBeInstanceOf(PageClass); + expect(instance.page!.children.get(0)!.parent).toBe(instance.page); + expect(instance.page!.children.get(1)!.parent).toBe(instance.page); + expect(instance.page!.children.get(2)!.parent).toBe(instance.page); + + expect(instance.pages).toBeInstanceOf(PageCollection); + expect(instance.pages.count()).toBe(2); + expect(instance.pages.get(0)).toBeInstanceOf(PageClass); + expect(instance.pages.get(1)).toBeInstanceOf(PageClass); + + expect(instance.pages.get(0)!.name).toBe('Foo'); + expect(instance.pages.get(1)!.name).toBe('Bar'); + expect(instance.pages.get(0)!.parent).toBeUndefined(); + expect(instance.pages.get(1)!.parent).toBeUndefined(); + + expect(instance.pages.get(0)!.document).toBe(instance); + expect(instance.pages.get(1)!.document).toBe(instance); + + expect(instance.pages.get(0)!.children).toBeInstanceOf(PageCollection); + + const foo_1 = instance.pages.get(0)!.children.get(0); + expect(foo_1).toBeInstanceOf(PageClass); + expect(foo_1!.name).toBe('Foo.1'); + expect(foo_1!.document).toBe(instance); + + expect(foo_1!.parent).not.toBeUndefined(); + expect(foo_1!.parent!.name).toBe('Foo'); + expect(foo_1!.parent).toBe(instance.pages.get(0)); + + expect(foo_1!.children.count()).toBe(3); + const foo_1_1 = foo_1!.children.get(0); + const foo_1_2 = foo_1!.children.get(1); + const foo_1_3 = foo_1!.children.get(2); + + expect(foo_1_1).toBeInstanceOf(PageClass); + expect(foo_1_2).toBeInstanceOf(PageClass); + expect(foo_1_3).toBeInstanceOf(PageClass); + + expect(foo_1_1!.parent).toBeInstanceOf(PageClass); + expect(foo_1_2!.parent).toBeInstanceOf(PageClass); + expect(foo_1_3!.parent).toBeInstanceOf(PageClass); + + expect(foo_1_1!.parent).toBe(foo_1); + expect(foo_1_2!.parent).toBe(foo_1); + expect(foo_1_3!.parent).toBe(foo_1); + + expect(foo_1_1!.document).toBe(instance); + expect(foo_1_2!.document).toBe(instance); + expect(foo_1_3!.document).toBe(instance); + + expect(foo_1_1!.parent!.name).toBe('Foo.1'); + expect(foo_1_2!.parent!.name).toBe('Foo.1'); + expect(foo_1_3!.parent!.name).toBe('Foo.1'); + + expect(() => { + const clone = cloneClass(instance.page); + }).toThrow('PageClass::document is in constructor has'); + + const clone = cloneClass(instance.page, [instance]); + expect(clone).toBeInstanceOf(PageClass); + expect(clone!.document).toBe(instance); + expect(clone!.parent).toBeUndefined(); + + for (const toPlain of [classToPlain, classToMongo]) { + const plain = toPlain(DocumentClass, instance); + + expect(plain.name).toBe('myName'); + expect(plain.pages[0].name).toEqual('Foo'); + expect(plain.pages[1].name).toEqual('Bar'); + expect(plain.pages[0].children[0].name).toEqual('Foo.1'); + expect(plain.pages[0].parent).toBeUndefined(); + + expect(plain.pages[0].parent).toBeUndefined(); + } + } +}); - expect(foo_1_1!.parent).toBeInstanceOf(PageClass); - expect(foo_1_2!.parent).toBeInstanceOf(PageClass); - expect(foo_1_3!.parent).toBeInstanceOf(PageClass); +test('simple string + number + boolean', () => { + class Model { + @StringType() + name?: string; - expect(foo_1_1!.parent).toBe(foo_1); - expect(foo_1_2!.parent).toBe(foo_1); - expect(foo_1_3!.parent).toBe(foo_1); + @NumberType() + age?: string; - expect(foo_1_1!.document).toBe(instance); - expect(foo_1_2!.document).toBe(instance); - expect(foo_1_3!.document).toBe(instance); + @BooleanType() + yesNo?: boolean; + } - expect(foo_1_1!.parent!.name).toBe('Foo.1'); - expect(foo_1_2!.parent!.name).toBe('Foo.1'); - expect(foo_1_3!.parent!.name).toBe('Foo.1'); + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + name: 1, + age: '2', + yesNo: 'false', + }); + expect(instance.name).toBe('1'); + expect(instance.age).toBe(2); + + expect(toClass(Model, { yesNo: 'false' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: '0' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: false }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: 0 }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: 'nothing' }).yesNo).toBeFalse(); + expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); + + expect(toClass(Model, { yesNo: 'true' }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: '1' }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: true }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: 1 }).yesNo).toBeTrue(); + expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); + } +}); - expect(() => { - const clone = cloneClass(instance.page); - }).toThrow('PageClass::document is in constructor has'); +test('cloneClass', () => { + class SubModel { + @StringType() + name?: string; + } - const clone = cloneClass(instance.page, [instance]); - expect(clone).toBeInstanceOf(PageClass); - expect(clone!.document).toBe(instance); - expect(clone!.parent).toBeUndefined(); + class DataStruct { + @StringType() + name?: string; + } - for (const toPlain of [classToPlain, classToMongo]) { - const plain = toPlain(DocumentClass, instance); + class Model { + @AnyType() + data: any; - expect(plain.name).toBe('myName'); - expect(plain.pages[0].name).toEqual('Foo'); - expect(plain.pages[1].name).toEqual('Bar'); - expect(plain.pages[0].children[0].name).toEqual('Foo.1'); - expect(plain.pages[0].parent).toBeUndefined(); + @Class(DataStruct) + dataStruct?: DataStruct; - expect(plain.pages[0].parent).toBeUndefined(); + @ClassArray(SubModel) + subs?: SubModel[]; } - } -}); -test('simple string + number + boolean', () => { - class Model { - @StringType() - name?: string; - - @NumberType() - age?: string; - - @BooleanType() - yesNo?: boolean; - } - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - name: 1, - age: '2', - yesNo: 'false', - }); - expect(instance.name).toBe('1'); - expect(instance.age).toBe(2); - - expect(toClass(Model, { yesNo: 'false' }).yesNo).toBeFalse(); - expect(toClass(Model, { yesNo: '0' }).yesNo).toBeFalse(); - expect(toClass(Model, { yesNo: false }).yesNo).toBeFalse(); - expect(toClass(Model, { yesNo: 0 }).yesNo).toBeFalse(); - expect(toClass(Model, { yesNo: 'nothing' }).yesNo).toBeFalse(); - expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); - - expect(toClass(Model, { yesNo: 'true' }).yesNo).toBeTrue(); - expect(toClass(Model, { yesNo: '1' }).yesNo).toBeTrue(); - expect(toClass(Model, { yesNo: true }).yesNo).toBeTrue(); - expect(toClass(Model, { yesNo: 1 }).yesNo).toBeTrue(); - expect(toClass(Model, { yesNo: null }).yesNo).toBeNull(); - } + const data = { + a: 'true', + }; + + const dataStruct = { + name: 'Foo', + }; + + for (const toClass of [plainToClass, mongoToClass]) { + const instance = toClass(Model, { + data: data, + dataStruct: dataStruct, + subs: [{ name: 'foo' }], + }); + + expect(instance.data).toEqual({ a: 'true' }); + expect(instance.data).not.toBe(data); + expect(instance.dataStruct!.name).toBe('Foo'); + expect(instance.dataStruct).toEqual(dataStruct); + expect(instance.dataStruct).not.toBe(dataStruct); + + const cloned = cloneClass(instance); + expect(cloned.data).toEqual({ a: 'true' }); + expect(cloned.data).not.toBe(data); + expect(cloned.dataStruct!.name).toBe('Foo'); + expect(cloned.dataStruct).toEqual(dataStruct); + expect(cloned.dataStruct).not.toBe(dataStruct); + + expect(cloned.subs![0]).not.toBe(instance.subs![0]); + } }); -test('cloneClass', () => { - class SubModel { - @StringType() - name?: string; - } - - class DataStruct { - @StringType() - name?: string; - } - - class Model { - @AnyType() - data: any; - - @Class(DataStruct) - dataStruct?: DataStruct; - - @ClassArray(SubModel) - subs?: SubModel[]; - } - - const data = { - a: 'true', - }; - - const dataStruct = { - name: 'Foo', - }; - - for (const toClass of [plainToClass, mongoToClass]) { - const instance = toClass(Model, { - data: data, - dataStruct: dataStruct, - subs: [{ name: 'foo' }], - }); +test('enums', () => { + enum Enum1 { + first, + second, + } - expect(instance.data).toEqual({ a: 'true' }); - expect(instance.data).not.toBe(data); - expect(instance.dataStruct!.name).toBe('Foo'); - expect(instance.dataStruct).toEqual(dataStruct); - expect(instance.dataStruct).not.toBe(dataStruct); - - const cloned = cloneClass(instance); - expect(cloned.data).toEqual({ a: 'true' }); - expect(cloned.data).not.toBe(data); - expect(cloned.dataStruct!.name).toBe('Foo'); - expect(cloned.dataStruct).toEqual(dataStruct); - expect(cloned.dataStruct).not.toBe(dataStruct); - - expect(cloned.subs![0]).not.toBe(instance.subs![0]); - } -}); + enum Enum2 { + first = 'z', + second = 'x', + } -test('enums', () => { - enum Enum1 { - first, - second, - } - - enum Enum2 { - first = 'z', - second = 'x', - } - - enum Enum3 { - first = 200, - second = 100, - } - - enum Enum4 { - first = '200', - second = 'x', - } - - class Model { - @EnumType(Enum1) - enum1?: Enum1; - - @EnumType(Enum2) - enum2?: Enum2; - - @EnumType(Enum3) - enum3?: Enum3; - - @EnumType(Enum4) - enum4?: Enum4; - - @EnumType(Enum4, true) - enumLabels?: Enum4; - } - - expect(getEnumLabels(Enum1)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum2)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum3)).toEqual(['first', 'second']); - expect(getEnumLabels(Enum4)).toEqual(['first', 'second']); - - expect(getEnumKeys(Enum1)).toEqual([0, 1]); - expect(getEnumKeys(Enum2)).toEqual(['z', 'x']); - expect(getEnumKeys(Enum3)).toEqual([200, 100]); - expect(getEnumKeys(Enum4)).toEqual(['200', 'x']); - - expect(isValidEnumValue(Enum1, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum1, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum1, 0)).toBeTrue(); - expect(isValidEnumValue(Enum1, 1)).toBeTrue(); - expect(isValidEnumValue(Enum1, '0')).toBeTrue(); - expect(isValidEnumValue(Enum1, '1')).toBeTrue(); - expect(isValidEnumValue(Enum1, 2)).toBeFalse(); - expect(isValidEnumValue(Enum1, '2')).toBeFalse(); - - expect(getValidEnumValue(Enum1, 1)).toBe(1); - expect(getValidEnumValue(Enum1, '1')).toBe(1); - expect(getValidEnumValue(Enum1, '2')).toBeUndefined(); - - expect(isValidEnumValue(Enum2, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum2, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum2, 'z')).toBeTrue(); - expect(isValidEnumValue(Enum2, 'x')).toBeTrue(); - - expect(getValidEnumValue(Enum2, 1)).toBeUndefined(); - expect(getValidEnumValue(Enum2, 'x')).toBe('x'); - expect(getValidEnumValue(Enum2, '2')).toBeUndefined(); - - expect(isValidEnumValue(Enum3, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum3, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum3, '200')).toBeTrue(); - expect(isValidEnumValue(Enum3, '100')).toBeTrue(); - expect(isValidEnumValue(Enum3, 200)).toBeTrue(); - expect(isValidEnumValue(Enum3, 100)).toBeTrue(); - - expect(isValidEnumValue(Enum4, 'first')).toBeFalse(); - expect(isValidEnumValue(Enum4, 'second')).toBeFalse(); - expect(isValidEnumValue(Enum4, '200')).toBeTrue(); - expect(isValidEnumValue(Enum4, 200)).toBeTrue(); - expect(isValidEnumValue(Enum4, 'x')).toBeTrue(); - - expect(isValidEnumValue(Enum4, 'first', true)).toBeTrue(); - expect(isValidEnumValue(Enum4, 'second', true)).toBeTrue(); - - expect(getValidEnumValue(Enum4, 1)).toBeUndefined(); - expect(getValidEnumValue(Enum4, 200)).toBe('200'); - expect(getValidEnumValue(Enum4, '200')).toBe('200'); - expect(getValidEnumValue(Enum4, '2')).toBeUndefined(); - expect(getValidEnumValue(Enum4, 'first', true)).toBe('200'); - expect(getValidEnumValue(Enum4, 'second', true)).toBe('x'); - - for (const toClass of [plainToClass, mongoToClass]) { - { - const instance = toClass(Model, { - enum1: 1, - enum2: 'x', - enum3: 100, - enum4: 'x', - enumLabels: 'x', - }); - - expect(instance.enum1).toBe(Enum1.second); - expect(instance.enum2).toBe(Enum2.second); - expect(instance.enum3).toBe(Enum3.second); - expect(instance.enum4).toBe(Enum4.second); - expect(instance.enumLabels).toBe(Enum4.second); + enum Enum3 { + first = 200, + second = 100, } - { - const instance = toClass(Model, { - enum1: '1', - enum2: 'x', - enum3: '100', - enum4: 'x', - enumLabels: 'second', - }); - - expect(instance.enum1).toBe(Enum1.second); - expect(instance.enum2).toBe(Enum2.second); - expect(instance.enum3).toBe(Enum3.second); - expect(instance.enum4).toBe(Enum4.second); - expect(instance.enumLabels).toBe(Enum4.second); + enum Enum4 { + first = '200', + second = 'x', } - expect(() => { - const instance = toClass(Model, { - enum1: 2, - }); - }).toThrow('Invalid ENUM given in property'); + class Model { + @EnumType(Enum1) + enum1?: Enum1; - expect(() => { - const instance = plainToClass(Model, { - enum2: 2, - }); - }).toThrow('Invalid ENUM given in property'); + @EnumType(Enum2) + enum2?: Enum2; - expect(() => { - const instance = toClass(Model, { - enum3: 2, - }); - }).toThrow('Invalid ENUM given in property'); + @EnumType(Enum3) + enum3?: Enum3; - expect(() => { - const instance = toClass(Model, { - enum3: 4, - }); - }).toThrow('Invalid ENUM given in property'); - } + @EnumType(Enum4) + enum4?: Enum4; + + @EnumType(Enum4, true) + enumLabels?: Enum4; + } + + expect(getEnumLabels(Enum1)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum2)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum3)).toEqual(['first', 'second']); + expect(getEnumLabels(Enum4)).toEqual(['first', 'second']); + + expect(getEnumKeys(Enum1)).toEqual([0, 1]); + expect(getEnumKeys(Enum2)).toEqual(['z', 'x']); + expect(getEnumKeys(Enum3)).toEqual([200, 100]); + expect(getEnumKeys(Enum4)).toEqual(['200', 'x']); + + expect(isValidEnumValue(Enum1, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum1, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum1, 0)).toBeTrue(); + expect(isValidEnumValue(Enum1, 1)).toBeTrue(); + expect(isValidEnumValue(Enum1, '0')).toBeTrue(); + expect(isValidEnumValue(Enum1, '1')).toBeTrue(); + expect(isValidEnumValue(Enum1, 2)).toBeFalse(); + expect(isValidEnumValue(Enum1, '2')).toBeFalse(); + + expect(getValidEnumValue(Enum1, 1)).toBe(1); + expect(getValidEnumValue(Enum1, '1')).toBe(1); + expect(getValidEnumValue(Enum1, '2')).toBeUndefined(); + + expect(isValidEnumValue(Enum2, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum2, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum2, 'z')).toBeTrue(); + expect(isValidEnumValue(Enum2, 'x')).toBeTrue(); + + expect(getValidEnumValue(Enum2, 1)).toBeUndefined(); + expect(getValidEnumValue(Enum2, 'x')).toBe('x'); + expect(getValidEnumValue(Enum2, '2')).toBeUndefined(); + + expect(isValidEnumValue(Enum3, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum3, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum3, '200')).toBeTrue(); + expect(isValidEnumValue(Enum3, '100')).toBeTrue(); + expect(isValidEnumValue(Enum3, 200)).toBeTrue(); + expect(isValidEnumValue(Enum3, 100)).toBeTrue(); + + expect(isValidEnumValue(Enum4, 'first')).toBeFalse(); + expect(isValidEnumValue(Enum4, 'second')).toBeFalse(); + expect(isValidEnumValue(Enum4, '200')).toBeTrue(); + expect(isValidEnumValue(Enum4, 200)).toBeTrue(); + expect(isValidEnumValue(Enum4, 'x')).toBeTrue(); + + expect(isValidEnumValue(Enum4, 'first', true)).toBeTrue(); + expect(isValidEnumValue(Enum4, 'second', true)).toBeTrue(); + + expect(getValidEnumValue(Enum4, 1)).toBeUndefined(); + expect(getValidEnumValue(Enum4, 200)).toBe('200'); + expect(getValidEnumValue(Enum4, '200')).toBe('200'); + expect(getValidEnumValue(Enum4, '2')).toBeUndefined(); + expect(getValidEnumValue(Enum4, 'first', true)).toBe('200'); + expect(getValidEnumValue(Enum4, 'second', true)).toBe('x'); + + for (const toClass of [plainToClass, mongoToClass]) { + { + const instance = toClass(Model, { + enum1: 1, + enum2: 'x', + enum3: 100, + enum4: 'x', + enumLabels: 'x', + }); + + expect(instance.enum1).toBe(Enum1.second); + expect(instance.enum2).toBe(Enum2.second); + expect(instance.enum3).toBe(Enum3.second); + expect(instance.enum4).toBe(Enum4.second); + expect(instance.enumLabels).toBe(Enum4.second); + } + + { + const instance = toClass(Model, { + enum1: '1', + enum2: 'x', + enum3: '100', + enum4: 'x', + enumLabels: 'second', + }); + + expect(instance.enum1).toBe(Enum1.second); + expect(instance.enum2).toBe(Enum2.second); + expect(instance.enum3).toBe(Enum3.second); + expect(instance.enum4).toBe(Enum4.second); + expect(instance.enumLabels).toBe(Enum4.second); + } + + expect(() => { + const instance = toClass(Model, { + enum1: 2, + }); + }).toThrow('Invalid ENUM given in property'); + + expect(() => { + const instance = plainToClass(Model, { + enum2: 2, + }); + }).toThrow('Invalid ENUM given in property'); + + expect(() => { + const instance = toClass(Model, { + enum3: 2, + }); + }).toThrow('Invalid ENUM given in property'); + + expect(() => { + const instance = toClass(Model, { + enum3: 4, + }); + }).toThrow('Invalid ENUM given in property'); + } }); diff --git a/packages/mongo/tests/to-mongo.spec.ts b/packages/mongo/tests/to-mongo.spec.ts index 7d1f578cb..4c80ae5f2 100644 --- a/packages/mongo/tests/to-mongo.spec.ts +++ b/packages/mongo/tests/to-mongo.spec.ts @@ -1,22 +1,22 @@ import 'jest-extended'; import 'reflect-metadata'; import { - BinaryType, - classToPlain, - EnumType, - getReflectionType, - getResolvedReflection, - MongoIdType, - plainToClass, - UUIDType, + BinaryType, + classToPlain, + EnumType, + getReflectionType, + getResolvedReflection, + MongoIdType, + plainToClass, + UUIDType, } from '@marcj/marshal'; import { Plan, SimpleModel, SubModel } from '@marcj/marshal/tests/entities'; import { Binary, ObjectID } from 'mongodb'; import { - classToMongo, - mongoToClass, - partialClassToMongo, - partialPlainToMongo, + classToMongo, + mongoToClass, + partialClassToMongo, + partialPlainToMongo, } from '../src/mapping'; import { Buffer } from 'buffer'; import { DocumentClass } from '@marcj/marshal/tests/document-scenario/DocumentClass'; @@ -24,272 +24,272 @@ import { PageCollection } from '@marcj/marshal/tests/document-scenario/PageColle import { PageClass } from '@marcj/marshal/tests/document-scenario/PageClass'; test('test simple model', () => { - const instance = new SimpleModel('myName'); - const mongo = classToMongo(SimpleModel, instance); + const instance = new SimpleModel('myName'); + const mongo = classToMongo(SimpleModel, instance); - expect(mongo['id']).toBeInstanceOf(Binary); - expect(mongo['name']).toBe('myName'); + expect(mongo['id']).toBeInstanceOf(Binary); + expect(mongo['name']).toBe('myName'); }); test('test simple model all fields', () => { - const instance = new SimpleModel('myName'); - instance.plan = Plan.PRO; - instance.type = 5; - instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); - instance.children.push(new SubModel('fooo')); - instance.children.push(new SubModel('barr')); - - instance.childrenMap.foo = new SubModel('bar'); - instance.childrenMap.foo2 = new SubModel('bar2'); - - const mongo = classToMongo(SimpleModel, instance); - - expect(mongo['id']).toBeInstanceOf(Binary); - expect(mongo['name']).toBe('myName'); - expect(mongo['type']).toBe(5); - expect(mongo['plan']).toBe(Plan.PRO); - expect(mongo['created']).toBeDate(); - expect(mongo['children']).toBeArrayOfSize(2); - expect(mongo['children'][0]).toBeObject(); - expect(mongo['children'][0].label).toBe('fooo'); - expect(mongo['children'][1].label).toBe('barr'); - - expect(mongo['childrenMap']).toBeObject(); - expect(mongo['childrenMap'].foo).toBeObject(); - expect(mongo['childrenMap'].foo.label).toBe('bar'); - expect(mongo['childrenMap'].foo2.label).toBe('bar2'); + const instance = new SimpleModel('myName'); + instance.plan = Plan.PRO; + instance.type = 5; + instance.created = new Date('Sat Oct 13 2018 14:17:35 GMT+0200'); + instance.children.push(new SubModel('fooo')); + instance.children.push(new SubModel('barr')); + + instance.childrenMap.foo = new SubModel('bar'); + instance.childrenMap.foo2 = new SubModel('bar2'); + + const mongo = classToMongo(SimpleModel, instance); + + expect(mongo['id']).toBeInstanceOf(Binary); + expect(mongo['name']).toBe('myName'); + expect(mongo['type']).toBe(5); + expect(mongo['plan']).toBe(Plan.PRO); + expect(mongo['created']).toBeDate(); + expect(mongo['children']).toBeArrayOfSize(2); + expect(mongo['children'][0]).toBeObject(); + expect(mongo['children'][0].label).toBe('fooo'); + expect(mongo['children'][1].label).toBe('barr'); + + expect(mongo['childrenMap']).toBeObject(); + expect(mongo['childrenMap'].foo).toBeObject(); + expect(mongo['childrenMap'].foo.label).toBe('bar'); + expect(mongo['childrenMap'].foo2.label).toBe('bar2'); }); test('convert IDs and invalid values', () => { - enum Enum { - first, - second, - } + enum Enum { + first, + second, + } - class Model { - @MongoIdType() - id2?: string; + class Model { + @MongoIdType() + id2?: string; - @UUIDType() - uuid?: string; + @UUIDType() + uuid?: string; - @EnumType(Enum) - enum?: Enum; - } + @EnumType(Enum) + enum?: Enum; + } - const instance = new Model(); - instance.id2 = '5be340cb2ffb5e901a9b62e4'; - - const mongo = classToMongo(Model, instance); - expect(mongo.id2).toBeInstanceOf(ObjectID); - expect(mongo.id2.toHexString()).toBe('5be340cb2ffb5e901a9b62e4'); - - expect(() => { const instance = new Model(); - instance.id2 = 'notavalidId'; - const mongo = classToMongo(Model, instance); - }).toThrow('Invalid ObjectID given in property'); + instance.id2 = '5be340cb2ffb5e901a9b62e4'; - expect(() => { - const instance = new Model(); - instance.uuid = 'notavalidId'; const mongo = classToMongo(Model, instance); - }).toThrow('Invalid UUID given in property'); + expect(mongo.id2).toBeInstanceOf(ObjectID); + expect(mongo.id2.toHexString()).toBe('5be340cb2ffb5e901a9b62e4'); + + expect(() => { + const instance = new Model(); + instance.id2 = 'notavalidId'; + const mongo = classToMongo(Model, instance); + }).toThrow('Invalid ObjectID given in property'); + + expect(() => { + const instance = new Model(); + instance.uuid = 'notavalidId'; + const mongo = classToMongo(Model, instance); + }).toThrow('Invalid UUID given in property'); }); test('binary', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } - const i = new Model(); - expect(i.preview.toString('utf8')).toBe('FooBar'); + const i = new Model(); + expect(i.preview.toString('utf8')).toBe('FooBar'); - const mongo = classToMongo(Model, i); - expect(mongo.preview).toBeInstanceOf(Binary); - expect((mongo.preview as Binary).length()).toBe(6); + const mongo = classToMongo(Model, i); + expect(mongo.preview).toBeInstanceOf(Binary); + expect((mongo.preview as Binary).length()).toBe(6); }); test('binary from mongo', () => { - class Model { - @BinaryType() - preview: Buffer = new Buffer('FooBar', 'utf8'); - } + class Model { + @BinaryType() + preview: Buffer = new Buffer('FooBar', 'utf8'); + } - const i = mongoToClass(Model, { - preview: new Binary(new Buffer('FooBar', 'utf8')), - }); + const i = mongoToClass(Model, { + preview: new Binary(new Buffer('FooBar', 'utf8')), + }); - expect(i.preview.length).toBe(6); - expect(i.preview.toString('utf8')).toBe('FooBar'); + expect(i.preview.length).toBe(6); + expect(i.preview.toString('utf8')).toBe('FooBar'); }); test('partial 2', () => { - const instance = partialClassToMongo(SimpleModel, { - name: 'Hi', - 'children.0.label': 'Foo', - }); - - expect(instance).not.toBeInstanceOf(SimpleModel); - expect(instance['id']).toBeUndefined(); - expect(instance['type']).toBeUndefined(); - expect(instance.name).toBe('Hi'); - expect(instance['children.0.label']).toBe('Foo'); - - { - expect( - partialClassToMongo(SimpleModel, { - 'children.0.label': 2, - }) - ).toEqual({ 'children.0.label': '2' }); - - const i = partialClassToMongo(SimpleModel, { - 'children.0': new SubModel('3'), + const instance = partialClassToMongo(SimpleModel, { + name: 'Hi', + 'children.0.label': 'Foo', }); - expect(i['children.0'].label).toBe('3'); - } - - { - expect( - partialPlainToMongo(SimpleModel, { - 'children.0.label': 2, - }) - ).toEqual({ 'children.0.label': '2' }); - const i = partialPlainToMongo(SimpleModel, { - 'children.0': { label: 3 }, - }); - expect(i['children.0'].label).toBe('3'); - } + expect(instance).not.toBeInstanceOf(SimpleModel); + expect(instance['id']).toBeUndefined(); + expect(instance['type']).toBeUndefined(); + expect(instance.name).toBe('Hi'); + expect(instance['children.0.label']).toBe('Foo'); + + { + expect( + partialClassToMongo(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i = partialClassToMongo(SimpleModel, { + 'children.0': new SubModel('3'), + }); + expect(i['children.0'].label).toBe('3'); + } + + { + expect( + partialPlainToMongo(SimpleModel, { + 'children.0.label': 2, + }) + ).toEqual({ 'children.0.label': '2' }); + + const i = partialPlainToMongo(SimpleModel, { + 'children.0': { label: 3 }, + }); + expect(i['children.0'].label).toBe('3'); + } }); test('partial 3', () => { - { - const i = partialClassToMongo(SimpleModel, { - children: [new SubModel('3')], - }); - expect(i['children'][0].label).toBe('3'); - } - - { - const i = partialPlainToMongo(SimpleModel, { - children: [{ label: 3 }], - }); - expect(i['children'][0].label).toBe('3'); - } + { + const i = partialClassToMongo(SimpleModel, { + children: [new SubModel('3')], + }); + expect(i['children'][0].label).toBe('3'); + } + + { + const i = partialPlainToMongo(SimpleModel, { + children: [{ label: 3 }], + }); + expect(i['children'][0].label).toBe('3'); + } }); test('partial with required', () => { - { - expect(() => { - partialPlainToMongo(SimpleModel, { - children: [{}], - }); - }).toThrow( - 'Missing value for SubModel::children. Can not convert to mongo' - ); - } + { + expect(() => { + partialPlainToMongo(SimpleModel, { + children: [{}], + }); + }).toThrow( + 'Missing value for SubModel::children. Can not convert to mongo' + ); + } }); test('partial 4', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'stringChildrenCollection.0': 4, - }); - expect(i['stringChildrenCollection.0']).toBe('4'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'stringChildrenCollection.0': 4, - }); - expect(i['stringChildrenCollection.0']).toBe('4'); - } + { + const i = partialClassToMongo(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i['stringChildrenCollection.0']).toBe('4'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'stringChildrenCollection.0': 4, + }); + expect(i['stringChildrenCollection.0']).toBe('4'); + } }); test('partial 5', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'childrenMap.foo.label': 5, - }); - expect(i['childrenMap.foo.label']).toBe('5'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'childrenMap.foo.label': 5, - }); - expect(i['childrenMap.foo.label']).toBe('5'); - } + { + const i = partialClassToMongo(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i['childrenMap.foo.label']).toBe('5'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'childrenMap.foo.label': 5, + }); + expect(i['childrenMap.foo.label']).toBe('5'); + } }); test('partial 6', () => { - { - const i = partialClassToMongo(SimpleModel, { - types: [6, 7], - }); - expect(i['types']).toEqual(['6', '7']); - } - { - const i = partialPlainToMongo(SimpleModel, { - types: [6, 7], - }); - expect(i['types']).toEqual(['6', '7']); - } + { + const i = partialClassToMongo(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); + } + { + const i = partialPlainToMongo(SimpleModel, { + types: [6, 7], + }); + expect(i['types']).toEqual(['6', '7']); + } }); test('partial 7', () => { - { - const i = partialClassToMongo(SimpleModel, { - 'types.0': [7], - }); - expect(i['types.0']).toEqual('7'); - } - { - const i = partialPlainToMongo(SimpleModel, { - 'types.0': [7], - }); - expect(i['types.0']).toEqual('7'); - } + { + const i = partialClassToMongo(SimpleModel, { + 'types.0': [7], + }); + expect(i['types.0']).toEqual('7'); + } + { + const i = partialPlainToMongo(SimpleModel, { + 'types.0': [7], + }); + expect(i['types.0']).toEqual('7'); + } }); test('partial document', () => { - const doc = new DocumentClass(); - const document = partialClassToMongo(DocumentClass, { - 'pages.0.name': 5, - 'pages.0.children.0.name': 6, - 'pages.0.children': new PageCollection([new PageClass(doc, '7')]), - }); - expect(document['pages.0.name']).toBe('5'); - expect(document['pages.0.children.0.name']).toBe('6'); - expect(document['pages.0.children']).toBeInstanceOf(Array); - expect(document['pages.0.children'][0].name).toBe('7'); - - expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); - - expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'children', - type: 'class', - typeValue: PageCollection, - array: false, - map: false, - }); - - expect( - getResolvedReflection(DocumentClass, 'pages.0.children.0.name') - ).toEqual({ - resolvedClassType: PageClass, - resolvedPropertyName: 'name', - type: 'string', - typeValue: null, - array: false, - map: false, - }); + const doc = new DocumentClass(); + const document = partialClassToMongo(DocumentClass, { + 'pages.0.name': 5, + 'pages.0.children.0.name': 6, + 'pages.0.children': new PageCollection([new PageClass(doc, '7')]), + }); + expect(document['pages.0.name']).toBe('5'); + expect(document['pages.0.children.0.name']).toBe('6'); + expect(document['pages.0.children']).toBeInstanceOf(Array); + expect(document['pages.0.children'][0].name).toBe('7'); + + expect(getResolvedReflection(DocumentClass, 'pages.0.name')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); + + expect(getResolvedReflection(DocumentClass, 'pages.0.children')).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'children', + type: 'class', + typeValue: PageCollection, + array: false, + map: false, + }); + + expect( + getResolvedReflection(DocumentClass, 'pages.0.children.0.name') + ).toEqual({ + resolvedClassType: PageClass, + resolvedPropertyName: 'name', + type: 'string', + typeValue: null, + array: false, + map: false, + }); }); diff --git a/packages/mongo/tests/to-plain.spec.ts b/packages/mongo/tests/to-plain.spec.ts index 8e5814338..437b368ed 100644 --- a/packages/mongo/tests/to-plain.spec.ts +++ b/packages/mongo/tests/to-plain.spec.ts @@ -5,44 +5,44 @@ import { DateType, ID, MongoIdType, UUIDType } from '@marcj/marshal'; import { mongoToPlain, partialMongoToPlain, uuid4Binary } from '../src/mapping'; test('mongo to plain', () => { - class Model { - @ID() - @MongoIdType() - _id?: string; - - @DateType() - date?: Date; - } - - const plain = mongoToPlain(Model, { - _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), - date: new Date('2018-11-07 19:45:15.805Z'), - }); - - expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); - expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); + class Model { + @ID() + @MongoIdType() + _id?: string; + + @DateType() + date?: Date; + } + + const plain = mongoToPlain(Model, { + _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), + date: new Date('2018-11-07 19:45:15.805Z'), + }); + + expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); + expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); }); test('mongo to plain partial', () => { - class Model { - @ID() - @MongoIdType() - _id?: string; - - @UUIDType() - uuid?: string; - - @DateType() - date?: Date; - } - - const plain = partialMongoToPlain(Model, { - uuid: uuid4Binary('12345678-1234-5678-1234-567812345678'), - _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), - date: new Date('2018-11-07 19:45:15.805Z'), - }); - - expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); - expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); - expect(plain.uuid).toBe('12345678-1234-5678-1234-567812345678'); + class Model { + @ID() + @MongoIdType() + _id?: string; + + @UUIDType() + uuid?: string; + + @DateType() + date?: Date; + } + + const plain = partialMongoToPlain(Model, { + uuid: uuid4Binary('12345678-1234-5678-1234-567812345678'), + _id: new ObjectID('5be340cb2ffb5e901a9b62e4'), + date: new Date('2018-11-07 19:45:15.805Z'), + }); + + expect(plain._id).toBe('5be340cb2ffb5e901a9b62e4'); + expect(plain.date).toBe('2018-11-07T19:45:15.805Z'); + expect(plain.uuid).toBe('12345678-1234-5678-1234-567812345678'); }); diff --git a/packages/nest/src/nest.ts b/packages/nest/src/nest.ts index dd412bd59..8a2fcd794 100644 --- a/packages/nest/src/nest.ts +++ b/packages/nest/src/nest.ts @@ -1,33 +1,38 @@ import { - PipeTransform, - ArgumentMetadata, - BadRequestException, + PipeTransform, + ArgumentMetadata, + BadRequestException, } from '@nestjs/common'; import { validate, plainToClass, applyDefaultValues } from '@marcj/marshal'; export class ValidationPipe implements PipeTransform { - constructor( - private options?: { transform?: boolean; disableErrorMessages?: boolean } - ) {} + constructor( + private options?: { + transform?: boolean; + disableErrorMessages?: boolean; + } + ) {} - async transform(value: any, metadata: ArgumentMetadata): Promise { - if (!metadata.metatype) { - return; - } + async transform(value: any, metadata: ArgumentMetadata): Promise { + if (!metadata.metatype) { + return; + } - const valueWithDefaults = applyDefaultValues(metadata.metatype, value); - const errors = await validate(metadata.metatype, valueWithDefaults); + const valueWithDefaults = applyDefaultValues(metadata.metatype, value); + const errors = await validate(metadata.metatype, valueWithDefaults); - if (errors.length > 0) { - throw new BadRequestException( - this.options && this.options.disableErrorMessages ? undefined : errors - ); - } + if (errors.length > 0) { + throw new BadRequestException( + this.options && this.options.disableErrorMessages + ? undefined + : errors + ); + } - if (this.options && this.options.transform) { - return plainToClass(metadata.metatype, value); - } + if (this.options && this.options.transform) { + return plainToClass(metadata.metatype, value); + } - return valueWithDefaults; - } + return valueWithDefaults; + } } diff --git a/packages/nest/tests/validation.spec.ts b/packages/nest/tests/validation.spec.ts index d987b4791..1030ac53c 100644 --- a/packages/nest/tests/validation.spec.ts +++ b/packages/nest/tests/validation.spec.ts @@ -1,116 +1,125 @@ import 'reflect-metadata'; import 'jest-extended'; import { - Optional, - validate, - StringType, - ArrayType, - MapType, + Optional, + validate, + StringType, + ArrayType, + MapType, } from '@marcj/marshal'; import { ValidationPipe } from '../'; import { BadRequestException } from '@nestjs/common'; test('test required', async () => { - class Model { - @StringType() - id: string = '1'; + class Model { + @StringType() + id: string = '1'; - @StringType() - name?: string; + @StringType() + name?: string; - @Optional() - optional?: string; + @Optional() + optional?: string; - @Optional() - @MapType() - map?: { [name: string]: string }; + @Optional() + @MapType() + map?: { [name: string]: string }; - @Optional() - @ArrayType() - array?: string[]; - } + @Optional() + @ArrayType() + array?: string[]; + } - const instance = new Model(); - expect(await validate(Model, instance)).toBeArrayOfSize(1); - expect(await validate(Model, instance)).toEqual([ - { message: 'Required value is undefined', path: 'name' }, - ]); + const instance = new Model(); + expect(await validate(Model, instance)).toBeArrayOfSize(1); + expect(await validate(Model, instance)).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); - expect(await validate(Model, { name: 'foo', map: true })).toEqual([ - { message: 'Invalid type. Expected object, but got boolean', path: 'map' }, - ]); - expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ - { message: 'Invalid type. Expected array, but got number', path: 'array' }, - ]); + expect(await validate(Model, { name: 'foo', map: true })).toEqual([ + { + message: 'Invalid type. Expected object, but got boolean', + path: 'map', + }, + ]); + expect(await validate(Model, { name: 'foo', array: 233 })).toEqual([ + { + message: 'Invalid type. Expected array, but got number', + path: 'array', + }, + ]); - { - const pipe = new ValidationPipe(); - const result = await pipe.transform({ name: 'Foo' }, { type: 'body' }); - expect(result).toBeUndefined(); - } + { + const pipe = new ValidationPipe(); + const result = await pipe.transform({ name: 'Foo' }, { type: 'body' }); + expect(result).toBeUndefined(); + } - { - const pipe = new ValidationPipe(); - const result = await pipe.transform( - { name: 'Foo' }, - { type: 'body', metatype: Model } - ); - expect(result).not.toBeInstanceOf(Model); - expect(result.id).toBe('1'); //because ValidationPipe is reading default values - } + { + const pipe = new ValidationPipe(); + const result = await pipe.transform( + { name: 'Foo' }, + { type: 'body', metatype: Model } + ); + expect(result).not.toBeInstanceOf(Model); + expect(result.id).toBe('1'); //because ValidationPipe is reading default values + } - { - const pipe = new ValidationPipe(); - const result = await pipe.transform( - { name: 'Foo', optional: 'two' }, - { type: 'body', metatype: Model } - ); - expect(result).not.toBeInstanceOf(Model); - expect(result.id).toBe('1'); //because ValidationPipe is reading default values - } + { + const pipe = new ValidationPipe(); + const result = await pipe.transform( + { name: 'Foo', optional: 'two' }, + { type: 'body', metatype: Model } + ); + expect(result).not.toBeInstanceOf(Model); + expect(result.id).toBe('1'); //because ValidationPipe is reading default values + } - { - const pipe = new ValidationPipe({ transform: true }); - const result = await pipe.transform( - { name: 'Foo', optional: 'two' }, - { type: 'body', metatype: Model } - ); - expect(result).toBeInstanceOf(Model); - } + { + const pipe = new ValidationPipe({ transform: true }); + const result = await pipe.transform( + { name: 'Foo', optional: 'two' }, + { type: 'body', metatype: Model } + ); + expect(result).toBeInstanceOf(Model); + } - { - const pipe = new ValidationPipe({ transform: true }); - try { - const result = await pipe.transform( - { optional: 'two' }, - { type: 'body', metatype: Model } - ); - fail('no exception thrown'); - } catch (error) { - expect(error).toBeInstanceOf(BadRequestException); - expect(error.message).toEqual([ - { message: 'Required value is undefined', path: 'name' }, - ]); + { + const pipe = new ValidationPipe({ transform: true }); + try { + const result = await pipe.transform( + { optional: 'two' }, + { type: 'body', metatype: Model } + ); + fail('no exception thrown'); + } catch (error) { + expect(error).toBeInstanceOf(BadRequestException); + expect(error.message).toEqual([ + { message: 'Required value is undefined', path: 'name' }, + ]); + } } - } - { - const pipe = new ValidationPipe({ - transform: true, - disableErrorMessages: true, - }); - try { - const result = await pipe.transform( - { optional: 'two' }, - { type: 'body', metatype: Model } - ); - fail('no exception thrown'); - } catch (error) { - expect(error).toBeInstanceOf(BadRequestException); - expect(error.message).toEqual({ error: 'Bad Request', statusCode: 400 }); + { + const pipe = new ValidationPipe({ + transform: true, + disableErrorMessages: true, + }); + try { + const result = await pipe.transform( + { optional: 'two' }, + { type: 'body', metatype: Model } + ); + fail('no exception thrown'); + } catch (error) { + expect(error).toBeInstanceOf(BadRequestException); + expect(error.message).toEqual({ + error: 'Bad Request', + statusCode: 400, + }); + } } - } - instance.name = 'Pete'; - expect(await validate(Model, instance)).toEqual([]); + instance.name = 'Pete'; + expect(await validate(Model, instance)).toEqual([]); }); From e8ad11b745a78c985b0a0bbe1f6eebf528f1d142 Mon Sep 17 00:00:00 2001 From: sebastianhoitz Date: Thu, 7 Feb 2019 16:18:20 +0100 Subject: [PATCH 3/5] Add docke-compose file for running tests --- .gitignore | 1 + docker-compose.yml | 19 +++++++++++++++++++ package.json | 1 + packages/core/tsconfig.json | 1 + packages/mongo/tsconfig.json | 1 + tsconfig.json | 1 + 6 files changed, 24 insertions(+) create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index c86acb9f2..94e7d71e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +.vscode .idea lib/ coverage diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..e12e445a5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3' +services: + mongo: + image: mongo + ports: + - '27017:27017' + + mysql: + image: mysql:5.6 + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_ROOT_PASSWORD: 'secret' + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + MYSQL_PORT: 3306 + + ports: + - '3306:3306' diff --git a/package.json b/package.json index 820273ef7..667c5d4bf 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "author": "Marc J. Schmidt ", "license": "MIT", "scripts": { + "test-local": "docker-compose up -d && npm run test", "test": "jest --coverage", "bootstrap": "lerna bootstrap --hoist --no-ci", "tsc": "lerna run tsc" diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index de609c099..0b142fc8e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./lib" }, + "files": [], "include": [ "./src", "index.ts", diff --git a/packages/mongo/tsconfig.json b/packages/mongo/tsconfig.json index 8d8bf1983..8ca79190b 100644 --- a/packages/mongo/tsconfig.json +++ b/packages/mongo/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./lib" }, + "files": [], "include": [ "./src", "index.ts" diff --git a/tsconfig.json b/tsconfig.json index e966d6e4b..203b40e40 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ "dom" ] }, + "files": [], "exclude": [ "node_modules", "**/*.spec.ts" From 4b97eccb39d2050f8ab95cf71a0c4928319a6702 Mon Sep 17 00:00:00 2001 From: sebastianhoitz Date: Thu, 7 Feb 2019 16:19:48 +0100 Subject: [PATCH 4/5] Use npm test command with docker-compose --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 667c5d4bf..13e41a601 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "author": "Marc J. Schmidt ", "license": "MIT", "scripts": { - "test-local": "docker-compose up -d && npm run test", - "test": "jest --coverage", + "test": "docker-compose up -d && jest --coverage", "bootstrap": "lerna bootstrap --hoist --no-ci", "tsc": "lerna run tsc" }, From 5de3df87cb7f61b95eaee323ade7073982bc6ab7 Mon Sep 17 00:00:00 2001 From: sebastianhoitz Date: Thu, 7 Feb 2019 16:33:38 +0100 Subject: [PATCH 5/5] Add custom test tslint --- packages/core/tests/tslint.json | 6 ++++++ packages/core/tsconfig.json | 2 +- packages/mongo/tests/tslint.json | 6 ++++++ packages/mongo/tsconfig.json | 2 +- packages/nest/tests/tslint.json | 6 ++++++ packages/nest/tsconfig.json | 1 + tsconfig.json | 4 +--- 7 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 packages/core/tests/tslint.json create mode 100644 packages/mongo/tests/tslint.json create mode 100644 packages/nest/tests/tslint.json diff --git a/packages/core/tests/tslint.json b/packages/core/tests/tslint.json new file mode 100644 index 000000000..32e42deb6 --- /dev/null +++ b/packages/core/tests/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "no-non-null-assertion": false, + } +} \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 0b142fc8e..aa703d784 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,9 +3,9 @@ "compilerOptions": { "outDir": "./lib" }, - "files": [], "include": [ "./src", + "./tests", "index.ts", "global.d.ts" ] diff --git a/packages/mongo/tests/tslint.json b/packages/mongo/tests/tslint.json new file mode 100644 index 000000000..32e42deb6 --- /dev/null +++ b/packages/mongo/tests/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "no-non-null-assertion": false, + } +} \ No newline at end of file diff --git a/packages/mongo/tsconfig.json b/packages/mongo/tsconfig.json index 8ca79190b..24d8ed1af 100644 --- a/packages/mongo/tsconfig.json +++ b/packages/mongo/tsconfig.json @@ -3,9 +3,9 @@ "compilerOptions": { "outDir": "./lib" }, - "files": [], "include": [ "./src", + "./tests", "index.ts" ] } \ No newline at end of file diff --git a/packages/nest/tests/tslint.json b/packages/nest/tests/tslint.json new file mode 100644 index 000000000..32e42deb6 --- /dev/null +++ b/packages/nest/tests/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "no-non-null-assertion": false, + } +} \ No newline at end of file diff --git a/packages/nest/tsconfig.json b/packages/nest/tsconfig.json index 8d8bf1983..24d8ed1af 100644 --- a/packages/nest/tsconfig.json +++ b/packages/nest/tsconfig.json @@ -5,6 +5,7 @@ }, "include": [ "./src", + "./tests", "index.ts" ] } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 203b40e40..33bb6ff2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,9 +21,7 @@ "dom" ] }, - "files": [], "exclude": [ - "node_modules", - "**/*.spec.ts" + "node_modules" ] } \ No newline at end of file