4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 | ## Description
25 |
26 | [TypeORM](http://typeorm.io) module for [Nest](https://github.com/nestjs/nest).
27 |
28 | ## Installation
29 |
30 | ```bash
31 | $ npm i --save @nestjs/typeorm typeorm
32 | ```
33 |
34 | ## Quick Start
35 |
36 | [Overview & Tutorial](https://docs.nestjs.com/techniques/sql)
37 |
38 | ## Support
39 |
40 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
41 |
42 | ## Stay in touch
43 |
44 | * Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
45 | * Website - [https://nestjs.com](https://nestjs.com/)
46 | * Twitter - [@nestframework](https://twitter.com/nestframework)
47 |
48 | ## License
49 |
50 | Nest is [MIT licensed](LICENSE).
51 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | postgres:
5 | image: postgres:17.3
6 | environment:
7 | POSTGRES_USER: root
8 | POSTGRES_PASSWORD: root
9 | POSTGRES_DB: test
10 | ports:
11 | - "3306:5432"
12 | restart: always
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4 | import globals from 'globals';
5 | import tseslint from 'typescript-eslint';
6 |
7 | export default tseslint.config(
8 | {
9 | ignores: ['tests/**'],
10 | },
11 | eslint.configs.recommended,
12 | ...tseslint.configs.recommendedTypeChecked,
13 | eslintPluginPrettierRecommended,
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.node,
18 | ...globals.jest,
19 | },
20 | ecmaVersion: 5,
21 | sourceType: 'module',
22 | parserOptions: {
23 | projectService: true,
24 | tsconfigRootDir: import.meta.dirname,
25 | },
26 | },
27 | },
28 | {
29 | rules: {
30 | '@typescript-eslint/no-explicit-any': 'off',
31 | '@typescript-eslint/no-unsafe-assignment': 'off',
32 | '@typescript-eslint/no-unsafe-call': 'off',
33 | '@typescript-eslint/no-unsafe-member-access': 'off',
34 | '@typescript-eslint/no-unsafe-function-type': 'off',
35 | '@typescript-eslint/no-unsafe-argument': 'off',
36 | '@typescript-eslint/no-unsafe-return': 'off',
37 | '@typescript-eslint/require-await': 'warn',
38 | '@typescript-eslint/no-misused-promises': 'warn',
39 | },
40 | },
41 | );
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | function __export(m) {
3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4 | }
5 | exports.__esModule = true;
6 | __export(require("./dist"));
7 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/lib/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './typeorm.decorators';
2 | export * from './typeorm.utils';
3 |
--------------------------------------------------------------------------------
/lib/common/typeorm.decorators.ts:
--------------------------------------------------------------------------------
1 | import { Inject } from '@nestjs/common';
2 | import { DataSource, DataSourceOptions } from 'typeorm';
3 | import { EntityClassOrSchema } from '../interfaces/entity-class-or-schema.type';
4 | import { DEFAULT_DATA_SOURCE_NAME } from '../typeorm.constants';
5 | import {
6 | getDataSourceToken,
7 | getEntityManagerToken,
8 | getRepositoryToken,
9 | } from './typeorm.utils';
10 |
11 | /**
12 | * @publicApi
13 | */
14 | export const InjectRepository = (
15 | entity: EntityClassOrSchema,
16 | dataSource: string = DEFAULT_DATA_SOURCE_NAME,
17 | ): ReturnType => Inject(getRepositoryToken(entity, dataSource));
18 |
19 | /**
20 | * @publicApi
21 | */
22 | export const InjectDataSource: (
23 | dataSource?: DataSource | DataSourceOptions | string,
24 | ) => ReturnType = (
25 | dataSource?: DataSource | DataSourceOptions | string,
26 | ) => Inject(getDataSourceToken(dataSource));
27 |
28 | /** @deprecated */
29 | export const InjectConnection = InjectDataSource;
30 |
31 | /**
32 | * @publicApi
33 | */
34 | export const InjectEntityManager: (
35 | dataSource?: DataSource | DataSourceOptions | string,
36 | ) => ReturnType = (
37 | dataSource?: DataSource | DataSourceOptions | string,
38 | ) => Inject(getEntityManagerToken(dataSource));
39 |
--------------------------------------------------------------------------------
/lib/common/typeorm.utils.ts:
--------------------------------------------------------------------------------
1 | import { Logger, Type } from '@nestjs/common';
2 | import { Observable } from 'rxjs';
3 | import { delay, retryWhen, scan } from 'rxjs/operators';
4 | import {
5 | AbstractRepository,
6 | Connection,
7 | DataSource,
8 | DataSourceOptions,
9 | EntityManager,
10 | EntitySchema,
11 | Repository,
12 | } from 'typeorm';
13 | import { CircularDependencyException } from '../exceptions/circular-dependency.exception';
14 | import { EntityClassOrSchema } from '../interfaces/entity-class-or-schema.type';
15 | import { DEFAULT_DATA_SOURCE_NAME } from '../typeorm.constants';
16 |
17 | const logger = new Logger('TypeOrmModule');
18 |
19 | /**
20 | * This function generates an injection token for an Entity or Repository
21 | * @param {EntityClassOrSchema} entity parameter can either be an Entity or Repository
22 | * @param {string} [dataSource='default'] DataSource name
23 | * @returns {string} The Entity | Repository injection token
24 | *
25 | * @publicApi
26 | */
27 | export function getRepositoryToken(
28 | entity: EntityClassOrSchema,
29 | dataSource:
30 | | DataSource
31 | | DataSourceOptions
32 | | string = DEFAULT_DATA_SOURCE_NAME,
33 | ): Function | string {
34 | if (entity === null || entity === undefined) {
35 | throw new CircularDependencyException('@InjectRepository()');
36 | }
37 | const dataSourcePrefix = getDataSourcePrefix(dataSource);
38 | if (
39 | entity instanceof Function &&
40 | (entity.prototype instanceof Repository ||
41 | entity.prototype instanceof AbstractRepository)
42 | ) {
43 | if (!dataSourcePrefix) {
44 | return entity;
45 | }
46 | return `${dataSourcePrefix}${getCustomRepositoryToken(entity)}`;
47 | }
48 |
49 | if (entity instanceof EntitySchema) {
50 | return `${dataSourcePrefix}${
51 | entity.options.target ? entity.options.target.name : entity.options.name
52 | }Repository`;
53 | }
54 | return `${dataSourcePrefix}${entity.name}Repository`;
55 | }
56 |
57 | /**
58 | * This function generates an injection token for an Entity or Repository
59 | * @param {Function} This parameter can either be an Entity or Repository
60 | * @returns {string} The Repository injection token
61 | *
62 | * @publicApi
63 | */
64 | export function getCustomRepositoryToken(repository: Function): string {
65 | if (repository === null || repository === undefined) {
66 | throw new CircularDependencyException('@InjectRepository()');
67 | }
68 | return repository.name;
69 | }
70 |
71 | /**
72 | * This function returns a DataSource injection token for the given DataSource, DataSourceOptions or dataSource name.
73 | * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either
74 | * a DataSource, or a DataSourceOptions or a string.
75 | * @returns {string | Function} The DataSource injection token.
76 | *
77 | * @publicApi
78 | */
79 | export function getDataSourceToken(
80 | dataSource:
81 | | DataSource
82 | | DataSourceOptions
83 | | string = DEFAULT_DATA_SOURCE_NAME,
84 | ): string | Function | Type {
85 | return DEFAULT_DATA_SOURCE_NAME === dataSource
86 | ? (DataSource ?? Connection)
87 | : 'string' === typeof dataSource
88 | ? `${dataSource}DataSource`
89 | : DEFAULT_DATA_SOURCE_NAME === dataSource.name || !dataSource.name
90 | ? (DataSource ?? Connection)
91 | : `${dataSource.name}DataSource`;
92 | }
93 |
94 | /**
95 | * @deprecated
96 | *
97 | * @publicApi
98 | */
99 | export const getConnectionToken = getDataSourceToken;
100 |
101 | /**
102 | * This function returns a DataSource prefix based on the dataSource name
103 | * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either
104 | * a DataSource, or a DataSourceOptions or a string.
105 | * @returns {string | Function} The DataSource injection token.
106 | */
107 | export function getDataSourcePrefix(
108 | dataSource:
109 | | DataSource
110 | | DataSourceOptions
111 | | string = DEFAULT_DATA_SOURCE_NAME,
112 | ): string {
113 | if (dataSource === DEFAULT_DATA_SOURCE_NAME) {
114 | return '';
115 | }
116 | if (typeof dataSource === 'string') {
117 | return dataSource + '_';
118 | }
119 | if (dataSource.name === DEFAULT_DATA_SOURCE_NAME || !dataSource.name) {
120 | return '';
121 | }
122 | return dataSource.name + '_';
123 | }
124 |
125 | /**
126 | * This function returns an EntityManager injection token for the given DataSource, DataSourceOptions or dataSource name.
127 | * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either
128 | * a DataSource, or a DataSourceOptions or a string.
129 | * @returns {string | Function} The EntityManager injection token.
130 | */
131 | export function getEntityManagerToken(
132 | dataSource:
133 | | DataSource
134 | | DataSourceOptions
135 | | string = DEFAULT_DATA_SOURCE_NAME,
136 | ): string | Function {
137 | return DEFAULT_DATA_SOURCE_NAME === dataSource
138 | ? EntityManager
139 | : 'string' === typeof dataSource
140 | ? `${dataSource}EntityManager`
141 | : DEFAULT_DATA_SOURCE_NAME === dataSource.name || !dataSource.name
142 | ? EntityManager
143 | : `${dataSource.name}EntityManager`;
144 | }
145 |
146 | export function handleRetry(
147 | retryAttempts = 9,
148 | retryDelay = 3000,
149 | dataSourceName = DEFAULT_DATA_SOURCE_NAME,
150 | verboseRetryLog = false,
151 | toRetry?: (err: any) => boolean,
152 | ): (source: Observable) => Observable {
153 | return (source: Observable) =>
154 | source.pipe(
155 | retryWhen((e) =>
156 | e.pipe(
157 | scan((errorCount, error: Error) => {
158 | if (toRetry && !toRetry(error)) {
159 | throw error;
160 | }
161 | const dataSourceInfo =
162 | dataSourceName === DEFAULT_DATA_SOURCE_NAME
163 | ? ''
164 | : ` (${dataSourceName})`;
165 | const verboseMessage = verboseRetryLog
166 | ? ` Message: ${error.message}.`
167 | : '';
168 |
169 | logger.error(
170 | `Unable to connect to the database${dataSourceInfo}.${verboseMessage} Retrying (${
171 | errorCount + 1
172 | })...`,
173 | error.stack,
174 | );
175 | if (errorCount + 1 >= retryAttempts) {
176 | throw error;
177 | }
178 | return errorCount + 1;
179 | }, 0),
180 | delay(retryDelay),
181 | ),
182 | ),
183 | );
184 | }
185 |
186 | export function getDataSourceName(options: DataSourceOptions): string {
187 | return options && options.name ? options.name : DEFAULT_DATA_SOURCE_NAME;
188 | }
189 |
190 | export const generateString = (): string => crypto.randomUUID();
191 |
--------------------------------------------------------------------------------
/lib/entities-metadata.storage.ts:
--------------------------------------------------------------------------------
1 | import { DataSource, DataSourceOptions } from 'typeorm';
2 | import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type';
3 |
4 | type DataSourceToken = DataSource | DataSourceOptions | string;
5 |
6 | export class EntitiesMetadataStorage {
7 | private static readonly storage = new Map();
8 |
9 | static addEntitiesByDataSource(
10 | dataSource: DataSourceToken,
11 | entities: EntityClassOrSchema[],
12 | ): void {
13 | const dataSourceToken =
14 | typeof dataSource === 'string' ? dataSource : dataSource.name;
15 | if (!dataSourceToken) {
16 | return;
17 | }
18 |
19 | let collection = this.storage.get(dataSourceToken);
20 | if (!collection) {
21 | collection = [];
22 | this.storage.set(dataSourceToken, collection);
23 | }
24 | entities.forEach((entity) => {
25 | if (collection.includes(entity)) {
26 | return;
27 | }
28 | collection.push(entity);
29 | });
30 | }
31 |
32 | static getEntitiesByDataSource(
33 | dataSource: DataSourceToken,
34 | ): EntityClassOrSchema[] {
35 | const dataSourceToken =
36 | typeof dataSource === 'string' ? dataSource : dataSource.name;
37 |
38 | if (!dataSourceToken) {
39 | return [];
40 | }
41 | return this.storage.get(dataSourceToken) || [];
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/exceptions/circular-dependency.exception.ts:
--------------------------------------------------------------------------------
1 | export class CircularDependencyException extends Error {
2 | constructor(context?: string) {
3 | const ctx = context ? ` inside ${context}` : ``;
4 | super(
5 | `A circular dependency has been detected${ctx}. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Also, try to eliminate barrel files because they can lead to an unexpected behavior too.`,
6 | );
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common';
2 | export * from './interfaces';
3 | export * from './typeorm.module';
4 |
--------------------------------------------------------------------------------
/lib/interfaces/entity-class-or-schema.type.ts:
--------------------------------------------------------------------------------
1 | import { EntitySchema } from 'typeorm';
2 |
3 | export type EntityClassOrSchema = Function | EntitySchema;
4 |
--------------------------------------------------------------------------------
/lib/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './typeorm-options.interface';
2 |
--------------------------------------------------------------------------------
/lib/interfaces/typeorm-options.interface.ts:
--------------------------------------------------------------------------------
1 | import { ModuleMetadata, Provider, Type } from '@nestjs/common';
2 | import { DataSource, DataSourceOptions } from 'typeorm';
3 |
4 | /**
5 | * @publicApi
6 | */
7 | export type TypeOrmModuleOptions = {
8 | /**
9 | * Number of times to retry connecting
10 | * Default: 10
11 | */
12 | retryAttempts?: number;
13 | /**
14 | * Delay between connection retry attempts (ms)
15 | * Default: 3000
16 | */
17 | retryDelay?: number;
18 | /**
19 | * Function that determines whether the module should
20 | * attempt to connect upon failure.
21 | *
22 | * @param err error that was thrown
23 | * @returns whether to retry connection or not
24 | */
25 | toRetry?: (err: any) => boolean;
26 | /**
27 | * If `true`, entities will be loaded automatically.
28 | */
29 | autoLoadEntities?: boolean;
30 | /**
31 | * If `true`, will show verbose error messages on each connection retry.
32 | */
33 | verboseRetryLog?: boolean;
34 | /**
35 | * If `true` database initialization will not be performed during module initialization.
36 | * This means that database connection will not be established and migrations will not run.
37 | * Database initialization will have to be performed manually using `DataSource.initialize`
38 | * and it will have to implement own retry mechanism (if necessary).
39 | */
40 | manualInitialization?: boolean;
41 | } & Partial;
42 |
43 | /**
44 | * @publicApi
45 | */
46 | export interface TypeOrmOptionsFactory {
47 | createTypeOrmOptions(
48 | connectionName?: string,
49 | ): Promise | TypeOrmModuleOptions;
50 | }
51 |
52 | /**
53 | * @publicApi
54 | */
55 | export type TypeOrmDataSourceFactory = (
56 | options?: DataSourceOptions,
57 | ) => Promise;
58 |
59 | /**
60 | * @publicApi
61 | */
62 | export interface TypeOrmModuleAsyncOptions
63 | extends Pick {
64 | name?: string;
65 | useExisting?: Type;
66 | useClass?: Type;
67 | useFactory?: (
68 | ...args: any[]
69 | ) => Promise | TypeOrmModuleOptions;
70 | dataSourceFactory?: TypeOrmDataSourceFactory;
71 | inject?: any[];
72 | extraProviders?: Provider[];
73 | }
74 |
--------------------------------------------------------------------------------
/lib/typeorm-core.module.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DynamicModule,
3 | Global,
4 | Inject,
5 | Logger,
6 | Module,
7 | OnApplicationShutdown,
8 | Provider,
9 | Type,
10 | } from '@nestjs/common';
11 | import { ModuleRef } from '@nestjs/core';
12 | import { defer, lastValueFrom } from 'rxjs';
13 | import {
14 | Connection,
15 | createConnection,
16 | DataSource,
17 | DataSourceOptions,
18 | } from 'typeorm';
19 | import {
20 | generateString,
21 | getDataSourceName,
22 | getDataSourceToken,
23 | getEntityManagerToken,
24 | handleRetry,
25 | } from './common/typeorm.utils';
26 | import { EntitiesMetadataStorage } from './entities-metadata.storage';
27 | import {
28 | TypeOrmDataSourceFactory,
29 | TypeOrmModuleAsyncOptions,
30 | TypeOrmModuleOptions,
31 | TypeOrmOptionsFactory,
32 | } from './interfaces/typeorm-options.interface';
33 | import { TYPEORM_MODULE_ID, TYPEORM_MODULE_OPTIONS } from './typeorm.constants';
34 |
35 | @Global()
36 | @Module({})
37 | export class TypeOrmCoreModule implements OnApplicationShutdown {
38 | private readonly logger = new Logger('TypeOrmModule');
39 |
40 | constructor(
41 | @Inject(TYPEORM_MODULE_OPTIONS)
42 | private readonly options: TypeOrmModuleOptions,
43 | private readonly moduleRef: ModuleRef,
44 | ) {}
45 |
46 | static forRoot(options: TypeOrmModuleOptions = {}): DynamicModule {
47 | const typeOrmModuleOptions = {
48 | provide: TYPEORM_MODULE_OPTIONS,
49 | useValue: options,
50 | };
51 | const dataSourceProvider = {
52 | provide: getDataSourceToken(options as DataSourceOptions),
53 | useFactory: async () => await this.createDataSourceFactory(options),
54 | };
55 | const entityManagerProvider = this.createEntityManagerProvider(
56 | options as DataSourceOptions,
57 | );
58 |
59 | const providers = [
60 | entityManagerProvider,
61 | dataSourceProvider,
62 | typeOrmModuleOptions,
63 | ];
64 | const exports = [entityManagerProvider, dataSourceProvider];
65 |
66 | // TODO: "Connection" class is going to be removed in the next version of "typeorm"
67 | if (dataSourceProvider.provide === DataSource) {
68 | providers.push({
69 | provide: Connection,
70 | useExisting: DataSource,
71 | });
72 | exports.push(Connection);
73 | }
74 |
75 | return {
76 | module: TypeOrmCoreModule,
77 | providers,
78 | exports,
79 | };
80 | }
81 |
82 | static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule {
83 | const dataSourceProvider = {
84 | provide: getDataSourceToken(options as DataSourceOptions),
85 | useFactory: async (typeOrmOptions: TypeOrmModuleOptions) => {
86 | if (options.name) {
87 | return await this.createDataSourceFactory(
88 | {
89 | ...typeOrmOptions,
90 | name: options.name,
91 | },
92 | options.dataSourceFactory,
93 | );
94 | }
95 | return await this.createDataSourceFactory(
96 | typeOrmOptions,
97 | options.dataSourceFactory,
98 | );
99 | },
100 | inject: [TYPEORM_MODULE_OPTIONS],
101 | };
102 | const entityManagerProvider = {
103 | provide: getEntityManagerToken(options as DataSourceOptions) as string,
104 | useFactory: (dataSource: DataSource) => dataSource.manager,
105 | inject: [getDataSourceToken(options as DataSourceOptions)],
106 | };
107 |
108 | const asyncProviders = this.createAsyncProviders(options);
109 | const providers = [
110 | ...asyncProviders,
111 | entityManagerProvider,
112 | dataSourceProvider,
113 | {
114 | provide: TYPEORM_MODULE_ID,
115 | useValue: generateString(),
116 | },
117 | ...(options.extraProviders || []),
118 | ];
119 | const exports: Array = [
120 | entityManagerProvider,
121 | dataSourceProvider,
122 | ];
123 |
124 | // TODO: "Connection" class is going to be removed in the next version of "typeorm"
125 | if (dataSourceProvider.provide === DataSource) {
126 | providers.push({
127 | provide: Connection,
128 | useExisting: DataSource,
129 | });
130 | exports.push(Connection);
131 | }
132 |
133 | return {
134 | module: TypeOrmCoreModule,
135 | imports: options.imports,
136 | providers,
137 | exports,
138 | };
139 | }
140 |
141 | async onApplicationShutdown(): Promise {
142 | const dataSource = this.moduleRef.get(
143 | getDataSourceToken(this.options as DataSourceOptions) as Type,
144 | );
145 | try {
146 | if (dataSource && dataSource.isInitialized) {
147 | await dataSource.destroy();
148 | }
149 | } catch (e) {
150 | this.logger.error(e?.message);
151 | }
152 | }
153 |
154 | private static createAsyncProviders(
155 | options: TypeOrmModuleAsyncOptions,
156 | ): Provider[] {
157 | if (options.useExisting || options.useFactory) {
158 | return [this.createAsyncOptionsProvider(options)];
159 | }
160 | const useClass = options.useClass as Type;
161 | return [
162 | this.createAsyncOptionsProvider(options),
163 | {
164 | provide: useClass,
165 | useClass,
166 | },
167 | ];
168 | }
169 |
170 | private static createAsyncOptionsProvider(
171 | options: TypeOrmModuleAsyncOptions,
172 | ): Provider {
173 | if (options.useFactory) {
174 | return {
175 | provide: TYPEORM_MODULE_OPTIONS,
176 | useFactory: options.useFactory,
177 | inject: options.inject || [],
178 | };
179 | }
180 | // `as Type` is a workaround for microsoft/TypeScript#31603
181 | const inject = [
182 | (options.useClass || options.useExisting) as Type,
183 | ];
184 | return {
185 | provide: TYPEORM_MODULE_OPTIONS,
186 | useFactory: async (optionsFactory: TypeOrmOptionsFactory) =>
187 | await optionsFactory.createTypeOrmOptions(options.name),
188 | inject,
189 | };
190 | }
191 |
192 | private static createEntityManagerProvider(
193 | options: DataSourceOptions,
194 | ): Provider {
195 | return {
196 | provide: getEntityManagerToken(options) as string,
197 | useFactory: (dataSource: DataSource) => dataSource.manager,
198 | inject: [getDataSourceToken(options)],
199 | };
200 | }
201 |
202 | private static async createDataSourceFactory(
203 | options: TypeOrmModuleOptions,
204 | dataSourceFactory?: TypeOrmDataSourceFactory,
205 | ): Promise {
206 | const dataSourceToken = getDataSourceName(options as DataSourceOptions);
207 | const createTypeormDataSource =
208 | dataSourceFactory ??
209 | ((options: DataSourceOptions) => {
210 | return DataSource === undefined
211 | ? createConnection(options)
212 | : new DataSource(options);
213 | });
214 | return await lastValueFrom(
215 | defer(async () => {
216 | let dataSource: DataSource;
217 | if (!options.autoLoadEntities) {
218 | dataSource = await createTypeormDataSource(
219 | options as DataSourceOptions,
220 | );
221 | } else {
222 | let entities = options.entities;
223 | if (Array.isArray(entities)) {
224 | entities = entities.concat(
225 | EntitiesMetadataStorage.getEntitiesByDataSource(dataSourceToken),
226 | );
227 | } else {
228 | entities =
229 | EntitiesMetadataStorage.getEntitiesByDataSource(dataSourceToken);
230 | }
231 | dataSource = await createTypeormDataSource({
232 | ...options,
233 | entities,
234 | } as DataSourceOptions);
235 | }
236 | // TODO: remove "dataSource.initialize" condition (left for backward compatibility)
237 | return (dataSource as any).initialize &&
238 | !dataSource.isInitialized &&
239 | !options.manualInitialization
240 | ? dataSource.initialize()
241 | : dataSource;
242 | }).pipe(
243 | handleRetry(
244 | options.retryAttempts,
245 | options.retryDelay,
246 | dataSourceToken,
247 | options.verboseRetryLog,
248 | options.toRetry,
249 | ),
250 | ),
251 | );
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/lib/typeorm.constants.ts:
--------------------------------------------------------------------------------
1 | export const TYPEORM_MODULE_OPTIONS = 'TypeOrmModuleOptions';
2 | export const TYPEORM_MODULE_ID = 'TypeOrmModuleId';
3 | export const DEFAULT_DATA_SOURCE_NAME = 'default';
4 |
--------------------------------------------------------------------------------
/lib/typeorm.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module } from '@nestjs/common';
2 | import { DataSource, DataSourceOptions } from 'typeorm';
3 | import { EntitiesMetadataStorage } from './entities-metadata.storage';
4 | import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type';
5 | import {
6 | TypeOrmModuleAsyncOptions,
7 | TypeOrmModuleOptions,
8 | } from './interfaces/typeorm-options.interface';
9 | import { TypeOrmCoreModule } from './typeorm-core.module';
10 | import { DEFAULT_DATA_SOURCE_NAME } from './typeorm.constants';
11 | import { createTypeOrmProviders } from './typeorm.providers';
12 |
13 | /**
14 | * @publicApi
15 | */
16 | @Module({})
17 | export class TypeOrmModule {
18 | static forRoot(options?: TypeOrmModuleOptions): DynamicModule {
19 | return {
20 | module: TypeOrmModule,
21 | imports: [TypeOrmCoreModule.forRoot(options)],
22 | };
23 | }
24 |
25 | static forFeature(
26 | entities: EntityClassOrSchema[] = [],
27 | dataSource:
28 | | DataSource
29 | | DataSourceOptions
30 | | string = DEFAULT_DATA_SOURCE_NAME,
31 | ): DynamicModule {
32 | const providers = createTypeOrmProviders(entities, dataSource);
33 | EntitiesMetadataStorage.addEntitiesByDataSource(dataSource, [...entities]);
34 | return {
35 | module: TypeOrmModule,
36 | providers: providers,
37 | exports: providers,
38 | };
39 | }
40 |
41 | static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule {
42 | return {
43 | module: TypeOrmModule,
44 | imports: [TypeOrmCoreModule.forRootAsync(options)],
45 | };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/typeorm.providers.ts:
--------------------------------------------------------------------------------
1 | import { Provider } from '@nestjs/common';
2 | import { DataSource, DataSourceOptions, getMetadataArgsStorage } from 'typeorm';
3 | import { getDataSourceToken, getRepositoryToken } from './common/typeorm.utils';
4 | import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type';
5 |
6 | export function createTypeOrmProviders(
7 | entities?: EntityClassOrSchema[],
8 | dataSource?: DataSource | DataSourceOptions | string,
9 | ): Provider[] {
10 | return (entities || []).map((entity) => ({
11 | provide: getRepositoryToken(entity, dataSource),
12 | useFactory: (dataSource: DataSource) => {
13 | const entityMetadata = dataSource.entityMetadatas.find(
14 | (meta) => meta.target === entity,
15 | );
16 | const isTreeEntity = typeof entityMetadata?.treeType !== 'undefined';
17 | return isTreeEntity
18 | ? dataSource.getTreeRepository(entity)
19 | : dataSource.options.type === 'mongodb'
20 | ? dataSource.getMongoRepository(entity)
21 | : dataSource.getRepository(entity);
22 | },
23 | inject: [getDataSourceToken(dataSource)],
24 | /**
25 | * Extra property to workaround dynamic modules serialisation issue
26 | * that occurs when "TypeOrm#forFeature()" method is called with the same number
27 | * of arguments and all entities share the same class names.
28 | */
29 | targetEntitySchema: getMetadataArgsStorage().tables.find(
30 | (item) => item.target === entity,
31 | ),
32 | }));
33 | }
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nestjs/typeorm",
3 | "version": "11.0.0",
4 | "description": "Nest - modern, fast, powerful node.js web framework (@typeorm)",
5 | "author": "Kamil Mysliwiec",
6 | "license": "MIT",
7 | "url": "https://github.com/nestjs/typeorm#readme",
8 | "scripts": {
9 | "build": "rm -rf dist && tsc -p tsconfig.json",
10 | "format": "prettier --write \"**/*.ts\"",
11 | "lint": "eslint 'lib/**/*.ts' --fix",
12 | "prepublish:npm": "npm run build",
13 | "publish:npm": "npm publish --access public",
14 | "prepublish:next": "npm run build",
15 | "publish:next": "npm publish --access public --tag next",
16 | "test:e2e": "jest --config ./tests/jest-e2e.json --runInBand",
17 | "test:e2e:dev": "jest --config ./tests/jest-e2e.json --runInBand --watch",
18 | "prerelease": "npm run build",
19 | "release": "release-it",
20 | "prepare": "husky"
21 | },
22 | "devDependencies": {
23 | "@commitlint/cli": "19.8.1",
24 | "@commitlint/config-angular": "19.8.1",
25 | "@eslint/eslintrc": "3.3.1",
26 | "@eslint/js": "9.28.0",
27 | "@nestjs/common": "11.1.3",
28 | "@nestjs/core": "11.1.3",
29 | "@nestjs/platform-express": "11.1.3",
30 | "@nestjs/testing": "11.1.3",
31 | "@types/jest": "29.5.14",
32 | "@types/node": "22.15.30",
33 | "@types/supertest": "6.0.3",
34 | "eslint": "9.28.0",
35 | "eslint-config-prettier": "10.1.5",
36 | "eslint-plugin-prettier": "5.4.1",
37 | "globals": "16.2.0",
38 | "husky": "9.1.7",
39 | "jest": "29.7.0",
40 | "lint-staged": "16.1.0",
41 | "mysql": "2.18.1",
42 | "pg": "8.16.0",
43 | "prettier": "3.5.3",
44 | "reflect-metadata": "0.2.2",
45 | "release-it": "19.0.3",
46 | "rxjs": "7.8.2",
47 | "supertest": "7.1.1",
48 | "ts-jest": "29.3.4",
49 | "typeorm": "0.3.24",
50 | "typescript": "5.8.3",
51 | "typescript-eslint": "8.33.1"
52 | },
53 | "peerDependencies": {
54 | "@nestjs/common": "^10.0.0 || ^11.0.0",
55 | "@nestjs/core": "^10.0.0 || ^11.0.0",
56 | "reflect-metadata": "^0.1.13 || ^0.2.0",
57 | "rxjs": "^7.2.0",
58 | "typeorm": "^0.3.0"
59 | },
60 | "lint-staged": {
61 | "**/*.{ts,json}": []
62 | },
63 | "repository": {
64 | "type": "git",
65 | "url": "https://github.com/nestjs/typeorm"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "semanticCommits": true,
3 | "packageRules": [{
4 | "depTypeList": ["devDependencies"],
5 | "automerge": true
6 | }],
7 | "extends": [
8 | "config:base"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-async-class.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { AsyncOptionsClassModule } from '../src/async-class-options.module';
5 | import { Server } from 'http';
6 |
7 | describe('TypeOrm (async configuration)', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AsyncOptionsClassModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-async-connection-factory-options.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import { Server } from 'http';
4 | import * as request from 'supertest';
5 | import { AsyncConnectionFactoryOptionsFactoryModule } from '../src/async-connection-factory-options.module';
6 |
7 | describe('TypeOrm (async configuration with connectionFactory)', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AsyncConnectionFactoryOptionsFactoryModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-async-existing.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { AsyncOptionsExistingModule } from '../src/async-existing-options.module';
5 | import { Server } from 'http';
6 |
7 | describe('TypeOrm (async configuration)', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AsyncOptionsExistingModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-async-options.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { AsyncOptionsFactoryModule } from '../src/async-options.module';
5 | import { Server } from 'http';
6 |
7 | describe('TypeOrm (async configuration)', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AsyncOptionsFactoryModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-async.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { AsyncApplicationModule } from '../src/app-async.module';
5 | import { Server } from 'http';
6 |
7 | describe('TypeOrm (async configuration)', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AsyncApplicationModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm-schema.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import { Server } from 'http';
4 | import * as request from 'supertest';
5 | import { AppSchemaModule } from '../src/app-schema.module';
6 |
7 | describe('TypeOrm', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [AppSchemaModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/e2e/typeorm.spec.ts:
--------------------------------------------------------------------------------
1 | import { INestApplication } from '@nestjs/common';
2 | import { Test } from '@nestjs/testing';
3 | import * as request from 'supertest';
4 | import { ApplicationModule } from '../src/app.module';
5 | import { Server } from 'http';
6 |
7 | describe('TypeOrm', () => {
8 | let server: Server;
9 | let app: INestApplication;
10 |
11 | beforeEach(async () => {
12 | const module = await Test.createTestingModule({
13 | imports: [ApplicationModule],
14 | }).compile();
15 |
16 | app = module.createNestApplication();
17 | server = app.getHttpServer();
18 | await app.init();
19 | });
20 |
21 | it(`should return created entity`, () => {
22 | return request(server)
23 | .post('/photo')
24 | .expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
25 | });
26 |
27 | afterEach(async () => {
28 | await app.close();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/tests/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tests/src/app-async.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { DatabaseModule } from './database.module';
3 | import { PhotoModule } from './photo/photo.module';
4 |
5 | @Module({
6 | imports: [DatabaseModule.forRoot(), PhotoModule],
7 | })
8 | export class AsyncApplicationModule {}
9 |
--------------------------------------------------------------------------------
/tests/src/app-schema.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../lib';
3 | import { PhotoSchemaModule } from './photo/schema/photo-schema.module';
4 |
5 | @Module({
6 | imports: [
7 | TypeOrmModule.forRoot({
8 | type: 'postgres',
9 | host: '0.0.0.0',
10 | port: 3306,
11 | username: 'root',
12 | password: 'root',
13 | database: 'test',
14 | synchronize: true,
15 | autoLoadEntities: true,
16 | retryAttempts: 2,
17 | retryDelay: 1000,
18 | }),
19 | PhotoSchemaModule,
20 | TypeOrmModule.forRoot({
21 | name: 'connection_2',
22 | type: 'postgres',
23 | host: '0.0.0.0',
24 | port: 3306,
25 | username: 'root',
26 | password: 'root',
27 | database: 'test',
28 | synchronize: true,
29 | autoLoadEntities: true,
30 | retryAttempts: 2,
31 | retryDelay: 1000,
32 | }),
33 | ],
34 | })
35 | export class AppSchemaModule {}
36 |
--------------------------------------------------------------------------------
/tests/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../lib';
3 | import { Photo } from './photo/photo.entity';
4 | import { PhotoModule } from './photo/photo.module';
5 |
6 | @Module({
7 | imports: [
8 | TypeOrmModule.forRoot({
9 | type: 'postgres',
10 | host: '0.0.0.0',
11 | port: 3306,
12 | username: 'root',
13 | password: 'root',
14 | database: 'test',
15 | entities: [Photo],
16 | synchronize: true,
17 | autoLoadEntities: true,
18 | retryAttempts: 2,
19 | retryDelay: 1000,
20 | }),
21 | PhotoModule,
22 | TypeOrmModule.forRoot({
23 | name: 'connection_2',
24 | type: 'postgres',
25 | host: '0.0.0.0',
26 | port: 3306,
27 | username: 'root',
28 | password: 'root',
29 | database: 'test',
30 | entities: [Photo],
31 | synchronize: true,
32 | autoLoadEntities: true,
33 | retryAttempts: 2,
34 | retryDelay: 1000,
35 | }),
36 | ],
37 | })
38 | export class ApplicationModule {}
39 |
--------------------------------------------------------------------------------
/tests/src/async-class-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import {
3 | TypeOrmModule,
4 | TypeOrmModuleOptions,
5 | TypeOrmOptionsFactory,
6 | } from '../../lib';
7 | import { Photo } from './photo/photo.entity';
8 | import { PhotoModule } from './photo/photo.module';
9 |
10 | class ConfigService implements TypeOrmOptionsFactory {
11 | createTypeOrmOptions(): TypeOrmModuleOptions {
12 | return {
13 | type: 'postgres',
14 | host: '0.0.0.0',
15 | port: 3306,
16 | username: 'root',
17 | password: 'root',
18 | database: 'test',
19 | entities: [Photo],
20 | synchronize: true,
21 | retryAttempts: 2,
22 | retryDelay: 1000,
23 | };
24 | }
25 | }
26 |
27 | @Module({
28 | imports: [
29 | TypeOrmModule.forRootAsync({
30 | useClass: ConfigService,
31 | }),
32 | TypeOrmModule.forRoot({
33 | name: 'connection_2',
34 | type: 'postgres',
35 | host: '0.0.0.0',
36 | port: 3306,
37 | username: 'root',
38 | password: 'root',
39 | database: 'test',
40 | entities: [Photo],
41 | synchronize: true,
42 | retryAttempts: 2,
43 | retryDelay: 1000,
44 | }),
45 | PhotoModule,
46 | ],
47 | })
48 | export class AsyncOptionsClassModule {}
49 |
--------------------------------------------------------------------------------
/tests/src/async-connection-factory-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { DataSource } from 'typeorm';
3 | import { TypeOrmModule } from '../../lib';
4 | import { Photo } from './photo/photo.entity';
5 | import { PhotoModule } from './photo/photo.module';
6 |
7 | @Module({
8 | imports: [
9 | TypeOrmModule.forRootAsync({
10 | useFactory: () => ({
11 | type: 'postgres',
12 | host: '0.0.0.0',
13 | port: 3306,
14 | username: 'root',
15 | password: 'root',
16 | database: 'test',
17 | entities: [Photo],
18 | synchronize: true,
19 | retryAttempts: 2,
20 | retryDelay: 1000,
21 | }),
22 | dataSourceFactory: async (options) => {
23 | // Realistically, this function would be used for more than simply creating a connection,
24 | // i.e. checking for an existing and active connection prior to creating a new one.
25 | // However, including that logic here causes runtime test errors about variables being used before assignment.
26 | // Therefore, given the simple nature of this test case, simply create and return a connection.
27 | return new DataSource(options!);
28 | },
29 | }),
30 | TypeOrmModule.forRoot({
31 | name: 'connection_2',
32 | type: 'postgres',
33 | host: '0.0.0.0',
34 | port: 3306,
35 | username: 'root',
36 | password: 'root',
37 | database: 'test',
38 | entities: [Photo],
39 | synchronize: true,
40 | retryAttempts: 2,
41 | retryDelay: 1000,
42 | }),
43 | PhotoModule,
44 | ],
45 | })
46 | export class AsyncConnectionFactoryOptionsFactoryModule {}
47 |
--------------------------------------------------------------------------------
/tests/src/async-existing-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import {
3 | TypeOrmModule,
4 | TypeOrmModuleOptions,
5 | TypeOrmOptionsFactory,
6 | } from '../../lib';
7 | import { Photo } from './photo/photo.entity';
8 | import { PhotoModule } from './photo/photo.module';
9 |
10 | class ConfigService implements TypeOrmOptionsFactory {
11 | createTypeOrmOptions(): TypeOrmModuleOptions {
12 | return {
13 | type: 'postgres',
14 | host: '0.0.0.0',
15 | port: 3306,
16 | username: 'root',
17 | password: 'root',
18 | database: 'test',
19 | entities: [Photo],
20 | synchronize: true,
21 | retryAttempts: 2,
22 | retryDelay: 1000,
23 | };
24 | }
25 | }
26 |
27 | @Module({
28 | providers: [ConfigService],
29 | exports: [ConfigService],
30 | })
31 | class ConfigModule {}
32 |
33 | @Module({
34 | imports: [
35 | TypeOrmModule.forRootAsync({
36 | imports: [ConfigModule],
37 | useExisting: ConfigService,
38 | }),
39 | TypeOrmModule.forRoot({
40 | name: 'connection_2',
41 | type: 'postgres',
42 | host: '0.0.0.0',
43 | port: 3306,
44 | username: 'root',
45 | password: 'root',
46 | database: 'test',
47 | entities: [],
48 | autoLoadEntities: true,
49 | synchronize: true,
50 | retryAttempts: 2,
51 | retryDelay: 1000,
52 | }),
53 | PhotoModule,
54 | ],
55 | })
56 | export class AsyncOptionsExistingModule {}
57 |
--------------------------------------------------------------------------------
/tests/src/async-options.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../lib';
3 | import { Photo } from './photo/photo.entity';
4 | import { PhotoModule } from './photo/photo.module';
5 |
6 | @Module({
7 | imports: [
8 | TypeOrmModule.forRootAsync({
9 | useFactory: () => ({
10 | type: 'postgres',
11 | host: '0.0.0.0',
12 | port: 3306,
13 | username: 'root',
14 | password: 'root',
15 | database: 'test',
16 | entities: [Photo],
17 | synchronize: true,
18 | retryAttempts: 2,
19 | retryDelay: 1000,
20 | }),
21 | }),
22 | TypeOrmModule.forRoot({
23 | name: 'connection_2',
24 | type: 'postgres',
25 | host: '0.0.0.0',
26 | port: 3306,
27 | username: 'root',
28 | password: 'root',
29 | database: 'test',
30 | entities: [Photo],
31 | synchronize: true,
32 | retryAttempts: 2,
33 | retryDelay: 1000,
34 | }),
35 | PhotoModule,
36 | ],
37 | })
38 | export class AsyncOptionsFactoryModule {}
39 |
--------------------------------------------------------------------------------
/tests/src/database.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../lib';
3 | import { Photo } from './photo/photo.entity';
4 |
5 | @Module({})
6 | export class DatabaseModule {
7 | static async forRoot(): Promise {
8 | await new Promise((resolve) => setTimeout(resolve, 1000));
9 | return {
10 | module: DatabaseModule,
11 | imports: [
12 | TypeOrmModule.forRoot({
13 | type: 'postgres',
14 | host: '0.0.0.0',
15 | port: 3306,
16 | username: 'root',
17 | password: 'root',
18 | database: 'test',
19 | entities: [Photo],
20 | synchronize: true,
21 | retryAttempts: 2,
22 | retryDelay: 1000,
23 | }),
24 | TypeOrmModule.forRoot({
25 | name: 'connection_2',
26 | type: 'postgres',
27 | host: '0.0.0.0',
28 | port: 3306,
29 | username: 'root',
30 | password: 'root',
31 | database: 'test',
32 | entities: [Photo],
33 | synchronize: true,
34 | retryAttempts: 2,
35 | retryDelay: 1000,
36 | }),
37 | ],
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { ApplicationModule } from './app.module';
3 |
4 | async function bootstrap() {
5 | const app = await NestFactory.create(ApplicationModule);
6 | await app.listen(3001);
7 | }
8 | bootstrap();
9 |
--------------------------------------------------------------------------------
/tests/src/photo/photo.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Post } from '@nestjs/common';
2 | import { Photo } from './photo.entity';
3 | import { PhotoService } from './photo.service';
4 |
5 | @Controller('photo')
6 | export class PhotoController {
7 | constructor(private readonly photoService: PhotoService) {}
8 |
9 | @Get()
10 | findAll(): Promise {
11 | return this.photoService.findAll();
12 | }
13 |
14 | @Post()
15 | create(): Promise {
16 | return this.photoService.create();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/src/photo/photo.entity.ts:
--------------------------------------------------------------------------------
1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
2 |
3 | @Entity()
4 | export class Photo {
5 | @PrimaryGeneratedColumn() id!: number;
6 |
7 | @Column({ length: 500 })
8 | name!: string;
9 |
10 | @Column('text') description!: string;
11 |
12 | @Column() filename!: string;
13 |
14 | @Column('int') views!: number;
15 |
16 | @Column() isPublished!: boolean;
17 | }
18 |
--------------------------------------------------------------------------------
/tests/src/photo/photo.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../../lib';
3 | import { PhotoController } from './photo.controller';
4 | import { Photo } from './photo.entity';
5 | import { CustomPhotoRepository } from './photo.repository';
6 | import { PhotoService } from './photo.service';
7 |
8 | @Module({
9 | imports: [
10 | TypeOrmModule.forFeature([Photo, CustomPhotoRepository]),
11 | TypeOrmModule.forFeature([Photo, CustomPhotoRepository], 'connection_2'),
12 | ],
13 | providers: [PhotoService],
14 | controllers: [PhotoController],
15 | })
16 | export class PhotoModule {}
17 |
--------------------------------------------------------------------------------
/tests/src/photo/photo.repository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import { Photo } from './photo.entity';
3 |
4 | @EntityRepository(Photo)
5 | export class CustomPhotoRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/tests/src/photo/photo.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { Repository } from 'typeorm';
3 | import { InjectRepository } from '../../../lib';
4 | import { Photo } from './photo.entity';
5 | import { CustomPhotoRepository } from './photo.repository';
6 |
7 | @Injectable()
8 | export class PhotoService {
9 | constructor(
10 | @InjectRepository(Photo, 'connection_2')
11 | private readonly photoRepository2: Repository,
12 | @InjectRepository(Photo)
13 | private readonly photoRepository: Repository,
14 | @InjectRepository(CustomPhotoRepository, 'connection_2')
15 | private readonly customPhotoRepository2: CustomPhotoRepository,
16 | private readonly customPhotoRepository: CustomPhotoRepository,
17 | ) {}
18 |
19 | async findAll(): Promise {
20 | return await this.photoRepository.find();
21 | }
22 |
23 | async create(): Promise {
24 | const photoEntity = new Photo();
25 | photoEntity.name = 'Nest';
26 | photoEntity.description = 'Is great!';
27 | photoEntity.views = 6000;
28 |
29 | return await this.photoRepository.create(photoEntity);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo-schema.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Post } from '@nestjs/common';
2 | import { Photo } from '../photo.entity';
3 | import { PhotoSchemaService } from './photo-schema.service';
4 |
5 | @Controller('photo')
6 | export class PhotoSchemaController {
7 | constructor(private readonly photoService: PhotoSchemaService) {}
8 |
9 | @Get()
10 | findAll(): Promise {
11 | return this.photoService.findAll();
12 | }
13 |
14 | @Post()
15 | create(): Promise {
16 | return this.photoService.create();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo-schema.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '../../../../lib';
3 | import { PhotoSchemaController } from './photo-schema.controller';
4 | import { CustomPhotoSchemaRepository } from './photo-schema.repository';
5 | import { PhotoSchemaService } from './photo-schema.service';
6 | import { PhotoWithoutTargetSchema } from './photo-without-target.schema';
7 | import { PhotoSchema } from './photo.schema';
8 |
9 | @Module({
10 | imports: [
11 | TypeOrmModule.forFeature([PhotoWithoutTargetSchema]),
12 | TypeOrmModule.forFeature([PhotoSchema, CustomPhotoSchemaRepository]),
13 | TypeOrmModule.forFeature(
14 | [PhotoSchema, CustomPhotoSchemaRepository],
15 | 'connection_2',
16 | ),
17 | ],
18 | providers: [PhotoSchemaService],
19 | controllers: [PhotoSchemaController],
20 | })
21 | export class PhotoSchemaModule {}
22 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo-schema.repository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import { Photo } from '../photo.entity';
3 | import { PhotoSchema } from './photo.schema';
4 |
5 | @EntityRepository(PhotoSchema)
6 | export class CustomPhotoSchemaRepository extends Repository {}
7 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo-schema.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { Repository } from 'typeorm';
3 | import { InjectRepository } from '../../../../lib';
4 | import { Photo } from '../photo.entity';
5 | import { CustomPhotoSchemaRepository } from './photo-schema.repository';
6 | import { PhotoWithoutTargetSchema } from './photo-without-target.schema';
7 | import { PhotoSchema } from './photo.schema';
8 |
9 | @Injectable()
10 | export class PhotoSchemaService {
11 | constructor(
12 | @InjectRepository(PhotoWithoutTargetSchema)
13 | private readonly photoWithoutTargetRepository: Repository,
14 | @InjectRepository(PhotoSchema, 'connection_2')
15 | private readonly photoRepository2: Repository,
16 | @InjectRepository(PhotoSchema)
17 | private readonly photoRepository: Repository,
18 | @InjectRepository(CustomPhotoSchemaRepository, 'connection_2')
19 | private readonly customPhotoRepository2: CustomPhotoSchemaRepository,
20 | private readonly customPhotoRepository: CustomPhotoSchemaRepository,
21 | ) {}
22 |
23 | async findAll(): Promise {
24 | return await this.photoRepository.find();
25 | }
26 |
27 | async create(): Promise {
28 | const photoEntity = new Photo();
29 | photoEntity.name = 'Nest';
30 | photoEntity.description = 'Is great!';
31 | photoEntity.views = 6000;
32 |
33 | return await this.photoRepository.create(photoEntity);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo-without-target.schema.ts:
--------------------------------------------------------------------------------
1 | import { EntitySchema } from 'typeorm';
2 | import { Photo } from '../photo.entity';
3 |
4 | export const PhotoWithoutTargetSchema = new EntitySchema({
5 | name: 'photo-without-target',
6 | columns: {
7 | id: {
8 | type: Number,
9 | generated: true,
10 | primary: true,
11 | },
12 | name: {
13 | type: String,
14 | },
15 | description: {
16 | type: String,
17 | },
18 | filename: {
19 | type: String,
20 | },
21 | views: {
22 | type: Number,
23 | },
24 | isPublished: {
25 | type: Boolean,
26 | },
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/tests/src/photo/schema/photo.schema.ts:
--------------------------------------------------------------------------------
1 | import { EntitySchema } from 'typeorm';
2 | import { Photo } from '../photo.entity';
3 |
4 | export const PhotoSchema = new EntitySchema({
5 | name: 'Photo',
6 | target: Photo,
7 | columns: {
8 | id: {
9 | type: Number,
10 | generated: true,
11 | primary: true,
12 | },
13 | name: {
14 | type: String,
15 | },
16 | description: {
17 | type: String,
18 | },
19 | filename: {
20 | type: String,
21 | },
22 | views: {
23 | type: Number,
24 | },
25 | isPublished: {
26 | type: Boolean,
27 | },
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/tests/wait-for-sql.sh:
--------------------------------------------------------------------------------
1 | until nc -z -v -w30 localhost 3306
2 | do
3 | echo "Waiting for database connection..."
4 | # wait for 5 seconds before check again
5 | sleep 5
6 | done
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "strict": true,
6 | "removeComments": false,
7 | "noLib": false,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "ES2021",
11 | "sourceMap": false,
12 | "outDir": "./dist",
13 | "rootDir": "./lib",
14 | "skipLibCheck": true,
15 | "useUnknownInCatchVariables": false
16 | },
17 | "include": [
18 | "lib/**/*"
19 | ],
20 | "exclude": [
21 | "node_modules",
22 | "**/*.spec.ts",
23 | "tests",
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------