Skip to content

Mutating class prototype incompatible with babel transpiled class #840

@waihokwok

Description

@waihokwok

Describe the bug
I try to define an entity with a SerializedPrimaryKey property. The id getter/setter can be correctly defined when using ts-jest. However, transpiling the code with babel in production build (I am using nestjs), the id getter/setter fails to be defined.
For example:

@Entity()
class MyEntity {
  @PrimaryKey()
  _id!: ObjectId;

  @SerializedPrimaryKey()
  id!: string;
}

// some where in my code
const myEntity = new MyEntity();
wrap(myEntity).assign({ _id: new ObjectId() }, { em })
console.log(myEntity.id) // output string id in test but undefined in production

Later, after looking into the source code, I found that EntityHelper define the id property to the entity prototype. However, with above definition, the transpiled MyEntity class constructor would define id property with descriptor { value: undefined, writable: true, enumerable: true, configurable: true}. As a result, the entity instance will never reference to its prototype for its id property.

Workaround
I figured out two workarounds.

The first way is to use Object.create instead of constructor. The limitation is that property initializers would not be called.

@Entity()
class MyEntity {
  @PrimaryKey()
  _id!: ObjectId;

  @SerializedPrimaryKey()
  id!: string;
}

// some where in my code
const myEntity = Object.create(MyEntity.prototype);
wrap(myEntity).assign({ _id: new ObjectId() }, { em })
console.log(myEntity.id) // output string id in both test and production

The second way is to define id as a dummy getter as it would be overwrite by EntityHelper anyway. The dummy getter is not looking good.

@Entity()
class MyEntity {
  @PrimaryKey()
  _id!: ObjectId;

  @SerializedPrimaryKey()
  get id(): string { return '' };
}

// some where in my code
const myEntity = new MyEntity();
wrap(myEntity).assign({ _id: new ObjectId() }, { em })
console.log(myEntity.id) // output string id in both test and production

To Reproduce
Here is my babel.config.json:

{
	"presets": [
		[
			"@babel/env",
			{
				"targets": {
					"browsers": "> 0.5%, last 2 versions, Firefox ESR, not dead"
				},
				"useBuiltIns": "usage",
				"corejs": { "version": 3, "proposals": true }
			}
		],
		"@babel/preset-react",
		"@babel/preset-typescript"
	],
	"plugins": [
		"babel-plugin-transform-typescript-metadata",
		["@babel/plugin-proposal-decorators", { "legacy": true }],
		["@babel/plugin-proposal-class-properties", { "loose": true }]
	]
}

Afterthought
It seems that mutating class prototype may cause some inconsistent behaviors between untranspiled and transpiled code. Is there a better way to achieve this? Babel complies the ES6 spec and typescript may change in future. Thus, I think this issue should be addressed.
Thanks for the great work.

Versions

Dependency Version
node 12.8.0
typescript 4.0.2
@mikro-orm/core 4.0.0
@mikro-orm/mongodb 4.0.0
@babel/cli 7.10.5
@babel/core 7.11.4
@babel/plugin-proposal-class-properties 7.10.4
@babel/plugin-proposal-decorators 7.10.5
@babel/preset-env 7.11.0
@babel/preset-react 7.10.4
@babel/preset-typescript 7.10.4

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions