├── .env.example ├── .github └── workflows │ ├── release.yml │ └── tests.yaml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── bun.lockb ├── commitlint.config.js ├── eslint.config.mjs ├── examples ├── auto-pipeline │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── components │ │ │ └── DataComponent.tsx │ │ ├── data │ │ │ ├── getEvents.ts │ │ │ ├── getUsers.ts │ │ │ └── redis.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ └── tsconfig.json ├── aws-cdk-python │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── bin │ │ └── aws-cdk-python.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lib │ │ ├── api │ │ │ ├── index.py │ │ │ └── requirements.txt │ │ └── aws-cdk-python-stack.ts │ ├── package-lock.json │ ├── package.json │ ├── test │ │ └── aws-cdk-python.test.ts │ └── tsconfig.json ├── aws-cdk-typescript │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── api │ │ └── counter.ts │ ├── bin │ │ └── aws-cdk-typescript.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lib │ │ └── aws-cdk-typescript-stack.ts │ ├── package-lock.json │ ├── package.json │ ├── test │ │ └── aws-cdk-typescript.test.ts │ └── tsconfig.json ├── aws-lambda │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── package-lock.json │ └── package.json ├── aws-sam │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── events │ │ └── event.json │ ├── hello_world │ │ ├── __init__.py │ │ ├── app.py │ │ └── requirements.txt │ ├── samconfig.toml │ ├── template.yaml │ └── tests │ │ ├── __init__.py │ │ ├── integration │ │ ├── __init__.py │ │ └── test_api_gateway.py │ │ ├── requirements.txt │ │ └── unit │ │ ├── __init__.py │ │ └── test_handler.py ├── azure-functions │ ├── .funcignore │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── host.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── functions │ │ │ └── CounterFunction.ts │ └── tsconfig.json ├── cloudflare-workers-with-typescript │ ├── README.md │ ├── ci.test.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── wrangler.toml ├── cloudflare-workers │ ├── README.md │ ├── bun.lockb │ ├── ci.test.ts │ ├── index.js │ ├── package.json │ └── wrangler.toml ├── deno │ ├── main.test.ts │ └── main.ts ├── fastapi │ ├── README.md │ ├── main.py │ └── requirements.txt ├── fastly │ ├── .gitignore │ ├── README.md │ ├── fastly.toml │ ├── package.json │ ├── src │ │ └── index.js │ └── webpack.config.js ├── google-cloud-functions │ ├── README.md │ ├── index.js │ └── package.json ├── ion │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── sst-env.d.ts │ ├── sst.config.ts │ ├── tailwind.config.ts │ └── tsconfig.json ├── nextjs-app-router │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── tailwind.config.ts │ └── tsconfig.json ├── nextjs-pages-router │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── api │ │ │ └── hello.ts │ │ └── index.tsx │ ├── postcss.config.mjs │ ├── styles │ │ └── globals.css │ ├── tailwind.config.ts │ └── tsconfig.json ├── nodejs │ ├── .env.example │ ├── README.md │ ├── index.js │ └── package.json ├── serverless-framework │ └── counter │ │ ├── .env.example │ │ ├── .gitignore │ │ ├── README.md │ │ ├── handler.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── serverless.yml ├── sst-v2 │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── package.json │ ├── packages │ │ ├── core │ │ │ ├── package.json │ │ │ ├── sst-env.d.ts │ │ │ └── tsconfig.json │ │ ├── functions │ │ │ ├── package.json │ │ │ ├── sst-env.d.ts │ │ │ └── tsconfig.json │ │ └── web │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── next.config.mjs │ │ │ ├── package.json │ │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── api │ │ │ │ └── hello.ts │ │ │ └── index.tsx │ │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── next.svg │ │ │ └── vercel.svg │ │ │ ├── sst-env.d.ts │ │ │ ├── styles │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ │ └── tsconfig.json │ ├── pnpm-workspace.yaml │ ├── sst.config.ts │ ├── stacks │ │ └── Default.ts │ └── tsconfig.json ├── terraform │ ├── .gitignore │ ├── README.md │ ├── counter │ │ ├── counter.js │ │ └── package.json │ ├── main.tf │ ├── outputs.tf │ ├── terraform.tf │ └── variables.tf ├── vercel-functions-app-router │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ └── hello │ │ │ │ └── route.ts │ │ ├── globals.css │ │ └── layout.tsx │ ├── ci.test.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── tailwind.config.ts │ └── tsconfig.json ├── vercel-functions-pages-router │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── ci.test.ts │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── api │ │ │ └── hello.ts │ ├── postcss.config.mjs │ ├── styles │ │ └── globals.css │ ├── tailwind.config.ts │ └── tsconfig.json ├── vercel-python-runtime-django │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── example │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── urls.py │ │ └── views.py │ ├── manage.py │ ├── package.json │ ├── requirements.txt │ └── vercel.json └── with-sentry │ ├── index.ts │ └── package.json ├── package.json ├── pkg ├── auto-pipeline.test.ts ├── auto-pipeline.ts ├── commands │ ├── append.test.ts │ ├── append.ts │ ├── bitcount.test.ts │ ├── bitcount.ts │ ├── bitfield.test.ts │ ├── bitfield.ts │ ├── bitop.test.ts │ ├── bitop.ts │ ├── bitpos.test.ts │ ├── bitpos.ts │ ├── command.test.ts │ ├── command.ts │ ├── copy.test.ts │ ├── copy.ts │ ├── dbsize.test.ts │ ├── dbsize.ts │ ├── decr.test.ts │ ├── decr.ts │ ├── decrby.test.ts │ ├── decrby.ts │ ├── del.test.ts │ ├── del.ts │ ├── echo.test.ts │ ├── echo.ts │ ├── eval.test.ts │ ├── eval.ts │ ├── evalRo.test.ts │ ├── evalRo.ts │ ├── evalsha.test.ts │ ├── evalsha.ts │ ├── evalshaRo.test.ts │ ├── evalshaRo.ts │ ├── exec.test.ts │ ├── exec.ts │ ├── exists.test.ts │ ├── exists.ts │ ├── expire.test.ts │ ├── expire.ts │ ├── expireat.test.ts │ ├── expireat.ts │ ├── flushall.test.ts │ ├── flushall.ts │ ├── flushdb.test.ts │ ├── flushdb.ts │ ├── geo_add.test.ts │ ├── geo_add.ts │ ├── geo_dist.test.ts │ ├── geo_dist.ts │ ├── geo_hash.test.ts │ ├── geo_hash.ts │ ├── geo_pos.test.ts │ ├── geo_pos.ts │ ├── geo_search.test.ts │ ├── geo_search.ts │ ├── geo_search_store.test.ts │ ├── geo_search_store.ts │ ├── get.test.ts │ ├── get.ts │ ├── getbit.test.ts │ ├── getbit.ts │ ├── getdel.test.ts │ ├── getdel.ts │ ├── getex.test.ts │ ├── getex.ts │ ├── getrange.test.ts │ ├── getrange.ts │ ├── getset.test.ts │ ├── getset.ts │ ├── hdel.test.ts │ ├── hdel.ts │ ├── hexists.test.ts │ ├── hexists.ts │ ├── hexpire.test.ts │ ├── hexpire.ts │ ├── hexpireat.test.ts │ ├── hexpireat.ts │ ├── hexpiretime.test.ts │ ├── hexpiretime.ts │ ├── hget.test.ts │ ├── hget.ts │ ├── hgetall.test.ts │ ├── hgetall.ts │ ├── hincrby.test.ts │ ├── hincrby.ts │ ├── hincrbyfloat.test.ts │ ├── hincrbyfloat.ts │ ├── hkeys.test.ts │ ├── hkeys.ts │ ├── hlen.test.ts │ ├── hlen.ts │ ├── hmget.test.ts │ ├── hmget.ts │ ├── hmset.test.ts │ ├── hmset.ts │ ├── hpersist.test.ts │ ├── hpersist.ts │ ├── hpexpire.test.ts │ ├── hpexpire.ts │ ├── hpexpireat.test.ts │ ├── hpexpireat.ts │ ├── hpexpiretime.test.ts │ ├── hpexpiretime.ts │ ├── hpttl.test.ts │ ├── hpttl.ts │ ├── hrandfield.test.ts │ ├── hrandfield.ts │ ├── hscan.test.ts │ ├── hscan.ts │ ├── hset.test.ts │ ├── hset.ts │ ├── hsetnx.test.ts │ ├── hsetnx.ts │ ├── hstrlen.test.ts │ ├── hstrlen.ts │ ├── httl.test.ts │ ├── httl.ts │ ├── hvals.test.ts │ ├── hvals.ts │ ├── incr.test.ts │ ├── incr.ts │ ├── incrby.test.ts │ ├── incrby.ts │ ├── incrbyfloat.test.ts │ ├── incrbyfloat.ts │ ├── json_arrappend.test.ts │ ├── json_arrappend.ts │ ├── json_arrindex.test.ts │ ├── json_arrindex.ts │ ├── json_arrinsert.test.ts │ ├── json_arrinsert.ts │ ├── json_arrlen.test.ts │ ├── json_arrlen.ts │ ├── json_arrpop.test.ts │ ├── json_arrpop.ts │ ├── json_arrtrim.test.ts │ ├── json_arrtrim.ts │ ├── json_clear.test.ts │ ├── json_clear.ts │ ├── json_del.test.ts │ ├── json_del.ts │ ├── json_forget.test.ts │ ├── json_forget.ts │ ├── json_get.test.ts │ ├── json_get.ts │ ├── json_merge.test.ts │ ├── json_merge.ts │ ├── json_mget.test.ts │ ├── json_mget.ts │ ├── json_mset.test.ts │ ├── json_mset.ts │ ├── json_numincrby.test.ts │ ├── json_numincrby.ts │ ├── json_nummultby.test.ts │ ├── json_nummultby.ts │ ├── json_objkeys.test.ts │ ├── json_objkeys.ts │ ├── json_objlen.test.ts │ ├── json_objlen.ts │ ├── json_resp.test.ts │ ├── json_resp.ts │ ├── json_set.test.ts │ ├── json_set.ts │ ├── json_strappend.test.ts │ ├── json_strappend.ts │ ├── json_strlen.test.ts │ ├── json_strlen.ts │ ├── json_toggle.test.ts │ ├── json_toggle.ts │ ├── json_type.test.ts │ ├── json_type.ts │ ├── keys.test.ts │ ├── keys.ts │ ├── lindex.test.ts │ ├── lindex.ts │ ├── linsert.test.ts │ ├── linsert.ts │ ├── llen.test.ts │ ├── llen.ts │ ├── lmove.test.ts │ ├── lmove.ts │ ├── lmpop.test.ts │ ├── lmpop.ts │ ├── lpop.test.ts │ ├── lpop.ts │ ├── lpos.test.ts │ ├── lpos.ts │ ├── lpush.test.ts │ ├── lpush.ts │ ├── lpushx.test.ts │ ├── lpushx.ts │ ├── lrange.test.ts │ ├── lrange.ts │ ├── lrem.test.ts │ ├── lrem.ts │ ├── lset.test.ts │ ├── lset.ts │ ├── ltrim.test.ts │ ├── ltrim.ts │ ├── mget.test.ts │ ├── mget.ts │ ├── mod.ts │ ├── mset.test.ts │ ├── mset.ts │ ├── msetnx.test.ts │ ├── msetnx.ts │ ├── persist.test.ts │ ├── persist.ts │ ├── pexpire.test.ts │ ├── pexpire.ts │ ├── pexpireat.test.ts │ ├── pexpireat.ts │ ├── pfadd.test.ts │ ├── pfadd.ts │ ├── pfcount.test.ts │ ├── pfcount.ts │ ├── pfmerge.test.ts │ ├── pfmerge.ts │ ├── ping.test.ts │ ├── ping.ts │ ├── psetex.test.ts │ ├── psetex.ts │ ├── psubscribe.test.ts │ ├── psubscribe.ts │ ├── pttl.test.ts │ ├── pttl.ts │ ├── publish.test.ts │ ├── publish.ts │ ├── randomkey.test.ts │ ├── randomkey.ts │ ├── rename.test.ts │ ├── rename.ts │ ├── renamenx.test.ts │ ├── renamenx.ts │ ├── rpop.test.ts │ ├── rpop.ts │ ├── rpush.test.ts │ ├── rpush.ts │ ├── rpushx.test.ts │ ├── rpushx.ts │ ├── sadd.test.ts │ ├── sadd.ts │ ├── scan.test.ts │ ├── scan.ts │ ├── scard.test.ts │ ├── scard.ts │ ├── script_exists.test.ts │ ├── script_exists.ts │ ├── script_flush.ts │ ├── script_load.test.ts │ ├── script_load.ts │ ├── sdiff.test.ts │ ├── sdiff.ts │ ├── sdiffstore.test.ts │ ├── sdiffstore.ts │ ├── set.test.ts │ ├── set.ts │ ├── setbit.test.ts │ ├── setbit.ts │ ├── setex.test.ts │ ├── setex.ts │ ├── setnx.test.ts │ ├── setnx.ts │ ├── setrange.test.ts │ ├── setrange.ts │ ├── sinter.test.ts │ ├── sinter.ts │ ├── sinterstore.test.ts │ ├── sinterstore.ts │ ├── sismember.test.ts │ ├── sismember.ts │ ├── smembers.test.ts │ ├── smembers.ts │ ├── smismember.test.ts │ ├── smismember.ts │ ├── smove.test.ts │ ├── smove.ts │ ├── spop.test.ts │ ├── spop.ts │ ├── srandmember.test.ts │ ├── srandmember.ts │ ├── srem.test.ts │ ├── srem.ts │ ├── sscan.test.ts │ ├── sscan.ts │ ├── strlen.test.ts │ ├── strlen.ts │ ├── subscribe.test.ts │ ├── subscribe.ts │ ├── sunion.test.ts │ ├── sunion.ts │ ├── sunionstore.test.ts │ ├── sunionstore.ts │ ├── time.test.ts │ ├── time.ts │ ├── touch.test.ts │ ├── touch.ts │ ├── ttl.test.ts │ ├── ttl.ts │ ├── type.test.ts │ ├── type.ts │ ├── types.ts │ ├── unlink.test.ts │ ├── unlink.ts │ ├── xack.test.ts │ ├── xack.ts │ ├── xadd.test.ts │ ├── xadd.ts │ ├── xautoclaim.test.ts │ ├── xautoclaim.ts │ ├── xclaim.test.ts │ ├── xclaim.ts │ ├── xdel.test.ts │ ├── xdel.ts │ ├── xgroup.test.ts │ ├── xgroup.ts │ ├── xinfo.test.ts │ ├── xinfo.ts │ ├── xlen.test.ts │ ├── xlen.ts │ ├── xpending.test.ts │ ├── xpending.ts │ ├── xrange.test.ts │ ├── xrange.ts │ ├── xread.test.ts │ ├── xread.ts │ ├── xreadgroup.test.ts │ ├── xreadgroup.ts │ ├── xrevrange.test.ts │ ├── xrevrange.ts │ ├── xtrim.test.ts │ ├── xtrim.ts │ ├── zadd.test.ts │ ├── zadd.ts │ ├── zcard.test.ts │ ├── zcard.ts │ ├── zcount.test.ts │ ├── zcount.ts │ ├── zdiffstore.test.ts │ ├── zdiffstore.ts │ ├── zincrby.test.ts │ ├── zincrby.ts │ ├── zinterstore.test.ts │ ├── zinterstore.ts │ ├── zlexcount.test.ts │ ├── zlexcount.ts │ ├── zmscore.test.ts │ ├── zmscore.ts │ ├── zpopmax.test.ts │ ├── zpopmax.ts │ ├── zpopmin.test.ts │ ├── zpopmin.ts │ ├── zrange.test.ts │ ├── zrange.ts │ ├── zrank.test.ts │ ├── zrank.ts │ ├── zrem.test.ts │ ├── zrem.ts │ ├── zremrangebylex.test.ts │ ├── zremrangebylex.ts │ ├── zremrangebyrank.test.ts │ ├── zremrangebyrank.ts │ ├── zremrangebyscore.test.ts │ ├── zremrangebyscore.ts │ ├── zrevrank.test.ts │ ├── zrevrank.ts │ ├── zscan.test.ts │ ├── zscan.ts │ ├── zscore.test.ts │ ├── zscore.ts │ ├── zunion.test.ts │ ├── zunion.ts │ ├── zunionstore.test.ts │ └── zunionstore.ts ├── error.ts ├── http.test.ts ├── http.ts ├── index.ts ├── pipeline.test.ts ├── pipeline.ts ├── read-your-writes.test.ts ├── redis.test.ts ├── redis.ts ├── script.test.ts ├── script.ts ├── scriptRo.test.ts ├── scriptRo.ts ├── test-utils.test.ts ├── test-utils.ts ├── types.ts └── util.ts ├── platforms ├── cloudflare.ts ├── fastly.ts └── nodejs.ts ├── prettier.config.mjs ├── tsconfig.json ├── tsup.config.ts └── version.ts /.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | bun --no -- commitlint --edit "" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | 2 | bun run fmt -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | bun run build 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/examples -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "git.autofetch": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": "explicit" 6 | }, 7 | "[json]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[javascript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | }, 13 | "[jsonc]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | }, 16 | "[typescript]": { 17 | "editor.defaultFormatter": "esbenp.prettier-vscode" 18 | }, 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | } 21 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/bun.lockb -------------------------------------------------------------------------------- /examples/auto-pipeline/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/auto-pipeline/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/auto-pipeline/app/data/getEvents.ts: -------------------------------------------------------------------------------- 1 | 2 | import client from "./redis" 3 | 4 | export async function getEvents() { 5 | const keys = await client.scan(0, { match: 'event:*' }); 6 | 7 | if (keys[1].length === 0) { 8 | // If no keys found, insert sample data 9 | client.hmset('event:1', {'name': 'Sample Event 1', 'date': '2024-05-13'}); 10 | client.hmset('event:2', {'name': 'Sample Event 2', 'date': '2024-05-14'}); 11 | // Add more sample events as needed 12 | } 13 | 14 | const events = await Promise.all(keys[1].map(async key => { 15 | return client.hgetall(key) ?? {name: "default", date: "2000-01-01"}; 16 | })); 17 | return events as {name: string, date: string}[] 18 | }; 19 | -------------------------------------------------------------------------------- /examples/auto-pipeline/app/data/getUsers.ts: -------------------------------------------------------------------------------- 1 | 2 | import client from "./redis" 3 | 4 | export async function getUsers() { 5 | const keys = await client.scan(0, { match: 'user:*' }); 6 | 7 | if (keys[1].length === 0) { 8 | // If no keys found, insert sample data 9 | client.hmset('user:1', {'username': 'Adam', 'birthday': '1990-01-01'}); 10 | client.hmset('user:2', {'username': 'Eve', 'birthday': '1980-01-05'}); 11 | // Add more sample users as needed 12 | } 13 | 14 | const users = await Promise.all(keys[1].map(async key => { 15 | return client.hgetall(key) ?? {username: "default", birthday: "2000-01-01"}; 16 | })); 17 | return users as {username: string, birthday: string}[] 18 | } -------------------------------------------------------------------------------- /examples/auto-pipeline/app/data/redis.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Redis } from '@upstash/redis' 3 | 4 | export const LATENCY_LOGGING = true 5 | export const ENABLE_AUTO_PIPELINING = true 6 | 7 | const client = Redis.fromEnv({ 8 | latencyLogging: LATENCY_LOGGING, 9 | enableAutoPipelining: ENABLE_AUTO_PIPELINING 10 | }); 11 | 12 | export default client; 13 | -------------------------------------------------------------------------------- /examples/auto-pipeline/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/auto-pipeline/app/favicon.ico -------------------------------------------------------------------------------- /examples/auto-pipeline/app/globals.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/auto-pipeline/app/globals.css -------------------------------------------------------------------------------- /examples/auto-pipeline/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/auto-pipeline/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import React from 'react'; 4 | import DataComponent from './components/DataComponent' 5 | 6 | const HomePage = () => { 7 | return ( 8 |
9 |

Home Page

10 | 11 |
12 | ); 13 | }; 14 | 15 | export default HomePage; 16 | -------------------------------------------------------------------------------- /examples/auto-pipeline/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/auto-pipeline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-pipeline", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.3", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.3", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/auto-pipeline/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/auto-pipeline/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/auto-pipeline/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/auto-pipeline/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/aws-cdk-python/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /examples/aws-cdk-python/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /examples/aws-cdk-python/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /examples/aws-cdk-python/lib/api/index.py: -------------------------------------------------------------------------------- 1 | from upstash_redis import Redis 2 | 3 | redis = Redis.from_env() 4 | 5 | def handler(event, context): 6 | count = redis.incr('counter') 7 | return { 8 | 'statusCode': 200, 9 | 'body': f'Counter: {count}' 10 | } -------------------------------------------------------------------------------- /examples/aws-cdk-python/lib/api/requirements.txt: -------------------------------------------------------------------------------- 1 | upstash-redis -------------------------------------------------------------------------------- /examples/aws-cdk-python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-cdk-python", 3 | "version": "0.1.0", 4 | "bin": { 5 | "aws-cdk-python": "bin/aws-cdk-python.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^29.5.12", 15 | "@types/node": "20.14.2", 16 | "jest": "^29.7.0", 17 | "ts-jest": "^29.1.4", 18 | "aws-cdk": "2.147.2", 19 | "ts-node": "^10.9.2", 20 | "typescript": "~5.4.5" 21 | }, 22 | "dependencies": { 23 | "aws-cdk-lib": "2.147.2", 24 | "constructs": "^10.0.0", 25 | "source-map-support": "^0.5.21" 26 | } 27 | } -------------------------------------------------------------------------------- /examples/aws-cdk-python/test/aws-cdk-python.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as AwsCdkPython from '../lib/aws-cdk-python-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/aws-cdk-python-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new AwsCdkPython.AwsCdkPythonStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/api/counter.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from '@upstash/redis'; 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | export const handler = async function() { 6 | const count = await redis.incr("counter"); 7 | return { 8 | statusCode: 200, 9 | body: JSON.stringify('Counter: ' + count), 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-cdk-typescript", 3 | "version": "0.1.0", 4 | "bin": { 5 | "aws-cdk-typescript": "bin/aws-cdk-typescript.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^29.5.12", 15 | "@types/node": "20.14.2", 16 | "aws-cdk": "2.147.2", 17 | "jest": "^29.7.0", 18 | "ts-jest": "^29.1.4", 19 | "ts-node": "^10.9.2", 20 | "typescript": "~5.4.5" 21 | }, 22 | "dependencies": { 23 | "@upstash/redis": "latest", 24 | "aws-cdk-lib": "2.147.2", 25 | "constructs": "^10.0.0", 26 | "source-map-support": "^0.5.21" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/aws-cdk-typescript/test/aws-cdk-typescript.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as AwsCdkTypescript from '../lib/aws-cdk-typescript-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/aws-cdk-typescript-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new AwsCdkTypescript.AwsCdkTypescriptStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/aws-lambda/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.zip 3 | .env.local -------------------------------------------------------------------------------- /examples/aws-lambda/index.js: -------------------------------------------------------------------------------- 1 | const { Redis } = require('@upstash/redis'); 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | exports.handler = async (event) => { 6 | const count = await redis.incr("counter"); 7 | return { 8 | statusCode: 200, 9 | body: JSON.stringify('Counter: ' + count), 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/aws-lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@upstash/redis": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/aws-sam/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/aws-sam/__init__.py -------------------------------------------------------------------------------- /examples/aws-sam/hello_world/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/aws-sam/hello_world/__init__.py -------------------------------------------------------------------------------- /examples/aws-sam/hello_world/app.py: -------------------------------------------------------------------------------- 1 | from upstash_redis import Redis 2 | 3 | redis = Redis.from_env() 4 | 5 | def lambda_handler(event, context): 6 | count = redis.incr('counter') 7 | return { 8 | 'statusCode': 200, 9 | 'body': f'Counter: {count}' 10 | } -------------------------------------------------------------------------------- /examples/aws-sam/hello_world/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | upstash-redis -------------------------------------------------------------------------------- /examples/aws-sam/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/aws-sam/tests/__init__.py -------------------------------------------------------------------------------- /examples/aws-sam/tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/aws-sam/tests/integration/__init__.py -------------------------------------------------------------------------------- /examples/aws-sam/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | boto3 3 | requests 4 | -------------------------------------------------------------------------------- /examples/aws-sam/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/aws-sam/tests/unit/__init__.py -------------------------------------------------------------------------------- /examples/azure-functions/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | getting_started.md 8 | node_modules/@types/ 9 | node_modules/azure-functions-core-tools/ 10 | node_modules/typescript/ -------------------------------------------------------------------------------- /examples/azure-functions/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | 25 | node_modules 26 | dist 27 | 28 | # Local python packages 29 | .python_packages/ 30 | 31 | # Python Environments 32 | .env 33 | .venv 34 | env/ 35 | venv/ 36 | ENV/ 37 | env.bak/ 38 | venv.bak/ 39 | 40 | # Byte-compiled / optimized / DLL files 41 | __pycache__/ 42 | *.py[cod] 43 | *$py.class 44 | 45 | # Azurite artifacts 46 | __blobstorage__ 47 | __queuestorage__ 48 | __azurite_db*__.json -------------------------------------------------------------------------------- /examples/azure-functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } -------------------------------------------------------------------------------- /examples/azure-functions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[4.*, 5.0.0)" 14 | } 15 | } -------------------------------------------------------------------------------- /examples/azure-functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/src/functions/*.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc -w", 9 | "clean": "rimraf dist", 10 | "prestart": "npm run clean && npm run build", 11 | "start": "func start", 12 | "test": "echo \"No tests yet...\"" 13 | }, 14 | "dependencies": { 15 | "@azure/functions": "^4.0.0", 16 | "@upstash/redis": "latest" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "18.x", 20 | "azure-functions-core-tools": "^4.x", 21 | "rimraf": "^5.0.0", 22 | "typescript": "^4.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/azure-functions/src/functions/CounterFunction.ts: -------------------------------------------------------------------------------- 1 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; 2 | import { Redis } from "@upstash/redis"; 3 | 4 | const redis = new Redis({ 5 | url: process.env.UPSTASH_REDIS_REST_URL, 6 | token: process.env.UPSTASH_REDIS_REST_TOKEN 7 | }); 8 | 9 | export async function CounterFunction(request: HttpRequest, context: InvocationContext): Promise { 10 | const count = await redis.incr("counter"); 11 | 12 | return { status: 200, body: `Counter: ${count}` }; 13 | }; 14 | 15 | app.http('CounterFunction', { 16 | methods: ['GET', 'POST'], 17 | authLevel: 'anonymous', 18 | handler: CounterFunction 19 | }); -------------------------------------------------------------------------------- /examples/azure-functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "dist", 6 | "rootDir": ".", 7 | "sourceMap": true, 8 | "strict": false 9 | } 10 | } -------------------------------------------------------------------------------- /examples/cloudflare-workers-with-typescript/README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Worker Typescript Example 2 | 3 | This example uses 4 | [Wrangler](https://developers.cloudflare.com/workers/wrangler/) to create a 5 | typescript worker 6 | 7 | ## How to use 8 | 9 | 1. Clone and install the example 10 | 11 | ```bash 12 | git clone https://github.com/upstash/upstash-redis.git 13 | cd upstash-redis/examples/cloudflare-workers-with-typescript 14 | npm install 15 | ``` 16 | 17 | 2. Create a free Database on [upstash.com](https://console.upstash.com/redis) 18 | 3. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to 19 | `wrangler.toml` 20 | 4. Start the development server 21 | 22 | ```bash 23 | npm run start 24 | ``` 25 | 26 | 5. Open your browser at [localhost:8787](http://localhost:8787) 27 | -------------------------------------------------------------------------------- /examples/cloudflare-workers-with-typescript/ci.test.ts: -------------------------------------------------------------------------------- 1 | import {test, expect} from "bun:test" 2 | const deploymentURL = process.env.DEPLOYMENT_URL; 3 | if (!deploymentURL) { 4 | throw new Error("DEPLOYMENT_URL not set"); 5 | } 6 | 7 | test("works", async () => { 8 | console.log({ deploymentURL }); 9 | const url = `${deploymentURL}/`; 10 | const res = await fetch(url); 11 | if (res.status !== 200) { 12 | console.log(await res.text()); 13 | } 14 | expect(res.status).toEqual(200); 15 | const json = (await res.json()) as { count: number }; 16 | expect(typeof json.count).toEqual("number"); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/cloudflare-workers-with-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare-workers-with-typescript", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "@cloudflare/workers-types": "^4.20221111.1", 6 | "typescript": "^4.8.4", 7 | "wrangler": "^2.4.4" 8 | }, 9 | "private": true, 10 | "scripts": { 11 | "start": "wrangler dev", 12 | "publish": "wrangler publish" 13 | }, 14 | "dependencies": { 15 | "@upstash/redis": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cloudflare-workers-with-typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis/cloudflare"; 2 | 3 | export interface Env { 4 | UPSTASH_REDIS_REST_URL: string; 5 | UPSTASH_REDIS_REST_TOKEN: string; 6 | } 7 | 8 | export default { 9 | async fetch(_request: Request, env: Env, _ctx: ExecutionContext): Promise { 10 | const redis = Redis.fromEnv(env); 11 | 12 | const count = await redis.incr("cloudflare-workers-with-typescript-count"); 13 | 14 | return new Response(JSON.stringify({ count })); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /examples/cloudflare-workers-with-typescript/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "cloudflare-workers-with-typescript" 2 | main = "src/index.ts" 3 | compatibility_date = "2022-05-31" 4 | 5 | 6 | # Set variables here or on cloudflare 7 | # [vars] 8 | # UPSTASH_REDIS_REST_URL = "REPLACE_THIS" 9 | # UPSTASH_REDIS_REST_TOKEN = "REPLACE_THIS" 10 | -------------------------------------------------------------------------------- /examples/cloudflare-workers/README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Workers Example 2 | 3 | This example uses 4 | [Wrangler](https://developers.cloudflare.com/workers/wrangler/) to create a 5 | javascript worker. 6 | 7 | ## How to use 8 | 9 | 1. Clone and install the example 10 | 11 | ```bash 12 | git clone https://github.com/upstash/upstash-redis.git 13 | cd upstash-redis/examples/cloudflare-workers 14 | npm install 15 | ``` 16 | 17 | 2. Create a free Database on [upstash.com](https://console.upstash.com/redis) 18 | 3. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to 19 | `wrangler.toml` 20 | 4. Start the development server 21 | 22 | ```bash 23 | npm run start 24 | ``` 25 | 26 | 5. Open your browser at [localhost:8787](http://localhost:8787) 27 | -------------------------------------------------------------------------------- /examples/cloudflare-workers/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/cloudflare-workers/bun.lockb -------------------------------------------------------------------------------- /examples/cloudflare-workers/ci.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | 3 | const deploymentURL = process.env.DEPLOYMENT_URL; 4 | if (!deploymentURL) { 5 | throw new Error("DEPLOYMENT_URL not set"); 6 | } 7 | 8 | test("works", async () => { 9 | console.log({ deploymentURL }); 10 | const url = `${deploymentURL}/`; 11 | const res = await fetch(url); 12 | if (res.status !== 200) { 13 | console.log(await res.text()); 14 | } 15 | expect(res.status).toEqual(200); 16 | const json = (await res.json()) as { count: number }; 17 | expect(typeof json.count).toEqual("number"); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/cloudflare-workers/index.js: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis/cloudflare"; 2 | 3 | export default { 4 | async fetch(_request, env) { 5 | const redis = Redis.fromEnv(env); 6 | 7 | const count = await redis.incr("cloudflare-workers-count"); 8 | 9 | return new Response(JSON.stringify({ count })); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /examples/cloudflare-workers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare-workers", 3 | "version": "1.0.0", 4 | "description": "Example project using wrangler2", 5 | "author": "Andreas Thomas ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "wrangler dev", 9 | "publish": "wrangler publish" 10 | }, 11 | "devDependencies": { 12 | "wrangler": "^2.20.0" 13 | }, 14 | "dependencies": { 15 | "@upstash/redis": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cloudflare-workers/wrangler.toml: -------------------------------------------------------------------------------- 1 | 2 | 3 | name = "upstash-redis" 4 | main = "index.js" 5 | compatibility_date = "2022-05-27" 6 | 7 | 8 | # Set variables here or on cloudflare 9 | # [vars] 10 | # UPSTASH_REDIS_REST_URL = "REPLACE_THIS" 11 | # UPSTASH_REDIS_REST_TOKEN = "REPLACE_THIS" 12 | -------------------------------------------------------------------------------- /examples/deno/main.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "bun:test"; 2 | 3 | const deploymentURL = process.env.DEPLOYMENT_URL; 4 | if (!deploymentURL) { 5 | throw new Error("DEPLOYMENT_URL not set"); 6 | } 7 | 8 | test("works", async () => { 9 | console.log({ deploymentURL }); 10 | const res = await fetch(deploymentURL); 11 | const body = await res.text(); 12 | console.log({ body }); 13 | expect(res.status).toBe(200); 14 | const json = JSON.parse(body) as { counter: number }; 15 | expect(typeof json.counter).toBe("number"); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/deno/main.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "https://deno.land/std@0.224.0/http/server.ts"; 2 | import { Redis } from "https://esm.sh/@upstash/redis@latest"; 3 | 4 | serve(async (_req: Request) => { 5 | const redis = Redis.fromEnv(); 6 | const counter = await redis.incr("deno deploy counter"); 7 | 8 | return new Response(JSON.stringify({ counter }), { status: 200 }); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/fastapi/README.md: -------------------------------------------------------------------------------- 1 | # FastAPI Example 2 | 3 | ### Project Setup 4 | 5 | Clone the example and install dependencies 6 | 7 | ```shell 8 | git clone https://github.com/upstash/redis-js.git 9 | cd redis-js/examples/fastapi 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | ### Database Setup 14 | 15 | Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and export the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your environment. 16 | 17 | ```shell 18 | export UPSTASH_REDIS_REST_URL= 19 | export UPSTASH_REDIS_REST_TOKEN= 20 | ``` 21 | 22 | ### Run 23 | Run the app locally with `fastapi dev main.py`, check `http://127.0.0.1:8000/` 24 | -------------------------------------------------------------------------------- /examples/fastapi/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from upstash_redis import Redis 4 | 5 | app = FastAPI() 6 | 7 | redis = Redis.from_env() 8 | 9 | @app.get("/") 10 | def read_root(): 11 | count = redis.incr('counter') 12 | return {"count": count} 13 | -------------------------------------------------------------------------------- /examples/fastapi/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | upstash-redis 3 | -------------------------------------------------------------------------------- /examples/fastly/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /bin 3 | /pkg 4 | -------------------------------------------------------------------------------- /examples/fastly/README.md: -------------------------------------------------------------------------------- 1 | # Deploying to Fastly Compute 2 | 3 | You can use @upstash/redis in Fastly Compute as it accesses the Redis using REST 4 | calls. 5 | 6 | Check [this guide](https://blog.upstash.com/fastly-compute-edge-with-redis) for 7 | step by step instructions. 8 | -------------------------------------------------------------------------------- /examples/fastly/fastly.toml: -------------------------------------------------------------------------------- 1 | # This file describes a Fastly Compute@Edge package. To learn more visit: 2 | # https://developer.fastly.com/reference/fastly-toml/ 3 | 4 | authors = ["upstash"] 5 | description = "Example of using Upstash with Fastly Compute@Edge" 6 | language = "javascript" 7 | manifest_version = 2 8 | name = "fastly-upstash" 9 | service_id = "" 10 | 11 | [local_server.backends.upstash-db] 12 | url = "" 13 | -------------------------------------------------------------------------------- /examples/fastly/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastly-upstash-example", 3 | "version": "0.2.0", 4 | "main": "src/index.js", 5 | "author": "woverton@fastly.com", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "core-js": "^3.19.1", 9 | "webpack": "^5.74.0", 10 | "webpack-cli": "^5.0.0" 11 | }, 12 | "dependencies": { 13 | "@fastly/js-compute": "^0.5.5", 14 | "@upstash/redis": "latest", 15 | "flight-path": "^1.0.10" 16 | }, 17 | "scripts": { 18 | "prebuild": "webpack", 19 | "build": "js-compute-runtime bin/index.js bin/main.wasm", 20 | "deploy": "npm run build && fastly compute deploy", 21 | "dev": "fastly compute serve --watch" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/fastly/src/index.js: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis/fastly"; 2 | 3 | addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); 4 | 5 | async function handleRequest(_event) { 6 | try { 7 | const redis = new Redis({ 8 | url: "", 9 | token: "", 10 | backend: "upstash-db", // same name you used in `fastly.toml` 11 | }); 12 | const count = await redis.incr("fastly"); 13 | return new Response(JSON.stringify({ count }), { 14 | headers: { "Content-Type": "application/json" }, 15 | }); 16 | } catch (err) { 17 | return new Response(err.message, { status: 500 }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/google-cloud-functions/index.js: -------------------------------------------------------------------------------- 1 | const { Redis } = require("@upstash/redis"); 2 | const functions = require('@google-cloud/functions-framework'); 3 | 4 | const redis = new Redis({ 5 | url: process.env.UPSTASH_REDIS_REST_URL, 6 | token: process.env.UPSTASH_REDIS_REST_TOKEN 7 | }); 8 | 9 | functions.http('counter', async (req, res) => { 10 | const count = await redis.incr("counter"); 11 | res.send("Counter:" + count); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/google-cloud-functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@google-cloud/functions-framework": "^3.0.0", 4 | "@upstash/redis": "latest" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/ion/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/ion/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/ion/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # sst 39 | .sst 40 | 41 | # open-next 42 | .open-next 43 | -------------------------------------------------------------------------------- /examples/ion/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/ion/app/favicon.ico -------------------------------------------------------------------------------- /examples/ion/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/ion/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/ion/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | export default async function Home() { 6 | const count = await redis.incr("counter"); 7 | return ( 8 |
9 |

Counter: {count}

10 |
11 | ) 12 | } -------------------------------------------------------------------------------- /examples/ion/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/ion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "sst dev next dev", 8 | "lint": "next lint", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.5", 14 | "react": "^18", 15 | "react-dom": "^18", 16 | "sst": "ion" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20", 20 | "@types/react": "^18", 21 | "@types/react-dom": "^18", 22 | "eslint": "^8", 23 | "eslint-config-next": "14.2.5", 24 | "postcss": "^8", 25 | "tailwindcss": "^3.4.1", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/ion/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/ion/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/ion/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | import "sst" 4 | declare module "sst" { 5 | export interface Resource { 6 | MyWeb: { 7 | type: "sst.aws.Nextjs" 8 | url: string 9 | } 10 | } 11 | } 12 | export {} -------------------------------------------------------------------------------- /examples/ion/sst.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export default $config({ 4 | app(input) { 5 | return { 6 | name: "my-app", 7 | removal: input?.stage === "production" ? "retain" : "remove", 8 | home: "aws", 9 | }; 10 | }, 11 | async run() { 12 | new sst.aws.Nextjs("MyWeb", { 13 | environment: { 14 | UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL || "", 15 | UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN || "", 16 | }, 17 | }); 18 | }, 19 | }); -------------------------------------------------------------------------------- /examples/ion/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/ion/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules","sst.config.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/nextjs-app-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/README.md: -------------------------------------------------------------------------------- 1 | # Next.js 14 - App Router Example 2 | 3 | ### Project Setup 4 | 5 | Clone the example and install dependencies 6 | 7 | ```shell 8 | git clone https://github.com/upstash/redis-js.git 9 | cd redis-js/examples/nextjs-app-router 10 | npm install 11 | ``` 12 | 13 | ### Database Setup 14 | 15 | Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `.env` file. 16 | 17 | ```shell 18 | UPSTASH_REDIS_REST_URL= 19 | UPSTASH_REDIS_REST_TOKEN= 20 | ``` 21 | 22 | 23 | ### Run & Deploy 24 | Run the app locally with `npm run dev`, check `http://localhost:3000/` 25 | 26 | Deploy your app with `vercel` 27 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | export default async function Home() { 6 | const count = await redis.incr("counter"); 7 | return ( 8 |
9 |

Counter: {count}

10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-app-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.5", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.5", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/nextjs-app-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/nextjs-pages-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/README.md: -------------------------------------------------------------------------------- 1 | # Next.js 14 - Pages Router Example 2 | 3 | ### Project Setup 4 | 5 | Clone the example and install dependencies 6 | 7 | ```shell 8 | git clone https://github.com/upstash/redis-js.git 9 | cd redis-js/examples/nextjs-pages-router 10 | npm install 11 | ``` 12 | 13 | ### Database Setup 14 | 15 | Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `.env` file. 16 | 17 | ```shell 18 | UPSTASH_REDIS_REST_URL= 19 | UPSTASH_REDIS_REST_TOKEN= 20 | ``` 21 | 22 | 23 | ### Run & Deploy 24 | Run the app locally with `npm run dev`, check `http://localhost:3000/` 25 | 26 | Deploy your app with `vercel` 27 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-pages-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.5", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.5", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse, 11 | ) { 12 | res.status(200).json({ name: "John Doe" }); 13 | } 14 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { InferGetServerSidePropsType, GetServerSideProps } from 'next' 2 | import { Redis } from "@upstash/redis"; 3 | 4 | const redis = Redis.fromEnv(); 5 | 6 | export const getServerSideProps = (async () => { 7 | const count = await redis.incr("counter"); 8 | return { props: { count } } 9 | }) satisfies GetServerSideProps<{ count: number }> 10 | 11 | export default function Home({ 12 | count, 13 | }: InferGetServerSidePropsType) { 14 | return ( 15 |
16 |

Counter: {count}

17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/nextjs-pages-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "paths": { 16 | "@/*": ["./*"] 17 | } 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/nodejs/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/nodejs/README.md: -------------------------------------------------------------------------------- 1 | # Node.js v20 Example 2 | 3 | ### Project Setup 4 | 5 | Clone the example and install dependencies 6 | 7 | ```shell 8 | git clone https://github.com/upstash/redis-js.git 9 | cd redis-js/examples/nodejs 10 | npm install 11 | ``` 12 | 13 | ### Database Setup 14 | 15 | Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) and copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` into your `.env` file. 16 | 17 | ```shell 18 | UPSTASH_REDIS_REST_URL= 19 | UPSTASH_REDIS_REST_TOKEN= 20 | ``` 21 | 22 | 23 | ### Run 24 | Run the script with `node index.js` 25 | -------------------------------------------------------------------------------- /examples/nodejs/index.js: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | async function run() { 6 | const count = await redis.incr("counter"); 7 | console.log("Counter:", count); 8 | } 9 | 10 | run(); -------------------------------------------------------------------------------- /examples/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@upstash/redis": "latest" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/serverless-framework/counter/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/serverless-framework/counter/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .env -------------------------------------------------------------------------------- /examples/serverless-framework/counter/handler.js: -------------------------------------------------------------------------------- 1 | const { Redis } = require('@upstash/redis'); 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | exports.counter = async (event) => { 6 | const count = await redis.incr("counter"); 7 | return { 8 | statusCode: 200, 9 | body: JSON.stringify('Counter: ' + count), 10 | }; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /examples/serverless-framework/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@upstash/redis": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/serverless-framework/counter/serverless.yml: -------------------------------------------------------------------------------- 1 | service: counter 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs20.x 6 | environment: 7 | UPSTASH_REDIS_REST_URL: ${env:UPSTASH_REDIS_REST_URL} 8 | UPSTASH_REDIS_REST_TOKEN: ${env:UPSTASH_REDIS_REST_TOKEN} 9 | 10 | functions: 11 | counter: 12 | handler: handler.counter 13 | events: 14 | - httpApi: 15 | path: / 16 | method: get 17 | -------------------------------------------------------------------------------- /examples/sst-v2/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # sst 5 | .sst 6 | .build 7 | 8 | # opennext 9 | .open-next 10 | 11 | # misc 12 | .DS_Store 13 | 14 | # local env files 15 | .env*.local 16 | -------------------------------------------------------------------------------- /examples/sst-v2/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug SST Start", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", 9 | "runtimeArgs": ["start", "--increase-timeout"], 10 | "console": "integratedTerminal", 11 | "skipFiles": ["/**"], 12 | "env": {} 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/sst-v2/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.sst": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/sst-v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sst-v2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "sst dev", 8 | "build": "sst build", 9 | "deploy": "sst deploy", 10 | "remove": "sst remove", 11 | "console": "sst console", 12 | "typecheck": "tsc --noEmit" 13 | }, 14 | "devDependencies": { 15 | "@tsconfig/node18": "^18.2.4", 16 | "aws-cdk-lib": "2.142.1", 17 | "constructs": "10.3.0", 18 | "sst": "^2.43.4", 19 | "typescript": "^5.5.4" 20 | }, 21 | "workspaces": [ 22 | "packages/*" 23 | ], 24 | "dependencies": { 25 | "@upstash/redis": "latest" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sst-v2/core", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "test": "sst bind vitest", 7 | "typecheck": "tsc -noEmit" 8 | }, 9 | "devDependencies": { 10 | "vitest": "^2.0.5", 11 | "@types/node": "^22.0.0", 12 | "sst": "^2.43.4" 13 | } 14 | } -------------------------------------------------------------------------------- /examples/sst-v2/packages/core/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sst-v2/functions", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "test": "sst bind vitest", 7 | "typecheck": "tsc -noEmit" 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^22.0.0", 11 | "@types/aws-lambda": "^8.10.142", 12 | "vitest": "^2.0.5", 13 | "sst": "^2.43.4" 14 | } 15 | } -------------------------------------------------------------------------------- /examples/sst-v2/packages/functions/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "baseUrl": ".", 7 | "paths": { 8 | "@sst-v2/core/*": ["../core/src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "sst bind next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^18", 13 | "react-dom": "^18", 14 | "next": "14.2.5" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "sst": "^2.43.4" 22 | } 23 | } -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | import { Config } from "sst/node/config"; 4 | 5 | const redis = new Redis({ 6 | url: Config.UPSTASH_REDIS_REST_URL, 7 | token: Config.UPSTASH_REDIS_REST_TOKEN, 8 | }); 9 | 10 | export default async function handler( 11 | req: NextApiRequest, 12 | res: NextApiResponse, 13 | ) { 14 | const count = await redis.incr("counter"); 15 | res.status(200).json({ count }); 16 | } -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/sst-v2/packages/web/public/favicon.ico -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/sst-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/sst-v2/packages/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true, 19 | "paths": { 20 | "@/*": [ 21 | "./*" 22 | ], 23 | "@sst-v2/core/*": [ 24 | "../core/src/*" 25 | ] 26 | } 27 | }, 28 | "include": [ 29 | "next-env.d.ts", 30 | "**/*.ts", 31 | "**/*.tsx" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } -------------------------------------------------------------------------------- /examples/sst-v2/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**/*" 3 | -------------------------------------------------------------------------------- /examples/sst-v2/sst.config.ts: -------------------------------------------------------------------------------- 1 | import { SSTConfig } from "sst"; 2 | import { Default } from "./stacks/Default"; 3 | 4 | export default { 5 | config(_input) { 6 | return { 7 | name: "sst-v2", 8 | region: "us-east-1", 9 | }; 10 | }, 11 | stacks(app) { 12 | app.stack(Default); 13 | } 14 | } satisfies SSTConfig; 15 | -------------------------------------------------------------------------------- /examples/sst-v2/stacks/Default.ts: -------------------------------------------------------------------------------- 1 | import { Config, StackContext, NextjsSite } from "sst/constructs"; 2 | 3 | export function Default({ stack }: StackContext) { 4 | const UPSTASH_REDIS_REST_URL = new Config.Secret(stack, "UPSTASH_REDIS_REST_URL"); 5 | const UPSTASH_REDIS_REST_TOKEN = new Config.Secret(stack, "UPSTASH_REDIS_REST_TOKEN"); 6 | const site = new NextjsSite(stack, "site", { 7 | bind: [UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN], 8 | path: "packages/web", 9 | }); 10 | stack.addOutputs({ 11 | SiteUrl: site.url, 12 | }); 13 | } -------------------------------------------------------------------------------- /examples/sst-v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "exclude": ["packages"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "moduleResolution": "node" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/terraform/counter/counter.js: -------------------------------------------------------------------------------- 1 | const { Redis } = require('@upstash/redis'); 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | module.exports.handler = async (event) => { 6 | const count = await redis.incr("counter"); 7 | return { 8 | statusCode: 200, 9 | body: JSON.stringify('Counter: ' + count), 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/terraform/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@upstash/redis": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "lambda_bucket_name" { 2 | description = "Name of the S3 bucket used to store function code." 3 | 4 | value = aws_s3_bucket.lambda_bucket.id 5 | } 6 | 7 | output "function_name" { 8 | description = "Name of the Lambda function." 9 | 10 | value = aws_lambda_function.counter.function_name 11 | } 12 | 13 | output "base_url" { 14 | description = "Base URL for API Gateway stage." 15 | 16 | value = aws_apigatewayv2_stage.lambda.invoke_url 17 | } 18 | -------------------------------------------------------------------------------- /examples/terraform/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.38.0" 6 | } 7 | random = { 8 | source = "hashicorp/random" 9 | version = "~> 3.6.0" 10 | } 11 | archive = { 12 | source = "hashicorp/archive" 13 | version = "~> 2.4.2" 14 | } 15 | } 16 | 17 | required_version = "~> 1.2" 18 | } -------------------------------------------------------------------------------- /examples/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region for all resources." 3 | 4 | type = string 5 | default = "us-east-1" 6 | } -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | import { NextResponse } from "next/server"; 3 | 4 | const redis = Redis.fromEnv(); 5 | 6 | export async function GET() { 7 | const count = await redis.incr("counter"); 8 | return NextResponse.json({ count }); 9 | } 10 | 11 | export const dynamic = 'force-dynamic' 12 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/ci.test.ts: -------------------------------------------------------------------------------- 1 | import {test,expect} from "bun:test" 2 | const deploymentURL = process.env.DEPLOYMENT_URL; 3 | if (!deploymentURL) { 4 | throw new Error("DEPLOYMENT_URL not set"); 5 | } 6 | 7 | test("works", async () => { 8 | console.log({ deploymentURL }); 9 | const url = `${deploymentURL}/api/hello`; 10 | const res = await fetch(url); 11 | expect(res.status).toEqual(200); 12 | const json = (await res.json()) as { count: number }; 13 | expect(typeof json.count).toEqual("number"); 14 | }); -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vercel-functions-app-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.5", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.5", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/vercel-functions-app-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/ci.test.ts: -------------------------------------------------------------------------------- 1 | import {test,expect} from "bun:test" 2 | const deploymentURL = process.env.DEPLOYMENT_URL; 3 | if (!deploymentURL) { 4 | throw new Error("DEPLOYMENT_URL not set"); 5 | } 6 | 7 | test("works", async () => { 8 | console.log({ deploymentURL }); 9 | const url = `${deploymentURL}/api/hello`; 10 | const res = await fetch(url); 11 | expect(res.status).toEqual(200); 12 | const json = (await res.json()) as { count: number }; 13 | expect(typeof json.count).toEqual("number"); 14 | }); -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vercel-functions-pages-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "latest", 13 | "next": "14.2.5", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.5", 23 | "postcss": "^8", 24 | "tailwindcss": "^3.4.1", 25 | "typescript": "^5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | const redis = Redis.fromEnv(); 5 | 6 | export default async function handler( 7 | req: NextApiRequest, 8 | res: NextApiResponse, 9 | ) { 10 | const count = await redis.incr("counter"); 11 | res.status(200).json({ count }); 12 | } 13 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /examples/vercel-functions-pages-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "paths": { 16 | "@/*": ["./*"] 17 | } 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/.env.example: -------------------------------------------------------------------------------- 1 | UPSTASH_REDIS_REST_URL= 2 | UPSTASH_REDIS_REST_TOKEN= -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | *.log 3 | *.pyc 4 | __pycache__ 5 | db.sqlite3 6 | media 7 | 8 | # Environments 9 | .env 10 | .venv 11 | env/ 12 | venv/ 13 | ENV/ 14 | env.bak/ 15 | venv.bak/ -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/vercel-python-runtime-django/api/__init__.py -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/api/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for api project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/api/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for api project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``app``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') 15 | 16 | app = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/redis-js/09f2eba6fd7ea2ed890386d10f7bef17d10e1769/examples/vercel-python-runtime-django/example/__init__.py -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/example/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/example/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ExampleConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'example' 7 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/example/urls.py: -------------------------------------------------------------------------------- 1 | # example/urls.py 2 | from django.urls import path 3 | 4 | from example.views import index 5 | 6 | 7 | urlpatterns = [ 8 | path('', index), 9 | ] -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/example/views.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.http import HttpResponse 4 | 5 | from upstash_redis import Redis 6 | 7 | redis = Redis.from_env() 8 | 9 | def index(request): 10 | count = redis.incr('counter') 11 | html = f''' 12 | 13 | 14 |

Counter: { count } 15 | 16 | 17 | ''' 18 | return HttpResponse(html) -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'api.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": "18.x" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==4.1.3 2 | upstash-redis -------------------------------------------------------------------------------- /examples/vercel-python-runtime-django/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "src": "/(.*)", 5 | "dest": "api/wsgi.py" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-sentry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-sentry", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Andreas Thomas", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@sentry/node": "^7.14.2", 14 | "@upstash/redis": "latest", 15 | "isomorphic-fetch": "^3.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/append.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/append 6 | */ 7 | export class AppendCommand extends Command { 8 | constructor(cmd: [key: string, value: string], opts?: CommandOptions) { 9 | super(["append", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/bitop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/bitop 6 | */ 7 | export class BitOpCommand extends Command { 8 | constructor( 9 | cmd: [op: "and" | "or" | "xor", destinationKey: string, ...sourceKeys: string[]], 10 | opts?: CommandOptions 11 | ); 12 | constructor( 13 | cmd: [op: "not", destinationKey: string, sourceKey: string], 14 | opts?: CommandOptions 15 | ); 16 | constructor( 17 | cmd: [op: "and" | "or" | "xor" | "not", destinationKey: string, ...sourceKeys: string[]], 18 | opts?: CommandOptions 19 | ) { 20 | super(["bitop", ...cmd], opts); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/commands/bitpos.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/bitpos 6 | */ 7 | export class BitPosCommand extends Command { 8 | constructor( 9 | cmd: [key: string, bit: 0 | 1, start?: number, end?: number], 10 | opts?: CommandOptions 11 | ) { 12 | super(["bitpos", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/command.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | import { Command } from "./command"; 3 | 4 | import { afterAll, describe, expect, test } from "bun:test"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | describe("deserialize large numbers", () => { 12 | test("returns the correct number", async () => { 13 | const key = newKey(); 14 | const field = randomID(); 15 | const value = "101600000000150081467"; 16 | 17 | await new Command(["hset", key, field, value]).exec(client); 18 | 19 | const res = await new Command(["hget", key, field]).exec(client); 20 | expect(res).toEqual(value); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /pkg/commands/copy.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/copy 6 | */ 7 | export class CopyCommand extends Command { 8 | constructor( 9 | [key, destinationKey, opts]: [key: string, destinationKey: string, opts?: { replace: boolean }], 10 | commandOptions?: CommandOptions 11 | ) { 12 | super(["COPY", key, destinationKey, ...(opts?.replace ? ["REPLACE"] : [])], { 13 | ...commandOptions, 14 | deserialize(result) { 15 | if (result > 0) { 16 | return "COPIED"; 17 | } 18 | return "NOT_COPIED"; 19 | }, 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/commands/dbsize.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { DBSizeCommand } from "./dbsize"; 5 | import { SetCommand } from "./set"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns the db size", async () => { 13 | const key = newKey(); 14 | const value = randomID(); 15 | await new SetCommand([key, value]).exec(client); 16 | const res = await new DBSizeCommand().exec(client); 17 | expect(res > 0).toEqual(true); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/dbsize.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/dbsize 6 | */ 7 | export class DBSizeCommand extends Command { 8 | constructor(opts?: CommandOptions) { 9 | super(["dbsize"], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/decr.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { DecrCommand } from "./decr"; 4 | import { SetCommand } from "./set"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | test("decrements a non-existing value", async () => { 11 | const key = newKey(); 12 | const res = await new DecrCommand([key]).exec(client); 13 | 14 | expect(res).toEqual(-1); 15 | }); 16 | 17 | test("decrements and existing value", async () => { 18 | const key = newKey(); 19 | await new SetCommand([key, 4]).exec(client); 20 | const res = await new DecrCommand([key]).exec(client); 21 | 22 | expect(res).toEqual(3); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/decr.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/decr 6 | */ 7 | export class DecrCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["decr", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/decrby.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient } from "../test-utils"; 2 | import { SetCommand } from "./set"; 3 | 4 | import { afterAll, expect, test } from "bun:test"; 5 | import { DecrByCommand } from "./decrby"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("decrements a non-existing value", async () => { 12 | const key = newKey(); 13 | const res = await new DecrByCommand([key, 2]).exec(client); 14 | 15 | expect(res).toEqual(-2); 16 | }); 17 | 18 | test("decrements and existing value", async () => { 19 | const key = newKey(); 20 | await new SetCommand([key, 5]).exec(client); 21 | const res = await new DecrByCommand([key, 2]).exec(client); 22 | 23 | expect(res).toEqual(3); 24 | }); 25 | -------------------------------------------------------------------------------- /pkg/commands/decrby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/decrby 6 | */ 7 | export class DecrByCommand extends Command { 8 | constructor(cmd: [key: string, decrement: number], opts?: CommandOptions) { 9 | super(["decrby", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/del.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/del 6 | */ 7 | export class DelCommand extends Command { 8 | constructor(cmd: [...keys: string[]], opts?: CommandOptions) { 9 | super(["del", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/echo.test.ts: -------------------------------------------------------------------------------- 1 | import { newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { expect, test } from "bun:test"; 4 | import { EchoCommand } from "./echo"; 5 | const client = newHttpClient(); 6 | 7 | test("returns the message", async () => { 8 | const message = randomID(); 9 | const res = await new EchoCommand([message]).exec(client); 10 | expect(res).toEqual(message); 11 | }); 12 | -------------------------------------------------------------------------------- /pkg/commands/echo.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/echo 6 | */ 7 | export class EchoCommand extends Command { 8 | constructor(cmd: [message: string], opts?: CommandOptions) { 9 | super(["echo", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/eval.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/eval 6 | */ 7 | export class EvalCommand extends Command { 8 | constructor( 9 | [script, keys, args]: [script: string, keys: string[], args: TArgs], 10 | opts?: CommandOptions 11 | ) { 12 | super(["eval", script, keys.length, ...keys, ...(args ?? [])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/evalRo.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/eval_ro 6 | */ 7 | export class EvalROCommand extends Command { 8 | constructor( 9 | [script, keys, args]: [script: string, keys: string[], args: TArgs], 10 | opts?: CommandOptions 11 | ) { 12 | super(["eval_ro", script, keys.length, ...keys, ...(args ?? [])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/evalsha.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, describe, expect, test } from "bun:test"; 4 | import { EvalshaCommand } from "./evalsha"; 5 | import { ScriptLoadCommand } from "./script_load"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | describe("without keys", () => { 13 | test("returns something", async () => { 14 | const value = randomID(); 15 | const sha1 = await new ScriptLoadCommand([`return {ARGV[1], "${value}"}`]).exec(client); 16 | const res = await new EvalshaCommand([sha1, [], [value]]).exec(client); 17 | expect(res).toEqual([value, value]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/evalsha.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/evalsha 6 | */ 7 | export class EvalshaCommand extends Command { 8 | constructor( 9 | [sha, keys, args]: [sha: string, keys: string[], args?: TArgs], 10 | opts?: CommandOptions 11 | ) { 12 | super(["evalsha", sha, keys.length, ...keys, ...(args ?? [])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/evalshaRo.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/evalsha_ro 6 | */ 7 | export class EvalshaROCommand extends Command { 8 | constructor( 9 | [sha, keys, args]: [sha: string, keys: string[], args?: TArgs], 10 | opts?: CommandOptions 11 | ) { 12 | super(["evalsha_ro", sha, keys.length, ...keys, ...(args ?? [])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/exists.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/exists 6 | */ 7 | export class ExistsCommand extends Command { 8 | constructor(cmd: [...keys: string[]], opts?: CommandOptions) { 9 | super(["exists", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/expire.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export type ExpireOption = "NX" | "nx" | "XX" | "xx" | "GT" | "gt" | "LT" | "lt"; 5 | 6 | export class ExpireCommand extends Command<"0" | "1", 0 | 1> { 7 | constructor( 8 | cmd: [key: string, seconds: number, option?: ExpireOption], 9 | opts?: CommandOptions<"0" | "1", 0 | 1> 10 | ) { 11 | super(["expire", ...cmd.filter(Boolean)], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/expireat.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | import type { ExpireOption } from "./expire"; 4 | 5 | /** 6 | * @see https://redis.io/commands/expireat 7 | */ 8 | export class ExpireAtCommand extends Command<"0" | "1", 0 | 1> { 9 | constructor( 10 | cmd: [key: string, unix: number, option?: ExpireOption], 11 | opts?: CommandOptions<"0" | "1", 0 | 1> 12 | ) { 13 | super(["expireat", ...cmd], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/flushall.test.ts: -------------------------------------------------------------------------------- 1 | import { newHttpClient } from "../test-utils"; 2 | import { FlushAllCommand } from "./flushall"; 3 | 4 | import { describe, expect, test } from "bun:test"; 5 | const client = newHttpClient(); 6 | 7 | describe("without options", () => { 8 | test("flushes the db", async () => { 9 | const res = await new FlushAllCommand().exec(client); 10 | expect(res).toEqual("OK"); 11 | }); 12 | }); 13 | describe("async", () => { 14 | test("flushes the db", async () => { 15 | const res = await new FlushAllCommand([{ async: true }]).exec(client); 16 | expect(res).toEqual("OK"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/flushall.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/flushall 5 | */ 6 | export class FlushAllCommand extends Command<"OK", "OK"> { 7 | constructor(args?: [{ async?: boolean }], opts?: CommandOptions<"OK", "OK">) { 8 | const command = ["flushall"]; 9 | if (args && args.length > 0 && args[0].async) { 10 | command.push("async"); 11 | } 12 | super(command, opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/flushdb.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "bun:test"; 2 | import { newHttpClient } from "../test-utils"; 3 | import { FlushDBCommand } from "./flushdb"; 4 | const client = newHttpClient(); 5 | 6 | describe("without options", () => { 7 | test("flushes the db", async () => { 8 | const res = await new FlushDBCommand([]).exec(client); 9 | expect(res).toEqual("OK"); 10 | }); 11 | }); 12 | describe("async", () => { 13 | test("flushes the db", async () => { 14 | const res = await new FlushDBCommand([{ async: true }]).exec(client); 15 | expect(res).toEqual("OK"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/flushdb.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/flushdb 5 | */ 6 | export class FlushDBCommand extends Command<"OK", "OK"> { 7 | constructor([opts]: [opts?: { async?: boolean }], cmdOpts?: CommandOptions<"OK", "OK">) { 8 | const command = ["flushdb"]; 9 | if (opts?.async) { 10 | command.push("async"); 11 | } 12 | super(command, cmdOpts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/geo_dist.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/geodist 6 | */ 7 | export class GeoDistCommand extends Command { 8 | constructor( 9 | [key, member1, member2, unit = "M"]: [ 10 | key: string, 11 | member1: TMemberType, 12 | member2: TMemberType, 13 | unit?: "M" | "KM" | "FT" | "MI", 14 | ], 15 | opts?: CommandOptions 16 | ) { 17 | super(["GEODIST", key, member1, member2, unit], opts); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/commands/get.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/get 6 | */ 7 | export class GetCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["get", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/getbit.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { SetBitCommand } from "./setbit"; 4 | 5 | import { GetBitCommand } from "./getbit"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the bit at offset", async () => { 12 | const key = newKey(); 13 | 14 | await new SetBitCommand([key, 0, 1]).exec(client); 15 | const res = await new GetBitCommand([key, 0]).exec(client); 16 | expect(res).toEqual(1); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/getbit.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/getbit 6 | */ 7 | export class GetBitCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor(cmd: [key: string, offset: number], opts?: CommandOptions<"0" | "1", 0 | 1>) { 9 | super(["getbit", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/getdel.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/getdel 6 | */ 7 | export class GetDelCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["getdel", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/getrange.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/getrange 6 | */ 7 | export class GetRangeCommand extends Command { 8 | constructor( 9 | cmd: [key: string, start: number, end: number], 10 | opts?: CommandOptions 11 | ) { 12 | super(["getrange", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/getset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/getset 6 | */ 7 | export class GetSetCommand extends Command { 8 | constructor( 9 | cmd: [key: string, value: TData], 10 | opts?: CommandOptions 11 | ) { 12 | super(["getset", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hdel.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hdel 6 | */ 7 | export class HDelCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor(cmd: [key: string, ...fields: string[]], opts?: CommandOptions<"0" | "1", 0 | 1>) { 9 | super(["hdel", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/hexists.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hexists 6 | */ 7 | export class HExistsCommand extends Command { 8 | constructor(cmd: [key: string, field: string], opts?: CommandOptions) { 9 | super(["hexists", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/hexpiretime.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class HExpireTimeCommand extends Command { 5 | constructor( 6 | cmd: [key: string, fields: (string | number) | (string | number)[]], 7 | opts?: CommandOptions 8 | ) { 9 | const [key, fields] = cmd; 10 | const fieldArray = Array.isArray(fields) ? fields : [fields]; 11 | super(["hexpiretime", key, "FIELDS", fieldArray.length, ...fieldArray], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/hget.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hget 6 | */ 7 | export class HGetCommand extends Command { 8 | constructor( 9 | cmd: [key: string, field: string], 10 | opts?: CommandOptions 11 | ) { 12 | super(["hget", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hincrby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hincrby 6 | */ 7 | export class HIncrByCommand extends Command { 8 | constructor( 9 | cmd: [key: string, field: string, increment: number], 10 | opts?: CommandOptions 11 | ) { 12 | super(["hincrby", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hincrbyfloat.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hincrbyfloat 6 | */ 7 | export class HIncrByFloatCommand extends Command { 8 | constructor( 9 | cmd: [key: string, field: string, increment: number], 10 | opts?: CommandOptions 11 | ) { 12 | super(["hincrbyfloat", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hkeys.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, describe, expect, test } from "bun:test"; 4 | import { HKeysCommand } from "./hkeys"; 5 | import { HMSetCommand } from "./hmset"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | describe("with existing hash", () => { 13 | test("returns all keys", async () => { 14 | const key = newKey(); 15 | const kv = { 16 | [randomID()]: randomID(), 17 | [randomID()]: randomID(), 18 | }; 19 | await new HMSetCommand([key, kv]).exec(client); 20 | const res = await new HKeysCommand([key]).exec(client); 21 | expect(res.sort()).toEqual(Object.keys(kv).sort()); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/hkeys.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hkeys 6 | */ 7 | export class HKeysCommand extends Command { 8 | constructor([key]: [key: string], opts?: CommandOptions) { 9 | super(["hkeys", key], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/hlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hlen 6 | */ 7 | export class HLenCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["hlen", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/hmset.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { HMGetCommand } from "./hmget"; 5 | import { HMSetCommand } from "./hmset"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | test("gets exiting values", async () => { 12 | const key = newKey(); 13 | const kv = { 14 | [randomID()]: randomID(), 15 | [randomID()]: randomID(), 16 | }; 17 | const res = await new HMSetCommand([key, kv]).exec(client); 18 | 19 | expect(res).toEqual("OK"); 20 | const res2 = await new HMGetCommand([key, ...Object.keys(kv)]).exec(client); 21 | 22 | expect(res2).toEqual(kv); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/hmset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hmset 6 | */ 7 | export class HMSetCommand extends Command<"OK", "OK"> { 8 | constructor( 9 | [key, kv]: [key: string, kv: Record], 10 | opts?: CommandOptions<"OK", "OK"> 11 | ) { 12 | super(["hmset", key, ...Object.entries(kv).flatMap(([field, value]) => [field, value])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hpersist.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class HPersistCommand extends Command<(-2 | -1 | 1)[], (-2 | -1 | 1)[]> { 5 | constructor( 6 | cmd: [key: string, fields: (string | number) | (string | number)[]], 7 | opts?: CommandOptions<(-2 | -1 | 1)[], (-2 | -1 | 1)[]> 8 | ) { 9 | const [key, fields] = cmd; 10 | const fieldArray = Array.isArray(fields) ? fields : [fields]; 11 | super(["hpersist", key, "FIELDS", fieldArray.length, ...fieldArray], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/hpexpiretime.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class HPExpireTimeCommand extends Command { 5 | constructor( 6 | cmd: [key: string, fields: (string | number) | (string | number)[]], 7 | opts?: CommandOptions 8 | ) { 9 | const [key, fields] = cmd; 10 | const fieldArray = Array.isArray(fields) ? fields : [fields]; 11 | super(["hpexpiretime", key, "FIELDS", fieldArray.length, ...fieldArray], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/hpttl.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class HPTtlCommand extends Command { 5 | constructor( 6 | cmd: [key: string, fields: (string | number) | (string | number)[]], 7 | opts?: CommandOptions 8 | ) { 9 | const [key, fields] = cmd; 10 | const fieldArray = Array.isArray(fields) ? fields : [fields]; 11 | super(["hpttl", key, "FIELDS", fieldArray.length, ...fieldArray], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/hset.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { HGetCommand } from "./hget"; 5 | import { HSetCommand } from "./hset"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | test("sets value", async () => { 11 | const key = newKey(); 12 | const field = randomID(); 13 | const value = randomID(); 14 | 15 | const res = await new HSetCommand([key, { [field]: value }]).exec(client); 16 | 17 | expect(res).toEqual(1); 18 | const res2 = await new HGetCommand([key, field]).exec(client); 19 | 20 | expect(res2).toEqual(value); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg/commands/hset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hset 6 | */ 7 | export class HSetCommand extends Command { 8 | constructor( 9 | [key, kv]: [key: string, kv: Record], 10 | opts?: CommandOptions 11 | ) { 12 | super(["hset", key, ...Object.entries(kv).flatMap(([field, value]) => [field, value])], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hsetnx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hsetnx 6 | */ 7 | export class HSetNXCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor( 9 | cmd: [key: string, field: string, value: TData], 10 | opts?: CommandOptions<"0" | "1", 0 | 1> 11 | ) { 12 | super(["hsetnx", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/hstrlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hstrlen 6 | */ 7 | export class HStrLenCommand extends Command { 8 | constructor(cmd: [key: string, field: string], opts?: CommandOptions) { 9 | super(["hstrlen", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/httl.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | import { afterAll, expect, test } from "bun:test"; 3 | import { HSetCommand } from "./hset"; 4 | import { HExpireCommand } from "./hexpire"; 5 | import { HTtlCommand } from "./httl"; 6 | 7 | const client = newHttpClient(); 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("retrieves the TTL of a hash key", async () => { 12 | const key = newKey(); 13 | const hashKey = newKey(); 14 | const value = randomID(); 15 | 16 | await new HSetCommand([key, { [hashKey]: value }]).exec(client); 17 | await new HExpireCommand([key, hashKey, 5]).exec(client); 18 | 19 | const res = await new HTtlCommand([key, hashKey]).exec(client); 20 | expect(res[0]).toBeGreaterThan(0); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg/commands/httl.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class HTtlCommand extends Command { 5 | constructor( 6 | cmd: [key: string, fields: (string | number) | (string | number)[]], 7 | opts?: CommandOptions 8 | ) { 9 | const [key, fields] = cmd; 10 | const fieldArray = Array.isArray(fields) ? fields : [fields]; 11 | super(["httl", key, "FIELDS", fieldArray.length, ...fieldArray], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/hvals.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | 5 | import { HSetCommand } from "./hset"; 6 | import { HValsCommand } from "./hvals"; 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns correct length", async () => { 13 | const key = newKey(); 14 | const field = randomID(); 15 | const value = randomID(); 16 | 17 | const res = await new HValsCommand([key]).exec(client); 18 | expect(res).toEqual([]); 19 | await new HSetCommand([key, { [field]: value }]).exec(client); 20 | 21 | const res2 = await new HValsCommand([key]).exec(client); 22 | 23 | expect(res2).toEqual([value]); 24 | }); 25 | -------------------------------------------------------------------------------- /pkg/commands/hvals.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/hvals 6 | */ 7 | export class HValsCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["hvals", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/incr.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { SetCommand } from "./set"; 4 | 5 | import { IncrCommand } from "./incr"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("increments a non-existing value", async () => { 12 | const key = newKey(); 13 | const res = await new IncrCommand([key]).exec(client); 14 | 15 | expect(res).toEqual(1); 16 | }); 17 | 18 | test("increments and existing value", async () => { 19 | const key = newKey(); 20 | await new SetCommand([key, 4]).exec(client); 21 | const res = await new IncrCommand([key]).exec(client); 22 | 23 | expect(res).toEqual(5); 24 | }); 25 | -------------------------------------------------------------------------------- /pkg/commands/incr.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/incr 6 | */ 7 | export class IncrCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["incr", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/incrby.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { IncrByCommand } from "./incrby"; 4 | 5 | import { SetCommand } from "./set"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("increments a non-existing value", async () => { 12 | const key = newKey(); 13 | const res = await new IncrByCommand([key, 2]).exec(client); 14 | 15 | expect(res).toEqual(2); 16 | }); 17 | 18 | test("increments and existing value", async () => { 19 | const key = newKey(); 20 | await new SetCommand([key, 5]).exec(client); 21 | const res = await new IncrByCommand([key, 2]).exec(client); 22 | 23 | expect(res).toEqual(7); 24 | }); 25 | -------------------------------------------------------------------------------- /pkg/commands/incrby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/incrby 6 | */ 7 | export class IncrByCommand extends Command { 8 | constructor(cmd: [key: string, value: number], opts?: CommandOptions) { 9 | super(["incrby", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/incrbyfloat.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/incrbyfloat 6 | */ 7 | export class IncrByFloatCommand extends Command { 8 | constructor(cmd: [key: string, value: number], opts?: CommandOptions) { 9 | super(["incrbyfloat", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_arrappend.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrappend 6 | */ 7 | export class JsonArrAppendCommand extends Command< 8 | (null | string)[], 9 | (null | number)[] 10 | > { 11 | constructor( 12 | cmd: [key: string, path: string, ...values: TData], 13 | opts?: CommandOptions<(null | string)[], (null | number)[]> 14 | ) { 15 | super(["JSON.ARRAPPEND", ...cmd], opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/json_arrindex.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrindex 6 | */ 7 | export class JsonArrIndexCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path: string, value: TValue, start?: number, stop?: number], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | super(["JSON.ARRINDEX", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_arrinsert.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrinsert 6 | */ 7 | export class JsonArrInsertCommand extends Command< 8 | (null | string)[], 9 | (null | number)[] 10 | > { 11 | constructor( 12 | cmd: [key: string, path: string, index: number, ...values: TData], 13 | opts?: CommandOptions<(null | string)[], (null | number)[]> 14 | ) { 15 | super(["JSON.ARRINSERT", ...cmd], opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/json_arrlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrlen 6 | */ 7 | export class JsonArrLenCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | super(["JSON.ARRLEN", cmd[0], cmd[1] ?? "$"], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_arrpop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrpop 6 | */ 7 | export class JsonArrPopCommand extends Command<(null | string)[], (TData | null)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string, index?: number], 10 | opts?: CommandOptions<(null | string)[], (TData | null)[]> 11 | ) { 12 | super(["JSON.ARRPOP", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_arrtrim.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.arrtrim 6 | */ 7 | export class JsonArrTrimCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string, start?: number, stop?: number], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | const path = cmd[1] ?? "$"; 13 | const start = cmd[2] ?? 0; 14 | const stop = cmd[3] ?? 0; 15 | 16 | super(["JSON.ARRTRIM", cmd[0], path, start, stop], opts); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/commands/json_clear.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.clear 6 | */ 7 | export class JsonClearCommand extends Command { 8 | constructor(cmd: [key: string, path?: string], opts?: CommandOptions) { 9 | super(["JSON.CLEAR", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_del.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.del 6 | */ 7 | export class JsonDelCommand extends Command { 8 | constructor(cmd: [key: string, path?: string], opts?: CommandOptions) { 9 | super(["JSON.DEL", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_forget.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.forget 6 | */ 7 | export class JsonForgetCommand extends Command { 8 | constructor(cmd: [key: string, path?: string], opts?: CommandOptions) { 9 | super(["JSON.FORGET", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_merge.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.merge 6 | */ 7 | export class JsonMergeCommand< 8 | TData extends string | number | Record | Array, 9 | > extends Command<"OK" | null, "OK" | null> { 10 | constructor( 11 | cmd: [key: string, path: string, value: TData], 12 | opts?: CommandOptions<"OK" | null, "OK" | null> 13 | ) { 14 | const command: unknown[] = ["JSON.MERGE", ...cmd]; 15 | super(command, opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/json_mget.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.mget 6 | */ 7 | export class JsonMGetCommand extends Command { 8 | constructor(cmd: [keys: string[], path: string], opts?: CommandOptions) { 9 | super(["JSON.MGET", ...cmd[0], cmd[1]], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_mset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.mset 6 | */ 7 | export class JsonMSetCommand< 8 | TData extends 9 | | number 10 | | string 11 | | boolean 12 | | Record 13 | | (number | string | boolean | Record)[], 14 | > extends Command<"OK" | null, "OK" | null> { 15 | constructor( 16 | cmd: { key: string; path: string; value: TData }[], 17 | opts?: CommandOptions<"OK" | null, "OK" | null> 18 | ) { 19 | const command: unknown[] = ["JSON.MSET"]; 20 | 21 | for (const c of cmd) { 22 | command.push(c.key, c.path, c.value); 23 | } 24 | super(command, opts); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/commands/json_numincrby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.numincrby 6 | */ 7 | export class JsonNumIncrByCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path: string, value: number], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | super(["JSON.NUMINCRBY", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_nummultby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.nummultby 6 | */ 7 | export class JsonNumMultByCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path: string, value: number], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | super(["JSON.NUMMULTBY", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_objkeys.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.objkeys 6 | */ 7 | export class JsonObjKeysCommand extends Command<(string[] | null)[], (string[] | null)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string], 10 | opts?: CommandOptions<(string[] | null)[], (string[] | null)[]> 11 | ) { 12 | super(["JSON.OBJKEYS", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_objlen.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | 4 | import { JsonSetCommand } from "./json_set"; 5 | 6 | import { JsonObjLenCommand } from "./json_objlen"; 7 | 8 | const client = newHttpClient(); 9 | 10 | const { newKey, cleanup } = keygen(); 11 | afterAll(cleanup); 12 | 13 | test("return the length", async () => { 14 | const key = newKey(); 15 | const res1 = await new JsonSetCommand([ 16 | key, 17 | "$", 18 | { 19 | a: [3], 20 | nested: { a: { b: 2, c: 1 } }, 21 | }, 22 | ]).exec(client); 23 | expect(res1).toBe("OK"); 24 | const res2 = await new JsonObjLenCommand([key, "$..a"]).exec(client); 25 | expect(res2).toEqual([2, null]); 26 | }); 27 | -------------------------------------------------------------------------------- /pkg/commands/json_objlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.objlen 6 | */ 7 | export class JsonObjLenCommand extends Command<(number | null)[], (number | null)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string], 10 | opts?: CommandOptions<(number | null)[], (number | null)[]> 11 | ) { 12 | super(["JSON.OBJLEN", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_resp.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.resp 6 | */ 7 | export class JsonRespCommand extends Command { 8 | constructor(cmd: [key: string, path?: string], opts?: CommandOptions) { 9 | super(["JSON.RESP", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_strappend.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.strappend 6 | */ 7 | export class JsonStrAppendCommand extends Command<(null | string)[], (null | number)[]> { 8 | constructor( 9 | cmd: [key: string, path: string, value: string], 10 | opts?: CommandOptions<(null | string)[], (null | number)[]> 11 | ) { 12 | super(["JSON.STRAPPEND", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_strlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.strlen 6 | */ 7 | export class JsonStrLenCommand extends Command<(number | null)[], (number | null)[]> { 8 | constructor( 9 | cmd: [key: string, path?: string], 10 | opts?: CommandOptions<(number | null)[], (number | null)[]> 11 | ) { 12 | super(["JSON.STRLEN", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/json_toggle.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.toggle 6 | */ 7 | export class JsonToggleCommand extends Command { 8 | constructor(cmd: [key: string, path: string], opts?: CommandOptions) { 9 | super(["JSON.TOGGLE", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/json_type.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/json.type 6 | */ 7 | export class JsonTypeCommand extends Command { 8 | constructor(cmd: [key: string, path?: string], opts?: CommandOptions) { 9 | super(["JSON.TYPE", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/keys.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { KeysCommand } from "./keys"; 4 | import { SetCommand } from "./set"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("when keys are found", () => { 12 | test("returns keys", async () => { 13 | const key = newKey(); 14 | await new SetCommand([key, "value"]).exec(client); 15 | const res = await new KeysCommand([key]).exec(client); 16 | expect(res).toEqual([key]); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/keys.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/keys 6 | */ 7 | export class KeysCommand extends Command { 8 | constructor(cmd: [pattern: string], opts?: CommandOptions) { 9 | super(["keys", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/lindex.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class LIndexCommand extends Command { 5 | constructor( 6 | cmd: [key: string, index: number], 7 | opts?: CommandOptions 8 | ) { 9 | super(["lindex", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/linsert.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | export class LInsertCommand extends Command { 4 | constructor( 5 | cmd: [key: string, direction: "before" | "after", pivot: TData, value: TData], 6 | opts?: CommandOptions 7 | ) { 8 | super(["linsert", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/llen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/llen 6 | */ 7 | export class LLenCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["llen", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/lmove.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/lmove 6 | */ 7 | export class LMoveCommand extends Command { 8 | constructor( 9 | cmd: [ 10 | source: string, 11 | destination: string, 12 | whereFrom: "left" | "right", 13 | whereTo: "left" | "right", 14 | ], 15 | opts?: CommandOptions 16 | ) { 17 | super(["lmove", ...cmd], opts); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/commands/lmpop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/lmpop 6 | */ 7 | export class LmPopCommand extends Command< 8 | [string, TValues[]] | null, 9 | [string, TValues[]] | null 10 | > { 11 | constructor( 12 | cmd: [numkeys: number, keys: string[], "LEFT" | "RIGHT", count?: number], 13 | opts?: CommandOptions<[string, TValues[]] | null, [string, TValues[]] | null> 14 | ) { 15 | const [numkeys, keys, direction, count] = cmd; 16 | 17 | super(["LMPOP", numkeys, ...keys, direction, ...(count ? ["COUNT", count] : [])], opts); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/commands/lpop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/lpop 6 | */ 7 | export class LPopCommand extends Command { 8 | constructor( 9 | cmd: [key: string, count?: number], 10 | opts?: CommandOptions 11 | ) { 12 | super(["lpop", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/lpush.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { LPushCommand } from "./lpush"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the length after command", async () => { 12 | const key = newKey(); 13 | const res = await new LPushCommand([key, randomID()]).exec(client); 14 | expect(res).toEqual(1); 15 | const res2 = await new LPushCommand([key, randomID(), randomID()]).exec(client); 16 | 17 | expect(res2).toEqual(3); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/lpush.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/lpush 6 | */ 7 | export class LPushCommand extends Command { 8 | constructor(cmd: [key: string, ...elements: TData[]], opts?: CommandOptions) { 9 | super(["lpush", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/lpushx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/lpushx 6 | */ 7 | export class LPushXCommand extends Command { 8 | constructor(cmd: [key: string, ...elements: TData[]], opts?: CommandOptions) { 9 | super(["lpushx", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/lrange.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { LRangeCommand } from "./lrange"; 5 | import { RPushCommand } from "./rpush"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the correct range", async () => { 12 | const key = newKey(); 13 | const value1 = randomID(); 14 | const value2 = randomID(); 15 | const value3 = randomID(); 16 | await new RPushCommand([key, value1, value2, value3]).exec(client); 17 | const res = await new LRangeCommand([key, 1, 2]).exec(client); 18 | expect(res.length).toBe(2); 19 | expect(res[0]).toBe(value2); 20 | expect(res[1]).toBe(value3); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg/commands/lrange.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class LRangeCommand extends Command { 5 | constructor( 6 | cmd: [key: string, start: number, end: number], 7 | opts?: CommandOptions 8 | ) { 9 | super(["lrange", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/lrem.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | 4 | import { LPushCommand } from "./lpush"; 5 | import { LRemCommand } from "./lrem"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the number of deleted elements", async () => { 12 | const key = newKey(); 13 | await new LPushCommand([key, "element"]).exec(client); 14 | await new LPushCommand([key, "element"]).exec(client); 15 | await new LPushCommand([key, "something else"]).exec(client); 16 | 17 | const res = await new LRemCommand([key, 2, "element"]).exec(client); 18 | expect(res).toEqual(2); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/lrem.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | export class LRemCommand extends Command { 4 | constructor( 5 | cmd: [key: string, count: number, value: TData], 6 | opts?: CommandOptions 7 | ) { 8 | super(["lrem", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/lset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class LSetCommand extends Command<"OK", "OK"> { 5 | constructor(cmd: [key: string, index: number, data: TData], opts?: CommandOptions<"OK", "OK">) { 6 | super(["lset", ...cmd], opts); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg/commands/ltrim.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export class LTrimCommand extends Command<"OK", "OK"> { 5 | constructor(cmd: [key: string, start: number, end: number], opts?: CommandOptions<"OK", "OK">) { 6 | super(["ltrim", ...cmd], opts); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg/commands/mget.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/mget 5 | */ 6 | export class MGetCommand extends Command<(string | null)[], TData> { 7 | constructor(cmd: [string[]] | [...string[]], opts?: CommandOptions<(string | null)[], TData>) { 8 | const keys = Array.isArray(cmd[0]) ? cmd[0] : (cmd as string[]); 9 | super(["mget", ...keys], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/mset.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { MGetCommand } from "./mget"; 5 | import { MSetCommand } from "./mset"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("gets exiting values", async () => { 13 | const key1 = newKey(); 14 | const key2 = newKey(); 15 | const kv = { 16 | [key1]: randomID(), 17 | [key2]: randomID(), 18 | }; 19 | const res = await new MSetCommand([kv]).exec(client); 20 | 21 | expect(res).toEqual("OK"); 22 | const res2 = await new MGetCommand([key1, key2]).exec(client); 23 | expect(res2).toEqual(Object.values(kv)); 24 | }); 25 | -------------------------------------------------------------------------------- /pkg/commands/mset.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/mset 6 | */ 7 | export class MSetCommand extends Command<"OK", "OK"> { 8 | constructor([kv]: [kv: Record], opts?: CommandOptions<"OK", "OK">) { 9 | super(["mset", ...Object.entries(kv).flatMap(([key, value]) => [key, value])], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/msetnx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/msetnx 6 | */ 7 | export class MSetNXCommand extends Command { 8 | constructor([kv]: [kv: Record], opts?: CommandOptions) { 9 | super(["msetnx", ...Object.entries(kv).flat()], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/persist.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/persist 6 | */ 7 | export class PersistCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor(cmd: [key: string], opts?: CommandOptions<"0" | "1", 0 | 1>) { 9 | super(["persist", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/pexpire.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | import type { ExpireOption } from "./expire"; 4 | 5 | /** 6 | * @see https://redis.io/commands/pexpire 7 | */ 8 | export class PExpireCommand extends Command<"0" | "1", 0 | 1> { 9 | constructor( 10 | cmd: [key: string, milliseconds: number, option?: ExpireOption], 11 | opts?: CommandOptions<"0" | "1", 0 | 1> 12 | ) { 13 | super(["pexpire", ...cmd], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/pexpireat.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | import type { ExpireOption } from "./expire"; 4 | 5 | /** 6 | * @see https://redis.io/commands/pexpireat 7 | */ 8 | export class PExpireAtCommand extends Command<"0" | "1", 0 | 1> { 9 | constructor( 10 | cmd: [key: string, unix: number, option?: ExpireOption], 11 | opts?: CommandOptions<"0" | "1", 0 | 1> 12 | ) { 13 | super(["pexpireat", ...cmd], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/pfadd.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command.ts"; 2 | import { Command } from "./command.ts"; 3 | 4 | /** 5 | * @see https://redis.io/commands/pfadd 6 | */ 7 | export class PfAddCommand extends Command { 8 | constructor(cmd: [string, ...TData[]], opts?: CommandOptions) { 9 | super(["pfadd", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/pfcount.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command.ts"; 2 | import { Command } from "./command.ts"; 3 | 4 | /** 5 | * @see https://redis.io/commands/pfcount 6 | */ 7 | export class PfCountCommand extends Command { 8 | constructor(cmd: [string, ...string[]], opts?: CommandOptions) { 9 | super(["pfcount", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/pfmerge.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command.ts"; 2 | import { Command } from "./command.ts"; 3 | 4 | /** 5 | * @see https://redis.io/commands/pfmerge 6 | */ 7 | export class PfMergeCommand extends Command<"OK", "OK"> { 8 | constructor(cmd: [destination_key: string, ...string[]], opts?: CommandOptions<"OK", "OK">) { 9 | super(["pfmerge", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/ping.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "bun:test"; 2 | import { newHttpClient, randomID } from "../test-utils"; 3 | import { PingCommand } from "./ping"; 4 | 5 | const client = newHttpClient(); 6 | 7 | describe("with message", () => { 8 | test("returns the message", async () => { 9 | const message = randomID(); 10 | const res = await new PingCommand([message]).exec(client); 11 | expect(res).toEqual(message); 12 | }); 13 | }); 14 | describe("without message", () => { 15 | test("returns pong", async () => { 16 | const res = await new PingCommand([]).exec(client); 17 | expect(res).toEqual("PONG"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/ping 5 | */ 6 | export class PingCommand extends Command { 7 | constructor(cmd?: [message?: string], opts?: CommandOptions) { 8 | const command: string[] = ["ping"]; 9 | if (cmd?.[0] !== undefined) { 10 | command.push(cmd[0]); 11 | } 12 | super(command, opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/psetex.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | 5 | import { GetCommand } from "./get"; 6 | import { PSetEXCommand } from "./psetex"; 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("sets value", async () => { 13 | const key = newKey(); 14 | const value = randomID(); 15 | 16 | const res = await new PSetEXCommand([key, 1000, value]).exec(client); 17 | 18 | expect(res).toEqual("OK"); 19 | await new Promise((res) => setTimeout(res, 2000)); 20 | const res2 = await new GetCommand([key]).exec(client); 21 | 22 | expect(res2).toEqual(null); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/psetex.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/psetex 6 | */ 7 | export class PSetEXCommand extends Command { 8 | constructor( 9 | cmd: [key: string, ttl: number, value: TData], 10 | opts?: CommandOptions 11 | ) { 12 | super(["psetex", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/psubscribe.ts: -------------------------------------------------------------------------------- 1 | import { Command, type CommandOptions } from "./command"; 2 | 3 | /** 4 | * @see https://redis.io/commands/psubscribe 5 | */ 6 | export class PSubscribeCommand extends Command { 7 | constructor(cmd: [...patterns: string[]], opts?: CommandOptions) { 8 | const sseHeaders = { 9 | Accept: "text/event-stream", 10 | "Cache-Control": "no-cache", 11 | Connection: "keep-alive", 12 | }; 13 | 14 | super([], { 15 | ...opts, 16 | headers: sseHeaders, 17 | path: ["psubscribe", ...cmd], 18 | streamOptions: { 19 | isStreaming: true, 20 | onMessage: opts?.streamOptions?.onMessage, 21 | signal: opts?.streamOptions?.signal, 22 | }, 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/commands/pttl.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient } from "../test-utils"; 2 | import { PTtlCommand } from "./pttl"; 3 | 4 | import { afterAll, expect, test } from "bun:test"; 5 | import { SetExCommand } from "./setex"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns the ttl on a key", async () => { 13 | const key = newKey(); 14 | const ttl = 60; 15 | await new SetExCommand([key, ttl, "value"]).exec(client); 16 | const res = await new PTtlCommand([key]).exec(client); 17 | expect(res <= ttl * 1000).toBe(true); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/pttl.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/pttl 6 | */ 7 | export class PTtlCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["pttl", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/publish.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | import { newHttpClient } from "../test-utils"; 3 | 4 | import { PublishCommand } from "./publish"; 5 | 6 | const client = newHttpClient(); 7 | 8 | test("returns the number of clients that received the message", async () => { 9 | const res = await new PublishCommand(["channel", "hello"]).exec(client); 10 | 11 | expect(typeof res).toBe("number"); 12 | }); 13 | -------------------------------------------------------------------------------- /pkg/commands/publish.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/publish 6 | */ 7 | export class PublishCommand extends Command { 8 | constructor(cmd: [channel: string, message: TMessage], opts?: CommandOptions) { 9 | super(["publish", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/randomkey.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { RandomKeyCommand } from "./randomkey"; 5 | import { SetCommand } from "./set"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns a random key", async () => { 12 | const key = newKey(); 13 | await new SetCommand([key, randomID()]).exec(client); 14 | const res = await new RandomKeyCommand().exec(client); 15 | expect(typeof res).toBe("string"); 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/commands/randomkey.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/randomkey 6 | */ 7 | export class RandomKeyCommand extends Command { 8 | constructor(opts?: CommandOptions) { 9 | super(["randomkey"], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/rename.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | 5 | import { RenameCommand } from "./rename"; 6 | import { SetCommand } from "./set"; 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("renames the key", async () => { 13 | const source = newKey(); 14 | const destination = newKey(); 15 | const value = randomID(); 16 | await new SetCommand([source, value]).exec(client); 17 | const res = await new RenameCommand([source, destination]).exec(client); 18 | expect(res).toEqual("OK"); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/rename.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/rename 6 | */ 7 | export class RenameCommand extends Command<"OK", "OK"> { 8 | constructor(cmd: [source: string, destination: string], opts?: CommandOptions<"OK", "OK">) { 9 | super(["rename", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/renamenx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/renamenx 6 | */ 7 | export class RenameNXCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor(cmd: [source: string, destination: string], opts?: CommandOptions<"0" | "1", 0 | 1>) { 9 | super(["renamenx", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/rpop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/rpop 6 | */ 7 | export class RPopCommand extends Command< 8 | unknown | null, 9 | TData | null 10 | > { 11 | constructor( 12 | cmd: [key: string, count?: number], 13 | opts?: CommandOptions 14 | ) { 15 | super(["rpop", ...cmd], opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/rpush.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { RPushCommand } from "./rpush"; 5 | const client = newHttpClient(); 6 | 7 | const { newKey, cleanup } = keygen(); 8 | afterAll(cleanup); 9 | 10 | test("returns the length after command", async () => { 11 | const key = newKey(); 12 | const res = await new RPushCommand([key, randomID()]).exec(client); 13 | expect(res).toEqual(1); 14 | const res2 = await new RPushCommand([key, randomID(), randomID()]).exec(client); 15 | 16 | expect(res2).toEqual(3); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/rpush.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/rpush 6 | */ 7 | export class RPushCommand extends Command { 8 | constructor(cmd: [key: string, ...elements: TData[]], opts?: CommandOptions) { 9 | super(["rpush", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/rpushx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/rpushx 6 | */ 7 | export class RPushXCommand extends Command { 8 | constructor(cmd: [key: string, ...elements: TData[]], opts?: CommandOptions) { 9 | super(["rpushx", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/sadd.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/sadd 6 | */ 7 | export class SAddCommand extends Command { 8 | constructor( 9 | cmd: [key: string, member: TData, ...members: TData[]], 10 | opts?: CommandOptions 11 | ) { 12 | super(["sadd", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/scard.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { SAddCommand } from "./sadd"; 4 | 5 | import { SCardCommand } from "./scard"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the cardinality", async () => { 12 | const key = newKey(); 13 | await new SAddCommand([key, "member1"]).exec(client); 14 | const res = await new SCardCommand([key]).exec(client); 15 | expect(res).toEqual(1); 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/commands/scard.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/scard 5 | */ 6 | export class SCardCommand extends Command { 7 | constructor(cmd: [key: string], opts?: CommandOptions) { 8 | super(["scard", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/script_exists.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/script-exists 6 | */ 7 | export class ScriptExistsCommand extends Command { 8 | constructor(hashes: T, opts?: CommandOptions) { 9 | super(["script", "exists", ...hashes], { 10 | deserialize: (result) => result as unknown as number[], 11 | ...opts, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/script_flush.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export type ScriptFlushCommandOptions = 5 | | { sync: true; async?: never } 6 | | { sync?: never; async: true }; 7 | 8 | /** 9 | * @see https://redis.io/commands/script-flush 10 | */ 11 | export class ScriptFlushCommand extends Command<"OK", "OK"> { 12 | constructor([opts]: [opts?: ScriptFlushCommandOptions], cmdOpts?: CommandOptions<"OK", "OK">) { 13 | const cmd = ["script", "flush"]; 14 | if (opts?.sync) { 15 | cmd.push("sync"); 16 | } else if (opts?.async) { 17 | cmd.push("async"); 18 | } 19 | super(cmd, cmdOpts); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/commands/script_load.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | import { newHttpClient } from "../test-utils"; 3 | import { ScriptLoadCommand } from "./script_load"; 4 | 5 | const client = newHttpClient(); 6 | 7 | test("returns the hash", async () => { 8 | const script = "return ARGV[1]"; 9 | const res = await new ScriptLoadCommand([script]).exec(client); 10 | expect(res).toEqual("098e0f0d1448c0a81dafe820f66d460eb09263da"); 11 | }); 12 | -------------------------------------------------------------------------------- /pkg/commands/script_load.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/script-load 6 | */ 7 | export class ScriptLoadCommand extends Command { 8 | constructor(args: [script: string], opts?: CommandOptions) { 9 | super(["script", "load", ...args], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/sdiff.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SAddCommand } from "./sadd"; 5 | import { SDiffCommand } from "./sdiff"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the diff", async () => { 12 | const key1 = newKey(); 13 | const member1 = randomID(); 14 | const key2 = newKey(); 15 | const member2 = randomID(); 16 | await new SAddCommand([key1, member1]).exec(client); 17 | await new SAddCommand([key2, member2]).exec(client); 18 | const res = await new SDiffCommand([key1, key2]).exec(client); 19 | expect(res).toEqual([member1]); 20 | }); 21 | -------------------------------------------------------------------------------- /pkg/commands/sdiff.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/sdiff 5 | */ 6 | export class SDiffCommand extends Command { 7 | constructor(cmd: [key: string, ...keys: string[]], opts?: CommandOptions) { 8 | super(["sdiff", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/sdiffstore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/sdiffstore 5 | */ 6 | export class SDiffStoreCommand extends Command { 7 | constructor( 8 | cmd: [destination: string, ...keys: string[]], 9 | opts?: CommandOptions 10 | ) { 11 | super(["sdiffstore", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/setbit.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SetBitCommand } from "./setbit"; 5 | const client = newHttpClient(); 6 | 7 | const { newKey, cleanup } = keygen(); 8 | afterAll(cleanup); 9 | 10 | test("returns the original bit", async () => { 11 | const key = newKey(); 12 | const res = await new SetBitCommand([key, 0, 1]).exec(client); 13 | expect(res).toEqual(0); 14 | const res2 = await new SetBitCommand([key, 0, 1]).exec(client); 15 | 16 | expect(res2).toEqual(1); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/setbit.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/setbit 5 | */ 6 | 7 | export class SetBitCommand extends Command<"0" | "1", 0 | 1> { 8 | constructor( 9 | cmd: [key: string, offset: number, value: 0 | 1], 10 | opts?: CommandOptions<"0" | "1", 0 | 1> 11 | ) { 12 | super(["setbit", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/setex.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { GetCommand } from "./get"; 5 | import { SetExCommand } from "./setex"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | test("sets value", async () => { 12 | const key = newKey(); 13 | const value = randomID(); 14 | 15 | const res = await new SetExCommand([key, 1, value]).exec(client); 16 | 17 | expect(res).toEqual("OK"); 18 | await new Promise((res) => setTimeout(res, 2000)); 19 | const res2 = await new GetCommand([key]).exec(client); 20 | 21 | expect(res2).toEqual(null); 22 | }); 23 | -------------------------------------------------------------------------------- /pkg/commands/setex.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/setex 6 | */ 7 | export class SetExCommand extends Command<"OK", "OK"> { 8 | constructor(cmd: [key: string, ttl: number, value: TData], opts?: CommandOptions<"OK", "OK">) { 9 | super(["setex", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/setnx.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/setnx 6 | */ 7 | export class SetNxCommand extends Command { 8 | constructor(cmd: [key: string, value: TData], opts?: CommandOptions) { 9 | super(["setnx", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/setrange.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/setrange 6 | */ 7 | export class SetRangeCommand extends Command { 8 | constructor( 9 | cmd: [key: string, offset: number, value: string], 10 | opts?: CommandOptions 11 | ) { 12 | super(["setrange", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/sinter.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/sinter 5 | */ 6 | export class SInterCommand extends Command { 7 | constructor(cmd: [key: string, ...keys: string[]], opts?: CommandOptions) { 8 | super(["sinter", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/sinterstore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/sinterstore 5 | */ 6 | export class SInterStoreCommand extends Command { 7 | constructor( 8 | cmd: [destination: string, key: string, ...keys: string[]], 9 | opts?: CommandOptions 10 | ) { 11 | super(["sinterstore", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/sismember.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/sismember 5 | */ 6 | export class SIsMemberCommand extends Command<"0" | "1", 0 | 1> { 7 | constructor(cmd: [key: string, member: TData], opts?: CommandOptions<"0" | "1", 0 | 1>) { 8 | super(["sismember", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/smembers.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/smembers 6 | */ 7 | export class SMembersCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["smembers", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/smismember.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/smismember 5 | */ 6 | export class SMIsMemberCommand extends Command< 7 | ("0" | "1")[], 8 | (0 | 1)[] 9 | > { 10 | constructor( 11 | cmd: [key: string, members: TMembers], 12 | opts?: CommandOptions<("0" | "1")[], (0 | 1)[]> 13 | ) { 14 | super(["smismember", cmd[0], ...cmd[1]], opts); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/commands/smove.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SAddCommand } from "./sadd"; 5 | import { SMoveCommand } from "./smove"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("moves the member", async () => { 12 | const source = newKey(); 13 | const destination = newKey(); 14 | const member = randomID(); 15 | await new SAddCommand([source, member]).exec(client); 16 | const res = await new SMoveCommand([source, destination, member]).exec(client); 17 | expect(res).toEqual(1); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/smove.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/smove 5 | */ 6 | export class SMoveCommand extends Command<"0" | "1", 0 | 1> { 7 | constructor( 8 | cmd: [source: string, destination: string, member: TData], 9 | opts?: CommandOptions<"0" | "1", 0 | 1> 10 | ) { 11 | super(["smove", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/spop.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/spop 5 | */ 6 | export class SPopCommand extends Command { 7 | constructor( 8 | [key, count]: [key: string, count?: number], 9 | opts?: CommandOptions 10 | ) { 11 | const command: unknown[] = ["spop", key]; 12 | if (typeof count === "number") { 13 | command.push(count); 14 | } 15 | super(command, opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/srandmember.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/srandmember 5 | */ 6 | export class SRandMemberCommand extends Command { 7 | constructor( 8 | [key, count]: [key: string, count?: number], 9 | opts?: CommandOptions 10 | ) { 11 | const command: unknown[] = ["srandmember", key]; 12 | if (typeof count === "number") { 13 | command.push(count); 14 | } 15 | super(command, opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/srem.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SAddCommand } from "./sadd"; 5 | import { SRemCommand } from "./srem"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns the number of removed members", async () => { 13 | const key = newKey(); 14 | const value1 = randomID(); 15 | const value2 = randomID(); 16 | await new SAddCommand([key, value1, value2]).exec(client); 17 | const res = await new SRemCommand([key, value1]).exec(client); 18 | expect(res).toEqual(1); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/srem.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/srem 5 | */ 6 | export class SRemCommand extends Command { 7 | constructor(cmd: [key: string, ...members: TData[]], opts?: CommandOptions) { 8 | super(["srem", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/strlen.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { SetCommand } from "./set"; 4 | 5 | import { StrLenCommand } from "./strlen"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the correct length", async () => { 12 | const key = newKey(); 13 | const value = "abcd"; 14 | await new SetCommand([key, value]).exec(client); 15 | const res = await new StrLenCommand([key]).exec(client); 16 | expect(res).toEqual(value.length); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/strlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/strlen 6 | */ 7 | export class StrLenCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["strlen", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/sunion.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SAddCommand } from "./sadd"; 5 | import { SUnionCommand } from "./sunion"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the union", async () => { 12 | const key1 = newKey(); 13 | const key2 = newKey(); 14 | 15 | const member1 = randomID(); 16 | const member2 = randomID(); 17 | 18 | await new SAddCommand([key1, member1]).exec(client); 19 | await new SAddCommand([key2, member2]).exec(client); 20 | const res = await new SUnionCommand([key1, key2]).exec(client); 21 | expect(res.sort()).toEqual([member1, member2].sort()); 22 | }); 23 | -------------------------------------------------------------------------------- /pkg/commands/sunion.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/sunion 6 | */ 7 | export class SUnionCommand extends Command { 8 | constructor(cmd: [key: string, ...keys: string[]], opts?: CommandOptions) { 9 | super(["sunion", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/sunionstore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/sunionstore 6 | */ 7 | export class SUnionStoreCommand extends Command { 8 | constructor( 9 | cmd: [destination: string, key: string, ...keys: string[]], 10 | opts?: CommandOptions 11 | ) { 12 | super(["sunionstore", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/time.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | import { newHttpClient } from "../test-utils"; 3 | 4 | import { TimeCommand } from "./time"; 5 | const client = newHttpClient(); 6 | 7 | test("returns the time", async () => { 8 | const res = await new TimeCommand().exec(client); 9 | 10 | expect(typeof res[0]).toBe("number"); 11 | expect(typeof res[1]).toBe("number"); 12 | }); 13 | -------------------------------------------------------------------------------- /pkg/commands/time.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/time 5 | */ 6 | export class TimeCommand extends Command<[number, number], [number, number]> { 7 | constructor(opts?: CommandOptions<[number, number], [number, number]>) { 8 | super(["time"], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/touch.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { MSetCommand } from "./mset"; 5 | import { TouchCommand } from "./touch"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns the number of touched keys", async () => { 13 | const key1 = newKey(); 14 | const key2 = newKey(); 15 | const kv: Record = {}; 16 | kv[key1] = randomID(); 17 | kv[key2] = randomID(); 18 | await new MSetCommand([kv]).exec(client); 19 | const res = await new TouchCommand([key1, key2]).exec(client); 20 | expect(res).toEqual(2); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg/commands/touch.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/touch 6 | */ 7 | export class TouchCommand extends Command { 8 | constructor(cmd: [...keys: string[]], opts?: CommandOptions) { 9 | super(["touch", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/ttl.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { SetExCommand } from "./setex"; 5 | import { TtlCommand } from "./ttl"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the ttl on a key", async () => { 12 | const key = newKey(); 13 | const ttl = 60; 14 | await new SetExCommand([key, ttl, "value"]).exec(client); 15 | const res = await new TtlCommand([key]).exec(client); 16 | expect(res).toEqual(ttl); 17 | }); 18 | -------------------------------------------------------------------------------- /pkg/commands/ttl.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/ttl 6 | */ 7 | export class TtlCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["ttl", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/type.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | export type Type = "string" | "list" | "set" | "zset" | "hash" | "none"; 5 | /** 6 | * @see https://redis.io/commands/type 7 | */ 8 | export class TypeCommand extends Command { 9 | constructor(cmd: [key: string], opts?: CommandOptions) { 10 | super(["type", ...cmd], opts); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg/commands/unlink.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { MSetCommand } from "./mset"; 5 | import { UnlinkCommand } from "./unlink"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("unlinks the keys", async () => { 13 | const key1 = newKey(); 14 | const key2 = newKey(); 15 | const key3 = newKey(); 16 | const kv: Record = {}; 17 | kv[key1] = randomID(); 18 | kv[key2] = randomID(); 19 | await new MSetCommand([kv]).exec(client); 20 | const res = await new UnlinkCommand([key1, key2, key3]).exec(client); 21 | expect(res).toEqual(2); 22 | }); 23 | -------------------------------------------------------------------------------- /pkg/commands/unlink.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/unlink 6 | */ 7 | export class UnlinkCommand extends Command { 8 | constructor(cmd: [...keys: string[]], opts?: CommandOptions) { 9 | super(["unlink", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/xack.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/xack 6 | */ 7 | export class XAckCommand extends Command { 8 | constructor( 9 | [key, group, id]: [key: string, group: string, id: string | string[]], 10 | opts?: CommandOptions 11 | ) { 12 | const ids = Array.isArray(id) ? [...id] : [id]; 13 | super(["XACK", key, group, ...ids], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/xdel.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/xdel 6 | */ 7 | export class XDelCommand extends Command { 8 | constructor( 9 | [key, ids]: [key: string, ids: string[] | string], 10 | opts?: CommandOptions 11 | ) { 12 | const cmds = Array.isArray(ids) ? [...ids] : [ids]; 13 | super(["XDEL", key, ...cmds], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/xinfo.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | type XInfoCommands = 5 | | { 6 | type: "CONSUMERS"; 7 | group: string; 8 | } 9 | | { type: "GROUPS" }; 10 | 11 | /** 12 | * @see https://redis.io/commands/xinfo 13 | */ 14 | export class XInfoCommand extends Command { 15 | constructor( 16 | [key, options]: [key: string, options: XInfoCommands], 17 | opts?: CommandOptions 18 | ) { 19 | const cmds: unknown[] = []; 20 | if (options.type === "CONSUMERS") { 21 | cmds.push("CONSUMERS", key, options.group); 22 | } else { 23 | cmds.push("GROUPS", key); 24 | } 25 | super(["XINFO", ...cmds], opts); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/commands/xlen.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/xlen 6 | */ 7 | export class XLenCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["XLEN", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/xtrim.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/xtrim 6 | */ 7 | 8 | type XTrimOptions = { 9 | strategy: "MAXLEN" | "MINID"; 10 | exactness?: "~" | "="; 11 | threshold: number | string; 12 | limit?: number; 13 | }; 14 | 15 | export class XTrimCommand extends Command { 16 | constructor( 17 | [key, options]: [key: string, options: XTrimOptions], 18 | opts?: CommandOptions 19 | ) { 20 | const { limit, strategy, threshold, exactness = "~" } = options; 21 | 22 | super(["XTRIM", key, strategy, exactness, threshold, ...(limit ? ["LIMIT", limit] : [])], opts); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/commands/zcard.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { ZAddCommand } from "./zadd"; 4 | import { ZCardCommand } from "./zcard"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the cardinality", async () => { 12 | const key = newKey(); 13 | await new ZAddCommand([key, { score: 1, member: "member1" }]).exec(client); 14 | const res = await new ZCardCommand([key]).exec(client); 15 | expect(res).toEqual(1); 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/commands/zcard.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/zcard 6 | */ 7 | export class ZCardCommand extends Command { 8 | constructor(cmd: [key: string], opts?: CommandOptions) { 9 | super(["zcard", ...cmd], opts); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/commands/zcount.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { ZAddCommand } from "./zadd"; 4 | import { ZCountCommand } from "./zcount"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the cardinality", async () => { 12 | const key = newKey(); 13 | await new ZAddCommand([key, { score: 1, member: "member1" }]).exec(client); 14 | const res = await new ZCountCommand([key, 0, 2]).exec(client); 15 | expect(res).toEqual(1); 16 | }); 17 | -------------------------------------------------------------------------------- /pkg/commands/zcount.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zcount 5 | */ 6 | export class ZCountCommand extends Command { 7 | constructor( 8 | cmd: [key: string, min: number | string, max: number | string], 9 | opts?: CommandOptions 10 | ) { 11 | super(["zcount", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/zdiffstore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/zdiffstore 6 | */ 7 | export class ZDiffStoreCommand extends Command { 8 | constructor( 9 | cmd: [destination: string, numkeys: number, ...keys: string[]], 10 | opts?: CommandOptions 11 | ) { 12 | super(["zdiffstore", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/zincrby.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient, randomID } from "../test-utils"; 3 | import { ZIncrByCommand } from "./zincrby"; 4 | 5 | import { ZAddCommand } from "./zadd"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | test("increments and existing value", async () => { 12 | const key = newKey(); 13 | const score = 1; 14 | const member = randomID(); 15 | await new ZAddCommand([key, { score, member }]).exec(client); 16 | const res = await new ZIncrByCommand([key, 2, member]).exec(client); 17 | 18 | expect(res).toEqual(3); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/commands/zincrby.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zincrby 5 | */ 6 | export class ZIncrByCommand extends Command { 7 | constructor( 8 | cmd: [key: string, increment: number, member: TData], 9 | opts?: CommandOptions 10 | ) { 11 | super(["zincrby", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/zlexcount.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zlexcount 5 | */ 6 | export class ZLexCountCommand extends Command { 7 | constructor(cmd: [key: string, min: string, max: string], opts?: CommandOptions) { 8 | super(["zlexcount", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/zmscore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/zmscore 6 | */ 7 | export class ZMScoreCommand extends Command { 8 | constructor( 9 | cmd: [key: string, members: TData[]], 10 | opts?: CommandOptions 11 | ) { 12 | const [key, members] = cmd; 13 | super(["zmscore", key, ...members], opts); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/commands/zpopmax.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zpopmax 5 | */ 6 | export class ZPopMaxCommand extends Command { 7 | constructor( 8 | [key, count]: [key: string, count?: number], 9 | opts?: CommandOptions 10 | ) { 11 | const command: unknown[] = ["zpopmax", key]; 12 | if (typeof count === "number") { 13 | command.push(count); 14 | } 15 | super(command, opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/zpopmin.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zpopmin 5 | */ 6 | export class ZPopMinCommand extends Command { 7 | constructor( 8 | [key, count]: [key: string, count?: number], 9 | opts?: CommandOptions 10 | ) { 11 | const command: unknown[] = ["zpopmin", key]; 12 | if (typeof count === "number") { 13 | command.push(count); 14 | } 15 | super(command, opts); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/commands/zrank.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { ZAddCommand } from "./zadd"; 4 | import { ZRankCommand } from "./zrank"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the rank", async () => { 12 | const key = newKey(); 13 | 14 | await new ZAddCommand([ 15 | key, 16 | { score: 1, member: "member1" }, 17 | { score: 2, member: "member2" }, 18 | { score: 3, member: "member3" }, 19 | ]).exec(client); 20 | 21 | const res = await new ZRankCommand([key, "member2"]).exec(client); 22 | expect(res).toEqual(1); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/zrank.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zrank 5 | */ 6 | 7 | export class ZRankCommand extends Command { 8 | constructor( 9 | cmd: [key: string, member: TData], 10 | opts?: CommandOptions 11 | ) { 12 | super(["zrank", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/zrem.test.ts: -------------------------------------------------------------------------------- 1 | import { keygen, newHttpClient, randomID } from "../test-utils"; 2 | 3 | import { afterAll, expect, test } from "bun:test"; 4 | import { ZAddCommand } from "./zadd"; 5 | import { ZRemCommand } from "./zrem"; 6 | 7 | const client = newHttpClient(); 8 | 9 | const { newKey, cleanup } = keygen(); 10 | afterAll(cleanup); 11 | 12 | test("returns the number of removed members", async () => { 13 | const key = newKey(); 14 | const member1 = randomID(); 15 | const member2 = randomID(); 16 | await new ZAddCommand([key, { score: 1, member: member1 }, { score: 2, member: member2 }]).exec( 17 | client 18 | ); 19 | const res = await new ZRemCommand([key, member1, member2]).exec(client); 20 | expect(res).toEqual(2); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg/commands/zrem.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zrem 5 | */ 6 | export class ZRemCommand extends Command { 7 | constructor(cmd: [key: string, ...members: TData[]], opts?: CommandOptions) { 8 | super(["zrem", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/zremrangebylex.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zremrangebylex 5 | */ 6 | export class ZRemRangeByLexCommand extends Command { 7 | constructor(cmd: [key: string, min: string, max: string], opts?: CommandOptions) { 8 | super(["zremrangebylex", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/zremrangebyrank.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zremrangebyrank 5 | */ 6 | export class ZRemRangeByRankCommand extends Command { 7 | constructor( 8 | cmd: [key: string, start: number, stop: number], 9 | opts?: CommandOptions 10 | ) { 11 | super(["zremrangebyrank", ...cmd], opts); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/commands/zremrangebyscore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zremrangebyscore 5 | */ 6 | export class ZRemRangeByScoreCommand extends Command { 7 | constructor(cmd: [key: string, min: number, max: number], opts?: CommandOptions) { 8 | super(["zremrangebyscore", ...cmd], opts); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/commands/zrevrank.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient } from "../test-utils"; 3 | import { ZAddCommand } from "./zadd"; 4 | import { ZRevRankCommand } from "./zrevrank"; 5 | 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the rank", async () => { 12 | const key = newKey(); 13 | 14 | await new ZAddCommand([ 15 | key, 16 | { score: 1, member: "member1" }, 17 | { score: 2, member: "member2" }, 18 | { score: 3, member: "member3" }, 19 | ]).exec(client); 20 | 21 | const res = await new ZRevRankCommand([key, "member2"]).exec(client); 22 | expect(res).toEqual(1); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/commands/zrevrank.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | /** 4 | * @see https://redis.io/commands/zrevrank 5 | */ 6 | 7 | export class ZRevRankCommand extends Command { 8 | constructor( 9 | cmd: [key: string, member: TData], 10 | opts?: CommandOptions 11 | ) { 12 | super(["zrevrank", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/commands/zscore.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, expect, test } from "bun:test"; 2 | import { keygen, newHttpClient, randomID } from "../test-utils"; 3 | import { ZAddCommand } from "./zadd"; 4 | 5 | import { ZScoreCommand } from "./zscore"; 6 | const client = newHttpClient(); 7 | 8 | const { newKey, cleanup } = keygen(); 9 | afterAll(cleanup); 10 | 11 | test("returns the score", async () => { 12 | const key = newKey(); 13 | const member = randomID(); 14 | const score = Math.floor(Math.random() * 10); 15 | await new ZAddCommand([key, { score, member }]).exec(client); 16 | const res = await new ZScoreCommand([key, member]).exec(client); 17 | expect(res).toEqual(score); 18 | }); 19 | -------------------------------------------------------------------------------- /pkg/commands/zscore.ts: -------------------------------------------------------------------------------- 1 | import type { CommandOptions } from "./command"; 2 | import { Command } from "./command"; 3 | 4 | /** 5 | * @see https://redis.io/commands/zscore 6 | */ 7 | export class ZScoreCommand extends Command { 8 | constructor( 9 | cmd: [key: string, member: TData], 10 | opts?: CommandOptions 11 | ) { 12 | super(["zscore", ...cmd], opts); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Result of a bad request to upstash 3 | */ 4 | export class UpstashError extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = "UpstashError"; 8 | } 9 | } 10 | 11 | export class UrlError extends Error { 12 | constructor(url: string) { 13 | super( 14 | `Upstash Redis client was passed an invalid URL. You should pass a URL starting with https. Received: "${url}". ` 15 | ); 16 | this.name = "UrlError"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./error"; 2 | -------------------------------------------------------------------------------- /pkg/test-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | 3 | import { randomUnsafeIntegerString } from "./test-utils"; 4 | 5 | test("randomUnsafeIntegerString() should return a string", () => { 6 | const result = randomUnsafeIntegerString(); 7 | expect(typeof result).toEqual("string"); 8 | }); 9 | test("randomUnsafeIntegerString() should return different values", () => { 10 | const result1 = randomUnsafeIntegerString(); 11 | const result2 = randomUnsafeIntegerString(); 12 | expect(result1).not.toEqual(result2); 13 | }); 14 | test("randomUnsafeIntegerString() should return a string with unsafe integer", () => { 15 | const result = randomUnsafeIntegerString(); 16 | expect(Number.isSafeInteger(Number(result))).toBeFalse(); 17 | }); 18 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Config} 3 | */ 4 | const config = { 5 | endOfLine: "lf", 6 | singleQuote: false, 7 | tabWidth: 2, 8 | trailingComma: "es5", 9 | printWidth: 100, 10 | arrowParens: "always", 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext"], 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "bundler", 7 | "moduleDetection": "force", 8 | "allowImportingTsExtensions": true, 9 | "noEmit": true, 10 | "strict": true, 11 | "downlevelIteration": true, 12 | "skipLibCheck": true, 13 | "jsx": "react-jsx", 14 | "allowSyntheticDefaultImports": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowJs": true, 17 | "types": [ 18 | "bun-types" // add Bun global 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["platforms/nodejs.ts", "platforms/cloudflare.ts", "platforms/fastly.ts"], 5 | format: ["cjs", "esm"], 6 | clean: true, 7 | dts: true, 8 | }); 9 | -------------------------------------------------------------------------------- /version.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = "v1.30.2"; 2 | --------------------------------------------------------------------------------