Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom Drivers

Both CacheManager and StoreManager support registering custom drivers via extend(). A driver is a named backend — register it from a ServiceProvider’s boot() hook, where the manager is already bound.

Custom Store driver

Implement the StoreAdapter interface:

import type { StoreAdapter } from "@raubjo/architect-core"

class RedisAdapter implements StoreAdapter {
  constructor(private client: RedisClient) {}

  async get<T>(key: string): Promise<T | null> {
    const value = await this.client.get(key)
    return value === null ? null : JSON.parse(value)
  }

  async set<T>(key: string, value: T): Promise<void> {
    await this.client.set(key, JSON.stringify(value))
  }

  async has(key: string): Promise<boolean> {
    return (await this.client.exists(key)) === 1
  }

  async delete(key: string): Promise<void> {
    await this.client.del(key)
  }

  async clear(): Promise<void> {
    await this.client.flushDb()
  }

  async keys(): Promise<string[]> {
    return this.client.keys("*")
  }
}

Register it in a ServiceProvider:

import { StoreManager, type ServiceProviderContext } from "@raubjo/architect-core"

export class RedisStoreProvider extends ServiceProvider {
  boot({ container }: ServiceProviderContext): void {
    const store = container.make(StoreManager)

    store.extend("redis", (config) => {
      const url = config.get<string>("store.stores.redis.url", "redis://localhost:6379")
      return new RedisAdapter(new RedisClient(url))
    })
  }
}

Then configure it as the active driver:

Application.configure({
  config: {
    store: {
      default: "redis",
      stores: {
        redis: { driver: "redis", url: "redis://localhost:6379" },
      },
    },
  },
}).withProviders([new RedisStoreProvider()])

Custom Cache driver

Cache drivers use the same StoreAdapter interface — the Cache TTL wrapper is applied automatically by CacheManager. You do not need to implement TTL yourself:

import { CacheManager, type ServiceProviderContext } from "@raubjo/architect-core"

export class RedisCacheProvider extends ServiceProvider {
  boot({ container }: ServiceProviderContext): void {
    const cache = container.make(CacheManager)

    cache.extend("redis", (config) => {
      const url = config.get<string>("cache.stores.redis.url")
      return new RedisAdapter(new RedisClient(url))
    })
  }
}

Driver factory signature

The factory callback receives ConfigRepository and must return a raw StoreAdapter:

manager.extend("my-driver", (config: ConfigRepository): StoreAdapter => {
  return new MyAdapter(config.get("store.stores.my-driver"))
})

The factory is called lazily — only when the driver is first accessed — and the result is cached for subsequent calls.

Switching drivers at runtime

import { Store } from "@raubjo/architect-core/facades"

Store.use("redis")
await Store.set("user:42", userData)

// Switch back
Store.use("memory")