Skip to content

Caching

Honocord uses a modular caching system that allows you to easily integrate different caching backends, such as in-memory, Durable Objects, MongoDB, or your own custom implementation. This guide will walk you through the basics of Honocord’s caching system, how to set it up, and best practices for using it in your implementation.

Honocord provides several built-in cache adapters:

You can also create your own custom cache adapter by implementing the abstract BaseCacheAdapter class.

It is important to note that you cannot use every adapter in every environment.

AdapterCloudflare WorkersNode.js / Bun
MemoryCacheAdapter
DurableObjectCacheAdapter
MongoCacheAdapter *?

* I’m not really sure about this one, it could work on CF Workers, but I haven’t tested it yet. If you try it out, let me know how it goes!

Every caching setup is basically the same, as you have to use the withCache method on the Honocord instance to provide a factory function that creates your cache adapter.
Only the setup before that may vary slightly based on the adapter.

Terminal window
pnpm add @honocord/cache-memory
pnpm add @honocord/cache-do
pnpm add @honocord/cache-mongo
pnpm add @honocord/cache-base # for custom adapters
import { Honocord } from "honocord";
import { MemoryCacheAdapter } from "@honocord/cache-memory";
const bot = new Honocord().withCache(() => new MemoryCacheAdapter());

The cache polulates automatically every time an interaction is received.

However, some things are not automatically cached due to Discord only providing partial objects in certain cases.

// TODO: Finish this section

Cache Diagram

As you can see, you effectivly only have to interact with the CacheManager. The CacheManager is responsible for managing the different namespaces and providing a unified interface for accessing the cache.
When you call ctx.cache.getUser(userId), the CacheManager will delegate the call to the appropriate namespace accessor, which will then interact with the underlying cache adapter to retrieve the data.

If you write a custom cache adapter, you only need to extend the abstract BaseCacheAdapter class and implement the required methods.

import Redis from "iovalkey"; // works for both Valkey AND Redis
export class RedisCacheAdapter implements CacheAdapter {
private client: Redis;
private ready: Promise<void>;
constructor(urlOrOptions: string | Redis.RedisOptions) {
this.client = new Redis(urlOrOptions as any);
this.ready = new Promise((resolve, reject) => {
this.client.once("ready", resolve);
this.client.once("error", reject);
});
}
async connect(): Promise<this> {
await this.ready;
return this;
}
async get<T>(key: string): Promise<T | null> {
await this.ready;
const val = await this.client.get(key);
if (!val) return null;
return JSON.parse(val) as T;
}
async set<T>(key: string, value: T, ttlMs?: number): Promise<void> {
await this.ready;
const serialized = JSON.stringify(value);
if (ttlMs) {
await this.client.set(key, serialized, "PX", ttlMs); // PX = milliseconds TTL
} else {
await this.client.set(key, serialized);
}
}
async mset(entries: { key: string; value: unknown; ttlMs?: number }[]): Promise<void> {
await this.ready;
if (entries.length === 0) return;
const pipeline = this.client.pipeline();
for (const { key, value, ttlMs } of entries) {
const serialized = JSON.stringify(value);
if (ttlMs) {
pipeline.set(key, serialized, "PX", ttlMs);
} else {
pipeline.set(key, serialized);
}
}
await pipeline.exec();
}
async delete(key: string): Promise<void> {
await this.ready;
await this.client.del(key);
}
async clear(): Promise<void> {
await this.ready;
await this.client.flushdb();
}
}

Then use it with Honocord just like any built-in adapter:

import { Honocord } from "honocord";
import { RedisCacheAdapter } from "./RedisCacheAdapter";
const cache = new RedisCacheAdapter(process.env.REDIS_URL!, {
namespace: "my-bot",
ttl: 300, // 5-minute default TTL
});
await cache.connect(); // You can also don't do this and let Honocord handle connecting, but doing it yourself allows you to catch connection errors at startup
const bot = new Honocord().withCache(() => cache);