Skip to content

Commit

Permalink
feat: context, cache, macro and more
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Jan 11, 2025
1 parent 31472c6 commit 9db266b
Show file tree
Hide file tree
Showing 22 changed files with 958 additions and 84 deletions.
3 changes: 2 additions & 1 deletion apps/test-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
.env
.commandkit
5 changes: 4 additions & 1 deletion apps/test-bot/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"name": "test-bot",
"version": "1.0.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"dev": "commandkit dev",
"build": "commandkit build",
"start": "commandkit start"
},
"dependencies": {
"commandkit": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/test-bot/src/commands/misc/dm-only.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SlashCommandProps, CommandData, dmOnly } from 'commandkit';

export const data: CommandData = {
name: 'dm-only',
description: 'This is a dm only command',
};

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

await interaction.reply('This command can only be used in a dm.');
}
12 changes: 12 additions & 0 deletions apps/test-bot/src/commands/misc/guild-only.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SlashCommandProps, CommandData, guildOnly } from 'commandkit';

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

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

await interaction.reply('This command can only be used in a guild.');
}
43 changes: 43 additions & 0 deletions apps/test-bot/src/commands/misc/help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { unstable_cache, SlashCommandProps, CommandData } 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);
return Date.now();
}

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

await interaction.deferReply();

const time = await someExpensiveDatabaseCall();

const version = $botVersion();

return interaction.editReply({
embeds: [
{
title: 'Help',
description: `This is a help command. The current time is \`${time}\``,
color: 0x7289da,
footer: {
text: `Bot Version: ${version}`,
},
timestamp: new Date().toISOString(),
},
],
});
}
20 changes: 20 additions & 0 deletions apps/test-bot/src/commands/misc/run-after.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SlashCommandProps, CommandData, after } from 'commandkit';

export const data: CommandData = {
name: 'run-after',
description: 'This is a run-after command',
};

export async function run({ interaction }: SlashCommandProps) {
after((env) => {
console.log(
`The command ${interaction.commandName} was executed successfully in ${env
.getExecutionTime()
.toFixed(2)} milliseconds!`,
);
});

await interaction.reply(
'Hello, you will see a new message printed in your console after this command runs.',
);
}
2 changes: 1 addition & 1 deletion apps/test-bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ new CommandKit({
validationsPath: `${__dirname}/validations`,
devGuildIds: process.env.DEV_GUILD_ID?.split(',') ?? [],
devUserIds: process.env.DEV_USER_ID?.split(',') ?? [],
bulkRegister: true,
bulkRegister: false,
});

await client.login(process.env.TOKEN);
3 changes: 3 additions & 0 deletions packages/commandkit/bin/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
write,
} from './common.mjs';
import ora from 'ora';
import { esbuildPluginUseMacro } from 'use-macro';

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

await injectShims(outDir, main, antiCrash, polyfillRequire);
Expand Down
1 change: 1 addition & 0 deletions packages/commandkit/bin/development.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export async function bootstrapDevelopmentServer(opts) {
silent: true,
entry: [src, '!dist', '!.commandkit', `!${outDir}`].filter(Boolean),
watch: watchMode,
cjsInterop: true,
async onSuccess() {
return await injectShims('.commandkit', main, false, requirePolyfill);
},
Expand Down
4 changes: 2 additions & 2 deletions packages/commandkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"ora": "^8.0.1",
"rfdc": "^1.3.1",
"rimraf": "^5.0.5",
"tsup": "^8.0.1"
"tsup": "^8.3.5",
"use-macro": "^1.0.1"
},
"devDependencies": {
"@types/node": "^22.10.2",
Expand All @@ -56,7 +57,6 @@
"tsconfig": "workspace:*",
"tsx": "^4.7.0",
"typescript": "^5.7.2",
"use-macro": "^1.0.1",
"vitest": "^1.2.1"
},
"peerDependencies": {
Expand Down
51 changes: 49 additions & 2 deletions packages/commandkit/src/CommandKit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import EventEmitter from 'node:events';
import { CommandHandler, EventHandler, ValidationHandler } from './handlers';
import type {
CommandKitData,
Expand All @@ -6,8 +7,10 @@ import type {
ReloadOptions,
} from './types';
import colors from './utils/colors';
import { CacheProvider } from './cache/CacheProvider';
import { MemoryCache } from './cache/MemoryCache';

export class CommandKit {
export class CommandKit extends EventEmitter {
#data: CommandKitData;

/**
Expand All @@ -29,9 +32,39 @@ export class CommandKit {
);
}

super();

options.debugCommands ??= process.env.NODE_ENV !== 'production';

if (
options.cacheProvider !== null &&
(!options.cacheProvider ||
!(options.cacheProvider instanceof CacheProvider))
) {
options.cacheProvider = new MemoryCache();
}

this.#data = options;

this.#init();
this.#init().then(() => {
// Increment client listeners count, as commandkit registers internal event listeners.
this.incrementClientListenersCount();
});
}

/**
* Resolves the current cache provider.
*/
getCacheProvider(): CacheProvider | null {
const provider = this.#data.cacheProvider;
return provider ?? null;
}

/**
* Whether or not to debug the command handler.
*/
isDebuggingCommands() {
return this.#data.debugCommands || false;
}

/**
Expand Down Expand Up @@ -177,4 +210,18 @@ export class CommandKit {
get devRoleIds(): string[] {
return this.#data.devRoleIds || [];
}

/**
* Increment the client listeners count.
*/
incrementClientListenersCount() {
this.#data.client.setMaxListeners(this.#data.client.getMaxListeners() + 1);
}

/**
* Decrement the client listeners count.
*/
decrementClientListenersCount() {
this.#data.client.setMaxListeners(this.#data.client.getMaxListeners() - 1);
}
}
7 changes: 7 additions & 0 deletions packages/commandkit/src/cache/CacheProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export abstract class CacheProvider {
abstract get<T>(key: string): Promise<T | undefined>;
abstract set<T>(key: string, value: T, ttl?: number): Promise<void>;
abstract exists(key: string): Promise<boolean>;
abstract delete(key: string): Promise<void>;
abstract clear(): Promise<void>;
}
46 changes: 46 additions & 0 deletions packages/commandkit/src/cache/MemoryCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { CacheProvider } from './CacheProvider';

export interface MemoryCacheEntry {
key: string;
value: any;
ttl?: number;
}

export class MemoryCache extends CacheProvider {
#cache = new Map<string, MemoryCacheEntry>();

public async get<T>(key: string): Promise<T | undefined> {
const entry = this.#cache.get(key);

if (!entry) {
return undefined;
}

if (entry.ttl && Date.now() > entry.ttl) {
this.#cache.delete(key);
return undefined;
}

return entry.value;
}

public async set<T>(key: string, value: T, ttl?: number): Promise<void> {
this.#cache.set(key, {
key,
value,
ttl: ttl != null ? Date.now() + ttl : undefined,
});
}

public async exists(key: string): Promise<boolean> {
return this.#cache.has(key);
}

public async delete(key: string): Promise<void> {
this.#cache.delete(key);
}

public async clear(): Promise<void> {
this.#cache.clear();
}
}
Loading

0 comments on commit 9db266b

Please sign in to comment.