Demo

Caching

Declare one or more caches with @Cache on the root module. Inject them with @CacheClient by owner plugin (and optional name) — including caches owned by other plugins. Use route- and method-level helpers to read/invalidate those same named clients.

Stack

  • @Cache on the root @Module — in-memory or Redis; one plugin may declare multiple named caches
  • @CacheClient(plugin, name?) — inject an ICache from this plugin or another
  • @CacheRoute / @DeleteCacheRoute — HTTP response cache keyed by plugin + name
  • @CacheFn / @DeleteCacheFn — method result cache keyed by plugin + name
  • Cross-plugin reuse — consumer plugins inject the owner’s cache; they do not re-declare @Cache for the same client

@Cache on the root module

@Cache must sit on the @Module class — not on services or controllers. Pass a unique name when the plugin opens more than one cache. Multiple @Cache decorators on the same module class are allowed.

  • type — "in-memory" or Redis options (host/port/password from env)
  • name — optional client id (required when one plugin declares multiple @Cache entries)
  • checkperiod / ttl / other options — follow the shared-backend-core Cache options for that type
  • Owner is the loading plugin — consumers address it by that plugin’s metadata.name (or CacheManager.DEFAULT_PLUGIN for core)
TSXmultiple caches in one plugin
1import { Cache, Module } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3 4@Cache({ 5 name: "publisher", 6 type: "in-memory", 7 checkperiod: 1000, 8}) 9@Cache({ 10 name: "session", 11 type: "redis", 12 host: process.env.REDIS_HOST, 13 port: Number(process.env.REDIS_PORT ?? 6379), 14 password: process.env.REDIS_PASSWORD, 15}) 16@Module({ 17 name: metadata.name, 18 providers: [...], 19 controllers: [...], 20}) 21export class MyPluginModule {}

@CacheClient

Inject an ICache on a service property. The first argument is the plugin that owns the cache; the second is the optional connection name when that plugin declared more than one @Cache.

  • CacheManager.DEFAULT_PLUGIN — core / shared default cache owner
  • metadata.name — this plugin’s own cache (add name when it has several)
  • "other-plugin" — a cache created by another plugin (same process / registry)
  • Second arg name — named client (e.g. "publisher", "session")
TSXinject
1import { 2 CacheClient, 3 CacheManager, 4 Service, 5} from "@quan-erp/shared-backend-core"; 6import type { ICache } from "@quan-erp/shared-backend-core"; 7import metadata from "../../module.metadata.json" with { type: "json" }; 8 9@Service() 10export class PublisherService { 11 // Core / default owner 12 @CacheClient(CacheManager.DEFAULT_PLUGIN, "publisher") 13 private defaultPublisher: ICache; 14 15 // This plugin’s named cache 16 @CacheClient(metadata.name, "session") 17 private session: ICache; 18 19 // Cache owned by another plugin 20 @CacheClient("warehouse", "replica") 21 private warehouseReplica: ICache; 22}

Use another plugin’s cache

Caches live in a process-wide registry keyed by owner plugin (+ optional name). A consumer does not re-declare @Cache for that client — it injects the owner’s cache and points route/method helpers at the same plugin + name.

  • Owner plugin — declares @Cache({ name, … }) and boots first (list it in pluginDependencies)
  • Consumer plugin — @CacheClient("owner-plugin", "cache-name")
  • Route / method helpers — set plugin: "owner-plugin" and name: "cache-name"
  • Do not hard-code a second identical @Cache in every consumer — reuse the owner’s client
  • Ensure the owner plugin is installed and loaded before consumers that inject its ICache
TSXconsumer service
1import { CacheClient, Service } from "@quan-erp/shared-backend-core"; 2import type { ICache } from "@quan-erp/shared-backend-core"; 3 4@Service() 5export class ReportService { 6 // "warehouse" plugin declared @Cache({ name: "replica", ... }) 7 @CacheClient("warehouse", "replica") 8 private warehouseReplica: ICache; 9}

Route-level

  • @CacheRoute({ key, plugin, name }) — key may be string or (req) => string
  • @DeleteCacheRoute({ key, plugin, name }) — callback key must return string[]
  • plugin + name must match the owning @Cache (including another plugin’s client)
TSXroute cache
1import { 2 CacheRoute, 3 Controller, 4 DeleteCacheRoute, 5 Get, 6 Post, 7} from "@quan-erp/shared-backend-core"; 8 9@Controller("/branch") 10export class BranchController { 11 @Get("/") 12 @CacheRoute({ 13 key: (req) => `branch-${req.query.skip}-${req.query.limit}`, 14 plugin: "warehouse", 15 name: "replica", 16 }) 17 async list() { /* … */ } 18 19 @Post("/") 20 @DeleteCacheRoute({ 21 key: (req) => [`branch-${(req as any).user.payload.id}`], 22 plugin: "warehouse", 23 name: "replica", 24 }) 25 async create() { /* … */ } 26}

Method-level

  • @CacheFn({ key, plugin, name }) — key is (...args) => string
  • @DeleteCacheFn({ key, plugin, name }) — key returns string[] (wildcards allowed)
  • Same plugin + name rules as @CacheClient / route helpers
TSXmethod cache
1import { 2 CacheFn, 3 CacheManager, 4 DeleteCacheFn, 5 Service, 6} from "@quan-erp/shared-backend-core"; 7import type { CreateJobDTO } from "./create-job.dto.js"; 8 9@Service() 10export class JobService { 11 @CacheFn({ 12 plugin: CacheManager.DEFAULT_PLUGIN, 13 name: "publisher", 14 key: (skip: number, limit: number) => `active-jobs-${skip}-${limit}`, 15 }) 16 async getActiveJobs(skip: number, limit: number) { /* … */ } 17 18 @DeleteCacheFn({ 19 plugin: CacheManager.DEFAULT_PLUGIN, 20 name: "publisher", 21 key: () => [`active-jobs-*`], 22 }) 23 async register(dto: CreateJobDTO) { /* … */ } 24}