From 20f6e9cd68b822158915940707f8b269ed04434c Mon Sep 17 00:00:00 2001 From: Hagen Schulze Date: Sat, 1 Jun 2019 16:54:58 +0200 Subject: [PATCH] feat(class-transformer-shim): Added support for metadata reflection closes #4219 --- CHANGELOG.md | 1 + extra/typeorm-class-transformer-shim.js | 146 +++++++++++------------- package-lock.json | 47 ++++++-- package.json | 1 + test/github-issues/4219/entity/Photo.ts | 14 +++ test/github-issues/4219/entity/User.ts | 37 ++++++ test/github-issues/4219/issue-4219.ts | 34 ++++++ test/github-issues/4219/shim.ts | 10 ++ 8 files changed, 200 insertions(+), 90 deletions(-) create mode 100644 test/github-issues/4219/entity/Photo.ts create mode 100644 test/github-issues/4219/entity/User.ts create mode 100644 test/github-issues/4219/issue-4219.ts create mode 100644 test/github-issues/4219/shim.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d85cb4d..8a30b029e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ feel free to ask us and community. * added support for `dirty_read` (NOLOCK) in SQLServer ([#4133](https://github.com/typeorm/typeorm/pull/4133)) * extend afterLoad() subscriber interface to take LoadEvent ([issue #4185](https://github.com/typeorm/typeorm/issues/4185)) * relation decorators (e.g. `@OneToMany`) now also accept `string` instead of `typeFunction`, which prevents circular dependency issues in the frontend/browser ([issue #4190](https://github.com/typeorm/typeorm/issues/4190)) +* added support for metadata reflection in typeorm-class-transformer-shim.js ([issue #4219](https://github.com/typeorm/typeorm/issues/4219)) ## 0.2.17 (2019-05-01) diff --git a/extra/typeorm-class-transformer-shim.js b/extra/typeorm-class-transformer-shim.js index e86e0bbf3..506668719 100644 --- a/extra/typeorm-class-transformer-shim.js +++ b/extra/typeorm-class-transformer-shim.js @@ -1,5 +1,6 @@ // this "shim" can be used on the frontend to make class-transformer to work with typeorm models automatically // without having to put @Type decorator on properties that already have type information inside relational decorators. +// if "reflect-metadata" is imported, you can also leave out the @Type decorator for Date or other types. // using this shim you can share same models across backend and frontend more easily. // to use this shim simply configure your systemjs/webpack configuration to use this file instead of typeorm module. @@ -23,225 +24,214 @@ const class_transformer_1 = require("class-transformer"); // import {Type} from "class-transformer"; +/** + * If a metadata designType exists, it returns a function + * that resolves to the designType + */ +function getDesignTypeFunction(object, propertyName) { + if ( + typeof Reflect !== "undefined" && + Reflect.getMetadata instanceof Function + ) { + var type = Reflect.getMetadata("design:type", object, propertyName); + if (type instanceof Function) { + return function() { + return type; + }; + } + } +} + +/** + * Returns a decorator that maps the property type + * to the `@Type` decorator of class-transformer + */ +function makePropertyDecorator(typeFunction) { + return function(object, propertyName) { + if (!(typeFunction instanceof Function)) { + typeFunction = getDesignTypeFunction(object, propertyName); + } + if (typeFunction) { + class_transformer_1.Type(typeFunction)(object, propertyName); + } + }; +} + // columns /* export */ function Column(typeOrOptions, options) { - return function (object, propertyName) { - if (typeOrOptions instanceof Function) - class_transformer_1.Type(typeOrOptions)(object, propertyName); - }; + return makePropertyDecorator(typeOrOptions); } exports.Column = Column; /* export */ function CreateDateColumn(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.CreateDateColumn = CreateDateColumn; /* export */ function ObjectIdColumn(typeOrOptions, options) { - return function (object, propertyName) { - if (typeOrOptions instanceof Function) - class_transformer_1.Type(typeOrOptions)(object, propertyName); - }; + return makePropertyDecorator(typeOrOptions); } exports.ObjectIdColumn = ObjectIdColumn; /* export */ function PrimaryColumn(typeOrOptions, options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.PrimaryColumn = PrimaryColumn; /* export */ function PrimaryGeneratedColumn(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.PrimaryGeneratedColumn = PrimaryGeneratedColumn; /* export */ function UpdateDateColumn(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.UpdateDateColumn = UpdateDateColumn; /* export */ function VersionColumn(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.VersionColumn = VersionColumn; // listeners /* export */ function AfterInsert() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.AfterInsert = AfterInsert; /* export */ function AfterLoad() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.AfterLoad = AfterLoad; /* export */ function AfterRemove() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.AfterRemove = AfterRemove; /* export */ function AfterUpdate() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.AfterUpdate = AfterUpdate; /* export */ function BeforeInsert() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.BeforeInsert = BeforeInsert; /* export */ function BeforeRemove() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.BeforeRemove = BeforeRemove; /* export */ function BeforeUpdate() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.BeforeUpdate = BeforeUpdate; /* export */ function EventSubscriber() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.EventSubscriber = EventSubscriber; // relations /* export */ function JoinColumn(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.JoinColumn = JoinColumn; /* export */ function JoinTable(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.JoinTable = JoinTable; /* export */ function ManyToMany(typeFunction, inverseSideOrOptions, options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(typeFunction); } exports.ManyToMany = ManyToMany; /* export */ function ManyToOne(typeFunction, inverseSideOrOptions, options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(typeFunction); } exports.ManyToOne = ManyToOne; /* export */ function OneToMany(typeFunction, inverseSideOrOptions, options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(typeFunction); } exports.OneToMany = OneToMany; /* export */ function OneToOne(typeFunction, inverseSideOrOptions, options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(typeFunction); } exports.OneToOne = OneToOne; /* export */ function RelationCount(relation) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.RelationCount = RelationCount; /* export */ function RelationId(relation) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.RelationId = RelationId; // entities /* export */ function ChildEntity(tableName, options) { - return function (object) { - }; + return function(object) {}; } exports.ChildEntity = ChildEntity; /* export */ function Entity(name, options) { - return function (object) { - }; + return function(object) {}; } exports.Entity = Entity; /* export */ function TableInheritance(type) { - return function (object) { - }; + return function(object) {}; } exports.TableInheritance = TableInheritance; // tree /* export */ function Tree(name, options) { - return function (object) { - }; + return function(object) {}; } exports.Tree = Tree; /* export */ function TreeChildren(options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(); } exports.TreeChildren = TreeChildren; /* export */ function TreeChildrenCount(options) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(); } exports.TreeChildrenCount = TreeChildrenCount; /* export */ function TreeLevelColumn() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.TreeLevelColumn = TreeLevelColumn; /* export */ function TreeParent(typeFunction) { - return function (object, propertyName) { - class_transformer_1.Type(typeFunction)(object, propertyName); - }; + return makePropertyDecorator(); } exports.TreeParent = TreeParent; // other /* export */ function Generated(options) { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } exports.Generated = Generated; /* export */ function Index() { - return function (object, propertyName) { - }; + return function(object, propertyName) {}; } -exports.Index = Index; \ No newline at end of file +exports.Index = Index; diff --git a/package-lock.json b/package-lock.json index cb8ea4134..ca557fae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1111,6 +1111,12 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "class-transformer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz", + "integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -2308,7 +2314,8 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2332,13 +2339,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2348,19 +2357,22 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2481,7 +2493,8 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2495,6 +2508,7 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2511,6 +2525,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2519,13 +2534,15 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "mkdirp": { "version": "0.5.1", "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2614,7 +2631,8 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2628,6 +2646,7 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2723,7 +2742,8 @@ "version": "5.1.1", "resolved": false, "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2765,6 +2785,7 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2786,6 +2807,7 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2843,7 +2865,8 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", @@ -2915,7 +2938,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -6384,7 +6407,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/package.json b/package.json index bd4a9bee1..0a498ad86 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@types/yargs": "^12.0.9", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", + "class-transformer": "^0.2.3", "del": "^3.0.0", "gulp": "^4.0.0", "gulp-istanbul": "^1.1.3", diff --git a/test/github-issues/4219/entity/Photo.ts b/test/github-issues/4219/entity/Photo.ts new file mode 100644 index 000000000..847fd0153 --- /dev/null +++ b/test/github-issues/4219/entity/Photo.ts @@ -0,0 +1,14 @@ +import {Shim} from "../shim"; + +@Shim.Entity() +export class Photo { + + @Shim.PrimaryGeneratedColumn() + id: number; + + @Shim.Column() + url: string; + + user: any; + +} \ No newline at end of file diff --git a/test/github-issues/4219/entity/User.ts b/test/github-issues/4219/entity/User.ts new file mode 100644 index 000000000..0551967a5 --- /dev/null +++ b/test/github-issues/4219/entity/User.ts @@ -0,0 +1,37 @@ +import {Shim} from "../shim"; +import {Photo} from "./Photo"; + +// NOTE: The relations in here make no sense, we just care for the types. +// In real applications, this would of course not work! + +@Shim.Entity() +export class User { + + @Shim.PrimaryGeneratedColumn() + id: number; + + @Shim.Column() + name: string; + + @Shim.Column() + someDate: Date; + + @Shim.OneToOne(() => Photo) + @Shim.JoinColumn() + oneToOnePhoto: Photo; + + @Shim.OneToMany(() => Photo, (photo: Photo) => photo.user) + oneToManyPhotos: Photo[]; + + @Shim.ManyToOne(() => Photo) + @Shim.JoinColumn() + manyToOnePhoto: Photo; + + @Shim.ManyToMany(() => Photo) + @Shim.JoinColumn() + manyToManyPhotos: Photo[]; + + @Shim.TreeParent() + treeParentPhoto: Photo; + +} diff --git a/test/github-issues/4219/issue-4219.ts b/test/github-issues/4219/issue-4219.ts new file mode 100644 index 000000000..3c8730127 --- /dev/null +++ b/test/github-issues/4219/issue-4219.ts @@ -0,0 +1,34 @@ +import "reflect-metadata"; +import {plainToClass} from "class-transformer"; + +import {Photo} from "./entity/Photo"; +import {User} from "./entity/User"; + +describe("github issues > #4219 class-transformer-shim: support metadata reflection", () => { + + it("should create instances with the correct property types", () => { + + const photoLiteral = { + url: "typeorm.io" + }; + + const user = plainToClass(User, { + someDate: "Sat Jun 01 2019", + oneToOnePhoto: photoLiteral, + oneToManyPhotos: [photoLiteral], + manyToOnePhoto: photoLiteral, + manyToManyPhotos: [photoLiteral], + treeChildrenPhotos: [photoLiteral], + treeParentPhoto: photoLiteral + }); + + user.someDate.should.be.instanceof(Date); + user.oneToOnePhoto.should.be.instanceof(Photo); + user.oneToManyPhotos[0].should.be.instanceof(Photo); + user.manyToOnePhoto.should.be.instanceof(Photo); + user.manyToManyPhotos[0].should.be.instanceof(Photo); + user.treeParentPhoto.should.be.instanceof(Photo); + + }); + +}); \ No newline at end of file diff --git a/test/github-issues/4219/shim.ts b/test/github-issues/4219/shim.ts new file mode 100644 index 000000000..24ed2db38 --- /dev/null +++ b/test/github-issues/4219/shim.ts @@ -0,0 +1,10 @@ +let _Shim: any; +try { + // We're in /test + _Shim = require("../../../../extra/typeorm-class-transformer-shim"); +} catch (e) { + // We're in /build/compiled/test + _Shim = require("../../../../../extra/typeorm-class-transformer-shim"); +} + +export const Shim = _Shim; \ No newline at end of file