I have created a durable provider to return a db connection for a given tenant. The provider will also keep a local cache of connections to reuse. As request-scoped providers cannot tap into application lifecycle events, I don’t have a way to trigger closing the connections when app.close()
is triggered. In my example below, onModuleDestroy
will never be called, which results in Jest warnings like
Jest did not exit one second after the test run has completed.
Would love to here how others have dealt with this.
Example of the TenancyService below:
import { Inject, Injectable, OnModuleDestroy, Scope } from '@nestjs/common'
import { REQUEST } from '@nestjs/core'
import { DataSource, DataSourceOptions } from 'typeorm'
import { tenantedConfig } from '@/orm.config'
import { TenantService } from '@/modules/public/tenants/tenant.service'
interface ContextPayload {
tenantId?: string
}
@Injectable({ scope: Scope.REQUEST, durable: true })
export class TenancyService implements OnModuleDestroy {
private tenantConnections: { [schemaName: string]: DataSource } = {}
private readonly tenantId: string
constructor(
private readonly tenantService: TenantService,
@Inject(REQUEST) private readonly requestContext: ContextPayload
) {
console.info(this.requestContext)
this.tenantId = this.requestContext.tenantId
}
async getDataSource(): Promise<DataSource> {
if (this.tenantConnections[this.tenantId] == null) {
const { schema } = await this.tenantService.findOneByTenant(this.tenantId)
const dataSource = new DataSource({
...tenantedConfig,
schema
} as DataSourceOptions)
try {
await dataSource.initialize()
} catch (error: unknown) {
console.error('Failed to initialize tenant connection', error)
throw error
}
this.tenantConnections[this.tenantId] = dataSource
}
return this.tenantConnections[this.tenantId]
}
async onModuleDestroy() {
await Promise.all(
Object.values(this.tenantConnections).map((dataSource) =>
dataSource.destroy()
)
)
console.debug('Tenant connections destroyed')
}
}