Inconsistent reflected enum types with --transpile-only enabled / disabled
LeoBakerHytch opened this issue · 1 comments
Problem
When running ts-node --transpile-only
, a class field declared with an imported (string) enum gets annotated as an Object
, rather than the expected String
.
This causes a problem with typegoose, which uses decorators to annotate a MongoDB / Mongoose database schema. Its prop
decorator runs some validations to ensure consistency between the specified field type and the constraints provided in the decorator options.
Specifically, it checks that a field decorated with @prop({ enum: MyStringEnum })
is actually a string field. With --transpile-only
enabled, this check always fails (this validation was added fairly recently: szokodiakos/typegoose@7418c34).
Repro
Can be cloned from https://gist.github.com/LeoBakerHytch/a45a6ed03b27807def5ec799d5a56c26
index.ts:
import 'reflect-metadata';
import { ExternalEnum } from './enum-metadata';
enum LocalEnum {
bar = 'bar'
}
class Class {
@prop()
local: LocalEnum; // OK
@prop()
external: ExternalEnum; // Errors only with --transpile-only: Type === Object
}
function prop() {
return function (target, key) {
const Type = Reflect.getMetadata('design:type', target, key);
if (Type !== String) {
throw Error(`Expected String for field "${key}"; got ${Type.name}`);
}
};
}
enum-metadata.ts:
export enum ExternalEnum {
foo = 'foo'
}
package.json scripts:
"scripts": {
"ok": "ts-node index.ts",
"ok2": "ts-node --transpile-only --type-check index.ts",
"error": "ts-node --transpile-only index.ts"
},
Compiled output
For comparison, TypeScript does emit the correct output when transpiling with isolatedModules: true
:
tslib_1.__decorate([
prop(),
tslib_1.__metadata("design:type", String) // <—— what we want
], Class.prototype, "external");
index.js:
"use strict";
exports.__esModule = true;
var tslib_1 = require("tslib");
require("reflect-metadata");
var enum_metadata_1 = require("./enum-metadata");
var LocalEnum;
(function (LocalEnum) {
LocalEnum["bar"] = "bar";
})(LocalEnum || (LocalEnum = {}));
var Class = /** @class */ (function () {
function Class() {
}
tslib_1.__decorate([
prop(),
tslib_1.__metadata("design:type", String)
], Class.prototype, "local");
tslib_1.__decorate([
prop(),
tslib_1.__metadata("design:type", String)
], Class.prototype, "external");
return Class;
}());
function prop() {
return function (target, key) {
var Type = Reflect.getMetadata('design:type', target, key);
if (Type !== String) {
throw Error("Expected String for field \"" + key + "\"; got " + Type.name);
}
};
}
Solution?
Is there any way around this other than enabling --type-check
or disabling --transpile-only
? (I’m not actually clear on whether those two flags are complementary — does it make any sense to use both flags?)
We have been using --transpile-only
as restarts of our server are incredibly slow without it... it would be a shame to have to disable it.
Related
The relavant Typegoose issue is szokodiakos/typegoose#196.
I’m not sure where the best place is to solve this issue. The additional validation added in Typegoose to ensure consistency of field types & constraints makes sense, so I wouldn’t really like to suggest that it be weakened for the sake of an environment-specific problem.
You would only need one of either --type-check
or --transpile-only
(--type-check
is there for backward compatibly and not documented anywhere as far as I know).
Unfortunately this is just how the TypeScript compiler works. For certain use-cases (such as these enums), TypeScript requires type information to compile correctly. If it needs type information, --transpile-only
will be incapable of generating the expected output.