From c31eb85f1749582e3cca7112f590b2efe2bf0708 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 21 Feb 2026 08:31:46 -0800 Subject: [PATCH] fix: use singleton PostHog client instead of per-request instantiation A new PostHog client (with its own connection pool and flush interval) was created for every request. Moved to a module-level lazy singleton. Co-authored-by: Cursor --- .../middleware/legacy/mirror.middleware.ts | 18 +++-- .../middleware/legacy/mirror.posthog.test.ts | 68 +++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 tests/unit/middleware/legacy/mirror.posthog.test.ts diff --git a/apps/backend/middleware/legacy/mirror.middleware.ts b/apps/backend/middleware/legacy/mirror.middleware.ts index f126f1a..415cab5 100644 --- a/apps/backend/middleware/legacy/mirror.middleware.ts +++ b/apps/backend/middleware/legacy/mirror.middleware.ts @@ -3,6 +3,16 @@ import { MirrorCommandQueue } from './commandqueue.mirror'; import { PostHog } from 'posthog-node'; +let postHogClient: PostHog | null = null; +function getPostHogClient(): PostHog { + if (!postHogClient) { + postHogClient = new PostHog(process.env.POSTHOG_API_KEY ?? '', { + host: 'https://us.i.posthog.com', + }); + } + return postHogClient; +} + export const createBackendMirrorMiddleware = (createCommand: (req: Request, data: T) => Promise) => async (req: Request, res: Response, next: NextFunction) => { @@ -12,9 +22,7 @@ export const createBackendMirrorMiddleware = res.once('finish', () => { void (async () => { try { - const postHogClient = new PostHog(process.env.POSTHOG_API_KEY ?? '', { - host: 'https://us.i.posthog.com', - }); + const client = getPostHogClient(); console.log('Response finished, checking for mirror work...'); const ok = res.statusCode >= 200 && res.statusCode < 305; @@ -23,7 +31,7 @@ export const createBackendMirrorMiddleware = console.log('Response status:', res.statusCode, 'ok?', ok); const distinctId = (req as any).user?.id ?? req.ip ?? 'anonymous'; - let mirrorOn = await postHogClient.isFeatureEnabled('backend-mirror', distinctId); + let mirrorOn = await client.isFeatureEnabled('backend-mirror', distinctId); mirrorOn ??= false; if (!ok || data == null || !mirrorOn) return; @@ -32,8 +40,6 @@ export const createBackendMirrorMiddleware = const queue = MirrorCommandQueue.instance(); queue.enqueue(await createCommand(req, data)); - - await postHogClient.shutdown(); } catch (e) { console.error('Error in mirror middleware:', e); } diff --git a/tests/unit/middleware/legacy/mirror.posthog.test.ts b/tests/unit/middleware/legacy/mirror.posthog.test.ts new file mode 100644 index 0000000..f8b074e --- /dev/null +++ b/tests/unit/middleware/legacy/mirror.posthog.test.ts @@ -0,0 +1,68 @@ +const mockPostHogInstance = { + isFeatureEnabled: jest.fn().mockResolvedValue(true), + shutdown: jest.fn().mockResolvedValue(undefined), +}; + +const PostHogConstructor = jest.fn(() => mockPostHogInstance); + +jest.mock('posthog-node', () => ({ + PostHog: PostHogConstructor, +})); + +jest.mock('../../../../apps/backend/middleware/legacy/commandqueue.mirror', () => ({ + MirrorCommandQueue: { + instance: jest.fn(() => ({ + enqueue: jest.fn(), + })), + }, +})); + +import { createBackendMirrorMiddleware } from '../../../../apps/backend/middleware/legacy/mirror.middleware'; +import { Request, Response } from 'express'; +import { EventEmitter } from 'events'; + +function createMockReqRes() { + const req = { user: { id: 'user-1' }, ip: '127.0.0.1' } as unknown as Request; + + const res = new EventEmitter() as Response & EventEmitter; + res.statusCode = 200; + (res as any).locals = {}; + res.getHeader = jest.fn().mockReturnValue('application/json'); + const origSend = jest.fn().mockReturnThis(); + res.send = origSend; + + return { req, res }; +} + +describe('PostHog client instantiation', () => { + beforeEach(() => { + PostHogConstructor.mockClear(); + mockPostHogInstance.isFeatureEnabled.mockClear(); + mockPostHogInstance.shutdown.mockClear(); + }); + + it('creates PostHog at most once across multiple requests', async () => { + const createCommand = jest.fn().mockResolvedValue(['SQL1']); + const middleware = createBackendMirrorMiddleware(createCommand); + + const { req: req1, res: res1 } = createMockReqRes(); + const { req: req2, res: res2 } = createMockReqRes(); + const next = jest.fn(); + + await middleware(req1, res1, next); + await middleware(req2, res2, next); + + // Send responses to populate mirrorData + res1.send(JSON.stringify({ ok: true })); + res2.send(JSON.stringify({ ok: true })); + + // Trigger finish events + res1.emit('finish'); + res2.emit('finish'); + + // Allow async callbacks to settle + await new Promise((r) => setTimeout(r, 50)); + + expect(PostHogConstructor).toHaveBeenCalledTimes(1); + }); +});