@rsdk/db.typeorm 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.env +2 -0
  2. package/CHANGELOG.md +155 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +12 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/internal/context.d.ts +46 -0
  7. package/dist/internal/context.js +123 -0
  8. package/dist/internal/context.js.map +1 -0
  9. package/dist/internal/exceptions.d.ts +67 -0
  10. package/dist/internal/exceptions.js +99 -0
  11. package/dist/internal/exceptions.js.map +1 -0
  12. package/dist/internal/initializer.d.ts +15 -0
  13. package/dist/internal/initializer.js +158 -0
  14. package/dist/internal/initializer.js.map +1 -0
  15. package/dist/internal/strategy.d.ts +55 -0
  16. package/dist/internal/strategy.js +109 -0
  17. package/dist/internal/strategy.js.map +1 -0
  18. package/dist/internal/test/strategy.test.e2e.d.ts +5 -0
  19. package/dist/internal/test/strategy.test.e2e.js +779 -0
  20. package/dist/internal/test/strategy.test.e2e.js.map +1 -0
  21. package/dist/internal/test/util.d.ts +22 -0
  22. package/dist/internal/test/util.js +72 -0
  23. package/dist/internal/test/util.js.map +1 -0
  24. package/dist/providers/index.d.ts +3 -0
  25. package/dist/providers/index.js +20 -0
  26. package/dist/providers/index.js.map +1 -0
  27. package/dist/providers/typeorm-logger.adapter.d.ts +13 -0
  28. package/dist/providers/typeorm-logger.adapter.js +61 -0
  29. package/dist/providers/typeorm-logger.adapter.js.map +1 -0
  30. package/dist/providers/typeorm.config.d.ts +10 -0
  31. package/dist/providers/typeorm.config.js +79 -0
  32. package/dist/providers/typeorm.config.js.map +1 -0
  33. package/dist/providers/typeorm.healthcheck.d.ts +8 -0
  34. package/dist/providers/typeorm.healthcheck.js +36 -0
  35. package/dist/providers/typeorm.healthcheck.js.map +1 -0
  36. package/dist/typeorm.errors-transformer.d.ts +5 -0
  37. package/dist/typeorm.errors-transformer.js +32 -0
  38. package/dist/typeorm.errors-transformer.js.map +1 -0
  39. package/dist/typeorm.plugin.d.ts +15 -0
  40. package/dist/typeorm.plugin.js +67 -0
  41. package/dist/typeorm.plugin.js.map +1 -0
  42. package/dist/types.d.ts +14 -0
  43. package/dist/types.js +18 -0
  44. package/dist/types.js.map +1 -0
  45. package/jest.config.js +7 -0
  46. package/package.json +26 -0
  47. package/src/index.ts +9 -0
  48. package/src/internal/context.ts +164 -0
  49. package/src/internal/exceptions.ts +96 -0
  50. package/src/internal/initializer.ts +213 -0
  51. package/src/internal/strategy.ts +149 -0
  52. package/src/internal/test/strategy.test.e2e.ts +1050 -0
  53. package/src/internal/test/util.ts +51 -0
  54. package/src/providers/index.ts +3 -0
  55. package/src/providers/typeorm-logger.adapter.ts +59 -0
  56. package/src/providers/typeorm.config.ts +61 -0
  57. package/src/providers/typeorm.healthcheck.ts +20 -0
  58. package/src/typeorm.errors-transformer.ts +35 -0
  59. package/src/typeorm.plugin.ts +83 -0
  60. package/src/types.ts +23 -0
  61. package/tsconfig.build.json +9 -0
  62. package/tsconfig.json +9 -0
@@ -0,0 +1,51 @@
1
+ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
2
+
3
+ import { Initializer } from '../initializer';
4
+
5
+ export const secondCat = {
6
+ id: 2,
7
+ name: 'Barsik',
8
+ };
9
+ export const cat = {
10
+ id: 1,
11
+ name: 'Pushistik',
12
+ };
13
+
14
+ @Entity()
15
+ export class Cat {
16
+ @PrimaryGeneratedColumn()
17
+ id!: number;
18
+
19
+ @Column()
20
+ name!: string;
21
+ }
22
+
23
+ @Entity()
24
+ export class Account {
25
+ @PrimaryGeneratedColumn()
26
+ id!: number;
27
+
28
+ @Column('int')
29
+ balance!: number;
30
+
31
+ @Column({ default: new Date() })
32
+ balanceLastUpdateAt!: Date;
33
+ }
34
+
35
+ export class SyntheticException extends Error {
36
+ override name = this.constructor.name;
37
+ }
38
+
39
+ export const contextTransactionMustBeInitialized = (): void => {
40
+ const transactionActive =
41
+ Initializer.storage.getContext()?.queryRunner?.isTransactionActive;
42
+
43
+ expect(transactionActive).toBe(true);
44
+ };
45
+
46
+ export const contextTransactionMustNotInitialized = (): void => {
47
+ const transactionActive =
48
+ Initializer.storage.getContext()?.queryRunner?.isTransactionActive;
49
+
50
+ expect(transactionActive).toBeFalsy();
51
+ };
@@ -0,0 +1,3 @@
1
+ export * from './typeorm-logger.adapter';
2
+ export * from './typeorm.config';
3
+ export * from './typeorm.healthcheck';
@@ -0,0 +1,59 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectLogger } from '@rsdk/core';
3
+ import { ILogger } from '@rsdk/logging';
4
+ import type { Logger } from 'typeorm';
5
+
6
+ @Injectable()
7
+ export class TypeormLoggerAdapter implements Logger {
8
+ constructor(@InjectLogger('TypeOrm') private platformLogger: ILogger) {}
9
+
10
+ logQuery(query: string, parameters?: any[]): any {
11
+ const message = this.formatParts(
12
+ `query: ${query}`,
13
+ parameters && `parameters: ${JSON.stringify(parameters)}`,
14
+ );
15
+
16
+ this.platformLogger.debug(message);
17
+ }
18
+
19
+ logMigration(message: string): any {
20
+ this.platformLogger.info('Migration: ' + message);
21
+ }
22
+
23
+ formatParts(...parts: (string | boolean | undefined)[]): string {
24
+ return parts.filter(Boolean).join(' ');
25
+ }
26
+
27
+ logQuerySlow(time: number, query: string, parameters?: any[]): any {
28
+ const message = this.formatParts(
29
+ `query: ${query}`,
30
+ parameters && 'parameters: ' + parameters,
31
+ `time: ${time}`,
32
+ );
33
+
34
+ this.platformLogger.warn(message);
35
+ }
36
+
37
+ logQueryError(error: string | Error, query: string, parameters?: any[]): any {
38
+ const parts = [
39
+ `error: ${JSON.stringify(error)}`,
40
+ `query: ${query}`,
41
+ parameters && `parameters: ${JSON.stringify(parameters)}`,
42
+ ];
43
+ const message = this.formatParts(...parts);
44
+
45
+ this.platformLogger.error(message);
46
+ }
47
+
48
+ logSchemaBuild(message: string): any {
49
+ this.platformLogger.info(`SchemaBuild: ${message}`);
50
+ }
51
+
52
+ log(level: 'log' | 'info' | 'warn', message: any): any {
53
+ if (level === 'log') {
54
+ return this.platformLogger.info(message);
55
+ }
56
+
57
+ return this.platformLogger[level](message);
58
+ }
59
+ }
@@ -0,0 +1,61 @@
1
+ import { text, Timespan } from '@rsdk/common';
2
+ import {
3
+ Config,
4
+ ConfigSection,
5
+ ConfigTag,
6
+ IntParser,
7
+ Property,
8
+ StringParser,
9
+ TimespanParser,
10
+ UrlParser,
11
+ } from '@rsdk/core';
12
+
13
+ @ConfigSection({
14
+ name: 'typeorm database config',
15
+ tags: [ConfigTag.infrastructure, ConfigTag.storage],
16
+ })
17
+ export class TypeOrmPluginConfig extends Config {
18
+ @Property('DB_URL', new UrlParser(), {
19
+ description: 'Database server url',
20
+ })
21
+ url!: URL;
22
+
23
+ @Property('DB_SCHEMA', new StringParser(), {
24
+ description: text`
25
+ Use schema for all entities.
26
+ **Only for postgres at the moment!**
27
+ `,
28
+ defaultValue: undefined,
29
+ })
30
+ schema!: string;
31
+
32
+ @Property('DB_POOL_MIN', new IntParser(), {
33
+ defaultValue: 1,
34
+ description: text`
35
+ Minimum connection pool size.
36
+ **Only for postgres at the moment!**
37
+ `,
38
+ })
39
+ poolMin!: number;
40
+
41
+ @Property('DB_POOL_MAX', new IntParser(), {
42
+ defaultValue: 10,
43
+ description: text`
44
+ Maximum connection pool size.
45
+ **Only for postgres at the moment!**
46
+ `,
47
+ })
48
+ poolMax!: number;
49
+
50
+ @Property('DB_RECONNECT_ATTEMPTS', new IntParser(), {
51
+ defaultValue: Number.POSITIVE_INFINITY,
52
+ description: 'Retry attempt count before give up',
53
+ })
54
+ reconnectAttempts!: number;
55
+
56
+ @Property('DB_RECONNECT_INTERVAL', new TimespanParser(), {
57
+ defaultValue: new Timespan(1, 's'),
58
+ description: 'Delay between retries',
59
+ })
60
+ reconnectInterval!: Timespan;
61
+ }
@@ -0,0 +1,20 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import type { HealthIndicator } from '@rsdk/core';
3
+ import { CheckResult, Indicator } from '@rsdk/core';
4
+ import { EntityManager } from 'typeorm';
5
+
6
+ @Injectable()
7
+ @Indicator('typeorm')
8
+ export class TypeOrmHealthIndicator implements HealthIndicator {
9
+ constructor(private manager: EntityManager) {}
10
+
11
+ async check(): Promise<CheckResult> {
12
+ try {
13
+ const result = await this.manager.query('select 1 + 1');
14
+
15
+ return CheckResult.up(result);
16
+ } catch (error: any) {
17
+ return CheckResult.down(error);
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,35 @@
1
+ import type { IErrorsTransformer, PipelineException } from '@rsdk/core';
2
+ import { InternalException, NotFoundException } from '@rsdk/core';
3
+ import { EntityNotFoundError, TypeORMError } from 'typeorm';
4
+
5
+ const DEFAULT_MESSAGE = 'Something went wrong';
6
+
7
+ export class TypeormErrorTransformer implements IErrorsTransformer {
8
+ match(ex: unknown): boolean {
9
+ return ex instanceof TypeORMError;
10
+ }
11
+
12
+ transform(ex: any): PipelineException {
13
+ /**
14
+ * Потому что в `message` typeorm exception может быть информация о таблицах и сущностях, которую не хочется лишний раз светить
15
+ */
16
+ const message = ex.message;
17
+
18
+ ex.message = DEFAULT_MESSAGE;
19
+ ex._message = message;
20
+
21
+ if (ex instanceof EntityNotFoundError) {
22
+ return new NotFoundException(DEFAULT_MESSAGE, {
23
+ cause: ex,
24
+ });
25
+ }
26
+
27
+ if (ex instanceof TypeORMError) {
28
+ return new InternalException(DEFAULT_MESSAGE, {
29
+ cause: ex,
30
+ });
31
+ }
32
+
33
+ throw new Error('unknown exception');
34
+ }
35
+ }
@@ -0,0 +1,83 @@
1
+ import type { DynamicModule } from '@nestjs/common';
2
+ import type { TypeOrmModuleOptions } from '@nestjs/typeorm';
3
+ import { TypeOrmModule } from '@nestjs/typeorm';
4
+ import type { Constructor } from '@rsdk/common';
5
+ import type {
6
+ AppropriateTransports,
7
+ IErrorsTransformer,
8
+ PlatformAppPlugin,
9
+ } from '@rsdk/core';
10
+ import { APP_NAME, PlatformConfigModule } from '@rsdk/core';
11
+
12
+ import { Initializer } from './internal/initializer';
13
+ import {
14
+ TypeOrmHealthIndicator,
15
+ TypeormLoggerAdapter,
16
+ TypeOrmPluginConfig,
17
+ } from './providers';
18
+ import { TypeormErrorTransformer } from './typeorm.errors-transformer';
19
+ import type { TypeOrmPluginOptions } from './types';
20
+
21
+ export class TypeOrmPlugin implements PlatformAppPlugin {
22
+ /**
23
+ * По умолчанию парсит тип драйвера из env %PACKAGE_NAME%_DB_URL
24
+ */
25
+ constructor(private readonly options: TypeOrmPluginOptions) {
26
+ Initializer.initialize();
27
+ }
28
+
29
+ get entities(): (string | any)[] | undefined {
30
+ return this?.options?.entities;
31
+ }
32
+
33
+ forTransports(): AppropriateTransports {
34
+ return 'any';
35
+ }
36
+
37
+ modules(): (Constructor | DynamicModule)[] {
38
+ return [
39
+ TypeOrmModule.forRootAsync({
40
+ imports: [
41
+ PlatformConfigModule.forFeature(TypeOrmPluginConfig),
42
+ {
43
+ module: TypeOrmModule,
44
+ providers: [TypeOrmHealthIndicator, TypeormLoggerAdapter],
45
+ exports: [TypeormLoggerAdapter],
46
+ },
47
+ ],
48
+ inject: [TypeOrmPluginConfig, TypeormLoggerAdapter, APP_NAME],
49
+ useFactory: (
50
+ config: TypeOrmPluginConfig,
51
+ logger: TypeormLoggerAdapter,
52
+ appName: string,
53
+ ): TypeOrmModuleOptions => ({
54
+ /**
55
+ * Так как typeorm поддерживает целый зоопарк не совместимых друг с другом СУБД, для всего нужны разные опции
56
+ * И он начинает ругаться на то что какие-то поля должны быть, каких то не должно быть
57
+ * Принято волевое решение пока остановиться на нашей основной СУБД, а именно `postgres`
58
+ */
59
+ type: this.options.type,
60
+ url: config.url.toString(),
61
+ ...(config.schema && {
62
+ schema: config.schema,
63
+ }),
64
+ logger,
65
+ ...(this.entities && { entities: this.entities }),
66
+ autoLoadEntities: !this.entities,
67
+ retryDelay: config.reconnectInterval.millis(),
68
+ retryAttempts: config.reconnectAttempts,
69
+ applicationName: appName + ':typeorm',
70
+ extra: {
71
+ min: config.poolMin,
72
+ max: config.poolMax,
73
+ },
74
+ ...(this.options.overrideConfig as any),
75
+ }),
76
+ }),
77
+ ];
78
+ }
79
+
80
+ errorTransformers(): IErrorsTransformer[] {
81
+ return [new TypeormErrorTransformer()];
82
+ }
83
+ }
package/src/types.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { TypeOrmModuleOptions } from '@nestjs/typeorm';
2
+
3
+ export interface TypeOrmPluginOptions {
4
+ type: NonNullable<TypeOrmModuleOptions['type']>;
5
+ entities?: (string | any)[];
6
+ overrideConfig?: Partial<Omit<TypeOrmModuleOptions, 'type'>>;
7
+ }
8
+
9
+ export enum IsolationLevel {
10
+ READ_UNCOMMITTED = 'READ UNCOMMITTED',
11
+ READ_COMMITTED = 'READ COMMITTED',
12
+ REPEATABLE_READ = 'REPEATABLE READ',
13
+ SERIALIZABLE = 'SERIALIZABLE',
14
+ }
15
+
16
+ export const isolationLevels: readonly IsolationLevel[] = [
17
+ IsolationLevel.READ_UNCOMMITTED,
18
+ IsolationLevel.READ_COMMITTED,
19
+ IsolationLevel.REPEATABLE_READ,
20
+ IsolationLevel.SERIALIZABLE,
21
+ ];
22
+
23
+ export const DEFAULT_ISOLATION_LEVEL = IsolationLevel.READ_UNCOMMITTED;
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "incremental": false,
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts", "**/*test.ts"]
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+
3
+ "extends": "@rsdk/tsconfig/base.json",
4
+ "compilerOptions": {
5
+ "declaration": true,
6
+ "incremental": false,
7
+ "outDir": "dist"
8
+ }
9
+ }