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 "@artisansdk/architect"

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 { ServiceProvider, StoreManager, type ContainerContract as Container } from "@artisansdk/architect"

export class RedisStoreProvider extends ServiceProvider {
  boot(container: Container): void {
    // StoreManager isn't bound by class — only the string identifier "store" is registered.
    const store = container.make<StoreManager>("store")

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

Then configure it as the active driver. Unlike CacheManager, StoreManager has no stores map in its config shape — it reads a single flat store.driver key, and any config a custom driver’s factory needs is just a plain key you choose and read back yourself:

Application.configure({
  config: {
    store: {
      driver: "redis",
      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 { ServiceProvider, CacheManager, type ContainerContract as Container } from "@artisansdk/architect"

export class RedisCacheProvider extends ServiceProvider {
  boot(container: Container): 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))
    })
  }
}

Set cache.default: "redis" (or call Cache.use("redis") later) to activate it — cache.stores.redis here is just where this example chose to stash the driver’s own config; CacheManager only reads .driver out of cache.stores entries for its three built-in drivers, so a custom driver’s config path is otherwise up to you, same as StoreManager above.

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.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 "@artisansdk/architect/facades"

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

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