Skip to content
Closed
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
24 changes: 24 additions & 0 deletions packages/bun-types/redis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,30 @@ declare module "bun" {
*/
getex(key: RedisClient.KeyLike): Promise<string | null>;

/**
* Increment the integer value of a key by the given number
* @param key The key to increment
* @param count The amount to increment by
* @returns Promise that resolves with the new value
*/
incrby(key: RedisClient.KeyLike, count: string | number): Promise<number>;

/**
* Decrement the integer value of a key by the given number
* @param key The key to decrement
* @param count The amount to decrement by
* @returns Promise that resolves with the new value
*/
decrby(key: RedisClient.KeyLike, count: string | number): Promise<number>;

/**
* Increment the float value of a key by the given amount
* @param key The key to increment
* @param count The amount to increment by (float or integer)
* @returns Promise that resolves with the new value as a string
*/
incrbyfloat(key: RedisClient.KeyLike, count: string | number): Promise<string>;

/**
* Ping the server
* @returns Promise that resolves with "PONG" if the server is reachable, or throws an error if the server is not reachable
Expand Down
12 changes: 12 additions & 0 deletions src/bun.js/api/valkey.classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ export default [
unsubscribe: { fn: "unsubscribe" },
punsubscribe: { fn: "punsubscribe" },
pubsub: { fn: "pubsub" },
incrby: {
fn: "incrby",
length: 2,
},
decrby: {
fn: "decrby",
length: 2,
},
incrbyfloat: {
fn: "incrbyfloat",
length: 2,
},
},
values: ["onconnect", "onclose", "connectionPromise", "hello"],
}),
Expand Down
3 changes: 3 additions & 0 deletions src/valkey/js_valkey.zig
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ pub const JSValkeyClient = struct {
pub const append = fns.append;
pub const bitcount = fns.bitcount;
pub const decr = fns.decr;
pub const decrby = fns.decrby;
pub const del = fns.del;
pub const dump = fns.dump;
pub const exists = fns.exists;
Expand All @@ -662,6 +663,8 @@ pub const JSValkeyClient = struct {
pub const hstrlen = fns.hstrlen;
pub const hvals = fns.hvals;
pub const incr = fns.incr;
pub const incrby = fns.incrby;
pub const incrbyfloat = fns.incrbyfloat;
pub const keys = fns.keys;
pub const llen = fns.llen;
pub const lpop = fns.lpop;
Expand Down
78 changes: 78 additions & 0 deletions src/valkey/js_valkey_functions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,84 @@ pub fn hmset(this: *JSValkeyClient, globalObject: *JSC.JSGlobalObject, callframe
return promise.toJS();
}

// Your INCRBY implementation
pub fn incrby(this: *JSValkeyClient, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const key = try callframe.argument(0).toBunString(globalObject);
defer key.deref();
const value = try callframe.argument(1).toBunString(globalObject);
defer value.deref();

const key_slice = key.toUTF8WithoutRef(bun.default_allocator);
defer key_slice.deinit();
const value_slice = value.toUTF8WithoutRef(bun.default_allocator);
defer value_slice.deinit();

// Send INCRBY command
const promise = this.send(
globalObject,
callframe.this(),
&.{
.command = "INCRBY",
.args = .{ .slices = &.{ key_slice, value_slice } },
},
) catch |err| {
return protocol.valkeyErrorToJS(globalObject, "Failed to send INCRBY command", err);
};
return promise.toJS();
}

// Your DECRBY implementation
pub fn decrby(this: *JSValkeyClient, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const key = try callframe.argument(0).toBunString(globalObject);
defer key.deref();
const value = try callframe.argument(1).toBunString(globalObject);
defer value.deref();

const key_slice = key.toUTF8WithoutRef(bun.default_allocator);
defer key_slice.deinit();
const value_slice = value.toUTF8WithoutRef(bun.default_allocator);
defer value_slice.deinit();

// Send DECRBY command
const promise = this.send(
globalObject,
callframe.this(),
&.{
.command = "DECRBY",
.args = .{ .slices = &.{ key_slice, value_slice } },
},
) catch |err| {
return protocol.valkeyErrorToJS(globalObject, "Failed to send DECRBY command", err);
};
return promise.toJS();
}

// Your INCRBYFLOAT implementation
pub fn incrbyfloat(this: *JSValkeyClient, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const key = try callframe.argument(0).toBunString(globalObject);
defer key.deref();
const value = try callframe.argument(1).toBunString(globalObject);
defer value.deref();

const key_slice = key.toUTF8WithoutRef(bun.default_allocator);
defer key_slice.deinit();
const value_slice = value.toUTF8WithoutRef(bun.default_allocator);
defer value_slice.deinit();

// Send INCRBYFLOAT command
const promise = this.send(
globalObject,
callframe.this(),
&.{
.command = "INCRBYFLOAT",
.args = .{ .slices = &.{ key_slice, value_slice } },
},
) catch |err| {
return protocol.valkeyErrorToJS(globalObject, "Failed to send INCRBYFLOAT command", err);
};
return promise.toJS();
}

// Implement ping (send a PING command with an optional message)
pub fn ping(this: *JSValkeyClient, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
var message_buf: [1]JSArgument = undefined;
Expand Down
48 changes: 48 additions & 0 deletions test/js/valkey/valkey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ describe.skipIf(!isEnabled)("Valkey Redis Client", () => {
expect(decrementedValue).toBe(10);
});

test("should increment and decrement counters by a specific amount", async () => {
const redis = ctx.redis;
const counterKey = "counter";
await redis.set(counterKey, "10");

// INCRBY should increment by a specific amount
const incrementedValue = await redis.incrby(counterKey, 5);
expect(incrementedValue).toBeDefined();
expect(typeof incrementedValue).toBe("number");
expect(incrementedValue).toBe(15);

// INCRBY (with negative amount) should decrement by a specific amount
const decrementedValue = await redis.incrby(counterKey, -3);
expect(decrementedValue).toBeDefined();
expect(typeof decrementedValue).toBe("number");
expect(decrementedValue).toBe(12);

// DECRBY should decrement by a specific amount
const decrementedValue2 = await redis.decrby(counterKey, 3);
expect(decrementedValue2).toBeDefined();
expect(typeof decrementedValue2).toBe("number");
expect(decrementedValue2).toBe(9);

// DECRBY (with negative amount) should increment by a specific amount
const incrementedValue2 = await redis.decrby(counterKey, -3);
expect(incrementedValue2).toBeDefined();
expect(typeof incrementedValue2).toBe("number");
expect(incrementedValue2).toBe(12);
});

test("should increment and decrement counters by a specific amount with floats", async () => {
const redis = ctx.redis;
const counterKey = "counter";
await redis.set(counterKey, "10");

// INCRBYFLOAT should increment by a specific amount
const incrementedValue = await redis.incrbyfloat(counterKey, 2.5);
expect(incrementedValue).toBeDefined();
expect(typeof incrementedValue).toBe("string");
expect(incrementedValue).toBe("12.5");

// INCRBYFLOAT (with negative amount) should decrement by a specific amount
const decrementedValue = await redis.incrbyfloat(counterKey, -1.5);
expect(decrementedValue).toBeDefined();
expect(typeof decrementedValue).toBe("string");
expect(decrementedValue).toBe("11");
});

test("should manage key expiration", async () => {
const redis = ctx.redis;
// Set a key first
Expand Down