typescript - TypeScript 元数据反射在定义之前引用其他类
问题描述
我的代码库中有一些 TypeORM 实体相互关联,形成循环依赖。由于在每个实体类上都使用了装饰器元数据,因此 TypeScript 在每个定义元数据的类之后插入代码。说类是Business
和Qualification
。在相关字段上,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
}
以下是导致问题的类: Business.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;
本项目也使用了 Babel。这是.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
在关系定义中使用而不是 true type 来解决Type
。IE:
@OneToMany(type => Qualification)
qualification!: Qualification;
// instead of (will not work)
@OneToMany(Qualification)
qualification!: Qualification;
这是由于 TS 工作,所以它type => Qualification
本质上仅用于提取关于 的元数据Qualification
,而不在运行时引用它(或者,更好的是,在第一次运行时不直接引用它,因此由于它是惰性的,因此不存在循环依赖)