diff --git a/apps/website/docs/api-reference/cache/classes/memory-cache.mdx b/apps/website/docs/api-reference/cache/classes/memory-cache.mdx
index 0ff69848..157b1210 100644
--- a/apps/website/docs/api-reference/cache/classes/memory-cache.mdx
+++ b/apps/website/docs/api-reference/cache/classes/memory-cache.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## MemoryCache
-
+
MemoryCache is an in-memory cache provider that implements the CacheProvider interface.
It stores cache entries in a Map and supports basic operations like get, set, exists, delete, clear, and expire.
diff --git a/apps/website/docs/api-reference/cache/classes/redis-cache.mdx b/apps/website/docs/api-reference/cache/classes/redis-cache.mdx
new file mode 100644
index 00000000..cde5269a
--- /dev/null
+++ b/apps/website/docs/api-reference/cache/classes/redis-cache.mdx
@@ -0,0 +1,124 @@
+---
+title: "RedisCache"
+isDefaultIndex: false
+generated: true
+---
+
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+
+
+## RedisCache
+
+
+
+A cache provider that uses Redis as the cache store.
+
+
+
+*Example*
+
+```ts
+const redisCache = new RedisCache();
+new CommandKit({
+...
+cacheProvider: redisCache,
+});
+```
+
+```ts title="Signature"
+class RedisCache extends CacheProvider {
+ public redis: Redis;
+ public serialize: SerializeFunction;
+ public deserialize: DeserializeFunction;
+ constructor()
+ constructor(redis: Redis)
+ constructor(redis: RedisOptions)
+ constructor(redis?: Redis | RedisOptions)
+ get(key: string) => Promise | undefined>;
+ set(key: string, value: T, ttl?: number) => Promise;
+ clear() => Promise;
+ delete(key: string) => Promise;
+ exists(key: string) => Promise;
+ expire(key: string, ttl: number) => Promise;
+}
+```
+* Extends: CacheProvider
+
+
+
+
+
+### redis
+
+
+
+
+### serialize
+
+
SerializeFunction`} />
+
+Serialize function used to serialize values before storing them in the cache.
+By default, it uses `JSON.stringify`.
+### deserialize
+
+DeserializeFunction`} />
+
+Deserialize function used to deserialize values before returning them from the cache.
+By default, it uses `JSON.parse`.
+### constructor
+
+ RedisCache`} />
+
+Create a new RedisCache instance.
+### constructor
+
+ RedisCache`} />
+
+Create a new RedisCache instance with the provided Redis client.
+### constructor
+
+ RedisCache`} />
+
+Create a new RedisCache instance with the provided Redis options.
+### constructor
+
+ RedisCache`} />
+
+
+### get
+
+ Promise<CacheEntry<T> | undefined>`} />
+
+Retrieve a value from the cache.
+### set
+
+ Promise<void>`} />
+
+Store a value in the cache.
+### clear
+
+ Promise<void>`} />
+
+Clear all entries from the cache.
+### delete
+
+ Promise<void>`} />
+
+Delete a value from the cache.
+### exists
+
+ Promise<boolean>`} />
+
+Check if a value exists in the cache.
+### expire
+
+ Promise<void>`} />
+
+Set the time-to-live for a cache entry.
+
+
+
diff --git a/apps/website/docs/api-reference/cache/functions/cache-life.mdx b/apps/website/docs/api-reference/cache/functions/cache-life.mdx
index 2b1bfbe0..579f6ce7 100644
--- a/apps/website/docs/api-reference/cache/functions/cache-life.mdx
+++ b/apps/website/docs/api-reference/cache/functions/cache-life.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## cacheLife
-
+
Sets the TTL for the current cache operation
diff --git a/apps/website/docs/api-reference/cache/functions/cache-tag.mdx b/apps/website/docs/api-reference/cache/functions/cache-tag.mdx
index 8d739f2f..2694de6f 100644
--- a/apps/website/docs/api-reference/cache/functions/cache-tag.mdx
+++ b/apps/website/docs/api-reference/cache/functions/cache-tag.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## cacheTag
-
+
Sets a custom identifier for the current cache operation
diff --git a/apps/website/docs/api-reference/cache/functions/cleanup.mdx b/apps/website/docs/api-reference/cache/functions/cleanup.mdx
index fa37868b..5bf058ce 100644
--- a/apps/website/docs/api-reference/cache/functions/cleanup.mdx
+++ b/apps/website/docs/api-reference/cache/functions/cleanup.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## cleanup
-
+
Cleans up stale cache entries.
diff --git a/apps/website/docs/api-reference/cache/functions/is-cached-function.mdx b/apps/website/docs/api-reference/cache/functions/is-cached-function.mdx
index 44ba721a..6a1273a2 100644
--- a/apps/website/docs/api-reference/cache/functions/is-cached-function.mdx
+++ b/apps/website/docs/api-reference/cache/functions/is-cached-function.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## isCachedFunction
-
+
Checks if a function is wrapped with cache functionality
diff --git a/apps/website/docs/api-reference/cache/functions/revalidate-tag.mdx b/apps/website/docs/api-reference/cache/functions/revalidate-tag.mdx
index eff56e68..8608e74a 100644
--- a/apps/website/docs/api-reference/cache/functions/revalidate-tag.mdx
+++ b/apps/website/docs/api-reference/cache/functions/revalidate-tag.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## revalidateTag
-
+
Marks cache entries for invalidation by their tag. The invalidation only happens when the path is next visited.
diff --git a/apps/website/docs/api-reference/cache/interfaces/cache-context.mdx b/apps/website/docs/api-reference/cache/interfaces/cache-context.mdx
index 8a40167d..96a5492f 100644
--- a/apps/website/docs/api-reference/cache/interfaces/cache-context.mdx
+++ b/apps/website/docs/api-reference/cache/interfaces/cache-context.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## CacheContext
-
+
Context for managing cache operations within an async scope
diff --git a/apps/website/docs/api-reference/cache/interfaces/cache-metadata.mdx b/apps/website/docs/api-reference/cache/interfaces/cache-metadata.mdx
index f1b51dd7..bd2856e8 100644
--- a/apps/website/docs/api-reference/cache/interfaces/cache-metadata.mdx
+++ b/apps/website/docs/api-reference/cache/interfaces/cache-metadata.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## CacheMetadata
-
+
Configuration options for cache behavior
diff --git a/apps/website/docs/api-reference/cache/types/awaitable.mdx b/apps/website/docs/api-reference/cache/types/awaitable.mdx
new file mode 100644
index 00000000..515bba18
--- /dev/null
+++ b/apps/website/docs/api-reference/cache/types/awaitable.mdx
@@ -0,0 +1,22 @@
+---
+title: "Awaitable"
+isDefaultIndex: false
+generated: true
+---
+
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+
+
+## Awaitable
+
+
+
+
+
+```ts title="Signature"
+type Awaitable = T | Promise
+```
diff --git a/apps/website/docs/api-reference/cache/types/deserialize-function.mdx b/apps/website/docs/api-reference/cache/types/deserialize-function.mdx
new file mode 100644
index 00000000..a9f63795
--- /dev/null
+++ b/apps/website/docs/api-reference/cache/types/deserialize-function.mdx
@@ -0,0 +1,22 @@
+---
+title: "DeserializeFunction"
+isDefaultIndex: false
+generated: true
+---
+
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+
+
+## DeserializeFunction
+
+
+
+
+
+```ts title="Signature"
+type DeserializeFunction = (value: string) => Awaitable
+```
diff --git a/apps/website/docs/api-reference/cache/types/index.mdx b/apps/website/docs/api-reference/cache/types/index.mdx
new file mode 100644
index 00000000..224a2db8
--- /dev/null
+++ b/apps/website/docs/api-reference/cache/types/index.mdx
@@ -0,0 +1,16 @@
+---
+title: "Type Aliases"
+isDefaultIndex: true
+generated: true
+---
+
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+
+
+import DocCardList from '@theme/DocCardList';
+
+
\ No newline at end of file
diff --git a/apps/website/docs/api-reference/cache/types/serialize-function.mdx b/apps/website/docs/api-reference/cache/types/serialize-function.mdx
new file mode 100644
index 00000000..46e0012c
--- /dev/null
+++ b/apps/website/docs/api-reference/cache/types/serialize-function.mdx
@@ -0,0 +1,22 @@
+---
+title: "SerializeFunction"
+isDefaultIndex: false
+generated: true
+---
+
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+
+
+## SerializeFunction
+
+
+
+
+
+```ts title="Signature"
+type SerializeFunction = (value: any) => Awaitable
+```
diff --git a/apps/website/docs/api-reference/redis/classes/redis-cache.mdx b/apps/website/docs/api-reference/redis/classes/redis-cache.mdx
index 8bd58b3a..8f363f28 100644
--- a/apps/website/docs/api-reference/redis/classes/redis-cache.mdx
+++ b/apps/website/docs/api-reference/redis/classes/redis-cache.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## RedisCache
-
+
A cache provider that uses Redis as the cache store.
@@ -23,10 +23,7 @@ A cache provider that uses Redis as the cache store.
```ts
const redisCache = new RedisCache();
-new CommandKit({
-...
-cacheProvider: redisCache,
-});
+setCacheProvider(redisCache);
```
```ts title="Signature"
diff --git a/apps/website/docs/api-reference/redis/classes/redis-plugin.mdx b/apps/website/docs/api-reference/redis/classes/redis-plugin.mdx
index cb39432e..c19a247e 100644
--- a/apps/website/docs/api-reference/redis/classes/redis-plugin.mdx
+++ b/apps/website/docs/api-reference/redis/classes/redis-plugin.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## RedisPlugin
-
+
diff --git a/apps/website/docs/api-reference/redis/functions/redis.mdx b/apps/website/docs/api-reference/redis/functions/redis.mdx
index 95fe7d5a..6e28c847 100644
--- a/apps/website/docs/api-reference/redis/functions/redis.mdx
+++ b/apps/website/docs/api-reference/redis/functions/redis.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## redis
-
+
Create a new Redis plugin instance.
diff --git a/apps/website/docs/api-reference/redis/types/awaitable.mdx b/apps/website/docs/api-reference/redis/types/awaitable.mdx
index 2c51e358..3bcca9cd 100644
--- a/apps/website/docs/api-reference/redis/types/awaitable.mdx
+++ b/apps/website/docs/api-reference/redis/types/awaitable.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## Awaitable
-
+
diff --git a/apps/website/docs/api-reference/redis/types/deserialize-function.mdx b/apps/website/docs/api-reference/redis/types/deserialize-function.mdx
index a9146253..b222ff5c 100644
--- a/apps/website/docs/api-reference/redis/types/deserialize-function.mdx
+++ b/apps/website/docs/api-reference/redis/types/deserialize-function.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## DeserializeFunction
-
+
diff --git a/apps/website/docs/api-reference/redis/types/serialize-function.mdx b/apps/website/docs/api-reference/redis/types/serialize-function.mdx
index a3d819c2..2d0dc49f 100644
--- a/apps/website/docs/api-reference/redis/types/serialize-function.mdx
+++ b/apps/website/docs/api-reference/redis/types/serialize-function.mdx
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
## SerializeFunction
-
+
diff --git a/apps/website/docs/guide/05-official-plugins/03-commandkit-cache.mdx b/apps/website/docs/guide/05-official-plugins/03-commandkit-cache.mdx
index 49824b81..f943d0f9 100644
--- a/apps/website/docs/guide/05-official-plugins/03-commandkit-cache.mdx
+++ b/apps/website/docs/guide/05-official-plugins/03-commandkit-cache.mdx
@@ -207,7 +207,7 @@ Redis:
```ts title="commandkit.config.ts"
import { defineConfig } from 'commandkit';
import { cache, setCacheProvider } from '@commandkit/cache';
-import { RedisCache } from '@commandkit/redis';
+import { RedisCache } from '@commandkit/cache/redis';
// Set up Redis as the cache provider
setCacheProvider(
@@ -240,12 +240,3 @@ setInterval(
24 * 60 * 60 * 1000,
); // Run daily
```
-
-:::tip
-
-The `@commandkit/cache` plugin works seamlessly with other CommandKit
-features and plugins. You can use it alongside the
-[`@commandkit/redis`](./07-commandkit-redis.mdx) plugin for
-distributed caching across multiple bot instances.
-
-:::
diff --git a/apps/website/docs/guide/05-official-plugins/07-commandkit-redis.mdx b/apps/website/docs/guide/05-official-plugins/07-commandkit-redis.mdx
deleted file mode 100644
index f7056626..00000000
--- a/apps/website/docs/guide/05-official-plugins/07-commandkit-redis.mdx
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: '@commandkit/redis'
----
-
-The `@commandkit/redis` plugin provides a cache provider for
-CommandKit that allows you to store data in Redis. It works out of the
-box with the [`@commandkit/cache`](./03-commandkit-cache.mdx) plugin.
-
-## Installation
-
-```sh npm2yarn
-npm install @commandkit/redis@next
-```
-
-## Usage
-
-This plugin will automatically register the Redis cache provider with
-your CommandKit instance.
-
-```js
-import { defineConfig } from 'commandkit';
-import { redis } from '@commandkit/redis';
-
-export default defineConfig({
- plugins: [redis()],
-});
-```
-
-Once configured, your cache functions will automatically use Redis as
-the storage backend:
-
-```ts
-async function getCachedData() {
- 'use cache'; // This directive enables caching for the function
-
- // Your data retrieval logic
- const data = await getFromDatabase('something');
-
- return data;
-}
-```
-
-## Manual Configuration
-
-If you need more control over the Redis client configuration, you can
-set up the cache provider manually instead of using the plugin:
-
-```ts
-import { setCacheProvider } from '@commandkit/cache';
-import { RedisCacheProvider } from '@commandkit/redis';
-import { Redis } from 'ioredis';
-
-// Configure the Redis client with custom options
-const redis = new Redis({
- host: 'your-redis-host',
- port: 6379,
- // Add other Redis options as needed
-});
-
-const redisProvider = new RedisCacheProvider(redis);
-
-// Register the provider with CommandKit
-setCacheProvider(redisProvider);
-```
-
-This approach gives you full control over the Redis client
-configuration while still integrating with CommandKit's caching
-system.
diff --git a/packages/cache/README.md b/packages/cache/README.md
index 2aca340c..da4129be 100644
--- a/packages/cache/README.md
+++ b/packages/cache/README.md
@@ -24,7 +24,7 @@ Next, you can define advanced configurations for the plugin if needed. For examp
```ts
import { setCacheProvider } from '@commandkit/cache';
-import { RedisCache } from '@commandkit/redis';
+import { RedisCache } from '@commandkit/cache/providers/redis';
setCacheProvider(new RedisCache({...}));
```
diff --git a/packages/cache/package.json b/packages/cache/package.json
index 94577955..1c625173 100644
--- a/packages/cache/package.json
+++ b/packages/cache/package.json
@@ -7,6 +7,20 @@
"files": [
"dist"
],
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "./redis": {
+ "import": "./dist/providers/redis.js",
+ "types": "./dist/providers/redis.d.ts"
+ },
+ "./memory-cache": {
+ "import": "./dist/providers/memory-cache.js",
+ "types": "./dist/providers/memory-cache.d.ts"
+ }
+ },
"scripts": {
"check-types": "tsc --noEmit",
"build": "tsc"
@@ -34,6 +48,7 @@
"devDependencies": {
"@types/ms": "^2.0.0",
"commandkit": "workspace:*",
+ "ioredis": "^5.8.1",
"tsconfig": "workspace:*",
"typescript": "catalog:build"
},
diff --git a/packages/cache/src/cache-plugin.ts b/packages/cache/src/cache-plugin.ts
index e34c31e4..e7957b4a 100644
--- a/packages/cache/src/cache-plugin.ts
+++ b/packages/cache/src/cache-plugin.ts
@@ -1,6 +1,6 @@
import { Logger, RuntimePlugin } from 'commandkit';
import type { CacheProvider } from './cache-provider';
-import { MemoryCache } from './memory-cache';
+import { MemoryCache } from './providers/memory-cache';
let cacheProvider: CacheProvider | null = null;
diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts
index b5a31f1f..4c9d95ef 100644
--- a/packages/cache/src/index.ts
+++ b/packages/cache/src/index.ts
@@ -6,7 +6,7 @@ import { UseCacheDirectivePlugin } from './use-cache-directive';
import { CachePlugin } from './cache-plugin';
export * from './cache-provider';
-export * from './memory-cache';
+export * from './providers/memory-cache';
export * from './use-cache';
export * from './use-cache-directive';
export * from './cache-plugin';
diff --git a/packages/cache/src/memory-cache.ts b/packages/cache/src/providers/memory-cache.ts
similarity index 97%
rename from packages/cache/src/memory-cache.ts
rename to packages/cache/src/providers/memory-cache.ts
index 668f00df..9c038383 100644
--- a/packages/cache/src/memory-cache.ts
+++ b/packages/cache/src/providers/memory-cache.ts
@@ -1,4 +1,4 @@
-import { CacheEntry, CacheProvider } from './cache-provider';
+import { CacheEntry, CacheProvider } from '../cache-provider';
/**
* MemoryCache is an in-memory cache provider that implements the CacheProvider interface.
diff --git a/packages/cache/src/providers/redis.ts b/packages/cache/src/providers/redis.ts
new file mode 100644
index 00000000..f7233841
--- /dev/null
+++ b/packages/cache/src/providers/redis.ts
@@ -0,0 +1,134 @@
+import { Redis, type RedisOptions } from 'ioredis';
+import { CacheProvider, CacheEntry } from '../cache-provider';
+
+export type Awaitable = T | Promise;
+export type SerializeFunction = (value: any) => Awaitable;
+export type DeserializeFunction = (value: string) => Awaitable;
+
+/**
+ * A cache provider that uses Redis as the cache store.
+ * @example const redisCache = new RedisCache();
+ * new CommandKit({
+ * ...
+ * cacheProvider: redisCache,
+ * });
+ */
+export class RedisCache extends CacheProvider {
+ public redis: Redis;
+
+ /**
+ * Serialize function used to serialize values before storing them in the cache.
+ * By default, it uses `JSON.stringify`.
+ */
+ public serialize: SerializeFunction;
+
+ /**
+ * Deserialize function used to deserialize values before returning them from the cache.
+ * By default, it uses `JSON.parse`.
+ */
+ public deserialize: DeserializeFunction;
+
+ /**
+ * Create a new RedisCache instance.
+ */
+ public constructor();
+ /**
+ * Create a new RedisCache instance with the provided Redis client.
+ * @param redis The Redis client to use.
+ */
+ public constructor(redis: Redis);
+ /**
+ * Create a new RedisCache instance with the provided Redis options.
+ * @param redis The Redis client to use.
+ */
+ public constructor(redis: RedisOptions);
+ public constructor(redis?: Redis | RedisOptions) {
+ super();
+
+ if (redis instanceof Redis) {
+ this.redis = redis;
+ } else {
+ this.redis = new Redis(redis ?? {});
+ }
+
+ this.serialize = JSON.stringify;
+ this.deserialize = JSON.parse;
+ }
+
+ /**
+ * Retrieve a value from the cache.
+ * @param key The key to retrieve the value for.
+ * @returns The value stored in the cache, or `undefined` if it does not exist.
+ */
+ public async get(key: string): Promise | undefined> {
+ const value = await this.redis.get(key);
+
+ if (value === null) {
+ return undefined;
+ }
+
+ const entry = this.deserialize(value) as CacheEntry;
+ if (entry.ttl && Date.now() > entry.ttl) {
+ await this.delete(key);
+ return undefined;
+ }
+
+ return entry;
+ }
+
+ /**
+ * Store a value in the cache.
+ * @param key The key to store the value under.
+ * @param value The value to store in the cache.
+ * @param ttl The time-to-live for the cache entry in milliseconds.
+ */
+ public async set(key: string, value: T, ttl?: number): Promise {
+ const entry: CacheEntry = {
+ value,
+ ttl: ttl != null ? Date.now() + ttl : undefined,
+ };
+
+ const serialized = this.serialize(entry);
+ const finalValue =
+ serialized instanceof Promise ? await serialized : serialized;
+
+ if (typeof ttl === 'number') {
+ await this.redis.set(key, finalValue, 'PX', ttl);
+ } else {
+ await this.redis.set(key, finalValue);
+ }
+ }
+
+ /**
+ * Clear all entries from the cache.
+ */
+ public async clear(): Promise {
+ await this.redis.flushall();
+ }
+
+ /**
+ * Delete a value from the cache.
+ * @param key The key to delete the value for.
+ */
+ public async delete(key: string): Promise {
+ await this.redis.del(key);
+ }
+
+ /**
+ * Check if a value exists in the cache.
+ * @param key The key to check for.
+ * @returns True if the key exists in the cache, false otherwise.
+ */
+ public async exists(key: string): Promise {
+ return Boolean(await this.redis.exists(key));
+ }
+
+ /**
+ * Set the time-to-live for a cache entry.
+ * @param key The key to set the time-to-live for.
+ * @param ttl The time-to-live value in milliseconds.
+ */
+ public async expire(key: string, ttl: number): Promise {
+ await this.redis.pexpire(key, ttl);
+ }
+}
diff --git a/packages/cache/src/use-cache.ts b/packages/cache/src/use-cache.ts
index a54ac194..4d07da5d 100644
--- a/packages/cache/src/use-cache.ts
+++ b/packages/cache/src/use-cache.ts
@@ -6,7 +6,6 @@ import {
getCommandKit,
} from 'commandkit';
import { AnalyticsEvents } from 'commandkit/analytics';
-import { randomUUID } from 'node:crypto';
import ms, { type StringValue } from 'ms';
import { getCacheProvider } from './cache-plugin';
import { createHash } from './utils';
@@ -18,18 +17,12 @@ const fnStore = new Map<
key: string;
hash: string;
ttl?: number;
- original: GenericFunction;
- memo: GenericFunction;
tags: Set;
lastAccess: number;
}
>();
-const CACHE_FN_ID = `__cache_fn_id_${Date.now()}__${Math.random()}__`;
const CACHED_FN_SYMBOL = Symbol('commandkit.cache.sentinel');
-// WeakMap to store function metadata without preventing garbage collection
-const fnMetadata = new WeakMap();
-
/**
* Context for managing cache operations within an async scope
*/
@@ -68,40 +61,19 @@ export interface CacheMetadata {
* }
* ```
*/
-function useCache>(
- fn: F,
- id?: string,
- params?: CacheMetadata,
-): F {
- const isLocal = id === CACHE_FN_ID;
-
- if (id && !isLocal) {
- throw new Error('Illegal use of cache function.');
- }
-
- // Get or create function metadata
- let metadata = fnMetadata.get(fn);
- if (!metadata) {
- metadata = randomUUID();
- fnMetadata.set(fn, metadata);
- }
+function useCache>(fn: F): F {
+ // assign unique id to the function to avoid collisions with other functions
+ const metadata = crypto.randomUUID();
const memo = (async (...args) => {
const analytics = getCommandKit()?.analytics;
const keyHash = createHash(metadata, args);
- const resolvedTTL =
- isLocal && params?.ttl != null
- ? typeof params.ttl === 'string'
- ? ms(params.ttl as StringValue)
- : params.ttl
- : null;
-
return cacheContext.run(
{
params: {
- ttl: resolvedTTL,
- tags: new Set(params?.tags ?? []),
+ ttl: null,
+ tags: new Set(),
},
},
async () => {
@@ -151,8 +123,6 @@ function useCache>(
key: keyHash,
hash: keyHash,
ttl: ttl ?? undefined,
- original: fn,
- memo,
tags: context.params.tags,
lastAccess: Date.now(),
});
diff --git a/packages/commandkit/src/index.ts b/packages/commandkit/src/index.ts
index a097dd97..12932455 100644
--- a/packages/commandkit/src/index.ts
+++ b/packages/commandkit/src/index.ts
@@ -30,6 +30,7 @@ export {
debounce,
defer,
} from './utils/utilities';
+export { warnDeprecated, emitWarning, warnUnstable } from './utils/warning';
export { toFileURL } from './utils/resolve-file-url';
export * from './app/interrupt/signals';
export type { CommandKitHMREvent } from './utils/dev-hooks';
diff --git a/packages/redis/README.md b/packages/redis/README.md
index b34686df..49a3d686 100644
--- a/packages/redis/README.md
+++ b/packages/redis/README.md
@@ -10,6 +10,9 @@ npm install @commandkit/redis
## Usage
+> [!WARNING]
+> `RedisCache` from `@commandkit/redis` is deprecated. Import `RedisCache` from `@commandkit/cache/redis` instead.
+
This package provides a commandkit plugin that automatically registers the cache provider with the commandkit instance.
```js
diff --git a/packages/redis/src/cache-storage.ts b/packages/redis/src/cache-storage.ts
new file mode 100644
index 00000000..dc9c9d03
--- /dev/null
+++ b/packages/redis/src/cache-storage.ts
@@ -0,0 +1,139 @@
+import { CacheProvider, CacheEntry } from '@commandkit/cache';
+import { warnDeprecated } from 'commandkit';
+import Redis, { type RedisOptions } from 'ioredis';
+
+export type Awaitable = T | Promise;
+export type SerializeFunction = (value: any) => Awaitable;
+export type DeserializeFunction = (value: string) => Awaitable;
+
+/**
+ * A cache provider that uses Redis as the cache store.
+ * @example const redisCache = new RedisCache();
+ * setCacheProvider(redisCache);
+ * @deprecated Import `RedisCache` from `@commandkit/cache/redis` instead.
+ */
+export class RedisCache extends CacheProvider {
+ public redis: Redis;
+
+ /**
+ * Serialize function used to serialize values before storing them in the cache.
+ * By default, it uses `JSON.stringify`.
+ */
+ public serialize: SerializeFunction;
+
+ /**
+ * Deserialize function used to deserialize values before returning them from the cache.
+ * By default, it uses `JSON.parse`.
+ */
+ public deserialize: DeserializeFunction;
+
+ /**
+ * Create a new RedisCache instance.
+ */
+ public constructor();
+ /**
+ * Create a new RedisCache instance with the provided Redis client.
+ * @param redis The Redis client to use.
+ */
+ public constructor(redis: Redis);
+ /**
+ * Create a new RedisCache instance with the provided Redis options.
+ * @param redis The Redis client to use.
+ */
+ public constructor(redis: RedisOptions);
+ public constructor(redis?: Redis | RedisOptions) {
+ warnDeprecated({
+ what: 'RedisCache',
+ where: '@commandkit/redis',
+ message: 'Import `RedisCache` from `@commandkit/cache/redis` instead.',
+ });
+
+ super();
+
+ if (redis instanceof Redis) {
+ this.redis = redis;
+ } else {
+ this.redis = new Redis(redis ?? {});
+ }
+
+ this.serialize = JSON.stringify;
+ this.deserialize = JSON.parse;
+ }
+
+ /**
+ * Retrieve a value from the cache.
+ * @param key The key to retrieve the value for.
+ * @returns The value stored in the cache, or `undefined` if it does not exist.
+ */
+ public async get(key: string): Promise | undefined> {
+ const value = await this.redis.get(key);
+
+ if (value === null) {
+ return undefined;
+ }
+
+ const entry = this.deserialize(value) as CacheEntry;
+ if (entry.ttl && Date.now() > entry.ttl) {
+ await this.delete(key);
+ return undefined;
+ }
+
+ return entry;
+ }
+
+ /**
+ * Store a value in the cache.
+ * @param key The key to store the value under.
+ * @param value The value to store in the cache.
+ * @param ttl The time-to-live for the cache entry in milliseconds.
+ */
+ public async set(key: string, value: T, ttl?: number): Promise {
+ const entry: CacheEntry = {
+ value,
+ ttl: ttl != null ? Date.now() + ttl : undefined,
+ };
+
+ const serialized = this.serialize(entry);
+ const finalValue =
+ serialized instanceof Promise ? await serialized : serialized;
+
+ if (typeof ttl === 'number') {
+ await this.redis.set(key, finalValue, 'PX', ttl);
+ } else {
+ await this.redis.set(key, finalValue);
+ }
+ }
+
+ /**
+ * Clear all entries from the cache.
+ */
+ public async clear(): Promise {
+ await this.redis.flushall();
+ }
+
+ /**
+ * Delete a value from the cache.
+ * @param key The key to delete the value for.
+ */
+ public async delete(key: string): Promise {
+ await this.redis.del(key);
+ }
+
+ /**
+ * Check if a value exists in the cache.
+ * @param key The key to check for.
+ * @returns True if the key exists in the cache, false otherwise.
+ */
+ public async exists(key: string): Promise {
+ return Boolean(await this.redis.exists(key));
+ }
+
+ /**
+ * Set the time-to-live for a cache entry.
+ * @param key The key to set the time-to-live for.
+ * @param ttl The time-to-live value in milliseconds.
+ */
+ public async expire(key: string, ttl: number): Promise {
+ await this.redis.pexpire(key, ttl);
+ }
+}
diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts
index c65d6a52..f76fd1b6 100644
--- a/packages/redis/src/index.ts
+++ b/packages/redis/src/index.ts
@@ -1,138 +1,7 @@
-import { Redis, type RedisOptions } from 'ioredis';
+import { type RedisOptions } from 'ioredis';
import { CommandKitPluginRuntime, RuntimePlugin } from 'commandkit';
-import { CacheProvider, CacheEntry, setCacheProvider } from '@commandkit/cache';
-
-export type Awaitable = T | Promise;
-export type SerializeFunction = (value: any) => Awaitable;
-export type DeserializeFunction = (value: string) => Awaitable;
-
-/**
- * A cache provider that uses Redis as the cache store.
- * @example const redisCache = new RedisCache();
- * new CommandKit({
- * ...
- * cacheProvider: redisCache,
- * });
- */
-export class RedisCache extends CacheProvider {
- public redis: Redis;
-
- /**
- * Serialize function used to serialize values before storing them in the cache.
- * By default, it uses `JSON.stringify`.
- */
- public serialize: SerializeFunction;
-
- /**
- * Deserialize function used to deserialize values before returning them from the cache.
- * By default, it uses `JSON.parse`.
- */
- public deserialize: DeserializeFunction;
-
- /**
- * Create a new RedisCache instance.
- */
- public constructor();
- /**
- * Create a new RedisCache instance with the provided Redis client.
- * @param redis The Redis client to use.
- */
- public constructor(redis: Redis);
- /**
- * Create a new RedisCache instance with the provided Redis options.
- * @param redis The Redis client to use.
- */
- public constructor(redis: RedisOptions);
- public constructor(redis?: Redis | RedisOptions) {
- super();
-
- if (redis instanceof Redis) {
- this.redis = redis;
- } else {
- this.redis = new Redis(redis ?? {});
- }
-
- this.serialize = JSON.stringify;
- this.deserialize = JSON.parse;
- }
-
- /**
- * Retrieve a value from the cache.
- * @param key The key to retrieve the value for.
- * @returns The value stored in the cache, or `undefined` if it does not exist.
- */
- public async get(key: string): Promise | undefined> {
- const value = await this.redis.get(key);
-
- if (value === null) {
- return undefined;
- }
-
- const entry = this.deserialize(value) as CacheEntry;
- if (entry.ttl && Date.now() > entry.ttl) {
- await this.delete(key);
- return undefined;
- }
-
- return entry;
- }
-
- /**
- * Store a value in the cache.
- * @param key The key to store the value under.
- * @param value The value to store in the cache.
- * @param ttl The time-to-live for the cache entry in milliseconds.
- */
- public async set(key: string, value: T, ttl?: number): Promise {
- const entry: CacheEntry = {
- value,
- ttl: ttl != null ? Date.now() + ttl : undefined,
- };
-
- const serialized = this.serialize(entry);
- const finalValue =
- serialized instanceof Promise ? await serialized : serialized;
-
- if (typeof ttl === 'number') {
- await this.redis.set(key, finalValue, 'PX', ttl);
- } else {
- await this.redis.set(key, finalValue);
- }
- }
-
- /**
- * Clear all entries from the cache.
- */
- public async clear(): Promise {
- await this.redis.flushall();
- }
-
- /**
- * Delete a value from the cache.
- * @param key The key to delete the value for.
- */
- public async delete(key: string): Promise {
- await this.redis.del(key);
- }
-
- /**
- * Check if a value exists in the cache.
- * @param key The key to check for.
- * @returns True if the key exists in the cache, false otherwise.
- */
- public async exists(key: string): Promise {
- return Boolean(await this.redis.exists(key));
- }
-
- /**
- * Set the time-to-live for a cache entry.
- * @param key The key to set the time-to-live for.
- * @param ttl The time-to-live value in milliseconds.
- */
- public async expire(key: string, ttl: number): Promise {
- await this.redis.pexpire(key, ttl);
- }
-}
+import { setCacheProvider } from '@commandkit/cache';
+import { RedisCache } from './cache-storage';
export class RedisPlugin extends RuntimePlugin {
public readonly name = 'RedisPlugin';
@@ -154,4 +23,4 @@ export function redis(options?: RedisOptions) {
export * from './ratelimit-storage';
export * from './mutex-storage';
export * from './semaphore-storage';
-export { RedisCache as RedisCacheProvider };
+export { RedisCache as RedisCacheProvider, RedisCache };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index daa085f4..c4f78500 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -251,6 +251,9 @@ importers:
commandkit:
specifier: workspace:*
version: link:../commandkit
+ ioredis:
+ specifier: ^5.8.1
+ version: 5.8.1
tsconfig:
specifier: workspace:*
version: link:../tsconfig
@@ -2348,9 +2351,6 @@ packages:
'@types/node':
optional: true
- '@ioredis/commands@1.2.0':
- resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
-
'@ioredis/commands@1.4.0':
resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==}
@@ -6254,10 +6254,6 @@ packages:
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
- ioredis@5.6.1:
- resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==}
- engines: {node: '>=12.22.0'}
-
ioredis@5.8.1:
resolution: {integrity: sha512-Qho8TgIamqEPdgiMadJwzRMW3TudIg6vpg4YONokGDudy4eqRIJtDbVX72pfLBcWxvbn3qm/40TyGUObdW4tLQ==}
engines: {node: '>=12.22.0'}
@@ -9776,7 +9772,7 @@ snapshots:
'@babel/traverse': 7.28.0
'@babel/types': 7.28.2
convert-source-map: 2.0.0
- debug: 4.4.1
+ debug: 4.4.3
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -11134,7 +11130,7 @@ snapshots:
'@babel/parser': 7.28.0
'@babel/template': 7.27.2
'@babel/types': 7.28.2
- debug: 4.4.1
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -11462,7 +11458,7 @@ snapshots:
dependencies:
'@msgpack/msgpack': 3.1.2
'@vladfrangu/async_event_emitter': 2.4.6
- ioredis: 5.6.1
+ ioredis: 5.8.1
transitivePeerDependencies:
- supports-color
@@ -12659,8 +12655,6 @@ snapshots:
optionalDependencies:
'@types/node': 22.18.8
- '@ioredis/commands@1.2.0': {}
-
'@ioredis/commands@1.4.0': {}
'@isaacs/balanced-match@4.0.1': {}
@@ -14886,7 +14880,7 @@ snapshots:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
- debug: 4.4.1
+ debug: 4.4.3
http-errors: 2.0.0
iconv-lite: 0.6.3
on-finished: 2.4.1
@@ -16398,7 +16392,7 @@ snapshots:
finalhandler@2.1.0:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
@@ -17018,20 +17012,6 @@ snapshots:
dependencies:
loose-envify: 1.4.0
- ioredis@5.6.1:
- dependencies:
- '@ioredis/commands': 1.2.0
- cluster-key-slot: 1.1.2
- debug: 4.4.3
- denque: 2.1.0
- lodash.defaults: 4.2.0
- lodash.isarguments: 3.1.0
- redis-errors: 1.2.0
- redis-parser: 3.0.0
- standard-as-callback: 2.1.0
- transitivePeerDependencies:
- - supports-color
-
ioredis@5.8.1:
dependencies:
'@ioredis/commands': 1.4.0
@@ -19587,7 +19567,7 @@ snapshots:
router@2.2.0:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
@@ -19689,7 +19669,7 @@ snapshots:
send@1.2.0:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
@@ -20542,7 +20522,7 @@ snapshots:
vite-node@3.2.4(@types/node@22.18.8)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.39.2)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
cac: 6.7.14
- debug: 4.4.1
+ debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 7.0.6(@types/node@22.18.8)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.39.2)(tsx@4.20.6)(yaml@2.8.1)