Skip to content

Commit ac612cc

Browse files
committed
feat(kv): add math method
1 parent 0bab134 commit ac612cc

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed

packages/commandkit/package.json

Lines changed: 2 additions & 2 deletions
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": {
@@ -190,4 +190,4 @@
190190
"engines": {
191191
"node": ">=24"
192192
}
193-
}
193+
}

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)