diff --git a/packages/client/lib/commands/HEXPIRE.spec.ts b/packages/client/lib/commands/HEXPIRE.spec.ts new file mode 100644 index 00000000000..83afa14f3b6 --- /dev/null +++ b/packages/client/lib/commands/HEXPIRE.spec.ts @@ -0,0 +1,40 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIRE from './HEXPIRE'; + +describe('HEXPIRE', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', 'field', 1), + ['HEXPIRE', 'key', '1', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', ['field1', 'field2'], 1), + ['HEXPIRE', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HEXPIRE.transformArguments('key', 'field1', 1, 'NX'), + ['HEXPIRE', 'key', '1', 'NX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hExpire', async client => { + assert.equal( + await client.hExpire('key', 'field', 0), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIRE.ts b/packages/client/lib/commands/HEXPIRE.ts new file mode 100644 index 00000000000..228b7931520 --- /dev/null +++ b/packages/client/lib/commands/HEXPIRE.ts @@ -0,0 +1,36 @@ +import { Command, NumberReply, ArrayReply, RedisArgument } from "../RESP/types"; +import { RedisVariadicArgument, pushVariadicArgument } from "./generic-transformers"; + +export const HASH_EXPIRATION = { + /** The field does not exist */ + FIELD_NOT_EXISTS: -2, + /** Specified NX | XX | GT | LT condition not met */ + CONDITION_NOT_MET: 0, + /** Expiration time was set or updated */ + UPDATED: 1, + /** Field deleted because the specified expiration time is in the past */ + DELETED: 2 +} as const; + +export type HashExpiration = typeof HASH_EXPIRATION[keyof typeof HASH_EXPIRATION]; + +export type HashExpirationReply = NumberReply; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + seconds: number, + mode?: 'NX' | 'XX' | 'GT' | 'LT', + ) { + const args = ['HEXPIRE', key, seconds.toString()]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HEXPIREAT.spec.ts b/packages/client/lib/commands/HEXPIREAT.spec.ts new file mode 100644 index 00000000000..8fc5ebb4b15 --- /dev/null +++ b/packages/client/lib/commands/HEXPIREAT.spec.ts @@ -0,0 +1,48 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIREAT from './HEXPIREAT'; + +describe('HEXPIREAT', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string + number', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', 'field', 1), + ['HEXPIREAT', 'key', '1', '1', 'field'] + ); + }); + + it('array + number', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', ['field1', 'field2'], 1), + ['HEXPIREAT', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('date', () => { + const d = new Date(); + assert.deepEqual( + HEXPIREAT.transformArguments('key', ['field1'], d), + ['HEXPIREAT', 'key', Math.floor(d.getTime() / 1000).toString(), '1', 'field1'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HEXPIREAT.transformArguments('key', 'field1', 1, 'GT'), + ['HEXPIREAT', 'key', '1', 'GT', 1, 'field1'] + ); + }); + }); + + testUtils.testAll('expireAt', async client => { + assert.equal( + await client.hExpireAt('key', 'field', 1), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIREAT.ts b/packages/client/lib/commands/HEXPIREAT.ts new file mode 100644 index 00000000000..e4b062ae8b3 --- /dev/null +++ b/packages/client/lib/commands/HEXPIREAT.ts @@ -0,0 +1,23 @@ +import { RedisArgument, ArrayReply, Command } from '../RESP/types'; +import { HashExpirationReply } from './HEXPIRE'; +import { RedisVariadicArgument, pushVariadicArgument, transformEXAT } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + timestamp: number | Date, + mode?: 'NX' | 'XX' | 'GT' | 'LT' + ) { + const args = ['HEXPIREAT', key, transformEXAT(timestamp)]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HEXPIRETIME.spec.ts b/packages/client/lib/commands/HEXPIRETIME.spec.ts new file mode 100644 index 00000000000..ffed3232a91 --- /dev/null +++ b/packages/client/lib/commands/HEXPIRETIME.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HEXPIRETIME from './HEXPIRETIME'; + +describe('HEXPIRETIME', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HEXPIRETIME.transformArguments('key', 'field'), + ['HEXPIRETIME', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HEXPIRETIME.transformArguments('key', ['field1', 'field2']), + ['HEXPIRETIME', 'key', '2', 'field1', 'field2'] + ); + }); + }) + + testUtils.testAll('hExpireTime', async client => { + assert.equal( + await client.hExpireTime('key', 'field'), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HEXPIRETIME.ts b/packages/client/lib/commands/HEXPIRETIME.ts new file mode 100644 index 00000000000..c17cc12d17a --- /dev/null +++ b/packages/client/lib/commands/HEXPIRETIME.ts @@ -0,0 +1,11 @@ +import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HEXPIRETIME', key], fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPERSIST.spec.ts b/packages/client/lib/commands/HPERSIST.spec.ts new file mode 100644 index 00000000000..a7c968ffaad --- /dev/null +++ b/packages/client/lib/commands/HPERSIST.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPERSIST from './HPERSIST'; + +describe('HPERSIST', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPERSIST.transformArguments('key', 'field'), + ['HPERSIST', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPERSIST.transformArguments('key', ['field1', 'field2']), + ['HPERSIST', 'key', '2', 'field1', 'field2'] + ); + }); + }) + + testUtils.testAll('hPersist', async client => { + assert.equal( + await client.hPersist('key', 'field'), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPERSIST.ts b/packages/client/lib/commands/HPERSIST.ts new file mode 100644 index 00000000000..97fd8b6c0dd --- /dev/null +++ b/packages/client/lib/commands/HPERSIST.ts @@ -0,0 +1,23 @@ +import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export const HASH_FIELD_PERSIST = { + /** The expiration time was removed */ + EXPIRATION_REMOVED: 1, + /** The field has no expiration time */ + NO_EXPIRATION_TIME: -1, + /** The field does not exist */ + FIELD_NOT_EXISTS: -2, +} as const; + +export type HashFieldPersist = typeof HASH_FIELD_PERSIST[keyof typeof HASH_FIELD_PERSIST]; + +export type HashFieldPersistReply = NumberReply; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HPERSIST', key], fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPEXPIRE.spec.ts b/packages/client/lib/commands/HPEXPIRE.spec.ts new file mode 100644 index 00000000000..4a60813b436 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRE.spec.ts @@ -0,0 +1,40 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIRE from './HPEXPIRE'; + +describe('HPEXPIRE', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', 'field', 1), + ['HPEXPIRE', 'key', '1', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', ['field1', 'field2'], 1), + ['HPEXPIRE', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HPEXPIRE.transformArguments('key', ['field1'], 1, 'NX'), + ['HPEXPIRE', 'key', '1', 'NX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hpRxpire', async client => { + assert.equal( + await client.hpExpire('key', 'field', 0), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIRE.ts b/packages/client/lib/commands/HPEXPIRE.ts new file mode 100644 index 00000000000..47cea066844 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRE.ts @@ -0,0 +1,22 @@ +import { RedisArgument, ArrayReply, Command } from '../RESP/types'; +import { HashExpirationReply } from './HEXPIRE'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + ms: number, + mode?: 'NX' | 'XX' | 'GT' | 'LT', + ) { + const args = ['HPEXPIRE', key, ms.toString()]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPEXPIREAT.spec.ts b/packages/client/lib/commands/HPEXPIREAT.spec.ts new file mode 100644 index 00000000000..bdf916bd3de --- /dev/null +++ b/packages/client/lib/commands/HPEXPIREAT.spec.ts @@ -0,0 +1,48 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIREAT from './HPEXPIREAT'; + +describe('HPEXPIREAT', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string + number', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', 'field', 1), + ['HPEXPIREAT', 'key', '1', '1', 'field'] + ); + }); + + it('array + number', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1', 'field2'], 1), + ['HPEXPIREAT', 'key', '1', '2', 'field1', 'field2'] + ); + }); + + it('date', () => { + const d = new Date(); + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1'], d), + ['HPEXPIREAT', 'key', d.getTime().toString(), '1', 'field1'] + ); + }); + + it('with set option', () => { + assert.deepEqual( + HPEXPIREAT.transformArguments('key', ['field1'], 1, 'XX'), + ['HPEXPIREAT', 'key', '1', 'XX', '1', 'field1'] + ); + }); + }); + + testUtils.testAll('hpExpireAt', async client => { + assert.equal( + await client.hpExpireAt('key', 'field', 1), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIREAT.ts b/packages/client/lib/commands/HPEXPIREAT.ts new file mode 100644 index 00000000000..f05a0fc2f53 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIREAT.ts @@ -0,0 +1,23 @@ +import { RedisArgument, ArrayReply, Command } from '../RESP/types'; +import { HashExpirationReply } from './HEXPIRE'; +import { RedisVariadicArgument, pushVariadicArgument, transformEXAT } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments( + key: RedisArgument, + fields: RedisVariadicArgument, + timestamp: number | Date, + mode?: 'NX' | 'XX' | 'GT' | 'LT' + ) { + const args = ['HPEXPIREAT', key, transformEXAT(timestamp)]; + + if (mode) { + args.push(mode); + } + + return pushVariadicArgument(args, fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPEXPIRETIME.spec.ts b/packages/client/lib/commands/HPEXPIRETIME.spec.ts new file mode 100644 index 00000000000..4fbdb4b97b6 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRETIME.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPEXPIRETIME from './HPEXPIRETIME'; + +describe('HPEXPIRETIME', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPEXPIRETIME.transformArguments('key', 'field'), + ['HPEXPIRETIME', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPEXPIRETIME.transformArguments('key', ['field1', 'field2']), + ['HPEXPIRETIME', 'key', '2', 'field1', 'field2'] + ); + }); + }); + + testUtils.testAll('hpExpireTime', async client => { + assert.equal( + await client.hpExpireTime('key', 'field'), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPEXPIRETIME.ts b/packages/client/lib/commands/HPEXPIRETIME.ts new file mode 100644 index 00000000000..93b6f5a7ee6 --- /dev/null +++ b/packages/client/lib/commands/HPEXPIRETIME.ts @@ -0,0 +1,18 @@ +import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export const HASH_FIELD_EXPIRETIME = { + /** The field does not exist */ + FIELD_NOT_EXISTS: -2, + /** The field has no expiration time */ + NO_EXPIRATION_TIME: -1, +} as const; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HPEXPIRETIME', key], fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HPTTL.spec.ts b/packages/client/lib/commands/HPTTL.spec.ts new file mode 100644 index 00000000000..949802091ca --- /dev/null +++ b/packages/client/lib/commands/HPTTL.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HPTTL from './HPTTL'; + +describe('HPTTL', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HPTTL.transformArguments('key', 'field'), + ['HPTTL', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HPTTL.transformArguments('key', ['field1', 'field2']), + ['HPTTL', 'key', '2', 'field1', 'field2'] + ); + }); + }); + + testUtils.testAll('hpTTL', async client => { + assert.equal( + await client.hpTTL('key', 'field'), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HPTTL.ts b/packages/client/lib/commands/HPTTL.ts new file mode 100644 index 00000000000..584c7e75b91 --- /dev/null +++ b/packages/client/lib/commands/HPTTL.ts @@ -0,0 +1,11 @@ +import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HPTTL', key], fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/HTTL.spec.ts b/packages/client/lib/commands/HTTL.spec.ts new file mode 100644 index 00000000000..5b3dbfa0916 --- /dev/null +++ b/packages/client/lib/commands/HTTL.spec.ts @@ -0,0 +1,33 @@ +import { strict as assert } from 'node:assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import HTTL from './HTTL'; + +describe('HTTL', () => { + testUtils.isVersionGreaterThanHook([7, 4]); + + describe('transformArguments', () => { + it('string', () => { + assert.deepEqual( + HTTL.transformArguments('key', 'field'), + ['HTTL', 'key', '1', 'field'] + ); + }); + + it('array', () => { + assert.deepEqual( + HTTL.transformArguments('key', ['field1', 'field2']), + ['HTTL', 'key', '2', 'field1', 'field2'] + ); + }); + }); + + testUtils.testAll('hTTL', async client => { + assert.equal( + await client.hTTL('key', 'field'), + [-2] + ); + }, { + client: GLOBAL.SERVERS.OPEN, + cluster: GLOBAL.CLUSTERS.OPEN + }); +}); diff --git a/packages/client/lib/commands/HTTL.ts b/packages/client/lib/commands/HTTL.ts new file mode 100644 index 00000000000..c59ad4ceccf --- /dev/null +++ b/packages/client/lib/commands/HTTL.ts @@ -0,0 +1,11 @@ +import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types'; +import { RedisVariadicArgument, pushVariadicArgument } from './generic-transformers'; + +export default { + FIRST_KEY_INDEX: 1, + IS_READ_ONLY: true, + transformArguments(key: RedisArgument, fields: RedisVariadicArgument) { + return pushVariadicArgument(['HTTL', key], fields); + }, + transformReply: undefined as unknown as () => ArrayReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/index.ts b/packages/client/lib/commands/index.ts index b2898988386..71455e34e53 100644 --- a/packages/client/lib/commands/index.ts +++ b/packages/client/lib/commands/index.ts @@ -133,6 +133,9 @@ import FUNCTION_STATS from './FUNCTION_STATS'; import HDEL from './HDEL'; import HELLO from './HELLO'; import HEXISTS from './HEXISTS'; +import HEXPIRE from './HEXPIRE'; +import HEXPIREAT from './HEXPIREAT'; +import HEXPIRETIME from './HEXPIRETIME'; import HGET from './HGET'; import HGETALL from './HGETALL'; import HINCRBY from './HINCRBY'; @@ -140,6 +143,12 @@ import HINCRBYFLOAT from './HINCRBYFLOAT'; import HKEYS from './HKEYS'; import HLEN from './HLEN'; import HMGET from './HMGET'; +import HPERSIST from './HPERSIST'; +import HPEXPIRE from './HPEXPIRE'; +import HPEXPIREAT from './HPEXPIREAT'; +import HPEXPIRETIME from './HPEXPIRETIME'; +import HPTTL from './HPTTL'; +import HTTL from './HTTL'; import HRANDFIELD_COUNT_WITHVALUES from './HRANDFIELD_COUNT_WITHVALUES'; import HRANDFIELD_COUNT from './HRANDFIELD_COUNT'; import HRANDFIELD from './HRANDFIELD'; @@ -601,6 +610,12 @@ export default { hello: HELLO, HEXISTS, hExists: HEXISTS, + HEXPIRE, + hExpire: HEXPIRE, + HEXPIREAT, + hExpireAt: HEXPIREAT, + HEXPIRETIME, + hExpireTime: HEXPIRETIME, HGET, hGet: HGET, HGETALL, @@ -615,6 +630,18 @@ export default { hLen: HLEN, HMGET, hmGet: HMGET, + HPERSIST, + hPersist: HPERSIST, + HPEXPIRE, + hpExpire: HPEXPIRE, + HPEXPIREAT, + hpExpireAt: HPEXPIRE, + HPEXPIRETIME, + hpExpireTime: HPEXPIRETIME, + HPTTL, + hpTTL: HPTTL, + HTTL, + hTTL: HTTL, HRANDFIELD_COUNT_WITHVALUES, hRandFieldCountWithValues: HRANDFIELD_COUNT_WITHVALUES, HRANDFIELD_COUNT,