Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy provider issues with tenant connections #208

Open
knovu opened this issue Feb 4, 2025 · 4 comments
Open

Proxy provider issues with tenant connections #208

knovu opened this issue Feb 4, 2025 · 4 comments
Labels
investigation Why is this happening? needs info Waiting for more information from the OP

Comments

@knovu
Copy link

knovu commented Feb 4, 2025

Problem statement:
I want to allow the ability to dynamically connect to databases with my Nest.js API based on the user's assigned organization id (tenant id). We currently use this package globally in root to generate request id's.

The request object from CLS_REQ is returning undefined

@Module({
    imports: [
        ConfigModule.forRoot({
            isGlobal: true,
            load: [config],
        }),

    // CLS Module for async storage in context, helps with request id generation & storage
        // See: https://papooch.github.io/nestjs-cls/introduction/how-it-works
        ClsModule.forRootAsync({
            global: true,
            useFactory: () => ({
                middleware: {
                    mount: true,
                    generateId: true,

                    // Generates the requestId as a UUID string type
                    idGenerator: () => uuidv4(),
                    saveReq: true,
                },
            }),
        }),


       // <-- THIS IS CAUSING ISSUES, CLS_REQ imports causes node.js to throw a TypeError somewhere internally? -->
           ClsModule.forFeatureAsync({
            provide: TENANT_CONNECTION,
            inject: [CLS_REQ], // <-- this is causing issues
            useFactory: async (req: Request) => {
                console.log(req);  // <-- Logs undefined
            },

            // make the TENANT_CONNECTION available for injection globally
            global: true,
        }),
    ],
})
export class AppModule implements NestModule {
// Other implementations
}
@knovu
Copy link
Author

knovu commented Feb 4, 2025

Resolved by manually hard setting the CLS_REQ value in the storage (saveReq option was not working as intended):

// CLS Module for async storage in context, helps with request id generation & storage
        // See: https://papooch.github.io/nestjs-cls/introduction/how-it-works
        ClsModule.forRootAsync({
            global: true,
            useFactory: () => ({
                middleware: {
                    mount: true,
                    generateId: true,

                    // Generates the requestId as a UUID string type
                    idGenerator: () => uuidv4(),
                    setup: (cls, req) => {
                        cls.set('req', req);
                    },
                },
            }),
        }),

        ClsModule.forFeatureAsync({
            global: true,
            provide: TENANT_CONNECTION,
            imports: [TenantModule],
            inject: [ClsService, MultiTenantService],
            useFactory: async (
                cls: ClsService,
                service: MultiTenantService,
            ): Promise<DataSource | undefined> => {
                const request = cls.get<Request>('req');
                const user = request.user;

                if (user) {
                    const connection = await service.createConnection(user.tenantId);
                    return connection;
                }

                return undefined;
            },
        }),

@Papooch
Copy link
Owner

Papooch commented Feb 5, 2025

Thank you for the bug report ad the code snippets. I'll investigate this.

@Papooch Papooch added bug Something isn't working investigation Why is this happening? labels Feb 5, 2025
@knovu
Copy link
Author

knovu commented Feb 5, 2025

Thank you for the bug report ad the code snippets. I'll investigate this.

Keep in mind, I'm trying to generate RequestIds and create a proxy provider bother at root... I see in the forFeatureAsync notes, (creates CLS service), so IDK if maybe its duplicating the efforts as the forRootAsync function as well.

@Papooch
Copy link
Owner

Papooch commented Feb 8, 2025

Hi, @knovu, I tried to test the issue locally with the setup you provided, but I couldn't reproduce the behavior that you describe.

The Proxy Provider CLS_REQ was only undefined in the factory, when I explicitly set saveReq to false (it is true by default).

This is the test code that I tried (based on the original post):

@Controller()
class TestController {
    constructor(
        @Inject(PROXY_RESPONSE)
        private readonly proxyResponse: any,
    ) {}

    @Post('/hello')
    async getHello(): Promise<void> {
        return this.proxyResponse;
    }
}

@Module({
    imports: [
        ClsModule.forRootAsync({
            global: true,
            useFactory: () => ({
                middleware: {
                    mount: true,
                    generateId: true,
                    idGenerator: () => Math.random().toString(),
                    // saveReq: true,
                },
            }),
        }),
        ClsModule.forFeatureAsync({
            provide: PROXY_RESPONSE,
            inject: [CLS_REQ],
            useFactory: async (req: Request) => {
                return {
                    response: req.body.request,
                };
            },

            // make the TENANT_CONNECTION available for injection globally
            global: true,
        }),
    ],
    controllers: [TestController],
})
class TestModule {}

describe('Using CLS_REQ Proxy provider', () => {
    let app: INestApplication;

    beforeAll(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [TestModule],
        }).compile();
        app = moduleFixture.createNestApplication();
        await app.init();
        return app;
    });

    afterAll(async () => {
        await app.close();
    });

    it('should work', async () => {
        await request(app.getHttpServer())
            .post('/hello')
            .send({ request: 'testing value' })
            .expect(201)
            .expect({ response: 'testing value' });
    });
});

It works as intended on both version 4.x and 5.x.


That said, the issues you describe - and the reason why it works if you manually save the request using a string key might indicate a problem with your package setup.

  • Are you possibly linking the package locally (via the link: protocol), or in a monorepo?
  • Is it possible that there are two copies of the nestjs-cls dependency? (npm ls -r nestjs-cls)

The CLS_REQ constant is a Symbol, so if there are multiple copies of nestjs-cls library, it is possible that each reference points to a different symbol. This issue is not present for your manual string key.

It's the same root cause as this dependency error in the NestJS FAQ.

@Papooch Papooch added needs info Waiting for more information from the OP and removed bug Something isn't working labels Feb 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigation Why is this happening? needs info Waiting for more information from the OP
Projects
None yet
Development

No branches or pull requests

2 participants