Skip to content

Regression: Tenant connection mix-up after upgrading from 2.5.1 to 2.6.0 #404

@KamalAman

Description

@KamalAman

I’m using a tenant-per-database setup and durable providers for TENANT_KYSELY_CONNECTION.

After upgrading @nestjs-cls/transactional from 2.5.1 to 2.6.0, I noticed an issue:
When making requests in the following sequence — Tenant A → Tenant B → Tenant A —
the second request for Tenant A incorrectly returns Tenant B’s connection (and data) 😅

Here’s the comparison between the versions:
@nestjs-cls/[email protected]...@nestjs-cls/[email protected]

I’ll try to create a reproducible example soon, but I’m not doing anything unusual.
Downgrading back to 2.5.1 resolves the issue.

A sample outline of the module. Everything in the registry is correct, it just the front txHost connection a service that uses it.

@Global()
@Module({
  imports: [ClsModule],
  providers: [TenantKyselyRegistry],
  exports: [TenantKyselyRegistry],
})
export class TenantKyselyModule {
  public static register(): DynamicModule {
    return {
      module: TenantKyselyModule,
      imports: [
        RequestConnectionModule,
        VaultModule,
        ConfigModule,
        ClsModule.forRoot({
          plugins: [
            new ClsPluginTransactional({
              imports: [],
              adapter: new TransactionalAdapterKysely({
                kyselyInstanceToken: TENANT_KYSELY_CONNECTION,
              }),
            }),
          ],
        }),
      ],
      providers: [
        {
          provide: TENANT_KYSELY_CONNECTION,
          scope: Scope.REQUEST,
          durable: true,
          inject: [
            REQUEST,
            TenantKyselyRegistry,
            ConfigService,
          ],
          useFactory: (  request) => {
              const tenantId = request.headers['tenant-id']
               const { db } = registry.getOrCreate(tenantId, { database: tenantId , ....});
               return db;
           },
        },
      ],
      exports: [TENANT_KYSELY_CONNECTION],
    };
  }
}

@Injectable()
export class TenantKyselyRegistry implements OnApplicationShutdown {
  private readonly registry = new Map<string, PoolEntry>();
  private readonly logger = new Logger('TenantKyselyRegistry');

  getOrCreate(tenantId: string, poolConfig: PoolConfig): PoolEntry {
    const key = `${tenantId}`;
    const existing = this.registry.get(key);
    if (existing) return existing;

    const pool = new Pool(poolConfig);
    const db = new Kysely<unknown>({
      dialect: new PostgresDialect({ pool }),
    });
    const entry = { pool, db } as const;
    this.registry.set(key, entry);
    return entry;
  }

  async onApplicationShutdown() {
    const entries = Array.from(this.registry.values());
    await Promise.allSettled(entries.map((e) => e.pool.end()));
    this.registry.clear();
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    investigationWhy is this happening?solvedThe question was answered or the problem was solved

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions