Skip to content

Commit a90d632

Browse files
authored
Merge pull request #295 from underctrl-io/kv
feat: kv additional data type support
2 parents aadffe5 + 1e66bb8 commit a90d632

File tree

11 files changed

+772
-1030
lines changed

11 files changed

+772
-1030
lines changed

apps/test-bot/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.env
22
.commandkit
3-
compiled-commandkit.config.mjs
3+
compiled-commandkit.config.mjs
4+
commandkit*.db*

apps/test-bot/src/app/commands/(leveling)/xp.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
ChatInputCommandContext,
3-
CommandData,
4-
MessageCommandContext,
5-
} from 'commandkit';
1+
import { ChatInputCommandContext, CommandData } from 'commandkit';
62
import { database } from '@/database/store.ts';
73
import { cacheTag } from '@commandkit/cache';
84
import { AiCommand, AiConfig } from '@commandkit/ai';
@@ -27,7 +23,7 @@ async function getUserXP(guildId: string, userId: string) {
2723
const key = `xp:${guildId}:${userId}`;
2824
cacheTag(key);
2925

30-
const xp: number = (await database.get(key)) ?? 0;
26+
const xp = database.get<number>(key) ?? 0;
3127

3228
return xp;
3329
}

apps/test-bot/src/app/events/messageCreate/give-xp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export default async function (message: Message) {
77

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

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

14-
await database.set(key, newXp);
14+
database.set(key, newXp);
1515
await revalidateTag(key);
1616
}
Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,3 @@
1-
import { setTimeout } from 'node:timers/promises';
1+
import { openKV } from 'commandkit/kv';
22

3-
// Simulate a random latency between 30ms to 1.5s
4-
const randomLatency = () => setTimeout(Math.floor(Math.random() * 1500) + 100);
5-
6-
class DataStore {
7-
private store = new Map<string, any>();
8-
9-
async get(key: string) {
10-
await randomLatency();
11-
const value = this.store.get(key);
12-
13-
return value;
14-
}
15-
16-
async set(key: string, value: any) {
17-
this.store.set(key, value);
18-
}
19-
20-
async delete(key: string) {
21-
this.store.delete(key);
22-
}
23-
24-
async clear() {
25-
this.store.clear();
26-
}
27-
}
28-
29-
export const database = new DataStore();
3+
export const database = openKV();

apps/website/docs/guide/15-key-value-store/01-introduction.mdx

Lines changed: 44 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -5,187 +5,71 @@ description: Learn how to use CommandKit's built-in key-value store for persiste
55

66
# Key-Value Store
77

8-
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.
8+
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.
99

10-
## What is the KV Store?
10+
## Features
1111

12-
The KV store is a simple, persistent key-value storage solution that:
13-
14-
- **Persistent**: Data is stored in a SQLite database file
15-
- **Namespaced**: Organize data into logical groups using namespaces
16-
- **Type-safe**: Full TypeScript support with proper typing
17-
- **Iterable**: Use standard JavaScript iteration patterns
18-
- **Resource-safe**: Implements disposable patterns for automatic cleanup
19-
20-
## When to Use the KV Store
21-
22-
Use the KV store when you need to:
23-
24-
- Store user preferences and settings
25-
- Cache frequently accessed data persistently
26-
- Store bot configuration that needs to survive restarts
27-
- Keep track of user statistics and progress
28-
- Store temporary data that needs to persist between sessions
29-
30-
:::warning Node.js Version Requirement
31-
The KV store requires Node.js version that supports the `node:sqlite` module. Make sure you're using a compatible Node.js version.
32-
:::
33-
34-
## Basic Setup
35-
36-
The KV store is available directly from the CommandKit package:
37-
38-
```ts
39-
import { KV, openKV } from 'commandkit/kv';
40-
```
12+
- **JSON Serialization**: Store any JSON-serializable data types directly
13+
- **Dot Notation**: Access nested properties using dot notation (e.g., `user:123.settings.theme`)
14+
- **Namespaces**: Organize data into separate namespaces
15+
- **Expiration**: Set time-to-live (TTL) for automatic cleanup
16+
- **Transactions**: Execute multiple operations atomically
17+
- **Iteration**: Iterate over all key-value pairs
18+
- **Type Safety**: Full TypeScript support with strong typing
4119

4220
## Quick Start
4321

44-
Here's a simple example of how to use the KV store:
45-
46-
```ts
47-
import { openKV } from 'commandkit/kv';
22+
```typescript
23+
import { KV } from '@commandkit/kv';
4824

49-
// Create a new KV store instance (uses default database file)
50-
const kv = openKV();
25+
// Create a new KV store
26+
const kv = new KV('data.db');
5127

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

55-
// Or create in-memory store for caching
56-
const kv = openKV(':memory:');
57-
58-
// Store some data
59-
kv.set('user:123', JSON.stringify({ name: 'John', level: 5 }));
34+
// Use dot notation for nested properties
35+
kv.set('user:123.settings.theme', 'dark');
36+
kv.set('user:123.settings.notifications', true);
6037

6138
// Retrieve data
62-
const userData = kv.get('user:123');
63-
if (userData) {
64-
const user = JSON.parse(userData);
65-
console.log(`User ${user.name} is level ${user.level}`);
66-
}
67-
68-
// Check if a key exists
69-
if (kv.has('user:123')) {
70-
console.log('User data exists');
71-
}
72-
73-
// Get all keys
74-
const allKeys = kv.keys();
75-
console.log('All stored keys:', allKeys);
76-
77-
// Clean up when done
78-
kv.close();
79-
```
80-
81-
## Key Features
39+
const user = kv.get('user:123'); // { name: 'John', age: 30, settings: { theme: 'dark', notifications: true } }
40+
const theme = kv.get('user:123.settings.theme'); // 'dark'
8241

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

85-
Organize your data into logical groups:
86-
87-
```ts
45+
// Use namespaces
8846
const userKv = kv.namespace('users');
89-
const configKv = kv.namespace('config');
90-
91-
userKv.set('123', JSON.stringify({ name: 'John' }));
92-
configKv.set('theme', 'dark');
47+
userKv.set('123', { name: 'John', age: 30 });
9348
```
9449

95-
### 2. **Iteration Support**
50+
## Supported Data Types
9651

97-
Use standard JavaScript iteration patterns:
52+
The KV store supports storing and retrieving the following data types:
9853

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

105-
// Convert to array
106-
const entries = [...kv];
107-
```
108-
109-
### 3. **Automatic Resource Management**
63+
## Installation
11064

111-
The KV store implements disposable patterns:
65+
The KV store is included with CommandKit. No additional installation is required.
11266

113-
```ts
114-
// Using with statement (automatic cleanup)
115-
{
116-
using kv = openKV();
117-
kv.set('key', 'value');
118-
} // kv is automatically closed
119-
120-
// Using async/await with automatic disposal (fake promise wrapper)
121-
await using kv = openKV();
122-
kv.set('key', 'value');
123-
// kv is automatically closed when the block ends
67+
```bash
68+
npm install @commandkit/kv
12469
```
12570

126-
:::note Async Disposal
127-
The `async using` statement is just a fake promise wrapper around the synchronous `using` statement. The disposal is still synchronous.
128-
:::
129-
130-
### 4. **Expiration Support**
131-
132-
Store temporary data with automatic expiration:
133-
134-
```ts
135-
// Set data with expiration (1 hour)
136-
kv.setex('session:123', 'user_data', 60 * 60 * 1000);
137-
138-
// Set expiration for existing key (30 minutes)
139-
kv.expire('user:123', 30 * 60 * 1000);
140-
141-
// Check time to live
142-
const ttl = kv.ttl('user:123');
143-
if (ttl > 0) {
144-
console.log(`Expires in ${ttl}ms`);
145-
}
146-
```
147-
148-
### 5. **Transaction Support**
149-
150-
Execute multiple operations atomically:
151-
152-
```ts
153-
kv.transaction(() => {
154-
kv.set('user:123', JSON.stringify({ name: 'John' }));
155-
kv.set('user:456', JSON.stringify({ name: 'Jane' }));
156-
// If any operation fails, all changes are rolled back
157-
});
158-
```
159-
160-
### 6. **Bulk Operations**
161-
162-
Perform operations on multiple items:
163-
164-
```ts
165-
// Get all data as an object (excluding expired keys)
166-
const allData = kv.all();
167-
console.log('All data:', allData);
168-
169-
// Get all values (excluding expired keys)
170-
const values = kv.values();
171-
console.log('All values:', values);
172-
173-
// Count total entries (excluding expired keys)
174-
const count = kv.count();
175-
console.log(`Total entries: ${count}`);
176-
```
177-
178-
## Best Practices
179-
180-
1. **Use Meaningful Keys**: Use descriptive key names that make sense in your context
181-
2. **Serialize Complex Data**: Store objects as JSON strings
182-
3. **Use Namespaces**: Organize related data into namespaces
183-
4. **Handle Missing Data**: Always check if data exists before using it
184-
5. **Clean Up Resources**: Use disposable patterns or manually close connections
185-
6. **Backup Important Data**: Regularly backup your database files
186-
18771
## Next Steps
18872

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

0 commit comments

Comments
 (0)