Skip to content

Commit

Permalink
feat: improved cache and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Jan 12, 2025
1 parent 9db266b commit d5f1bdc
Show file tree
Hide file tree
Showing 18 changed files with 674 additions and 177 deletions.
31 changes: 14 additions & 17 deletions apps/test-bot/src/commands/misc/help.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
import { unstable_cache, SlashCommandProps, CommandData } from 'commandkit';
import {
SlashCommandProps,
CommandData,
unstable_cacheTag as cacheTag,
} from 'commandkit';
import { setTimeout } from 'node:timers/promises';

export const data: CommandData = {
name: 'help',
description: 'This is a help command.',
};

function $botVersion() {
'use macro';
// this function is inlined in production build
const process = require('node:process');
return require(`${process.cwd()}/package.json`).version;
}

async function someExpensiveDatabaseCall() {
await setTimeout(3000);
'use cache';

await setTimeout(5000);

return Date.now();
}

export async function run({ interaction }: SlashCommandProps) {
await unstable_cache({ name: interaction.commandName, ttl: 60_000 });
cacheTag(15000, someExpensiveDatabaseCall);

export async function run({ interaction }: SlashCommandProps) {
await interaction.deferReply();

const dataRetrievalStart = Date.now();
const time = await someExpensiveDatabaseCall();

const version = $botVersion();
const dataRetrievalEnd = Date.now() - dataRetrievalStart;

return interaction.editReply({
embeds: [
{
title: 'Help',
description: `This is a help command. The current time is \`${time}\``,
description: `This is a help command. The current time is \`${time}\`. Fetched in ${dataRetrievalEnd}ms.`,
color: 0x7289da,
footer: {
text: `Bot Version: ${version}`,
},
timestamp: new Date().toISOString(),
},
],
Expand Down
124 changes: 124 additions & 0 deletions apps/website/docs/guide/11-caching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
title: Caching
description: A guide on how to implement caching in your bot using CommandKit.
---

# Caching

:::warning
This feature is currently available in development version of CommandKit only.
:::

Caching is a technique used to store data in a temporary storage to reduce the time it takes to fetch the data from the original source. This can be useful in Discord bots to reduce the number of database queries or external API calls.

CommandKit provides an easy way to implement caching in your bot without having to worry about the underlying implementation. This guide will show you how to use the caching feature in CommandKit.

## Setting up the cache

By default, commandkit enables in-memory caching. This means that the cache will be stored in the bot's memory and will be lost when the bot restarts.
You can provide a custom cache store by specifying the `cacheProvider` option when instantiating CommandKit.

```js
const { CommandKit } = require('commandkit');

new CommandKit({
client,
commandsPath,
eventsPath,
cacheProvider: new MyCustomCacheProvider(),
});
```

The `MyCustomCacheProvider` class should extend `CacheProvider` from CommandKit and implement the required methods. You may use this to store the cache in redis, a database or a file system.

## Using the cache

### Using commandkit CLI

If you are using the commandkit cli to run your bot, you can simply add `"use cache"` directive on a function that you want to cache the result of.

```js
async function fetchData() {
'use cache';

// Fetch data from an external source
const data = await fetch('https://my-example-api.com/data');

return data.json();
}

export async function run({ interaction }) {
await interaction.deferReply();

// Fetch data
const data = await fetchData();

// Send the data to the user
await interaction.editReply(data);
}
```

### Using the cache manually

To use the cache manually, you can import the `unstable_cache()` function from CommandKit and use it to cache the result of a function.

```js
import { unstable_cache as cache } from 'commandkit';

const fetchData = cache(async () => {
// Fetch data from an external source
const data = await fetch('https://my-example-api.com/data');

return data.json();
});

export async function run({ interaction }) {
await interaction.deferReply();

// Fetch data
const data = await fetchData();

// Send the data to the user
await interaction.editReply(data);
}
```

By default, the cached data will be stored forever until `unstable_revalidate()` or `unstable_invalidate()` is called on the cache object. You can also specify a custom TTL (time to live) for the cache by passing a second argument to the `cache` function.

```js
const fetchData = cache(
async () => {
// Fetch data from an external source
const data = await fetch('https://my-example-api.com/data');

return data.json();
},
{
name: 'fetchData', // name of the cache
ttl: 60_000, // cache for 1 minute
},
);
```

You may want to specify the cache parameters when using `"use cache"` directive. When using this approach, you can use `unstable_cacheTag()` to tag the cache with custom parameters.

```js
import { unstable_cacheTag as cacheTag } from 'commandkit';

async function fetchData() {
'use cache';

// Fetch data from an external source
const data = await fetch('https://my-example-api.com/data');

return data.json();
}

cacheTag(
{
name: 'fetchData', // name of the cache
ttl: 60_000, // cache for 1 minute
},
fetchData,
);
```
30 changes: 30 additions & 0 deletions apps/website/docs/guide/12-post-command-hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Post-command hooks
description: Post-command hooks allow you to run a function after a command has been executed.
---

:::warning
This feature is currently available in development version of CommandKit only.
:::

# Post-command hooks

Post-command hooks allow you to run a function after a command has been executed. This can be useful for logging, analytics, or any other post-processing tasks.

## Setting up post-command hooks

To set up a post-command hook, you need to define a function that will be called after a command has been executed. This feature is dynamic and you must use this inside your command.

```ts
import { after } from 'commandkit';

export async function run({ interaction }) {
after(() => {
// handle post-processing logic here
});

// handle your command
}
```

The `after()` function is guaranteed to be called after the command has been executed, regardless of whether the command was successful or not. The registered functions are called sequentially in the order they were defined.
40 changes: 40 additions & 0 deletions apps/website/docs/guide/13-command-restriction-helpers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Command restriction helpers
description: Command restriction helpers allow you to restrict commands based on various conditions.
---

# Command restriction helpers

:::warning
This feature is currently available in development version of CommandKit only.
:::

Command restriction helpers allow you to restrict commands based on various conditions. At the moment, only `guildOnly` and `dmOnly` restrictions are available.

## `guildOnly`

The `guildOnly` restriction allows you to restrict a command to be used only in guilds (servers) and not in direct messages. This is useful when your command is available both in guilds and direct messages, but you want to restrict it to guilds only for some reason.

```ts
import { guildOnly } from 'commandkit';

export async function run({ interaction }) {
guildOnly();

// Your command logic here
}
```

## `dmOnly`

The `dmOnly` restriction allows you to restrict a command to be used only in direct messages and not in guilds (servers). This is useful when your command is available both in guilds and direct messages, but you want to restrict it to direct messages only for some reason.

```ts
import { dmOnly } from 'commandkit';

export async function run({ interaction }) {
dmOnly();

// Your command logic here
}
```
3 changes: 2 additions & 1 deletion packages/commandkit/bin/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './common.mjs';
import ora from 'ora';
import { esbuildPluginUseMacro } from 'use-macro';
import { cacheDirectivePlugin } from './esbuild-plugins/use-cache.mjs';

export async function bootstrapProductionBuild(config) {
const {
Expand Down Expand Up @@ -47,7 +48,7 @@ export async function bootstrapProductionBuild(config) {
watch: false,
cjsInterop: true,
entry: [src, '!dist', '!.commandkit', `!${outDir}`],
esbuildPlugins: [esbuildPluginUseMacro()],
esbuildPlugins: [cacheDirectivePlugin()],
});

await injectShims(outDir, main, antiCrash, polyfillRequire);
Expand Down
2 changes: 2 additions & 0 deletions packages/commandkit/bin/development.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { parseEnv } from './parse-env.mjs';
import child_process from 'node:child_process';
import ora from 'ora';
import { injectShims } from './build.mjs';
import { cacheDirectivePlugin } from './esbuild-plugins/use-cache.mjs';

const RESTARTING_MSG_PATTERN = /^Restarting '|".+'|"\n?$/;
const FAILED_RUNNING_PATTERN = /^Failed running '.+'|"\n?$/;
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function bootstrapDevelopmentServer(opts) {
async onSuccess() {
return await injectShims('.commandkit', main, false, requirePolyfill);
},
esbuildPlugins: [cacheDirectivePlugin()],
});

status.succeed(
Expand Down
Loading

0 comments on commit d5f1bdc

Please sign in to comment.