├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── flake.lock ├── flake.nix ├── package-lock.json ├── package.json ├── packages.dhall ├── spago.dhall ├── src └── Database │ ├── Redis.js │ └── Redis.purs └── test └── Main.purs /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /node_modules 3 | /output 4 | .psc-ide-port 5 | .spago 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Tomasz Rybarczyk (aka paluh) 4 | Copyright (c) 2017, Tinker.Travel 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-redis-client 2 | 3 | Redis client library for PureScript. This library depends on the npm library 4 | ioredis. To use this library, you must manually install ioredis. 5 | 6 | ## API notes 7 | 8 | ### Sorted sets `score` values 9 | 10 | In case of sorted set methods which operate on `score` value (like `zrange`, `zadd` etc.) we are using `Int53` type to represent `score` values. However all functions which take `score` as an input accept values of all types which implement `Int53Value` instance. In other words you can pass just `Int` score values to them. 11 | 12 | ### Patched ioredis behavior 13 | 14 | For compatibility reasons with raw redis protocol this library mutates globaly response type of javascript `hgetall` method to: 15 | 16 | ``` purescript 17 | Array { key ∷ ByteString, value ∷ ByteString } 18 | ``` 19 | 20 | If you provide better way to handle this (custom method) I would happily merge it... 21 | 22 | ## Testing 23 | 24 | Please run `redis-server` on port 43210 with clear redis database - test suite refuse to run if db is no empty. 25 | 26 | Basic workflow can look like this: 27 | 28 | ```shell 29 | redis-server --port 43210 & 30 | ``` 31 | 32 | and running tests with db cleanup can be done with: 33 | 34 | ```shell 35 | echo flushdb | redis-cli -p 43210 | pulp test 36 | ``` 37 | 38 | ## Credits 39 | 40 | This library started as a fork of [`TinkerTravel/purescript-redis`](https://github.com/TinkerTravel/purescript-redis) which is not maintained any more. 41 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-redis-client", 3 | "license": [ 4 | "BSD-3-Clause" 5 | ], 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/paluh/purescript-redis-client.git" 9 | }, 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "output" 15 | ], 16 | "dependencies": { 17 | "purescript-aff": "^v6.0.0", 18 | "purescript-arrays": "^v6.0.1", 19 | "purescript-bytestrings": "^v8.0.0", 20 | "purescript-effect": "^v3.0.0", 21 | "purescript-foldable-traversable": "^v5.0.1", 22 | "purescript-int-53": "https://github.com/paluh/purescript-int-53.git#e4ebfe7edbf9f5275efec14e53d643b6cd505720", 23 | "purescript-maybe": "^v5.0.0", 24 | "purescript-nonempty": "^v6.0.0", 25 | "purescript-nullable": "^v5.0.0", 26 | "purescript-prelude": "^v5.0.1", 27 | "purescript-psci-support": "^v5.0.0", 28 | "purescript-test-unit": "^v16.0.0", 29 | "purescript-transformers": "^v5.2.0", 30 | "purescript-tuples": "^v6.0.1", 31 | "purescript-unsafe-coerce": "^v5.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "easyPSSrc": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1671011575, 7 | "narHash": "sha256-tESal32bcqqdZO+aKnBzc1GoL2mtnaDtj2y7ociCRGA=", 8 | "owner": "justinwoo", 9 | "repo": "easy-purescript-nix", 10 | "rev": "11d3bd58ce6e32703bf69cec04dc7c38eabe14ba", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "justinwoo", 15 | "repo": "easy-purescript-nix", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "locked": { 21 | "lastModified": 1644229661, 22 | "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", 23 | "owner": "numtide", 24 | "repo": "flake-utils", 25 | "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "type": "github" 32 | } 33 | }, 34 | "flakeUtils": { 35 | "inputs": { 36 | "flake-utils": "flake-utils" 37 | }, 38 | "locked": { 39 | "lastModified": 1657226504, 40 | "narHash": "sha256-GIYNjuq4mJlFgqKsZ+YrgzWm0IpA4axA3MCrdKYj7gs=", 41 | "owner": "gytis-ivaskevicius", 42 | "repo": "flake-utils-plus", 43 | "rev": "2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "gytis-ivaskevicius", 48 | "repo": "flake-utils-plus", 49 | "type": "github" 50 | } 51 | }, 52 | "nixpkgs": { 53 | "locked": { 54 | "lastModified": 1669809720, 55 | "narHash": "sha256-RMT77f6CPOYtLLQ2esj+EJ1BPVWxf4RDidjrSvA5OhI=", 56 | "owner": "nixos", 57 | "repo": "nixpkgs", 58 | "rev": "227de2b3bbec142f912c09d5e8a1b4e778aa54fb", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "nixos", 63 | "ref": "nixpkgs-unstable", 64 | "repo": "nixpkgs", 65 | "type": "github" 66 | } 67 | }, 68 | "root": { 69 | "inputs": { 70 | "easyPSSrc": "easyPSSrc", 71 | "flakeUtils": "flakeUtils", 72 | "nixpkgs": "nixpkgs" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | # nixConfig = { 3 | # bash-prompt-suffix = "[dev]"; 4 | # }; 5 | inputs = { 6 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 7 | flakeUtils.url = "github:gytis-ivaskevicius/flake-utils-plus"; 8 | easyPSSrc = { 9 | flake = false; 10 | url = "github:justinwoo/easy-purescript-nix"; 11 | }; 12 | }; 13 | 14 | outputs = { self, nixpkgs, flakeUtils, easyPSSrc }: 15 | flakeUtils.lib.eachSystem ["x86_64-linux"] (system: 16 | let 17 | pkgs = nixpkgs.legacyPackages.${system}; 18 | easyPS = pkgs.callPackage easyPSSrc { inherit pkgs; }; 19 | nodejs-16 = pkgs.writeShellScriptBin "nodejs-16" '' 20 | ${ pkgs.nodejs-16_x.out}/bin/node $@ 21 | ''; 22 | in { 23 | devShell = pkgs.mkShell { 24 | buildInputs = [ 25 | 26 | # Please update spago and purescript in `package.json` `scripts` section 27 | easyPS."purs-0_15_7" 28 | easyPS.purescript-language-server 29 | easyPS.pscid 30 | easyPS.purs-tidy 31 | easyPS.pulp 32 | easyPS.spago 33 | 34 | pkgs.jq 35 | pkgs.docker 36 | pkgs.nodePackages.bower 37 | pkgs.nodePackages.jshint 38 | pkgs.nodePackages.nodemon 39 | pkgs.nodePackages.yarn 40 | pkgs.nodePackages.webpack 41 | pkgs.nodePackages.webpack-cli 42 | pkgs.nodePackages.webpack-dev-server 43 | pkgs.dhall 44 | pkgs.nodejs-18_x 45 | nodejs-16 46 | pkgs.pkgconfig 47 | pkgs.postgresql 48 | pkgs.python27 49 | pkgs.python37 50 | pkgs.unzip 51 | pkgs.nixpacks 52 | ]; 53 | shellHook = '' 54 | npm install 55 | NODE_OPTIONS=--experimental-fetch --trace-warnings 56 | export PATH=$PATH:./node_modules/.bin/:./bin 57 | export PS1="\n\[\033[1;32m\][nix develop:\w]\$\[\033[0m\] "; 58 | ''; 59 | }; 60 | } 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-redis", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "purescript-redis", 8 | "dependencies": { 9 | "ioredis": "^2.5.0" 10 | }, 11 | "devDependencies": { 12 | "bower": "^1.8.2", 13 | "bower-update-all": "^0.1.2" 14 | } 15 | }, 16 | "node_modules/bluebird": { 17 | "version": "3.7.2", 18 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 19 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 20 | }, 21 | "node_modules/bower": { 22 | "version": "1.8.14", 23 | "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.14.tgz", 24 | "integrity": "sha512-8Rq058FD91q9Nwthyhw0la9fzpBz0iwZTrt51LWl+w+PnJgZk9J+5wp3nibsJcIUPglMYXr4NRBaR+TUj0OkBQ==", 25 | "dev": true, 26 | "bin": { 27 | "bower": "bin/bower" 28 | }, 29 | "engines": { 30 | "node": ">=0.10.0" 31 | } 32 | }, 33 | "node_modules/bower-update-all": { 34 | "version": "0.1.2", 35 | "resolved": "https://registry.npmjs.org/bower-update-all/-/bower-update-all-0.1.2.tgz", 36 | "integrity": "sha512-NoZWCWq4EY5bH2AZf6+OoOV3XquFCuV7AJ64cG8UaZX88IlrDpXEo1AprqGZaliz01RgmexK9aAcHZiwqUao6A==", 37 | "dev": true, 38 | "bin": { 39 | "bower-update-all": "bin/bower-update-all" 40 | } 41 | }, 42 | "node_modules/cluster-key-slot": { 43 | "version": "1.1.2", 44 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 45 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", 46 | "engines": { 47 | "node": ">=0.10.0" 48 | } 49 | }, 50 | "node_modules/debug": { 51 | "version": "2.6.9", 52 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 53 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 54 | "dependencies": { 55 | "ms": "2.0.0" 56 | } 57 | }, 58 | "node_modules/double-ended-queue": { 59 | "version": "2.1.0-0", 60 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 61 | "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" 62 | }, 63 | "node_modules/flexbuffer": { 64 | "version": "0.0.6", 65 | "resolved": "https://registry.npmjs.org/flexbuffer/-/flexbuffer-0.0.6.tgz", 66 | "integrity": "sha512-OapHwT5Ug1c3iiv8JNrwh/3XGjP45l/2iwfl25chePrvCgl0786ZvsJK+hxCCaUZjPm1lHdh5LHZhmKRxunziA==", 67 | "engines": { 68 | "node": "*" 69 | } 70 | }, 71 | "node_modules/ioredis": { 72 | "version": "2.5.0", 73 | "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-2.5.0.tgz", 74 | "integrity": "sha512-2jcmF1oVbNf8z+BJ8OmlwKsT+tqOJsJJuTh8qAX5Hwo0424E92A3qu9qcWM9vRhP0R4gCrJ9Y72QZwuVj8weMw==", 75 | "dependencies": { 76 | "bluebird": "^3.3.4", 77 | "cluster-key-slot": "^1.0.6", 78 | "debug": "^2.2.0", 79 | "double-ended-queue": "^2.1.0-0", 80 | "flexbuffer": "0.0.6", 81 | "lodash": "^4.8.2", 82 | "redis-commands": "^1.2.0", 83 | "redis-parser": "^1.3.0" 84 | }, 85 | "engines": { 86 | "iojs": ">= 1.0.0", 87 | "node": ">= 0.10.16" 88 | } 89 | }, 90 | "node_modules/lodash": { 91 | "version": "4.17.21", 92 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 93 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 94 | }, 95 | "node_modules/ms": { 96 | "version": "2.0.0", 97 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 98 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 99 | }, 100 | "node_modules/redis-commands": { 101 | "version": "1.7.0", 102 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", 103 | "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" 104 | }, 105 | "node_modules/redis-parser": { 106 | "version": "1.3.0", 107 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz", 108 | "integrity": "sha512-pPJS2ChF11wKm+YsHrRKON3m6MLGjBS+o9aXn6AjQqip+qMzk0zIG//jEagK+nKO5zrNljI1IdNV8JcvKrs0Iw==", 109 | "engines": { 110 | "node": ">=0.10.0" 111 | } 112 | } 113 | }, 114 | "dependencies": { 115 | "bluebird": { 116 | "version": "3.7.2", 117 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 118 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 119 | }, 120 | "bower": { 121 | "version": "1.8.14", 122 | "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.14.tgz", 123 | "integrity": "sha512-8Rq058FD91q9Nwthyhw0la9fzpBz0iwZTrt51LWl+w+PnJgZk9J+5wp3nibsJcIUPglMYXr4NRBaR+TUj0OkBQ==", 124 | "dev": true 125 | }, 126 | "bower-update-all": { 127 | "version": "0.1.2", 128 | "resolved": "https://registry.npmjs.org/bower-update-all/-/bower-update-all-0.1.2.tgz", 129 | "integrity": "sha512-NoZWCWq4EY5bH2AZf6+OoOV3XquFCuV7AJ64cG8UaZX88IlrDpXEo1AprqGZaliz01RgmexK9aAcHZiwqUao6A==", 130 | "dev": true 131 | }, 132 | "cluster-key-slot": { 133 | "version": "1.1.2", 134 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", 135 | "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" 136 | }, 137 | "debug": { 138 | "version": "2.6.9", 139 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 140 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 141 | "requires": { 142 | "ms": "2.0.0" 143 | } 144 | }, 145 | "double-ended-queue": { 146 | "version": "2.1.0-0", 147 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 148 | "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" 149 | }, 150 | "flexbuffer": { 151 | "version": "0.0.6", 152 | "resolved": "https://registry.npmjs.org/flexbuffer/-/flexbuffer-0.0.6.tgz", 153 | "integrity": "sha512-OapHwT5Ug1c3iiv8JNrwh/3XGjP45l/2iwfl25chePrvCgl0786ZvsJK+hxCCaUZjPm1lHdh5LHZhmKRxunziA==" 154 | }, 155 | "ioredis": { 156 | "version": "2.5.0", 157 | "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-2.5.0.tgz", 158 | "integrity": "sha512-2jcmF1oVbNf8z+BJ8OmlwKsT+tqOJsJJuTh8qAX5Hwo0424E92A3qu9qcWM9vRhP0R4gCrJ9Y72QZwuVj8weMw==", 159 | "requires": { 160 | "bluebird": "^3.3.4", 161 | "cluster-key-slot": "^1.0.6", 162 | "debug": "^2.2.0", 163 | "double-ended-queue": "^2.1.0-0", 164 | "flexbuffer": "0.0.6", 165 | "lodash": "^4.8.2", 166 | "redis-commands": "^1.2.0", 167 | "redis-parser": "^1.3.0" 168 | } 169 | }, 170 | "lodash": { 171 | "version": "4.17.21", 172 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 173 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 174 | }, 175 | "ms": { 176 | "version": "2.0.0", 177 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 178 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 179 | }, 180 | "redis-commands": { 181 | "version": "1.7.0", 182 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", 183 | "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" 184 | }, 185 | "redis-parser": { 186 | "version": "1.3.0", 187 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz", 188 | "integrity": "sha512-pPJS2ChF11wKm+YsHrRKON3m6MLGjBS+o9aXn6AjQqip+qMzk0zIG//jEagK+nKO5zrNljI1IdNV8JcvKrs0Iw==" 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-redis", 3 | "type": "module", 4 | "dependencies": { 5 | "ioredis": "^2.5.0" 6 | }, 7 | "devDependencies": { 8 | "bower": "^1.8.2", 9 | "bower-update-all": "^0.1.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let mkPackage = 2 | https://raw.githubusercontent.com/purescript/package-sets/psc-0.13.0-20190626/src/mkPackage.dhall 3 | sha256:0b197efa1d397ace6eb46b243ff2d73a3da5638d8d0ac8473e8e4a8fc528cf57 4 | 5 | let upstream = 6 | https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20221201/packages.dhall 7 | sha256:d1a68fa15709eaa686515eb5b9950d82c743f7bf73e3d87a4abe9e1be6fda571 8 | in upstream 9 | with 10 | int-53 = ../purescript-int-53/spago.dhall as Location 11 | with 12 | bytestrings = 13 | mkPackage 14 | [ "arrays", "console", "effect", "exceptions", "foldable-traversable" 15 | , "integers", "leibniz", "maybe", "newtype", "node-buffer", "partial" 16 | , "prelude", "quotient", "quickcheck", "quickcheck-laws", "unsafe-coerce" 17 | ] 18 | "https://github.com/rightfold/purescript-bytestrings.git" 19 | "6733a32fca306015b3428e9985ffac65325a9864" 20 | with quotient = 21 | mkPackage 22 | [ "prelude", "quickcheck" ] 23 | "https://github.com/rightfold/purescript-quotient.git" 24 | "v3.0.0" 25 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | {- 2 | Welcome to a Spago project! 3 | You can edit this file as you like. 4 | -} 5 | { name = "redis-client" 6 | , dependencies = 7 | [ "aff" 8 | , "arrays" 9 | , "bytestrings" 10 | , "effect" 11 | , "foldable-traversable" 12 | , "int-53" 13 | , "maybe" 14 | , "nonempty" 15 | , "nullable" 16 | , "prelude" 17 | , "test-unit" 18 | , "transformers" 19 | , "tuples" 20 | , "unsafe-coerce" 21 | ] 22 | , license = "BSD-3-Clause" 23 | , packages = ./packages.dhall 24 | , repository = "https://github.com/paluh/purescript-redis-client.git" 25 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 26 | } 27 | -------------------------------------------------------------------------------- /src/Database/Redis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ioredis from 'ioredis'; 4 | 5 | ioredis.Command.setReplyTransformer('hgetall', function (result) { 6 | var arr = []; 7 | for (var i = 0; i < result.length; i += 2) { 8 | arr.push({ key: result[i], value: result[i + 1] }); 9 | } 10 | return arr; 11 | }); 12 | 13 | export const _handleBlockingPopResult = function(onError, onSuccess) { 14 | return function(err, val) { 15 | if (err !== null) { 16 | onError(err); 17 | return; 18 | } 19 | if(val !== null) { 20 | var key = val[0], value = val[1]; 21 | onSuccess({key: key, value: value}); 22 | return; 23 | } 24 | onSuccess(null); 25 | }; 26 | }; 27 | 28 | export const blpopImpl = function(conn) { 29 | return function(keys) { 30 | return function(timeout) { 31 | return function(onError, onSuccess) { 32 | var handler = _handleBlockingPopResult(onError, onSuccess); 33 | conn.blpopBuffer.apply(conn, [keys, timeout, handler]); 34 | return function(cancelError, cancelerError, cancelerSuccess) { 35 | cancelError(); 36 | }; 37 | }; 38 | }; 39 | }; 40 | }; 41 | 42 | export const brpopImpl = function(conn) { 43 | return function(keys) { 44 | return function(timeout) { 45 | return function(onError, onSuccess) { 46 | var handler = _handleBlockingPopResult(onError, onSuccess); 47 | conn.brpopBuffer.apply(conn, [keys, timeout, handler]); 48 | return function(cancelError, cancelerError, cancelerSuccess) { 49 | cancelError(); 50 | }; 51 | }; 52 | }; 53 | }; 54 | }; 55 | 56 | export const connectImpl = function(connstr) { 57 | return function(onError, onSuccess) { 58 | var redis = new ioredis(connstr); 59 | redis.connect(function() { 60 | onSuccess(redis); 61 | }); 62 | return function(cancelError, cancelerError, cancelerSuccess) { 63 | cancelError(); 64 | }; 65 | }; 66 | }; 67 | 68 | export const disconnectImpl = function(conn) { 69 | return function(onError, onSuccess) { 70 | conn.disconnect(); 71 | onSuccess(); 72 | return function(cancelError, cancelerError, cancelerSuccess) { 73 | cancelError(); 74 | }; 75 | }; 76 | }; 77 | 78 | export const delImpl = function(conn) { 79 | return function(keys) { 80 | return function(onError, onSuccess) { 81 | if (keys.length === 0) { 82 | onSuccess(); 83 | } else { 84 | conn.del.apply(conn, keys.concat([function(err) { 85 | if (err !== null) { 86 | onError(err); 87 | return; 88 | } 89 | onSuccess(); 90 | }])); 91 | } 92 | return function(cancelError, cancelerError, cancelerSuccess) { 93 | cancelError(); 94 | }; 95 | }; 96 | }; 97 | }; 98 | 99 | export const flushdbImpl = function(conn) { 100 | return function(onError, onSuccess) { 101 | conn.flushdb(); 102 | onSuccess(); 103 | return function(cancelError, cancelerError, cancelerSuccess) { 104 | cancelError(); 105 | }; 106 | }; 107 | }; 108 | 109 | export const getImpl = function(conn) { 110 | return function(key) { 111 | return function(onError, onSuccess) { 112 | conn.getBuffer(key, function(err, value) { 113 | if (err !== null) { 114 | onError(err); 115 | return; 116 | } 117 | onSuccess(value); 118 | }); 119 | return function(cancelError, cancelerError, cancelerSuccess) { 120 | cancelError(); 121 | }; 122 | }; 123 | }; 124 | }; 125 | 126 | export const hgetallImpl = function(conn) { 127 | return function(key) { 128 | return function(onError, onSuccess) { 129 | conn.hgetallBuffer(key, function(err, value) { 130 | if (err !== null) { 131 | onError(err); 132 | return; 133 | } 134 | onSuccess(value); 135 | }); 136 | return function(cancelError, cancelerError, cancelerSuccess) { 137 | cancelError(); 138 | }; 139 | }; 140 | }; 141 | }; 142 | 143 | export const hgetImpl = function(conn) { 144 | return function(key) { 145 | return function(field) { 146 | return function(onError, onSuccess) { 147 | conn.hgetBuffer(key, field, function(err, value) { 148 | if (err !== null) { 149 | onError(err); 150 | return; 151 | } 152 | onSuccess(value); 153 | }); 154 | return function(cancelError, cancelerError, cancelerSuccess) { 155 | cancelError(); 156 | }; 157 | }; 158 | }; 159 | }; 160 | }; 161 | 162 | export const hsetImpl = function(conn) { 163 | return function(key) { 164 | return function(field) { 165 | return function(value) { 166 | return function(onError, onSuccess) { 167 | conn.hsetBuffer(key, field, value, function(err, value) { 168 | if (err !== null) { 169 | onError(err); 170 | return; 171 | } 172 | onSuccess(parseInt(value)); 173 | }); 174 | return function(cancelError, cancelerError, cancelerSuccess) { 175 | cancelError(); 176 | }; 177 | }; 178 | }; 179 | }; 180 | }; 181 | }; 182 | 183 | export const incrImpl = function(conn) { 184 | return function(key) { 185 | return function(onError, onSuccess) { 186 | conn.incr(key, function(err, value) { 187 | if (err !== null) { 188 | onError(err); 189 | return; 190 | } 191 | onSuccess(value); 192 | }); 193 | return function(cancelError, cancelerError, cancelerSuccess) { 194 | cancelError(); 195 | }; 196 | }; 197 | }; 198 | }; 199 | 200 | export const keysImpl = function(conn) { 201 | return function(pattern) { 202 | return function(onError, onSuccess) { 203 | conn.keysBuffer(pattern, function(err, value) { 204 | if (err !== null) { 205 | onError(err); 206 | return; 207 | } 208 | onSuccess(value); 209 | }); 210 | return function(cancelError, cancelerError, cancelerSuccess) { 211 | cancelError(); 212 | }; 213 | }; 214 | }; 215 | }; 216 | 217 | export const lpopImpl = function(conn) { 218 | return function(key) { 219 | return function(onError, onSuccess) { 220 | var handler = _plainValueHandler(onError, onSuccess); 221 | conn.lpopBuffer.apply(conn, [key, handler]); 222 | return function(cancelError, cancelerError, cancelerSuccess) { 223 | cancelError(); 224 | }; 225 | }; 226 | }; 227 | }; 228 | 229 | export const lpushImpl = function(conn) { 230 | return function(key) { 231 | return function(value) { 232 | return function(onError, onSuccess) { 233 | var handler = _intHandler(onError, onSuccess, false); 234 | conn.lpushBuffer.apply(conn, [key, value, handler]); 235 | return function(cancelError, cancelerError, cancelerSuccess) { 236 | cancelError(); 237 | }; 238 | }; 239 | }; 240 | }; 241 | }; 242 | 243 | export const rpopImpl = function(conn) { 244 | return function(key) { 245 | return function(onError, onSuccess) { 246 | var handler = _plainValueHandler(onError, onSuccess); 247 | conn.rpopBuffer.apply(conn, [key, handler]); 248 | return function(cancelError, cancelerError, cancelerSuccess) { 249 | cancelError(); 250 | }; 251 | }; 252 | }; 253 | }; 254 | 255 | export const rpushImpl = function(conn) { 256 | return function(key) { 257 | return function(value) { 258 | return function(onError, onSuccess) { 259 | var handler = _intHandler(onError, onSuccess, false); 260 | conn.rpushBuffer.apply(conn, [key, value, handler]); 261 | return function(cancelError, cancelerError, cancelerSuccess) { 262 | cancelError(); 263 | }; 264 | }; 265 | }; 266 | }; 267 | }; 268 | 269 | export const lrangeImpl = function(conn) { 270 | return function(key) { 271 | return function(start) { 272 | return function(end) { 273 | return function(onError, onSuccess) { 274 | var handler = _plainValueHandler(onError, onSuccess); 275 | conn.lrangeBuffer.apply(conn, [key, start, end, handler]); 276 | return function(cancelError, cancelerError, cancelerSuccess) { 277 | cancelError(); 278 | }; 279 | }; 280 | }; 281 | }; 282 | }; 283 | }; 284 | 285 | export const _plainValueHandler = function(onError, onSuccess) { 286 | return function(err, val) { 287 | if (err !== null) { 288 | onError(err); 289 | return; 290 | } 291 | onSuccess(val); 292 | }; 293 | }; 294 | 295 | export const _nullValueHandler = function(onError, onSuccess) { 296 | return function(err, val) { 297 | if (err !== null) { 298 | onError(err); 299 | return; 300 | } 301 | onSuccess(null); 302 | }; 303 | }; 304 | 305 | export const ltrimImpl = function(conn) { 306 | return function(key) { 307 | return function(start) { 308 | return function(end) { 309 | return function(onError, onSuccess) { 310 | var handler = _nullValueHandler(onError, onSuccess); 311 | conn.ltrimBuffer.apply(conn, [key, start, end, handler]); 312 | return function(cancelError, cancelerError, cancelerSuccess) { 313 | cancelError(); 314 | }; 315 | }; 316 | }; 317 | }; 318 | }; 319 | }; 320 | 321 | export const mgetImpl = function(conn) { 322 | return function(keys) { 323 | return function(onError, onSuccess) { 324 | conn.mgetBuffer(keys, function(err, value) { 325 | if (err !== null) { 326 | onError(err); 327 | return; 328 | } 329 | onSuccess(value); 330 | }); 331 | return function(cancelError, cancelerError, cancelerSuccess) { 332 | cancelError(); 333 | }; 334 | }; 335 | }; 336 | }; 337 | 338 | export const setImpl = function(conn) { 339 | return function(key) { 340 | return function(value) { 341 | return function(expire) { 342 | return function(write) { 343 | return function(onError, onSuccess) { 344 | var handler = function(err) { 345 | if (err !== null) { 346 | onError(err); 347 | return; 348 | } 349 | onSuccess(); 350 | }; 351 | var args = [key, value]; 352 | if(expire !== null) { 353 | args.push(expire.unit); 354 | args.push(expire.value); 355 | } 356 | if(write !== null) { 357 | args.push(write); 358 | } 359 | args.push(handler); 360 | conn.set.apply(conn, args); 361 | return function(cancelError, cancelerError, cancelerSuccess) { 362 | cancelError(); 363 | }; 364 | }; 365 | }; 366 | }; 367 | }; 368 | }; 369 | }; 370 | 371 | export const zaddImpl = function(conn) { 372 | return function(key) { 373 | return function(writeMode) { 374 | return function(returnMode) { 375 | return function(members) { 376 | return function(onError, onSuccess) { 377 | var args = [key]; 378 | if(writeMode !== null) { 379 | args.push(writeMode); 380 | } 381 | if(returnMode !== null) { 382 | args.push(returnMode); 383 | } 384 | members.forEach(function(i) { 385 | args.push(i.score); 386 | args.push(i.member); 387 | }); 388 | var handler = function(err, val) { 389 | if (err !== null) { 390 | onError(err); 391 | return; 392 | } 393 | onSuccess(val); 394 | }; 395 | args.push(handler); 396 | conn.zaddBuffer.apply(conn, args); 397 | return function(cancelError, cancelerError, cancelerSuccess) { 398 | cancelError(); 399 | }; 400 | }; 401 | }; 402 | }; 403 | }; 404 | }; 405 | }; 406 | 407 | export const zcardImpl = function(conn) { 408 | return function(key) { 409 | return function(onError, onSuccess) { 410 | conn.zcardBuffer.apply(conn, [key, _intHandler(onError, onSuccess, false)]); 411 | return function(cancelError, cancelerError, cancelerSuccess) { 412 | cancelError(); 413 | }; 414 | }; 415 | }; 416 | }; 417 | 418 | export const zincrbyImpl = function(conn) { 419 | return function(key) { 420 | return function(increment) { 421 | return function(member) { 422 | return function(onError, onSuccess) { 423 | var handler = function(err, val) { 424 | if (err !== null) { 425 | onError(err); 426 | return; 427 | } 428 | onSuccess(parseFloat(val)); 429 | }; 430 | conn.zincrbyBuffer.apply(conn, [key, increment, member, handler]); 431 | return function(cancelError, cancelerError, cancelerSuccess) { 432 | cancelError(); 433 | }; 434 | }; 435 | }; 436 | }; 437 | }; 438 | }; 439 | 440 | export const _intHandler = function(onError, onSuccess, nullable) { 441 | return function(err, val) { 442 | if (err !== null) { 443 | onError(err); 444 | return; 445 | } 446 | if(nullable && val === null) { 447 | onSuccess(null); 448 | } else { 449 | onSuccess(parseInt(val)); 450 | } 451 | return; 452 | }; 453 | }; 454 | 455 | export const _sortedSetItemsHanlder = function(onError, onSuccess) { 456 | return function(err, val) { 457 | var curr = {}, result = []; 458 | if (err !== null) { 459 | onError(err); 460 | return; 461 | } 462 | val.forEach(function(i) { 463 | if(curr.member === undefined) { 464 | curr.member = i; 465 | } else { 466 | // XXX: I'm not sure if this is safe parsing 467 | curr.score = parseFloat(i); 468 | result.push(curr); 469 | curr = {}; 470 | } 471 | }); 472 | onSuccess(result); 473 | }; 474 | }; 475 | 476 | export const zrangeImpl = function(conn) { 477 | return function(key) { 478 | return function(start) { 479 | return function(stop) { 480 | return function(onError, onSuccess) { 481 | var handler = _sortedSetItemsHanlder(onError, onSuccess); 482 | conn.zrangeBuffer.apply(conn, [key, start, stop, 'WITHSCORES', handler]); 483 | return function(cancelError, cancelerError, cancelerSuccess) { 484 | cancelError(); 485 | }; 486 | }; 487 | }; 488 | }; 489 | }; 490 | }; 491 | 492 | export const zrangebyscoreImpl = function(conn) { 493 | return function(key) { 494 | return function(min) { 495 | return function(max) { 496 | return function(limit) { 497 | return function(onError, onSuccess) { 498 | var handler = _sortedSetItemsHanlder(onError, onSuccess), 499 | args =[key, min, max, 'WITHSCORES']; 500 | if(limit !== null) { 501 | args.push('LIMIT'); 502 | args.push(limit.offset); 503 | args.push(limit.count); 504 | } 505 | args.push(handler); 506 | conn.zrangebyscoreBuffer.apply(conn, args); 507 | }; 508 | }; 509 | }; 510 | }; 511 | }; 512 | }; 513 | 514 | export const zrankImpl = function(conn) { 515 | return function(key) { 516 | return function(member) { 517 | return function(onError, onSuccess) { 518 | conn.zrankBuffer.apply(conn, [key, member, _intHandler(onError, onSuccess, true)]); 519 | return function(cancelError, cancelerError, cancelerSuccess) { 520 | cancelError(); 521 | }; 522 | }; 523 | }; 524 | }; 525 | }; 526 | 527 | export const zremImpl = function(conn) { 528 | return function(key) { 529 | return function(members) { 530 | return function(onError, onSuccess) { 531 | conn.zremBuffer.apply(conn, [key, members, _intHandler(onError, onSuccess, false)]); 532 | return function(cancelError, cancelerError, cancelerSuccess) { 533 | cancelError(); 534 | }; 535 | }; 536 | }; 537 | }; 538 | }; 539 | 540 | export const zremrangebylexImpl = function(conn) { 541 | return function(key) { 542 | return function(min) { 543 | return function(max) { 544 | return function(onError, onSuccess) { 545 | conn.zremrangebylexBuffer.apply(conn, [key, min, max, _intHandler(onError, onSuccess, false)]); 546 | return function(cancelError, cancelerError, cancelerSuccess) { 547 | cancelError(); 548 | }; 549 | }; 550 | }; 551 | }; 552 | }; 553 | }; 554 | 555 | export const zremrangebyrankImpl = function(conn) { 556 | return function(key) { 557 | return function(start) { 558 | return function(stop) { 559 | return function(onError, onSuccess) { 560 | conn.zremrangebyrankBuffer.apply(conn, [key, start, stop, _intHandler(onError, onSuccess, false)]); 561 | return function(cancelError, cancelerError, cancelerSuccess) { 562 | cancelError(); 563 | }; 564 | }; 565 | }; 566 | }; 567 | }; 568 | }; 569 | 570 | export const zremrangebyscoreImpl = function(conn) { 571 | return function(key) { 572 | return function(min) { 573 | return function(max) { 574 | return function(onError, onSuccess) { 575 | conn.zremrangebyscoreBuffer.apply(conn, [key, min, max, _intHandler(onError, onSuccess, false)]); 576 | return function(cancelError, cancelerError, cancelerSuccess) { 577 | cancelError(); 578 | }; 579 | }; 580 | }; 581 | }; 582 | }; 583 | }; 584 | 585 | export const zrevrangebyscoreImpl = function(conn) { 586 | return function(key) { 587 | return function(min) { 588 | return function(max) { 589 | return function(limit) { 590 | return function(onError, onSuccess) { 591 | var handler = _sortedSetItemsHanlder(onError, onSuccess), 592 | args =[key, min, max, 'WITHSCORES']; 593 | if(limit !== null) { 594 | args.push('LIMIT'); 595 | args.push(limit.offset); 596 | args.push(limit.count); 597 | } 598 | args.push(handler); 599 | conn.zrevrangebyscoreBuffer.apply(conn, args); 600 | }; 601 | }; 602 | }; 603 | }; 604 | }; 605 | }; 606 | 607 | export const zscoreImpl = function(conn) { 608 | return function(key) { 609 | return function(member) { 610 | return function(onError, onSuccess) { 611 | var handler = function(err, val) { 612 | if (err !== null) { 613 | onError(err); 614 | return; 615 | } 616 | if(val !== null) { 617 | onSuccess(parseFloat(val)); 618 | } else { 619 | onSuccess(val); 620 | } 621 | }; 622 | conn.zscoreBuffer.apply(conn, [key, member, handler]); 623 | }; 624 | }; 625 | }; 626 | }; 627 | -------------------------------------------------------------------------------- /src/Database/Redis.purs: -------------------------------------------------------------------------------- 1 | module Database.Redis 2 | ( Connection 3 | , Config 4 | , defaultConfig 5 | , Expire(..) 6 | , IPFamily 7 | , negInf 8 | , posInf 9 | , Write(..) 10 | , Zadd(..) 11 | , ZaddReturn(..) 12 | , ZscoreInterval(..) 13 | 14 | , blpop 15 | , blpopIndef 16 | , brpop 17 | , brpopIndef 18 | , connect 19 | , del 20 | , disconnect 21 | , flushdb 22 | , hget 23 | , hgetall 24 | , hset 25 | , get 26 | , incr 27 | , keys 28 | , lpop 29 | , lpush 30 | , lrange 31 | , ltrim 32 | , mget 33 | , rpop 34 | , rpush 35 | , set 36 | , withConnection 37 | , zadd 38 | , zcard 39 | , zrank 40 | , zincrby 41 | , zrange 42 | , zrangebyscore 43 | , zrem 44 | , zremrangebylex 45 | , zremrangebyrank 46 | , zremrangebyscore 47 | , zrevrangebyscore 48 | , zscore 49 | ) where 50 | 51 | import Prelude 52 | 53 | import Effect.Aff (Aff, bracket) 54 | import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff) 55 | import Data.Array (fromFoldable) 56 | import Data.ByteString (ByteString, toUTF8) 57 | import Data.Int53 (class Int53Value, Int53, toInt53, toString) 58 | import Data.Maybe (Maybe(..)) 59 | import Data.NonEmpty (NonEmpty) 60 | import Data.Nullable (Nullable, toMaybe, toNullable) 61 | import Data.Tuple (Tuple(..)) 62 | import Unsafe.Coerce (unsafeCoerce) 63 | 64 | -------------------------------------------------------------------------------- 65 | 66 | foreign import data Connection :: Type 67 | 68 | data IPFamily = IPv4 | IPv6 69 | derive instance eqIPFamily ∷ Eq IPFamily 70 | 71 | type Config = 72 | { db :: Maybe Int 73 | , family :: IPFamily 74 | , host :: String 75 | , password :: Maybe String 76 | , port :: Int 77 | } 78 | 79 | foreign import data Null :: Type 80 | 81 | defaultConfig ∷ Config 82 | defaultConfig = 83 | { db: Nothing 84 | , family: IPv4 85 | , host: "127.0.0.1" 86 | , password: Nothing 87 | , port: 6379 88 | } 89 | 90 | withConnection 91 | :: ∀ a 92 | . Config 93 | -> (Connection -> Aff a) 94 | -> Aff a 95 | withConnection c = bracket (connect c) disconnect 96 | 97 | type Config' = 98 | { db :: Nullable Int 99 | , family :: Int 100 | , host :: String 101 | , password :: Nullable String 102 | , port :: Int 103 | } 104 | foreign import connectImpl :: Config' -> EffectFnAff Connection 105 | foreign import disconnectImpl :: Connection -> EffectFnAff Unit 106 | 107 | connect :: Config -> Aff Connection 108 | connect cfg = fromEffectFnAff <<< connectImpl <<< ser $ cfg 109 | where 110 | ser c = 111 | { db: toNullable c.db 112 | , family: if c.family == IPv4 then 4 else 6 113 | , host: c.host 114 | , password: toNullable c.password 115 | , port: c.port 116 | } 117 | 118 | disconnect :: Connection -> Aff Unit 119 | disconnect = fromEffectFnAff <<< disconnectImpl 120 | 121 | -------------------------------------------------------------------------------- 122 | 123 | data Expire = PX Int | EX Int 124 | data Write = NX | XX 125 | 126 | serWrite :: Write -> ByteString 127 | serWrite NX = toUTF8 "NX" 128 | serWrite XX = toUTF8 "XX" 129 | 130 | data ZaddReturn = Changed | Added 131 | data Zadd = ZaddAll ZaddReturn | ZaddRestrict Write 132 | type Zitem i = { member :: ByteString, score :: i } 133 | type Zlimit = { offset :: Int, count :: Int } 134 | data ZscoreInterval i = Excl i | Incl i | NegInf | PosInf 135 | 136 | serZscoreInterval ∷ ∀ i. Int53Value i => ZscoreInterval i → ByteString 137 | serZscoreInterval NegInf = toUTF8 "-inf" 138 | serZscoreInterval PosInf = toUTF8 "+inf" 139 | serZscoreInterval (Excl i) = toUTF8 <<< ("(" <> _) <<< toString <<< toInt53 $ i 140 | serZscoreInterval (Incl i) = toUTF8 <<< toString <<< toInt53 $ i 141 | -- | Use this to avoid type signatures 142 | negInf = NegInf :: ZscoreInterval Int 143 | posInf = PosInf :: ZscoreInterval Int 144 | 145 | foreign import blpopImpl 146 | :: Connection 147 | -> Array ByteString 148 | -> Int 149 | -> EffectFnAff 150 | 151 | (Nullable { key ∷ ByteString, value ∷ ByteString }) 152 | foreign import brpopImpl 153 | :: Connection 154 | -> Array ByteString 155 | -> Int 156 | -> EffectFnAff 157 | 158 | (Nullable { key ∷ ByteString, value ∷ ByteString }) 159 | foreign import delImpl 160 | :: Connection 161 | -> Array ByteString 162 | -> EffectFnAff Unit 163 | foreign import flushdbImpl 164 | :: Connection 165 | -> EffectFnAff Unit 166 | foreign import getImpl 167 | :: Connection 168 | -> ByteString 169 | -> EffectFnAff (Nullable ByteString) 170 | foreign import hgetallImpl 171 | :: Connection 172 | -> ByteString 173 | -> EffectFnAff 174 | (Array { key :: ByteString, value :: ByteString }) 175 | foreign import hgetImpl 176 | :: Connection 177 | -> ByteString 178 | -> ByteString 179 | -> EffectFnAff (Nullable ByteString) 180 | foreign import hsetImpl 181 | :: Connection 182 | -> ByteString 183 | -> ByteString 184 | -> ByteString 185 | -> EffectFnAff Int 186 | foreign import incrImpl 187 | :: Connection 188 | -> ByteString 189 | -> EffectFnAff Int 190 | foreign import keysImpl 191 | :: Connection 192 | -> ByteString 193 | -> EffectFnAff (Array ByteString) 194 | foreign import lpopImpl 195 | :: Connection 196 | -> ByteString 197 | -> EffectFnAff (Nullable ByteString) 198 | foreign import lpushImpl 199 | :: Connection 200 | -> ByteString 201 | -> ByteString 202 | -> EffectFnAff Int 203 | foreign import lrangeImpl 204 | :: Connection 205 | -> ByteString 206 | -> Int 207 | -> Int 208 | -> EffectFnAff (Array ByteString) 209 | foreign import ltrimImpl 210 | :: Connection 211 | -> ByteString 212 | -> Int 213 | -> Int 214 | -> EffectFnAff Null 215 | foreign import rpopImpl 216 | :: Connection 217 | -> ByteString 218 | -> EffectFnAff (Nullable ByteString) 219 | foreign import rpushImpl 220 | :: Connection 221 | -> ByteString 222 | -> ByteString 223 | -> EffectFnAff Int 224 | foreign import mgetImpl 225 | :: Connection 226 | -> Array ByteString 227 | -> EffectFnAff (Array ByteString) 228 | foreign import setImpl 229 | :: Connection 230 | -> ByteString 231 | -> ByteString 232 | -> Nullable { unit :: ByteString, value :: Int } 233 | -> Nullable ByteString 234 | -> EffectFnAff Unit 235 | -- | ZADD key [NX|XX] [CH] 236 | -- | INCR mode would be supported by `zaddIncrImpl` 237 | foreign import zaddImpl 238 | :: Connection 239 | -> ByteString 240 | -> Nullable ByteString 241 | -> Nullable ByteString 242 | -> Array (Zitem Int53) 243 | -> EffectFnAff Int 244 | foreign import zcardImpl 245 | :: Connection 246 | -> ByteString 247 | -> EffectFnAff Int 248 | foreign import zincrbyImpl 249 | :: Connection 250 | -> ByteString 251 | -> Int53 252 | -> ByteString 253 | -> EffectFnAff Int53 254 | foreign import zrangeImpl 255 | :: Connection 256 | -> ByteString 257 | -> Int 258 | -> Int 259 | -> EffectFnAff (Array (Zitem Int53)) 260 | foreign import zrangebyscoreImpl 261 | :: Connection 262 | -> ByteString 263 | -> ByteString 264 | -> ByteString 265 | -> Nullable Zlimit 266 | -> EffectFnAff (Array (Zitem Int53)) 267 | foreign import zrankImpl 268 | :: Connection 269 | -> ByteString 270 | -> ByteString 271 | -> EffectFnAff (Nullable Int) 272 | foreign import zremImpl 273 | :: Connection 274 | -> ByteString 275 | -> Array ByteString 276 | -> EffectFnAff Int 277 | foreign import zremrangebylexImpl 278 | :: Connection 279 | -> ByteString 280 | -> ByteString 281 | -> ByteString 282 | -> EffectFnAff Int 283 | foreign import zremrangebyrankImpl 284 | :: Connection 285 | -> ByteString 286 | -> Int 287 | -> Int 288 | -> EffectFnAff Int 289 | foreign import zremrangebyscoreImpl 290 | :: Connection 291 | -> ByteString 292 | -> ByteString 293 | -> ByteString 294 | -> EffectFnAff Int 295 | foreign import zrevrangebyscoreImpl 296 | :: Connection 297 | -> ByteString 298 | -> ByteString 299 | -> ByteString 300 | -> Nullable Zlimit 301 | -> EffectFnAff (Array (Zitem Int53)) 302 | foreign import zscoreImpl 303 | :: Connection 304 | -> ByteString 305 | -> ByteString 306 | -> EffectFnAff (Nullable Int53) 307 | blpop 308 | :: Connection 309 | -> NonEmpty Array ByteString 310 | -> Int 311 | -> Aff (Maybe {key ∷ ByteString, value ∷ ByteString}) 312 | blpop conn keys' timeout = toMaybe <$> (fromEffectFnAff $ blpopImpl conn (fromFoldable keys') timeout) 313 | blpopIndef 314 | :: Connection 315 | -> NonEmpty Array ByteString 316 | -> Aff {key ∷ ByteString, value ∷ ByteString} 317 | blpopIndef conn keys' = unsafeCoerce (fromEffectFnAff $ blpopImpl conn (fromFoldable keys') 0) 318 | brpop 319 | :: Connection 320 | -> NonEmpty Array ByteString 321 | -> Int 322 | -> Aff (Maybe {key ∷ ByteString, value ∷ ByteString}) 323 | brpop conn keys' timeout = toMaybe <$> (fromEffectFnAff $ brpopImpl conn (fromFoldable keys') timeout) 324 | brpopIndef 325 | :: Connection 326 | -> NonEmpty Array ByteString 327 | -> Aff {key ∷ ByteString, value ∷ ByteString} 328 | brpopIndef conn keys' = unsafeCoerce (fromEffectFnAff $ brpopImpl conn (fromFoldable keys') 0) 329 | del 330 | :: Connection 331 | -> NonEmpty Array ByteString 332 | -> Aff Unit 333 | del conn = fromEffectFnAff <<< delImpl conn <<< fromFoldable 334 | flushdb 335 | :: Connection 336 | -> Aff Unit 337 | flushdb = fromEffectFnAff <<< flushdbImpl 338 | get 339 | :: Connection 340 | -> ByteString 341 | -> Aff (Maybe ByteString) 342 | get conn key = toMaybe <$> (fromEffectFnAff $ getImpl conn key) 343 | hget 344 | :: Connection 345 | -> ByteString 346 | -> ByteString 347 | -> Aff (Maybe ByteString) 348 | hget conn key field = toMaybe <$> (fromEffectFnAff $ hgetImpl conn key field) 349 | hgetall 350 | :: Connection 351 | -> ByteString 352 | -> Aff (Array {key :: ByteString, value :: ByteString}) 353 | hgetall conn = fromEffectFnAff <<< hgetallImpl conn 354 | hset 355 | :: Connection 356 | -> ByteString 357 | -> ByteString 358 | -> ByteString 359 | -> Aff Int 360 | hset conn key field = fromEffectFnAff <<< hsetImpl conn key field 361 | incr 362 | :: Connection 363 | -> ByteString 364 | -> Aff Int 365 | incr conn = fromEffectFnAff <<< incrImpl conn 366 | keys 367 | :: Connection 368 | -> ByteString 369 | -> Aff (Array ByteString) 370 | keys conn = fromEffectFnAff <<< keysImpl conn 371 | lpop 372 | :: Connection 373 | -> ByteString 374 | -> Aff (Maybe ByteString) 375 | lpop conn key = toMaybe <$> (fromEffectFnAff $ lpopImpl conn key) 376 | lpush 377 | :: Connection 378 | -> ByteString 379 | -> ByteString 380 | -> Aff Int 381 | lpush conn key = fromEffectFnAff <<< lpushImpl conn key 382 | lrange 383 | :: Connection 384 | -> ByteString 385 | -> Int 386 | -> Int 387 | -> Aff (Array ByteString) 388 | lrange conn key start = fromEffectFnAff <<< lrangeImpl conn key start 389 | ltrim 390 | :: Connection 391 | -> ByteString 392 | -> Int 393 | -> Int 394 | -> Aff Unit 395 | ltrim conn key start end = unit <$ (fromEffectFnAff $ ltrimImpl conn key start end) 396 | mget 397 | :: Connection 398 | -> NonEmpty Array ByteString 399 | -> Aff (Array ByteString) 400 | mget conn = fromEffectFnAff <<< mgetImpl conn <<< fromFoldable 401 | rpop 402 | :: Connection 403 | -> ByteString 404 | -> Aff (Maybe ByteString) 405 | rpop conn key = toMaybe <$> (fromEffectFnAff $ rpopImpl conn key) 406 | rpush 407 | :: Connection 408 | -> ByteString 409 | -> ByteString 410 | -> Aff Int 411 | rpush conn key = fromEffectFnAff <<< rpushImpl conn key 412 | set 413 | :: Connection 414 | -> ByteString 415 | -> ByteString 416 | -> Maybe Expire 417 | -> Maybe Write 418 | -> Aff Unit 419 | set conn key value expire write = fromEffectFnAff $ setImpl conn key value expire' write' 420 | where 421 | serExpire (PX v) = { unit: toUTF8 "PX", value: v } 422 | serExpire (EX v) = { unit: toUTF8 "EX", value: v } 423 | expire' = toNullable $ serExpire <$> expire 424 | write' = toNullable $ serWrite <$> write 425 | -- | This API allows you to build only these 426 | -- | combination of write modes and return values: 427 | -- | ``` 428 | -- | ZADD key CH score member [score member] 429 | -- | ZADD key score member [score member] 430 | -- | ZADD key XX CH score member [score member] 431 | -- | ZADD key NX score member [score member] 432 | zadd 433 | :: ∀ i 434 | . Int53Value i 435 | => Connection 436 | -> ByteString 437 | -> Zadd 438 | -> NonEmpty Array (Zitem i) 439 | -> Aff Int 440 | zadd conn key mode = 441 | fromEffectFnAff <<< zaddImpl conn key write' return' <<< map (\r → r { score = toInt53 r.score }) <<< fromFoldable 442 | where 443 | Tuple write' return' = case mode of 444 | ZaddAll Changed -> Tuple (toNullable Nothing) (toNullable $ Just (toUTF8 "CH")) 445 | ZaddAll Added -> Tuple (toNullable Nothing) (toNullable $ Nothing) 446 | ZaddRestrict XX -> Tuple (toNullable $ Just (serWrite XX)) (toNullable $ Just (toUTF8 "CH")) 447 | ZaddRestrict NX -> Tuple (toNullable $ Just (serWrite NX)) (toNullable Nothing) 448 | 449 | zcard 450 | :: Connection 451 | -> ByteString 452 | -> Aff Int 453 | zcard conn = fromEffectFnAff <<< zcardImpl conn 454 | 455 | zincrby 456 | :: ∀ i 457 | . Int53Value i 458 | => Connection 459 | -> ByteString 460 | -> i 461 | -> ByteString 462 | -> Aff Int53 463 | zincrby conn key increment = fromEffectFnAff <<< zincrbyImpl conn key (toInt53 increment) 464 | 465 | zrange 466 | :: Connection 467 | -> ByteString 468 | -> Int 469 | -> Int 470 | -> Aff (Array (Zitem Int53)) 471 | zrange conn key start = fromEffectFnAff <<< zrangeImpl conn key start 472 | 473 | zrangebyscore 474 | :: ∀ max min 475 | . Int53Value min 476 | => Int53Value max 477 | => Connection 478 | -> ByteString 479 | -> (ZscoreInterval min) 480 | -> (ZscoreInterval max) 481 | -> Maybe Zlimit 482 | -> Aff (Array (Zitem Int53)) 483 | zrangebyscore conn key min max limit = fromEffectFnAff $ zrangebyscoreImpl conn key min' max' limit' 484 | where 485 | min' = serZscoreInterval min 486 | max' = serZscoreInterval max 487 | limit' = toNullable limit 488 | 489 | zrank 490 | :: Connection 491 | -> ByteString 492 | -> ByteString 493 | -> Aff (Maybe Int) 494 | zrank conn key member = toMaybe <$> (fromEffectFnAff $ zrankImpl conn key member) 495 | 496 | zrem 497 | :: Connection 498 | -> ByteString 499 | -> NonEmpty Array ByteString 500 | -> Aff Int 501 | zrem conn key = fromEffectFnAff <<< zremImpl conn key <<< fromFoldable 502 | 503 | zremrangebylex 504 | :: Connection 505 | -> ByteString 506 | -> ByteString 507 | -> ByteString 508 | -> Aff Int 509 | zremrangebylex conn key min = fromEffectFnAff <<< zremrangebylexImpl conn key min 510 | zremrangebyrank 511 | :: Connection 512 | -> ByteString 513 | -> Int 514 | -> Int 515 | -> Aff Int 516 | zremrangebyrank conn key min = fromEffectFnAff <<< zremrangebyrankImpl conn key min 517 | zremrangebyscore 518 | :: ∀ max min 519 | . Int53Value min 520 | => Int53Value max 521 | => Connection 522 | -> ByteString 523 | -> (ZscoreInterval min) 524 | -> (ZscoreInterval max) 525 | -> Aff Int 526 | zremrangebyscore conn key min max = fromEffectFnAff $ zremrangebyscoreImpl conn key min' max' 527 | where 528 | min' = serZscoreInterval min 529 | max' = serZscoreInterval max 530 | zrevrangebyscore 531 | :: ∀ max min 532 | . Int53Value min 533 | => Int53Value max 534 | => Connection 535 | -> ByteString 536 | -> (ZscoreInterval min) 537 | -> (ZscoreInterval max) 538 | -> Maybe Zlimit 539 | -> Aff (Array (Zitem Int53)) 540 | zrevrangebyscore conn key min max limit = fromEffectFnAff $ zrevrangebyscoreImpl conn key min' max' limit' 541 | where 542 | min' = serZscoreInterval min 543 | max' = serZscoreInterval max 544 | limit' = toNullable limit 545 | 546 | zscore 547 | :: Connection 548 | -> ByteString 549 | -> ByteString 550 | -> Aff (Maybe Int53) 551 | zscore conn key = (toMaybe <$> _) <<< fromEffectFnAff <<< zscoreImpl conn key 552 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main 2 | ( main 3 | ) where 4 | 5 | import Prelude 6 | 7 | import Effect.Aff (Aff, Milliseconds(Milliseconds), delay, forkAff) 8 | import Effect (Effect) 9 | import Control.Monad.Except (catchError, throwError) 10 | import Data.Array (drop, filter, fromFoldable, sort, sortWith, take) 11 | import Data.ByteString (ByteString) 12 | import Data.ByteString as ByteString 13 | import Data.Foldable (length) 14 | import Data.Int53 (fromInt) 15 | import Data.Maybe (Maybe(..)) 16 | import Data.NonEmpty (singleton, (:|)) 17 | import Database.Redis (Connection, Expire(..), Write(..), ZscoreInterval(..), Config, defaultConfig, flushdb, keys, negInf, posInf, withConnection) 18 | import Database.Redis as Redis 19 | import Test.Unit (TestSuite, suite) 20 | import Test.Unit as Test.Unit 21 | import Test.Unit.Assert as Assert 22 | import Test.Unit.Main (runTest) 23 | 24 | b :: String -> ByteString 25 | b = ByteString.toUTF8 26 | 27 | test 28 | :: forall a 29 | . Config 30 | -> String 31 | -> (Connection -> Aff a) 32 | -> TestSuite 33 | test s title action = 34 | Test.Unit.test title $ do 35 | withFlushdb s action 36 | 37 | withFlushdb 38 | :: ∀ a 39 | . Config 40 | -> (Connection -> Aff a) 41 | -> Aff Unit 42 | withFlushdb c action = Redis.withConnection c \conn -> do 43 | k <- keys conn (b "*") 44 | -- Safe guard 45 | Assert.assert "Test database should be empty" (length k == 0) 46 | catchError (action conn >>= const (flushdb conn)) (\e -> flushdb conn >>= const (throwError e)) 47 | 48 | main :: Effect Unit 49 | main = runTest $ do 50 | let 51 | addr = defaultConfig { port=43210 } 52 | key1 = b "purescript-redis:test:key1" 53 | key2 = b "purescript-redis:test:key2" 54 | 55 | suite "Database.Redis" do 56 | test addr "set and get" $ \conn -> do 57 | let set = b "value1" 58 | Redis.set conn key1 set Nothing Nothing 59 | got <- Redis.get conn key1 60 | Assert.equal (Just set) got 61 | n <- Redis.get conn (b "nonexisting") 62 | Assert.equal Nothing n 63 | 64 | test addr "incr on empty value" $ \conn -> do 65 | got <- Redis.incr conn key2 66 | Assert.equal 1 got 67 | 68 | test addr "keys *" $ \conn -> do 69 | void $ Redis.incr conn key1 70 | void $ Redis.incr conn key2 71 | got <- Redis.keys conn (b "*") 72 | Assert.equal (sort [key1, key2]) (sort got) 73 | 74 | test addr "key expiration" $ \conn -> do 75 | let set = b "value1" 76 | Redis.set conn key1 set (Just (EX 1)) Nothing 77 | got1 <- Redis.get conn key1 78 | Assert.equal (Just set) got1 79 | delay (Milliseconds 1000.0) 80 | got2 <- Redis.get conn key1 81 | Assert.equal Nothing got2 82 | 83 | test addr "set with XX" $ \conn -> do 84 | let set = b "value1" 85 | Redis.del conn (key1 :| []) 86 | Redis.set conn key1 set Nothing (Just XX) 87 | got1 <- Redis.get conn key1 88 | Assert.equal Nothing got1 89 | Redis.set conn key1 set Nothing (Just NX) 90 | got2 <- Redis.get conn key1 91 | Assert.equal (Just set) got2 92 | 93 | test addr "set with NX" $ \conn -> do 94 | let set = b "value1" 95 | Redis.del conn (key1 :| []) 96 | Redis.set conn key1 set Nothing (Just NX) 97 | got1 <- Redis.get conn key1 98 | Assert.equal (Just set) got1 99 | Redis.set conn key1 (b "new") Nothing (Just NX) 100 | got2 <- Redis.get conn key1 101 | Assert.equal (Just set) got2 102 | 103 | test addr "mget" $ \conn -> do 104 | void $ Redis.incr conn key1 105 | void $ Redis.incr conn key2 106 | got <- Redis.mget conn (key1 :| [key2]) 107 | Assert.equal [b "1", b "1"] got 108 | 109 | suite "sorted set" do 110 | let testSet = b "testSet" 111 | test addr "zadd/zscore" $ \conn -> do 112 | 113 | n ← Redis.zscore conn testSet (b "nonexisting") 114 | Assert.equal Nothing n 115 | 116 | let 117 | members = 118 | (:|) { member: b "m1", score: 1 } 119 | [ { member: b "m2", score: 2 } 120 | , { member: b "m3", score: 3 } 121 | ] 122 | count <- Redis.zadd 123 | conn 124 | testSet 125 | (Redis.ZaddAll Redis.Added) 126 | members 127 | Assert.equal count 3 128 | got <- Redis.zrange conn testSet 0 1 129 | Assert.equal (map _.member <<< take 2 <<< fromFoldable $ members) (map _.member got) 130 | Assert.equal (map (fromInt <<< _.score) <<< take 2 <<< fromFoldable $ members) (map _.score got) 131 | 132 | s1 ← Redis.zscore conn testSet (b "m1") 133 | Assert.equal (Just $ fromInt 1) s1 134 | s2 ← Redis.zscore conn testSet (b "m2") 135 | Assert.equal (Just $ fromInt 2) s2 136 | n' ← Redis.zscore conn testSet (b "nonexisting") 137 | Assert.equal Nothing n' 138 | 139 | test addr "zadd XX" $ \conn -> do 140 | let 141 | members = 142 | {member: b "m1", score: 1 } :| [{ member: b "m2", score: 2 } , { member: b "m3", score: 3 }] 143 | void $ Redis.zadd 144 | conn 145 | testSet 146 | (Redis.ZaddAll Redis.Added) 147 | members 148 | 149 | let 150 | updated = 151 | (:|) { member: b "m1", score: 1 } 152 | [ { member: b "m2", score: 3 } 153 | , { member: b "m3", score: 4 } 154 | , { member: b "new", score: 5 } 155 | ] 156 | count <- Redis.zadd 157 | conn 158 | testSet 159 | (Redis.ZaddRestrict Redis.XX) 160 | updated 161 | 162 | Assert.equal 2 count 163 | got <- Redis.zrange conn testSet 0 100 164 | -- | We should only modify existing items and not add new ones 165 | let updated' = filter ((_ /= b "new") <<< _.member ) <<< fromFoldable $ updated 166 | Assert.equal (map _.member updated') (map _.member got) 167 | Assert.equal (map (fromInt <<< _.score) updated') (map _.score got) 168 | 169 | test addr "zadd NX" $ \conn -> do 170 | let 171 | members = 172 | (:|) { member: b "m1", score: 1 } 173 | [ { member: b "m2", score: 2 } 174 | , { member: b "m3", score: 3 } 175 | ] 176 | void $ Redis.zadd 177 | conn 178 | testSet 179 | (Redis.ZaddAll Redis.Added) 180 | members 181 | 182 | let 183 | updated = 184 | (:|) { member: b "m1", score: 1 } 185 | [ { member: b "m2", score: 8 } 186 | , { member: b "m3", score: 9 } 187 | , { member: b "new", score: 4 } 188 | ] 189 | count <- Redis.zadd 190 | conn 191 | testSet 192 | (Redis.ZaddRestrict Redis.NX) 193 | updated 194 | 195 | Assert.equal 1 count 196 | got <- Redis.zrange conn testSet 0 100 197 | -- | We should only add new items and not modify existing ones 198 | let updated' = (fromFoldable members) <> [{ member: b "new", score: 4 }] 199 | Assert.equal (map _.member updated') (map _.member got) 200 | Assert.equal (map (fromInt <<< _.score) updated') (map _.score got) 201 | 202 | test addr "zcard" $ \conn -> do 203 | let 204 | members = 205 | {member: b "m1", score: 1 } :| [{ member: b "m2", score: 2 } , { member: b "m3", score: 3 }] 206 | void $ Redis.zadd 207 | conn 208 | testSet 209 | (Redis.ZaddAll Redis.Added) 210 | members 211 | count ← Redis.zcard conn testSet 212 | Assert.equal count 3 213 | 214 | test addr "zrangebyscore/zrevrangebyscore" $ \conn -> do 215 | let 216 | members = 217 | (:|) { member: b "one", score: 1 } 218 | [ { member: b "two", score: 2 } 219 | , { member: b "three", score: 3 } 220 | , { member: b "four", score: 4 } 221 | , { member: b "five", score: 5 } 222 | ] 223 | void $ Redis.zadd 224 | conn 225 | testSet 226 | (Redis.ZaddAll Redis.Added) 227 | members 228 | 229 | -- Member with maximum score: 230 | -- ZREVRANGEBYSCORE myset +inf -inf WITHSCORES LIMIT 0 1 231 | -- ZRANGEBYSCORE myset -inf +inf WITHSCORES LIMIT 0 1 232 | min <- Redis.zrangebyscore conn testSet (negInf) (posInf) (Just {offset: 0, count: 1}) 233 | Assert.equal [fromInt 1] (map _.score min) 234 | 235 | max <- Redis.zrevrangebyscore conn testSet (posInf) (negInf) (Just {offset: 0, count: 1}) 236 | Assert.equal [fromInt 5] (map _.score max) 237 | 238 | got <- Redis.zrangebyscore conn testSet (Incl 0) (Excl 3) Nothing 239 | Assert.equal [b "one", b "two"] (map _.member got) 240 | 241 | got' <- Redis.zrangebyscore conn testSet negInf posInf Nothing 242 | Assert.equal [b "one", b "two", b "three", b "four", b "five"] (map _.member got') 243 | 244 | got'' <- Redis.zrangebyscore conn testSet negInf posInf (Just { offset: 2, count: 2 }) 245 | Assert.equal [b "three", b "four"] (map _.member got'') 246 | 247 | test addr "zincrby/zrank" $ \conn -> do 248 | let 249 | member1 = { member: b "m1", score: 1 } 250 | member2 = { member: b "m2", score: 2 } 251 | 252 | m1Rank <- Redis.zrank conn testSet member1.member 253 | Assert.equal Nothing m1Rank 254 | 255 | count <- Redis.zadd 256 | conn 257 | testSet 258 | (Redis.ZaddAll Redis.Added) 259 | (member1 :| [member2]) 260 | 261 | m1Rank' <- Redis.zrank conn testSet member1.member 262 | Assert.equal (Just 0) m1Rank' 263 | 264 | got <- Redis.zincrby conn testSet 2 member1.member 265 | Assert.equal (fromInt 3) got 266 | 267 | m1Rank'' <- Redis.zrank conn testSet member1.member 268 | Assert.equal (Just 1) m1Rank'' 269 | 270 | test addr "zrem" $ \conn -> do 271 | let 272 | members = 273 | (:|) { member: b "m1", score: 1 } 274 | [ { member: b "m2", score: 2 } 275 | , { member: b "m3", score: 3 } 276 | ] 277 | void $ Redis.zadd 278 | conn 279 | testSet 280 | (Redis.ZaddAll Redis.Added) 281 | members 282 | 283 | count <- Redis.zrem 284 | conn 285 | testSet 286 | (b "m1" :| [b "m2"]) 287 | 288 | Assert.equal 2 count 289 | got <- Redis.zrange conn testSet 0 (-1) 290 | Assert.equal (map _.member <<< drop 2 <<< fromFoldable $ members) (map _.member got) 291 | 292 | test addr "zremrangebylex" $ \conn -> do 293 | let 294 | members = 295 | (:|) {member: b "aaaa", score: 0 } 296 | [ { member: b "b", score: 0 } 297 | , { member: b "c", score: 0 } 298 | , { member: b "d", score: 0 } 299 | , { member: b "e", score: 0 } 300 | , { member: b "foo", score: 0 } 301 | , { member: b "zap", score: 0 } 302 | , { member: b "zip", score: 0 } 303 | , { member: b "ALPHA", score: 0 } 304 | , { member: b "alpha", score: 0 } 305 | ] 306 | void $ Redis.zadd 307 | conn 308 | testSet 309 | (Redis.ZaddAll Redis.Added) 310 | members 311 | 312 | count <- Redis.zremrangebylex 313 | conn 314 | testSet 315 | (b "[alpha") 316 | (b "[omega") 317 | 318 | Assert.equal 6 count 319 | got <- Redis.zrange conn testSet 0 (-1) 320 | Assert.equal ([b "ALPHA", b "aaaa", b "zap", b "zip"]) (map _.member got) 321 | 322 | test addr "zremrangebyrank" $ \conn -> do 323 | let 324 | members = 325 | (:|) { member: b "one", score: 1 } 326 | [ { member: b "two", score: 2 } 327 | , { member: b "three", score: 3 } 328 | , { member: b "four", score: 4 } 329 | , { member: b "five", score: 5 } 330 | ] 331 | void $ Redis.zadd 332 | conn 333 | testSet 334 | (Redis.ZaddAll Redis.Added) 335 | members 336 | 337 | count <- Redis.zremrangebyrank conn testSet 0 2 338 | 339 | Assert.equal 3 count 340 | got <- Redis.zrange conn testSet 0 (-1) 341 | Assert.equal ([b "four", b "five"]) (map _.member got) 342 | 343 | test addr "zremrangebyscore" $ \conn -> do 344 | let 345 | members = 346 | (:|) { member: b "one", score: 1 } 347 | [ { member: b "two", score: 2 } 348 | , { member: b "three", score: 3 } 349 | , { member: b "four", score: 4 } 350 | , { member: b "five", score: 5 } 351 | ] 352 | void $ Redis.zadd 353 | conn 354 | testSet 355 | (Redis.ZaddAll Redis.Added) 356 | members 357 | 358 | count <- Redis.zremrangebyscore conn testSet (Incl 0) (Excl 3) 359 | Assert.equal 2 count 360 | 361 | got <- Redis.zrange conn testSet 0 (-1) 362 | Assert.equal ([b "three", b "four", b "five"]) (map _.member got) 363 | 364 | count' <- Redis.zremrangebyscore conn testSet negInf posInf 365 | Assert.equal 3 count' 366 | 367 | got' <- Redis.zrange conn testSet 0 (-1) 368 | Assert.equal ([]) (map _.member got') 369 | 370 | suite "hash" do 371 | let 372 | testHash = b "testHash" 373 | value1 = { key: b "key1", value: b "val1" } 374 | value2 = { key: b "key2", value: b "val2" } 375 | value3 = { key: b "key3", value: b "val3" } 376 | test addr "hset return value" $ \conn -> do 377 | s1 <- Redis.hset conn testHash value1.key value1.value 378 | s2 <- Redis.hset conn testHash value2.key value2.value 379 | s2' <- Redis.hset conn testHash value2.key value2.value 380 | Assert.equal 1 s1 381 | Assert.equal 1 s2 382 | Assert.equal 0 s2' 383 | 384 | test addr "hset / hget" $ \conn -> do 385 | s1 <- Redis.hset conn testHash value1.key value1.value 386 | s2 <- Redis.hset conn testHash value2.key value2.value 387 | 388 | v1 <- Redis.hget conn testHash value1.key 389 | v2 <- Redis.hget conn testHash value2.key 390 | 391 | Assert.equal (Just value1.value) v1 392 | Assert.equal (Just value2.value) v2 393 | 394 | test addr "hgetall" $ \conn -> do 395 | void $ Redis.hset conn testHash value1.key value1.value 396 | void $ Redis.hset conn testHash value2.key value2.value 397 | 398 | values <- Redis.hgetall conn testHash 399 | 400 | Assert.equal 401 | [value1.value, value2.value] 402 | (map _.value <<< sortWith _.key $ values) 403 | 404 | suite "list" do 405 | let 406 | testList = b "testList" 407 | value1 = b "val1" 408 | value2 = b "val2" 409 | value3 = b "val3" 410 | 411 | test addr "lpush / blpop" $ \conn -> do 412 | v <- Redis.blpop conn (singleton testList) 1 413 | Assert.equal Nothing (v <#> _.value) 414 | 415 | test addr "lpush / lpop" $ \conn -> do 416 | void $ Redis.lpush conn testList value1 417 | void $ Redis.lpush conn testList value2 418 | v2 <- Redis.lpop conn testList 419 | v1 <- Redis.lpop conn testList 420 | n <- Redis.lpop conn testList 421 | Assert.equal (Just value2) v2 422 | Assert.equal (Just value1) v1 423 | Assert.equal Nothing n 424 | 425 | test addr "lrange" $ \conn -> do 426 | void $ Redis.lpush conn testList value3 427 | void $ Redis.lpush conn testList value2 428 | void $ Redis.lpush conn testList value1 429 | g1 <- Redis.lrange conn testList 0 1 430 | Assert.equal [value1, value2] g1 431 | g2 <- Redis.lrange conn testList (-3) (-1) 432 | Assert.equal [value1, value2, value3] g2 433 | 434 | test addr "ltrim" $ \conn -> do 435 | void $ Redis.lpush conn testList value3 436 | void $ Redis.lpush conn testList value2 437 | void $ Redis.lpush conn testList value1 438 | 439 | Redis.ltrim conn testList 0 1 440 | got <- Redis.lrange conn testList 0 3 441 | Assert.equal [value1, value2] got 442 | 443 | Redis.ltrim conn testList 1 0 444 | got' <- Redis.lrange conn testList 0 3 445 | Assert.equal [] got' 446 | 447 | test addr "rpush / rpop" $ \conn -> do 448 | void $ Redis.rpush conn testList value1 449 | void $ Redis.rpush conn testList value2 450 | v2 <- Redis.rpop conn testList 451 | v1 <- Redis.rpop conn testList 452 | n <- Redis.rpop conn testList 453 | Assert.equal (Just value2) v2 454 | Assert.equal (Just value1) v1 455 | Assert.equal Nothing n 456 | 457 | test addr "rpush / brpop" $ \conn -> do 458 | void $ Redis.rpush conn testList value1 459 | void $ Redis.rpush conn testList value2 460 | v2 <- Redis.brpop conn (singleton testList) 1 461 | v1 <- Redis.brpop conn (singleton testList) 1 462 | n <- Redis.brpop conn (singleton testList) 1 463 | Assert.equal (Just value2) (v2 <#> _.value) 464 | Assert.equal (Just value1) (v1 <#> _.value) 465 | Assert.equal Nothing (n <#> _.value) 466 | 467 | test addr "rpush / brpopIndef" $ \conn -> do 468 | void $ forkAff $ withConnection addr \conn2 -> do 469 | delay (Milliseconds 1000.0) 470 | void $ Redis.rpush conn2 testList value1 471 | v <- Redis.brpopIndef conn (singleton testList) 472 | Assert.equal v.value value1 473 | --------------------------------------------------------------------------------