@buka/nestjs-type-helper
Version:
An easy to use nestjs config module
1 lines • 142 kB
Source Map (JSON)
{"version":3,"sources":["../src/mikro-orm/models/base-entity.ts","../src/mikro-orm/models/timestamped-entity.ts","../src/mikro-orm/decorators/entity-property.decorator.ts","../src/mikro-orm/decorators/api-entity-property.decorator.ts","../src/mikro-orm/decorators/api-scalar-entity-property.decorator.ts","../src/decorators/nested-property.decorator.ts","../src/decorators/model/model.register.ts","../src/decorators/model/model.decorator.ts","../src/exceptions/exceptions.ts","../src/exceptions/type-exception.ts","../src/decorators/model/property.decorator.ts","../src/decorators/model/relation.decorator.ts","../src/decorators/model/nested.decorator.ts","../src/decorators/model/list.decorator.ts","../src/decorators/reference-property.decorator.ts","../src/decorators/class-validator/has-any-key.ts","../src/decorators/class-validator/match-json-schema.ts","../src/decorators/page-query.ts","../src/pipes/page-query-validation.pipe.ts","../src/utils/logger.ts","../src/utils/nestjs-swagger-utils.ts","../src/utils/mikro-orm-utils.ts","../src/mikro-orm/converters/entity-ref-type/entity-ref-type.ts","../src/mikro-orm/converters/entity-dto-type/entity-dto-type.ts","../src/mikro-orm/decorators/entity-enum.decorator.ts","../src/mikro-orm/decorators/entity-transient.decorator.ts","../src/mikro-orm/decorators/entity-one-to-one.decorator.ts","../src/mikro-orm/decorators/entity-one-to-many.decorator.ts","../src/mikro-orm/decorators/entity-many-to-one.decorator.ts","../src/mikro-orm/decorators/entity-many-to-many.decorator.ts","../src/mikro-orm/database.config.ts","../src/swagger/is-reference-object.ts","../src/swagger/deep-dereference.ts","../src/swagger/for-each-parameter.ts","../src/swagger/deep-objectify-queries.ts","../src/pipes/validation.pipe.ts","../src/converters/filter-query-type/filter-query-type.ts","../src/utils/class-validator-utils.ts","../src/utils/class-transformer-utils.ts","../src/converters/filter-query-type/decorators/filter-query-operators.decorator.ts","../src/converters/filter-query-type/utils/get-collection-operators.ts","../src/converters/filter-query-type/utils/get-property-operators.ts","../src/converters/filter-query-type/utils/swagger-metadata-to-json-schema.ts","../src/converters/filter-query-type/get-filter-query-schema.ts","../src/converters/pick-type/pick-type.ts","../src/converters/omit-type/omit-type.ts","../src/converters/partial-type/partial-type.ts","../src/converters/order-query-type/get-order-query-schema.ts","../src/converters/order-query-type/order-query-type.ts","../src/ro/offset-pagination.ro.ts","../src/ro/cursor-pagination.ro.ts","../src/converters/list-response-body-type/list-response-body-type.ts","../src/converters/response-body-type/response-body-type.ts","../src/models/offset-pagination.ts","../src/models/cursor-pagination.ts","../src/models/slice.ts"],"sourcesContent":["import { BigIntType, Config, DefineConfig, PrimaryKey, PrimaryKeyProp } from '@mikro-orm/core'\nimport { ApiProperty } from '@nestjs/swagger'\nimport { IsNumberString } from 'class-validator'\nimport { TimestampedEntity } from './timestamped-entity.js'\n\n\nexport abstract class BaseEntity<Optional = never> extends TimestampedEntity<Optional> {\n // [Config]?: DefineConfig<{ forceObject: true }>;\n [PrimaryKeyProp]?: 'id'\n\n @ApiProperty({\n type: 'string',\n description: 'PK',\n example: '1',\n required: true,\n })\n @PrimaryKey({\n type: new BigIntType('string'),\n comment: '主键',\n })\n @IsNumberString()\n id!: string\n}\n","import { Config, DefineConfig, OptionalProps } from '@mikro-orm/core'\nimport { EntityProperty } from '../decorators/entity-property.decorator'\n\n\nexport abstract class TimestampedEntity<Optional = never> {\n [Config]?: DefineConfig<{ forceObject: true }>;\n [OptionalProps]?: 'createdAt' | 'updatedAt' | Optional\n\n @EntityProperty({\n type: 'datetime',\n onCreate: () => new Date(),\n defaultRaw: 'CURRENT_TIMESTAMP',\n comment: '创建时间',\n })\n createdAt: Date = new Date()\n\n @EntityProperty({\n type: 'datetime',\n onUpdate: () => new Date(),\n defaultRaw: 'CURRENT_TIMESTAMP',\n comment: '更新时间',\n })\n updatedAt: Date = new Date()\n}\n","import { Property as OrmProperty, PropertyOptions } from '@mikro-orm/core'\nimport { ApiPropertyOptions } from '@nestjs/swagger'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\nimport { ColumnTypeRequired } from '../types/column-type-required'\n\n\nexport function EntityProperty<T extends object>(\n options?: PropertyOptions<T> & Pick<ApiPropertyOptions, 'example'> & ColumnTypeRequired<T>,\n): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityProperty() can only be used on string property')\n\n OrmProperty(options)(target, propertyKey)\n ApiEntityProperty({ example: options?.example })(target, propertyKey)\n }\n}\n","import { ApiHideProperty, getSchemaPath } from '@nestjs/swagger'\nimport { EntityProperty, MetadataStorage, ReferenceKind } from '@mikro-orm/core'\nimport { ApiScalarEntityProperty } from './api-scalar-entity-property.decorator'\nimport { EntityRefType } from '../converters/entity-ref-type/entity-ref-type'\nimport { NestedProperty, Property, Relation } from '~/decorators'\nimport { Type } from '@nestjs/common'\n\n\ninterface ApiEntityPropertyOptions {\n example?: any\n enumName?: string\n}\n\nexport function ApiEntityProperty<T extends object>(options?: ApiEntityPropertyOptions): PropertyDecorator {\n return (target, propertyKey) => {\n const meta = MetadataStorage.getMetadataFromDecorator(target.constructor as T)\n const prop = meta.properties[propertyKey] as EntityProperty<T> | undefined\n if (!prop) return\n\n if (prop.hidden === true) {\n ApiHideProperty()(target, propertyKey)\n return\n }\n\n if (prop.kind === ReferenceKind.EMBEDDED) {\n return\n }\n\n if (prop.kind === ReferenceKind.SCALAR) {\n ApiScalarEntityProperty({\n meta: prop,\n schema: options,\n })(target, propertyKey)\n return\n }\n\n const getType = (): Type => {\n const ent = prop.entity()\n if (prop.eager === true) return ent as Type\n\n if (typeof ent === 'function') {\n return EntityRefType(ent as any)\n }\n\n return Object\n }\n\n if (prop.kind === ReferenceKind.ONE_TO_ONE || prop.kind === ReferenceKind.MANY_TO_ONE) {\n Property({\n type: () => getType(),\n relation: {\n kind: prop.kind,\n type: () => getType(),\n },\n schema: {\n description: prop.comment,\n required: !prop.nullable,\n },\n })(target, propertyKey)\n\n return\n }\n\n if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {\n NestedProperty(\n () => getType(),\n {\n each: true,\n optional: prop.nullable,\n schema: {\n description: prop.comment,\n type: 'array',\n items: {\n type: getSchemaPath(() => getType()),\n },\n },\n },\n )(target, propertyKey)\n\n Relation({\n kind: prop.kind,\n type: () => getType(),\n })(target, propertyKey)\n return\n }\n }\n}\n\n","import { BigIntType, EntityProperty } from '@mikro-orm/core'\nimport { applyDecorators } from '@nestjs/common'\nimport { IsBoolean, IsCurrency, IsInt, IsISO8601, IsNumber, IsString, MaxLength, MinLength } from 'class-validator'\nimport { Property } from '~/decorators'\nimport { logger } from '~/utils/logger'\n\n\ninterface ApiScalarEntityPropertyOptions {\n meta: EntityProperty\n unverified?: boolean\n schema?: {\n example?: any\n enumName?: string\n }\n}\n\nexport function ApiScalarEntityProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const { meta } = options\n\n if (typeof meta['columnType'] === 'string') {\n const columnType = meta['columnType'].toLowerCase()\n\n if (columnType.startsWith('varchar')) {\n const length = Number(meta['columnType'].match(/\\d+/)?.[0])\n return VarcharProperty(length, options)\n }\n\n if (columnType.startsWith('char')) {\n const length = Number(meta['columnType'].match(/\\d+/)?.[0])\n return CharProperty(length, options)\n }\n\n if (columnType === 'text') return TextProperty(options)\n if (columnType === 'money') return MoneyProperty(options)\n if (columnType.startsWith('int')) return IntProperty(options)\n if (columnType.startsWith('smallint')) return IntProperty(options)\n if (columnType.startsWith('tinyint')) return TinyintProperty(options)\n if (columnType.startsWith('double')) return DoubleProperty(options)\n if (columnType.startsWith('decimal') || columnType.startsWith('numeric')) return DecimalProperty(options)\n if (columnType === 'datetime') return DatetimeProperty(options)\n }\n\n if (meta.type === 'varchar') return VarcharProperty(meta.length, options)\n if (meta.type === 'char') return CharProperty(meta.length, options)\n if (meta.type === 'text') return TextProperty(options)\n if (meta.type === 'money') return MoneyProperty(options)\n if (meta.type === 'int') return IntProperty(options)\n if (meta.type === 'smallint') return IntProperty(options)\n if (meta.type === 'tinyint') return TinyintProperty(options)\n if (meta.type === 'double') return DoubleProperty(options)\n if (meta.type === 'decimal' || meta.type === 'numeric') return DecimalProperty(options)\n if (meta.type === 'datetime') return DatetimeProperty(options)\n if (meta.type === 'boolean' || meta.type === 'bool') return BooleanProperty(options)\n if (meta.type === 'bigint') return BigIntProperty(options)\n if ((meta.type as any) instanceof BigIntType) return BigIntProperty(options)\n\n logger.warn(`No decorator founded for type ${meta.type} for property ${meta.name}.`)\n\n return () => {}\n}\n\nfunction getEnumOptions(options: ApiScalarEntityPropertyOptions): { enum?: (string | number)[]; enumName?: string } {\n if (options.meta.enum !== true) return {}\n\n const items = options.meta.items as ((() => (string | number)[]) | (string | number)[])\n const values = typeof items === 'function' ? items() : items\n const enumName = options.schema?.enumName\n\n return {\n enum: values,\n enumName,\n }\n}\n\n\nfunction VarcharProperty(length: number | undefined, options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsString())\n if (length) decorators.push(MaxLength(length))\n }\n\n decorators.push(Property({\n type: () => String,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n maxLength: length,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nfunction CharProperty(length: number | undefined, options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsString())\n if (length) decorators.push(MaxLength(length), MinLength(length))\n }\n\n decorators.push(Property({\n type: () => String,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n maxLength: length,\n minimum: length,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nfunction TextProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n if (!options.unverified) {\n decorators.push(IsString())\n }\n\n decorators.push(Property({\n type: () => String,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\n\nexport function MoneyProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsCurrency({ symbol: '' }))\n }\n\n decorators.push(Property({\n type: () => String,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n format: 'money',\n ...(options.schema || {}),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function BigIntProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const { meta } = options\n\n let mode: 'string' | 'number'\n if (meta.type === 'bigint') {\n mode = 'string'\n } else if ((meta.type as any) instanceof BigIntType) {\n const metaType = meta.type as unknown as BigIntType\n mode = (metaType.mode as any) === 'string' ? 'string' : 'number'\n } else {\n throw new Error(`Unsupported type for BigIntProperty: ${meta.type}`)\n }\n\n const decorators: PropertyDecorator[] = []\n\n if (mode === 'string') {\n if (!options.unverified) {\n decorators.push(IsString())\n }\n\n decorators.push(Property({\n type: () => String,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n } else {\n if (!options.unverified) {\n decorators.push(IsInt())\n }\n\n decorators.push(Property({\n type: () => Number,\n optional: options.meta.nullable,\n schema: {\n type: 'integer',\n format: 'int64',\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n }\n\n\n return applyDecorators(...decorators)\n}\n\nexport function IntProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsInt())\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Number,\n optional: options.meta.nullable,\n schema: {\n type: 'integer',\n minimum: options.meta.unsigned ? 0 : -2147483647,\n maximum: options.meta.unsigned ? 4294967295 : 2147483647,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function TinyintProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsInt())\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Number,\n optional: options.meta.nullable,\n schema: {\n type: 'integer',\n minimum: options.meta.unsigned ? 0 : -128,\n maximum: options.meta.unsigned ? 255 : 127,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function DoubleProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsNumber())\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Number,\n optional: options.meta.nullable,\n schema: {\n type: 'number',\n format: 'double',\n minimum: options.meta.unsigned ? 0 : undefined,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function DecimalProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n if (options.meta.scale) decorators.push(IsNumber({ maxDecimalPlaces: options.meta.scale }))\n else decorators.push(IsNumber())\n\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Number,\n optional: options.meta.nullable,\n schema: {\n type: 'number',\n format: 'double',\n minimum: options.meta.unsigned ? 0 : undefined,\n ...(options.schema || {}),\n ...getEnumOptions(options),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function DatetimeProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsISO8601())\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Date,\n optional: options.meta.nullable,\n schema: {\n type: 'string',\n format: 'date-time',\n ...(options.schema || {}),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n\nexport function BooleanProperty(options: ApiScalarEntityPropertyOptions): PropertyDecorator {\n const decorators: PropertyDecorator[] = []\n\n if (!options.unverified) {\n decorators.push(IsBoolean())\n // if (options.meta.nullable) decorators.push(IsOptional())\n }\n\n decorators.push(Property({\n type: () => Boolean,\n optional: options.meta.nullable,\n schema: {\n type: 'boolean',\n ...(options.schema || {}),\n required: !options.meta.nullable,\n description: options.meta.comment,\n },\n }))\n\n return applyDecorators(...decorators)\n}\n","import * as R from 'ramda'\nimport { applyDecorators } from '@nestjs/common'\nimport { ApiPropertyOptions } from '@nestjs/swagger'\nimport { Type as ClassType } from 'class-transformer'\nimport { IsNotEmpty, ValidateNested } from 'class-validator'\nimport { Nested, PropertyMetadata } from './model'\nimport { List } from './model/list.decorator'\nimport { Class } from 'type-fest'\n\ninterface NestedPropertiesOptions extends Pick<PropertyMetadata, 'relation' | 'optional' | 'schema'> {\n each?: boolean\n}\n\nexport function NestedProperty(type: () => Class<object>, options: NestedPropertiesOptions = {}): PropertyDecorator {\n const decorators = [\n ValidateNested({ each: !!options.each }),\n ClassType(type),\n ]\n\n /**\n * 这里不能写成 ({ type: type })\n * 否则 swagger 会无法识别到正确的类型\n * 因为 @nestjs/swagger 中通过 `fn.name === 'type'` 来判断 type 是函数还是对象\n */\n const schema: ApiPropertyOptions = options.schema || { type: () => type() }\n const propertyMetadata = R.pick(['relation', 'optional'], options)\n\n if (options.each) {\n decorators.push(List({ type, schema, ...propertyMetadata }))\n } else {\n decorators.push(Nested({ type, schema, ...propertyMetadata }))\n }\n\n if (!options.optional) {\n decorators.push(...[\n // 如果不添加 IsNotEmpty,ValidateNested 在没有 IsOptional 的情况下也会允许 undefined 值通过验证\n // https://github.com/typestack/class-validator/issues/717\n IsNotEmpty({ each: options.each, message: '$property is required' }),\n ])\n }\n\n return applyDecorators(...decorators)\n}\n","import * as R from 'ramda'\nimport { Class } from 'type-fest'\nimport { ModelKind, ModelMetadata } from './model.decorator'\nimport { PropertyMetadata } from './property.decorator'\n\n\nconst MODEL_METADATA_KEY = 'buka:model'\nconst PROPERTY_METADATA_KEY = 'buka:property'\n\nexport class ModelRegister {\n static setModel(target: Class<any>, metadata: ModelMetadata): void {\n Reflect.defineMetadata(MODEL_METADATA_KEY, metadata, target)\n }\n\n static getModel(target: Class<any>): ModelMetadata | undefined {\n return Reflect.getMetadata(MODEL_METADATA_KEY, target) as ModelMetadata | undefined\n }\n\n static setProperty(target: Class<any>, propertyName: string | symbol, metadata: PropertyMetadata): void {\n Reflect.defineMetadata(PROPERTY_METADATA_KEY, metadata, target.prototype, propertyName)\n }\n\n static getProperty(target: Class<any>, propertyName: string | symbol): PropertyMetadata | undefined {\n return Reflect.getMetadata(PROPERTY_METADATA_KEY, target.prototype, propertyName) as PropertyMetadata | undefined\n }\n\n static copyProperty(source: Class<any>, target: Class<any>, propertyName: string | symbol): void {\n const metadata = this.getProperty(source, propertyName)\n if (metadata) {\n const modelMetadata = this.addModel(target)\n\n if (!modelMetadata.propertyKeys.includes(propertyName)) {\n modelMetadata.propertyKeys.push(propertyName)\n }\n\n this.setProperty(target, propertyName, metadata)\n }\n }\n\n static addModel(target: Class<any>, kind: ModelKind = 'model'): ModelMetadata {\n const metadata = this.getModel(target)\n\n if (!metadata) {\n const metadata: ModelMetadata = {\n kind,\n propertyKeys: [],\n }\n\n this.setModel(target, metadata)\n return metadata\n }\n\n return metadata\n }\n\n static addProperty(target: Class<any>, propertyName: string | symbol, metadata: Partial<PropertyMetadata> = {}): void {\n const modelMetadata = this.addModel(target)\n\n if (!modelMetadata.propertyKeys.includes(propertyName)) {\n modelMetadata.propertyKeys.push(propertyName)\n }\n\n const propertyMetadata: PropertyMetadata = {\n type() {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return Reflect.getMetadata('design:type', target.prototype, propertyName)\n },\n ...(this.getProperty(target, propertyName) || {}),\n ...R.omit(['schema'], metadata),\n }\n\n this.setProperty(target, propertyName, propertyMetadata)\n }\n\n /**\n * 判断是否为已注册的 Model\n *\n * @example\n * ```typescript\n * const isModel = ModelRegister.isModel(Class.prototype)\n * ```\n */\n static isModel(target: Class<any>): boolean {\n return !!this.getModel(target)\n }\n\n /**\n * 获取 Model 的所有属性\n *\n * @example\n * ```typescript\n * const properties = ModelRegister.getProperties(Class.prototype)\n * ```\n */\n static getModelPropertyKeys(target: Class<any>): (string | symbol)[] {\n const metadata = this.getModel(target)\n return metadata ? metadata.propertyKeys : []\n }\n\n static getProperties(target: Class<any>): PropertyMetadata[] {\n const propertyKeys = this.getModelPropertyKeys(target)\n\n return propertyKeys\n .map((propertyKey) => this.getProperty(target, propertyKey))\n .filter((property): property is PropertyMetadata => !!property)\n }\n}\n","import { Class } from 'type-fest'\nimport { ModelRegister } from './model.register'\n\n\nexport type ModelKind = 'model' | 'entity' | 'dto' | 'bo' | 'ro'\n\nexport interface ModelMetadata {\n kind: ModelKind\n propertyKeys: (string | symbol)[]\n}\n\nexport function Model(): ClassDecorator {\n return (target: Function) => {\n ModelRegister.addModel(target as Class<any>)\n }\n}\n","import { CustomError } from 'ts-custom-error'\n\n\nexport class Exception extends CustomError {\n constructor(message: string) {\n super(`[@buka/nestjs] ${message}`)\n\n Object.defineProperty(this, 'name', { value: 'Exception' })\n }\n}\n","import { Exception } from './exceptions'\n\nexport class TypeException extends Exception {\n constructor(message: string) {\n super(message)\n Object.defineProperty(this, 'name', { value: 'TypeException' })\n }\n}\n","import { TypeException } from '~/exceptions'\nimport { ModelRegister } from './model.register'\nimport { Class } from 'type-fest'\nimport { RelationMetadata } from './relation.decorator'\nimport { IsOptional } from 'class-validator'\nimport { ApiPropertyOptions, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'\n\n\nexport type PropertyKind = 'nested' | 'dictionary' | 'list'\n\nexport interface PropertyMetadata {\n kind?: PropertyKind\n optional?: boolean\n type: () => Function\n relation?: RelationMetadata\n schema?: ApiPropertyOptions\n}\n\n\nexport function Property(options?: Partial<PropertyMetadata>): PropertyDecorator {\n return (target: object, propertyKey: string | symbol) => {\n if (!('constructor' in target)) {\n throw new TypeException('@Property decorator can only be applied to class properties.')\n }\n\n if (options?.optional) {\n IsOptional()(target, propertyKey)\n }\n\n if (options?.schema) {\n if (options?.optional) {\n ApiPropertyOptional(options.schema)(target, propertyKey)\n } else {\n ApiProperty(options.schema)(target, propertyKey)\n }\n }\n\n ModelRegister.addProperty(target.constructor as Class<any>, propertyKey, options)\n }\n}\n","import { Class } from 'type-fest'\nimport { ModelRegister } from './model.register'\n\nexport const RELATION_METADATA_KEY = 'buka:relation'\n\n\nexport type RelationKind = '1:1' | '1:m' | 'm:1' | 'm:n'\n\nexport interface RelationMetadata {\n kind: RelationKind\n type: () => Class<object>\n}\n\nexport function Relation(metadata: RelationMetadata): PropertyDecorator {\n return (target, propertyKey) => {\n ModelRegister.addProperty(target.constructor as Class<any>, propertyKey, { relation: metadata })\n }\n}\n","import { Property, PropertyMetadata } from './property.decorator'\n\ntype NestedOptions = Pick<PropertyMetadata, 'type' | 'optional' | 'schema' | 'relation'>\n\nexport function Nested(options: NestedOptions): PropertyDecorator {\n return (target, propertyKey) => {\n Property({\n kind: 'nested',\n ...options,\n })(target, propertyKey)\n }\n}\n","import { Property, PropertyMetadata } from './property.decorator'\n\n\ntype ListOptions = Pick<PropertyMetadata, 'optional' | 'type' | 'schema' | 'relation'>\n\nexport function List(options: ListOptions): PropertyDecorator {\n return (target, propertyKey) => {\n Property({\n kind: 'list',\n ...options,\n })(target, propertyKey)\n }\n}\n","import { applyDecorators } from '@nestjs/common'\nimport { Allow } from 'class-validator'\nimport { EntityRefType } from '~/mikro-orm'\nimport { Class } from 'type-fest'\nimport { NestedProperty } from './nested-property.decorator'\n\nexport const ReferencePropertiesMetadataKey = 'buka:reference-properties'\n\n\n/**\n *\n * @param entity MikroORM Entity\n */\nexport function ReferenceProperty(entity: () => Class<object>): PropertyDecorator {\n return applyDecorators(\n Allow(),\n NestedProperty(() => EntityRefType(entity()),\n {\n relation: {\n kind: '1:1',\n type: () => entity(),\n },\n },\n ),\n )\n}\n","import { registerDecorator, ValidationOptions } from 'class-validator'\n\n\nexport const HAS_ANY_KEY = 'hasAnyKey'\n\nexport function HasAnyKey(keys: readonly string[], validationOptions?: ValidationOptions) {\n return function (object: object, propertyName: string) {\n registerDecorator({\n name: HAS_ANY_KEY,\n target: object.constructor,\n propertyName: propertyName,\n constraints: [keys],\n options: validationOptions,\n validator: {\n validate(value: any) {\n if (typeof value !== 'object' || value === null) return false\n for (const key of keys) {\n if (key in value) return true\n }\n return false\n },\n defaultMessage() {\n return `$property must have at least one of the keys: ${keys.join(', ')}`\n },\n },\n })\n }\n}\n","import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'\nimport { validate } from 'jsonschema'\n\n\nexport const MATCH_JSON_SCHEMA = 'matchJsonSchema'\n\n\nexport function MatchJsonSchema(schema: any, validationOptions?: ValidationOptions) {\n return function (object: object, propertyName: string) {\n registerDecorator({\n name: MATCH_JSON_SCHEMA,\n target: object.constructor,\n propertyName: propertyName,\n constraints: [schema],\n options: validationOptions,\n validator: {\n validate(value: any, args: ValidationArguments) {\n const [schema] = args.constraints\n\n const results = validate(value, schema)\n return results.valid\n },\n defaultMessage({ value, constraints }: ValidationArguments) {\n return `${propertyName} must match the JSON schema`\n },\n },\n })\n }\n}\n\n","import { Query } from '@nestjs/common'\nimport { ApiQuery } from '@nestjs/swagger'\nimport { BukaPageQueryValidationPipe } from '~/pipes/page-query-validation.pipe'\n\n\nconst OffsetPageSchema = {\n type: 'object',\n properties: {\n limit: { type: 'number' },\n offset: { type: 'number' },\n },\n required: ['limit', 'offset'],\n}\n\nconst NextCursorPageSchema = {\n type: 'object',\n properties: {\n after: { type: 'string' },\n first: { type: 'number' },\n },\n required: ['after', 'first'],\n}\n\nconst PreviousCursorPageSchema = {\n type: 'object',\n properties: {\n before: { type: 'string' },\n last: { type: 'number' },\n },\n required: ['before', 'last'],\n}\n\n\nexport function PageQuery(mode?: 'cursor' | 'offset'): ParameterDecorator {\n return (target: object, propertyKey: string | symbol | undefined, parameterIndex: number) => {\n if (!propertyKey) {\n throw new Error('@PageQuery decorator can only be used on method parameters.')\n }\n\n const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey)!\n\n if (mode === 'offset') {\n ApiQuery({\n name: 'page',\n required: true,\n schema: OffsetPageSchema,\n })(target, propertyKey, descriptor)\n } else if (mode === 'cursor') {\n ApiQuery({\n name: 'page',\n required: true,\n schema: {\n oneOf: [\n NextCursorPageSchema,\n PreviousCursorPageSchema,\n ],\n },\n })(target, propertyKey, descriptor)\n } else {\n ApiQuery({\n name: 'page',\n required: true,\n schema: {\n oneOf: [\n OffsetPageSchema,\n NextCursorPageSchema,\n PreviousCursorPageSchema,\n ],\n },\n })(target, propertyKey, descriptor)\n }\n\n Query(new BukaPageQueryValidationPipe({ mode }))(target, propertyKey, parameterIndex)\n }\n}\n\nexport function OptionalPageQuery(mode?: 'cursor' | 'offset'): ParameterDecorator {\n return (target: object, propertyKey: string | symbol | undefined, parameterIndex: number) => {\n if (!propertyKey) {\n throw new Error('@PageQuery decorator can only be used on method parameters.')\n }\n\n const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey)!\n\n if (mode === 'offset') {\n ApiQuery({\n name: 'page',\n required: false,\n schema: OffsetPageSchema,\n })\n } else if (mode === 'cursor') {\n ApiQuery({\n name: 'page',\n required: false,\n schema: {\n oneOf: [\n NextCursorPageSchema,\n PreviousCursorPageSchema,\n ],\n },\n })(target, propertyKey, descriptor)\n } else {\n ApiQuery({\n name: 'page',\n required: false,\n schema: {\n oneOf: [\n OffsetPageSchema,\n NextCursorPageSchema,\n PreviousCursorPageSchema,\n ],\n },\n })(target, propertyKey, descriptor)\n }\n\n Query(new BukaPageQueryValidationPipe({ mode, optional: true }))(target, propertyKey, parameterIndex)\n }\n}\n","import { ArgumentMetadata, BadRequestException, Injectable, InternalServerErrorException, PipeTransform } from '@nestjs/common'\n\n\ninterface BukaPageQueryValidationPipeOptions {\n mode?: 'cursor' | 'offset'\n optional?: boolean\n}\n\nconst mixedOffsetAndCursorError = 'Invalid page query: cannot mix offset-based and cursor-based pagination parameters.'\nconst mixedNextAndLastCursorError = 'Invalid page query: cannot mix next and last cursor pagination parameters.'\n\n@Injectable()\nexport class BukaPageQueryValidationPipe implements PipeTransform {\n constructor(\n private readonly options: BukaPageQueryValidationPipeOptions = { optional: false },\n ) {}\n\n transform(value: any, metadata: ArgumentMetadata): any {\n if (metadata.type !== 'query') {\n throw new InternalServerErrorException('BukaPageQueryValidationPipe can only be used to validate query parameters.')\n }\n\n if (!value.page) {\n if (this.options.optional) {\n return value\n } else {\n throw new BadRequestException('Missing required query parameter: page')\n }\n }\n\n const mode = this.options.mode\n\n if ((!mode || mode === 'offset') && ('limit' in value.page || 'offset' in value.page)) {\n if ('after' in value.page || 'first' in value.page || 'before' in value.page || 'last' in value.page) {\n throw new BadRequestException(mixedOffsetAndCursorError)\n }\n\n const limit = parseInt(value.page.limit, 10)\n const offset = parseInt(value.page.offset, 10)\n\n if (isNaN(limit) || limit <= 0) {\n throw new BadRequestException('Invalid page query: limit must be a positive integer.')\n }\n\n if (isNaN(offset) || offset < 0) {\n throw new BadRequestException('Invalid page query: offset must be a non-negative integer.')\n }\n\n return { page: { limit, offset } }\n }\n\n if (!mode || mode === 'cursor') {\n if ('after' in value.page || 'first' in value.page) {\n if ('limit' in value.page || 'offset' in value.page) {\n throw new BadRequestException(mixedOffsetAndCursorError)\n }\n\n if ('before' in value.page || 'last' in value.page) {\n throw new BadRequestException(mixedNextAndLastCursorError)\n }\n\n const after = value.page.after\n const first = parseInt(value.page.first, 10)\n\n if (typeof after !== 'string') {\n throw new BadRequestException('Invalid page query: after must be a string.')\n }\n\n if (isNaN(first) || first <= 0) {\n throw new BadRequestException('Invalid page query: first must be a positive integer.')\n }\n\n return { page: { after, first } }\n }\n\n if ('before' in value.page || 'last' in value.page) {\n if ('limit' in value.page || 'offset' in value.page) {\n throw new BadRequestException(mixedOffsetAndCursorError)\n }\n\n if ('after' in value.page || 'first' in value.page) {\n throw new BadRequestException(mixedNextAndLastCursorError)\n }\n\n const before = value.page.before\n const last = parseInt(value.page.last, 10)\n\n if (typeof before !== 'string') {\n throw new BadRequestException('Invalid page query: before must be a string.')\n }\n\n if (isNaN(last) || last <= 0) {\n throw new BadRequestException('Invalid page query: last must be a positive integer.')\n }\n\n return { page: { before, last } }\n }\n }\n\n throw new BadRequestException('Invalid page query: missing pagination parameters.')\n }\n}\n","import { Logger } from '@nestjs/common'\n\nexport const logger = new Logger('@buka/nestjs-type-helpers')\n","/* eslint-disable @typescript-eslint/no-unsafe-return */\nimport * as R from 'ramda'\nimport { ModelPropertiesAccessor } from '@nestjs/swagger/dist/services/model-properties-accessor'\nimport { DECORATORS } from '@nestjs/swagger/dist/constants'\nimport { SchemaObjectMetadata } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface'\nimport { METADATA_FACTORY_NAME } from '@nestjs/swagger/dist/plugin/plugin-constants'\nimport { Type } from '@nestjs/common'\nimport { clonePluginMetadataFactory } from '@nestjs/swagger/dist/type-helpers/mapped-types.utils'\nimport { ApiProperty } from '@nestjs/swagger'\nimport { isFunction } from '@nestjs/common/utils/shared.utils'\nimport { isBuiltInType } from '@nestjs/swagger/dist/utils/is-built-in-type.util'\n\nexport { SchemaObjectMetadata } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface'\n\n\nconst modelPropertiesAccessor = new ModelPropertiesAccessor()\n\n/**\n * 克隆 @nestjs/swagger Plugin 添加的元数据\n */\n// export const cloneSwaggerPluginMetadataFactory = clonePluginMetadataFactory\n\nexport function cloneMetadata(target: Function, source: Type<unknown>, keys: string[]): void {\n clonePluginMetadataFactory(\n target as Type<unknown>,\n source.prototype,\n (metadata: Record<string, any>) => R.pick(keys, metadata),\n )\n\n for (const propertyKey of keys) {\n const metadata = getMetadataOfDecorator(source, propertyKey)\n if (metadata) {\n ApiProperty(metadata)(target.prototype, propertyKey)\n }\n }\n}\n\n/**\n * 获取 @ApiProperty 添加的 Metadata\n */\nexport function getMetadataOfDecorator(classRef: Type<any>): Record<string, SchemaObjectMetadata>\nexport function getMetadataOfDecorator(classRef: Type<any>, propertyKey: string): SchemaObjectMetadata | undefined\nexport function getMetadataOfDecorator(classRef: Type<any>, propertyKey?: string): SchemaObjectMetadata | undefined | Record<string, SchemaObjectMetadata> {\n if (propertyKey) {\n return Reflect.getMetadata(DECORATORS.API_MODEL_PROPERTIES, classRef.prototype, propertyKey)\n }\n\n const props = modelPropertiesAccessor\n .getModelProperties(classRef.prototype)\n\n return R.fromPairs(\n props.map((prop) => [\n prop,\n Reflect.getMetadata(DECORATORS.API_MODEL_PROPERTIES, classRef.prototype, prop),\n ]),\n )\n}\n\n/**\n * 获取 @nestjs/swagger Plugin 添加的 Schema\n */\n\nfunction getMetadataOfPlugin(classRef: Type<any>): Record<string, SchemaObjectMetadata>\nfunction getMetadataOfPlugin(classRef: Type<any>, propertyKey: string): SchemaObjectMetadata | undefined\nfunction getMetadataOfPlugin(classRef: Type<any>, propertyKey?: string): SchemaObjectMetadata | undefined | Record<string, SchemaObjectMetadata> {\n const propsInPlugin = typeof classRef[METADATA_FACTORY_NAME] === 'function' ? classRef[METADATA_FACTORY_NAME]() : {}\n\n if (propertyKey) return propsInPlugin[propertyKey]\n return propsInPlugin\n}\n\n/**\n * Get all metadata of @ApiProperty() defined on the class.\n *\n * 可以利用这个函数,遍历 Dto/Entity 上定义的所有属性,而不需要去实例化一个对象\n */\nexport function getMetadata(classRef: Type<any>): Record<string, SchemaObjectMetadata>\nexport function getMetadata(classRef: Type<any>, propertyKey: string): SchemaObjectMetadata | undefined\nexport function getMetadata(classRef: Type<any>, propertyKey?: string): SchemaObjectMetadata | Record<string, SchemaObjectMetadata> {\n const propsInDecorator = getMetadataOfDecorator(classRef)\n const propsInPlugin = getMetadataOfPlugin(classRef)\n\n const metadataMap = R.mergeRight(propsInPlugin, propsInDecorator)\n if (propertyKey) return metadataMap[propertyKey]\n return metadataMap\n}\n\n/**\n * Set @ApiProperty() to all properties of the class.\n */\nexport function overridePluginMetadata(classRef: Type<any>, props: Record<string, SchemaObjectMetadata>): void {\n classRef[METADATA_FACTORY_NAME] = () => props\n}\n\n\nexport function getMetadataType(metadata: SchemaObjectMetadata): string | Function {\n if (isFunction(metadata.type) && metadata.type.name === 'type') {\n return getMetadataType((<any>metadata.type)())\n } else if (isFunction(metadata.type) && isBuiltInType(metadata.type)) {\n return metadata.type.name.toLowerCase()\n }\n\n return metadata.type as (string | Function)\n}\n\n/**\n * 判断 type 是否是 lazy type function\n */\nexport function isLazyTypeFunc(\n type: any,\n): type is { type: Function } & Function {\n return isFunction(type) && type.name == 'type'\n}\n","import * as R from 'ramda'\nimport { EntityMetadata, MetadataStorage, EntityProperty } from '@mikro-orm/core'\nimport { Type } from '@nestjs/common'\n\n\nexport function getMetadata<T>(classRef: Type<T>): EntityProperty<T>[] {\n const metadatas: EntityMetadata<T>[] = []\n\n let parent: any = classRef\n do {\n const meta = MetadataStorage.getMetadataFromDecorator(parent)\n if (meta instanceof EntityMetadata) metadatas.push(meta)\n parent = Object.getPrototypeOf(parent)\n } while (parent && parent !== Object.prototype)\n\n return R.unnest(metadatas.map((meta) => R.values(meta.properties)))\n}\n","\nimport { IEntityRef } from './types/index.js'\nimport * as SwaggerUtils from '~/utils/nestjs-swagger-utils'\nimport * as MikroOrmUtils from '~/utils/mikro-orm-utils'\nimport { inheritTransformationMetadata, inheritValidationMetadata } from '@nestjs/mapped-types'\nimport { Class } from 'type-fest'\n\n\nexport const EntityRefTypeClassMetadataPropertyKey = Symbol('EntityRefTypeClassMetadataPropertyKey')\n\nconst storage = new WeakMap<Class<object>, Class<IEntityRef<any>>>()\n\nexport function EntityRefType<T extends object>(classRef: Class<T>): Class<IEntityRef<T>> {\n if (storage.has(classRef)) {\n return storage.get(classRef)!\n }\n\n const properties = MikroOrmUtils.getMetadata(classRef)\n\n const primaryProperties: string[] = properties.filter((prop) => prop.primary)\n .map((prop) => prop.name)\n\n if (!primaryProperties.length) {\n throw new Error(`Cannot create EntityRefType for ${classRef.name} because it has no primary properties.`)\n }\n\n class EntityRefTypeClass {}\n\n EntityRefTypeClass[EntityRefTypeClassMetadataPropertyKey] = {}\n\n SwaggerUtils.cloneMetadata(EntityRefTypeClass, classRef, primaryProperties)\n inheritValidationMetadata(classRef, EntityRefTypeClass, (key) => primaryProperties.includes(key))\n inheritTransformationMetadata(classRef, EntityRefTypeClass, (key) => primaryProperties.includes(key))\n\n storage.set(classRef, EntityRefTypeClass as Class<IEntityRef<T>>)\n return EntityRefTypeClass as Class<IEntityRef<T>>\n}\n","import * as R from 'ramda'\nimport { Type } from '@nestjs/common'\nimport { inheritTransformationMetadata, inheritValidationMetadata } from '@nestjs/mapped-types'\nimport * as ApiPropertyUtils from '~/utils/nestjs-swagger-utils'\nimport { IEntityDto } from './types/entity-dto'\nimport * as MikroOrmUtils from '~/utils/mikro-orm-utils'\nimport { ModelRegister } from '~/decorators'\nimport { wrap } from '@mikro-orm/core'\nimport { Class } from 'type-fest'\n\n\nexport type IEntityDtoClass<T> = Class<IEntityDto<T>> & {\n from(entity: T): IEntityDto<T>\n}\n\nexport function EntityDtoType<T extends object>(entity: Class<T>): IEntityDtoClass<T> {\n const properties = MikroOrmUtils.getMetadata(entity)\n\n if (!properties.length) {\n throw new Error(`Cannot create EntityDtoType for ${entity.name} because it isn't an MikroORM Entity.`)\n }\n\n // const keys = R.pluck('name', properties.filter((prop) => !prop.hidden))\n const keys = properties\n .filter((prop) => {\n if (prop.hidden) return false\n if (prop.kind === 'scalar' && prop.ref) return false\n return true\n })\n .map((prop) => prop.name)\n\n class EntityDtoTypeClass {\n static from(entity: T): EntityDtoTypeClass {\n const dto = new EntityDtoTypeClass()\n const json = wrap(entity).toPOJO()\n\n for (const property of properties) {\n if (property.hidden) continue\n if (property.kind === 'scalar' && !property.ref) {\n dto[property.name as any] = json[property.name as any]\n }\n if (property.kind === 'm:n' || property.kind === '1:m') {\n // array\n dto[property.name as any] = json[property.name as any]\n }\n\n if ((property.kind === '1:1' || property.kind === 'm:1')) {\n dto[property.name as any] = json[property.name as any]\n }\n }\n\n return dto\n }\n }\n\n inheritValidationMetadata(entity, EntityDtoTypeClass, (key) => keys.includes(key as any))\n inheritTransformationMetadata(entity, EntityDtoTypeClass, (key) => keys.includes(key as any))\n\n ApiPropertyUtils.cloneMetadata(EntityDtoTypeClass, entity, keys)\n\n for (const property of keys) {\n ModelRegister.copyProperty(entity, EntityDtoTypeClass, property)\n }\n\n return EntityDtoTypeClass as unknown as IEntityDtoClass<T>\n}\n","import { AnyEntity, Enum, EnumOptions } from '@mikro-orm/core'\nimport { ColumnTypeRequired } from '../types/column-type-required'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\n\n\nexport function EntityEnum<T extends object>(\n options: EnumOptions<AnyEntity> & ColumnTypeRequired<T> & {\n example?: any\n enumName?: string\n },\n): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityEnum() can only be used on string property')\n\n Enum<T>(options)(target, propertyKey)\n ApiEntityProperty({ example: options.example, enumName: options.enumName })(target, propertyKey)\n }\n}\n","import { Property } from '@mikro-orm/core'\n\n\nexport function EntityTransient(): PropertyDecorator {\n return Property({ persist: false }) as PropertyDecorator\n}\n\n","import { EntityName, OneToOne as OrmOneToOne, OneToOneOptions } from '@mikro-orm/core'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\n\n\nexport function EntityOneToOne<Target, Owner>(\n entity?: OneToOneOptions<Owner, Target> | string | ((e: Owner) => EntityName<Target>),\n mappedByOrOptions?: (string & keyof Target) | ((e: Target) => any) | Partial<OneToOneOptions<Owner, Target>>,\n options?: Partial<OneToOneOptions<Owner, Target>>,\n): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityOneToOne() can only be used on string property')\n\n OrmOneToOne(entity, mappedByOrOptions, options)(target, propertyKey)\n ApiEntityProperty()(target, propertyKey)\n }\n}\n","import { OneToMany as OrmOneToMany, OneToManyOptions } from '@mikro-orm/core'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\n\nexport function EntityOneToMany<Target, Owner>(options: OneToManyOptions<Owner, Target>): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityOneToMany() can only be used on string property')\n\n OrmOneToMany(options)(target, propertyKey)\n ApiEntityProperty()(target, propertyKey)\n }\n}\n","import { EntityName, ManyToOne as OrmManyToOne, ManyToOneOptions } from '@mikro-orm/core'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\n\n\nexport function EntityManyToOne<T extends object, O>(\n entity?: ManyToOneOptions<T, O> | string | ((e?: any) => EntityName<T>),\n options?: Partial<ManyToOneOptions<T, O>>,\n): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityManyToOne() can only be used on string property')\n\n OrmManyToOne(entity, options)(target, propertyKey)\n ApiEntityProperty()(target, propertyKey)\n }\n}\n","import { EntityName, ManyToMany, ManyToManyOptions } from '@mikro-orm/core'\nimport { ApiEntityProperty } from './api-entity-property.decorator'\n\nexport function EntityManyToMany<T extends object, O>(\n entity?: ManyToManyOptions<T, O> | string | (() => EntityName<T>),\n mappedBy?: (string & keyof T) | ((e: T) => any),\n options?: Partial<ManyToManyOptions<T, O>>,\n): PropertyDecorator {\n return (target, propertyKey) => {\n if (typeof propertyKey !== 'string') throw new TypeError('@EntityManyToMany() can only be used on string property')\n\n ManyToMany(entity, mappedBy, options)(target, propertyKey)\n ApiEntityProperty()(target, propertyKey)\n }\n}\n","import { ToNumber } from '@buka/class-transformer-extra'\nimport { IsBoolean, IsNumber, IsString } from 'class-validator'\n\nimport * as R from 'ramda'\nimport * as util from 'util'\nimport { Migrator } from '@mikro-orm/migrations'\nimport { FlushMode, Options } from '@mikro-orm/core'\nimport { BadRequestException } from '@nestjs/common'\n\n\nexport class DatabaseConfig {\n @IsBoolean()\n debug = false\n\n @IsBoolean()\n migration: boolean = false\n\n @IsString()\n dbName!: string\n\n @IsString()\n host!: string\n\n @ToNumber()\n @IsNumber({ allowNaN: false })\n port!: number\n\n @IsString()\n user!: string\n\n @IsString()\n password!: string\n\n @IsString()\n timezone? = '+08:00'\n\n toMikroOrmOptions(config?: Partial<Options>): Options {\n let options: Options = {\n host: this.host,\n port: this.port,\n user: this.user,\n password: this.password,\n dbName: this.dbName,\n debug: this.debug,\n timezone: this.timezone,\n\n forceUndefined: true,\n flushMode: FlushMode.COMMIT,\n serialization: {\n forceObject: true,\n },\n\n findOneOrFailHandler: (entityName, where) => new BadRequestException(`Cannot find ${entityName} where ${util.inspect(where)}`),\n }\n\n if (this.migration) {\n options = R.mergeDeepRight(options, {\n extensions: [Migrator],\n migrations: {\n sn