Skip to content

Commit d5f1bdc

Browse files
committed
feat: improved cache and docs
1 parent 9db266b commit d5f1bdc

File tree

18 files changed

+674
-177
lines changed

18 files changed

+674
-177
lines changed

apps/test-bot/src/commands/misc/help.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,38 @@
1-
import { unstable_cache, SlashCommandProps, CommandData } from 'commandkit';
1+
import {
2+
SlashCommandProps,
3+
CommandData,
4+
unstable_cacheTag as cacheTag,
5+
} from 'commandkit';
26
import { setTimeout } from 'node:timers/promises';
37

48
export const data: CommandData = {
59
name: 'help',
610
description: 'This is a help command.',
711
};
812

9-
function $botVersion() {
10-
'use macro';
11-
// this function is inlined in production build
12-
const process = require('node:process');
13-
return require(`${process.cwd()}/package.json`).version;
14-
}
15-
1613
async function someExpensiveDatabaseCall() {
17-
await setTimeout(3000);
14+
'use cache';
15+
16+
await setTimeout(5000);
17+
1818
return Date.now();
1919
}
2020

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

23+
export async function run({ interaction }: SlashCommandProps) {
2424
await interaction.deferReply();
2525

26+
const dataRetrievalStart = Date.now();
2627
const time = await someExpensiveDatabaseCall();
27-
28-
const version = $botVersion();
28+
const dataRetrievalEnd = Date.now() - dataRetrievalStart;
2929

3030
return interaction.editReply({
3131
embeds: [
3232
{
3333
title: 'Help',
34-
description: `This is a help command. The current time is \`${time}\``,
34+
description: `This is a help command. The current time is \`${time}\`. Fetched in ${dataRetrievalEnd}ms.`,
3535
color: 0x7289da,
36-
footer: {
37-
text: `Bot Version: ${version}`,
38-
},
3936
timestamp: new Date().toISOString(),
4037
},
4138
],
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
title: Caching
3+
description: A guide on how to implement caching in your bot using CommandKit.
4+
---
5+
6+
# Caching
7+
8+
:::warning
9+
This feature is currently available in development version of CommandKit only.
10+
:::
11+
12+
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.
13+
14+
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.
15+
16+
## Setting up the cache
17+
18+
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.
19+
You can provide a custom cache store by specifying the `cacheProvider` option when instantiating CommandKit.
20+
21+
```js
22+
const { CommandKit } = require('commandkit');
23+
24+
new CommandKit({
25+
client,
26+
commandsPath,
27+
eventsPath,
28+
cacheProvider: new MyCustomCacheProvider(),
29+
});
30+
```
31+
32+
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.
33+
34+
## Using the cache
35+
36+
### Using commandkit CLI
37+
38+
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.
39+
40+
```js
41+
async function fetchData() {
42+
'use cache';
43+
44+
// Fetch data from an external source
45+
const data = await fetch('https://my-example-api.com/data');
46+
47+
return data.json();
48+
}
49+
50+
export async function run({ interaction }) {
51+
await interaction.deferReply();
52+
53+
// Fetch data
54+
const data = await fetchData();
55+
56+
// Send the data to the user
57+
await interaction.editReply(data);
58+
}
59+
```
60+
61+
### Using the cache manually
62+
63+
To use the cache manually, you can import the `unstable_cache()` function from CommandKit and use it to cache the result of a function.
64+
65+
```js
66+
import { unstable_cache as cache } from 'commandkit';
67+
68+
const fetchData = cache(async () => {
69+
// Fetch data from an external source
70+
const data = await fetch('https://my-example-api.com/data');
71+
72+
return data.json();
73+
});
74+
75+
export async function run({ interaction }) {
76+
await interaction.deferReply();
77+
78+
// Fetch data
79+
const data = await fetchData();
80+
81+
// Send the data to the user
82+
await interaction.editReply(data);
83+
}
84+
```
85+
86+
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.
87+
88+
```js
89+
const fetchData = cache(
90+
async () => {
91+
// Fetch data from an external source
92+
const data = await fetch('https://my-example-api.com/data');
93+
94+
return data.json();
95+
},
96+
{
97+
name: 'fetchData', // name of the cache
98+
ttl: 60_000, // cache for 1 minute
99+
},
100+
);
101+
```
102+
103+
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.
104+
105+
```js
106+
import { unstable_cacheTag as cacheTag } from 'commandkit';
107+
108+
async function fetchData() {
109+
'use cache';
110+
111+
// Fetch data from an external source
112+
const data = await fetch('https://my-example-api.com/data');
113+
114+
return data.json();
115+
}
116+
117+
cacheTag(
118+
{
119+
name: 'fetchData', // name of the cache
120+
ttl: 60_000, // cache for 1 minute
121+
},
122+
fetchData,
123+
);
124+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Post-command hooks
3+
description: Post-command hooks allow you to run a function after a command has been executed.
4+
---
5+
6+
:::warning
7+
This feature is currently available in development version of CommandKit only.
8+
:::
9+
10+
# Post-command hooks
11+
12+
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.
13+
14+
## Setting up post-command hooks
15+
16+
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.
17+
18+
```ts
19+
import { after } from 'commandkit';
20+
21+
export async function run({ interaction }) {
22+
after(() => {
23+
// handle post-processing logic here
24+
});
25+
26+
// handle your command
27+
}
28+
```
29+
30+
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.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Command restriction helpers
3+
description: Command restriction helpers allow you to restrict commands based on various conditions.
4+
---
5+
6+
# Command restriction helpers
7+
8+
:::warning
9+
This feature is currently available in development version of CommandKit only.
10+
:::
11+
12+
Command restriction helpers allow you to restrict commands based on various conditions. At the moment, only `guildOnly` and `dmOnly` restrictions are available.
13+
14+
## `guildOnly`
15+
16+
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.
17+
18+
```ts
19+
import { guildOnly } from 'commandkit';
20+
21+
export async function run({ interaction }) {
22+
guildOnly();
23+
24+
// Your command logic here
25+
}
26+
```
27+
28+
## `dmOnly`
29+
30+
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.
31+
32+
```ts
33+
import { dmOnly } from 'commandkit';
34+
35+
export async function run({ interaction }) {
36+
dmOnly();
37+
38+
// Your command logic here
39+
}
40+
```

packages/commandkit/bin/build.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from './common.mjs';
1313
import ora from 'ora';
1414
import { esbuildPluginUseMacro } from 'use-macro';
15+
import { cacheDirectivePlugin } from './esbuild-plugins/use-cache.mjs';
1516

1617
export async function bootstrapProductionBuild(config) {
1718
const {
@@ -47,7 +48,7 @@ export async function bootstrapProductionBuild(config) {
4748
watch: false,
4849
cjsInterop: true,
4950
entry: [src, '!dist', '!.commandkit', `!${outDir}`],
50-
esbuildPlugins: [esbuildPluginUseMacro()],
51+
esbuildPlugins: [cacheDirectivePlugin()],
5152
});
5253

5354
await injectShims(outDir, main, antiCrash, polyfillRequire);

packages/commandkit/bin/development.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { parseEnv } from './parse-env.mjs';
1313
import child_process from 'node:child_process';
1414
import ora from 'ora';
1515
import { injectShims } from './build.mjs';
16+
import { cacheDirectivePlugin } from './esbuild-plugins/use-cache.mjs';
1617

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

7880
status.succeed(

0 commit comments

Comments
 (0)