diff --git a/CHANGELOG.md b/CHANGELOG.md index 435db9d..30bb1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.15.0] - 2024-06-24 + +### Changed + +- Updated @hexancore/common to 0.14.* +- Updated @hexancore/core to ^0.15.* + +### Removed + +- removed support for mysql driver + + ## [0.14.0] - 2024-02-11 ### Changed @@ -59,7 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - First release(start from 0.12 for consistency) - Implemented generic from domain infrastructre from Hexancore -[unreleased] https://github.com/hexancore/typeorm/compare/0.14.0...HEAD +[unreleased] https://github.com/hexancore/typeorm/compare/0.15.0...HEAD +[0.15.0] https://github.com/hexancore/typeorm/compare/0.14.0...0.15.0 [0.14.0] https://github.com/hexancore/typeorm/compare/0.13.3...0.14.0 [0.13.3] https://github.com/hexancore/typeorm/compare/0.13.2...0.13.3 [0.13.2] https://github.com/hexancore/typeorm/compare/0.13.1...0.13.2 diff --git a/config/test/config.yaml b/config/test/config.yaml index c2b1699..6d16890 100644 --- a/config/test/config.yaml +++ b/config/test/config.yaml @@ -1,5 +1,5 @@ core: - typeorm-mysql: + typeorm-mariadb: type: mariadb host: 127.0.0.1 port: 10020 @@ -7,7 +7,7 @@ core: dropSchema: true retryAttempts: 4 retryDelay: 3000 - authSecretKey: core.typeorm.mysql + authSecretKey: core.typeorm.mariadb typeorm-postgres: type: postgres host: 127.0.0.1 diff --git a/config/test/secrets/core.typeorm.mysql b/config/test/secrets/core.typeorm.mariadb similarity index 100% rename from config/test/secrets/core.typeorm.mysql rename to config/test/secrets/core.typeorm.mariadb diff --git a/package.json b/package.json index 70cc97f..4d25038 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hexancore/typeorm", - "version": "0.14.0", + "version": "0.15.0", "type": "commonjs", "engines": { "node": ">=22" @@ -53,11 +53,12 @@ "dependencies": { "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "tslib": "^2.6.3" + "tslib": "^2.6.3", + "zod": "^3.23.8" }, "devDependencies": { "@hexancore/common": "^0.14.0", - "@hexancore/core": "^0.15.2", + "@hexancore/core": "^0.15.4", "@hexancore/mocker": "^1.1.2", "@nestjs/cli": "^10.3.2", "@nestjs/common": "^10.3.9", @@ -112,12 +113,12 @@ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "lint": "eslint \"{src,test}/**/*.ts\"", "lint:fix": "eslint \"{src,test}/**/*.ts\" --fix", - "test": "jest --runInBand", - "test:clearCache": "jest --clearCache", - "test:unit": "jest --runInBand --group=unit", - "test:watch": "jest --runInBand --watchAll", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.ts", + "test": "yarn run jest --runInBand", + "test:clearCache": "yarn run jest --clearCache", + "test:unit": "yarn run jest --runInBand --group=unit", + "test:watch": "yarn run jest --runInBand --watchAll", + "test:cov": "yarn run jest --coverage", "prepublish": "yarn run build", "deps:upgrade": "yarn add -D @hexancore/common @hexancore/core" } diff --git a/src/DataSource/DataSourceContext.ts b/src/DataSource/AbstractDataSourceContext.ts similarity index 85% rename from src/DataSource/DataSourceContext.ts rename to src/DataSource/AbstractDataSourceContext.ts index 2ee8b6c..82cacde 100644 --- a/src/DataSource/DataSourceContext.ts +++ b/src/DataSource/AbstractDataSourceContext.ts @@ -3,8 +3,8 @@ import { DataSourceManager, WeakDataSourceRef } from './DataSourceManager'; import { DataSourceContextConfig } from './DataSourceContextConfig'; import { AR } from '@hexancore/common'; -export abstract class DataSourceContext { - public constructor(private manager: DataSourceManager) {} +export abstract class AbstractDataSourceContext { + public constructor(private manager: DataSourceManager) { } public get(): AR { return this.getConfig().onOk((config) => { diff --git a/src/DataSource/AccountDataSourceContext.ts b/src/DataSource/AccountDataSourceContext.ts index 48eff05..45e2590 100644 --- a/src/DataSource/AccountDataSourceContext.ts +++ b/src/DataSource/AccountDataSourceContext.ts @@ -1,14 +1,14 @@ import { DataSourceManager } from './DataSourceManager'; import { AR, OKA } from '@hexancore/common'; -import { DataSourceContext } from './DataSourceContext'; +import { AbstractDataSourceContext } from './AbstractDataSourceContext'; import { DataSourceContextConfig } from './DataSourceContextConfig'; import { Injectable } from '@nestjs/common'; import { AccountContext } from '@hexancore/core'; import { TYPEORM_ACCOUNT_PERSISTER_TYPE } from '@/Repository'; @Injectable() -export class AccountDataSourceContext extends DataSourceContext { +export class AccountDataSourceContext extends AbstractDataSourceContext { public constructor( manager: DataSourceManager, protected ac: AccountContext, diff --git a/src/DataSource/DataSourceFactory.ts b/src/DataSource/DataSourceFactory.ts index a4345f3..c3cdc40 100644 --- a/src/DataSource/DataSourceFactory.ts +++ b/src/DataSource/DataSourceFactory.ts @@ -13,6 +13,9 @@ export interface DataSourceFactoryOptions { maxRetryAttempts?: number; } +/** + * Factory to create TypeORM DataSource(db connection) from given context config. + */ export class DataSourceFactory { public constructor(private options: DataSourceFactoryOptions) { options.dbPrefix = options.dbPrefix ?? ''; @@ -29,22 +32,21 @@ export class DataSourceFactory { entities, }; - return RetryHelper.retryAsync( - () => { - let ds: DataSource| null = null; - try { - ds = new DataSource(options); - return ARW(ds.initialize()); - } catch (e) { - if (ds && ds.isInitialized) { - return ARW(ds.destroy()) - .onOk(() => INTERNAL_ERR(e)) - .onErr(() => INTERNAL_ERR(e)); - } - - return INTERNAL_ERRA(e as any); + return RetryHelper.retryAsync(() => { + let ds: DataSource | null = null; + try { + ds = new DataSource(options); + return ARW(ds.initialize()); + } catch (e) { + if (ds && ds.isInitialized) { + return ARW(ds.destroy()) + .onOk(() => INTERNAL_ERR(e)) + .onErr(() => INTERNAL_ERR(e)); } - }, + + return INTERNAL_ERRA(e as any); + } + }, { id: 'create_typeorm_data_source_' + config.id, retryDelay: this.options.retryDelay, diff --git a/src/DataSource/DataSourceManager.ts b/src/DataSource/DataSourceManager.ts index ee849c4..df9de6b 100644 --- a/src/DataSource/DataSourceManager.ts +++ b/src/DataSource/DataSourceManager.ts @@ -11,6 +11,10 @@ export interface WeakDataSourceRef { initPromise?: AR; } +/** + * Manages active database connections. + * Connection can be closed after defined idle timeout. + */ export class DataSourceManager { private map: Map; private logger: Logger; @@ -21,7 +25,7 @@ export class DataSourceManager { private ct: CurrentTime, ) { this.map = new Map(); - this.logger = getLogger('core.typeorm.infra.data_source'); + this.logger = getLogger('core.typeorm.infra.data_source', ['core', 'typeorm']); } public async get(context: DataSourceContextConfig): ARP { diff --git a/src/DataSource/SystemDataSourceContext.ts b/src/DataSource/SystemDataSourceContext.ts index 52aa039..f2291d8 100644 --- a/src/DataSource/SystemDataSourceContext.ts +++ b/src/DataSource/SystemDataSourceContext.ts @@ -1,13 +1,13 @@ import { AR, OKA } from '@hexancore/common'; import { DataSource, EntityManager } from 'typeorm'; -import { DataSourceContext } from './DataSourceContext'; +import { AbstractDataSourceContext } from './AbstractDataSourceContext'; import { DataSourceContextConfig } from './DataSourceContextConfig'; import { WeakDataSourceRef } from './DataSourceManager'; import { Injectable } from '@nestjs/common'; import { TYPEORM_SYSTEM_PERSISTER_TYPE } from '@/Repository'; @Injectable() -export class SystemDataSourceContext extends DataSourceContext { +export class SystemDataSourceContext extends AbstractDataSourceContext { protected getConfig(): AR { return OKA({ id: 'system', diff --git a/src/DataSource/index.ts b/src/DataSource/index.ts index d63f7ef..0e3a74a 100644 --- a/src/DataSource/index.ts +++ b/src/DataSource/index.ts @@ -1,5 +1,6 @@ export * from './DataSourceFactory'; export * from './DataSourceManager'; +export * from './AbstractDataSourceContext'; export * from './AccountDataSourceContext'; export * from './SystemDataSourceContext'; export * from './DataSourceContextConfig'; diff --git a/src/HcTypeOrmModule.ts b/src/HcTypeOrmModule.ts index 61bca8c..f7338d3 100644 --- a/src/HcTypeOrmModule.ts +++ b/src/HcTypeOrmModule.ts @@ -1,98 +1,60 @@ -import { AppMeta, CurrentTime, INTERNAL_ERROR, LogicError, OK, getLogger } from '@hexancore/common'; +import { CurrentTime, INTERNAL_ERROR, OK, getLogger } from '@hexancore/common'; import { AppConfig, EntityPersisterFactoryManager } from '@hexancore/core'; import { ConfigurableModuleBuilder, Module, OnApplicationShutdown } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; import { AccountDataSourceContext } from './DataSource'; import { DataSourceFactory } from './DataSource/DataSourceFactory'; import { DataSourceManager } from './DataSource/DataSourceManager'; -import { SnakeNamingStrategy } from './DataSource/SnakeNamingStrategy'; import { SystemDataSourceContext } from './DataSource/SystemDataSourceContext'; +import { CONFIG_TOKEN, HcTypeOrmModuleConfigProvider } from './HcTypeOrmModuleConfigProvider'; +import type { HcTypeOrmModuleOptions } from './HcTypeOrmModuleOptions'; import { TYPEORM_ACCOUNT_PERSISTER_TYPE, TYPEORM_SYSTEM_PERSISTER_TYPE } from './Repository'; import { TypeOrmEntityPersisterFactory } from './Repository/Persister/TypeOrmEntityPersisterFactory'; -export interface HcTypeOrmModuleOptions { - configPath?: string; - accountContext: boolean; -} - -const CONFIG_TOKEN = 'HC_TYPEORM_CONFIG'; +const SYSTEM_ENTITY_PERSISTER_FACTORY_TOKEN = 'HC_SystemEntityPersisterFactory'; +const ACCOUNT_ENTITY_PERSISTER_FACTORY_TOKEN = 'HC_AccountEntityPersisterFactory'; const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() .setClassMethodName('forRoot') .setExtras({ accountContext: false }, (def, extras) => { def.providers = def.providers ?? []; + if (extras.accountContext) { def.providers.push(AccountDataSourceContext); def.providers.push({ - provide: 'AccountEntityPersisterFactory', + provide: ACCOUNT_ENTITY_PERSISTER_FACTORY_TOKEN, inject: [AccountDataSourceContext, EntityPersisterFactoryManager], - useFactory: (account: AccountDataSourceContext, factoryManager: EntityPersisterFactoryManager) => { - const accountFactory = new TypeOrmEntityPersisterFactory(account); - factoryManager.registerFactory(TYPEORM_ACCOUNT_PERSISTER_TYPE, accountFactory); - return accountFactory; - }, + useFactory: (context: AccountDataSourceContext, manager: EntityPersisterFactoryManager) => { + const factory = new TypeOrmEntityPersisterFactory(context); + manager.registerFactory(TYPEORM_ACCOUNT_PERSISTER_TYPE, factory); + return factory; + } }); } + + def.providers.push(SystemDataSourceContext); + def.providers.push({ + provide: SYSTEM_ENTITY_PERSISTER_FACTORY_TOKEN, + inject: [SystemDataSourceContext, EntityPersisterFactoryManager], + useFactory: (context: SystemDataSourceContext, manager: EntityPersisterFactoryManager) => { + const factory = new TypeOrmEntityPersisterFactory(context); + manager.registerFactory(TYPEORM_SYSTEM_PERSISTER_TYPE, factory); + return factory; + }, + }); return def; }) .build(); @Module({ providers: [ - { - provide: CONFIG_TOKEN, - inject: [AppConfig, MODULE_OPTIONS_TOKEN], - useFactory: (config: AppConfig, options: HcTypeOrmModuleOptions) => { - const configPath = options.configPath ?? 'core.typeorm'; - const c = config.config.get(configPath); - if (!c) { - throw new LogicError('Empty config key for typeorm: ' + configPath); - } - - const supportedDrivers = ['mysql', 'postgres', 'mariadb']; - if (['mysql', 'postgres', 'mariadb'].indexOf(c.type) === -1) { - throw new LogicError(`HcTypeOrmModule supports only [${supportedDrivers.join(", ")}] drivers`); - } - - const isProd = AppMeta.get().isProd(); - const dropSchema = c.dropSchema == true && !isProd; - const synchronizeSchema = c.synchronizeSchema == true && !isProd; - - const common: MysqlConnectionOptions | PostgresConnectionOptions = { - type: c.type, - host: c.host, - port: Number.parseInt(c.port), - charset: c.charset ?? (c.type === 'postgres' ? 'utf8' : 'utf8mb4'), - applicationName: AppMeta.get().id, - useUTC: true, - timezone: 'Z', - synchronize: synchronizeSchema, - namingStrategy: new SnakeNamingStrategy(), - cache: c.cache == true ? true : false, - dropSchema: dropSchema, - }; - - return { - maxDataSourceIdleSecs: c.maxDataSourceIdleSecs ?? 60 * 60, - commonDataSourceConfig: common, - dbPrefix: c.dbPrefix ?? '', - authSecretKey: c.authSecretKey ?? 'core.typeorm', - retryDelay: c.retryDelay, - maxRetryAttempts: c.maxRetryAttempts, - }; - }, - }, + HcTypeOrmModuleConfigProvider(MODULE_OPTIONS_TOKEN), { provide: DataSourceFactory, inject: [CONFIG_TOKEN, AppConfig], useFactory: (typeOrmConfig: any, config: AppConfig) => { const secret = config.secrets.getAsBasicAuth(typeOrmConfig.authSecretKey); - if (secret.isError()) { - secret.e.panic(); - } - + secret.panicIfError(); typeOrmConfig.commonDataSourceConfig.username = secret.v.username; typeOrmConfig.commonDataSourceConfig.password = secret.v.password; @@ -111,18 +73,8 @@ const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModule return new DataSourceManager(factory, typeOrmConfig.maxDataSourceIdleSecs, ct); }, }, - SystemDataSourceContext, - { - provide: 'SystemEntityPersisterFactory', - inject: [SystemDataSourceContext, EntityPersisterFactoryManager], - useFactory: (system: SystemDataSourceContext, factoryManager: EntityPersisterFactoryManager) => { - const systemFactory = new TypeOrmEntityPersisterFactory(system); - factoryManager.registerFactory(TYPEORM_SYSTEM_PERSISTER_TYPE, systemFactory); - return systemFactory; - }, - }, ], - exports: [DataSourceManager], + exports: [DataSourceManager, SYSTEM_ENTITY_PERSISTER_FACTORY_TOKEN, ACCOUNT_ENTITY_PERSISTER_FACTORY_TOKEN], }) export class HcTypeOrmModule extends ConfigurableModuleClass implements OnApplicationShutdown { public constructor(private moduleRef: ModuleRef) { diff --git a/src/HcTypeOrmModuleConfigProvider.ts b/src/HcTypeOrmModuleConfigProvider.ts new file mode 100644 index 0000000..986964e --- /dev/null +++ b/src/HcTypeOrmModuleConfigProvider.ts @@ -0,0 +1,77 @@ +import { AppConfig } from '@hexancore/core'; +import type { HcTypeOrmModuleOptions } from './HcTypeOrmModuleOptions'; +import type { Provider } from '@nestjs/common'; +import { AppMeta } from '@hexancore/common'; +import z from 'zod'; +import { SnakeNamingStrategy } from './DataSource/SnakeNamingStrategy'; +import type { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions.js'; +import type { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; + +export const CONFIG_TOKEN = 'HC_TYPEORM_CONFIG'; + +function parseConfig(plain: any) { + const configParser = z.object({ + type: z.enum(['postgres', 'mariadb']), + dropSchema: z.boolean().default(false), + synchronizeSchema: z.boolean().default(false), + port: z.number().min(1024).max(65535), + charset: z.string().default((plain?.type === 'postgres' ? 'utf8' : 'utf8mb4')), + useUTC: z.boolean().default(true), + timezone: z.string().default('Z'), + cache: z.union([z.boolean().default(false), z.any()]).default(false), // TODO: replace any with typeorm config object + connectTimeoutMs: z.number().default(5 * 1000), + metadataTableName: z.string().default("hc_typeorm_metadata"), + migrationsTableName: z.string().default("hc_typeorm_migrations"), + poolSize: z.number().min(1).default(5), + maxDataSourceIdleSecs: z.number().default(5 * 60), + dbPrefix: z.string().default(''), + authSecretKey: z.string().default('core.typeorm'), + retryDelay: z.number().default(5000), + maxRetryAttempts: z.number().default(5), + }).describe("Hexancore TypeORM config"); + + return configParser.parse(plain); +} + +export const HcTypeOrmModuleConfigProvider = (moduleOptionsToken: string | symbol): Provider => { + return { + provide: CONFIG_TOKEN, + inject: [AppConfig, moduleOptionsToken], + useFactory: (config: AppConfig, options: HcTypeOrmModuleOptions) => { + const configPath = options.configPath ?? 'core.typeorm'; + const c = config.config.get(configPath); + const parsedConfig = parseConfig(c); + + const isProd = AppMeta.get().isProd(); + const dropSchema = parsedConfig.dropSchema === true && !isProd; + const synchronizeSchema = parsedConfig.synchronizeSchema === true && !isProd; + + const commonDataSourceConfig: MysqlConnectionOptions | PostgresConnectionOptions = { + type: parsedConfig.type, + port: parsedConfig.port, + connectTimeout: parsedConfig.connectTimeoutMs, + connectTimeoutMS: parsedConfig.connectTimeoutMs, + poolSize: parsedConfig.poolSize, + charset: parsedConfig.charset, + useUTC: parsedConfig.useUTC, + timezone: parsedConfig.timezone, + metadataTableName: parsedConfig.metadataTableName, + migrationsTableName: parsedConfig.migrationsTableName, + applicationName: AppMeta.get().id, + synchronize: synchronizeSchema, + dropSchema: dropSchema, + namingStrategy: new SnakeNamingStrategy(), + cache: parsedConfig.cache + }; + + return { + maxDataSourceIdleSecs: parsedConfig.maxDataSourceIdleSecs, + commonDataSourceConfig, + dbPrefix: parsedConfig.dbPrefix, + authSecretKey: parsedConfig.authSecretKey, + retryDelay: parsedConfig.retryDelay, + maxRetryAttempts: parsedConfig.maxRetryAttempts, + }; + }, + }; +}; \ No newline at end of file diff --git a/src/HcTypeOrmModuleOptions.ts b/src/HcTypeOrmModuleOptions.ts new file mode 100644 index 0000000..67f4857 --- /dev/null +++ b/src/HcTypeOrmModuleOptions.ts @@ -0,0 +1,4 @@ +export interface HcTypeOrmModuleOptions { + configPath?: string; + accountContext: boolean; +} \ No newline at end of file diff --git a/src/Repository/Persister/TypeOrmEntityPersister.ts b/src/Repository/Persister/TypeOrmEntityPersister.ts index 9f118bf..45011ec 100644 --- a/src/Repository/Persister/TypeOrmEntityPersister.ts +++ b/src/Repository/Persister/TypeOrmEntityPersister.ts @@ -1,5 +1,5 @@ -import { DataSourceContext } from '@/DataSource/DataSourceContext'; -import { AR, ARW, AppError, ERR, GetQueryOptions, IGNORE_ERROR, INTERNAL_ERROR, OK, OKA, P, isIgnoreError, wrapToArray } from '@hexancore/common'; +import type { AbstractDataSourceContext } from '@/DataSource'; +import { AR, ARW, ERR, GetQueryOptions, IGNORE_ERROR, INTERNAL_ERROR, OK, OKA, P, isIgnoreError, wrapToArray } from '@hexancore/common'; import { AbstractEntityCommon, AbstractEntityPersister, AbstractEntityRepositoryCommon, EntityIdTypeOf, EntityMetaCommon } from '@hexancore/core'; import { DataSource, EntityManager, EntityMetadata, FindManyOptions, FindOneOptions, Repository, UpdateValuesMissingError } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; @@ -20,7 +20,7 @@ export class TypeOrmEntityPersister, M exten public constructor( repository: AbstractEntityRepositoryCommon, any>, - protected context: DataSourceContext, + protected context: AbstractDataSourceContext, ) { super(repository); } diff --git a/src/Repository/Persister/TypeOrmEntityPersisterFactory.ts b/src/Repository/Persister/TypeOrmEntityPersisterFactory.ts index b7ba900..4c7ea27 100644 --- a/src/Repository/Persister/TypeOrmEntityPersisterFactory.ts +++ b/src/Repository/Persister/TypeOrmEntityPersisterFactory.ts @@ -1,9 +1,9 @@ import { AbstractEntityPersister, AbstractEntityRepositoryCommon, IEntityPersisterFactory } from '@hexancore/core'; import { TypeOrmEntityPersister } from './TypeOrmEntityPersister'; -import { DataSourceContext } from '../../DataSource/DataSourceContext'; +import { AbstractDataSourceContext } from '../../DataSource/AbstractDataSourceContext'; export class TypeOrmEntityPersisterFactory implements IEntityPersisterFactory { - public constructor(private context: DataSourceContext) {} + public constructor(private context: AbstractDataSourceContext) { } public create

>(repository: AbstractEntityRepositoryCommon): P { return new TypeOrmEntityPersister(repository, this.context) as any; diff --git a/test/helper/src/Test/Domain/test_entities.ts b/test/helper/src/Test/Domain/test_entities.ts index 6e0eb45..a9bdefb 100644 --- a/test/helper/src/Test/Domain/test_entities.ts +++ b/test/helper/src/Test/Domain/test_entities.ts @@ -12,21 +12,21 @@ import { } from '@hexancore/core'; @ValueObject('Test') -export class BookId extends UIntValue {} +export class BookId extends UIntValue { } @Entity() export class Book extends AbstractEntity { public readonly authorId!: AuthorId; public readonly createdAt!: DateTime; - public constructor(public name: string) { + public constructor(public title: string) { super(); return this.proxify(); } } @ValueObject('Test') -export class AuthorId extends UIntValue {} +export class AuthorId extends UIntValue { } @AggregateRoot() export class Author extends AbstractAggregateRoot { @@ -46,7 +46,7 @@ export class CompositeBook extends AbstractEntity { public constructor( id: BookId, - public name: string, + public title: string, ) { super(); this.id = id; @@ -65,7 +65,7 @@ export class CompositeAuthor extends AbstractAggregateRoot { } } -export interface AuthorRepository extends IAggregateRootRepository {} +export interface AuthorRepository extends IAggregateRootRepository { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CompositeAuthorRepository extends IAggregateRootRepository {} +export interface CompositeAuthorRepository extends IAggregateRootRepository { } diff --git a/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmBookRepository.ts b/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmBookRepository.ts index 6d6b1c9..793c38e 100644 --- a/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmBookRepository.ts +++ b/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmBookRepository.ts @@ -11,7 +11,7 @@ export const BookSchema = TypeOrmAccountEntitySchema(Book, { columns: { id: UIntValueColumn.asPrimaryKey(BookId), authorId: UIntValueColumn.as(AuthorId, { type: 'smallint' }), - name: { type: 'varchar', length: 255 }, + title: { type: 'varchar', length: 255 }, createdAt: DateTimeColumn.asSelf(), }, }); diff --git a/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmCompositeBookRepository.ts b/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmCompositeBookRepository.ts index 408613c..3de79f6 100644 --- a/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmCompositeBookRepository.ts +++ b/test/helper/src/Test/Infrastructure/Persistence/TypeOrm/Account/Author/TypeOrmCompositeBookRepository.ts @@ -11,7 +11,7 @@ export const CompositeBookSchema = TypeOrmAccountEntitySchema(CompositeBook, { columns: { authorId: UIntValueColumn.asPrimaryKey(AuthorId, { type: 'smallint', generated: null }), id: UIntValueColumn.asPrimaryKey(BookId, { type: 'smallint', generated: null }), - name: { type: 'varchar', length: 255 }, + title: { type: 'varchar', length: 255 }, createdAt: DateTimeColumn.asSelf(), }, }); diff --git a/test/helper/src/Test/Infrastructure/PrivateTestInfraModule.ts b/test/helper/src/Test/Infrastructure/PrivateTestInfraModule.ts index 88c1ae7..d60c5b1 100644 --- a/test/helper/src/Test/Infrastructure/PrivateTestInfraModule.ts +++ b/test/helper/src/Test/Infrastructure/PrivateTestInfraModule.ts @@ -3,5 +3,6 @@ import { Module } from '@nestjs/common'; @Module({ imports: [HcInfraDomainModule.forFeature({ moduleInfraDir: __dirname })], + exports: [HcInfraDomainModule], }) export class PrivateTestInfraModule {} diff --git a/test/integration/CompositePrimaryKey.test.ts b/test/integration/CompositePrimaryKey.test.ts index dd01290..a288274 100644 --- a/test/integration/CompositePrimaryKey.test.ts +++ b/test/integration/CompositePrimaryKey.test.ts @@ -14,7 +14,7 @@ describe('CompositePrimaryKey', () => { let module: TestingModule; let authorRepository: CompositeAuthorRepository; - describe.each([{ driver: 'mysql' }, { driver: 'postgres' }])('$driver', ({ driver }) => { + describe.each([{ driver: 'mariadb' }, { driver: 'postgres' }])('$driver', ({ driver }) => { beforeEach(async () => { module = await Test.createTestingModule({ imports: [ @@ -25,8 +25,7 @@ describe('CompositePrimaryKey', () => { }), PrivateTestInfraModule, ], - }).compile(); - + }).compile({}); authorRepository = (await module.get(AggregateRootRepositoryManager)).get(CompositeAuthor); }); diff --git a/test/integration/TypeOrmAggregateRootRepository.test.ts b/test/integration/TypeOrmAggregateRootRepository.test.ts index 3c29af3..685cd48 100644 --- a/test/integration/TypeOrmAggregateRootRepository.test.ts +++ b/test/integration/TypeOrmAggregateRootRepository.test.ts @@ -14,11 +14,11 @@ describe('TypeOrmAggregateRootRepository', () => { let module: TestingModule; let authorRepository: AuthorRepository; - describe.each([{ driver: 'mysql' }, { driver: 'postgres' }])('$driver', ({ driver }) => { + describe.each([{ driver: 'mariadb' }, { driver: 'postgres' }])('$driver', ({ driver }) => { beforeEach(async () => { module = await Test.createTestingModule({ imports: [ - HcModule.forRoot({ cls: true, accountContext: {useCls: true, currentAccountId: AccountId.cs("test")} }), + HcModule.forRoot({ cls: true, accountContext: { useCls: true, currentAccountId: AccountId.cs("test") } }), HcTypeOrmModule.forRoot({ configPath: 'core.typeorm-' + driver, accountContext: true, @@ -53,19 +53,24 @@ describe('TypeOrmAggregateRootRepository', () => { const book = new Book('test'); author.books.add(book); - let r: any = await authorRepository.persist(author); + const persistResult = await authorRepository.persist(author); - expect(r).toMatchSuccessResult(true); + expect(persistResult).toMatchSuccessResult(true); - r = await authorRepository.getAllAsArray(); - expect(r.v[0].id).toEqual(author.id); - const abr = await r.v[0].books.getAllAsArray(); - expect(abr.isSuccess()).toBeTruthy(); - const ab = abr.v; - expect(ab.length).toBe(1); - expect(ab[0]).toEqual(book); - const currentBookById = await r.v[0].books.getById(ab[0].id); - expect(currentBookById).toEqual(OK(book)); + const repositoryGetAllResult = await authorRepository.getAllAsArray(); + expect(repositoryGetAllResult.isSuccess()).toBeTruthy(); + const authors = repositoryGetAllResult.v; + expect(authors.length).toBe(1); + expect(authors[0].id).toEqual(author.id); + + const authorBooksGetAllResult = await authors[0].books.getAllAsArray(); + expect(authorBooksGetAllResult.isSuccess()).toBeTruthy(); + const books = authorBooksGetAllResult.v; + expect(books.length).toBe(1); + expect(books[0]).toEqual(book); + + const authorBooksGetByIdResult = await authors[0].books.getById(books[0].id!); + expect(authorBooksGetByIdResult).toEqual(OK(book)); }), ); @@ -81,13 +86,13 @@ describe('TypeOrmAggregateRootRepository', () => { let r = await authorRepository.getAllAsArray(); const abr = await r.v[0].books.getAllAsArray(); const ab = abr.v; - ab[0].name = 'test_new_name'; + ab[0].title = 'test_new_name'; r.v[0].books.update(ab[0]); await authorRepository.persist(r.v[0]); r = await authorRepository.getAllAsArray(); - expect((await r.v[0].books.getAllAsArray()).v[0].name).toEqual('test_new_name'); + expect((await r.v[0].books.getAllAsArray()).v[0].title).toEqual('test_new_name'); }); }); }); diff --git a/yarn.lock b/yarn.lock index f5561b9..93f2616 100644 --- a/yarn.lock +++ b/yarn.lock @@ -719,9 +719,9 @@ __metadata: languageName: node linkType: hard -"@hexancore/core@npm:^0.15.2": - version: 0.15.2 - resolution: "@hexancore/core@npm:0.15.2" +"@hexancore/core@npm:^0.15.4": + version: 0.15.4 + resolution: "@hexancore/core@npm:0.15.4" dependencies: "@fastify/cookie": "npm:^9.3.1" "@fastify/cors": "npm:^9.0.1" @@ -754,7 +754,7 @@ __metadata: "@nestjs/platform-fastify": ^10.3.9 "@nestjs/swagger": ^7.1.8 nestjs-cls: ^4.3.0 - checksum: 10c0/e669fb2c325ea202929d80b6fcfe2e0b41a471a37fd4a907ef6484518d9200bd8a3d0940ffff56456d2c70ffeb565fd181423e31c257533950cd4023622dd978 + checksum: 10c0/d98aa23a63ea0d5eb235580e7b7e4a17b4dcce6719a8a7e952d7285e1db695151a82d21ddb5a5bb9756abe144fdcbb81e919264944d568d6642471b8e2f64427 languageName: node linkType: hard @@ -770,7 +770,7 @@ __metadata: resolution: "@hexancore/typeorm@workspace:." dependencies: "@hexancore/common": "npm:^0.14.0" - "@hexancore/core": "npm:^0.15.2" + "@hexancore/core": "npm:^0.15.4" "@hexancore/mocker": "npm:^1.1.2" "@nestjs/cli": "npm:^10.3.2" "@nestjs/common": "npm:^10.3.9" @@ -808,6 +808,7 @@ __metadata: tslib: "npm:^2.6.3" typeorm: "npm:^0.3.20" typescript: "npm:5.4.5" + zod: "npm:^3.23.8" peerDependencies: "@hexancore/common": ^0.14.0 "@hexancore/core": ^0.15.1 @@ -8668,3 +8669,10 @@ __metadata: checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f languageName: node linkType: hard + +"zod@npm:^3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard