├── CHANGELOG.md ├── spec ├── node_modules │ └── serialazy-bson ├── tsconfig.json └── src │ ├── run.ts │ ├── frontend_functions.spec.ts │ └── default_type_serializer.spec.ts ├── .gitignore ├── .npmignore ├── README.md ├── lib ├── tsconfig.json └── src │ ├── bson_type.ts │ └── index.ts ├── LICENSE ├── tslint.json ├── package.json └── tsconfig.base.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/node_modules/serialazy-bson: -------------------------------------------------------------------------------- 1 | ../../lib/dist/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /lib/dist 3 | /samples/bin 4 | /spec/bin 5 | 6 | # IDEs 7 | .idea/ 8 | .vscode/ 9 | 10 | # OS Specific 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /docs 3 | /lib/src 4 | /samples 5 | /spec 6 | **/tsconfig.json 7 | **/tslint.json 8 | tsconfig.base.json 9 | 10 | # IDEs 11 | .idea/ 12 | .vscode/ 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [**Serialazy**](https://github.com/teq/serialazy) is an uniform library for TypeScript class serialization 2 | which supports various serialization backends. This package implements a BSON/MongoDB backend. Other backends: 3 | * [JSON](https://github.com/teq/serialazy) 4 | 5 | > **Note:** It is a development version which depends on oncoming serialazy@3.0.0. 6 | 7 | ## Installation 8 | 9 | This package depends on `serialazy` as a peer dependency. 10 | 11 | `npm i --save serialazy@next serialazy-bson` 12 | -------------------------------------------------------------------------------- /lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | 5 | /* Basic Options */ 6 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 7 | "sourceMap": false, /* Generates corresponding '.map' file. */ 8 | "outDir": "dist", /* Redirect output structure to the directory. */ 9 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | 5 | /* Basic Options */ 6 | "declaration": false, /* Generates corresponding '.d.ts' file. */ 7 | "sourceMap": true, /* Generates corresponding '.map' file. */ 8 | "outDir": "bin", /* Redirect output structure to the directory. */ 9 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/bson_type.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Binary, 3 | BSONRegExp, 4 | Code, 5 | Decimal128, 6 | Double, 7 | Int32, 8 | Long, 9 | ObjectID, 10 | Timestamp 11 | } from 'bson'; 12 | 13 | export { 14 | Binary, 15 | BSONRegExp, 16 | Code, 17 | Decimal128, 18 | Double, 19 | Int32, 20 | Long, 21 | ObjectID, 22 | Timestamp 23 | }; 24 | 25 | export interface BsonDocument { 26 | [prop: string]: BsonType; 27 | } 28 | 29 | export interface BsonArray extends Array {} 30 | 31 | export type BsonType = 32 | string | boolean | Date | // JS-types 33 | Binary | BSONRegExp | Code | // \ 34 | Decimal128 | Double | Int32 | // | BSON-specific types 35 | Long | ObjectID | Timestamp | // / 36 | BsonDocument | BsonArray; // nested BSON types 37 | 38 | export default BsonType; 39 | -------------------------------------------------------------------------------- /spec/src/run.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import chaiAsPromised = require('chai-as-promised'); 3 | import fs = require('fs'); 4 | import Mocha = require('mocha'); 5 | import path = require('path'); 6 | 7 | // register chai plugins 8 | chai.use(chaiAsPromised); 9 | 10 | function listFilesRecursive(fname: string) { 11 | let files: string[] = []; 12 | const stat = fs.lstatSync(fname); 13 | if (stat.isFile()) { 14 | files.push(fname); 15 | } else if (stat.isDirectory()) { 16 | fs.readdirSync(fname).forEach(nested => { 17 | files = files.concat(listFilesRecursive(path.join(fname, nested))); 18 | }); 19 | } 20 | return files; 21 | } 22 | 23 | let mocha = new Mocha({ reporter: 'spec', timeout: 10000 }); 24 | 25 | // search for spec files recursively 26 | listFilesRecursive(__dirname) 27 | .filter(fname => fname.endsWith('spec.js')) 28 | .forEach(fname => mocha.addFile(fname)); 29 | 30 | mocha.run(failureCount => { 31 | process.exitCode = failureCount; 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Andrey Tselischev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | 4 | // TypeScript Specific 5 | "adjacent-overload-signatures": true, 6 | "member-access": [ true, "check-accessor", "check-constructor" ], 7 | "no-inferrable-types": true, 8 | "no-internal-module": true, 9 | "no-var-requires": true, 10 | 11 | // Functionality 12 | "curly": true, 13 | "no-console": [ true, "log", "error", "debug" ], 14 | "no-debugger": true, 15 | "no-eval": true, 16 | "no-unused-expression": true, 17 | "no-var-keyword": true, 18 | 19 | // Maintainability 20 | "eofline": true, 21 | "indent": [ true, "spaces" ], 22 | "linebreak-style": [ true, "LF" ], 23 | "no-trailing-whitespace": true, 24 | 25 | // Style 26 | "class-name": true, 27 | "comment-format": [ true, "check-space" ], 28 | "jsdoc-format": true, 29 | "ordered-imports": true, 30 | "semicolon": [ true, "always" ], 31 | "variable-name": [ true, "ban-keywords", "check-format" ], 32 | "whitespace": [ true, 33 | "check-branch", "check-decl", "check-module", 34 | "check-operator", "check-separator", "check-type", "check-typecast" 35 | ] 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serialazy-bson", 3 | "version": "1.0.0-rc.17", 4 | "description": "A TypeScript class serialization library (BSON backend)", 5 | "keywords": [ 6 | "serialazy", 7 | "serialize", 8 | "deserialize", 9 | "bson", 10 | "mongodb", 11 | "mapping", 12 | "typescript" 13 | ], 14 | "homepage": "https://github.com/teq/serialazy-bson", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/teq/serialazy-bson.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/teq/serialazy-bson/issues" 21 | }, 22 | "main": "./lib/dist/index.js", 23 | "types": "./lib/dist/index.d.ts", 24 | "scripts": { 25 | "lint": "tslint -p ./lib && tslint -p ./spec", 26 | "apidoc": "typedoc --target ES6 --module commonjs --excludePrivate --out ./docs ./lib/src && touch ./docs/.nojekyll", 27 | "clean_lib": "rm -rf ./lib/dist", 28 | "clean_spec": "rm -rf ./spec/bin", 29 | "clean_all": "npm run clean_lib && npm run clean_spec", 30 | "build_lib": "cd ./lib && tsc", 31 | "build_spec": "cd ./spec && tsc", 32 | "build_all": "npm run build_lib && npm run build_spec", 33 | "test": "npm run build_lib && npm run build_spec && node ./spec/bin/run.js", 34 | "prepublishOnly": "npm run lint && npm run clean_all && npm run build_all && node ./spec/bin/run.js" 35 | }, 36 | "author": "Andrey Tselischev ", 37 | "license": "MIT", 38 | "dependencies": { 39 | "@types/bson": "^4.0.0", 40 | "bson": "^4.0.2", 41 | "serialazy": "^3.0.0-rc.16" 42 | }, 43 | "devDependencies": { 44 | "@types/chai": "^4.0.4", 45 | "@types/chai-as-promised": "^7.1.0", 46 | "@types/mocha": "^5.2.5", 47 | "@types/node": "^12.12.27", 48 | "chai": "^4.1.2", 49 | "chai-as-promised": "^7.1.1", 50 | "mocha": "^7.1.1", 51 | "tslint": "^6.1.0", 52 | "typescript": "^3.7.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spec/src/frontend_functions.spec.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | 3 | import { 4 | deflate, 5 | deflateToBinary, 6 | inflate, 7 | inflateFromBinary, 8 | Serialize 9 | } from 'serialazy-bson'; 10 | 11 | const { expect } = chai; 12 | 13 | describe('frontend functions', () => { 14 | 15 | describe('deflate', () => { 16 | it('is able to serialize null/undefined', () => { 17 | expect(deflate(null)).to.equal(null); 18 | expect(deflate(undefined)).to.equal(undefined); 19 | }); 20 | }); 21 | 22 | describe('inflate', () => { 23 | it('is able to deserialize null/undefined', () => { 24 | expect(inflate(Boolean, null)).to.equal(null); 25 | expect(inflate(Boolean, undefined)).to.equal(undefined); 26 | expect(inflate(Number, null)).to.equal(null); 27 | expect(inflate(Number, undefined)).to.equal(undefined); 28 | expect(inflate(String, null)).to.equal(null); 29 | expect(inflate(String, undefined)).to.equal(undefined); 30 | expect(inflate(Date, null)).to.equal(null); 31 | expect(inflate(Date, undefined)).to.equal(undefined); 32 | expect(inflate(RegExp as any, null)).to.equal(null); 33 | expect(inflate(RegExp as any, undefined)).to.equal(undefined); 34 | }); 35 | }); 36 | 37 | describe('deflateToBinary', () => { 38 | it('is able to serialize null/undefined', () => { 39 | expect(deflateToBinary(null)).to.equal(null); 40 | expect(deflateToBinary(undefined)).to.equal(undefined); 41 | }); 42 | }); 43 | 44 | describe('inflateFromBinary', () => { 45 | it('is able to deserialize null/undefined', () => { 46 | expect(inflateFromBinary(Boolean, null)).to.equal(null); 47 | expect(inflateFromBinary(Boolean, undefined)).to.equal(undefined); 48 | expect(inflateFromBinary(Number, null)).to.equal(null); 49 | expect(inflateFromBinary(Number, undefined)).to.equal(undefined); 50 | expect(inflateFromBinary(String, null)).to.equal(null); 51 | expect(inflateFromBinary(String, undefined)).to.equal(undefined); 52 | expect(inflateFromBinary(Date, null)).to.equal(null); 53 | expect(inflateFromBinary(Date, undefined)).to.equal(undefined); 54 | expect(inflateFromBinary(RegExp as any, null)).to.equal(null); 55 | expect(inflateFromBinary(RegExp as any, undefined)).to.equal(undefined); 56 | }); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | 4 | /* Basic Options */ 5 | "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation: */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | "removeComments": false, /* Do not emit comments to output. */ 17 | // "noEmit": true, /* Do not emit outputs. */ 18 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 19 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 20 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 21 | 22 | /* Strict Type-Checking Options */ 23 | "strict": true, /* Enable all strict type-checking options. */ 24 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 25 | "strictNullChecks": false, /* Enable strict null checks. */ 26 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 27 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 28 | 29 | /* Additional Checks */ 30 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 31 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 32 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 33 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 34 | 35 | /* Module Resolution Options */ 36 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 37 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 38 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 39 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 40 | // "typeRoots": [], /* List of folders to include type definitions from. */ 41 | // "types": [], /* Type declaration files to be included in compilation. */ 42 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 43 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 44 | 45 | /* Source Map Options */ 46 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 47 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 48 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 49 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 50 | 51 | /* Experimental Options */ 52 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 53 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/index.ts: -------------------------------------------------------------------------------- 1 | import { deserialize, serialize } from 'bson'; 2 | import { 3 | Constructor, 4 | DecoratorFactory, 5 | DecoratorOptions, 6 | DEFAULT_PROJECTION, 7 | DeflateOptions, 8 | FrontendFunctions, 9 | InflateOptions, 10 | MetadataManager, 11 | Util 12 | } from 'serialazy'; 13 | 14 | import { BSONRegExp, BsonType, Double, Int32 } from './bson_type'; 15 | 16 | export const BACKEND_NAME = 'bson'; 17 | 18 | /** 19 | * Define serializer for given property or type 20 | * @param options _(optional)_ Custom type serializer and/or other options 21 | * @returns Type/property decorator 22 | */ 23 | export function Serialize( 24 | options?: DecoratorOptions 25 | ): (protoOrCtor: Object | Constructor, propertyName?: string) => void { 26 | return DecoratorFactory(BACKEND_NAME, options); 27 | } 28 | 29 | /** 30 | * Serialize given serializable type instance to BSON type 31 | * @param serializable Serializable type instance 32 | * @param options _(optional)_ Deflate options 33 | * @returns BSON type (js-bson) 34 | */ 35 | export function deflate( 36 | serializable: TOriginal, 37 | options?: DeflateOptions 38 | ): BsonType { 39 | return FrontendFunctions(BACKEND_NAME).deflate(serializable, options); 40 | } 41 | 42 | /** 43 | * Serialize given serializable type instance to BSON binary 44 | * @param serializable Serializable type instance 45 | * @param options _(optional)_ Deflate options 46 | * @returns Buffer with BSON binary 47 | */ 48 | export function deflateToBinary( 49 | serializable: TOriginal, 50 | options?: DeflateOptions 51 | ): Buffer { 52 | const bsonType = deflate(serializable, options); 53 | if (bsonType === null || bsonType === undefined) { 54 | return bsonType as null | undefined; 55 | } else { 56 | return serialize(bsonType); 57 | } 58 | } 59 | 60 | /** 61 | * Construct/deserialize a serializable type instance from BSON type 62 | * @param ctor Serializable type constructor function 63 | * @param serialized BSON object (js-bson) 64 | * @param options _(optional)_ Inflate options 65 | * @returns Serializable type instance 66 | */ 67 | export function inflate( 68 | ctor: Constructor, 69 | serialized: BsonType, 70 | options?: InflateOptions 71 | ): TOriginal { 72 | return FrontendFunctions(BACKEND_NAME).inflate(ctor, serialized, options); 73 | } 74 | 75 | /** 76 | * Construct/deserialize a serializable type instance from BSON binary 77 | * @param ctor Serializable type constructor function 78 | * @param serialized Buffer with BSON binary 79 | * @param options _(optional)_ Inflate options 80 | * @returns Serializable type instance 81 | */ 82 | export function inflateFromBinary( 83 | ctor: Constructor, 84 | serialized: Buffer, 85 | options?: InflateOptions 86 | ): TOriginal { 87 | 88 | const bsonType: BsonType = (serialized === null || serialized === undefined) 89 | ? serialized 90 | : deserialize(serialized, { 91 | promoteValues: false, 92 | promoteLongs: false, 93 | bsonRegExp: true 94 | }); 95 | 96 | return inflate(ctor, bsonType, options); 97 | 98 | } 99 | 100 | // Types 101 | export * from './bson_type'; 102 | 103 | // Define serializers for built-in types 104 | 105 | const metaManager = MetadataManager.get(BACKEND_NAME, DEFAULT_PROJECTION); 106 | 107 | function expectDateOrNil(maybeDate: any): Date { 108 | if (maybeDate === null || maybeDate === undefined) { 109 | return maybeDate; 110 | } else if (!(maybeDate instanceof Date)) { 111 | throw new Error(`Not a Date (typeof: "${typeof(maybeDate)}", value: "${maybeDate}")`); 112 | } else { 113 | return maybeDate; 114 | } 115 | } 116 | 117 | if (!metaManager.getMetaFor(Boolean.prototype)) { 118 | Serialize({ 119 | down: (original: any) => Util.expectBooleanOrNil(original), 120 | up: (serialized: any) => Util.expectBooleanOrNil(serialized) 121 | })(Boolean); 122 | } 123 | 124 | if (!metaManager.getMetaFor(Number.prototype)) { 125 | Serialize({ 126 | down: (original: any) => { 127 | const num = Util.expectNumberOrNil(original); 128 | if (num === null || num === undefined) { 129 | return num as null | undefined; 130 | } else if (Number.isInteger(num)) { 131 | return new Int32(num); 132 | } else { 133 | return new Double(num); 134 | } 135 | }, 136 | up: (serialized: any) => { 137 | if (serialized === null || serialized === undefined) { 138 | return serialized as null | undefined; 139 | } else if (serialized._bsontype === 'Double' || serialized._bsontype === 'Int32') { 140 | const num = serialized.valueOf && serialized.valueOf(); 141 | if (typeof num === 'number') { 142 | return num; 143 | } 144 | } 145 | throw new Error(`Not a Double/Int32 BSON type (typeof: "${typeof(serialized)}", value: "${serialized}")`); 146 | } 147 | })(Number); 148 | } 149 | 150 | if (!metaManager.getMetaFor(String.prototype)) { 151 | Serialize({ 152 | down: (original: any) => Util.expectStringOrNil(original), 153 | up: (serialized: any) => Util.expectStringOrNil(serialized) 154 | })(String); 155 | } 156 | 157 | if (!metaManager.getMetaFor(Date.prototype)) { 158 | Serialize({ 159 | down: (original: any) => expectDateOrNil(original), 160 | up: (serialized: any) => expectDateOrNil(serialized) 161 | })(Date); 162 | } 163 | 164 | if (!metaManager.getMetaFor(RegExp.prototype)) { 165 | Serialize({ 166 | down: (original: any) => { 167 | if (original === null || original === undefined) { 168 | return original as null | undefined; 169 | } else if (!(original instanceof RegExp)) { 170 | throw new Error(`Not a RegExp (typeof: "${typeof(original)}", value: "${original}")`); 171 | } else { 172 | return new BSONRegExp(original.source, original.flags); 173 | } 174 | }, 175 | up: (serialized: any) => { 176 | if (serialized === null || serialized === undefined) { 177 | return serialized as null | undefined; 178 | } else if (serialized._bsontype === 'BSONRegExp') { 179 | return new RegExp(serialized.pattern, serialized.options); 180 | } else { 181 | throw new Error(`Not a BSONRegExp (typeof: "${typeof(serialized)}", value: "${serialized}")`); 182 | } 183 | } 184 | })(RegExp); 185 | } 186 | -------------------------------------------------------------------------------- /spec/src/default_type_serializer.spec.ts: -------------------------------------------------------------------------------- 1 | import { BSONRegExp, Double, Int32 } from 'bson'; 2 | import chai = require('chai'); 3 | 4 | import { deflate, inflate, Serialize } from 'serialazy-bson'; 5 | 6 | const { expect } = chai; 7 | 8 | describe('default type serializer', () => { 9 | 10 | describe('for boolean properties', () => { 11 | 12 | class Book { 13 | @Serialize() public read: boolean; 14 | } 15 | 16 | describe('when the value is a boolean', () => { 17 | 18 | describe('of primitive type', () => { 19 | 20 | it('serializes to a boolean primitive', () => { 21 | const book = Object.assign(new Book(), { read: true }); 22 | const serialized = deflate(book); 23 | expect(serialized).to.deep.equal({ read: true }); 24 | }); 25 | 26 | it('deserializes to a boolean primitive', () => { 27 | const deserialized = inflate(Book, { read: false }); 28 | expect(deserialized instanceof Book).to.equal(true); 29 | expect(deserialized).to.deep.equal({ read: false }); 30 | }); 31 | 32 | }); 33 | 34 | describe('of object type', () => { 35 | 36 | it('serializes to a boolean primitive', () => { 37 | const book = Object.assign(new Book(), { read: new Boolean(true) }); 38 | const serialized = deflate(book); 39 | expect(serialized).to.deep.equal({ read: true }); 40 | }); 41 | 42 | it('deserializes to a boolean primitive', () => { 43 | const deserialized = inflate(Book, { read: new Boolean(false) as boolean }); 44 | expect(deserialized instanceof Book).to.equal(true); 45 | expect(deserialized).to.deep.equal({ read: false }); 46 | }); 47 | 48 | }); 49 | 50 | }); 51 | 52 | describe('when the value is a non-boolean', () => { 53 | 54 | it('should fail to serialize', () => { 55 | const book = Object.assign(new Book(), { read: new Date() }); 56 | expect(() => deflate(book)).to.throw('Unable to serialize property "read": Not a boolean'); 57 | }); 58 | 59 | it('should fail to deserialize', () => { 60 | expect(() => inflate(Book, { read: new Date() as any })).to.throw('Unable to deserialize property "read": Not a boolean'); 61 | }); 62 | 63 | }); 64 | 65 | }); 66 | 67 | describe('for number properties', () => { 68 | 69 | class Person { 70 | @Serialize() public age: number; 71 | } 72 | 73 | describe('when serialized value is an integer', () => { 74 | 75 | describe('of primitive type', () => { 76 | 77 | it('serializes to BSON Int32', () => { 78 | const person = Object.assign(new Person(), { age: 40 }); 79 | const serialized = deflate(person); 80 | expect(serialized).to.deep.equal({ age: new Int32(40) }); 81 | }); 82 | 83 | }); 84 | 85 | describe('of object type', () => { 86 | 87 | it('serializes to BSON Int32', () => { 88 | const person = Object.assign(new Person(), { age: new Number(40) }); 89 | const serialized = deflate(person); 90 | expect(serialized).to.deep.equal({ age: new Int32(40) }); 91 | }); 92 | 93 | }); 94 | 95 | }); 96 | 97 | describe('when serialized value is a non-integer', () => { 98 | 99 | describe('of primitive type', () => { 100 | 101 | it('serializes to BSON Double', () => { 102 | const person = Object.assign(new Person(), { age: 40.5 }); 103 | const serialized = deflate(person); 104 | expect(serialized).to.deep.equal({ age: new Double(40.5) }); 105 | }); 106 | 107 | }); 108 | 109 | describe('of object type', () => { 110 | 111 | it('serializes to BSON Double', () => { 112 | const person = Object.assign(new Person(), { age: new Number(40.5) }); 113 | const serialized = deflate(person); 114 | expect(serialized).to.deep.equal({ age: new Double(40.5) }); 115 | }); 116 | 117 | }); 118 | 119 | }); 120 | 121 | describe('when deserialized value is BSON Int32', () => { 122 | 123 | it('deserializes to a number', () => { 124 | const deserialized = inflate(Person, { age: new Int32(45) }); 125 | expect(deserialized instanceof Person).to.equal(true); 126 | expect(deserialized).to.deep.equal({ age: 45 }); 127 | }); 128 | 129 | }); 130 | 131 | describe('when deserialized value is BSON Double', () => { 132 | 133 | it('deserializes to a number', () => { 134 | const deserialized = inflate(Person, { age: new Double(45.5) }); 135 | expect(deserialized instanceof Person).to.equal(true); 136 | expect(deserialized).to.deep.equal({ age: 45.5 }); 137 | }); 138 | 139 | }); 140 | 141 | describe('when the value is a non-number', () => { 142 | 143 | it('should fail to serialize', () => { 144 | const person = Object.assign(new Person(), { age: new Date() }); 145 | expect( 146 | () => deflate(person) 147 | ).to.throw('Unable to serialize property "age": Not a number'); 148 | }); 149 | 150 | it('should fail to deserialize', () => { 151 | expect( 152 | () => inflate(Person, { age: new Date() as any }) 153 | ).to.throw('Unable to deserialize property "age": Not a Double/Int32'); 154 | }); 155 | 156 | }); 157 | 158 | }); 159 | 160 | describe('for string properties', () => { 161 | 162 | class Greeter { 163 | @Serialize() public message: string; 164 | } 165 | 166 | describe('when the value is a string', () => { 167 | 168 | describe('of primitive type', () => { 169 | 170 | it('serializes to a string literal', () => { 171 | const greeter = Object.assign(new Greeter(), { message: 'hello' }); 172 | const serialized = deflate(greeter); 173 | expect(serialized).to.deep.equal({ message: 'hello' }); 174 | }); 175 | 176 | it('deserializes to a string literal', () => { 177 | const deserialized = inflate(Greeter, { message: 'hi' }); 178 | expect(deserialized instanceof Greeter).to.equal(true); 179 | expect(deserialized).to.deep.equal({ message: 'hi' }); 180 | }); 181 | 182 | }); 183 | 184 | describe('of object type', () => { 185 | 186 | it('serializes to a string literal', () => { 187 | const greeter = Object.assign(new Greeter(), { message: new String('hello') }); 188 | const serialized = deflate(greeter); 189 | expect(serialized).to.deep.equal({ message: 'hello' }); 190 | }); 191 | 192 | it('deserializes to a string literal', () => { 193 | const deserialized = inflate(Greeter, { message: new String('hello') as string }); 194 | expect(deserialized instanceof Greeter).to.equal(true); 195 | expect(deserialized).to.deep.equal({ message: 'hello' }); 196 | }); 197 | 198 | }); 199 | 200 | }); 201 | 202 | describe('when the value is a non-string', () => { 203 | 204 | it('should fail to serialize', () => { 205 | const greeter = Object.assign(new Greeter(), { message: new Date() }); 206 | expect( 207 | () => deflate(greeter) 208 | ).to.throw('Unable to serialize property "message": Not a string'); 209 | }); 210 | 211 | it('should fail to deserialize', () => { 212 | expect( 213 | () => inflate(Greeter, { message: new Date() as any }) 214 | ).to.throw('Unable to deserialize property "message": Not a string'); 215 | }); 216 | 217 | }); 218 | 219 | }); 220 | 221 | describe('for Date properties', () => { 222 | 223 | class Book { 224 | @Serialize() public release: Date; 225 | } 226 | 227 | describe('when the value is a Date', () => { 228 | 229 | it('serializes to a Date', () => { 230 | const book = Object.assign(new Book(), { release: new Date() }); 231 | const serialized = deflate(book); 232 | expect(serialized).to.deep.equal(book); 233 | }); 234 | 235 | it('deserializes to a Date', () => { 236 | const serialized = { release: new Date() }; 237 | const deserialized = inflate(Book, serialized); 238 | expect(deserialized).to.deep.equal(serialized); 239 | }); 240 | 241 | }); 242 | 243 | describe('when the value is not a Date', () => { 244 | 245 | it('should fail to serialize', () => { 246 | const book = Object.assign(new Book(), { release: 'none' }); 247 | expect( 248 | () => deflate(book) 249 | ).to.throw('Unable to serialize property "release": Not a Date'); 250 | }); 251 | 252 | it('should fail to deserialize', () => { 253 | expect( 254 | () => inflate(Book, { release: 123 }) 255 | ).to.throw('Unable to deserialize property "release": Not a Date'); 256 | }); 257 | 258 | }); 259 | 260 | }); 261 | 262 | describe('for RegExp properties', () => { 263 | 264 | class Matcher { 265 | @Serialize() public exp: RegExp; 266 | } 267 | 268 | describe('when serialized value is a RegExp', () => { 269 | 270 | it('serializes to a BSONRegExp', () => { 271 | const matcher = Object.assign(new Matcher(), { exp: /test/i }); 272 | const serialized = deflate(matcher); 273 | expect(serialized).to.deep.equal({ exp: new BSONRegExp('test', 'i') }); 274 | }); 275 | 276 | }); 277 | 278 | describe('when serialized value is not a RegExp', () => { 279 | 280 | it('should fail to serialize', () => { 281 | const matcher = Object.assign(new Matcher(), { exp: 'test' }); 282 | expect( 283 | () => deflate(matcher) 284 | ).to.throw('Unable to serialize property "exp": Not a RegExp'); 285 | }); 286 | 287 | }); 288 | 289 | describe('when deserialized value is a BSONRegExp', () => { 290 | 291 | it('deserializes to a RegExp', () => { 292 | const serialized = { exp: new BSONRegExp('test', 'i') }; 293 | const deserialized = inflate(Matcher, serialized); 294 | expect(deserialized).to.deep.equal({ exp: /test/i }); 295 | }); 296 | 297 | }); 298 | 299 | describe('when deserialized value is not a BSONRegExp', () => { 300 | 301 | it('should fail to deserialize', () => { 302 | const serialized = { exp: 'test' }; 303 | expect( 304 | () => inflate(Matcher, serialized) 305 | ).to.throw('Unable to deserialize property "exp": Not a BSONRegExp'); 306 | }); 307 | 308 | }); 309 | 310 | }); 311 | 312 | }); 313 | --------------------------------------------------------------------------------