TypeScript元数据反射在定义其他类之前引用这些类



我的代码库中有一些TypeORM实体,它们之间有关系,形成了循环依赖关系。由于decorator元数据用于每个实体类,所以TypeScript在每个类之后插入代码,在类上定义元数据。假设类是BusinessQualification。在相关字段上,TypeScript将发出如下代码:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
const decorator = (target, thing) => {
};
class Business {
}
class Qualification {
}
__decorate([
decorator,
__metadata("design:type", Business)
], Qualification.prototype, "business", void 0);

这一切都很好,只是__decorate部分总是在每个类之后,这意味着在定义其中一个类之前必须使用它,这会导致错误。以下是带有实际错误的实际代码的缩短版本:

let Qualification = (_dec = Object(external_typeorm_["Entity"])(), _dec2 = Object(external_typeorm_["PrimaryGeneratedColumn"])(), _dec3 = Reflect.metadata("design:type", Number), _dec4 = Object(external_typeorm_["Column"])({
nullable: true
}), _dec5 = Object(external_class_validator_["IsOptional"])(), _dec6 = Object(external_class_validator_["IsUrl"])(), _dec7 = Reflect.metadata("design:type", String), _dec8 = Object(external_typeorm_["ManyToOne"])(type => Business["c" /* default */], business => business.qualifications, {
onDelete: 'CASCADE'
}), _dec9 = Reflect.metadata("design:type", typeof Business["c" /* default */] === "undefined" ?
// ^ TypeError: cannot read property "c" of undefined

Object : Business["c" /* default */]), _dec10 = Object(external_typeorm_["Column"])({
type: 'enum',
enum: VALIDITY_STATES,
default: 'invalid'
}), _dec11 = Object(external_class_validator_["IsIn"])(VALIDITY_STATES), _dec12 = Reflect.metadata("design:type", Object), _dec13 = Object(external_typeorm_["Column"])('simple-json'), _dec14 = Object(external_class_validator_["ValidateNested"])(), _dec15 = Object(external_class_validator_["IsArray"])(), _dec16 = Object(external_class_validator_["IsIn"])(category["a" /* CATEGORIES */].filter(c => c.type === 'service').map(c => c.slug), {
each: true
}), _dec17 = Reflect.metadata("design:type", Array), _dec(_class = (_class2 = (_temp = class Qualification {
constructor() {
_initializerDefineProperty(this, "id", _descriptor, this);
_initializerDefineProperty(this, "imageUrl", _descriptor2, this);
_initializerDefineProperty(this, "business", _descriptor3, this);
_initializerDefineProperty(this, "validity", _descriptor4, this);
_initializerDefineProperty(this, "categories", _descriptor5, this);
}
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "id", [_dec2, _dec3], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "imageUrl", [_dec4, _dec5, _dec6, _dec7], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor3 = _applyDecoratedDescriptor(_class2.prototype, "business", [_dec8, _dec9], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor4 = _applyDecoratedDescriptor(_class2.prototype, "validity", [_dec10, _dec11, _dec12], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor5 = _applyDecoratedDescriptor(_class2.prototype, "categories", [_dec13, _dec14, _dec15, _dec16, _dec17], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class2)) || _class);

在代码的后面,定义了Business,但为时已晚:

let Business = (_dec6 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Entity"])(), _dec7 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])(), _dec8 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsString"])(), _dec9 = Reflect.metadata("design:type", String), _dec10 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
type: 'enum',
enum: BUSINESS_TYPES
}), _dec11 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsIn"])(BUSINESS_TYPES), _dec12 = Reflect.metadata("design:type", Object), _dec13 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])(), _dec14 = Reflect.metadata("design:type", Boolean), _dec15 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["CreateDateColumn"])(), _dec16 = Reflect.metadata("design:type", typeof Date === "undefined" ? Object : Date), _dec17 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
nullable: true
}), _dec18 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsOptional"])(), _dec19 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsUrl"])(), _dec20 = Reflect.metadata("design:type", String), _dec21 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
nullable: true
}), _dec22 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsOptional"])(), _dec23 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsString"])(), _dec24 = Reflect.metadata("design:type", String), _dec25 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
nullable: true
}), _dec26 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsOptional"])(), _dec27 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsString"])(), _dec28 = Reflect.metadata("design:type", String), _dec29 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])(), _dec30 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsString"])(), _dec31 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["MaxLength"])(200), _dec32 = Reflect.metadata("design:type", String), _dec33 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["OneToMany"])(type => _db_all_entities__WEBPACK_IMPORTED_MODULE_3__[/* Qualification */ "i"], qualification => qualification.business, {
cascade: true
}), _dec34 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ValidateNested"])(), _dec35 = Reflect.metadata("design:type", Array), _dec36 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])('simple-json'), _dec37 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsArray"])(), _dec38 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ValidateNested"])(), _dec39 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsIn"])(_misc_types_category__WEBPACK_IMPORTED_MODULE_2__[/* CATEGORIES */ "a"].filter(c => c.type === 'business').map(c => c.slug), {
each: true
}), _dec40 = Reflect.metadata("design:type", Array), _dec41 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])('simple-json'), _dec42 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsArray"])(), _dec43 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ValidateNested"])(), _dec44 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ArrayMinSize"])(1), _dec45 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ArrayMaxSize"])(5), _dec46 = Reflect.metadata("design:type", Array), _dec47 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
type: 'enum',
enum: PRICING_PLANS
}), _dec48 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsIn"])(PRICING_PLANS), _dec49 = Reflect.metadata("design:type", String), _dec50 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["OneToMany"])(type => _db_all_entities__WEBPACK_IMPORTED_MODULE_3__[/* BaseOffer */ "b"], offer => offer.offerer), _dec51 = Reflect.metadata("design:type", Array), _dec6(_class3 = (_class4 = (_temp2 = class Business extends _db_all_entities__WEBPACK_IMPORTED_MODULE_3__[/* Account */ "a"] {
constructor(...args) {
super(...args);
_initializerDefineProperty(this, "name", _descriptor3, this);
_initializerDefineProperty(this, "type", _descriptor4, this);
_initializerDefineProperty(this, "isApproved", _descriptor5, this);
_initializerDefineProperty(this, "since", _descriptor6, this);
_initializerDefineProperty(this, "logoUrl", _descriptor7, this);
_initializerDefineProperty(this, "fein", _descriptor8, this);
_initializerDefineProperty(this, "phoneNumber", _descriptor9, this);
_initializerDefineProperty(this, "bio", _descriptor10, this);
_initializerDefineProperty(this, "qualifications", _descriptor11, this);
_initializerDefineProperty(this, "businessCategories", _descriptor12, this);
_initializerDefineProperty(this, "geolocations", _descriptor13, this);
_initializerDefineProperty(this, "pricingPlan", _descriptor14, this);
_initializerDefineProperty(this, "offers", _descriptor15, this);
}
}, _temp2), (_descriptor3 = _applyDecoratedDescriptor(_class4.prototype, "name", [_dec7, _dec8, _dec9], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor4 = _applyDecoratedDescriptor(_class4.prototype, "type", [_dec10, _dec11, _dec12], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor5 = _applyDecoratedDescriptor(_class4.prototype, "isApproved", [_dec13, _dec14], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor6 = _applyDecoratedDescriptor(_class4.prototype, "since", [_dec15, _dec16], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor7 = _applyDecoratedDescriptor(_class4.prototype, "logoUrl", [_dec17, _dec18, _dec19, _dec20], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor8 = _applyDecoratedDescriptor(_class4.prototype, "fein", [_dec21, _dec22, _dec23, _dec24], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor9 = _applyDecoratedDescriptor(_class4.prototype, "phoneNumber", [_dec25, _dec26, _dec27, _dec28], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor10 = _applyDecoratedDescriptor(_class4.prototype, "bio", [_dec29, _dec30, _dec31, _dec32], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor11 = _applyDecoratedDescriptor(_class4.prototype, "qualifications", [_dec33, _dec34, _dec35], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor12 = _applyDecoratedDescriptor(_class4.prototype, "businessCategories", [_dec36, _dec37, _dec38, _dec39, _dec40], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor13 = _applyDecoratedDescriptor(_class4.prototype, "geolocations", [_dec41, _dec42, _dec43, _dec44, _dec45, _dec46], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor14 = _applyDecoratedDescriptor(_class4.prototype, "pricingPlan", [_dec47, _dec48, _dec49], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor15 = _applyDecoratedDescriptor(_class4.prototype, "offers", [_dec50, _dec51], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class4)) || _class3);

奇怪的是,代码在为开发模式编译时可以工作,因为Business不是直接引用的,而是通过模块常量引用的。以下是Qualification在开发模式中的定义:

let Qualification = (_dec = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Entity"])(), _dec2 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["PrimaryGeneratedColumn"])(), _dec3 = Reflect.metadata("design:type", Number), _dec4 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
nullable: true
}), _dec5 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsOptional"])(), _dec6 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsUrl"])(), _dec7 = Reflect.metadata("design:type", String), _dec8 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["ManyToOne"])(type => _db_all_entities__WEBPACK_IMPORTED_MODULE_2__["Business"], business => business.qualifications, {
onDelete: 'CASCADE'
}), _dec9 = Reflect.metadata("design:type", typeof _db_all_entities__WEBPACK_IMPORTED_MODULE_2__["Business"] === "undefined" ? Object : _db_all_entities__WEBPACK_IMPORTED_MODULE_2__["Business"]), _dec10 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])({
type: 'enum',
enum: VALIDITY_STATES,
default: 'invalid'
}), _dec11 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsIn"])(VALIDITY_STATES), _dec12 = Reflect.metadata("design:type", Object), _dec13 = Object(typeorm__WEBPACK_IMPORTED_MODULE_1__["Column"])('simple-json'), _dec14 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["ValidateNested"])(), _dec15 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsArray"])(), _dec16 = Object(class_validator__WEBPACK_IMPORTED_MODULE_0__["IsIn"])(_misc_types_category__WEBPACK_IMPORTED_MODULE_5__["CATEGORIES"].filter(c => c.type === 'service').map(c => c.slug), {
each: true
}), _dec17 = Reflect.metadata("design:type", Array), _dec(_class = (_class2 = (_temp = class Qualification {
constructor() {
_initializerDefineProperty(this, "id", _descriptor, this);
_initializerDefineProperty(this, "imageUrl", _descriptor2, this);
_initializerDefineProperty(this, "business", _descriptor3, this);
_initializerDefineProperty(this, "validity", _descriptor4, this);
_initializerDefineProperty(this, "categories", _descriptor5, this);
}
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "id", [_dec2, _dec3], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "imageUrl", [_dec4, _dec5, _dec6, _dec7], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor3 = _applyDecoratedDescriptor(_class2.prototype, "business", [_dec8, _dec9], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor4 = _applyDecoratedDescriptor(_class2.prototype, "validity", [_dec10, _dec11, _dec12], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor5 = _applyDecoratedDescriptor(_class2.prototype, "categories", [_dec13, _dec14, _dec15, _dec16, _dec17], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class2)) || _class);

实际的代码本身从all-entities.ts文件中导入模块,该文件以正确的顺序导出所有实体,这样超类就不会在其子类之后意外加载,从而导致错误。该文件如下(简化(:

export { default as Qualification } from '../entities/Qualification';
export { default as Business } from '../entities/Business';

./entities/Qualification.ts./entities/Business.ts都是包含TypeORM实体的默认导出的文件,我觉得它们不值得包含在这里,但如果有人想查看它们,我可以。以下是我的生产和开发webpack配置(由Next.js生成(之间的区别:

diff --git a/webpack-config-dev.txt b/webpack-config-prod.txt
index f8a28c3..8e5fa4d 100644
--- a/webpack-config-dev.txt
+++ b/webpack-config-prod.txt
@@ -1,80 +1,82 @@
{
externals: [ [Function] ],
optimization: {
checkWasmTypes: false,
nodeEnv: false,
splitChunks: false,
runtimeChunk: undefined,
minimize: false,
minimizer: [ [TerserPlugin], [CssMinimizerPlugin] ]
},
context: 'C:\Users\Robbie\Code\fit-society',
node: { setImmediate: false },
entry: [AsyncFunction: entry],
output: {
path: 'C:\Users\Robbie\Code\fit-society\.next\server',
filename: [Function: filename],
libraryTarget: 'commonjs2',
hotUpdateChunkFilename: 'static/webpack/[id].[hash].hot-update.js',
hotUpdateMainFilename: 'static/webpack/[hash].hot-update.json',
-    chunkFilename: '[name].js',
+    chunkFilename: '[name].[contenthash].js',
strictModuleExceptionHandling: true,
crossOriginLoading: undefined,
-    futureEmitAssets: false,
+    futureEmitAssets: true,
webassemblyModuleFilename: 'static/wasm/[modulehash].wasm'
},
performance: false,
resolve: {
extensions: [
'.tsx',  '.ts',
'.js',   '.mjs',
'.jsx',  '.json',
'.wasm'
],
modules: [ 'node_modules' ],
alias: {
'next/head': 'next/dist/next-server/lib/head.js',
'next/router': 'next/dist/client/router.js',
'next/config': 'next/dist/next-server/lib/runtime-config.js',
'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
next: 'C:\Users\Robbie\Code\fit-society\node_modules\next',
'private-next-pages': 'C:\Users\Robbie\Code\fit-society\src\pages',
'private-dot-next': 'C:\Users\Robbie\Code\fit-society\.next'
},
mainFields: [ 'main', 'module' ],
plugins: [ [Object] ]
},
resolveLoader: {
alias: {
'emit-file-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\emit-file-loader',
'error-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\error-loader',
'next-babel-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\next-babel-loader',
'next-client-pages-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\next-client-pages-loader',
'next-data-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\next-data-loader',
'next-serverless-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\next-serverless-loader',
'noop-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\noop-loader',
'next-plugin-loader': 'C:\Users\Robbie\Code\fit-society\node_modules\next\dist\build\webpack\loaders\next-plugin-loader'
},
modules: [ 'node_modules' ],
plugins: [ [Object] ]
},
module: {
rules: [ [Object], [Object], [Object] ],
strictExportPresence: true
},
plugins: [
ChunkNamesPlugin {},
DefinePlugin { definitions: [Object] },
-    UnlinkRemovedPagesPlugin { prevAssets: {} },
-    NoEmitOnErrorsPlugin {},
-    NextJsRequireCacheHotReloader { prevAssets: null },
+    HashedModuleIdsPlugin { options: [Object] },
+    IgnorePlugin {
+      options: [Object],
+      checkIgnore: [Function: bound checkIgnore]
+    },
PagesManifestPlugin { serverless: false },
NextJsSsrImportPlugin { options: [Object] },
NextJsSsrImportPlugin {},
FilterWarningsPlugin { exclude: [Array] }
],
-  mode: 'development',
+  mode: 'production',
name: 'server',
target: 'node',
-  devtool: 'cheap-module-source-map'
+  devtool: false
}

以下是导致问题的类:商业.ts:

import {
ArrayMaxSize,
ArrayMinSize,
IsArray,
IsIn,
IsOptional,
IsString,
IsUrl,
MaxLength,
ValidateNested,
IsEmail
} from 'class-validator';
import { Column, CreateDateColumn, Entity, OneToMany } from 'typeorm';
import { CATEGORIES } from '../../misc-types/category';
import { Geolocation } from '../../misc-types/geolocation';
import { Account, BaseOffer, Qualification, ValidateableQualification } from '../db/all-entities';
import { omit } from './utils/entity-type-manipulations';
import tuple from './utils/string-enum-from-tuple';
const BUSINESS_TYPES = tuple('individual', 'company');
const PRICING_PLANS = tuple('free');
@Entity()
export default class Business extends Account {
/**
* Public name for the business.
*/
@Column()
@IsString()
name!: string;
@Column({ type: 'enum', enum: BUSINESS_TYPES })
@IsIn(BUSINESS_TYPES)
type!: typeof BUSINESS_TYPES[number];
@Column()
isApproved!: boolean;
/**
* The date the business created their account (not when it was approved)
*/
@CreateDateColumn()
since!: Date;
@Column({ nullable: true })
@IsOptional()
@IsUrl()
logoUrl?: string;
@Column({ nullable: true })
@IsOptional()
@IsString()
fein?: string;
@Column({ nullable: true })
@IsOptional()
@IsString()
phoneNumber?: string;
@Column()
@IsString()
@MaxLength(200)
bio!: string;
@OneToMany(
type => Qualification,
qualification => qualification.business,
{ cascade: true }
)
@ValidateNested()
qualifications!: Qualification[];
@Column('simple-json')
@IsArray()
@ValidateNested()
@IsIn(
CATEGORIES.filter(c => c.type === 'business').map(c => c.slug),
{ each: true }
)
businessCategories!: string[];
/**
* Places this business is available at
*/
@Column('simple-json')
@IsArray()
@ValidateNested()
@ArrayMinSize(1)
@ArrayMaxSize(5)
geolocations!: Geolocation[];
@Column({ type: 'enum', enum: PRICING_PLANS })
@IsIn(PRICING_PLANS)
pricingPlan!: 'free';
@OneToMany(
type => BaseOffer,
offer => offer.offerer
)
offers!: BaseOffer[];
}
/**
* A DTO sent to change business properties, most of which align one-to-one (excluding password/passwordHash).
*/
export class EditableBusiness extends omit(Business, [
'id',
'since',
'isApproved',
'qualifications',
'passwordHash',
'offers'
]) {
@IsString()
password!: string;
}
export class BusinessApplication extends EditableBusiness {
@ValidateNested()
@IsOptional()
initialQualification!: ValidateableQualification;
}

在Qualification.ts:中

import { IsJSON, IsUrl, ValidateNested, IsOptional, IsBoolean, IsIn, IsArray } from 'class-validator';
import { Column, Entity, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Business } from '../db/all-entities';
import tuple from './utils/string-enum-from-tuple';
import { omit } from './utils/entity-type-manipulations';
import { CATEGORIES } from '../../misc-types/category';
const VALIDITY_STATES = tuple('valid', 'pending-review', 'invalid');
@Entity()
export default class Qualification {
@PrimaryGeneratedColumn()
id!: number;
@Column({ nullable: true })
// businesses do not need image proof
@IsOptional()
@IsUrl()
imageUrl?: string;
@ManyToOne(
type => Business,
business => business.qualifications,
{ onDelete: 'CASCADE' }
)
business!: Business;
@Column({ type: 'enum', enum: VALIDITY_STATES, default: 'invalid' })
@IsIn(VALIDITY_STATES)
validity!: typeof VALIDITY_STATES[number];
/**
* The categories (slugs)
*/
@Column('simple-json')
@ValidateNested()
@IsArray()
@IsIn(
CATEGORIES.filter(c => c.type === 'service').map(c => c.slug),
{ each: true }
)
categories!: string[];
}
/**
* A qualification that can be sent by a business which is not necessarily verified yet.
*/
export const ValidateableQualification = omit(Qualification, ['id', 'business', 'validity']);
export type ValidateableQualification = typeof ValidateableQualification extends new () => infer U ? U : never;

无论何时导入这些类中的任何一个,都会从该文件中导入它们,以确保正确的模块加载顺序:

/* eslint-disable import/first */
/**
* This file exists to solve circular dependency problems with Webpack by explicitly specifying the module loading order.
* @see https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
*/
export { default as Qualification, ValidateableQualification } from '../entities/Qualification';
export { default as Account } from '../entities/Account';
export { default as Business, EditableBusiness, BusinessApplication } from '../entities/Business';
export { default as Customer } from '../entities/Customer';
export { default as BaseOffer } from '../entities/Offer';
import ProductOffer, { EditableProductOffer } from '../entities/ProductOffer';
import ServiceOffer, { EditableServiceOffer } from '../entities/ServiceOffer';
export { default as ProductOffer, EditableProductOffer } from '../entities/ProductOffer';
export { default as ServiceOffer, EditableServiceOffer } from '../entities/ServiceOffer';
export type Offer = ProductOffer | ServiceOffer;
export type EditableOffer = EditableProductOffer | EditableServiceOffer;

巴别塔也在这个项目中使用。这是。babelrc:

{
"presets": [
[
"next/babel",
{
"class-properties": {
"loose": true
},
"styled-jsx": {
"plugins": [
"styled-jsx-plugin-postcss"
]
}
}
]
],
"plugins": [
"babel-plugin-transform-typescript-metadata",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}

抱歉代码太大了。有人能帮我解决这个问题,并想办法让它在生产中像在开发中一样发挥作用吗?谢谢

编辑:我看到您使用Qualification作为ValidateableQualification中的值。这听起来像是你可以在JS中做的事情,但我认为这会扰乱TS继承/编译,因为使用Qualification作为而不是类型会迫使TS在完成webpack绑定时导入实际代码。

此外,也许您可以通过class-validator本身或扩展类来实现这一点。

export const ValidateableQualification = omit(Qualification, ['id', 'business', 'validity']);

你能试着删除这个代码,看看循环依赖性是否还会上升吗?

此外,我读到您正在从一个中心文件导入所有实体。即使这听起来是解决循环依赖关系的一件好事,但它也可能导致错误,因为您可能要导入值而不是类型。我建议您不要使用类似的东西在之间导入实体,只需使用RelationalEntities.ts这样的中心文件并执行:

export const RelationalEntities = [
Qualification,
Business,
// ...
]

并在TypeORM数据库连接配置中使用,即entities: RelationalEntities

旧的答案,在看到更新的代码和配置之前:

通常,这是在TypeORM中解决的,只需在关系定义中使用type => Type,而不是真正的类型Type。即:

@OneToMany(type => Qualification)
qualification!: Qualification;
// instead of (will not work)
@OneToMany(Qualification)
qualification!: Qualification;

这是由于TS在工作,因此type => Qualification基本上只用于提取关于Qualification的元数据,而不在运行时引用它(或者,更好的是,不在第一次运行时直接引用它,因此由于它是惰性的,因此不存在循环依赖性(

最新更新