Skip to content

Commit c7ee8d1

Browse files
authored
Merge pull request #416 from underctrl-io/kv-math
feat: add kv math method
2 parents 0bab134 + 8232acb commit c7ee8d1

File tree

11 files changed

+195
-28
lines changed

11 files changed

+195
-28
lines changed

apps/website/docs/api-reference/commandkit/classes/kv.mdx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
1313

1414
## KV
1515

16-
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="42" packageName="commandkit" />
16+
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="47" packageName="commandkit" />
1717

1818
A key-value store implementation using SQLite
1919

@@ -56,6 +56,7 @@ class KV implements Disposable, AsyncDisposable {
5656
get(key: string) => T | undefined;
5757
set(key: string, value: any) => void;
5858
setex(key: string, value: any, ttl: number) => void;
59+
math(key: string, operator: KvMathOperator, value: number | bigint) => number | bigint;
5960
expire(key: string, ttl: number) => boolean;
6061
ttl(key: string) => number;
6162
delete(key: string) => void;
@@ -186,6 +187,34 @@ kv.setex('temp:data', { cached: true, timestamp: Date.now() }, 5 * 60 * 1000);
186187
// Use dot notation with expiration
187188
kv.setex('user:123.temp_settings', { theme: 'light' }, 30 * 60 * 1000);
188189
```
190+
### math
191+
192+
<MemberInfo kind="method" type={`(key: string, operator: <a href='/docs/next/api-reference/commandkit/types/kv-math-operator#kvmathoperator'>KvMathOperator</a>, value: number | bigint) => number | bigint`} />
193+
194+
Performs mathematical operations on numeric values in the KV store
195+
196+
197+
198+
*Example*
199+
200+
```typescript
201+
// Initialize a counter
202+
kv.set('counter', 10);
203+
204+
// Increment by 5
205+
const result1 = kv.math('counter', '+', 5); // 15
206+
207+
// Multiply by 2
208+
const result2 = kv.math('counter', '*', 2); // 30
209+
210+
// Use with bigint
211+
kv.set('big_counter', BigInt(1000));
212+
const result3 = kv.math('big_counter', '+', BigInt(500)); // 1500n
213+
214+
// Use with dot notation
215+
kv.set('user:123', { score: 100, level: 5 });
216+
const result4 = kv.math('user:123.score', '+', 50); // 150
217+
```
189218
### expire
190219

191220
<MemberInfo kind="method" type={`(key: string, ttl: number) => boolean`} />

apps/website/docs/api-reference/commandkit/functions/open-kv.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
1313

1414
## openKV
1515

16-
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="620" packageName="commandkit" />
16+
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="731" packageName="commandkit" />
1717

1818
Opens a new KV instance
1919

apps/website/docs/api-reference/commandkit/interfaces/kv-options.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
1313

1414
## KvOptions
1515

16-
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="10" packageName="commandkit" />
16+
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="15" packageName="commandkit" />
1717

1818
Configuration options for the KV store
1919

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: "KvMathOperator"
3+
isDefaultIndex: false
4+
generated: true
5+
---
6+
7+
import MemberInfo from '@site/src/components/MemberInfo';
8+
import GenerationInfo from '@site/src/components/GenerationInfo';
9+
import MemberDescription from '@site/src/components/MemberDescription';
10+
11+
<!-- This file was generated from the CommandKit source. Do not modify. Instead, re-run the "docgen" script -->
12+
13+
14+
## KvMathOperator
15+
16+
<GenerationInfo sourceFile="packages/commandkit/src/kv/kv.ts" sourceLine="10" packageName="commandkit" />
17+
18+
Mathematical operators supported by the KV math method
19+
20+
```ts title="Signature"
21+
type KvMathOperator = '+' | '-' | '*' | '/' | '^' | '%'
22+
```

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The CommandKit Key-Value (KV) store provides a simple, persistent storage soluti
2020
## Quick Start
2121

2222
```typescript
23-
import { KV } from '@commandkit/kv';
23+
import { KV } from 'commandkit/kv';
2424

2525
// Create a new KV store
2626
const kv = new KV('data.db');
@@ -65,7 +65,7 @@ The KV store supports storing and retrieving the following data types:
6565
The KV store is included with CommandKit. No additional installation is required.
6666

6767
```bash
68-
npm install @commandkit/kv
68+
npm install commandkit/kv
6969
```
7070

7171
## Next Steps

apps/website/docs/guide/15-key-value-store/02-basic-operations.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This guide covers the fundamental operations of the CommandKit KV store, includi
1010
## Creating a KV Store
1111

1212
```typescript
13-
import { KV, openKV } from '@commandkit/kv';
13+
import { KV, openKV } from 'commandkit/kv';
1414

1515
// Create with custom database file
1616
const kv = new KV('my-bot-data.db');

apps/website/ignore_build.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,37 @@ const { execSync } = require('child_process');
22

33
// Check if there are changes in the website directory
44
function hasWebsiteChanges() {
5-
try {
6-
// Get the list of changed files in the current commit compared to the base branch
7-
const baseBranch = process.env.BASE_BRANCH || 'main';
8-
const changedFiles = execSync(`git diff --name-only origin/${baseBranch}...HEAD`, { encoding: 'utf8' });
5+
try {
6+
// Get the list of changed files in the current commit compared to the base branch
7+
const baseBranch = process.env.BASE_BRANCH || 'main';
8+
const changedFiles = execSync(
9+
`git diff --name-only origin/${baseBranch}...HEAD`,
10+
{ encoding: 'utf8' },
11+
);
912

10-
// Check if any of the changed files are in the website directory
11-
const websiteChanges = changedFiles
12-
.split('\n')
13-
.filter(file => file.trim() && file.startsWith('apps/website/'));
13+
// Check if any of the changed files are in the website directory
14+
const websiteChanges = changedFiles
15+
.split('\n')
16+
.filter((file) => file.trim() && file.startsWith('apps/website/'));
1417

15-
return websiteChanges.length > 0;
16-
} catch (error) {
17-
// If we can't determine changes, allow the build to proceed
18-
console.log('Could not determine changes, allowing build to proceed');
19-
return true;
20-
}
18+
return websiteChanges.length > 0;
19+
} catch (error) {
20+
// If we can't determine changes, allow the build to proceed
21+
console.log('Could not determine changes, allowing build to proceed');
22+
return true;
23+
}
2124
}
2225

2326
// Ignore build if:
2427
// 1. Branch is owned by renovate, OR
2528
// 2. No changes in website directory
26-
const shouldIgnore = process.env.BRANCH?.includes('renovate') || !hasWebsiteChanges();
29+
const shouldIgnore =
30+
process.env.BRANCH?.includes('renovate') || !hasWebsiteChanges();
2731

2832
process.exitCode = shouldIgnore ? 0 : 1;
2933

3034
if (shouldIgnore) {
31-
console.log('Build ignored: No relevant changes detected');
35+
console.log('Build ignored: No relevant changes detected');
3236
} else {
33-
console.log('Build proceeding: Website changes detected');
34-
}
37+
console.log('Build proceeding: Website changes detected');
38+
}

apps/website/src/plugins/llms-txt.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ module.exports = function (context) {
2929
entry.name.endsWith('.md')
3030
) {
3131
const content = await fs.promises.readFile(fullPath, 'utf8');
32-
const { data: frontmatter, content: markdownContent } = matter(content);
32+
const { data: frontmatter, content: markdownContent } =
33+
matter(content);
3334

3435
// Get relative path from guide directory
3536
const relativePath = path

packages/commandkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
"picocolors": "^1.1.1",
175175
"rfdc": "^1.3.1",
176176
"rimraf": "^6.0.0",
177-
"tsdown": "^0.13.0",
177+
"tsdown": "^0.13.1",
178178
"use-macro": "^1.1.0"
179179
},
180180
"devDependencies": {

packages/commandkit/src/kv/kv.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { getNestedValue, setNestedValue } from './dotprops';
44

55
export type { SerializedValue } from './serde';
66

7+
/**
8+
* Mathematical operators supported by the KV math method
9+
*/
10+
export type KvMathOperator = '+' | '-' | '*' | '/' | '^' | '%';
11+
712
/**
813
* Configuration options for the KV store
914
*/
@@ -309,6 +314,112 @@ export class KV implements Disposable, AsyncDisposable {
309314
}
310315
}
311316

317+
/**
318+
* Performs mathematical operations on numeric values in the KV store
319+
*
320+
* @param key - The key to perform math operation on (supports dot notation for nested properties)
321+
* @param operator - The mathematical operator to apply
322+
* @param value - The value to use in the operation
323+
* @returns The updated value after the mathematical operation
324+
* @throws Error if the existing value is not numeric or if the operation is invalid
325+
*
326+
* @example
327+
* ```typescript
328+
* // Initialize a counter
329+
* kv.set('counter', 10);
330+
*
331+
* // Increment by 5
332+
* const result1 = kv.math('counter', '+', 5); // 15
333+
*
334+
* // Multiply by 2
335+
* const result2 = kv.math('counter', '*', 2); // 30
336+
*
337+
* // Use with bigint
338+
* kv.set('big_counter', BigInt(1000));
339+
* const result3 = kv.math('big_counter', '+', BigInt(500)); // 1500n
340+
*
341+
* // Use with dot notation
342+
* kv.set('user:123', { score: 100, level: 5 });
343+
* const result4 = kv.math('user:123.score', '+', 50); // 150
344+
* ```
345+
*/
346+
public math(
347+
key: string,
348+
operator: KvMathOperator,
349+
value: number | bigint,
350+
): number | bigint {
351+
const existingValue = this.get(key);
352+
353+
if (existingValue === undefined) {
354+
throw new Error(`Key '${key}' does not exist`);
355+
}
356+
357+
if (
358+
typeof existingValue !== 'number' &&
359+
typeof existingValue !== 'bigint'
360+
) {
361+
throw new Error(
362+
`Value at key '${key}' is not numeric. Expected number or bigint, got ${typeof existingValue}`,
363+
);
364+
}
365+
366+
// Handle mixed number/bigint operations by converting to bigint
367+
const isBigIntOperation =
368+
typeof existingValue === 'bigint' || typeof value === 'bigint';
369+
370+
const existing = isBigIntOperation
371+
? typeof existingValue === 'bigint'
372+
? existingValue
373+
: BigInt(existingValue)
374+
: (existingValue as number);
375+
const operand = isBigIntOperation
376+
? typeof value === 'bigint'
377+
? value
378+
: BigInt(value)
379+
: (value as number);
380+
381+
let result: number | bigint;
382+
383+
switch (operator) {
384+
case '+':
385+
result = (existing as any) + (operand as any);
386+
break;
387+
case '-':
388+
result = (existing as any) - (operand as any);
389+
break;
390+
case '*':
391+
result = (existing as any) * (operand as any);
392+
break;
393+
case '/':
394+
if (operand === 0 || operand === 0n) {
395+
throw new Error('Division by zero');
396+
}
397+
result = (existing as any) / (operand as any);
398+
break;
399+
case '^':
400+
if (isBigIntOperation && operand < 0n) {
401+
throw new Error(
402+
'Exponentiation with negative exponent is not supported for bigint',
403+
);
404+
}
405+
result = (existing as any) ** (operand as any);
406+
break;
407+
case '%':
408+
if (operand === 0 || operand === 0n) {
409+
throw new Error('Modulo by zero');
410+
}
411+
result = (existing as any) % (operand as any);
412+
break;
413+
default:
414+
throw new Error(`Invalid operator: ${operator}`);
415+
}
416+
417+
// Update the value in the store
418+
this.set(key, result);
419+
420+
return result;
421+
}
422+
312423
/**
313424
* Sets expiration for an existing key
314425
*

0 commit comments

Comments
 (0)