Jack Murphy Jack Murphy - 9 days ago 5
TypeScript Question

How do i prevent decorators from importing node modules in TypeScript?

I'm trying to use the typeorm library in typescript to create a DTO/DAO pattern between a TypeScript 2 Express server and Angular 2.

I'm calling these objects DTOs for brevity, but they are just a bunch of fields, and annotations.

import {autoserialize} from "cerialize";
import {DTOReport} from "./reports.dto";

import { PrimaryGeneratedColumn} from "typeorm/decorator/columns/PrimaryGeneratedColumn"
import { CreateDateColumn } from "typeorm/decorator/columns/CreateDateColumn";
import { Column } from "typeorm/decorator/columns/Column";
import { JoinColumn } from "typeorm/decorator/relations/JoinColumn";
import { OneToOne } from "typeorm/decorator/relations/OneToOne";
import { ManyToOne } from "typeorm/decorator/relations/ManyToOne";
import { OneToMany } from "typeorm/decorator/relations/OneToMany";
import { Table } from "typeorm/decorator/tables/Table";
import { ColumnTypes } from "typeorm/metadata/types/ColumnTypes";

@Table()
export class DTOSourceFile {
@PrimaryGeneratedColumn()
@autoserialize
id: number;

@Column()
@autoserialize
locationURL: string;

@CreateDateColumn()
@autoserialize
createdAt: Date;

@OneToMany(type => DTOReport, report => report.source, {nullable: true})
@autoserialize report: DTOReport;
@autoserialize reportId: number;

@Column(ColumnTypes.TEXT)
@autoserialize
originalname: string;

@Column(ColumnTypes.JSON)
@autoserialize
mutler: string;
}


I'm carefully importing only the decorators. However, at compile time i see a request go back to the root index.ts file.

"use strict";
const ColumnTypes_1 = require("../../metadata/types/ColumnTypes");
const ColumnTypeUndefinedError_1 = require("../error/ColumnTypeUndefinedError");
const index_1 = require("../../index"); // < --- THIS
const PrimaryColumnCannotBeNullableError_1 = require("../error/PrimaryColumnCannotBeNullableError");
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
* Primary columns also creates a PRIMARY KEY for this column in a db.
*/
function PrimaryColumn(typeOrOptions, options) {
let type;
if (typeof typeOrOptions === "string") {
type = typeOrOptions;
}
else {
options = typeOrOptions;
}
return function (object, propertyName) {
const reflectedType = ColumnTypes_1.ColumnTypes.typeToString(Reflect.getMetadata("design:type", object, propertyName));
// if type is not given implicitly then try to guess it
if (!type)
type = ColumnTypes_1.ColumnTypes.determineTypeFromFunction(Reflect.getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options)
options = {};
// check if there is no type in column options then set type from first function argument, or guessed one
if (!options.type)
options = Object.assign({ type: type }, options);
// if we still don't have a type then we need to give error to user that type is required
if (!options.type)
throw new ColumnTypeUndefinedError_1.ColumnTypeUndefinedError(object, propertyName);
// check if column is not nullable, because we cannot allow a primary key to be nullable
if (options.nullable)
throw new PrimaryColumnCannotBeNullableError_1.PrimaryColumnCannotBeNullableError(object, propertyName);
// implicitly set a primary to column options
options = Object.assign({ primary: true }, options);
// create and register a new column metadata
const args = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "regular",
options: options
};
index_1.getMetadataArgsStorage().columns.add(args); // < --- THIS
};
}
exports.PrimaryColumn = PrimaryColumn;

//# sourceMappingURL=PrimaryColumn.js.map


The resulting error from Angular 2's webpack compiler clearly shows the issue as it then tries to load node dependencies.

WARNING in ./~/typeorm/driver/sqlserver/SqlServerDriver.js
Module not found: Error: Can't resolve 'mssql' in '/Users/jmurphy/projects/ubq/web/node_modules/typeorm/driver/sqlserver'
@ ./~/typeorm/driver/sqlserver/SqlServerDriver.js 256:25-41
@ ./~/typeorm/connection/ConnectionManager.js
@ ./~/typeorm/index.js
@ ./~/typeorm/decorator/columns/PrimaryGeneratedColumn.js
@ ./src/app/dtos/lens.dto.ts
@ ./src/app/lens/lens.component.ts
@ ./src/app/app.module.ts
@ ./src/app/index.ts
@ ./src/main.ts
@ multi main
Child html-webpack-plugin for "index.html":
Asset Size Chunks Chunk Names
index.html 2.88 kB 0
webpack: bundle is now VALID.


Is there a way to allow TypeORM decorators to exist in a browser project without trying to load node dependencies?

Links:

TypeORM Source:
https://github.com/typeorm/typeorm/blob/master/src/decorator/columns/PrimaryColumn.ts

Answer

I think this library is not designed to work on web browsers but maybe there is a solution. The decorator uses the getMetadataArgsStorage function which is declared at /index.ts. The /index.ts imports a lot of things that are leading you to the problem.

You can see here how the getMetadataArgsStorage function uses defaultContainer and MetadataArgsStorage.

If getMetadataArgsStorage was moved from the index.ts to a new file maybe it will work for you but you are going to need to send a PR to the project on GitHub.

The problem is this line because ConnectionManager imports all the drivers:

import * as fs from "fs";
import {Connection} from "./Connection";
import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
import {ConnectionOptions} from "./ConnectionOptions";
import {DriverOptions} from "../driver/DriverOptions";
import {Driver} from "../driver/Driver";
import {MissingDriverError} from "./error/MissingDriverError";
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
import {AlreadyHasActiveConnectionError} from "./error/AlreadyHasActiveConnectionError";
import {Logger} from "../logger/Logger";
import {SqliteDriver} from "../driver/sqlite/SqliteDriver";
import {OracleDriver} from "../driver/oracle/OracleDriver";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {OrmUtils} from "../util/OrmUtils";
import {CannotDetermineConnectionOptionsError} from "./error/CannotDetermineConnectionOptionsError";

/**
 * ConnectionManager is used to store and manage all these different connections.
 * It also provides useful factory methods to simplify connection creation.
 */
export class ConnectionManager {
// ...

I recommend you to explain your use case to the authors of typeorm. Also maybe mention that it would be good if not all the drivers are always imported by default. It would be better to only load the required driver.

Comments