From 26a3a6618360302b011ed64c2b51290e581a26e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Olguncu?= <21091016+ogzhanolguncu@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:36:08 +0300 Subject: [PATCH] Dx 1102 (#1213) * feat: add new linter * chore: format files * chore: add linted files * fix: copy readme & package.json & license to dist after build otherwise, we get MissingPackageJSON error when installing upstash/redis in our CI tests https://github.com/upstash/redis-js/actions/runs/10091669210/job/27903832137 installing with npm instead of bun solves the issue but we get another error when running: https://github.com/upstash/redis-js/actions/runs/10091973099/job/27904892427 * fix: remove flaky tests --------- Co-authored-by: CahidArda --- .eslintrc.json | 44 ++++++ .gitignore | 184 +++++++++++++++++++++++--- .husky/commit-msg | 1 + .husky/pre-commit | 2 - .husky/pre-push | 1 + .prettierignore | 1 + .releaserc | 14 -- .vscode/extensions.json | 3 + .vscode/settings.json | 20 +++ README.md | 1 - biome.json | 34 ----- bun.lockb | Bin 62409 -> 149948 bytes commitlint.config.js | 46 +++++++ index.ts | 1 - package.json | 49 +++++-- pkg/auto-pipeline.test.ts | 18 +-- pkg/auto-pipeline.ts | 22 +-- pkg/commands/append.ts | 3 +- pkg/commands/bitcount.ts | 9 +- pkg/commands/bitfield.ts | 3 +- pkg/commands/bitop.ts | 9 +- pkg/commands/bitpos.test.ts | 6 +- pkg/commands/bitpos.ts | 5 +- pkg/commands/command.ts | 23 ++-- pkg/commands/copy.ts | 5 +- pkg/commands/dbsize.ts | 3 +- pkg/commands/decr.ts | 3 +- pkg/commands/decrby.ts | 3 +- pkg/commands/del.ts | 3 +- pkg/commands/echo.ts | 3 +- pkg/commands/eval.test.ts | 2 +- pkg/commands/eval.ts | 5 +- pkg/commands/evalsha.ts | 5 +- pkg/commands/exists.ts | 3 +- pkg/commands/expire.test.ts | 6 +- pkg/commands/expire.ts | 5 +- pkg/commands/expireat.ts | 3 +- pkg/commands/flushall.ts | 3 +- pkg/commands/flushdb.ts | 3 +- pkg/commands/geo_add.test.ts | 9 +- pkg/commands/geo_add.ts | 11 +- pkg/commands/geo_dist.test.ts | 24 ++-- pkg/commands/geo_dist.ts | 5 +- pkg/commands/geo_hash.test.ts | 12 +- pkg/commands/geo_hash.ts | 7 +- pkg/commands/geo_pos.test.ts | 14 +- pkg/commands/geo_pos.ts | 7 +- pkg/commands/geo_search.test.ts | 44 +++--- pkg/commands/geo_search.ts | 15 ++- pkg/commands/geo_search_store.test.ts | 18 +-- pkg/commands/geo_search_store.ts | 5 +- pkg/commands/get.ts | 3 +- pkg/commands/getbit.ts | 3 +- pkg/commands/getdel.ts | 3 +- pkg/commands/getrange.ts | 5 +- pkg/commands/getset.ts | 5 +- pkg/commands/hdel.ts | 3 +- pkg/commands/hexists.ts | 3 +- pkg/commands/hget.ts | 5 +- pkg/commands/hgetall.test.ts | 2 +- pkg/commands/hgetall.ts | 9 +- pkg/commands/hincrby.ts | 5 +- pkg/commands/hincrbyfloat.ts | 5 +- pkg/commands/hkeys.ts | 3 +- pkg/commands/hlen.ts | 3 +- pkg/commands/hmget.ts | 15 ++- pkg/commands/hmset.ts | 7 +- pkg/commands/hrandfield.ts | 9 +- pkg/commands/hscan.test.ts | 6 +- pkg/commands/hscan.ts | 7 +- pkg/commands/hset.ts | 7 +- pkg/commands/hsetnx.ts | 5 +- pkg/commands/hstrlen.ts | 3 +- pkg/commands/hvals.ts | 3 +- pkg/commands/incr.ts | 3 +- pkg/commands/incrby.ts | 3 +- pkg/commands/incrbyfloat.ts | 3 +- pkg/commands/json_arrappend.ts | 5 +- pkg/commands/json_arrindex.test.ts | 2 +- pkg/commands/json_arrindex.ts | 5 +- pkg/commands/json_arrinsert.test.ts | 2 +- pkg/commands/json_arrinsert.ts | 5 +- pkg/commands/json_arrlen.ts | 5 +- pkg/commands/json_arrpop.ts | 5 +- pkg/commands/json_arrtrim.ts | 5 +- pkg/commands/json_clear.ts | 3 +- pkg/commands/json_del.ts | 3 +- pkg/commands/json_forget.ts | 3 +- pkg/commands/json_get.ts | 9 +- pkg/commands/json_mget.ts | 3 +- pkg/commands/json_mset.test.ts | 16 +-- pkg/commands/json_mset.ts | 5 +- pkg/commands/json_numincrby.ts | 5 +- pkg/commands/json_nummultby.ts | 5 +- pkg/commands/json_objkeys.ts | 5 +- pkg/commands/json_objlen.ts | 5 +- pkg/commands/json_resp.ts | 3 +- pkg/commands/json_set.ts | 5 +- pkg/commands/json_strappend.ts | 5 +- pkg/commands/json_strlen.ts | 5 +- pkg/commands/json_toggle.ts | 3 +- pkg/commands/json_type.ts | 3 +- pkg/commands/keys.ts | 3 +- pkg/commands/lindex.ts | 5 +- pkg/commands/linsert.ts | 5 +- pkg/commands/llen.ts | 3 +- pkg/commands/lmove.test.ts | 4 +- pkg/commands/lmove.ts | 5 +- pkg/commands/lmpop.ts | 5 +- pkg/commands/lpop.ts | 5 +- pkg/commands/lpos.ts | 5 +- pkg/commands/lpush.ts | 3 +- pkg/commands/lpushx.ts | 3 +- pkg/commands/lrange.test.ts | 6 +- pkg/commands/lrange.ts | 5 +- pkg/commands/lrem.ts | 5 +- pkg/commands/lset.ts | 3 +- pkg/commands/ltrim.ts | 3 +- pkg/commands/mget.ts | 10 +- pkg/commands/mset.ts | 5 +- pkg/commands/msetnx.ts | 7 +- pkg/commands/persist.ts | 3 +- pkg/commands/pexpire.ts | 3 +- pkg/commands/pexpireat.ts | 3 +- pkg/commands/pfadd.ts | 5 +- pkg/commands/pfcount.ts | 5 +- pkg/commands/pfmerge.ts | 8 +- pkg/commands/ping.ts | 5 +- pkg/commands/psetex.ts | 5 +- pkg/commands/pttl.ts | 3 +- pkg/commands/publish.ts | 3 +- pkg/commands/randomkey.ts | 3 +- pkg/commands/rename.ts | 3 +- pkg/commands/renamenx.ts | 3 +- pkg/commands/rpop.ts | 5 +- pkg/commands/rpush.ts | 3 +- pkg/commands/rpushx.ts | 3 +- pkg/commands/sadd.ts | 3 +- pkg/commands/scan.ts | 5 +- pkg/commands/scard.ts | 3 +- pkg/commands/script_exists.ts | 3 +- pkg/commands/script_flush.test.ts | 34 ----- pkg/commands/script_flush.ts | 3 +- pkg/commands/script_load.ts | 3 +- pkg/commands/sdiff.ts | 3 +- pkg/commands/sdiffstore.ts | 5 +- pkg/commands/set.ts | 5 +- pkg/commands/setbit.ts | 5 +- pkg/commands/setex.ts | 3 +- pkg/commands/setnx.ts | 3 +- pkg/commands/setrange.ts | 5 +- pkg/commands/sinter.ts | 3 +- pkg/commands/sinterstore.ts | 5 +- pkg/commands/sismember.ts | 3 +- pkg/commands/smembers.test.ts | 6 +- pkg/commands/smembers.ts | 3 +- pkg/commands/smismember.ts | 5 +- pkg/commands/smove.ts | 5 +- pkg/commands/spop.ts | 5 +- pkg/commands/srandmember.ts | 5 +- pkg/commands/srem.ts | 3 +- pkg/commands/sscan.test.ts | 6 +- pkg/commands/sscan.ts | 7 +- pkg/commands/strlen.ts | 3 +- pkg/commands/sunion.test.ts | 2 +- pkg/commands/sunion.ts | 3 +- pkg/commands/sunionstore.test.ts | 2 +- pkg/commands/sunionstore.ts | 5 +- pkg/commands/time.ts | 3 +- pkg/commands/touch.ts | 3 +- pkg/commands/ttl.ts | 3 +- pkg/commands/type.ts | 3 +- pkg/commands/types.ts | 10 +- pkg/commands/unlink.ts | 3 +- pkg/commands/xack.test.ts | 4 +- pkg/commands/xack.ts | 5 +- pkg/commands/xadd.ts | 9 +- pkg/commands/xautoclaim.test.ts | 2 +- pkg/commands/xautoclaim.ts | 5 +- pkg/commands/xclaim.test.ts | 2 +- pkg/commands/xclaim.ts | 7 +- pkg/commands/xdel.test.ts | 6 +- pkg/commands/xdel.ts | 5 +- pkg/commands/xgroup.test.ts | 1 + pkg/commands/xgroup.ts | 25 ++-- pkg/commands/xinfo.test.ts | 4 +- pkg/commands/xinfo.ts | 5 +- pkg/commands/xlen.ts | 3 +- pkg/commands/xpending.ts | 15 ++- pkg/commands/xrange.ts | 11 +- pkg/commands/xread.test.ts | 18 +-- pkg/commands/xread.ts | 11 +- pkg/commands/xreadgroup.test.ts | 9 +- pkg/commands/xreadgroup.ts | 15 +-- pkg/commands/xrevrange.ts | 11 +- pkg/commands/xtrim.test.ts | 10 +- pkg/commands/xtrim.ts | 5 +- pkg/commands/zadd.test.ts | 22 +-- pkg/commands/zadd.ts | 5 +- pkg/commands/zcard.ts | 3 +- pkg/commands/zcount.ts | 5 +- pkg/commands/zdiffstore.ts | 5 +- pkg/commands/zincrby.ts | 5 +- pkg/commands/zinterstore.test.ts | 10 +- pkg/commands/zinterstore.ts | 9 +- pkg/commands/zlexcount.ts | 3 +- pkg/commands/zmscore.ts | 5 +- pkg/commands/zpopmax.test.ts | 4 +- pkg/commands/zpopmax.ts | 5 +- pkg/commands/zpopmin.ts | 5 +- pkg/commands/zrange.test.ts | 28 ++-- pkg/commands/zrange.ts | 15 ++- pkg/commands/zrank.ts | 5 +- pkg/commands/zrem.test.ts | 2 +- pkg/commands/zrem.ts | 3 +- pkg/commands/zremrangebylex.ts | 3 +- pkg/commands/zremrangebyrank.ts | 5 +- pkg/commands/zremrangebyscore.ts | 3 +- pkg/commands/zrevrank.ts | 5 +- pkg/commands/zscan.test.ts | 6 +- pkg/commands/zscan.ts | 7 +- pkg/commands/zscore.ts | 5 +- pkg/commands/zunion.test.ts | 12 +- pkg/commands/zunion.ts | 11 +- pkg/commands/zunionstore.test.ts | 10 +- pkg/commands/zunionstore.ts | 9 +- pkg/error.ts | 2 +- pkg/http.test.ts | 75 ----------- pkg/http.ts | 77 ++++++----- pkg/pipeline.test.ts | 4 +- pkg/pipeline.ts | 146 +++++++------------- pkg/redis.test.ts | 2 +- pkg/redis.ts | 105 ++++++--------- pkg/script.test.ts | 2 +- pkg/script.ts | 8 +- pkg/test-utils.ts | 2 +- platforms/cloudflare.ts | 37 +++--- platforms/fastly.ts | 26 ++-- platforms/nodejs.ts | 55 ++++---- prettier.config.mjs | 13 ++ 240 files changed, 1252 insertions(+), 1010 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .husky/commit-msg create mode 100755 .husky/pre-push create mode 100644 .prettierignore delete mode 100644 .releaserc create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json delete mode 100644 biome.json create mode 100644 commitlint.config.js delete mode 100644 index.ts delete mode 100644 pkg/commands/script_flush.test.ts delete mode 100644 pkg/http.test.ts create mode 100644 prettier.config.mjs diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..adcf4d25 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,44 @@ +{ + "env": { + "es2024": true + }, + "extends": [ + "eslint:recommended", + "plugin:unicorn/recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["@typescript-eslint", "unicorn"], + "parserOptions": { + "project": "./tsconfig.json" + }, + "ignorePatterns": ["*.config.*", "examples", "dist"], + "rules": { + "no-console": ["error", { "allow": ["warn", "error"] }], + "@typescript-eslint/no-magic-numbers": "off", + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/prefer-as-const": "error", + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/consistent-type-definitions": ["error", "type"], + "@typescript-eslint/no-unused-vars": [ + "error", + { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" } + ], + "@typescript-eslint/prefer-ts-expect-error": "off", + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": false + } + ], + "unicorn/prevent-abbreviations": "off", + "no-implicit-coercion": ["error", { "boolean": true }], + "no-extra-boolean-cast": ["error", { "enforceForLogicalOperands": true }], + "no-unneeded-ternary": ["error", { "defaultAssignment": true }], + "unicorn/no-array-reduce": ["off"], + "unicorn/no-nested-ternary": "off", + "unicorn/no-null": "off", + "unicorn/filename-case": "off" + } +} diff --git a/.gitignore b/.gitignore index 80f229e7..9b1ee42e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,175 @@ -.vscode/ -npm/ -node_modules/ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + coverage -.env* -!.env.example -.pnpm-debug.log -dist/ -.idea/ -.next/ +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions -examples/**/yarn.lock -examples/**/package-lock.json -examples/**/pnpm-lock.yaml +.vscode-test +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +# IntelliJ based IDEs +.idea -# Local Netlify folder -.netlify -x/ \ No newline at end of file +# Finder (MacOS) folder config +.DS_Store diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..10c3e1db --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +bun --no -- commitlint --edit "" \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 7f4c7fc8..ce7a0d90 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,2 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" bun run fmt && bun run test \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..1f9223c3 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +bun run build diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..303d4e42 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +**/examples \ No newline at end of file diff --git a/.releaserc b/.releaserc deleted file mode 100644 index 5110b639..00000000 --- a/.releaserc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "branches": [ - { - "name": "release" - }, - { - "name": "main", - "channel": "next", - "prerelease": "next" - } - ], - "dryRun": false, - "ci": true -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..1d7ac851 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ebb96b33 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.formatOnSave": true, + "git.autofetch": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} diff --git a/README.md b/README.md index 67b9c1a7..faec21dc 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,6 @@ bun run test bun run build ``` - ### Telemetry This library sends anonymous telemetry data to help us improve your experience. diff --git a/biome.json b/biome.json deleted file mode 100644 index 504def31..00000000 --- a/biome.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "correctness": { - "noUnusedVariables": "error" - }, - "performance": { - "noDelete": "off" - }, - "complexity": { - "noBannedTypes": "off" - }, - "suspicious": { - "noExplicitAny": "off" - }, - "style": { - "noNonNullAssertion": "off" - } - }, - "ignore": ["node_modules", "dist"] - }, - "formatter": { - "indentStyle": "space", - "indentWidth": 2, - "enabled": true, - "lineWidth": 100 - }, - "organizeImports": { - "enabled": true - } -} diff --git a/bun.lockb b/bun.lockb index 7727a90dec3dbec84c23812c9e6e54df65668204..df1c6fef72005621758f4bb5f8ec4fca2e291308 100755 GIT binary patch literal 149948 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

64(4w~_Pv+T$l%Z{qD6 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 literal 62409 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 Z-FkDC", "license": "MIT", @@ -49,17 +63,30 @@ "homepage": "https://github.com/upstash/upstash-redis#readme", "typesVersions": { "*": { - "nodejs": ["./nodejs.d.ts"], - "cloudflare": ["./cloudflare.d.ts"], - "fastly": ["./fastly.d.ts"] + "nodejs": [ + "./nodejs.d.ts" + ], + "cloudflare": [ + "./cloudflare.d.ts" + ], + "fastly": [ + "./fastly.d.ts" + ] } }, "devDependencies": { + "@biomejs/biome": "latest", + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", "@types/crypto-js": "^4.1.3", + "@typescript-eslint/eslint-plugin": "7.17.0", + "@typescript-eslint/parser": "7.17.0", "bun-types": "1.0.33", - "tsup": "^7.2.0", - "@biomejs/biome": "latest", - "husky": "^8.0.3", + "eslint": "8.56", + "eslint-plugin-unicorn": "55.0.0", + "husky": "^9.1.1", + "prettier": "^3.3.3", + "tsup": "^8.2.3", "typescript": "latest" }, "dependencies": { diff --git a/pkg/auto-pipeline.test.ts b/pkg/auto-pipeline.test.ts index 24810f08..ac0a0f36 100644 --- a/pkg/auto-pipeline.test.ts +++ b/pkg/auto-pipeline.test.ts @@ -38,7 +38,7 @@ describe("Auto pipeline", () => { redis.evalsha(scriptHash, [], ["Hello"]), redis.exists(newKey()), redis.expire(newKey(), 5), - redis.expireat(newKey(), Math.floor(new Date().getTime() / 1000) + 60), + redis.expireat(newKey(), Math.floor(Date.now() / 1000) + 60), redis.flushall(), redis.flushdb(), redis.get(newKey()), @@ -84,7 +84,7 @@ describe("Auto pipeline", () => { redis.msetnx({ key3: "value", key4: "value" }), redis.persist(newKey()), redis.pexpire(newKey(), 1000), - redis.pexpireat(newKey(), new Date().getTime() + 1000), + redis.pexpireat(newKey(), Date.now() + 1000), redis.ping(), redis.psetex(newKey(), 1, "value"), redis.pttl(newKey()), @@ -144,7 +144,7 @@ describe("Auto pipeline", () => { redis.zscore(newKey(), "member"), redis.zunionstore(newKey(), 1, [newKey()]), redis.zunion(1, [newKey()]), - redis.json.set(persistentKey3, '$', { log: ["one", "two"] }), + redis.json.set(persistentKey3, "$", { log: ["one", "two"] }), redis.json.arrappend(persistentKey3, "$.log", '"three"'), ]); expect(result).toBeTruthy(); @@ -162,11 +162,11 @@ describe("Auto pipeline", () => { expect(redis.pipelineCounter).toBe(0); // following five commands are added to the pipeline - redis.flushdb(); - redis.incr("baz"); - redis.incr("baz"); - redis.set("foo", "bar"); - redis.incr("baz"); + void redis.flushdb(); + void redis.incr("baz"); + void redis.incr("baz"); + void redis.set("foo", "bar"); + void redis.incr("baz"); // two get calls are added to the pipeline and pipeline // is executed since we called await @@ -186,7 +186,7 @@ describe("Auto pipeline", () => { // @ts-expect-error pipelineCounter is not in type but accessible expect(redis.pipelineCounter).toBe(0); - redis.flushdb(); + void redis.flushdb(); const res1 = await redis.incr("baz"); // @ts-expect-error pipelineCounter is not in type but accessible diff --git a/pkg/auto-pipeline.ts b/pkg/auto-pipeline.ts index 8fddbb64..40430884 100644 --- a/pkg/auto-pipeline.ts +++ b/pkg/auto-pipeline.ts @@ -1,7 +1,8 @@ -import { Command } from "./commands/command"; -import { Pipeline } from "./pipeline"; -import { Redis } from "./redis"; -import { CommandArgs } from "./types"; +/* eslint-disable @typescript-eslint/ban-types */ +import type { Command } from "./commands/command"; +import type { Pipeline } from "./pipeline"; +import type { Redis } from "./redis"; +import type { CommandArgs } from "./types"; // properties which are only available in redis type redisOnly = Exclude; @@ -11,6 +12,7 @@ export function createAutoPipelineProxy(_redis: Redis, json?: boolean): Redis { autoPipelineExecutor: AutoPipelineExecutor; }; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!redis.autoPipelineExecutor) { redis.autoPipelineExecutor = new AutoPipelineExecutor(redis); } @@ -35,8 +37,9 @@ export function createAutoPipelineProxy(_redis: Redis, json?: boolean): Redis { // If the method is a function on the pipeline, wrap it with the executor logic const isFunction = json - ? typeof redis.autoPipelineExecutor.pipeline.json[command as keyof Pipeline["json"]] === "function" - : typeof redis.autoPipelineExecutor.pipeline[command as keyof Pipeline] === "function" + ? typeof redis.autoPipelineExecutor.pipeline.json[command as keyof Pipeline["json"]] === + "function" + : typeof redis.autoPipelineExecutor.pipeline[command as keyof Pipeline] === "function"; if (isFunction) { return (...args: CommandArgs) => { // pass the function as a callback @@ -58,7 +61,7 @@ export function createAutoPipelineProxy(_redis: Redis, json?: boolean): Redis { } class AutoPipelineExecutor { - private pipelinePromises = new WeakMap>>(); + private pipelinePromises = new WeakMap>(); private activePipeline: Pipeline | null = null; private indexInCurrentPipeline = 0; private redis: Redis; @@ -71,7 +74,7 @@ class AutoPipelineExecutor { } async withAutoPipeline(executeWithPipeline: (pipeline: Pipeline) => unknown): Promise { - const pipeline = this.activePipeline || this.redis.pipeline(); + const pipeline = this.activePipeline ?? this.redis.pipeline(); if (!this.activePipeline) { this.activePipeline = pipeline; @@ -89,6 +92,7 @@ class AutoPipelineExecutor { this.pipelinePromises.set(pipeline, pipelinePromise); this.activePipeline = null; } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.pipelinePromises.get(pipeline)!; }); @@ -98,6 +102,6 @@ class AutoPipelineExecutor { private async deferExecution() { await Promise.resolve(); - return await Promise.resolve(); + await Promise.resolve(); } } diff --git a/pkg/commands/append.ts b/pkg/commands/append.ts index 04e71635..999a4271 100644 --- a/pkg/commands/append.ts +++ b/pkg/commands/append.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/append diff --git a/pkg/commands/bitcount.ts b/pkg/commands/bitcount.ts index 081d6d6f..b9fd5c4b 100644 --- a/pkg/commands/bitcount.ts +++ b/pkg/commands/bitcount.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/bitcount @@ -6,15 +7,15 @@ import { Command, CommandOptions } from "./command"; export class BitCountCommand extends Command { constructor( cmd: [key: string, start?: never, end?: never], - opts?: CommandOptions, + opts?: CommandOptions ); constructor( cmd: [key: string, start: number, end: number], - opts?: CommandOptions, + opts?: CommandOptions ); constructor( [key, start, end]: [key: string, start?: number, end?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["bitcount", key]; if (typeof start === "number") { diff --git a/pkg/commands/bitfield.ts b/pkg/commands/bitfield.ts index 3b5da3e7..baf44250 100644 --- a/pkg/commands/bitfield.ts +++ b/pkg/commands/bitfield.ts @@ -17,8 +17,7 @@ export class BitFieldCommand> { args: [key: string], private client: Requester, private opts?: CommandOptions, - private execOperation = (command: Command) => - command.exec(this.client) as T, + private execOperation = (command: Command) => command.exec(this.client) as T ) { this.command = ["bitfield", ...args]; } diff --git a/pkg/commands/bitop.ts b/pkg/commands/bitop.ts index 4bf5d84a..d1452afe 100644 --- a/pkg/commands/bitop.ts +++ b/pkg/commands/bitop.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/bitop @@ -6,15 +7,15 @@ import { Command, CommandOptions } from "./command"; export class BitOpCommand extends Command { constructor( cmd: [op: "and" | "or" | "xor", destinationKey: string, ...sourceKeys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ); constructor( cmd: [op: "not", destinationKey: string, sourceKey: string], - opts?: CommandOptions, + opts?: CommandOptions ); constructor( cmd: [op: "and" | "or" | "xor" | "not", destinationKey: string, ...sourceKeys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["bitop", ...cmd], opts); } diff --git a/pkg/commands/bitpos.test.ts b/pkg/commands/bitpos.test.ts index ccfb2912..1659a3c9 100644 --- a/pkg/commands/bitpos.test.ts +++ b/pkg/commands/bitpos.test.ts @@ -19,7 +19,7 @@ describe("when key is not set", () => { describe("when key is set", () => { test("returns position of first set bit", async () => { const key = newKey(); - const value = "\xff\xf0\x00"; + const value = "\u00FF\u00F0\u0000"; await new SetCommand([key, value]).exec(client); const res = await new BitPosCommand([key, 0]).exec(client); expect(res).toEqual(2); @@ -29,7 +29,7 @@ describe("when key is set", () => { describe("with start", () => { test("returns position of first set bit", async () => { const key = newKey(); - const value = "\x00\xff\xf0"; + const value = "\u0000\u00FF\u00F0"; await new SetCommand([key, value]).exec(client); const res = await new BitPosCommand([key, 0, 0]).exec(client); expect(res).toEqual(0); @@ -39,7 +39,7 @@ describe("with start", () => { describe("with start and end", () => { test("returns position of first set bit", async () => { const key = newKey(); - const value = "\x00\xff\xf0"; + const value = "\u0000\u00FF\u00F0"; await new SetCommand([key, value]).exec(client); const res = await new BitPosCommand([key, 1, 2, -1]).exec(client); expect(res).toEqual(16); diff --git a/pkg/commands/bitpos.ts b/pkg/commands/bitpos.ts index affda48c..3a4ca1ee 100644 --- a/pkg/commands/bitpos.ts +++ b/pkg/commands/bitpos.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/bitpos @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class BitPosCommand extends Command { constructor( cmd: [key: string, bit: 0 | 1, start?: number, end?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["bitpos", ...cmd], opts); } diff --git a/pkg/commands/command.ts b/pkg/commands/command.ts index e25c4b2a..ea524108 100644 --- a/pkg/commands/command.ts +++ b/pkg/commands/command.ts @@ -1,5 +1,5 @@ import { UpstashError } from "../error"; -import { Requester } from "../http"; +import type { Requester } from "../http"; import { parseResponse } from "../util"; type Serialize = (data: unknown) => string | number | boolean; @@ -9,11 +9,13 @@ const defaultSerializer: Serialize = (c: unknown) => { switch (typeof c) { case "string": case "number": - case "boolean": + case "boolean": { return c; + } - default: + default: { return JSON.stringify(c); + } } }; @@ -47,12 +49,12 @@ export class Command { */ constructor( command: (string | boolean | number | unknown)[], - opts?: CommandOptions, + opts?: CommandOptions ) { this.serialize = defaultSerializer; this.deserialize = - typeof opts?.automaticDeserialization === "undefined" || opts.automaticDeserialization - ? opts?.deserialize ?? parseResponse + opts?.automaticDeserialization === undefined || opts.automaticDeserialization + ? (opts?.deserialize ?? parseResponse) : (x) => x as unknown as TData; this.command = command.map((c) => this.serialize(c)); @@ -64,10 +66,11 @@ export class Command { const result = await originalExec(client); const end = performance.now(); const loggerResult = (end - start).toFixed(2); + // eslint-disable-next-line no-console console.log( - `Latency for \x1b[38;2;19;185;39m${this.command[0] + `Latency for \u001B[38;2;19;185;39m${this.command[0] .toString() - .toUpperCase()}\x1b[0m: \x1b[38;2;0;255;255m${loggerResult} ms\x1b[0m`, + .toUpperCase()}\u001B[0m: \u001B[38;2;0;255;255m${loggerResult} ms\u001B[0m` ); return result; }; @@ -84,8 +87,8 @@ export class Command { if (error) { throw new UpstashError(error); } - if (typeof result === "undefined") { - throw new Error("Request did not return a result"); + if (result === undefined) { + throw new TypeError("Request did not return a result"); } return this.deserialize(result); diff --git a/pkg/commands/copy.ts b/pkg/commands/copy.ts index 5d69677c..2f7ae739 100644 --- a/pkg/commands/copy.ts +++ b/pkg/commands/copy.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/copy @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class CopyCommand extends Command { constructor( [key, destinationKey, opts]: [key: string, destinationKey: string, opts?: { replace: boolean }], - commandOptions?: CommandOptions, + commandOptions?: CommandOptions ) { super(["COPY", key, destinationKey, ...(opts?.replace ? ["REPLACE"] : [])], { ...commandOptions, diff --git a/pkg/commands/dbsize.ts b/pkg/commands/dbsize.ts index eaea2bb1..2f16d795 100644 --- a/pkg/commands/dbsize.ts +++ b/pkg/commands/dbsize.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/dbsize diff --git a/pkg/commands/decr.ts b/pkg/commands/decr.ts index 6be229a2..e3f021e6 100644 --- a/pkg/commands/decr.ts +++ b/pkg/commands/decr.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/decr diff --git a/pkg/commands/decrby.ts b/pkg/commands/decrby.ts index dff67e20..37dcb795 100644 --- a/pkg/commands/decrby.ts +++ b/pkg/commands/decrby.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/decrby diff --git a/pkg/commands/del.ts b/pkg/commands/del.ts index 5e4e9f89..9fa7e7e5 100644 --- a/pkg/commands/del.ts +++ b/pkg/commands/del.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/del diff --git a/pkg/commands/echo.ts b/pkg/commands/echo.ts index 4663c7da..6afb0ea6 100644 --- a/pkg/commands/echo.ts +++ b/pkg/commands/echo.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/echo diff --git a/pkg/commands/eval.test.ts b/pkg/commands/eval.test.ts index e85b9130..b9e1ac4b 100644 --- a/pkg/commands/eval.test.ts +++ b/pkg/commands/eval.test.ts @@ -23,7 +23,7 @@ describe("with keys", () => { const key = newKey(); await new SetCommand([key, value]).exec(client); const res = await new EvalCommand([`return redis.call("GET", KEYS[1])`, [key], []]).exec( - client, + client ); expect(res).toEqual(value); }); diff --git a/pkg/commands/eval.ts b/pkg/commands/eval.ts index a253d5fc..d21bee2d 100644 --- a/pkg/commands/eval.ts +++ b/pkg/commands/eval.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/eval @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class EvalCommand extends Command { constructor( [script, keys, args]: [script: string, keys: string[], args: TArgs], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["eval", script, keys.length, ...keys, ...(args ?? [])], opts); } diff --git a/pkg/commands/evalsha.ts b/pkg/commands/evalsha.ts index 6ee4aea1..4ac46856 100644 --- a/pkg/commands/evalsha.ts +++ b/pkg/commands/evalsha.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/evalsha @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class EvalshaCommand extends Command { constructor( [sha, keys, args]: [sha: string, keys: string[], args?: TArgs], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["evalsha", sha, keys.length, ...keys, ...(args ?? [])], opts); } diff --git a/pkg/commands/exists.ts b/pkg/commands/exists.ts index 3e142b7d..55579819 100644 --- a/pkg/commands/exists.ts +++ b/pkg/commands/exists.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/exists diff --git a/pkg/commands/expire.test.ts b/pkg/commands/expire.test.ts index 81029642..4c0bc362 100644 --- a/pkg/commands/expire.test.ts +++ b/pkg/commands/expire.test.ts @@ -56,7 +56,7 @@ describe("XX", () => { const res2 = await new GetCommand([key]).exec(client); expect(res2).toEqual(null); }, - { timeout: 10000 }, + { timeout: 10_000 } ); test("should not set expiry when the key does not have an existing expiry", async () => { @@ -81,7 +81,7 @@ describe("GT", () => { const res2 = await new GetCommand([key]).exec(client); expect(res2).toEqual(null); }, - { timeout: 10000 }, + { timeout: 10_000 } ); test("should not set expiry when the new expiry is not greater than current one", async () => { @@ -106,7 +106,7 @@ describe("LT", () => { const res2 = await new GetCommand([key]).exec(client); expect(res2).toEqual(null); }, - { timeout: 10000 }, + { timeout: 10_000 } ); test("should not set expiry when the new expiry is not less than current one", async () => { diff --git a/pkg/commands/expire.ts b/pkg/commands/expire.ts index 04aad8f4..f85eb09a 100644 --- a/pkg/commands/expire.ts +++ b/pkg/commands/expire.ts @@ -1,10 +1,11 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; type ExpireOptions = "NX" | "nx" | "XX" | "xx" | "GT" | "gt" | "LT" | "lt"; export class ExpireCommand extends Command<"0" | "1", 0 | 1> { constructor( cmd: [key: string, seconds: number, option?: ExpireOptions], - opts?: CommandOptions<"0" | "1", 0 | 1>, + opts?: CommandOptions<"0" | "1", 0 | 1> ) { super(["expire", ...cmd.filter(Boolean)], opts); } diff --git a/pkg/commands/expireat.ts b/pkg/commands/expireat.ts index 72fef683..0929ba57 100644 --- a/pkg/commands/expireat.ts +++ b/pkg/commands/expireat.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/expireat diff --git a/pkg/commands/flushall.ts b/pkg/commands/flushall.ts index bd837714..de50a52c 100644 --- a/pkg/commands/flushall.ts +++ b/pkg/commands/flushall.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/flushall */ diff --git a/pkg/commands/flushdb.ts b/pkg/commands/flushdb.ts index a41ae2b4..1269aa80 100644 --- a/pkg/commands/flushdb.ts +++ b/pkg/commands/flushdb.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/flushdb */ diff --git a/pkg/commands/geo_add.test.ts b/pkg/commands/geo_add.test.ts index 72f061cc..793c62aa 100644 --- a/pkg/commands/geo_add.test.ts +++ b/pkg/commands/geo_add.test.ts @@ -2,17 +2,18 @@ import { keygen, newHttpClient, randomID } from "../test-utils"; import { afterAll, describe, expect, test } from "bun:test"; -import { GeoAddCommand, GeoMember } from "./geo_add"; +import type { GeoMember } from "./geo_add"; +import { GeoAddCommand } from "./geo_add"; const client = newHttpClient(); const { newKey, cleanup } = keygen(); afterAll(cleanup); -interface Coordinate { +type Coordinate = { latitude: number; longitude: number; -} +}; function generateRandomPoint(radius = 100): Coordinate { const center = { lat: 14.23, lng: 23.12 }; @@ -20,7 +21,7 @@ function generateRandomPoint(radius = 100): Coordinate { const x0 = center.lng; const y0 = center.lat; // Convert Radius from meters to degrees. - const rd = radius / 111300; + const rd = radius / 111_300; const u = Math.random(); const v = Math.random(); diff --git a/pkg/commands/geo_add.ts b/pkg/commands/geo_add.ts index a4710be2..364e57d8 100644 --- a/pkg/commands/geo_add.ts +++ b/pkg/commands/geo_add.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type GeoAddCommandOptions = | { @@ -10,11 +11,11 @@ export type GeoAddCommandOptions = xx?: boolean; } & { ch?: boolean }); -export interface GeoMember { +export type GeoMember = { latitude: number; longitude: number; member: TMemberType; -} +}; /** * @see https://redis.io/commands/geoadd @@ -26,7 +27,7 @@ export class GeoAddCommand extends Command | GeoAddCommandOptions, ...GeoMember[], ], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["geoadd", key]; @@ -45,7 +46,7 @@ export class GeoAddCommand extends Command [longitude, latitude, member]), + ...arg2.flatMap(({ latitude, longitude, member }) => [longitude, latitude, member]) ); super(command, opts); diff --git a/pkg/commands/geo_dist.test.ts b/pkg/commands/geo_dist.test.ts index 6de352ab..64043161 100644 --- a/pkg/commands/geo_dist.test.ts +++ b/pkg/commands/geo_dist.test.ts @@ -9,20 +9,20 @@ const client = newHttpClient(); test("should return distance successfully in meters", async () => { await new GeoAddCommand([ "Sicily", - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoDistCommand(["Sicily", "Palermo", "Catania"]).exec(client); - expect(res).toEqual(166274.1516); + expect(res).toEqual(166_274.1516); }); test("should return distance for object members", async () => { await new GeoAddCommand([ "Sicily", - { longitude: 13.361389, latitude: 38.115556, member: { name: "Palermo" } }, - { longitude: 15.087269, latitude: 37.502669, member: { name: "Catania" } }, + { longitude: 13.361_389, latitude: 38.115_556, member: { name: "Palermo" } }, + { longitude: 15.087_269, latitude: 37.502_669, member: { name: "Catania" } }, ]).exec(client); const res = await new GeoDistCommand([ @@ -33,14 +33,14 @@ test("should return distance for object members", async () => { }, ]).exec(client); - expect(res).toEqual(166274.1516); + expect(res).toEqual(166_274.1516); }); test("should return distance successfully in kilometers", async () => { await new GeoAddCommand([ "Sicily", - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoDistCommand(["Sicily", "Palermo", "Catania", "KM"]).exec(client); @@ -51,8 +51,8 @@ test("should return distance successfully in kilometers", async () => { test("should return distance successfully in miles", async () => { await new GeoAddCommand([ "Sicily", - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoDistCommand(["Sicily", "Palermo", "Catania", "MI"]).exec(client); @@ -63,8 +63,8 @@ test("should return distance successfully in miles", async () => { test("should return distance successfully in feet", async () => { await new GeoAddCommand([ "Sicily", - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoDistCommand(["Sicily", "Palermo", "Catania", "FT"]).exec(client); diff --git a/pkg/commands/geo_dist.ts b/pkg/commands/geo_dist.ts index ae26f825..d68b0bfa 100644 --- a/pkg/commands/geo_dist.ts +++ b/pkg/commands/geo_dist.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/geodist @@ -11,7 +12,7 @@ export class GeoDistCommand extends Command, + opts?: CommandOptions ) { super(["GEODIST", key, member1, member2, unit], opts); } diff --git a/pkg/commands/geo_hash.test.ts b/pkg/commands/geo_hash.test.ts index 55f0a90c..05aeb183 100644 --- a/pkg/commands/geo_hash.test.ts +++ b/pkg/commands/geo_hash.test.ts @@ -12,8 +12,8 @@ describe("GEOHASH tests", () => { const members = ["Palermo", "Catania"]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, ]).exec(client); const response = await new GeoHashCommand([key, members]).exec(client); @@ -25,8 +25,8 @@ describe("GEOHASH tests", () => { const members = ["Palermo", "Catania", "Marsala"]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, { longitude: 12.4372, latitude: 37.7981, member: members[2] }, ]).exec(client); @@ -39,8 +39,8 @@ describe("GEOHASH tests", () => { const members = [{ name: "Palermo" }, { name: "Catania" }]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, ]).exec(client); const response = await new GeoHashCommand([key, members]).exec(client); diff --git a/pkg/commands/geo_hash.ts b/pkg/commands/geo_hash.ts index 57008066..4ccdab2c 100644 --- a/pkg/commands/geo_hash.ts +++ b/pkg/commands/geo_hash.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; /** * @see https://redis.io/commands/geohash @@ -8,8 +9,8 @@ export class GeoHashCommand extends Command< (string | null)[] > { constructor( - cmd: [string, ...(TMember[] | TMember[])], - opts?: CommandOptions<(string | null)[], (string | null)[]>, + cmd: [string, ...TMember[]], + opts?: CommandOptions<(string | null)[], (string | null)[]> ) { const [key] = cmd; // Check if the second argument is an array of strings (members). diff --git a/pkg/commands/geo_pos.test.ts b/pkg/commands/geo_pos.test.ts index cab6d8bf..9e01ae2d 100644 --- a/pkg/commands/geo_pos.test.ts +++ b/pkg/commands/geo_pos.test.ts @@ -11,8 +11,8 @@ describe("GEOPOS tests", () => { const members = ["Palermo", "Catania", "Marsala"]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, { longitude: 12.4372, latitude: 37.7981, member: members[2] }, ]).exec(client); const response = await new GeoPosCommand([key, [...members, "FooBar"]]).exec(client); @@ -24,8 +24,8 @@ describe("GEOPOS tests", () => { const members = ["Palermo", "Catania", "Marsala"]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, { longitude: 12.4372, latitude: 37.7981, member: members[2] }, ]).exec(client); @@ -39,7 +39,7 @@ describe("GEOPOS tests", () => { const members = ["Palermo"]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, ]).exec(client); const response = await new GeoPosCommand([key, "FooBar"]).exec(client); expect(response).toEqual([]); @@ -50,8 +50,8 @@ describe("GEOPOS tests", () => { const members = [{ name: "Palermo" }, { name: "Catania" }, { name: "Marsala" }]; await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: members[0] }, - { longitude: 15.087269, latitude: 37.502669, member: members[1] }, + { longitude: 13.361_389, latitude: 38.115_556, member: members[0] }, + { longitude: 15.087_269, latitude: 37.502_669, member: members[1] }, { longitude: 12.4372, latitude: 37.7981, member: members[2] }, ]).exec(client); const response = await new GeoPosCommand([key, [...members, "FooBar"]]).exec(client); diff --git a/pkg/commands/geo_pos.ts b/pkg/commands/geo_pos.ts index 2533a6d2..13320b8e 100644 --- a/pkg/commands/geo_pos.ts +++ b/pkg/commands/geo_pos.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; type Coordinates = { lng: number; @@ -11,7 +12,7 @@ type Coordinates = { export class GeoPosCommand extends Command<(string | null)[][], Coordinates[]> { constructor( cmd: [string, ...(TMember[] | TMember[])], - opts?: CommandOptions<(string | null)[][], Coordinates[]>, + opts?: CommandOptions<(string | null)[][], Coordinates[]> ) { const [key] = cmd; // Check if the second argument is an array of strings (members). @@ -32,7 +33,7 @@ function transform(result: (string | null)[][]): Coordinates[] { if (!pos?.[0] || !pos?.[1]) { continue; } - final.push({ lng: parseFloat(pos[0]), lat: parseFloat(pos[1]) }); + final.push({ lng: Number.parseFloat(pos[0]), lat: Number.parseFloat(pos[1]) }); } return final; } diff --git a/pkg/commands/geo_search.test.ts b/pkg/commands/geo_search.test.ts index 437ddaaf..1b80d5e4 100644 --- a/pkg/commands/geo_search.test.ts +++ b/pkg/commands/geo_search.test.ts @@ -13,8 +13,8 @@ describe("GEOSEARCH tests", () => { const key = newKey(); await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -32,8 +32,8 @@ describe("GEOSEARCH tests", () => { await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -51,8 +51,8 @@ describe("GEOSEARCH tests", () => { await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -69,8 +69,8 @@ describe("GEOSEARCH tests", () => { dist: 88.526, hash: "3479099956230698", coord: { - long: 13.361389338970184, - lat: 38.1155563954963, + long: 13.361_389_338_970_184, + lat: 38.115_556_395_496_3, }, }, { @@ -78,8 +78,8 @@ describe("GEOSEARCH tests", () => { dist: 95.9406, hash: "3479447370796909", coord: { - long: 15.087267458438873, - lat: 37.50266842333162, + long: 15.087_267_458_438_873, + lat: 37.502_668_423_331_62, }, }, ]); @@ -90,8 +90,8 @@ describe("GEOSEARCH tests", () => { await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -121,8 +121,8 @@ describe("GEOSEARCH tests", () => { await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -136,11 +136,11 @@ describe("GEOSEARCH tests", () => { expect(res).toEqual([ { member: "Palermo", - coord: { long: 13.361389338970184, lat: 38.1155563954963 }, + coord: { long: 13.361_389_338_970_184, lat: 38.115_556_395_496_3 }, }, { member: "Catania", - coord: { long: 15.087267458438873, lat: 37.50266842333162 }, + coord: { long: 15.087_267_458_438_873, lat: 37.502_668_423_331_62 }, }, ]); }); @@ -150,8 +150,8 @@ describe("GEOSEARCH tests", () => { await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ @@ -166,12 +166,12 @@ describe("GEOSEARCH tests", () => { { member: "Palermo", hash: "3479099956230698", - coord: { long: 13.361389338970184, lat: 38.1155563954963 }, + coord: { long: 13.361_389_338_970_184, lat: 38.115_556_395_496_3 }, }, { member: "Catania", hash: "3479447370796909", - coord: { long: 15.087267458438873, lat: 37.50266842333162 }, + coord: { long: 15.087_267_458_438_873, lat: 37.502_668_423_331_62 }, }, ]); }); @@ -180,8 +180,8 @@ describe("GEOSEARCH tests", () => { const key = newKey(); await new GeoAddCommand([ key, - { longitude: 13.361389, latitude: 38.115556, member: "Palermo" }, - { longitude: 15.087269, latitude: 37.502669, member: "Catania" }, + { longitude: 13.361_389, latitude: 38.115_556, member: "Palermo" }, + { longitude: 15.087_269, latitude: 37.502_669, member: "Catania" }, ]).exec(client); const res = await new GeoSearchCommand([ diff --git a/pkg/commands/geo_search.ts b/pkg/commands/geo_search.ts index a8f23ca1..1360aef1 100644 --- a/pkg/commands/geo_search.ts +++ b/pkg/commands/geo_search.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; type RadiusOptions = "M" | "KM" | "FT" | "MI"; type CenterPoint = @@ -63,7 +64,7 @@ export class GeoSearchCommand< order: "ASC" | "DESC" | "asc" | "desc", opts?: TOptions, ], - commandOptions?: CommandOptions>, + commandOptions?: CommandOptions> ) { const command: unknown[] = ["GEOSEARCH", key]; @@ -101,21 +102,21 @@ export class GeoSearchCommand< const obj = {} as any; try { - obj.member = JSON.parse(members[0] as string); + obj.member = JSON.parse(members[0]); } catch { obj.member = members[0]; } if (opts.withDist) { - obj.dist = parseFloat(members[counter++]); + obj.dist = Number.parseFloat(members[counter++]); } if (opts.withHash) { obj.hash = members[counter++].toString(); } if (opts.withCoord) { obj.coord = { - long: parseFloat(members[counter][0]), - lat: parseFloat(members[counter][1]), + long: Number.parseFloat(members[counter][0]), + lat: Number.parseFloat(members[counter][1]), }; } return obj; @@ -132,7 +133,7 @@ export class GeoSearchCommand< { deserialize: transform, ...commandOptions, - }, + } ); } } diff --git a/pkg/commands/geo_search_store.test.ts b/pkg/commands/geo_search_store.test.ts index f388043a..d48ee0f6 100644 --- a/pkg/commands/geo_search_store.test.ts +++ b/pkg/commands/geo_search_store.test.ts @@ -32,15 +32,15 @@ describe("GEOSSEARCHSTORE tests", () => { "ASC", ]).exec(client); const zrangeRes = await new ZRangeCommand([destination, 0, -1, { withScores: true }]).exec( - client, + client ); expect(zrangeRes).toEqual([ "Empire State Building", - 1791875672666387, + 1_791_875_672_666_387, "Grand Central Terminal", - 1791875708058440, + 1_791_875_708_058_440, "Central Park", - 1791875790048608, + 1_791_875_790_048_608, ]); expect(res).toEqual(zrangeRes.length / 2); }); @@ -68,7 +68,7 @@ describe("GEOSSEARCHSTORE tests", () => { { storeDist: true }, ]).exec(client); const zrangeRes = await new ZRangeCommand([destination, 0, -1, { withScores: true }]).exec( - client, + client ); expect(zrangeRes).toEqual([ "Empire State Building", @@ -104,7 +104,7 @@ describe("GEOSSEARCHSTORE tests", () => { { storeDist: true }, ]).exec(client); const zrangeRes = await new ZRangeCommand([destination, 0, -1, { withScores: true }]).exec( - client, + client ); expect(zrangeRes).toEqual([ { name: "Empire State Building" }, @@ -140,13 +140,13 @@ describe("GEOSSEARCHSTORE tests", () => { { count: { limit: 2 } }, ]).exec(client); const zrangeRes = await new ZRangeCommand([destination, 0, -1, { withScores: true }]).exec( - client, + client ); expect(zrangeRes).toEqual([ "Empire State Building", - 1791875672666387, + 1_791_875_672_666_387, "Grand Central Terminal", - 1791875708058440, + 1_791_875_708_058_440, ]); expect(res).toEqual(zrangeRes.length / 2); }); diff --git a/pkg/commands/geo_search_store.ts b/pkg/commands/geo_search_store.ts index 6884c21a..dd73286e 100644 --- a/pkg/commands/geo_search_store.ts +++ b/pkg/commands/geo_search_store.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; type RadiusOptions = "M" | "KM" | "FT" | "MI"; type CenterPoint = @@ -40,7 +41,7 @@ export class GeoSearchStoreCommand< order: "ASC" | "DESC" | "asc" | "desc", opts?: TOptions, ], - commandOptions?: CommandOptions, + commandOptions?: CommandOptions ) { const command: unknown[] = ["GEOSEARCHSTORE", destination, key]; diff --git a/pkg/commands/get.ts b/pkg/commands/get.ts index fa38f085..b2b24593 100644 --- a/pkg/commands/get.ts +++ b/pkg/commands/get.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/get diff --git a/pkg/commands/getbit.ts b/pkg/commands/getbit.ts index d8754cd2..94813771 100644 --- a/pkg/commands/getbit.ts +++ b/pkg/commands/getbit.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/getbit diff --git a/pkg/commands/getdel.ts b/pkg/commands/getdel.ts index ebb9a2db..264e34fb 100644 --- a/pkg/commands/getdel.ts +++ b/pkg/commands/getdel.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/getdel diff --git a/pkg/commands/getrange.ts b/pkg/commands/getrange.ts index 747a6aa9..689c0d44 100644 --- a/pkg/commands/getrange.ts +++ b/pkg/commands/getrange.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/getrange @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class GetRangeCommand extends Command { constructor( cmd: [key: string, start: number, end: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["getrange", ...cmd], opts); } diff --git a/pkg/commands/getset.ts b/pkg/commands/getset.ts index 169baaae..5334777a 100644 --- a/pkg/commands/getset.ts +++ b/pkg/commands/getset.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/getset @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class GetSetCommand extends Command { constructor( cmd: [key: string, value: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["getset", ...cmd], opts); } diff --git a/pkg/commands/hdel.ts b/pkg/commands/hdel.ts index 95e6742a..5c448931 100644 --- a/pkg/commands/hdel.ts +++ b/pkg/commands/hdel.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hdel diff --git a/pkg/commands/hexists.ts b/pkg/commands/hexists.ts index e5da497c..71aa7192 100644 --- a/pkg/commands/hexists.ts +++ b/pkg/commands/hexists.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hexists diff --git a/pkg/commands/hget.ts b/pkg/commands/hget.ts index 8c8f3af3..4f706436 100644 --- a/pkg/commands/hget.ts +++ b/pkg/commands/hget.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hget @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class HGetCommand extends Command { constructor( cmd: [key: string, field: string], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["hget", ...cmd], opts); } diff --git a/pkg/commands/hgetall.test.ts b/pkg/commands/hgetall.test.ts index a9074c4d..15e0a997 100644 --- a/pkg/commands/hgetall.test.ts +++ b/pkg/commands/hgetall.test.ts @@ -35,7 +35,7 @@ test("properly return bigint precisely", async () => { const value2 = randomID(); const value3 = randomUnsafeIntegerString(); await new HSetCommand([key, { [field1]: value1, [field2]: value2, [field3]: value3 }]).exec( - client, + client ); const res = await new HGetAllCommand([key]).exec(client); diff --git a/pkg/commands/hgetall.ts b/pkg/commands/hgetall.ts index 8a2d6a84..2ae0f7f6 100644 --- a/pkg/commands/hgetall.ts +++ b/pkg/commands/hgetall.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; function deserialize>(result: string[]): TData | null { if (result.length === 0) { @@ -12,11 +13,7 @@ function deserialize>(result: string[]): T // handle unsafe integer const valueIsNumberAndNotSafeInteger = !Number.isNaN(Number(value)) && !Number.isSafeInteger(Number(value)); - if (valueIsNumberAndNotSafeInteger) { - obj[key] = value; - } else { - obj[key] = JSON.parse(value); - } + obj[key] = valueIsNumberAndNotSafeInteger ? value : JSON.parse(value); } catch { obj[key] = value; } diff --git a/pkg/commands/hincrby.ts b/pkg/commands/hincrby.ts index a07fb612..6bc21fe9 100644 --- a/pkg/commands/hincrby.ts +++ b/pkg/commands/hincrby.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hincrby @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class HIncrByCommand extends Command { constructor( cmd: [key: string, field: string, increment: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["hincrby", ...cmd], opts); } diff --git a/pkg/commands/hincrbyfloat.ts b/pkg/commands/hincrbyfloat.ts index 605ca6ab..db8f4ab6 100644 --- a/pkg/commands/hincrbyfloat.ts +++ b/pkg/commands/hincrbyfloat.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hincrbyfloat @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class HIncrByFloatCommand extends Command { constructor( cmd: [key: string, field: string, increment: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["hincrbyfloat", ...cmd], opts); } diff --git a/pkg/commands/hkeys.ts b/pkg/commands/hkeys.ts index 4d7f5cb6..3bf9b91b 100644 --- a/pkg/commands/hkeys.ts +++ b/pkg/commands/hkeys.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hkeys diff --git a/pkg/commands/hlen.ts b/pkg/commands/hlen.ts index fa01c09c..2b2d0466 100644 --- a/pkg/commands/hlen.ts +++ b/pkg/commands/hlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hlen diff --git a/pkg/commands/hmget.ts b/pkg/commands/hmget.ts index 75f0e283..54dbe580 100644 --- a/pkg/commands/hmget.ts +++ b/pkg/commands/hmget.ts @@ -1,18 +1,19 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; function deserialize>( fields: string[], - result: (string | null)[], + result: (string | null)[] ): TData | null { - if (result.length === 0 || result.every((field) => field === null)) { + if (result.every((field) => field === null)) { return null; } const obj: Record = {}; - for (let i = 0; i < fields.length; i++) { + for (const [i, field] of fields.entries()) { try { - obj[fields[i]] = JSON.parse(result[i]!); + obj[field] = JSON.parse(result[i]!); } catch { - obj[fields[i]] = result[i]; + obj[field] = result[i]; } } return obj as TData; @@ -35,7 +36,7 @@ export class HMGetCommand> extends Command > { constructor( [key, ...fields]: [key: string, ...fields: string[]], - opts?: CommandOptions<(string | null)[], TData | null>, + opts?: CommandOptions<(string | null)[], TData | null> ) { super(["hmget", key, ...fields], { deserialize: (result) => deserialize(fields, result), diff --git a/pkg/commands/hmset.ts b/pkg/commands/hmset.ts index d7fddbb9..40df5c99 100644 --- a/pkg/commands/hmset.ts +++ b/pkg/commands/hmset.ts @@ -1,12 +1,13 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hmset */ export class HMSetCommand extends Command<"OK", "OK"> { constructor( - [key, kv]: [key: string, kv: { [field: string]: TData }], - opts?: CommandOptions<"OK", "OK">, + [key, kv]: [key: string, kv: Record], + opts?: CommandOptions<"OK", "OK"> ) { super(["hmset", key, ...Object.entries(kv).flatMap(([field, value]) => [field, value])], opts); } diff --git a/pkg/commands/hrandfield.ts b/pkg/commands/hrandfield.ts index 9b427465..37efdfd3 100644 --- a/pkg/commands/hrandfield.ts +++ b/pkg/commands/hrandfield.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; function deserialize>(result: string[]): TData | null { if (result.length === 0) { @@ -27,11 +28,11 @@ export class HRandFieldCommand< constructor(cmd: [key: string, count: number], opts?: CommandOptions); constructor( cmd: [key: string, count: number, withValues: boolean], - opts?: CommandOptions>, + opts?: CommandOptions> ); constructor( cmd: [key: string, count?: number, withValues?: boolean], - opts?: CommandOptions>, + opts?: CommandOptions> ) { const command = ["hrandfield", cmd[0]] as unknown[]; if (typeof cmd[1] === "number") { @@ -41,7 +42,7 @@ export class HRandFieldCommand< command.push("WITHVALUES"); } super(command, { - // @ts-ignore TODO: + // @ts-expect-error to silence compiler deserialize: cmd[2] ? (result) => deserialize(result as string[]) : opts?.deserialize, ...opts, }); diff --git a/pkg/commands/hscan.test.ts b/pkg/commands/hscan.test.ts index b47869ba..c4425828 100644 --- a/pkg/commands/hscan.test.ts +++ b/pkg/commands/hscan.test.ts @@ -15,7 +15,7 @@ describe("without options", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -27,7 +27,7 @@ describe("with match", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -39,6 +39,6 @@ describe("with count", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); diff --git a/pkg/commands/hscan.ts b/pkg/commands/hscan.ts index 2022b8fd..818a2692 100644 --- a/pkg/commands/hscan.ts +++ b/pkg/commands/hscan.ts @@ -1,6 +1,7 @@ import { deserializeScanResponse } from "../util"; -import { Command, CommandOptions } from "./command"; -import { ScanCommandOptions } from "./scan"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; +import type { ScanCommandOptions } from "./scan"; /** * @see https://redis.io/commands/hscan @@ -11,7 +12,7 @@ export class HScanCommand extends Command< > { constructor( [key, cursor, cmdOpts]: [key: string, cursor: string | number, cmdOpts?: ScanCommandOptions], - opts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]>, + opts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]> ) { const command: (number | string)[] = ["hscan", key, cursor]; if (cmdOpts?.match) { diff --git a/pkg/commands/hset.ts b/pkg/commands/hset.ts index eb109da2..1fa30d88 100644 --- a/pkg/commands/hset.ts +++ b/pkg/commands/hset.ts @@ -1,12 +1,13 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hset */ export class HSetCommand extends Command { constructor( - [key, kv]: [key: string, kv: { [field: string]: TData }], - opts?: CommandOptions, + [key, kv]: [key: string, kv: Record], + opts?: CommandOptions ) { super(["hset", key, ...Object.entries(kv).flatMap(([field, value]) => [field, value])], opts); } diff --git a/pkg/commands/hsetnx.ts b/pkg/commands/hsetnx.ts index 738c0024..e5982b84 100644 --- a/pkg/commands/hsetnx.ts +++ b/pkg/commands/hsetnx.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hsetnx @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class HSetNXCommand extends Command<"0" | "1", 0 | 1> { constructor( cmd: [key: string, field: string, value: TData], - opts?: CommandOptions<"0" | "1", 0 | 1>, + opts?: CommandOptions<"0" | "1", 0 | 1> ) { super(["hsetnx", ...cmd], opts); } diff --git a/pkg/commands/hstrlen.ts b/pkg/commands/hstrlen.ts index 599c89f0..c05c07c2 100644 --- a/pkg/commands/hstrlen.ts +++ b/pkg/commands/hstrlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hstrlen diff --git a/pkg/commands/hvals.ts b/pkg/commands/hvals.ts index 4072d6db..c117fb0e 100644 --- a/pkg/commands/hvals.ts +++ b/pkg/commands/hvals.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/hvals diff --git a/pkg/commands/incr.ts b/pkg/commands/incr.ts index a11c4d6e..5c6abb31 100644 --- a/pkg/commands/incr.ts +++ b/pkg/commands/incr.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/incr diff --git a/pkg/commands/incrby.ts b/pkg/commands/incrby.ts index ec8ad68b..45096dba 100644 --- a/pkg/commands/incrby.ts +++ b/pkg/commands/incrby.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/incrby diff --git a/pkg/commands/incrbyfloat.ts b/pkg/commands/incrbyfloat.ts index dcd75c95..320e66d5 100644 --- a/pkg/commands/incrbyfloat.ts +++ b/pkg/commands/incrbyfloat.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/incrbyfloat diff --git a/pkg/commands/json_arrappend.ts b/pkg/commands/json_arrappend.ts index 2b7591ae..172335f8 100644 --- a/pkg/commands/json_arrappend.ts +++ b/pkg/commands/json_arrappend.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrappend @@ -9,7 +10,7 @@ export class JsonArrAppendCommand extends Command< > { constructor( cmd: [key: string, path: string, ...values: TData], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.ARRAPPEND", ...cmd], opts); } diff --git a/pkg/commands/json_arrindex.test.ts b/pkg/commands/json_arrindex.test.ts index 697a6dba..73559bba 100644 --- a/pkg/commands/json_arrindex.test.ts +++ b/pkg/commands/json_arrindex.test.ts @@ -43,7 +43,7 @@ test("Find the specific place of a color in a list of product colors", async () expect(res4).toEqual(["black", "silver", "blue"]); const res5 = await new JsonArrInsertCommand([key, "$.colors", 2, '"yellow"', '"gold"']).exec( - client, + client ); expect(res5).toEqual([5]); const res6 = await new JsonGetCommand([key, "$.colors"]).exec(client); diff --git a/pkg/commands/json_arrindex.ts b/pkg/commands/json_arrindex.ts index f95f5c20..dc7ebf42 100644 --- a/pkg/commands/json_arrindex.ts +++ b/pkg/commands/json_arrindex.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrindex @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonArrIndexCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path: string, value: TValue, start?: number, stop?: number], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.ARRINDEX", ...cmd], opts); } diff --git a/pkg/commands/json_arrinsert.test.ts b/pkg/commands/json_arrinsert.test.ts index d9abaa98..2c64c1c4 100644 --- a/pkg/commands/json_arrinsert.test.ts +++ b/pkg/commands/json_arrinsert.test.ts @@ -41,7 +41,7 @@ test("Add new colors to a specific place in a list of product colors", async () const res4 = await new JsonGetCommand([key, "$.colors"]).exec(client); expect(res4).toEqual([["black", "silver", "blue"]]); const res5 = await new JsonArrInsertCommand([key, "$.colors", 2, '"yellow"', '"gold"']).exec( - client, + client ); expect(res5).toEqual([5]); const res6 = await new JsonGetCommand([key, "$.colors"]).exec(client); diff --git a/pkg/commands/json_arrinsert.ts b/pkg/commands/json_arrinsert.ts index a25ef211..5a7680e0 100644 --- a/pkg/commands/json_arrinsert.ts +++ b/pkg/commands/json_arrinsert.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrinsert @@ -9,7 +10,7 @@ export class JsonArrInsertCommand extends Command< > { constructor( cmd: [key: string, path: string, index: number, ...values: TData], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.ARRINSERT", ...cmd], opts); } diff --git a/pkg/commands/json_arrlen.ts b/pkg/commands/json_arrlen.ts index 7a2498d7..d1d2178c 100644 --- a/pkg/commands/json_arrlen.ts +++ b/pkg/commands/json_arrlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrlen @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonArrLenCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path?: string], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.ARRLEN", cmd[0], cmd[1] ?? "$"], opts); } diff --git a/pkg/commands/json_arrpop.ts b/pkg/commands/json_arrpop.ts index c8566674..a470bd6f 100644 --- a/pkg/commands/json_arrpop.ts +++ b/pkg/commands/json_arrpop.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrpop @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonArrPopCommand extends Command<(null | string)[], (TData | null)[]> { constructor( cmd: [key: string, path?: string, index?: number], - opts?: CommandOptions<(null | string)[], (TData | null)[]>, + opts?: CommandOptions<(null | string)[], (TData | null)[]> ) { super(["JSON.ARRPOP", ...cmd], opts); } diff --git a/pkg/commands/json_arrtrim.ts b/pkg/commands/json_arrtrim.ts index f4847cb6..dc1c5b14 100644 --- a/pkg/commands/json_arrtrim.ts +++ b/pkg/commands/json_arrtrim.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.arrtrim @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonArrTrimCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path?: string, start?: number, stop?: number], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { const path = cmd[1] ?? "$"; const start = cmd[2] ?? 0; diff --git a/pkg/commands/json_clear.ts b/pkg/commands/json_clear.ts index 7ed7fc85..3b889667 100644 --- a/pkg/commands/json_clear.ts +++ b/pkg/commands/json_clear.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.clear diff --git a/pkg/commands/json_del.ts b/pkg/commands/json_del.ts index 007a7a0e..817186ba 100644 --- a/pkg/commands/json_del.ts +++ b/pkg/commands/json_del.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.del diff --git a/pkg/commands/json_forget.ts b/pkg/commands/json_forget.ts index 92f6a967..41449403 100644 --- a/pkg/commands/json_forget.ts +++ b/pkg/commands/json_forget.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.forget diff --git a/pkg/commands/json_get.ts b/pkg/commands/json_get.ts index a1068b96..dab8aa2f 100644 --- a/pkg/commands/json_get.ts +++ b/pkg/commands/json_get.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.get @@ -14,11 +15,11 @@ export class JsonGetCommand< ...path: string[], ] | [key: string, ...path: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { const command = ["JSON.GET"]; if (typeof cmd[1] === "string") { - // @ts-ignore - we know this is a string + // @ts-expect-error - we know this is a string command.push(...cmd); } else { command.push(cmd[0]); @@ -34,7 +35,7 @@ export class JsonGetCommand< command.push("SPACE", cmd[1].space); } } - // @ts-ignore - we know this is a string + // @ts-expect-error - we know this is a string command.push(...cmd.slice(2)); } diff --git a/pkg/commands/json_mget.ts b/pkg/commands/json_mget.ts index b18b1aea..6542d042 100644 --- a/pkg/commands/json_mget.ts +++ b/pkg/commands/json_mget.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.mget diff --git a/pkg/commands/json_mset.test.ts b/pkg/commands/json_mset.test.ts index b9c916aa..d33be7e6 100644 --- a/pkg/commands/json_mset.test.ts +++ b/pkg/commands/json_mset.test.ts @@ -23,13 +23,9 @@ test("add a new value", async () => { test("replace an existing value", async () => { const key = newKey(); - const res1 = await new JsonMSetCommand([ - { key, path: "$", value: { a: 2 } }, - ]).exec(client); + const res1 = await new JsonMSetCommand([{ key, path: "$", value: { a: 2 } }]).exec(client); expect(res1).toEqual("OK"); - const res2 = await new JsonMSetCommand([{ key, path: "$.a", value: 3 }]).exec( - client - ); + const res2 = await new JsonMSetCommand([{ key, path: "$.a", value: 3 }]).exec(client); expect(res2).toEqual("OK"); const res3 = await new JsonGetCommand([key, "$"]).exec(client); expect(res3).toEqual([{ a: 3 }]); @@ -41,13 +37,9 @@ test("update multi-paths", async () => { f1: { a: 1 }, f2: { a: 2 }, }; - const res1 = await new JsonMSetCommand([ - { key, path: "$", value: data }, - ]).exec(client); + const res1 = await new JsonMSetCommand([{ key, path: "$", value: data }]).exec(client); expect(res1).toEqual("OK"); - const res2 = await new JsonMSetCommand([ - { key, path: "$..a", value: 3 }, - ]).exec(client); + const res2 = await new JsonMSetCommand([{ key, path: "$..a", value: 3 }]).exec(client); expect(res2).toEqual("OK"); const res3 = await new JsonGetCommand([key, "$"]).exec(client); diff --git a/pkg/commands/json_mset.ts b/pkg/commands/json_mset.ts index 041ba91b..265ca907 100644 --- a/pkg/commands/json_mset.ts +++ b/pkg/commands/json_mset.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.mset @@ -9,7 +10,7 @@ export class JsonMSetCommand< | string | boolean | Record - | (number | string | boolean | Record)[] + | (number | string | boolean | Record)[], > extends Command<"OK" | null, "OK" | null> { constructor( cmd: { key: string; path: string; value: TData }[], diff --git a/pkg/commands/json_numincrby.ts b/pkg/commands/json_numincrby.ts index 07808563..04b1e5a5 100644 --- a/pkg/commands/json_numincrby.ts +++ b/pkg/commands/json_numincrby.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.numincrby @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonNumIncrByCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path: string, value: number], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.NUMINCRBY", ...cmd], opts); } diff --git a/pkg/commands/json_nummultby.ts b/pkg/commands/json_nummultby.ts index 50ff4753..e328a8d4 100644 --- a/pkg/commands/json_nummultby.ts +++ b/pkg/commands/json_nummultby.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.nummultby @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonNumMultByCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path: string, value: number], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.NUMMULTBY", ...cmd], opts); } diff --git a/pkg/commands/json_objkeys.ts b/pkg/commands/json_objkeys.ts index 3fc4dced..e8394081 100644 --- a/pkg/commands/json_objkeys.ts +++ b/pkg/commands/json_objkeys.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.objkeys @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonObjKeysCommand extends Command<(string[] | null)[], (string[] | null)[]> { constructor( cmd: [key: string, path?: string], - opts?: CommandOptions<(string[] | null)[], (string[] | null)[]>, + opts?: CommandOptions<(string[] | null)[], (string[] | null)[]> ) { super(["JSON.OBJKEYS", ...cmd], opts); } diff --git a/pkg/commands/json_objlen.ts b/pkg/commands/json_objlen.ts index 07127164..4c1764d8 100644 --- a/pkg/commands/json_objlen.ts +++ b/pkg/commands/json_objlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.objlen @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonObjLenCommand extends Command<(number | null)[], (number | null)[]> { constructor( cmd: [key: string, path?: string], - opts?: CommandOptions<(number | null)[], (number | null)[]>, + opts?: CommandOptions<(number | null)[], (number | null)[]> ) { super(["JSON.OBJLEN", ...cmd], opts); } diff --git a/pkg/commands/json_resp.ts b/pkg/commands/json_resp.ts index aa8979c8..aff05edb 100644 --- a/pkg/commands/json_resp.ts +++ b/pkg/commands/json_resp.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.resp diff --git a/pkg/commands/json_set.ts b/pkg/commands/json_set.ts index 01d83437..9a31d75b 100644 --- a/pkg/commands/json_set.ts +++ b/pkg/commands/json_set.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.set @@ -18,7 +19,7 @@ export class JsonSetCommand< value: TData, opts?: { nx: true; xx?: never } | { nx?: never; xx: true }, ], - opts?: CommandOptions<"OK" | null, "OK" | null>, + opts?: CommandOptions<"OK" | null, "OK" | null> ) { const command = ["JSON.SET", cmd[0], cmd[1], cmd[2]]; if (cmd[3]) { diff --git a/pkg/commands/json_strappend.ts b/pkg/commands/json_strappend.ts index f73ab681..b74c0293 100644 --- a/pkg/commands/json_strappend.ts +++ b/pkg/commands/json_strappend.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.strappend @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonStrAppendCommand extends Command<(null | string)[], (null | number)[]> { constructor( cmd: [key: string, path: string, value: string], - opts?: CommandOptions<(null | string)[], (null | number)[]>, + opts?: CommandOptions<(null | string)[], (null | number)[]> ) { super(["JSON.STRAPPEND", ...cmd], opts); } diff --git a/pkg/commands/json_strlen.ts b/pkg/commands/json_strlen.ts index 071bde4b..70aa8f56 100644 --- a/pkg/commands/json_strlen.ts +++ b/pkg/commands/json_strlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.strlen @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class JsonStrLenCommand extends Command<(number | null)[], (number | null)[]> { constructor( cmd: [key: string, path?: string], - opts?: CommandOptions<(number | null)[], (number | null)[]>, + opts?: CommandOptions<(number | null)[], (number | null)[]> ) { super(["JSON.STRLEN", ...cmd], opts); } diff --git a/pkg/commands/json_toggle.ts b/pkg/commands/json_toggle.ts index 7bdbcf0d..fbf2d4e3 100644 --- a/pkg/commands/json_toggle.ts +++ b/pkg/commands/json_toggle.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.toggle diff --git a/pkg/commands/json_type.ts b/pkg/commands/json_type.ts index 1e4a8392..1ad79f39 100644 --- a/pkg/commands/json_type.ts +++ b/pkg/commands/json_type.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/json.type diff --git a/pkg/commands/keys.ts b/pkg/commands/keys.ts index 9e890260..951b7b38 100644 --- a/pkg/commands/keys.ts +++ b/pkg/commands/keys.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/keys diff --git a/pkg/commands/lindex.ts b/pkg/commands/lindex.ts index 5b63ce21..fee29147 100644 --- a/pkg/commands/lindex.ts +++ b/pkg/commands/lindex.ts @@ -1,9 +1,10 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LIndexCommand extends Command { constructor( cmd: [key: string, index: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["lindex", ...cmd], opts); } diff --git a/pkg/commands/linsert.ts b/pkg/commands/linsert.ts index 0a527403..febef4ff 100644 --- a/pkg/commands/linsert.ts +++ b/pkg/commands/linsert.ts @@ -1,8 +1,9 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LInsertCommand extends Command { constructor( cmd: [key: string, direction: "before" | "after", pivot: TData, value: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["linsert", ...cmd], opts); } diff --git a/pkg/commands/llen.ts b/pkg/commands/llen.ts index b3b42241..1429bdc9 100644 --- a/pkg/commands/llen.ts +++ b/pkg/commands/llen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/llen diff --git a/pkg/commands/lmove.test.ts b/pkg/commands/lmove.test.ts index 75941668..060a1ec6 100644 --- a/pkg/commands/lmove.test.ts +++ b/pkg/commands/lmove.test.ts @@ -31,7 +31,9 @@ test("moves the entry from left to left", async () => { test("moves the entry from left to right", async () => { const source = newKey(); const destination = newKey(); - const values = new Array(5).fill(0).map((_) => randomID()); + const values = Array.from({ length: 5 }) + .fill(0) + .map((_) => randomID()); await new LPushCommand([source, ...values]).exec(client); diff --git a/pkg/commands/lmove.ts b/pkg/commands/lmove.ts index fe92a1a9..35ae47f5 100644 --- a/pkg/commands/lmove.ts +++ b/pkg/commands/lmove.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lmove @@ -11,7 +12,7 @@ export class LMoveCommand extends Command { whereFrom: "left" | "right", whereTo: "left" | "right", ], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["lmove", ...cmd], opts); } diff --git a/pkg/commands/lmpop.ts b/pkg/commands/lmpop.ts index aec9acb8..d0d445db 100644 --- a/pkg/commands/lmpop.ts +++ b/pkg/commands/lmpop.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lmpop @@ -9,7 +10,7 @@ export class LmPopCommand extends Command< > { constructor( cmd: [numkeys: number, keys: string[], "LEFT" | "RIGHT", count?: number], - opts?: CommandOptions<[string, TValues[]] | null, [string, TValues[]] | null>, + opts?: CommandOptions<[string, TValues[]] | null, [string, TValues[]] | null> ) { const [numkeys, keys, direction, count] = cmd; diff --git a/pkg/commands/lpop.ts b/pkg/commands/lpop.ts index 61a6a4ce..a5c7a802 100644 --- a/pkg/commands/lpop.ts +++ b/pkg/commands/lpop.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lpop @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class LPopCommand extends Command { constructor( cmd: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["lpop", ...cmd], opts); } diff --git a/pkg/commands/lpos.ts b/pkg/commands/lpos.ts index af74a0b0..48962127 100644 --- a/pkg/commands/lpos.ts +++ b/pkg/commands/lpos.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lpos @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class LPosCommand extends Command { constructor( cmd: [key: string, element: unknown, opts?: { rank?: number; count?: number; maxLen?: number }], - opts?: CommandOptions, + opts?: CommandOptions ) { const args = ["lpos", cmd[0], cmd[1]]; diff --git a/pkg/commands/lpush.ts b/pkg/commands/lpush.ts index 18554e56..e3f57c4a 100644 --- a/pkg/commands/lpush.ts +++ b/pkg/commands/lpush.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lpush diff --git a/pkg/commands/lpushx.ts b/pkg/commands/lpushx.ts index 08142b05..b6c3b5ce 100644 --- a/pkg/commands/lpushx.ts +++ b/pkg/commands/lpushx.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/lpushx diff --git a/pkg/commands/lrange.test.ts b/pkg/commands/lrange.test.ts index c44a8163..3c0bb9e5 100644 --- a/pkg/commands/lrange.test.ts +++ b/pkg/commands/lrange.test.ts @@ -15,7 +15,7 @@ test("returns the correct range", async () => { const value3 = randomID(); await new RPushCommand([key, value1, value2, value3]).exec(client); const res = await new LRangeCommand([key, 1, 2]).exec(client); - expect(res!.length).toBe(2); - expect(res![0]).toBe(value2); - expect(res![1]).toBe(value3); + expect(res.length).toBe(2); + expect(res[0]).toBe(value2); + expect(res[1]).toBe(value3); }); diff --git a/pkg/commands/lrange.ts b/pkg/commands/lrange.ts index e089a5bb..942607fe 100644 --- a/pkg/commands/lrange.ts +++ b/pkg/commands/lrange.ts @@ -1,9 +1,10 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LRangeCommand extends Command { constructor( cmd: [key: string, start: number, end: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["lrange", ...cmd], opts); } diff --git a/pkg/commands/lrem.ts b/pkg/commands/lrem.ts index e0bba2f6..c53aa4bc 100644 --- a/pkg/commands/lrem.ts +++ b/pkg/commands/lrem.ts @@ -1,8 +1,9 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LRemCommand extends Command { constructor( cmd: [key: string, count: number, value: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["lrem", ...cmd], opts); } diff --git a/pkg/commands/lset.ts b/pkg/commands/lset.ts index ba0561e2..398a4ea9 100644 --- a/pkg/commands/lset.ts +++ b/pkg/commands/lset.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LSetCommand extends Command<"OK", "OK"> { constructor(cmd: [key: string, index: number, data: TData], opts?: CommandOptions<"OK", "OK">) { diff --git a/pkg/commands/ltrim.ts b/pkg/commands/ltrim.ts index 7a8d7a00..fa7b6276 100644 --- a/pkg/commands/ltrim.ts +++ b/pkg/commands/ltrim.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class LTrimCommand extends Command<"OK", "OK"> { constructor(cmd: [key: string, start: number, end: number], opts?: CommandOptions<"OK", "OK">) { diff --git a/pkg/commands/mget.ts b/pkg/commands/mget.ts index 2b59a496..5f9aa03f 100644 --- a/pkg/commands/mget.ts +++ b/pkg/commands/mget.ts @@ -1,13 +1,11 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/mget */ export class MGetCommand extends Command<(string | null)[], TData> { - constructor( - cmd: [string[]] | [...(string[] | string[])], - opts?: CommandOptions<(string | null)[], TData>, - ) { - const keys = Array.isArray(cmd[0]) ? (cmd[0] as string[]) : (cmd as string[]); + constructor(cmd: [string[]] | [...string[]], opts?: CommandOptions<(string | null)[], TData>) { + const keys = Array.isArray(cmd[0]) ? cmd[0] : (cmd as string[]); super(["mget", ...keys], opts); } } diff --git a/pkg/commands/mset.ts b/pkg/commands/mset.ts index d0b262bd..2194168d 100644 --- a/pkg/commands/mset.ts +++ b/pkg/commands/mset.ts @@ -1,10 +1,11 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/mset */ export class MSetCommand extends Command<"OK", "OK"> { - constructor([kv]: [kv: { [key: string]: TData }], opts?: CommandOptions<"OK", "OK">) { + constructor([kv]: [kv: Record], opts?: CommandOptions<"OK", "OK">) { super(["mset", ...Object.entries(kv).flatMap(([key, value]) => [key, value])], opts); } } diff --git a/pkg/commands/msetnx.ts b/pkg/commands/msetnx.ts index 5b0af4a3..3ed03b4d 100644 --- a/pkg/commands/msetnx.ts +++ b/pkg/commands/msetnx.ts @@ -1,10 +1,11 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/msetnx */ export class MSetNXCommand extends Command { - constructor([kv]: [kv: { [key: string]: TData }], opts?: CommandOptions) { - super(["msetnx", ...Object.entries(kv).flatMap((_) => _)], opts); + constructor([kv]: [kv: Record], opts?: CommandOptions) { + super(["msetnx", ...Object.entries(kv).flat()], opts); } } diff --git a/pkg/commands/persist.ts b/pkg/commands/persist.ts index 92e1b263..09ba5638 100644 --- a/pkg/commands/persist.ts +++ b/pkg/commands/persist.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/persist diff --git a/pkg/commands/pexpire.ts b/pkg/commands/pexpire.ts index 9837a879..abcdc021 100644 --- a/pkg/commands/pexpire.ts +++ b/pkg/commands/pexpire.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/pexpire diff --git a/pkg/commands/pexpireat.ts b/pkg/commands/pexpireat.ts index dd75c7ac..66a09068 100644 --- a/pkg/commands/pexpireat.ts +++ b/pkg/commands/pexpireat.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/pexpireat diff --git a/pkg/commands/pfadd.ts b/pkg/commands/pfadd.ts index c01e77e4..6537a013 100644 --- a/pkg/commands/pfadd.ts +++ b/pkg/commands/pfadd.ts @@ -1,10 +1,11 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; /** * @see https://redis.io/commands/pfadd */ export class PfAddCommand extends Command { - constructor(cmd: [string, ...(TData[] | TData[])], opts?: CommandOptions) { + constructor(cmd: [string, ...TData[]], opts?: CommandOptions) { super(["pfadd", ...cmd], opts); } } diff --git a/pkg/commands/pfcount.ts b/pkg/commands/pfcount.ts index 99a8e3f3..3aaf5426 100644 --- a/pkg/commands/pfcount.ts +++ b/pkg/commands/pfcount.ts @@ -1,10 +1,11 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; /** * @see https://redis.io/commands/pfcount */ export class PfCountCommand extends Command { - constructor(cmd: [string, ...(string[] | string[])], opts?: CommandOptions) { + constructor(cmd: [string, ...string[]], opts?: CommandOptions) { super(["pfcount", ...cmd], opts); } } diff --git a/pkg/commands/pfmerge.ts b/pkg/commands/pfmerge.ts index c487173f..d4bc1ccd 100644 --- a/pkg/commands/pfmerge.ts +++ b/pkg/commands/pfmerge.ts @@ -1,13 +1,11 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; /** * @see https://redis.io/commands/pfmerge */ export class PfMergeCommand extends Command<"OK", "OK"> { - constructor( - cmd: [destination_key: string, ...(string[] | string[])], - opts?: CommandOptions<"OK", "OK">, - ) { + constructor(cmd: [destination_key: string, ...string[]], opts?: CommandOptions<"OK", "OK">) { super(["pfmerge", ...cmd], opts); } } diff --git a/pkg/commands/ping.ts b/pkg/commands/ping.ts index 7c806ad6..24c5350e 100644 --- a/pkg/commands/ping.ts +++ b/pkg/commands/ping.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/ping */ export class PingCommand extends Command { constructor(cmd?: [message?: string], opts?: CommandOptions) { const command: string[] = ["ping"]; - if (typeof cmd !== "undefined" && typeof cmd![0] !== "undefined") { + if (cmd?.[0] !== undefined) { command.push(cmd[0]); } super(command, opts); diff --git a/pkg/commands/psetex.ts b/pkg/commands/psetex.ts index 363b69ac..efc92c33 100644 --- a/pkg/commands/psetex.ts +++ b/pkg/commands/psetex.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/psetex @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class PSetEXCommand extends Command { constructor( cmd: [key: string, ttl: number, value: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["psetex", ...cmd], opts); } diff --git a/pkg/commands/pttl.ts b/pkg/commands/pttl.ts index 17a4e84c..99ef38e3 100644 --- a/pkg/commands/pttl.ts +++ b/pkg/commands/pttl.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/pttl diff --git a/pkg/commands/publish.ts b/pkg/commands/publish.ts index 3e8d1fdf..b3a1dcbc 100644 --- a/pkg/commands/publish.ts +++ b/pkg/commands/publish.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/publish diff --git a/pkg/commands/randomkey.ts b/pkg/commands/randomkey.ts index 8aa4fdd5..083b0f03 100644 --- a/pkg/commands/randomkey.ts +++ b/pkg/commands/randomkey.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/randomkey diff --git a/pkg/commands/rename.ts b/pkg/commands/rename.ts index b3fd079d..668af252 100644 --- a/pkg/commands/rename.ts +++ b/pkg/commands/rename.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/rename diff --git a/pkg/commands/renamenx.ts b/pkg/commands/renamenx.ts index e903818f..a1fd600b 100644 --- a/pkg/commands/renamenx.ts +++ b/pkg/commands/renamenx.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/renamenx diff --git a/pkg/commands/rpop.ts b/pkg/commands/rpop.ts index cbd94853..8d4f61af 100644 --- a/pkg/commands/rpop.ts +++ b/pkg/commands/rpop.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/rpop @@ -9,7 +10,7 @@ export class RPopCommand extends Com > { constructor( cmd: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["rpop", ...cmd], opts); } diff --git a/pkg/commands/rpush.ts b/pkg/commands/rpush.ts index 233ef212..c38f2068 100644 --- a/pkg/commands/rpush.ts +++ b/pkg/commands/rpush.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/rpush diff --git a/pkg/commands/rpushx.ts b/pkg/commands/rpushx.ts index 5d9533f7..cc4d4989 100644 --- a/pkg/commands/rpushx.ts +++ b/pkg/commands/rpushx.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/rpushx diff --git a/pkg/commands/sadd.ts b/pkg/commands/sadd.ts index ca99df18..41134a4a 100644 --- a/pkg/commands/sadd.ts +++ b/pkg/commands/sadd.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sadd diff --git a/pkg/commands/scan.ts b/pkg/commands/scan.ts index dea9e8b9..16199fa8 100644 --- a/pkg/commands/scan.ts +++ b/pkg/commands/scan.ts @@ -1,5 +1,6 @@ import { deserializeScanResponse } from "../util"; -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ScanCommandOptions = { match?: string; @@ -12,7 +13,7 @@ export type ScanCommandOptions = { export class ScanCommand extends Command<[string, string[]], [string, string[]]> { constructor( [cursor, opts]: [cursor: string | number, opts?: ScanCommandOptions], - cmdOpts?: CommandOptions<[string, string[]], [string, string[]]>, + cmdOpts?: CommandOptions<[string, string[]], [string, string[]]> ) { const command: (number | string)[] = ["scan", cursor]; if (opts?.match) { diff --git a/pkg/commands/scard.ts b/pkg/commands/scard.ts index 07f7acb9..0556fc68 100644 --- a/pkg/commands/scard.ts +++ b/pkg/commands/scard.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/scard */ diff --git a/pkg/commands/script_exists.ts b/pkg/commands/script_exists.ts index 7b1ad9ce..4affe923 100644 --- a/pkg/commands/script_exists.ts +++ b/pkg/commands/script_exists.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/script-exists diff --git a/pkg/commands/script_flush.test.ts b/pkg/commands/script_flush.test.ts deleted file mode 100644 index f3bdfec4..00000000 --- a/pkg/commands/script_flush.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { newHttpClient, randomID } from "../test-utils"; -import { ScriptLoadCommand } from "./script_load"; - -import { describe, expect, test } from "bun:test"; -import { ScriptExistsCommand } from "./script_exists"; -import { ScriptFlushCommand } from "./script_flush"; -const client = newHttpClient(); - -describe("sync", () => { - test("flushes all scripts", async () => { - const script = `return "${randomID()}"`; - const sha1 = await new ScriptLoadCommand([script]).exec(client); - expect(await new ScriptExistsCommand([sha1]).exec(client)).toEqual([1]); - - const res = await new ScriptFlushCommand([{ sync: true }]).exec(client); - expect(res).toEqual("OK"); - expect(await new ScriptExistsCommand([sha1]).exec(client)).toEqual([0]); - }); -}); - -describe("async", () => { - test("flushes all scripts", async () => { - const script = `return "${randomID()}"`; - const sha1 = await new ScriptLoadCommand([script]).exec(client); - expect(await new ScriptExistsCommand([sha1]).exec(client)).toEqual([1]); - - const res = await new ScriptFlushCommand([{ async: true }]).exec(client); - - expect(res).toEqual("OK"); - - await new Promise((res) => setTimeout(res, 5000)); - expect(await new ScriptExistsCommand([sha1]).exec(client)).toEqual([0]); - }); -}); diff --git a/pkg/commands/script_flush.ts b/pkg/commands/script_flush.ts index 17f29faa..0c5d93b9 100644 --- a/pkg/commands/script_flush.ts +++ b/pkg/commands/script_flush.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ScriptFlushCommandOptions = | { sync: true; async?: never } diff --git a/pkg/commands/script_load.ts b/pkg/commands/script_load.ts index ee402386..6050170a 100644 --- a/pkg/commands/script_load.ts +++ b/pkg/commands/script_load.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/script-load diff --git a/pkg/commands/sdiff.ts b/pkg/commands/sdiff.ts index b67af1e1..f7acbaed 100644 --- a/pkg/commands/sdiff.ts +++ b/pkg/commands/sdiff.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sdiff */ diff --git a/pkg/commands/sdiffstore.ts b/pkg/commands/sdiffstore.ts index b9f0b97b..a95b0afd 100644 --- a/pkg/commands/sdiffstore.ts +++ b/pkg/commands/sdiffstore.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sdiffstore */ export class SDiffStoreCommand extends Command { constructor( cmd: [destination: string, ...keys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["sdiffstore", ...cmd], opts); } diff --git a/pkg/commands/set.ts b/pkg/commands/set.ts index 273a018c..da0da164 100644 --- a/pkg/commands/set.ts +++ b/pkg/commands/set.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type SetCommandOptions = { get?: boolean } & ( | { ex: number; px?: never; exat?: never; pxat?: never; keepTtl?: never } @@ -19,7 +20,7 @@ export class SetCommand extends Command< > { constructor( [key, value, opts]: [key: string, value: TData, opts?: SetCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ) { const command: unknown[] = ["set", key, value]; if (opts) { diff --git a/pkg/commands/setbit.ts b/pkg/commands/setbit.ts index 06ea8597..f449c550 100644 --- a/pkg/commands/setbit.ts +++ b/pkg/commands/setbit.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/setbit */ @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class SetBitCommand extends Command<"0" | "1", 0 | 1> { constructor( cmd: [key: string, offset: number, value: 0 | 1], - opts?: CommandOptions<"0" | "1", 0 | 1>, + opts?: CommandOptions<"0" | "1", 0 | 1> ) { super(["setbit", ...cmd], opts); } diff --git a/pkg/commands/setex.ts b/pkg/commands/setex.ts index e8e813d5..de07e291 100644 --- a/pkg/commands/setex.ts +++ b/pkg/commands/setex.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/setex diff --git a/pkg/commands/setnx.ts b/pkg/commands/setnx.ts index 54ee17d4..65d68c96 100644 --- a/pkg/commands/setnx.ts +++ b/pkg/commands/setnx.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/setnx diff --git a/pkg/commands/setrange.ts b/pkg/commands/setrange.ts index cccfbf68..4a1fd16e 100644 --- a/pkg/commands/setrange.ts +++ b/pkg/commands/setrange.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/setrange @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class SetRangeCommand extends Command { constructor( cmd: [key: string, offset: number, value: string], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["setrange", ...cmd], opts); } diff --git a/pkg/commands/sinter.ts b/pkg/commands/sinter.ts index 9440955a..d76dc369 100644 --- a/pkg/commands/sinter.ts +++ b/pkg/commands/sinter.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sinter */ diff --git a/pkg/commands/sinterstore.ts b/pkg/commands/sinterstore.ts index c8a45749..51d3ed9d 100644 --- a/pkg/commands/sinterstore.ts +++ b/pkg/commands/sinterstore.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sinterstore */ export class SInterStoreCommand extends Command { constructor( cmd: [destination: string, key: string, ...keys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["sinterstore", ...cmd], opts); } diff --git a/pkg/commands/sismember.ts b/pkg/commands/sismember.ts index 62434054..c7dc22f9 100644 --- a/pkg/commands/sismember.ts +++ b/pkg/commands/sismember.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sismember */ diff --git a/pkg/commands/smembers.test.ts b/pkg/commands/smembers.test.ts index 4bcbc490..452584f8 100644 --- a/pkg/commands/smembers.test.ts +++ b/pkg/commands/smembers.test.ts @@ -16,7 +16,7 @@ test("returns all members of the set", async () => { await new SAddCommand([key, value1, value2]).exec(client); const res = await new SMembersCommand<{ v: string }[]>([key]).exec(client); - expect(res!.length).toBe(2); - expect(res!.map(({ v }) => v).includes(value1.v)); - expect(res!.map(({ v }) => v).includes(value2.v)).toBe(true); + expect(res.length).toBe(2); + expect(res.map(({ v }) => v).includes(value1.v)); + expect(res.map(({ v }) => v).includes(value2.v)).toBe(true); }); diff --git a/pkg/commands/smembers.ts b/pkg/commands/smembers.ts index 1ebff7b2..e8fe9e50 100644 --- a/pkg/commands/smembers.ts +++ b/pkg/commands/smembers.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/smembers diff --git a/pkg/commands/smismember.ts b/pkg/commands/smismember.ts index b5aebbee..9952b7d1 100644 --- a/pkg/commands/smismember.ts +++ b/pkg/commands/smismember.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/smismember */ @@ -8,7 +9,7 @@ export class SMIsMemberCommand extends Command< > { constructor( cmd: [key: string, members: TMembers], - opts?: CommandOptions<("0" | "1")[], (0 | 1)[]>, + opts?: CommandOptions<("0" | "1")[], (0 | 1)[]> ) { super(["smismember", cmd[0], ...cmd[1]], opts); } diff --git a/pkg/commands/smove.ts b/pkg/commands/smove.ts index 5e76080f..1afac991 100644 --- a/pkg/commands/smove.ts +++ b/pkg/commands/smove.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/smove */ export class SMoveCommand extends Command<"0" | "1", 0 | 1> { constructor( cmd: [source: string, destination: string, member: TData], - opts?: CommandOptions<"0" | "1", 0 | 1>, + opts?: CommandOptions<"0" | "1", 0 | 1> ) { super(["smove", ...cmd], opts); } diff --git a/pkg/commands/spop.ts b/pkg/commands/spop.ts index 6786ddb6..83b642fd 100644 --- a/pkg/commands/spop.ts +++ b/pkg/commands/spop.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/spop */ export class SPopCommand extends Command { constructor( [key, count]: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["spop", key]; if (typeof count === "number") { diff --git a/pkg/commands/srandmember.ts b/pkg/commands/srandmember.ts index adeaec46..e6d28f9c 100644 --- a/pkg/commands/srandmember.ts +++ b/pkg/commands/srandmember.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/srandmember */ export class SRandMemberCommand extends Command { constructor( [key, count]: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["srandmember", key]; if (typeof count === "number") { diff --git a/pkg/commands/srem.ts b/pkg/commands/srem.ts index a6d4b4ff..21b440a7 100644 --- a/pkg/commands/srem.ts +++ b/pkg/commands/srem.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/srem */ diff --git a/pkg/commands/sscan.test.ts b/pkg/commands/sscan.test.ts index c4bd0e13..20150b05 100644 --- a/pkg/commands/sscan.test.ts +++ b/pkg/commands/sscan.test.ts @@ -17,7 +17,7 @@ describe("without options", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -30,7 +30,7 @@ describe("with match", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -43,6 +43,6 @@ describe("with count", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); diff --git a/pkg/commands/sscan.ts b/pkg/commands/sscan.ts index 310b636d..c0ba4cea 100644 --- a/pkg/commands/sscan.ts +++ b/pkg/commands/sscan.ts @@ -1,6 +1,7 @@ import { deserializeScanResponse } from "../util"; -import { Command, CommandOptions } from "./command"; -import { ScanCommandOptions } from "./scan"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; +import type { ScanCommandOptions } from "./scan"; /** * @see https://redis.io/commands/sscan @@ -11,7 +12,7 @@ export class SScanCommand extends Command< > { constructor( [key, cursor, opts]: [key: string, cursor: string | number, opts?: ScanCommandOptions], - cmdOpts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]>, + cmdOpts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]> ) { const command: (number | string)[] = ["sscan", key, cursor]; if (opts?.match) { diff --git a/pkg/commands/strlen.ts b/pkg/commands/strlen.ts index 42edb017..81e664a4 100644 --- a/pkg/commands/strlen.ts +++ b/pkg/commands/strlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/strlen diff --git a/pkg/commands/sunion.test.ts b/pkg/commands/sunion.test.ts index 53d687fd..2d2b2d27 100644 --- a/pkg/commands/sunion.test.ts +++ b/pkg/commands/sunion.test.ts @@ -18,5 +18,5 @@ test("returns the union", async () => { await new SAddCommand([key1, member1]).exec(client); await new SAddCommand([key2, member2]).exec(client); const res = await new SUnionCommand([key1, key2]).exec(client); - expect(res?.sort()).toEqual([member1, member2].sort()); + expect(res.sort()).toEqual([member1, member2].sort()); }); diff --git a/pkg/commands/sunion.ts b/pkg/commands/sunion.ts index 4dbab2c6..0582261a 100644 --- a/pkg/commands/sunion.ts +++ b/pkg/commands/sunion.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sunion diff --git a/pkg/commands/sunionstore.test.ts b/pkg/commands/sunionstore.test.ts index 476af262..ae8854b6 100644 --- a/pkg/commands/sunionstore.test.ts +++ b/pkg/commands/sunionstore.test.ts @@ -26,5 +26,5 @@ test("writes the union to destination", async () => { const res2 = await new SMembersCommand([dest]).exec(client); expect(res2).toBeTruthy(); - expect(res2!.sort()).toEqual([member1, member2].sort()); + expect(res2.sort()).toEqual([member1, member2].sort()); }); diff --git a/pkg/commands/sunionstore.ts b/pkg/commands/sunionstore.ts index d1e6670c..cd37833a 100644 --- a/pkg/commands/sunionstore.ts +++ b/pkg/commands/sunionstore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/sunionstore @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class SUnionStoreCommand extends Command { constructor( cmd: [destination: string, key: string, ...keys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["sunionstore", ...cmd], opts); } diff --git a/pkg/commands/time.ts b/pkg/commands/time.ts index e6cefe17..a47b4586 100644 --- a/pkg/commands/time.ts +++ b/pkg/commands/time.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/time */ diff --git a/pkg/commands/touch.ts b/pkg/commands/touch.ts index ac740282..404ef0a3 100644 --- a/pkg/commands/touch.ts +++ b/pkg/commands/touch.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/touch diff --git a/pkg/commands/ttl.ts b/pkg/commands/ttl.ts index 8f8a271c..9d8ed22d 100644 --- a/pkg/commands/ttl.ts +++ b/pkg/commands/ttl.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/ttl diff --git a/pkg/commands/type.ts b/pkg/commands/type.ts index 774ea824..2586c06c 100644 --- a/pkg/commands/type.ts +++ b/pkg/commands/type.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type Type = "string" | "list" | "set" | "zset" | "hash" | "none"; /** diff --git a/pkg/commands/types.ts b/pkg/commands/types.ts index c1309108..bbe99801 100644 --- a/pkg/commands/types.ts +++ b/pkg/commands/types.ts @@ -132,10 +132,7 @@ export { type ZCardCommand } from "./zcard"; export { type ZCountCommand } from "./zcount"; export { type ZDiffStoreCommand } from "./zdiffstore"; export { type ZIncrByCommand } from "./zincrby"; -export { - type ZInterStoreCommand, - ZInterStoreCommandOptions, -} from "./zinterstore"; +export { type ZInterStoreCommand, ZInterStoreCommandOptions } from "./zinterstore"; export { type ZLexCountCommand } from "./zlexcount"; export { type ZMScoreCommand } from "./zmscore"; export { type ZPopMaxCommand } from "./zpopmax"; @@ -150,7 +147,4 @@ export { type ZRevRankCommand } from "./zrevrank"; export { type ZScanCommand } from "./zscan"; export { type ZScoreCommand } from "./zscore"; export { type ZUnionCommand, ZUnionCommandOptions } from "./zunion"; -export { - type ZUnionStoreCommand, - ZUnionStoreCommandOptions, -} from "./zunionstore"; +export { type ZUnionStoreCommand, ZUnionStoreCommandOptions } from "./zunionstore"; diff --git a/pkg/commands/unlink.ts b/pkg/commands/unlink.ts index 6b212ae5..1a2af332 100644 --- a/pkg/commands/unlink.ts +++ b/pkg/commands/unlink.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/unlink diff --git a/pkg/commands/xack.test.ts b/pkg/commands/xack.test.ts index 67708d28..f92a32e2 100644 --- a/pkg/commands/xack.test.ts +++ b/pkg/commands/xack.test.ts @@ -22,7 +22,7 @@ describe("XACK", () => { await new XGroupCommand([streamKey1, { type: "CREATE", group, id: "0" }]).exec(client); (await new XReadGroupCommand([group, consumer, streamKey1, ">", { count: 2 }]).exec( - client, + client )) as string[]; const res = await new XAckCommand([streamKey1, group, [streamId1, streamId2]]).exec(client); @@ -42,7 +42,7 @@ describe("XACK", () => { ]).exec(client); (await new XReadGroupCommand([group, consumer, streamKey1, ">", { count: 2 }]).exec( - client, + client )) as string[]; const res = await new XAckCommand([streamKey1, group, streamId1]).exec(client); diff --git a/pkg/commands/xack.ts b/pkg/commands/xack.ts index ad7c79fc..8fa302bc 100644 --- a/pkg/commands/xack.ts +++ b/pkg/commands/xack.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xack @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class XAckCommand extends Command { constructor( [key, group, id]: [key: string, group: string, id: string | string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { const ids = Array.isArray(id) ? [...id] : [id]; super(["XACK", key, group, ...ids], opts); diff --git a/pkg/commands/xadd.ts b/pkg/commands/xadd.ts index c0830b0a..eeaad9eb 100644 --- a/pkg/commands/xadd.ts +++ b/pkg/commands/xadd.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; type XAddCommandOptions = { nomkStream?: boolean; @@ -32,10 +33,10 @@ export class XAddCommand extends Command { [key, id, entries, opts]: [ key: string, id: "*" | string, - entries: { [field: string]: unknown }, + entries: Record, opts?: XAddCommandOptions, ], - commandOptions?: CommandOptions, + commandOptions?: CommandOptions ) { const command: unknown[] = ["XADD", key]; @@ -45,7 +46,7 @@ export class XAddCommand extends Command { } if (opts.trim) { command.push(opts.trim.type, opts.trim.comparison, opts.trim.threshold); - if (typeof opts.trim.limit !== "undefined") { + if (opts.trim.limit !== undefined) { command.push("LIMIT", opts.trim.limit); } } diff --git a/pkg/commands/xautoclaim.test.ts b/pkg/commands/xautoclaim.test.ts index 7fd3e94b..eab4d304 100644 --- a/pkg/commands/xautoclaim.test.ts +++ b/pkg/commands/xautoclaim.test.ts @@ -26,7 +26,7 @@ describe("XCLAIM", () => { await new XReadGroupCommand([group, consumer1, [streamKey], [">"], { count: 2 }]).exec(client); const res = (await new XAutoClaim([streamKey, group, consumer2, 10, "0-0", { count: 1 }]).exec( - client, + client )) as string[]; expect(res).toBeInstanceOf(Array); }); diff --git a/pkg/commands/xautoclaim.ts b/pkg/commands/xautoclaim.ts index 47acb86e..2614ec16 100644 --- a/pkg/commands/xautoclaim.ts +++ b/pkg/commands/xautoclaim.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xautoclaim @@ -13,7 +14,7 @@ export class XAutoClaim extends Command { start: string, options?: { count?: number; justId?: boolean }, ], - opts?: CommandOptions, + opts?: CommandOptions ) { const commands: unknown[] = []; diff --git a/pkg/commands/xclaim.test.ts b/pkg/commands/xclaim.test.ts index 835c5068..38b4d170 100644 --- a/pkg/commands/xclaim.test.ts +++ b/pkg/commands/xclaim.test.ts @@ -31,7 +31,7 @@ describe("XCLAIM", () => { await new XReadGroupCommand([group, consumer3, [streamKey], [">"], { count: 1 }]).exec(client); const res = (await new XClaimCommand([streamKey, group, consumer3, 100, streamId2]).exec( - client, + client )) as string[]; expect(res).toBeInstanceOf(Array); diff --git a/pkg/commands/xclaim.ts b/pkg/commands/xclaim.ts index c208441f..c0745e6a 100644 --- a/pkg/commands/xclaim.ts +++ b/pkg/commands/xclaim.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xclaim @@ -20,7 +21,7 @@ export class XClaimCommand extends Command { lastId?: number; }, ], - opts?: CommandOptions, + opts?: CommandOptions ) { const ids = Array.isArray(id) ? [...id] : [id]; const commands: unknown[] = []; @@ -34,7 +35,7 @@ export class XClaimCommand extends Command { } if (options?.retryCount) { - commands.push("RETRYCOUNT", options?.retryCount); + commands.push("RETRYCOUNT", options.retryCount); } if (options?.force) { diff --git a/pkg/commands/xdel.test.ts b/pkg/commands/xdel.test.ts index 380e1a33..b2f62b06 100644 --- a/pkg/commands/xdel.test.ts +++ b/pkg/commands/xdel.test.ts @@ -16,7 +16,7 @@ describe("XDEL", () => { await new XAddCommand([key, "*", { name: "Jane", surname: "Austen" }]).exec(client); const res = await new XAddCommand([key, "*", { name: "Toni", surname: "Morrison" }]).exec( - client, + client ); const xdelRes = await new XDelCommand([key, res]).exec(client); @@ -32,11 +32,11 @@ describe("XDEL", () => { const id1 = await new XAddCommand([key, "*", { name: "Jane", surname: "Austen" }]).exec(client); const id2 = await new XAddCommand([key, "*", { name: "Toni", surname: "Morrison" }]).exec( - client, + client ); const id3 = await new XAddCommand([key, "*", { name: "Agatha", surname: "Christie" }]).exec( - client, + client ); await new XAddCommand([key, "*", { name: "Ngozi", surname: "Adichie" }]).exec(client); diff --git a/pkg/commands/xdel.ts b/pkg/commands/xdel.ts index f022052d..f38e798d 100644 --- a/pkg/commands/xdel.ts +++ b/pkg/commands/xdel.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xdel @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class XDelCommand extends Command { constructor( [key, ids]: [key: string, ids: string[] | string], - opts?: CommandOptions, + opts?: CommandOptions ) { const cmds = Array.isArray(ids) ? [...ids] : [ids]; super(["XDEL", key, ...cmds], opts); diff --git a/pkg/commands/xgroup.test.ts b/pkg/commands/xgroup.test.ts index 92caeb9b..097bf20f 100644 --- a/pkg/commands/xgroup.test.ts +++ b/pkg/commands/xgroup.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/consistent-function-scoping */ import { keygen, newHttpClient } from "../test-utils"; import { afterAll, describe, expect, test } from "bun:test"; diff --git a/pkg/commands/xgroup.ts b/pkg/commands/xgroup.ts index 7b9cbc88..97d579b2 100644 --- a/pkg/commands/xgroup.ts +++ b/pkg/commands/xgroup.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command.ts"; +import type { CommandOptions } from "./command.ts"; +import { Command } from "./command.ts"; type XGroupCommandType = | { @@ -49,12 +50,12 @@ export class XGroupCommand { constructor( [key, opts]: [key: string, opts: TOptions], - commandOptions?: CommandOptions, + commandOptions?: CommandOptions ) { const command: unknown[] = ["XGROUP"]; switch (opts.type) { - case "CREATE": + case "CREATE": { command.push("CREATE", key, opts.group, opts.id); if (opts.options) { if (opts.options.MKSTREAM) { @@ -65,28 +66,34 @@ export class XGroupCommand { await new XGroupCommand([streamKey, { type: "CREATE", group, id: "0" }]).exec(client); const res = (await new XInfoCommand([streamKey, { type: "CONSUMERS", group }]).exec( - client, + client )) as string[]; expect(res).toEqual([]); @@ -73,7 +73,7 @@ describe("CONSUMERS", () => { (await new XReadGroupCommand([group, consumer, streamKey, ">"]).exec(client)) as string[]; const res = (await new XInfoCommand([streamKey, { type: "CONSUMERS", group }]).exec( - client, + client )) as string[]; const pendingCount = res[0][3]; diff --git a/pkg/commands/xinfo.ts b/pkg/commands/xinfo.ts index db2eb473..f2adfb77 100644 --- a/pkg/commands/xinfo.ts +++ b/pkg/commands/xinfo.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; type XInfoCommands = | { @@ -13,7 +14,7 @@ type XInfoCommands = export class XInfoCommand extends Command { constructor( [key, options]: [key: string, options: XInfoCommands], - opts?: CommandOptions, + opts?: CommandOptions ) { const cmds: unknown[] = []; if (options.type === "CONSUMERS") { diff --git a/pkg/commands/xlen.ts b/pkg/commands/xlen.ts index 01b5a316..d5f97cb0 100644 --- a/pkg/commands/xlen.ts +++ b/pkg/commands/xlen.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xlen diff --git a/pkg/commands/xpending.ts b/pkg/commands/xpending.ts index eacebcd2..01a29677 100644 --- a/pkg/commands/xpending.ts +++ b/pkg/commands/xpending.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xpending @@ -16,14 +17,14 @@ export class XPendingCommand extends Command { consumer?: string | string[]; }, ], - opts?: CommandOptions, + opts?: CommandOptions ) { const consumers = - typeof options?.consumer !== "undefined" - ? Array.isArray(options.consumer) + options?.consumer === undefined + ? [] + : Array.isArray(options.consumer) ? [...options.consumer] - : [options.consumer] - : []; + : [options.consumer]; super( [ @@ -36,7 +37,7 @@ export class XPendingCommand extends Command { count, ...consumers, ], - opts, + opts ); } } diff --git a/pkg/commands/xrange.ts b/pkg/commands/xrange.ts index 81228e11..340a0cfd 100644 --- a/pkg/commands/xrange.ts +++ b/pkg/commands/xrange.ts @@ -1,7 +1,8 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; function deserialize>>( - result: [string, string[]][], + result: [string, string[]][] ): TData { const obj: Record> = {}; for (const e of result) { @@ -13,8 +14,8 @@ function deserialize>>( obj[streamId] = {}; } while (entries.length >= 2) { - const field = (entries as string[]).shift()! as string; - const value = (entries as string[]).shift()! as string; + const field = (entries as string[]).shift()!; + const value = (entries as string[]).shift()!; try { obj[streamId][field] = JSON.parse(value); @@ -33,7 +34,7 @@ export class XRangeCommand> > { constructor( [key, start, end, count]: [key: string, start: string, end: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["XRANGE", key, start, end]; if (typeof count === "number") { diff --git a/pkg/commands/xread.test.ts b/pkg/commands/xread.test.ts index 1066bb03..c87abf04 100644 --- a/pkg/commands/xread.test.ts +++ b/pkg/commands/xread.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/consistent-function-scoping */ import { addNewItemToStream, keygen, newHttpClient } from "../test-utils"; import { afterAll, describe, expect, test } from "bun:test"; @@ -15,6 +16,7 @@ describe("COUNT", () => { const res = (await new XReadCommand([streamKey, "0-0"]).exec(client)) as string[]; + //@ts-expect-error to silence compiler expect(res[0][1][0][1]).toEqual(["field1", xmember1, "field2", xmember2]); }); test("should return multiple items", async () => { @@ -25,7 +27,7 @@ describe("COUNT", () => { await addNewItemToStream(streamKey, client); const res = (await new XReadCommand([streamKey, "0-0", { count: wantedLength }]).exec( - client, + client )) as any[]; expect(res[0][1].length).toBe(wantedLength); @@ -38,7 +40,7 @@ describe("COUNT", () => { await addNewItemToStream(streamKey, client); const res = (await new XReadCommand([streamKey, "0-0", { count: wantedLength }]).exec( - client, + client )) as any[]; expect(res[0][1].length).toBe(wantedLength); @@ -52,12 +54,12 @@ describe("IDs", () => { const streamKey = newKey(); await addNewItemToStream(streamKey, client); - const res = (await new XReadCommand([streamKey, `${Date.now() - 15000}-0`]).exec( - client, + const res = (await new XReadCommand([streamKey, `${Date.now() - 15_000}-0`]).exec( + client )) as string[]; expect(res).toBeInstanceOf(Array); }, - { retry: 3 }, + { retry: 3 } ); }); @@ -78,7 +80,7 @@ describe("Multiple stream", () => { ]).exec(client)) as string[]; expect(res.length).toBe(wantedLength); }, - { retry: 3 }, + { retry: 3 } ); test( @@ -95,7 +97,7 @@ describe("Multiple stream", () => { ]).exec(client)) as string[]; expect(res.length).toBe(wantedLength); }, - { retry: 3 }, + { retry: 3 } ); test( @@ -110,6 +112,6 @@ describe("Multiple stream", () => { expect(throwable).toThrow(UNBALANCED_XREAD_ERR); }, - { retry: 3 }, + { retry: 3 } ); }); diff --git a/pkg/commands/xread.ts b/pkg/commands/xread.ts index cd3c153f..c276a351 100644 --- a/pkg/commands/xread.ts +++ b/pkg/commands/xread.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export const UNBALANCED_XREAD_ERR = "ERR Unbalanced XREAD list of streams: for each stream key an ID or '$' must be specified"; @@ -27,10 +28,8 @@ type XReadOptions = XReadCommandOptions extends [infer K, infer I, ...any[]] */ export class XReadCommand extends Command { constructor([key, id, options]: XReadOptions, opts?: CommandOptions) { - if (Array.isArray(key) && Array.isArray(id)) { - if (key.length !== id.length) { - throw new Error(UNBALANCED_XREAD_ERR); - } + if (Array.isArray(key) && Array.isArray(id) && key.length !== id.length) { + throw new Error(UNBALANCED_XREAD_ERR); } const commands: unknown[] = []; @@ -44,7 +43,7 @@ export class XReadCommand extends Command { commands.push( "STREAMS", ...(Array.isArray(key) ? [...key] : [key]), - ...(Array.isArray(id) ? [...id] : [id]), + ...(Array.isArray(id) ? [...id] : [id]) ); super(["XREAD", ...commands], opts); diff --git a/pkg/commands/xreadgroup.test.ts b/pkg/commands/xreadgroup.test.ts index acb1f949..df49a8cc 100644 --- a/pkg/commands/xreadgroup.test.ts +++ b/pkg/commands/xreadgroup.test.ts @@ -24,7 +24,7 @@ describe("COUNT", () => { await new XGroupCommand([streamKey, { type: "CREATE", group, id: "0" }]).exec(client); const res = (await new XReadGroupCommand([group, consumer, streamKey, ">"]).exec( - client, + client )) as string[]; const listOfStreams = res[0][1]; @@ -69,7 +69,7 @@ describe("NOACK", () => { await new XReadGroupCommand([group, consumer, streamKey, ">", { NOACK: true }]).exec(client); const xinfoRes = (await new XInfoCommand([streamKey, { type: "CONSUMERS", group }]).exec( - client, + client )) as string[]; expect(xinfoRes).toEqual([]); }); @@ -89,10 +89,10 @@ describe("NOACK", () => { await new XReadGroupCommand([group, consumer, streamKey, ">", { NOACK: false }]).exec(client); const xinfoRes = (await new XInfoCommand([streamKey, { type: "CONSUMERS", group }]).exec( - client, + client )) as string[]; - const pendingCount = xinfoRes[0][3]; + const pendingCount = Number(xinfoRes[0][3]); expect(pendingCount).toBe(wantedCount); }); @@ -148,6 +148,7 @@ describe("Multiple Stream", () => { }); test("should throw when unbalanced is array passed", () => { + // eslint-disable-next-line unicorn/consistent-function-scoping const throwable = async () => { const streamKey = newKey(); const group = newKey(); diff --git a/pkg/commands/xreadgroup.ts b/pkg/commands/xreadgroup.ts index 06fed87a..eedb75a1 100644 --- a/pkg/commands/xreadgroup.ts +++ b/pkg/commands/xreadgroup.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export const UNBALANCED_XREADGROUP_ERR = "ERR Unbalanced XREADGROUP list of streams: for each stream key an ID or '$' must be specified"; @@ -37,12 +38,10 @@ type XReadGroupOptions = XReadGroupCommandOptions extends [ export class XReadGroupCommand extends Command { constructor( [group, consumer, key, id, options]: XReadGroupOptions, - opts?: CommandOptions, + opts?: CommandOptions ) { - if (Array.isArray(key) && Array.isArray(id)) { - if (key.length !== id.length) { - throw new Error(UNBALANCED_XREADGROUP_ERR); - } + if (Array.isArray(key) && Array.isArray(id) && key.length !== id.length) { + throw new Error(UNBALANCED_XREADGROUP_ERR); } const commands: unknown[] = []; @@ -52,14 +51,14 @@ export class XReadGroupCommand extends Command { if (typeof options?.blockMS === "number") { commands.push("BLOCK", options.blockMS); } - if (typeof options?.NOACK === "boolean" && options?.NOACK) { + if (typeof options?.NOACK === "boolean" && options.NOACK) { commands.push("NOACK"); } commands.push( "STREAMS", ...(Array.isArray(key) ? [...key] : [key]), - ...(Array.isArray(id) ? [...id] : [id]), + ...(Array.isArray(id) ? [...id] : [id]) ); super(["XREADGROUP", "GROUP", group, consumer, ...commands], opts); diff --git a/pkg/commands/xrevrange.ts b/pkg/commands/xrevrange.ts index c25d0aef..0d38b439 100644 --- a/pkg/commands/xrevrange.ts +++ b/pkg/commands/xrevrange.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export class XRevRangeCommand< TData extends Record>, > extends Command { constructor( [key, end, start, count]: [key: string, end: string, start: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["XREVRANGE", key, end, start]; if (typeof count === "number") { @@ -19,7 +20,7 @@ export class XRevRangeCommand< } function deserialize>>( - result: [string, string[]][], + result: [string, string[]][] ): TData { const obj: Record> = {}; for (const e of result) { @@ -31,8 +32,8 @@ function deserialize>>( obj[streamId] = {}; } while (entries.length >= 2) { - const field = (entries as string[]).shift()! as string; - const value = (entries as string[]).shift()! as string; + const field = (entries as string[]).shift()!; + const value = (entries as string[]).shift()!; try { obj[streamId][field] = JSON.parse(value); diff --git a/pkg/commands/xtrim.test.ts b/pkg/commands/xtrim.test.ts index 22fbd5ea..2b6be6f3 100644 --- a/pkg/commands/xtrim.test.ts +++ b/pkg/commands/xtrim.test.ts @@ -21,13 +21,13 @@ describe("XLEN", () => { } await Promise.all(promises); await new XTrimCommand([key, { strategy: "MAXLEN", threshold: 30, exactness: "~" }]).exec( - client, + client ); const len = await new XLenCommand([key]).exec(client); expect(len).toBeGreaterThanOrEqual(29); expect(len).toBeLessThanOrEqual(31); }, - { timeout: 1000 * 60 }, + { timeout: 1000 * 60 } ); test("should trim with zero threshold and remove everything", async () => { @@ -38,7 +38,7 @@ describe("XLEN", () => { } await Promise.all(promises); await new XTrimCommand([key, { strategy: "MAXLEN", threshold: 0, exactness: "=" }]).exec( - client, + client ); const len = await new XLenCommand([key]).exec(client); expect(len).toBeLessThanOrEqual(1); @@ -55,11 +55,11 @@ describe("XLEN", () => { } const midRangeId = `${baseTimestamp}-10`; await new XTrimCommand([key, { strategy: "MINID", threshold: midRangeId, limit: 2 }]).exec( - client, + client ); const len = await new XLenCommand([key]).exec(client); expect(len).toBeLessThanOrEqual(20); }, - { timeout: 20000 }, + { timeout: 20_000 } ); }); diff --git a/pkg/commands/xtrim.ts b/pkg/commands/xtrim.ts index 13155a82..f1b4ea3d 100644 --- a/pkg/commands/xtrim.ts +++ b/pkg/commands/xtrim.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/xtrim @@ -14,7 +15,7 @@ type XTrimOptions = { export class XTrimCommand extends Command { constructor( [key, options]: [key: string, options: XTrimOptions], - opts?: CommandOptions, + opts?: CommandOptions ) { const { limit, strategy, threshold, exactness = "~" } = options; diff --git a/pkg/commands/zadd.test.ts b/pkg/commands/zadd.test.ts index 5f0bdaad..8cede130 100644 --- a/pkg/commands/zadd.test.ts +++ b/pkg/commands/zadd.test.ts @@ -25,7 +25,7 @@ describe("command format", () => { describe("with nx", () => { test("build the correct command", () => { expect( - new ZAddCommand(["key", { nx: true }, { score: 0, member: "member" }]).command, + new ZAddCommand(["key", { nx: true }, { score: 0, member: "member" }]).command ).toEqual(["zadd", "key", "nx", 0, "member"]); }); }); @@ -33,7 +33,7 @@ describe("command format", () => { describe("with xx", () => { test("build the correct command", () => { expect( - new ZAddCommand(["key", { xx: true }, { score: 0, member: "member" }]).command, + new ZAddCommand(["key", { xx: true }, { score: 0, member: "member" }]).command ).toEqual(["zadd", "key", "xx", 0, "member"]); }); }); @@ -41,7 +41,7 @@ describe("command format", () => { describe("with ch", () => { test("build the correct command", () => { expect( - new ZAddCommand(["key", { ch: true }, { score: 0, member: "member" }]).command, + new ZAddCommand(["key", { ch: true }, { score: 0, member: "member" }]).command ).toEqual(["zadd", "key", "ch", 0, "member"]); }); }); @@ -49,7 +49,7 @@ describe("command format", () => { describe("with incr", () => { test("build the correct command", () => { expect( - new ZAddCommand(["key", { incr: true }, { score: 0, member: "member" }]).command, + new ZAddCommand(["key", { incr: true }, { score: 0, member: "member" }]).command ).toEqual(["zadd", "key", "incr", 0, "member"]); }); }); @@ -57,7 +57,7 @@ describe("command format", () => { describe("with nx and ch", () => { test("build the correct command", () => { expect( - new ZAddCommand(["key", { nx: true, ch: true }, { score: 0, member: "member" }]).command, + new ZAddCommand(["key", { nx: true, ch: true }, { score: 0, member: "member" }]).command ).toEqual(["zadd", "key", "nx", "ch", 0, "member"]); }); }); @@ -66,7 +66,7 @@ describe("command format", () => { test("build the correct command", () => { expect( new ZAddCommand(["key", { nx: true, ch: true, incr: true }, { score: 0, member: "member" }]) - .command, + .command ).toEqual(["zadd", "key", "nx", "ch", "incr", 0, "member"]); }); }); @@ -79,7 +79,7 @@ describe("command format", () => { { nx: true }, { score: 0, member: "member" }, { score: 1, member: "member1" }, - ]).command, + ]).command ).toEqual(["zadd", "key", "nx", 0, "member", 1, "member1"]); }); }); @@ -104,7 +104,7 @@ describe("xx", () => { await new ZAddCommand([key, { score, member }]).exec(client); const newScore = score + 1; const res = await new ZAddCommand([key, { xx: true }, { score: newScore, member }]).exec( - client, + client ); expect(res).toEqual(0); @@ -120,7 +120,7 @@ describe("xx", () => { await new ZAddCommand([key, { score, member }]).exec(client); const newScore = score + 1; const res = await new ZAddCommand([key, { xx: true }, { score: newScore, member }]).exec( - client, + client ); expect(res).toEqual(0); }); @@ -136,7 +136,7 @@ describe("nx", () => { await new ZAddCommand([key, { score, member }]).exec(client); const newScore = score + 1; const res = await new ZAddCommand([key, { nx: true }, { score: newScore, member }]).exec( - client, + client ); expect(res).toEqual(0); @@ -163,7 +163,7 @@ describe("ch", () => { await new ZAddCommand([key, { score, member }]).exec(client); const newScore = score + 1; const res = await new ZAddCommand([key, { ch: true }, { score: newScore, member }]).exec( - client, + client ); expect(res).toEqual(1); }); diff --git a/pkg/commands/zadd.ts b/pkg/commands/zadd.ts index 22f8139b..5c747468 100644 --- a/pkg/commands/zadd.ts +++ b/pkg/commands/zadd.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; type NXAndXXOptions = | { nx: true; xx?: never } @@ -20,7 +21,7 @@ export type ScoreMember = { score: number; member: TData }; export class ZAddCommand extends Command { constructor( [key, arg1, ...arg2]: [string, Arg2, ...ScoreMember[]], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["zadd", key]; if ("nx" in arg1 && arg1.nx) { diff --git a/pkg/commands/zcard.ts b/pkg/commands/zcard.ts index ebf63e29..37874a01 100644 --- a/pkg/commands/zcard.ts +++ b/pkg/commands/zcard.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zcard diff --git a/pkg/commands/zcount.ts b/pkg/commands/zcount.ts index 78aff358..9bf4410f 100644 --- a/pkg/commands/zcount.ts +++ b/pkg/commands/zcount.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zcount */ export class ZCountCommand extends Command { constructor( cmd: [key: string, min: number | string, max: number | string], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zcount", ...cmd], opts); } diff --git a/pkg/commands/zdiffstore.ts b/pkg/commands/zdiffstore.ts index 036c6483..937c553d 100644 --- a/pkg/commands/zdiffstore.ts +++ b/pkg/commands/zdiffstore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zdiffstore @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class ZDiffStoreCommand extends Command { constructor( cmd: [destination: string, numkeys: number, ...keys: string[]], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zdiffstore", ...cmd], opts); } diff --git a/pkg/commands/zincrby.ts b/pkg/commands/zincrby.ts index 594b633d..bfe8bfe0 100644 --- a/pkg/commands/zincrby.ts +++ b/pkg/commands/zincrby.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zincrby */ export class ZIncrByCommand extends Command { constructor( cmd: [key: string, increment: number, member: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zincrby", ...cmd], opts); } diff --git a/pkg/commands/zinterstore.test.ts b/pkg/commands/zinterstore.test.ts index 806f472e..45f23bab 100644 --- a/pkg/commands/zinterstore.test.ts +++ b/pkg/commands/zinterstore.test.ts @@ -53,7 +53,7 @@ describe("command format", () => { { weights: [2, 3], }, - ]).command, + ]).command ).toEqual(["zinterstore", "destination", 2, "key1", "key2", "weights", 2, 3]); }); describe("with aggregate", () => { @@ -67,7 +67,7 @@ describe("command format", () => { { aggregate: "sum", }, - ]).command, + ]).command ).toEqual(["zinterstore", "destination", 1, "key", "aggregate", "sum"]); }); }); @@ -81,7 +81,7 @@ describe("command format", () => { { aggregate: "min", }, - ]).command, + ]).command ).toEqual(["zinterstore", "destination", 1, "key", "aggregate", "min"]); }); }); @@ -95,7 +95,7 @@ describe("command format", () => { { aggregate: "max", }, - ]).command, + ]).command ).toEqual(["zinterstore", "destination", 1, "key", "aggregate", "max"]); }); }); @@ -111,7 +111,7 @@ describe("command format", () => { weights: [4, 2], aggregate: "max", }, - ]).command, + ]).command ).toEqual([ "zinterstore", "destination", diff --git a/pkg/commands/zinterstore.ts b/pkg/commands/zinterstore.ts index 33835675..c52785ed 100644 --- a/pkg/commands/zinterstore.ts +++ b/pkg/commands/zinterstore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ZInterStoreCommandOptions = { aggregate?: "sum" | "min" | "max"; @@ -14,11 +15,11 @@ export type ZInterStoreCommandOptions = { export class ZInterStoreCommand extends Command { constructor( cmd: [destination: string, numKeys: 1, key: string, opts?: ZInterStoreCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( cmd: [destination: string, numKeys: number, keys: string[], opts?: ZInterStoreCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( [destination, numKeys, keyOrKeys, opts]: [ @@ -27,7 +28,7 @@ export class ZInterStoreCommand extends Command { keyOrKeys: string | string[], opts?: ZInterStoreCommandOptions, ], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ) { const command: unknown[] = ["zinterstore", destination, numKeys]; if (Array.isArray(keyOrKeys)) { diff --git a/pkg/commands/zlexcount.ts b/pkg/commands/zlexcount.ts index 45dcef53..d5718b7f 100644 --- a/pkg/commands/zlexcount.ts +++ b/pkg/commands/zlexcount.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zlexcount */ diff --git a/pkg/commands/zmscore.ts b/pkg/commands/zmscore.ts index 9d31f6ec..a7ca1718 100644 --- a/pkg/commands/zmscore.ts +++ b/pkg/commands/zmscore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zmscore @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class ZMScoreCommand extends Command { constructor( cmd: [key: string, members: TData[]], - opts?: CommandOptions, + opts?: CommandOptions ) { const [key, members] = cmd; super(["zmscore", key, ...members], opts); diff --git a/pkg/commands/zpopmax.test.ts b/pkg/commands/zpopmax.test.ts index c17564ec..3505fe4c 100644 --- a/pkg/commands/zpopmax.test.ts +++ b/pkg/commands/zpopmax.test.ts @@ -23,8 +23,8 @@ describe("without options", () => { ]).exec(client); const res = await new ZPopMaxCommand([key]).exec(client); expect(res.length).toBe(2); - expect(res![0]).toEqual(member2); - expect(res![1]).toEqual(score2); + expect(res[0]).toEqual(member2); + expect(res[1]).toEqual(score2); }); }); diff --git a/pkg/commands/zpopmax.ts b/pkg/commands/zpopmax.ts index 4b31d711..211c1a24 100644 --- a/pkg/commands/zpopmax.ts +++ b/pkg/commands/zpopmax.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zpopmax */ export class ZPopMaxCommand extends Command { constructor( [key, count]: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["zpopmax", key]; if (typeof count === "number") { diff --git a/pkg/commands/zpopmin.ts b/pkg/commands/zpopmin.ts index fdcd15b5..7f5d2332 100644 --- a/pkg/commands/zpopmin.ts +++ b/pkg/commands/zpopmin.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zpopmin */ export class ZPopMinCommand extends Command { constructor( [key, count]: [key: string, count?: number], - opts?: CommandOptions, + opts?: CommandOptions ) { const command: unknown[] = ["zpopmin", key]; if (typeof count === "number") { diff --git a/pkg/commands/zrange.test.ts b/pkg/commands/zrange.test.ts index 386bfd97..2a33f833 100644 --- a/pkg/commands/zrange.test.ts +++ b/pkg/commands/zrange.test.ts @@ -25,7 +25,7 @@ describe("without options", () => { const res = await new ZRangeCommand([key, 1, 3]).exec(client); expect(res.length).toBe(1); - expect(res![0]).toEqual(member2); + expect(res[0]).toEqual(member2); }); }); @@ -46,8 +46,8 @@ describe("withscores", () => { const res = await new ZRangeCommand([key, 1, 3, { withScores: true }]).exec(client); expect(res.length).toBe(2); - expect(res![0]).toEqual(member2); - expect(res![1]).toEqual(score2); + expect(res[0]).toEqual(member2); + expect(res[1]).toEqual(score2); }); }); @@ -80,8 +80,8 @@ describe("byscore", () => { ]).exec(client); expect(res.length).toBe(2); - expect(res![0]).toEqual(member1); - expect(res![1]).toEqual(member2); + expect(res[0]).toEqual(member1); + expect(res[1]).toEqual(member2); const res2 = await new ZRangeCommand([ key, @@ -92,9 +92,9 @@ describe("byscore", () => { }, ]).exec(client); expect(res2.length).toBe(3); - expect(res2![0]).toEqual(member1); - expect(res2![1]).toEqual(member2); - expect(res2![2]).toEqual(member3); + expect(res2[0]).toEqual(member1); + expect(res2[1]).toEqual(member2); + expect(res2[2]).toEqual(member3); const res3 = await new ZRangeCommand([ key, @@ -122,8 +122,8 @@ describe("bylex", () => { // everything in between a and c, excluding "a" and including "c" const res = await new ZRangeCommand([key, "(a", "[c", { byLex: true }]).exec(client); expect(res.length).toBe(2); - expect(res![0]).toBe("b"); - expect(res![1]).toBe("c"); + expect(res[0]).toBe("b"); + expect(res[1]).toBe("c"); //everything after "a", excluding a const res2 = await new ZRangeCommand([key, "(a", "+", { byLex: true }]).exec(client); @@ -139,8 +139,8 @@ describe("bylex", () => { }, ]).exec(client); expect(res3.length).toBe(2); - expect(res3![0]).toBe("a"); - expect(res3![1]).toBe("b"); + expect(res3[0]).toBe("a"); + expect(res3[1]).toBe("b"); }); }); @@ -161,8 +161,8 @@ describe("rev", () => { const res = await new ZRangeCommand([key, 0, 7, { rev: true }]).exec(client); expect(res.length).toBe(2); - expect(res![0]).toEqual(member2); - expect(res![1]).toEqual(member1); + expect(res[0]).toEqual(member2); + expect(res[1]).toEqual(member1); }); }); diff --git a/pkg/commands/zrange.ts b/pkg/commands/zrange.ts index 744a0f84..c764f7bd 100644 --- a/pkg/commands/zrange.ts +++ b/pkg/commands/zrange.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ZRangeCommandOptions = { withScores?: boolean; @@ -15,7 +16,7 @@ export type ZRangeCommandOptions = { export class ZRangeCommand extends Command { constructor( cmd: [key: string, min: number, max: number, opts?: ZRangeCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( cmd: [ @@ -24,7 +25,7 @@ export class ZRangeCommand extends Command, + cmdOpts?: CommandOptions ); constructor( cmd: [ @@ -33,7 +34,7 @@ export class ZRangeCommand extends Command, + cmdOpts?: CommandOptions ); constructor( [key, min, max, opts]: [ @@ -42,7 +43,7 @@ export class ZRangeCommand extends Command, + cmdOpts?: CommandOptions ) { const command: unknown[] = ["zrange", key, min, max]; @@ -56,8 +57,8 @@ export class ZRangeCommand extends Command extends Command { constructor( cmd: [key: string, member: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zrank", ...cmd], opts); } diff --git a/pkg/commands/zrem.test.ts b/pkg/commands/zrem.test.ts index 1795cd3f..f0acd9f7 100644 --- a/pkg/commands/zrem.test.ts +++ b/pkg/commands/zrem.test.ts @@ -14,7 +14,7 @@ test("returns the number of removed members", async () => { const member1 = randomID(); const member2 = randomID(); await new ZAddCommand([key, { score: 1, member: member1 }, { score: 2, member: member2 }]).exec( - client, + client ); const res = await new ZRemCommand([key, member1, member2]).exec(client); expect(res).toEqual(2); diff --git a/pkg/commands/zrem.ts b/pkg/commands/zrem.ts index 1c20907f..be1ce56d 100644 --- a/pkg/commands/zrem.ts +++ b/pkg/commands/zrem.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zrem */ diff --git a/pkg/commands/zremrangebylex.ts b/pkg/commands/zremrangebylex.ts index 81b04679..e98817d7 100644 --- a/pkg/commands/zremrangebylex.ts +++ b/pkg/commands/zremrangebylex.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zremrangebylex */ diff --git a/pkg/commands/zremrangebyrank.ts b/pkg/commands/zremrangebyrank.ts index 3e744e6b..da7e96ed 100644 --- a/pkg/commands/zremrangebyrank.ts +++ b/pkg/commands/zremrangebyrank.ts @@ -1,11 +1,12 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zremrangebyrank */ export class ZRemRangeByRankCommand extends Command { constructor( cmd: [key: string, start: number, stop: number], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zremrangebyrank", ...cmd], opts); } diff --git a/pkg/commands/zremrangebyscore.ts b/pkg/commands/zremrangebyscore.ts index 2f7ef697..b16e083d 100644 --- a/pkg/commands/zremrangebyscore.ts +++ b/pkg/commands/zremrangebyscore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zremrangebyscore */ diff --git a/pkg/commands/zrevrank.ts b/pkg/commands/zrevrank.ts index 472becb3..eb5ce4c9 100644 --- a/pkg/commands/zrevrank.ts +++ b/pkg/commands/zrevrank.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zrevrank */ @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class ZRevRankCommand extends Command { constructor( cmd: [key: string, member: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zrevrank", ...cmd], opts); } diff --git a/pkg/commands/zscan.test.ts b/pkg/commands/zscan.test.ts index a7a6e1d4..b2e0bf62 100644 --- a/pkg/commands/zscan.test.ts +++ b/pkg/commands/zscan.test.ts @@ -17,7 +17,7 @@ describe("without options", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -30,7 +30,7 @@ describe("with match", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("string"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); @@ -43,6 +43,6 @@ test("with count", () => { expect(res.length).toBe(2); expect(typeof res[0]).toBe("number"); - expect(res![1].length > 0).toBe(true); + expect(res[1].length > 0).toBe(true); }); }); diff --git a/pkg/commands/zscan.ts b/pkg/commands/zscan.ts index 75c7b30a..9bf92190 100644 --- a/pkg/commands/zscan.ts +++ b/pkg/commands/zscan.ts @@ -1,6 +1,7 @@ import { deserializeScanResponse } from "../util"; -import { Command, CommandOptions } from "./command"; -import { ScanCommandOptions } from "./scan"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; +import type { ScanCommandOptions } from "./scan"; /** * @see https://redis.io/commands/zscan @@ -11,7 +12,7 @@ export class ZScanCommand extends Command< > { constructor( [key, cursor, opts]: [key: string, cursor: string | number, opts?: ScanCommandOptions], - cmdOpts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]>, + cmdOpts?: CommandOptions<[string, (string | number)[]], [string, (string | number)[]]> ) { const command: (number | string)[] = ["zscan", key, cursor]; if (opts?.match) { diff --git a/pkg/commands/zscore.ts b/pkg/commands/zscore.ts index 1226e01b..61a7835c 100644 --- a/pkg/commands/zscore.ts +++ b/pkg/commands/zscore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; /** * @see https://redis.io/commands/zscore @@ -6,7 +7,7 @@ import { Command, CommandOptions } from "./command"; export class ZScoreCommand extends Command { constructor( cmd: [key: string, member: TData], - opts?: CommandOptions, + opts?: CommandOptions ) { super(["zscore", ...cmd], opts); } diff --git a/pkg/commands/zunion.test.ts b/pkg/commands/zunion.test.ts index d70193bc..107ff2ae 100644 --- a/pkg/commands/zunion.test.ts +++ b/pkg/commands/zunion.test.ts @@ -46,7 +46,7 @@ describe("command format", () => { { weights: [2, 3], }, - ]).command, + ]).command ).toEqual(["zunion", 2, "key1", "key2", "weights", 2, 3]); }); describe("with aggregate", () => { @@ -59,7 +59,7 @@ describe("command format", () => { { aggregate: "sum", }, - ]).command, + ]).command ).toEqual(["zunion", 1, "key", "aggregate", "sum"]); }); }); @@ -72,7 +72,7 @@ describe("command format", () => { { aggregate: "min", }, - ]).command, + ]).command ).toEqual(["zunion", 1, "key", "aggregate", "min"]); }); }); @@ -85,7 +85,7 @@ describe("command format", () => { { aggregate: "max", }, - ]).command, + ]).command ).toEqual(["zunion", 1, "key", "aggregate", "max"]); }); }); @@ -100,7 +100,7 @@ describe("command format", () => { weights: [4, 2], aggregate: "max", }, - ]).command, + ]).command ).toEqual(["zunion", 2, "key1", "key2", "weights", 4, 2, "aggregate", "max"]); }); }); @@ -122,7 +122,7 @@ describe("without options", () => { const res = await new ZUnionCommand([2, [key1, key2]]).exec(client); expect(res.length).toBe(2); - expect(res?.sort()).toEqual([member1, member2].sort()); + expect(res.sort()).toEqual([member1, member2].sort()); }); }); diff --git a/pkg/commands/zunion.ts b/pkg/commands/zunion.ts index 81f7814d..046cdf68 100644 --- a/pkg/commands/zunion.ts +++ b/pkg/commands/zunion.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ZUnionCommandOptions = { withScores?: boolean; @@ -15,11 +16,11 @@ export type ZUnionCommandOptions = { export class ZUnionCommand extends Command { constructor( cmd: [numKeys: 1, key: string, opts?: ZUnionCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( cmd: [numKeys: number, keys: string[], opts?: ZUnionCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( [numKeys, keyOrKeys, opts]: [ @@ -27,7 +28,7 @@ export class ZUnionCommand extends Command, + cmdOpts?: CommandOptions ) { const command: unknown[] = ["zunion", numKeys]; if (Array.isArray(keyOrKeys)) { @@ -44,7 +45,7 @@ export class ZUnionCommand extends Command { { weights: [2, 3], }, - ]).command, + ]).command ).toEqual(["zunionstore", "destination", 2, "key1", "key2", "weights", 2, 3]); }); describe("with aggregate", () => { @@ -68,7 +68,7 @@ describe("command format", () => { { aggregate: "sum", }, - ]).command, + ]).command ).toEqual(["zunionstore", "destination", 1, "key", "aggregate", "sum"]); }); }); @@ -82,7 +82,7 @@ describe("command format", () => { { aggregate: "min", }, - ]).command, + ]).command ).toEqual(["zunionstore", "destination", 1, "key", "aggregate", "min"]); }); }); @@ -96,7 +96,7 @@ describe("command format", () => { { aggregate: "max", }, - ]).command, + ]).command ).toEqual(["zunionstore", "destination", 1, "key", "aggregate", "max"]); }); }); @@ -112,7 +112,7 @@ describe("command format", () => { weights: [4, 2], aggregate: "max", }, - ]).command, + ]).command ).toEqual([ "zunionstore", "destination", diff --git a/pkg/commands/zunionstore.ts b/pkg/commands/zunionstore.ts index e7345d0a..bd14c54a 100644 --- a/pkg/commands/zunionstore.ts +++ b/pkg/commands/zunionstore.ts @@ -1,4 +1,5 @@ -import { Command, CommandOptions } from "./command"; +import type { CommandOptions } from "./command"; +import { Command } from "./command"; export type ZUnionStoreCommandOptions = { aggregate?: "sum" | "min" | "max"; @@ -14,11 +15,11 @@ export type ZUnionStoreCommandOptions = { export class ZUnionStoreCommand extends Command { constructor( cmd: [destination: string, numKeys: 1, key: string, opts?: ZUnionStoreCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( cmd: [destination: string, numKeys: number, keys: string[], opts?: ZUnionStoreCommandOptions], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ); constructor( [destination, numKeys, keyOrKeys, opts]: [ @@ -27,7 +28,7 @@ export class ZUnionStoreCommand extends Command { keyOrKeys: string | string[], opts?: ZUnionStoreCommandOptions, ], - cmdOpts?: CommandOptions, + cmdOpts?: CommandOptions ) { const command: unknown[] = ["zunionstore", destination, numKeys]; if (Array.isArray(keyOrKeys)) { diff --git a/pkg/error.ts b/pkg/error.ts index a766e302..7e27c36d 100644 --- a/pkg/error.ts +++ b/pkg/error.ts @@ -11,7 +11,7 @@ export class UpstashError extends Error { export class UrlError extends Error { constructor(url: string) { super( - `Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". `, + `Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". ` ); this.name = "UrlError"; } diff --git a/pkg/http.test.ts b/pkg/http.test.ts deleted file mode 100644 index 56d4816f..00000000 --- a/pkg/http.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { HttpClient } from "./http"; - -import { UrlError } from "./error"; -import { newHttpClient } from "./test-utils"; -test("remove trailing slash from urls", () => { - const client = new HttpClient({ baseUrl: "https://example.com/" }); - - expect(client.baseUrl).toEqual("https://example.com"); -}); - -describe(new URL("", import.meta.url).pathname, () => { - describe("when the request is invalid", () => { - test("throws", async () => { - const client = newHttpClient(); - let hasThrown = false; - await client.request({ body: ["get", "1", "2"] }).catch(() => { - hasThrown = true; - }); - expect(hasThrown).toBeTrue(); - }); - }); - - describe("whithout authorization", () => { - test("throws", async () => { - const client = newHttpClient(); - client.headers = {}; - let hasThrown = false; - await client.request({ body: ["get", "1", "2"] }).catch(() => { - hasThrown = true; - }); - expect(hasThrown).toBeTrue(); - }); - }); -}); - -describe("Abort", () => { - test("should abort the request", async () => { - const controller = new AbortController(); - const signal = controller.signal; - - const client = newHttpClient(); - client.options.signal = signal; - const body = client.request({ - body: ["set", "name", "hezarfen"], - }); - controller.abort("Abort works!"); - - expect((await body).result).toEqual("Abort works!"); - }); -}); - -describe("should reject invalid urls", () => { - test("should reject when https is missing", () => { - try { - new HttpClient({ baseUrl: "eu1-flying-whale-434343.upstash.io" }); - } catch (error) { - expect(error instanceof UrlError).toBeTrue(); - return; - } - throw new Error("Test error. Should have raised when initializing client"); - }); - - test("should allow when http is used", () => { - new HttpClient({ - baseUrl: "http://eu1-flying-whale-434343.upstash.io", - }); - }); - - test("should allow when https is used", () => { - new HttpClient({ - baseUrl: "https://eu1-flying-whale-434343.upstash.io", - }); - }); -}); diff --git a/pkg/http.ts b/pkg/http.ts index 10df82f4..6bb54433 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -1,5 +1,5 @@ import { UpstashError, UrlError } from "./error"; -import { Telemetry } from "./types"; +import type { Telemetry } from "./types"; type CacheSetting = | "default" @@ -18,9 +18,9 @@ export type UpstashRequest = { }; export type UpstashResponse = { result?: TResult; error?: string }; -export interface Requester { +export type Requester = { request: (req: UpstashRequest) => Promise>; -} +}; type ResultError = { result?: string | number | null | (string | number | null)[]; @@ -135,7 +135,7 @@ export class HttpClient implements Requester { * - `[^\s]*` matches anything except white space * - `$` asserts the position at the end of the string. */ - const urlRegex = /^https?:\/\/[^\s/$.?#].[^\s]*$/; + const urlRegex = /^https?:\/\/[^\s#$./?].\S*$/; if (!urlRegex.test(this.baseUrl)) { throw new UrlError(this.baseUrl); } @@ -150,36 +150,19 @@ export class HttpClient implements Requester { this.headers["Upstash-Encoding"] = "base64"; } - if (typeof config?.retry === "boolean" && config?.retry === false) { - this.retry = { - attempts: 1, - backoff: () => 0, - }; - } else { - this.retry = { - attempts: config?.retry?.retries ?? 5, - backoff: config?.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50), - }; - } + this.retry = + typeof config.retry === "boolean" && !config.retry + ? { + attempts: 1, + backoff: () => 0, + } + : { + attempts: config.retry?.retries ?? 5, + backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50), + }; } public mergeTelemetry(telemetry: Telemetry): void { - function merge( - obj: Record, - key: string, - value?: string, - ): Record { - if (!value) { - return obj; - } - if (obj[key]) { - obj[key] = [obj[key], value].join(","); - } else { - obj[key] = value; - } - return obj; - } - this.headers = merge(this.headers, "Upstash-Telemetry-Runtime", telemetry.runtime); this.headers = merge(this.headers, "Upstash-Telemetry-Platform", telemetry.platform); this.headers = merge(this.headers, "Upstash-Telemetry-Sdk", telemetry.sdk); @@ -193,13 +176,13 @@ export class HttpClient implements Requester { headers: this.headers, body: JSON.stringify(req.body), keepalive: this.options.keepAlive, - agent: this.options?.agent, + agent: this.options.agent, signal: this.options.signal, /** * Fastly specific */ - backend: this.options?.backend, + backend: this.options.backend, }; let res: Response | null = null; @@ -208,7 +191,7 @@ export class HttpClient implements Requester { try { res = await fetch([this.baseUrl, ...(req.path ?? [])].join("/"), requestOptions); break; - } catch (err) { + } catch (error_) { if (this.options.signal?.aborted) { const myBlob = new Blob([ JSON.stringify({ result: this.options.signal.reason ?? "Aborted" }), @@ -220,7 +203,7 @@ export class HttpClient implements Requester { res = new Response(myBlob, myOptions); break; } - error = err as Error; + error = error_ as Error; await new Promise((r) => setTimeout(r, this.retry.backoff(i))); } } @@ -233,7 +216,7 @@ export class HttpClient implements Requester { throw new UpstashError(`${body.error}, command was: ${JSON.stringify(req.body)}`); } - if (this.options?.responseEncoding === "base64") { + if (this.options.responseEncoding === "base64") { if (Array.isArray(body)) { return body.map(({ result, error }) => ({ result: decode(result), @@ -257,6 +240,7 @@ function base64decode(b64: string): string { const size = binString.length; const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { + // eslint-disable-next-line unicorn/prefer-code-point bytes[i] = binString.charCodeAt(i); } dec = new TextDecoder().decode(bytes); @@ -274,17 +258,23 @@ function base64decode(b64: string): string { function decode(raw: ResultError["result"]): ResultError["result"] { let result: any = undefined; switch (typeof raw) { - case "undefined": + case "undefined": { return raw; + } case "number": { result = raw; break; } case "object": { + // eslint-disable-next-line unicorn/prefer-ternary if (Array.isArray(raw)) { result = raw.map((v) => - typeof v === "string" ? base64decode(v) : Array.isArray(v) ? v.map(decode) : v, + typeof v === "string" + ? base64decode(v) + : Array.isArray(v) + ? v.map((element) => decode(element)) + : v ); } else { // If it's not an array it must be null @@ -299,9 +289,18 @@ function decode(raw: ResultError["result"]): ResultError["result"] { break; } - default: + default: { break; + } } return result; } + +function merge(obj: Record, key: string, value?: string): Record { + if (!value) { + return obj; + } + obj[key] = obj[key] ? [obj[key], value].join(",") : value; + return obj; +} diff --git a/pkg/pipeline.test.ts b/pkg/pipeline.test.ts index c3cee85a..4b91de20 100644 --- a/pkg/pipeline.test.ts +++ b/pkg/pipeline.test.ts @@ -140,7 +140,7 @@ describe("use all the things", () => { .evalsha(scriptHash, [], ["Hello"]) .exists(newKey()) .expire(newKey(), 5) - .expireat(newKey(), Math.floor(new Date().getTime() / 1000) + 60) + .expireat(newKey(), Math.floor(Date.now() / 1000) + 60) .flushall() .flushdb() .get(newKey()) @@ -186,7 +186,7 @@ describe("use all the things", () => { .msetnx({ key3: "value", key4: "value" }) .persist(newKey()) .pexpire(newKey(), 1000) - .pexpireat(newKey(), new Date().getTime() + 1000) + .pexpireat(newKey(), Date.now() + 1000) .ping() .psetex(newKey(), 1, "value") .pttl(newKey()) diff --git a/pkg/pipeline.ts b/pkg/pipeline.ts index 7d174e67..3bd8a4fc 100644 --- a/pkg/pipeline.ts +++ b/pkg/pipeline.ts @@ -1,5 +1,11 @@ -import { Command, CommandOptions } from "./commands/command"; +import type { Command, CommandOptions } from "./commands/command"; import { HRandFieldCommand } from "./commands/hrandfield"; +import type { + ScoreMember, + SetCommandOptions, + ZAddCommandOptions, + ZRangeCommandOptions, +} from "./commands/mod"; import { AppendCommand, BitCountCommand, @@ -120,13 +126,11 @@ import { SUnionCommand, SUnionStoreCommand, ScanCommand, - ScoreMember, ScriptExistsCommand, ScriptFlushCommand, ScriptLoadCommand, SetBitCommand, SetCommand, - SetCommandOptions, SetExCommand, SetNxCommand, SetRangeCommand, @@ -151,7 +155,6 @@ import { XRevRangeCommand, XTrimCommand, ZAddCommand, - ZAddCommandOptions, ZCardCommand, ZCountCommand, ZIncrByCommand, @@ -160,7 +163,6 @@ import { ZPopMaxCommand, ZPopMinCommand, ZRangeCommand, - ZRangeCommandOptions, ZRankCommand, ZRemCommand, ZRemRangeByLexCommand, @@ -175,8 +177,8 @@ import { import { ZDiffStoreCommand } from "./commands/zdiffstore"; import { ZMScoreCommand } from "./commands/zmscore"; import { UpstashError } from "./error"; -import { Requester, UpstashResponse } from "./http"; -import { CommandArgs } from "./types"; +import type { Requester, UpstashResponse } from "./http"; +import type { CommandArgs } from "./types"; // Given a tuple of commands, returns a tuple of the response data of each command type InferResponseData = { @@ -243,18 +245,17 @@ export class Pipeline[] = []> { this.exec = async < TCommandResults extends unknown[] = [] extends TCommands ? unknown[] - : InferResponseData + : InferResponseData, >(): Promise => { const start = performance.now(); const result = await originalExec(); const end = performance.now(); const loggerResult = (end - start).toFixed(2); + // eslint-disable-next-line no-console console.log( - `Latency for \x1b[38;2;19;185;39m${ - this.multiExec - ? ["MULTI-EXEC"] - : ["PIPELINE"].toString().toUpperCase() - }\x1b[0m: \x1b[38;2;0;255;255m${loggerResult} ms\x1b[0m` + `Latency for \u001B[38;2;19;185;39m${ + this.multiExec ? ["MULTI-EXEC"] : ["PIPELINE"].toString().toUpperCase() + }\u001B[0m: \u001B[38;2;0;255;255m${loggerResult} ms\u001B[0m` ); return result as TCommandResults; }; @@ -276,7 +277,7 @@ export class Pipeline[] = []> { exec = async < TCommandResults extends unknown[] = [] extends TCommands ? unknown[] - : InferResponseData + : InferResponseData, >(): Promise => { if (this.commands.length === 0) { throw new Error("Pipeline is empty"); @@ -309,9 +310,7 @@ export class Pipeline[] = []> { * Pushes a command into the pipeline and returns a chainable instance of the * pipeline */ - private chain( - command: Command - ): Pipeline<[...TCommands, Command]> { + private chain(command: Command): Pipeline<[...TCommands, Command]> { this.commands.push(command); return this as any; // TS thinks we're returning Pipeline<[]> here, because we're not creating a new instance of the class, hence the cast } @@ -345,12 +344,7 @@ export class Pipeline[] = []> { * @see https://redis.io/commands/bitfield */ bitfield = (...args: CommandArgs) => - new BitFieldCommand( - args, - this.client, - this.commandOptions, - this.chain.bind(this) - ); + new BitFieldCommand(args, this.client, this.commandOptions, this.chain.bind(this)); /** * @see https://redis.io/commands/bitop @@ -362,9 +356,7 @@ export class Pipeline[] = []> { sourceKey: string, ...sourceKeys: string[] ): Pipeline<[...TCommands, BitOpCommand]>; - (op: "not", destinationKey: string, sourceKey: string): Pipeline< - [...TCommands, BitOpCommand] - >; + (op: "not", destinationKey: string, sourceKey: string): Pipeline<[...TCommands, BitOpCommand]>; } = ( op: "and" | "or" | "xor" | "not", destinationKey: string, @@ -372,10 +364,7 @@ export class Pipeline[] = []> { ...sourceKeys: string[] ) => this.chain( - new BitOpCommand( - [op as any, destinationKey, sourceKey, ...sourceKeys], - this.commandOptions - ) + new BitOpCommand([op as any, destinationKey, sourceKey, ...sourceKeys], this.commandOptions) ); /** @@ -502,9 +491,8 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/geosearchstore */ - geosearchstore = ( - ...args: CommandArgs> - ) => this.chain(new GeoSearchStoreCommand(args, this.commandOptions)); + geosearchstore = (...args: CommandArgs>) => + this.chain(new GeoSearchStoreCommand(args, this.commandOptions)); /** * @see https://redis.io/commands/get @@ -555,9 +543,8 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/hgetall */ - hgetall = >( - ...args: CommandArgs - ) => this.chain(new HGetAllCommand(args, this.commandOptions)); + hgetall = >(...args: CommandArgs) => + this.chain(new HGetAllCommand(args, this.commandOptions)); /** * @see https://redis.io/commands/hincrby @@ -586,14 +573,13 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/hmget */ - hmget = >( - ...args: CommandArgs - ) => this.chain(new HMGetCommand(args, this.commandOptions)); + hmget = >(...args: CommandArgs) => + this.chain(new HMGetCommand(args, this.commandOptions)); /** * @see https://redis.io/commands/hmset */ - hmset = (key: string, kv: { [field: string]: TData }) => + hmset = (key: string, kv: Record) => this.chain(new HMSetCommand([key, kv], this.commandOptions)); /** @@ -604,12 +590,7 @@ export class Pipeline[] = []> { count?: number, withValues?: boolean ) => - this.chain( - new HRandFieldCommand( - [key, count, withValues] as any, - this.commandOptions - ) - ); + this.chain(new HRandFieldCommand([key, count, withValues] as any, this.commandOptions)); /** * @see https://redis.io/commands/hscan @@ -620,16 +601,14 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/hset */ - hset = (key: string, kv: { [field: string]: TData }) => + hset = (key: string, kv: Record) => this.chain(new HSetCommand([key, kv], this.commandOptions)); /** * @see https://redis.io/commands/hsetnx */ hsetnx = (key: string, field: string, value: TData) => - this.chain( - new HSetNXCommand([key, field, value], this.commandOptions) - ); + this.chain(new HSetNXCommand([key, field, value], this.commandOptions)); /** * @see https://redis.io/commands/hstrlen @@ -676,18 +655,8 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/linsert */ - linsert = ( - key: string, - direction: "before" | "after", - pivot: TData, - value: TData - ) => - this.chain( - new LInsertCommand( - [key, direction, pivot, value], - this.commandOptions - ) - ); + linsert = (key: string, direction: "before" | "after", pivot: TData, value: TData) => + this.chain(new LInsertCommand([key, direction, pivot, value], this.commandOptions)); /** * @see https://redis.io/commands/llen @@ -723,17 +692,13 @@ export class Pipeline[] = []> { * @see https://redis.io/commands/lpush */ lpush = (key: string, ...elements: TData[]) => - this.chain( - new LPushCommand([key, ...elements], this.commandOptions) - ); + this.chain(new LPushCommand([key, ...elements], this.commandOptions)); /** * @see https://redis.io/commands/lpushx */ lpushx = (key: string, ...elements: TData[]) => - this.chain( - new LPushXCommand([key, ...elements], this.commandOptions) - ); + this.chain(new LPushXCommand([key, ...elements], this.commandOptions)); /** * @see https://redis.io/commands/lrange @@ -768,13 +733,13 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/mset */ - mset = (kv: { [key: string]: TData }) => + mset = (kv: Record) => this.chain(new MSetCommand([kv], this.commandOptions)); /** * @see https://redis.io/commands/msetnx */ - msetnx = (kv: { [key: string]: TData }) => + msetnx = (kv: Record) => this.chain(new MSetNXCommand([kv], this.commandOptions)); /** @@ -823,9 +788,7 @@ export class Pipeline[] = []> { * @see https://redis.io/commands/psetex */ psetex = (key: string, ttl: number, value: TData) => - this.chain( - new PSetEXCommand([key, ttl, value], this.commandOptions) - ); + this.chain(new PSetEXCommand([key, ttl, value], this.commandOptions)); /** * @see https://redis.io/commands/pttl @@ -972,28 +935,20 @@ export class Pipeline[] = []> { /** * @see https://redis.io/commands/smembers */ - smembers = ( - ...args: CommandArgs - ) => this.chain(new SMembersCommand(args, this.commandOptions)); + smembers = (...args: CommandArgs) => + this.chain(new SMembersCommand(args, this.commandOptions)); /** * @see https://redis.io/commands/smismember */ smismember = (key: string, members: TMembers) => - this.chain( - new SMIsMemberCommand([key, members], this.commandOptions) - ); + this.chain(new SMIsMemberCommand([key, members], this.commandOptions)); /** * @see https://redis.io/commands/smove */ smove = (source: string, destination: string, member: TData) => - this.chain( - new SMoveCommand( - [source, destination, member], - this.commandOptions - ) - ); + this.chain(new SMoveCommand([source, destination, member], this.commandOptions)); /** * @see https://redis.io/commands/spop @@ -1071,23 +1026,16 @@ export class Pipeline[] = []> { */ zadd = ( ...args: - | [ - key: string, - scoreMember: ScoreMember, - ...scoreMemberPairs: ScoreMember[] - ] + | [key: string, scoreMember: ScoreMember, ...scoreMemberPairs: ScoreMember[]] | [ key: string, opts: ZAddCommandOptions, - ...scoreMemberPairs: [ScoreMember, ...ScoreMember[]] + ...scoreMemberPairs: [ScoreMember, ...ScoreMember[]], ] ) => { if ("score" in args[1]) { return this.chain( - new ZAddCommand( - [args[0], args[1] as ScoreMember, ...(args.slice(2) as any)], - this.commandOptions - ) + new ZAddCommand([args[0], args[1], ...(args.slice(2) as any)], this.commandOptions) ); } @@ -1199,9 +1147,7 @@ export class Pipeline[] = []> { * @see https://redis.io/commands/zincrby */ zincrby = (key: string, increment: number, member: TData) => - this.chain( - new ZIncrByCommand([key, increment, member], this.commandOptions) - ); + this.chain(new ZIncrByCommand([key, increment, member], this.commandOptions)); /** * @see https://redis.io/commands/zinterstore @@ -1243,13 +1189,13 @@ export class Pipeline[] = []> { key: string, min: `(${string}` | `[${string}` | "-" | "+", max: `(${string}` | `[${string}` | "-" | "+", - opts: { byLex: true } & ZRangeCommandOptions + opts: { byLex: true } & ZRangeCommandOptions, ] | [ key: string, min: number | `(${number}` | "-inf" | "+inf", max: number | `(${number}` | "-inf" | "+inf", - opts: { byScore: true } & ZRangeCommandOptions + opts: { byScore: true } & ZRangeCommandOptions, ] ) => this.chain(new ZRangeCommand(args as any, this.commandOptions)); diff --git a/pkg/redis.test.ts b/pkg/redis.test.ts index 3562b6d8..a9ba867d 100644 --- a/pkg/redis.test.ts +++ b/pkg/redis.test.ts @@ -19,7 +19,7 @@ describe("when storing base64 data", () => { const res = await redis.get(key); expect(res).toEqual(value); }, - { timeout: 150000 }, + { timeout: 150_000 } ); // decode("OK") => 8 diff --git a/pkg/redis.ts b/pkg/redis.ts index 961c7554..103c31ee 100644 --- a/pkg/redis.ts +++ b/pkg/redis.ts @@ -1,11 +1,17 @@ import { createAutoPipelineProxy } from "../pkg/auto-pipeline"; +import type { + CommandOptions, + ScoreMember, + SetCommandOptions, + ZAddCommandOptions, + ZRangeCommandOptions, +} from "./commands/mod"; import { AppendCommand, BitCountCommand, BitFieldCommand, BitOpCommand, BitPosCommand, - CommandOptions, CopyCommand, DBSizeCommand, DecrByCommand, @@ -121,13 +127,11 @@ import { SUnionCommand, SUnionStoreCommand, ScanCommand, - ScoreMember, ScriptExistsCommand, ScriptFlushCommand, ScriptLoadCommand, SetBitCommand, SetCommand, - SetCommandOptions, SetExCommand, SetNxCommand, SetRangeCommand, @@ -152,7 +156,6 @@ import { XRevRangeCommand, XTrimCommand, ZAddCommand, - ZAddCommandOptions, ZCardCommand, ZCountCommand, ZIncrByCommand, @@ -161,7 +164,6 @@ import { ZPopMaxCommand, ZPopMinCommand, ZRangeCommand, - ZRangeCommandOptions, ZRankCommand, ZRemCommand, ZRemRangeByLexCommand, @@ -175,7 +177,7 @@ import { } from "./commands/mod"; import { ZDiffStoreCommand } from "./commands/zdiffstore"; import { ZMScoreCommand } from "./commands/zmscore"; -import { Requester, UpstashRequest, UpstashResponse } from "./http"; +import type { Requester, UpstashRequest, UpstashResponse } from "./http"; import { Pipeline } from "./pipeline"; import { Script } from "./script"; import type { CommandArgs, RedisOptions, Telemetry } from "./types"; @@ -352,14 +354,11 @@ export class Redis { use = ( middleware: ( r: UpstashRequest, - next: ( - req: UpstashRequest - ) => Promise> + next: (req: UpstashRequest) => Promise> ) => Promise> ) => { const makeRequest = this.client.request.bind(this.client); - this.client.request = (req: UpstashRequest) => - middleware(req, makeRequest) as any; + this.client.request = (req: UpstashRequest) => middleware(req, makeRequest) as any; }; /** @@ -370,6 +369,7 @@ export class Redis { return; } try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - The `Requester` interface does not know about this method but it will be there // as long as the user uses the standard HttpClient this.client.mergeTelemetry(telemetry); @@ -460,10 +460,9 @@ export class Redis { sourceKey: string, ...sourceKeys: string[] ) => - new BitOpCommand( - [op as any, destinationKey, sourceKey, ...sourceKeys], - this.opts - ).exec(this.client); + new BitOpCommand([op as any, destinationKey, sourceKey, ...sourceKeys], this.opts).exec( + this.client + ); /** * @see https://redis.io/commands/bitpos @@ -583,9 +582,8 @@ export class Redis { /** * @see https://redis.io/commands/geosearchstore */ - geosearchstore = ( - ...args: CommandArgs> - ) => new GeoSearchStoreCommand(args, this.opts).exec(this.client); + geosearchstore = (...args: CommandArgs>) => + new GeoSearchStoreCommand(args, this.opts).exec(this.client); /** * @see https://redis.io/commands/get @@ -637,9 +635,8 @@ export class Redis { /** * @see https://redis.io/commands/hgetall */ - hgetall = >( - ...args: CommandArgs - ) => new HGetAllCommand(args, this.opts).exec(this.client); + hgetall = >(...args: CommandArgs) => + new HGetAllCommand(args, this.opts).exec(this.client); /** * @see https://redis.io/commands/hincrby @@ -668,14 +665,13 @@ export class Redis { /** * @see https://redis.io/commands/hmget */ - hmget = >( - ...args: CommandArgs - ) => new HMGetCommand(args, this.opts).exec(this.client); + hmget = >(...args: CommandArgs) => + new HMGetCommand(args, this.opts).exec(this.client); /** * @see https://redis.io/commands/hmset */ - hmset = (key: string, kv: { [field: string]: TData }) => + hmset = (key: string, kv: Record) => new HMSetCommand([key, kv], this.opts).exec(this.client); /** @@ -693,11 +689,7 @@ export class Redis { key: string, count?: number, withValues?: boolean - ) => - new HRandFieldCommand( - [key, count, withValues] as any, - this.opts - ).exec(this.client); + ) => new HRandFieldCommand([key, count, withValues] as any, this.opts).exec(this.client); /** * @see https://redis.io/commands/hscan @@ -708,7 +700,7 @@ export class Redis { /** * @see https://redis.io/commands/hset */ - hset = (key: string, kv: { [field: string]: TData }) => + hset = (key: string, kv: Record) => new HSetCommand([key, kv], this.opts).exec(this.client); /** @@ -762,15 +754,8 @@ export class Redis { /** * @see https://redis.io/commands/linsert */ - linsert = ( - key: string, - direction: "before" | "after", - pivot: TData, - value: TData - ) => - new LInsertCommand([key, direction, pivot, value], this.opts).exec( - this.client - ); + linsert = (key: string, direction: "before" | "after", pivot: TData, value: TData) => + new LInsertCommand([key, direction, pivot, value], this.opts).exec(this.client); /** * @see https://redis.io/commands/llen @@ -847,13 +832,13 @@ export class Redis { /** * @see https://redis.io/commands/mset */ - mset = (kv: { [key: string]: TData }) => + mset = (kv: Record) => new MSetCommand([kv], this.opts).exec(this.client); /** * @see https://redis.io/commands/msetnx */ - msetnx = (kv: { [key: string]: TData }) => + msetnx = (kv: Record) => new MSetNXCommand([kv], this.opts).exec(this.client); /** @@ -1050,24 +1035,19 @@ export class Redis { * @see https://redis.io/commands/smismember */ smismember = (key: string, members: TMembers) => - new SMIsMemberCommand([key, members], this.opts).exec( - this.client - ); + new SMIsMemberCommand([key, members], this.opts).exec(this.client); /** * @see https://redis.io/commands/smembers */ - smembers = ( - ...args: CommandArgs - ) => new SMembersCommand(args, this.opts).exec(this.client); + smembers = (...args: CommandArgs) => + new SMembersCommand(args, this.opts).exec(this.client); /** * @see https://redis.io/commands/smove */ smove = (source: string, destination: string, member: TData) => - new SMoveCommand([source, destination, member], this.opts).exec( - this.client - ); + new SMoveCommand([source, destination, member], this.opts).exec(this.client); /** * @see https://redis.io/commands/spop @@ -1229,22 +1209,17 @@ export class Redis { */ zadd = ( ...args: - | [ - key: string, - scoreMember: ScoreMember, - ...scoreMemberPairs: ScoreMember[] - ] + | [key: string, scoreMember: ScoreMember, ...scoreMemberPairs: ScoreMember[]] | [ key: string, opts: ZAddCommandOptions, - ...scoreMemberPairs: [ScoreMember, ...ScoreMember[]] + ...scoreMemberPairs: [ScoreMember, ...ScoreMember[]], ] ) => { if ("score" in args[1]) { - return new ZAddCommand( - [args[0], args[1] as ScoreMember, ...(args.slice(2) as any)], - this.opts - ).exec(this.client); + return new ZAddCommand([args[0], args[1], ...(args.slice(2) as any)], this.opts).exec( + this.client + ); } return new ZAddCommand( @@ -1274,9 +1249,7 @@ export class Redis { * @see https://redis.io/commands/zincrby */ zincrby = (key: string, increment: number, member: TData) => - new ZIncrByCommand([key, increment, member], this.opts).exec( - this.client - ); + new ZIncrByCommand([key, increment, member], this.opts).exec(this.client); /** * @see https://redis.io/commands/zinterstore @@ -1318,13 +1291,13 @@ export class Redis { key: string, min: `(${string}` | `[${string}` | "-" | "+", max: `(${string}` | `[${string}` | "-" | "+", - opts: { byLex: true } & ZRangeCommandOptions + opts: { byLex: true } & ZRangeCommandOptions, ] | [ key: string, min: number | `(${number}` | "-inf" | "+inf", max: number | `(${number}` | "-inf" | "+inf", - opts: { byScore: true } & ZRangeCommandOptions + opts: { byScore: true } & ZRangeCommandOptions, ] ) => new ZRangeCommand(args as any, this.opts).exec(this.client); diff --git a/pkg/script.test.ts b/pkg/script.test.ts index 112e2c1d..0803ecbb 100644 --- a/pkg/script.test.ts +++ b/pkg/script.test.ts @@ -16,7 +16,7 @@ describe("create a new script", () => { const res = await script.eval([], ["Hello World"]); expect(res).toEqual("Hello World"); }, - { timeout: 15000 }, + { timeout: 15_000 } ); }); diff --git a/pkg/script.ts b/pkg/script.ts index 871f8c6b..acd349e6 100644 --- a/pkg/script.ts +++ b/pkg/script.ts @@ -1,6 +1,6 @@ import Hex from "crypto-js/enc-hex.js"; import sha1 from "crypto-js/sha1.js"; -import { Redis } from "./redis"; +import type { Redis } from "./redis"; /** * Creates a new script. * @@ -49,11 +49,11 @@ export class Script { * Following calls will be able to use the cached script */ public async exec(keys: string[], args: string[]): Promise { - const res = await this.redis.evalsha(this.sha1, keys, args).catch(async (err) => { - if (err instanceof Error && err.message.toLowerCase().includes("noscript")) { + const res = await this.redis.evalsha(this.sha1, keys, args).catch(async (error) => { + if (error instanceof Error && error.message.toLowerCase().includes("noscript")) { return await this.redis.eval(this.script, keys, args); } - throw err; + throw error; }); return res as TResult; } diff --git a/pkg/test-utils.ts b/pkg/test-utils.ts index ceb3d1bf..66b4a34c 100644 --- a/pkg/test-utils.ts +++ b/pkg/test-utils.ts @@ -11,7 +11,7 @@ export function randomID(): string { const s: string[] = []; for (let i = 0; i < bytes.byteLength; i++) { - s.push(String.fromCharCode(bytes[i])); + s.push(String.fromCodePoint(bytes[i])); } return btoa(s.join("")); } diff --git a/platforms/cloudflare.ts b/platforms/cloudflare.ts index 35881664..f661feca 100644 --- a/platforms/cloudflare.ts +++ b/platforms/cloudflare.ts @@ -1,6 +1,6 @@ -import type { Requester, UpstashRequest, UpstashResponse } from "../pkg/http"; -import { Pipeline } from "../pkg/pipeline"; -import { HttpClient, RequesterConfig } from "../pkg/http"; +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import type { RequesterConfig } from "../pkg/http"; +import { HttpClient } from "../pkg/http"; import * as core from "../pkg/redis"; import { VERSION } from "../version"; @@ -8,9 +8,9 @@ type Env = { UPSTASH_DISABLE_TELEMETRY?: string; }; -export * as errors from "../pkg/error" +export * as errors from "../pkg/error"; export type * from "../pkg/commands/types"; -export type { Requester, UpstashRequest, UpstashResponse, Pipeline }; + /** * Connection credentials for upstash redis. * Get them from https://console.upstash.com/redis/ @@ -50,12 +50,16 @@ export class Redis extends core.Redis { * ``` */ constructor(config: RedisConfigCloudflare, env?: Env) { - if(!config.url) { - throw new Error(`[Upstash Redis] The 'url' property is missing or undefined in your Redis config.`) + if (!config.url) { + throw new Error( + `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` + ); } - if(!config.token) { - throw new Error(`[Upstash Redis] The 'token' property is missing or undefined in your Redis config.`) + if (!config.token) { + throw new Error( + `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` + ); } if (config.url.startsWith(" ") || config.url.endsWith(" ") || /\r|\n/.test(config.url)) { @@ -87,7 +91,7 @@ export class Redis extends core.Redis { }); if (this.enableAutoPipelining) { - return this.autoPipeline() + return this.autoPipeline(); } } @@ -108,24 +112,27 @@ export class Redis extends core.Redis { UPSTASH_REDIS_REST_TOKEN: string; UPSTASH_DISABLE_TELEMETRY?: string; }, - opts?: Omit, + opts?: Omit ): Redis { - // @ts-ignore These will be defined by cloudflare + // @ts-expect-error These will be defined by cloudflare const url = env?.UPSTASH_REDIS_REST_URL ?? UPSTASH_REDIS_REST_URL; - // @ts-ignore These will be defined by cloudflare + // @ts-expect-error These will be defined by cloudflare const token = env?.UPSTASH_REDIS_REST_TOKEN ?? UPSTASH_REDIS_REST_TOKEN; if (!url) { throw new Error( - "Unable to find environment variable: `UPSTASH_REDIS_REST_URL`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_URL`", + "Unable to find environment variable: `UPSTASH_REDIS_REST_URL`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_URL`" ); } if (!token) { throw new Error( - "Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_TOKEN`", + "Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_TOKEN`" ); } return new Redis({ ...opts, url, token }, env); } } + +export { type Requester, type UpstashRequest, type UpstashResponse } from "../pkg/http"; +export { type Pipeline } from "../pkg/pipeline"; diff --git a/platforms/fastly.ts b/platforms/fastly.ts index 6dcef11a..af3fe8cf 100644 --- a/platforms/fastly.ts +++ b/platforms/fastly.ts @@ -1,12 +1,11 @@ -import type { Requester, RequesterConfig, UpstashRequest, UpstashResponse } from "../pkg/http"; -import { Pipeline } from "../pkg/pipeline"; +import type { RequesterConfig } from "../pkg/http"; + import { HttpClient } from "../pkg/http"; import * as core from "../pkg/redis"; import { VERSION } from "../version"; -export * as errors from "../pkg/error" +export * as errors from "../pkg/error"; export type * from "../pkg/commands/types"; -export type { Requester, UpstashRequest, UpstashResponse, Pipeline }; /** * Connection credentials for upstash redis. @@ -48,12 +47,16 @@ export class Redis extends core.Redis { * ``` */ constructor(config: RedisConfigFastly) { - if(!config.url) { - throw new Error(`[Upstash Redis] The 'url' property is missing or undefined in your Redis config.`) + if (!config.url) { + throw new Error( + `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` + ); } - if(!config.token) { - throw new Error(`[Upstash Redis] The 'token' property is missing or undefined in your Redis config.`) + if (!config.token) { + throw new Error( + `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` + ); } if (config.url.startsWith(" ") || config.url.endsWith(" ") || /\r|\n/.test(config.url)) { @@ -74,7 +77,7 @@ export class Redis extends core.Redis { super(client, { automaticDeserialization: config.automaticDeserialization, - enableAutoPipelining: config.enableAutoPipelining + enableAutoPipelining: config.enableAutoPipelining, }); this.addTelemetry({ sdk: `@upstash/redis@${VERSION}`, @@ -82,7 +85,10 @@ export class Redis extends core.Redis { }); if (this.enableAutoPipelining) { - return this.autoPipeline() + return this.autoPipeline(); } } } + +export { type Requester, type UpstashRequest, type UpstashResponse } from "../pkg/http"; +export { type Pipeline } from "../pkg/pipeline"; diff --git a/platforms/nodejs.ts b/platforms/nodejs.ts index 7bf7d8a1..358e1613 100644 --- a/platforms/nodejs.ts +++ b/platforms/nodejs.ts @@ -1,13 +1,9 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // deno-lint-ignore-file -import { - HttpClient, - Requester, - RequesterConfig, - UpstashRequest, - UpstashResponse, -} from "../pkg/http"; -import { Pipeline } from "../pkg/pipeline"; +import type { Requester, RequesterConfig } from "../pkg/http"; +import { HttpClient } from "../pkg/http"; + import * as core from "../pkg/redis"; import { VERSION } from "../version"; @@ -15,11 +11,10 @@ import { VERSION } from "../version"; * Workaround for nodejs 14, where atob is not included in the standardlib */ if (typeof atob === "undefined") { - global.atob = (b64: string) => Buffer.from(b64, "base64").toString("utf-8"); + global.atob = (b64: string) => Buffer.from(b64, "base64").toString("utf8"); } -export * as errors from "../pkg/error" +export * as errors from "../pkg/error"; export type * from "../pkg/commands/types"; -export type { Requester, UpstashRequest, UpstashResponse, Pipeline }; /** * Connection credentials for upstash redis. @@ -56,7 +51,7 @@ export type RedisConfigNodejs = { */ signal?: AbortSignal; latencyLogging?: boolean; - agent?: any; + agent?: unknown; keepAlive?: boolean; } & core.RedisOptions & RequesterConfig; @@ -95,19 +90,22 @@ export class Redis extends core.Redis { * const redis = new Redis(requester) * ``` */ - constructor(requesters: Requester); constructor(configOrRequester: RedisConfigNodejs | Requester) { if ("request" in configOrRequester) { super(configOrRequester); return; } - if(!configOrRequester.url) { - throw new Error(`[Upstash Redis] The 'url' property is missing or undefined in your Redis config.`) + if (!configOrRequester.url) { + throw new Error( + `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` + ); } - if(!configOrRequester.token) { - throw new Error(`[Upstash Redis] The 'token' property is missing or undefined in your Redis config.`) + if (!configOrRequester.token) { + throw new Error( + `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` + ); } if ( @@ -129,9 +127,10 @@ export class Redis extends core.Redis { baseUrl: configOrRequester.url, retry: configOrRequester.retry, headers: { authorization: `Bearer ${configOrRequester.token}` }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment agent: configOrRequester.agent, responseEncoding: configOrRequester.responseEncoding, - cache: configOrRequester.cache || "no-store", + cache: configOrRequester.cache ?? "no-store", signal: configOrRequester.signal, keepAlive: configOrRequester.keepAlive, }); @@ -140,19 +139,19 @@ export class Redis extends core.Redis { automaticDeserialization: configOrRequester.automaticDeserialization, enableTelemetry: !process.env.UPSTASH_DISABLE_TELEMETRY, latencyLogging: configOrRequester.latencyLogging, - enableAutoPipelining: configOrRequester.enableAutoPipelining + enableAutoPipelining: configOrRequester.enableAutoPipelining, }); this.addTelemetry({ runtime: - // @ts-ignore + // @ts-expect-error to silence compiler typeof EdgeRuntime === "string" ? "edge-light" : `node@${process.version}`, platform: process.env.VERCEL ? "vercel" : process.env.AWS_REGION ? "aws" : "unknown", sdk: `@upstash/redis@${VERSION}`, }); if (this.enableAutoPipelining) { - return this.autoPipeline() + return this.autoPipeline(); } } @@ -167,21 +166,25 @@ export class Redis extends core.Redis { */ static fromEnv(config?: Omit): Redis { // @ts-ignore process will be defined in node - if (typeof process?.env === "undefined") { - throw new Error( - 'Unable to get environment variables, `process.env` is undefined. If you are deploying to cloudflare, please import from "@upstash/redis/cloudflare" instead', + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (process.env === undefined) { + throw new TypeError( + 'Unable to get environment variables, `process.env` is undefined. If you are deploying to cloudflare, please import from "@upstash/redis/cloudflare" instead' ); } // @ts-ignore process will be defined in node - const url = process?.env.UPSTASH_REDIS_REST_URL; + const url = process.env.UPSTASH_REDIS_REST_URL; if (!url) { throw new Error("Unable to find environment variable: `UPSTASH_REDIS_REST_URL`"); } // @ts-ignore process will be defined in node - const token = process?.env.UPSTASH_REDIS_REST_TOKEN; + const token = process.env.UPSTASH_REDIS_REST_TOKEN; if (!token) { throw new Error("Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`"); } return new Redis({ ...config, url, token }); } } + +export { type Pipeline } from "../pkg/pipeline"; +export { type UpstashRequest, type UpstashResponse, type Requester } from "../pkg/http"; diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 00000000..206e41eb --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,13 @@ +/** + * @type {import('prettier').Config} + */ +const config = { + endOfLine: "lf", + singleQuote: false, + tabWidth: 2, + trailingComma: "es5", + printWidth: 100, + arrowParens: "always", +}; + +export default config;