From 574c5316f04930b18c3fd6fb8a00acc08b8fae0e Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:59:24 +0100 Subject: [PATCH] feat(server): scaffold base query-cache module behind feature flag --- apps/server/src/core/base/base.module.ts | 6 +++- .../query-cache/base-query-cache.service.ts | 28 +++++++++++++++++++ .../base/query-cache/base-query-router.ts | 16 +++++++++++ .../base/query-cache/query-cache.config.ts | 22 +++++++++++++++ .../base/query-cache/query-cache.module.ts | 10 +++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/core/base/query-cache/base-query-cache.service.ts create mode 100644 apps/server/src/core/base/query-cache/base-query-router.ts create mode 100644 apps/server/src/core/base/query-cache/query-cache.config.ts create mode 100644 apps/server/src/core/base/query-cache/query-cache.module.ts diff --git a/apps/server/src/core/base/base.module.ts b/apps/server/src/core/base/base.module.ts index 12d37271..0dcc2ba3 100644 --- a/apps/server/src/core/base/base.module.ts +++ b/apps/server/src/core/base/base.module.ts @@ -14,9 +14,13 @@ import { BaseWsService } from './realtime/base-ws.service'; import { BaseWsConsumers } from './realtime/base-ws-consumers'; import { BasePresenceService } from './realtime/base-presence.service'; import { QueueName } from '../../integrations/queue/constants'; +import { BaseQueryCacheModule } from './query-cache/query-cache.module'; @Module({ - imports: [BullModule.registerQueue({ name: QueueName.BASE_QUEUE })], + imports: [ + BullModule.registerQueue({ name: QueueName.BASE_QUEUE }), + BaseQueryCacheModule, + ], controllers: [ BaseController, BasePropertyController, diff --git a/apps/server/src/core/base/query-cache/base-query-cache.service.ts b/apps/server/src/core/base/query-cache/base-query-cache.service.ts new file mode 100644 index 00000000..211210bd --- /dev/null +++ b/apps/server/src/core/base/query-cache/base-query-cache.service.ts @@ -0,0 +1,28 @@ +import { + Injectable, + Logger, + OnApplicationBootstrap, + OnModuleDestroy, +} from '@nestjs/common'; +import { QueryCacheConfigProvider } from './query-cache.config'; + +@Injectable() +export class BaseQueryCacheService + implements OnApplicationBootstrap, OnModuleDestroy +{ + private readonly logger = new Logger(BaseQueryCacheService.name); + + constructor(private readonly configProvider: QueryCacheConfigProvider) {} + + async onApplicationBootstrap(): Promise { + const { enabled } = this.configProvider.config; + this.logger.log( + `BaseQueryCacheService bootstrapped (enabled=${enabled}).`, + ); + // Real warm-up is added in task 9. + } + + async onModuleDestroy(): Promise { + // Real cleanup is added in task 5. + } +} diff --git a/apps/server/src/core/base/query-cache/base-query-router.ts b/apps/server/src/core/base/query-cache/base-query-router.ts new file mode 100644 index 00000000..c7cca3fd --- /dev/null +++ b/apps/server/src/core/base/query-cache/base-query-router.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { QueryCacheConfigProvider } from './query-cache.config'; + +export type RouteDecision = 'postgres' | 'cache'; + +@Injectable() +export class BaseQueryRouter { + constructor(private readonly configProvider: QueryCacheConfigProvider) {} + + // Stubbed: routes always to postgres in this commit so the existing + // behavior is preserved. Real decision logic is added in task 6. + decide(_args: unknown): RouteDecision { + if (!this.configProvider.config.enabled) return 'postgres'; + return 'postgres'; + } +} diff --git a/apps/server/src/core/base/query-cache/query-cache.config.ts b/apps/server/src/core/base/query-cache/query-cache.config.ts new file mode 100644 index 00000000..4b4fcdf1 --- /dev/null +++ b/apps/server/src/core/base/query-cache/query-cache.config.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { EnvironmentService } from '../../../integrations/environment/environment.service'; + +export type QueryCacheConfig = { + enabled: boolean; + minRows: number; + maxCollections: number; + warmTopN: number; +}; + +@Injectable() +export class QueryCacheConfigProvider { + readonly config: QueryCacheConfig; + constructor(env: EnvironmentService) { + this.config = { + enabled: env.getBaseQueryCacheEnabled(), + minRows: env.getBaseQueryCacheMinRows(), + maxCollections: env.getBaseQueryCacheMaxCollections(), + warmTopN: env.getBaseQueryCacheWarmTopN(), + }; + } +} diff --git a/apps/server/src/core/base/query-cache/query-cache.module.ts b/apps/server/src/core/base/query-cache/query-cache.module.ts new file mode 100644 index 00000000..fc67b4fb --- /dev/null +++ b/apps/server/src/core/base/query-cache/query-cache.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { QueryCacheConfigProvider } from './query-cache.config'; +import { BaseQueryCacheService } from './base-query-cache.service'; +import { BaseQueryRouter } from './base-query-router'; + +@Module({ + providers: [QueryCacheConfigProvider, BaseQueryCacheService, BaseQueryRouter], + exports: [BaseQueryCacheService, BaseQueryRouter, QueryCacheConfigProvider], +}) +export class BaseQueryCacheModule {}