I am using TypeORM with Babel 7 and Typescript and it seem that the metadata are not present in the compiled code. Can something be done about that or is this a limitation of using Babel ?
The error
ColumnTypeUndefinedError: Column type for Photo#isPublished is not defined and cannot be guessed. Make sure you have turned on an "emitDecoratorMetadata": true option in tsconfig.json. Also make sure you have imported "reflect-metadata" on top of the main entry file in your application (before any entity imported).If you are using JavaScript instead of TypeScript you must explicitly provide a column type. at new ColumnTypeUndefinedError
Photo.js
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
isPublished: boolean
}
orm code
import 'reflect-metadata'
import {createConnection} from 'typeorm'
import {Photo} from './entities/Photo'
createConnection({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'root',
database: 'test',
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error))
package.json
{
"name": "typescript-babel-node",
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "run-p -r dev:run type-check:watch",
"dev:run": "nodemon --exec babel-node --extensions '.ts,.js' src/index.js",
"build": "babel src -d build --extensions '.ts,.js' src/index.js",
"start": "node build/index.js",
"type-check:watch": "tsc --watch",
"test": "jest --watch"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^10.12.0",
"express": "^4.16.4",
"pg": "^7.6.0",
"ramda": "^0.25.0",
"reflect-metadata": "^0.1.12",
"typeorm": "^0.2.8"
},
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/node": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-decorators": "^7.1.2",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-typescript": "^7.1.0",
"@types/express": "^4.16.0",
"@types/jest": "^23.3.7",
"@types/ramda": "^0.25.39",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"babel-plugin-module-resolver": "^3.1.1",
"dot-env": "0.0.1",
"eslint": "^5.7.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"jest": "^23.6.0",
"nodemon": "^1.18.5",
"npm-run-all": "^4.1.3",
"regenerator-runtime": "^0.12.1",
"source-map-loader": "^0.2.4",
"ts-jest": "^23.10.4",
"typescript": "^3.1.3",
"webpack": "^4.23.0",
"webpack-cli": "^3.1.2"
}
}
EDIT: Getting to this problem after a while I was able to find another Babel plugin for this, apart the one mentioned in the accepted answer: babel-plugin-transform-typescript-metadata Seems to be working with TypeORM and Nest.
Out of the box this is not supported as is noted in another answer. We can however write a babel plugin to get it to work.
Writing the code is not very complicated, the problems come from the limitations of the information we have inside babel. Babel does not perform type checking on typescript. This means we don't have any semantic information, we just have the type annotation and what information we can derive from it. This means that our solution is by necessity very limited
Limitations:
undefined
at runtime, to avoid the undefined we can do type || Object
to default to object if the type does not have a runtime value associatedNumber
or String
in the metadata, depending on the type of the enum. Since we write the type name to the metadata, this means you will end up with the container object of the enum inside the metadata. The type serialization can be copied from the typescript compiler itself with minimal changes (and it's really just two functions serializeTypeNode
and serializeTypeList
all about 150 lines of code).
For this sample class the results we get are:
declare var decorator: any;
interface ISampleInterface {
}
enum Flags { One }
class OtherClass {}
type ArrayAlias = number[]
class Test {
@decorator
untypedProp; // no design:type
@decorator
nrProp: number // Number as expected
@decorator
strProp: string // String as expected
@decorator
boolProp: boolean // Boolean as expected
@decorator
nrPropUndefined: number | undefined // Number as expected
@decorator
strPropUndefined: string | undefined // String as expected
@decorator
boolPropUndefined: boolean | undefined // Boolean as expected
@decorator
arrayProp: number[]
// Type references
@decorator
classRefProp: OtherClass; // OtherClass || Object = Object since OtherClass is still a class at runtime
@decorator
interfaceRefProp: ISampleInterface; // ISampleInterface || Object = Object since ISampleInterface is undefined at runtime
@decorator
enumRefProp: Flags; // Flags || Object = Flags since Flags exists as a value at runtime, here TS would have written Number/String
@decorator
typeAliasProp: ArrayAlias; // ArrayAlias || Object = Object since ArrayAlias does not exist t runtime and in Babel swe have no idea ArrayAlias is actually an array
@decorator
selfClassRefProp: Test; // Test || Object = Object since Babel puts decorators instantiation before class definition, this is a quirk, this may be fixable
}
The actual plugin code is not very large (minus the methods copied over from the TS version):
export default declare((api: typeof import('@babel/core'), { jsxPragma = "React" }): PluginObj => {
api.assertVersion(7);
return {
name: "transform-typescript-decorator-metadata",
inherits: syntaxTypeScript,
visitor: {
ClassDeclaration(path) {
var node = path.node;
for (const field of node.body.body) {
if (field.type !== "ClassProperty") continue;
if (field.typeAnnotation && field.typeAnnotation.type === "TSTypeAnnotation" && field.decorators && field.decorators.length > 0) {
const key = field.key as t.Identifier;
const serializedType = serializeTypeNode(field.typeAnnotation.typeAnnotation);
field.decorators.push(decorator(
t.callExpression(
t.memberExpression(t.identifier("Reflect"), t.identifier("metadata")), [
t.stringLiteral("design:type"),
t.logicalExpression("||", serializedType, createIdentifier("Object"))
])
))
}
}
},
}
};
});
You can find the full plugin code, and a working sample here
Just as a side not, plugin ordering matters, if @babel/plugin-proposal-class-properties
comes before our plugin it will have erased all the properties and our plugin will not have the info to emit the decorators anymore. This is the .babelrc
I tested with and it works, I did not get it to with with any other configuration (but I can't say I tried that hard)
{
"env": {},
"ignore": [],
"plugins": [
"../plugin/plugin.js",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
"babel-plugin-transform-es2015-modules-commonjs"
],
"presets": [
"@babel/preset-typescript"
]
}
Unfortunately I'm reasonably sure that this is a limitation of using Babel with Typescript. What Babel does, is it simply strips the Typescript typings, and then treats the code as JavaScript.
Which means babel doesn't care about your tsconfig.json
at all, and thus not emitDecoratorMetadata
either.
So unfortunately, if you need the decorator metadata, you'll have to stick with tsc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With