Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/test-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
.commandkit
compiled-commandkit.config.mjs
compiled-commandkit.config.mjs
commandkit*.db*
8 changes: 2 additions & 6 deletions apps/test-bot/src/app/commands/(leveling)/xp.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
ChatInputCommandContext,
CommandData,
MessageCommandContext,
} from 'commandkit';
import { ChatInputCommandContext, CommandData } from 'commandkit';
import { database } from '@/database/store.ts';
import { cacheTag } from '@commandkit/cache';
import { AiCommand, AiConfig } from '@commandkit/ai';
Expand All @@ -27,7 +23,7 @@ async function getUserXP(guildId: string, userId: string) {
const key = `xp:${guildId}:${userId}`;
cacheTag(key);

const xp: number = (await database.get(key)) ?? 0;
const xp = database.get<number>(key) ?? 0;

return xp;
}
Expand Down
4 changes: 2 additions & 2 deletions apps/test-bot/src/app/events/messageCreate/give-xp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ export default async function (message: Message) {

const key = `xp:${message.guildId}:${message.author.id}`;

const oldXp = (await database.get(key)) ?? 0;
const oldXp = database.get<number>(key) ?? 0;
const xp = Math.floor(Math.random() * 10) + 1;
const newXp = oldXp + xp;

await database.set(key, newXp);
database.set(key, newXp);
await revalidateTag(key);
}
30 changes: 2 additions & 28 deletions apps/test-bot/src/database/store.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,3 @@
import { setTimeout } from 'node:timers/promises';
import { openKV } from 'commandkit/kv';

// Simulate a random latency between 30ms to 1.5s
const randomLatency = () => setTimeout(Math.floor(Math.random() * 1500) + 100);

class DataStore {
private store = new Map<string, any>();

async get(key: string) {
await randomLatency();
const value = this.store.get(key);

return value;
}

async set(key: string, value: any) {
this.store.set(key, value);
}

async delete(key: string) {
this.store.delete(key);
}

async clear() {
this.store.clear();
}
}

export const database = new DataStore();
export const database = openKV();
204 changes: 44 additions & 160 deletions apps/website/docs/guide/15-key-value-store/01-introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,187 +5,71 @@ description: Learn how to use CommandKit's built-in key-value store for persiste

# Key-Value Store

CommandKit provides a built-in key-value store implementation using SQLite for persistent data storage. This guide will show you how to use the KV store effectively in your bot for storing configuration, user data, and other persistent information.
The CommandKit Key-Value (KV) store provides a simple, persistent storage solution using SQLite. It supports storing any JSON-serializable data types directly, including objects, arrays, dates, maps, sets, and more.

## What is the KV Store?
## Features

The KV store is a simple, persistent key-value storage solution that:

- **Persistent**: Data is stored in a SQLite database file
- **Namespaced**: Organize data into logical groups using namespaces
- **Type-safe**: Full TypeScript support with proper typing
- **Iterable**: Use standard JavaScript iteration patterns
- **Resource-safe**: Implements disposable patterns for automatic cleanup

## When to Use the KV Store

Use the KV store when you need to:

- Store user preferences and settings
- Cache frequently accessed data persistently
- Store bot configuration that needs to survive restarts
- Keep track of user statistics and progress
- Store temporary data that needs to persist between sessions

:::warning Node.js Version Requirement
The KV store requires Node.js version that supports the `node:sqlite` module. Make sure you're using a compatible Node.js version.
:::

## Basic Setup

The KV store is available directly from the CommandKit package:

```ts
import { KV, openKV } from 'commandkit/kv';
```
- **JSON Serialization**: Store any JSON-serializable data types directly
- **Dot Notation**: Access nested properties using dot notation (e.g., `user:123.settings.theme`)
- **Namespaces**: Organize data into separate namespaces
- **Expiration**: Set time-to-live (TTL) for automatic cleanup
- **Transactions**: Execute multiple operations atomically
- **Iteration**: Iterate over all key-value pairs
- **Type Safety**: Full TypeScript support with strong typing

## Quick Start

Here's a simple example of how to use the KV store:

```ts
import { openKV } from 'commandkit/kv';
```typescript
import { KV } from '@commandkit/kv';

// Create a new KV store instance (uses default database file)
const kv = openKV();
// Create a new KV store
const kv = new KV('data.db');

// Or create with custom database file
const kv = openKV('bot-data.db');
// Store any data type directly
kv.set('user:123', { name: 'John', age: 30 });
kv.set('counter', 42);
kv.set('active', true);
kv.set('tags', ['javascript', 'typescript']);

// Or create in-memory store for caching
const kv = openKV(':memory:');

// Store some data
kv.set('user:123', JSON.stringify({ name: 'John', level: 5 }));
// Use dot notation for nested properties
kv.set('user:123.settings.theme', 'dark');
kv.set('user:123.settings.notifications', true);

// Retrieve data
const userData = kv.get('user:123');
if (userData) {
const user = JSON.parse(userData);
console.log(`User ${user.name} is level ${user.level}`);
}

// Check if a key exists
if (kv.has('user:123')) {
console.log('User data exists');
}

// Get all keys
const allKeys = kv.keys();
console.log('All stored keys:', allKeys);

// Clean up when done
kv.close();
```

## Key Features
const user = kv.get('user:123'); // { name: 'John', age: 30, settings: { theme: 'dark', notifications: true } }
const theme = kv.get('user:123.settings.theme'); // 'dark'

### 1. **Namespaces**
// Set expiration
kv.setex('session:123', { userId: 123, token: 'abc123' }, 60 * 60 * 1000); // 1 hour

Organize your data into logical groups:

```ts
// Use namespaces
const userKv = kv.namespace('users');
const configKv = kv.namespace('config');

userKv.set('123', JSON.stringify({ name: 'John' }));
configKv.set('theme', 'dark');
userKv.set('123', { name: 'John', age: 30 });
```

### 2. **Iteration Support**
## Supported Data Types

Use standard JavaScript iteration patterns:
The KV store supports storing and retrieving the following data types:

```ts
// Iterate over all key-value pairs
for (const [key, value] of kv) {
console.log(`${key}: ${value}`);
}
- **Primitives**: `string`, `number`, `boolean`, `bigint`, `null`, `undefined`
- **Objects**: Plain objects, nested objects
- **Arrays**: Any array of supported types
- **Dates**: JavaScript Date objects
- **Collections**: `Map`, `Set`
- **Buffers**: Node.js Buffer objects
- **Regular Expressions**: RegExp objects
- **Functions**: Function objects (serialized as strings)

// Convert to array
const entries = [...kv];
```

### 3. **Automatic Resource Management**
## Installation

The KV store implements disposable patterns:
The KV store is included with CommandKit. No additional installation is required.

```ts
// Using with statement (automatic cleanup)
{
using kv = openKV();
kv.set('key', 'value');
} // kv is automatically closed

// Using async/await with automatic disposal (fake promise wrapper)
await using kv = openKV();
kv.set('key', 'value');
// kv is automatically closed when the block ends
```bash
npm install @commandkit/kv
```

:::note Async Disposal
The `async using` statement is just a fake promise wrapper around the synchronous `using` statement. The disposal is still synchronous.
:::

### 4. **Expiration Support**

Store temporary data with automatic expiration:

```ts
// Set data with expiration (1 hour)
kv.setex('session:123', 'user_data', 60 * 60 * 1000);

// Set expiration for existing key (30 minutes)
kv.expire('user:123', 30 * 60 * 1000);

// Check time to live
const ttl = kv.ttl('user:123');
if (ttl > 0) {
console.log(`Expires in ${ttl}ms`);
}
```

### 5. **Transaction Support**

Execute multiple operations atomically:

```ts
kv.transaction(() => {
kv.set('user:123', JSON.stringify({ name: 'John' }));
kv.set('user:456', JSON.stringify({ name: 'Jane' }));
// If any operation fails, all changes are rolled back
});
```

### 6. **Bulk Operations**

Perform operations on multiple items:

```ts
// Get all data as an object (excluding expired keys)
const allData = kv.all();
console.log('All data:', allData);

// Get all values (excluding expired keys)
const values = kv.values();
console.log('All values:', values);

// Count total entries (excluding expired keys)
const count = kv.count();
console.log(`Total entries: ${count}`);
```

## Best Practices

1. **Use Meaningful Keys**: Use descriptive key names that make sense in your context
2. **Serialize Complex Data**: Store objects as JSON strings
3. **Use Namespaces**: Organize related data into namespaces
4. **Handle Missing Data**: Always check if data exists before using it
5. **Clean Up Resources**: Use disposable patterns or manually close connections
6. **Backup Important Data**: Regularly backup your database files

## Next Steps

- Learn about [basic operations](./02-basic-operations.mdx)
- Explore [namespaces](./03-namespaces.mdx)
- Understand [advanced features](./04-advanced-features.mdx) including transactions
- [Basic Operations](./02-basic-operations.md) - Learn about core KV operations
- [Namespaces](./03-namespaces.md) - Organize data with namespaces
- [Advanced Features](./04-advanced-features.md) - Expiration, transactions, and more
Loading