Search code examples
angulartypescriptwebpacktypeorm

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


Solution

  • I think this library is not designed to work in 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 (In your case none of them).