git.iko.soy/rowtype-yoga/purescript-yoga-redis
on main help sign in
7 files changed+721−569
since splitsnapshots
unifiedsplit
.github/workflows/ci.yml+34 bun.lock+34 docker-compose.test.yml+10 package.json+1−2 spago.lock+186−554 spago.yaml+12−13 test/Main.purs+444
.github/workflows/ci.ymlnew file+34
@@ -0,0 +1,34 @@
1+name: CI
2+
3+on:
4+ push:
5+ branches: [main]
6+ pull_request:
7+ branches: [main]
8+
9+jobs:
10+ build:
11+ runs-on: ubuntu-latest
12+
13+ services:
14+ redis:
15+ image: redis:7-alpine
16+ ports:
17+ - 6380:6379
18+ options: >-
19+ --health-cmd "redis-cli ping"
20+ --health-interval 10s
21+ --health-timeout 5s
22+ --health-retries 5
23+
24+ steps:
25+ - uses: actions/checkout@v4
26+ - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
27+
28+ - name: Build
29+ run: spago build
30+
31+ - name: Test (skip Docker - CI provides Redis)
32+ run: spago test
33+ env:
34+ SKIP_DOCKER: "true"
bun.locknew file+34
@@ -0,0 +1,34 @@
1+{
2+ "lockfileVersion": 1,
3+ "workspaces": {
4+ "": {
5+ "name": "purescript-yoga-redis",
6+ "dependencies": {
7+ "ioredis": "^5.3.0",
8+ },
9+ },
10+ },
11+ "packages": {
12+ "@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="],
13+
14+ "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
15+
16+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
17+
18+ "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
19+
20+ "ioredis": ["ioredis@5.9.2", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ=="],
21+
22+ "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
23+
24+ "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
25+
26+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
27+
28+ "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
29+
30+ "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
31+
32+ "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
33+ }
34+}
docker-compose.test.ymlnew file+10
@@ -0,0 +1,10 @@
1+services:
2+ redis:
3+ image: redis:7-alpine
4+ ports:
5+ - "6380:6379"
6+ healthcheck:
7+ test: ["CMD", "redis-cli", "ping"]
8+ interval: 1s
9+ timeout: 3s
10+ retries: 30
package.json+1−2
@@ -5,8 +5,7 @@
55 "license": "MIT",
66 "repository": {
77 "type": "git",
8− "url": "https://github.com/rowtype-yoga/purescript-yoga-backend-stack.git",
9− "directory": "packages/yoga-redis"
8+ "url": "https://github.com/rowtype-yoga/purescript-yoga-redis.git"
109 },
1110 "keywords": [
1211 "purescript",
spago.lock+186−554
@@ -6,26 +6,14 @@
66 "core": {
77 "dependencies": [
88 {
9− "aff": ">=7.0.0 <8.0.0"
9+ "aff": ">=7.1.0 <8.0.0"
1010 },
1111 {
12− "arrays": ">=7.0.0 <8.0.0"
13− },
14− {
15− "datetime": ">=6.0.0 <7.0.0"
12+ "datetime": ">=6.1.0 <7.0.0"
1613 },
1714 {
1815 "effect": ">=4.0.0 <5.0.0"
1916 },
20− {
21− "either": ">=6.0.0 <7.0.0"
22− },
23− {
24− "foldable-traversable": ">=6.0.0 <7.0.0"
25− },
26− {
27− "foreign-object": ">=4.0.0 <5.0.0"
28− },
2917 {
3018 "js-promise": ">=1.0.0 <2.0.0"
3119 },
@@ -42,518 +30,28 @@
4230 "nullable": ">=6.0.0 <7.0.0"
4331 },
4432 {
45− "prelude": ">=6.0.0 <7.0.0"
33+ "prelude": ">=6.0.2 <7.0.0"
34+ }
35+ ]
36+ },
37+ "test": {
38+ "dependencies": [
39+ {
40+ "console": ">=6.0.0 <7.0.0"
41+ },
42+ {
43+ "exceptions": ">=6.0.0 <7.0.0"
4644 },
4745 {
48− "record": ">=4.0.0 <5.0.0"
46+ "spec": ">=7.0.0 <8.0.0"
4947 },
5048 {
51− "transformers": ">=6.0.0 <7.0.0"
49+ "yoga-test-docker": ">=0.1.0 <1.0.0"
5250 }
5351 ]
54− },
55− "test": {
56− "dependencies": []
5752 }
5853 }
5954 },
60− "package_set": {
61− "address": {
62− "registry": "51.1.0"
63− },
64− "compiler": ">=0.15.15 <0.16.0",
65− "content": {
66− "abc-parser": "2.0.1",
67− "ace": "9.1.0",
68− "address-rfc2821": "0.1.1",
69− "aff": "7.1.0",
70− "aff-bus": "6.0.0",
71− "aff-coroutines": "9.0.0",
72− "aff-promise": "4.0.0",
73− "aff-retry": "2.0.0",
74− "affjax": "13.0.0",
75− "affjax-node": "1.0.0",
76− "affjax-web": "1.0.0",
77− "ansi": "7.0.0",
78− "apexcharts": "0.5.0",
79− "applicative-phases": "1.0.0",
80− "argonaut": "9.0.0",
81− "argonaut-aeson-generic": "0.4.1",
82− "argonaut-codecs": "9.1.0",
83− "argonaut-core": "7.0.0",
84− "argonaut-generic": "8.0.0",
85− "argonaut-traversals": "10.0.0",
86− "argparse-basic": "2.0.0",
87− "array-builder": "0.1.2",
88− "array-search": "0.5.6",
89− "arraybuffer": "13.2.0",
90− "arraybuffer-builder": "3.1.0",
91− "arraybuffer-types": "3.0.2",
92− "arrays": "7.3.0",
93− "arrays-extra": "0.6.1",
94− "arrays-zipper": "2.0.1",
95− "ask": "1.0.0",
96− "assert": "6.0.0",
97− "assert-multiple": "0.3.4",
98− "avar": "5.0.0",
99− "b64": "0.0.8",
100− "barbies": "1.0.1",
101− "barlow-lens": "0.9.0",
102− "bifunctors": "6.0.0",
103− "bigints": "7.0.1",
104− "bolson": "0.3.9",
105− "bookhound": "0.1.7",
106− "bower-json": "3.0.0",
107− "call-by-name": "4.0.1",
108− "canvas": "6.0.0",
109− "canvas-action": "9.0.0",
110− "cartesian": "1.0.6",
111− "catenable-lists": "7.0.0",
112− "cbor-stream": "1.0.4",
113− "chameleon": "1.0.0",
114− "chameleon-halogen": "1.0.3",
115− "chameleon-react-basic": "1.1.0",
116− "chameleon-styled": "2.5.0",
117− "chameleon-transformers": "1.0.0",
118− "channel": "1.0.0",
119− "checked-exceptions": "3.1.1",
120− "choku": "1.0.1",
121− "classless": "0.1.1",
122− "classless-arbitrary": "0.1.1",
123− "classless-decode-json": "0.1.1",
124− "classless-encode-json": "0.1.3",
125− "classnames": "2.0.0",
126− "codec": "6.1.0",
127− "codec-argonaut": "10.0.0",
128− "codec-json": "1.2.0",
129− "colors": "7.0.1",
130− "concur-core": "0.5.0",
131− "concur-react": "0.5.0",
132− "concurrent-queues": "3.0.0",
133− "console": "6.1.0",
134− "const": "6.0.0",
135− "contravariant": "6.0.0",
136− "control": "6.0.0",
137− "convertable-options": "1.0.0",
138− "coroutines": "7.0.0",
139− "css": "6.0.0",
140− "css-frameworks": "1.0.1",
141− "csv-stream": "2.0.2",
142− "data-mvc": "0.0.2",
143− "datetime": "6.1.0",
144− "datetime-parsing": "0.2.0",
145− "debounce": "0.1.0",
146− "debug": "6.0.2",
147− "decimals": "7.1.0",
148− "default-values": "1.0.1",
149− "deku": "0.9.23",
150− "deno": "0.0.5",
151− "dissect": "1.0.0",
152− "distributive": "6.0.0",
153− "dom-filereader": "7.0.0",
154− "dom-indexed": "12.0.0",
155− "dom-simple": "0.4.0",
156− "dotenv": "4.0.3",
157− "droplet": "0.6.0",
158− "dts": "1.0.0",
159− "dual-numbers": "1.0.2",
160− "dynamic-buffer": "3.0.1",
161− "echarts-simple": "0.0.1",
162− "effect": "4.0.0",
163− "either": "6.1.0",
164− "elmish": "0.11.3",
165− "elmish-enzyme": "0.1.1",
166− "elmish-hooks": "0.10.0",
167− "elmish-html": "0.8.2",
168− "elmish-testing-library": "0.3.2",
169− "email-validate": "7.0.0",
170− "encoding": "0.0.9",
171− "enums": "6.0.1",
172− "env-names": "0.3.4",
173− "error": "2.0.0",
174− "eta-conversion": "0.3.2",
175− "exceptions": "6.0.0",
176− "exists": "6.0.0",
177− "exitcodes": "4.0.0",
178− "expect-inferred": "3.0.0",
179− "fahrtwind": "2.0.0",
180− "fallback": "0.1.0",
181− "fast-vect": "1.2.0",
182− "fetch": "4.1.0",
183− "fetch-argonaut": "1.0.1",
184− "fetch-core": "5.1.0",
185− "fetch-yoga-json": "1.1.0",
186− "ffi-simple": "0.5.1",
187− "fft-js": "0.1.0",
188− "filterable": "5.0.0",
189− "fix-functor": "0.1.0",
190− "fixed-points": "7.0.0",
191− "fixed-precision": "5.0.0",
192− "flame": "1.3.0",
193− "float32": "2.0.0",
194− "fmt": "0.2.1",
195− "foldable-traversable": "6.0.0",
196− "foldable-traversable-extra": "0.0.6",
197− "foreign": "7.0.0",
198− "foreign-object": "4.1.0",
199− "foreign-readwrite": "3.4.0",
200− "forgetmenot": "0.1.0",
201− "fork": "6.0.0",
202− "form-urlencoded": "7.0.0",
203− "formatters": "7.0.0",
204− "framer-motion": "1.0.1",
205− "free": "7.1.0",
206− "freeap": "7.0.0",
207− "freer-free": "0.0.1",
208− "freet": "7.0.0",
209− "functions": "6.0.0",
210− "functor1": "3.0.0",
211− "functors": "5.0.0",
212− "fuzzy": "0.4.0",
213− "gen": "4.0.0",
214− "generate-values": "1.0.1",
215− "generic-router": "0.0.1",
216− "geojson": "0.0.5",
217− "geometry-plane": "1.0.3",
218− "gojs": "0.1.1",
219− "grain": "3.0.0",
220− "grain-router": "3.0.0",
221− "grain-virtualized": "3.0.0",
222− "graphs": "8.1.0",
223− "group": "4.1.1",
224− "halogen": "7.0.0",
225− "halogen-bootstrap5": "5.3.2",
226− "halogen-canvas": "1.0.0",
227− "halogen-css": "10.0.0",
228− "halogen-echarts-simple": "0.0.4",
229− "halogen-formless": "4.0.3",
230− "halogen-helix": "1.0.0",
231− "halogen-hooks": "0.6.3",
232− "halogen-hooks-extra": "0.9.0",
233− "halogen-infinite-scroll": "1.1.0",
234− "halogen-store": "0.5.4",
235− "halogen-storybook": "2.0.0",
236− "halogen-subscriptions": "2.0.0",
237− "halogen-svg-elems": "8.0.0",
238− "halogen-typewriter": "1.0.4",
239− "halogen-vdom": "8.0.0",
240− "halogen-vdom-string-renderer": "0.5.0",
241− "halogen-xterm": "2.0.0",
242− "heckin": "2.0.1",
243− "heterogeneous": "0.6.0",
244− "homogeneous": "0.4.0",
245− "http-methods": "6.0.0",
246− "httpurple": "4.0.0",
247− "humdrum": "0.0.1",
248− "hyrule": "2.3.8",
249− "identity": "6.0.0",
250− "identy": "4.0.1",
251− "indexed-db": "1.0.0",
252− "indexed-monad": "3.0.0",
253− "int64": "3.0.0",
254− "integers": "6.0.0",
255− "interpolate": "5.0.2",
256− "intersection-observer": "1.0.1",
257− "invariant": "6.0.0",
258− "jarilo": "1.0.1",
259− "jelly": "0.10.0",
260− "jelly-router": "0.3.0",
261− "jelly-signal": "0.4.0",
262− "jest": "1.0.0",
263− "js-abort-controller": "1.0.0",
264− "js-bigints": "2.2.1",
265− "js-date": "8.0.0",
266− "js-fetch": "0.2.1",
267− "js-fileio": "3.0.0",
268− "js-intl": "1.0.4",
269− "js-iterators": "0.1.1",
270− "js-maps": "0.1.2",
271− "js-promise": "1.0.0",
272− "js-promise-aff": "1.0.0",
273− "js-timers": "6.1.0",
274− "js-uri": "3.1.0",
275− "json": "1.1.0",
276− "json-codecs": "5.0.0",
277− "justifill": "0.5.0",
278− "jwt": "0.0.9",
279− "labeled-data": "0.2.0",
280− "language-cst-parser": "0.14.0",
281− "lazy": "6.0.0",
282− "lazy-joe": "1.0.0",
283− "lcg": "4.0.0",
284− "leibniz": "5.0.0",
285− "leveldb": "1.0.1",
286− "liminal": "1.0.1",
287− "linalg": "6.0.0",
288− "lists": "7.0.0",
289− "literals": "1.0.2",
290− "logging": "3.0.0",
291− "logging-journald": "0.4.0",
292− "lumi-components": "18.0.0",
293− "machines": "7.0.0",
294− "maps-eager": "0.4.1",
295− "marionette": "1.0.0",
296− "marionette-react-basic-hooks": "0.1.1",
297− "marked": "0.1.0",
298− "matrices": "5.0.1",
299− "matryoshka": "1.0.0",
300− "maybe": "6.0.0",
301− "media-types": "6.0.0",
302− "meowclient": "1.0.0",
303− "midi": "4.0.0",
304− "milkis": "9.0.0",
305− "minibench": "4.0.1",
306− "mmorph": "7.0.0",
307− "monad-control": "5.0.0",
308− "monad-logger": "1.3.1",
309− "monad-loops": "0.5.0",
310− "monad-unlift": "1.0.1",
311− "monoid-extras": "0.0.1",
312− "monoidal": "0.16.0",
313− "morello": "0.4.0",
314− "mote": "3.0.0",
315− "motsunabe": "2.0.0",
316− "mvc": "0.0.1",
317− "mysql": "6.0.1",
318− "n3": "0.1.0",
319− "nano-id": "1.1.0",
320− "nanoid": "0.1.0",
321− "naturals": "3.0.0",
322− "nested-functor": "0.2.1",
323− "newtype": "5.0.0",
324− "nextjs": "0.1.1",
325− "nextui": "0.2.0",
326− "node-buffer": "9.0.0",
327− "node-child-process": "11.1.0",
328− "node-event-emitter": "3.0.0",
329− "node-execa": "5.0.0",
330− "node-fs": "9.1.0",
331− "node-glob-basic": "1.3.0",
332− "node-http": "9.1.0",
333− "node-http2": "1.1.1",
334− "node-human-signals": "1.0.0",
335− "node-net": "5.1.0",
336− "node-os": "5.1.0",
337− "node-path": "5.0.0",
338− "node-process": "11.2.0",
339− "node-readline": "8.1.0",
340− "node-sqlite3": "8.0.0",
341− "node-stream-pipes": "1.4.1",
342− "node-streams": "9.0.0",
343− "node-tls": "0.3.1",
344− "node-url": "7.0.1",
345− "node-zlib": "0.4.0",
346− "nonempty": "7.0.0",
347− "now": "6.0.0",
348− "npm-package-json": "2.0.0",
349− "nullable": "6.0.0",
350− "numberfield": "0.1.0",
351− "numbers": "9.0.1",
352− "oak": "3.1.1",
353− "oak-debug": "1.2.2",
354− "object-maps": "0.3.0",
355− "ocarina": "1.5.4",
356− "open-folds": "6.3.0",
357− "open-memoize": "6.1.0",
358− "open-pairing": "6.1.0",
359− "options": "7.0.0",
360− "optparse": "5.0.1",
361− "ordered-collections": "3.2.0",
362− "ordered-set": "0.4.0",
363− "orders": "6.0.0",
364− "owoify": "1.2.0",
365− "pairs": "9.0.1",
366− "parallel": "7.0.0",
367− "parsing": "10.2.0",
368− "parsing-dataview": "3.2.4",
369− "partial": "4.0.0",
370− "pathy": "9.0.0",
371− "pha": "0.13.0",
372− "phaser": "0.7.0",
373− "phylio": "1.1.2",
374− "pipes": "8.0.0",
375− "pirates-charm": "0.0.1",
376− "pmock": "0.9.0",
377− "point-free": "1.0.0",
378− "pointed-list": "0.5.1",
379− "polymorphic-vectors": "4.0.0",
380− "posix-types": "6.0.0",
381− "postgresql": "1.6.3",
382− "precise": "6.0.0",
383− "precise-datetime": "7.0.0",
384− "prelude": "6.0.1",
385− "prettier-printer": "3.0.0",
386− "profunctor": "6.0.1",
387− "profunctor-lenses": "8.0.0",
388− "protobuf": "4.3.0",
389− "psa-utils": "8.0.0",
390− "psci-support": "6.0.0",
391− "punycode": "1.0.0",
392− "qualified-do": "2.2.0",
393− "quantities": "12.2.0",
394− "quickcheck": "8.0.1",
395− "quickcheck-combinators": "0.1.3",
396− "quickcheck-laws": "7.0.0",
397− "quickcheck-utf8": "0.0.0",
398− "random": "6.0.0",
399− "rationals": "6.0.0",
400− "rdf": "0.1.0",
401− "react": "11.0.0",
402− "react-aria": "0.2.0",
403− "react-basic": "17.0.0",
404− "react-basic-classic": "3.0.0",
405− "react-basic-dnd": "10.1.0",
406− "react-basic-dom": "6.1.0",
407− "react-basic-emotion": "7.1.0",
408− "react-basic-hooks": "8.2.0",
409− "react-basic-storybook": "2.0.0",
410− "react-dom": "8.0.0",
411− "react-halo": "3.0.0",
412− "react-icons": "1.1.5",
413− "react-markdown": "0.1.0",
414− "react-testing-library": "4.0.1",
415− "react-virtuoso": "1.0.0",
416− "reactix": "0.6.1",
417− "read": "1.0.1",
418− "recharts": "1.1.0",
419− "record": "4.0.0",
420− "record-extra": "5.0.1",
421− "record-ptional-fields": "0.1.2",
422− "record-studio": "1.0.4",
423− "refs": "6.0.0",
424− "remotedata": "5.0.1",
425− "resize-observer": "1.0.0",
426− "resource": "2.0.1",
427− "resourcet": "1.0.0",
428− "result": "1.0.3",
429− "return": "0.2.0",
430− "ring-modules": "5.0.1",
431− "rito": "0.3.4",
432− "rough-notation": "1.0.2",
433− "routing": "11.0.0",
434− "routing-duplex": "0.7.0",
435− "run": "5.0.0",
436− "safe-coerce": "2.0.0",
437− "safely": "4.0.1",
438− "school-of-music": "1.3.0",
439− "selection-foldable": "0.2.0",
440− "selective-functors": "1.0.1",
441− "semirings": "7.0.0",
442− "signal": "13.0.0",
443− "simple-emitter": "3.0.1",
444− "simple-i18n": "2.0.1",
445− "simple-json": "9.0.0",
446− "simple-json-generics": "0.2.1",
447− "simple-ulid": "3.0.0",
448− "sized-matrices": "1.0.0",
449− "sized-vectors": "5.0.2",
450− "slug": "3.0.8",
451− "small-ffi": "4.0.1",
452− "soundfonts": "4.1.0",
453− "sparse-matrices": "1.3.0",
454− "sparse-polynomials": "2.0.5",
455− "spec": "7.6.0",
456− "spec-mocha": "5.1.0",
457− "spec-quickcheck": "5.0.0",
458− "splitmix": "2.1.0",
459− "ssrs": "1.0.0",
460− "st": "6.2.0",
461− "statistics": "0.3.2",
462− "strictlypositiveint": "1.0.1",
463− "string-parsers": "8.0.0",
464− "strings": "6.0.1",
465− "strings-extra": "4.0.0",
466− "stringutils": "0.0.12",
467− "substitute": "0.2.3",
468− "supply": "0.2.0",
469− "svg-parser": "3.0.0",
470− "systemd-journald": "0.3.0",
471− "tagged": "4.0.2",
472− "tailrec": "6.1.0",
473− "tecton": "0.2.1",
474− "tecton-halogen": "0.2.0",
475− "test-unit": "17.0.0",
476− "thermite": "6.3.1",
477− "thermite-dom": "0.3.1",
478− "these": "6.0.0",
479− "toestand": "0.9.0",
480− "transformation-matrix": "1.0.1",
481− "transformers": "6.0.0",
482− "tree-rose": "4.0.2",
483− "ts-bridge": "4.0.0",
484− "tuples": "7.0.0",
485− "two-or-more": "1.0.0",
486− "type-equality": "4.0.1",
487− "typedenv": "2.0.1",
488− "typelevel": "6.0.0",
489− "typelevel-lists": "2.1.0",
490− "typelevel-peano": "1.0.1",
491− "typelevel-prelude": "7.0.0",
492− "typelevel-regex": "0.0.3",
493− "typelevel-rows": "0.1.0",
494− "typisch": "0.4.0",
495− "uint": "7.0.0",
496− "ulid": "3.0.1",
497− "uncurried-transformers": "1.1.0",
498− "undefined": "2.0.0",
499− "undefined-is-not-a-problem": "1.1.0",
500− "unfoldable": "6.0.0",
501− "unicode": "6.0.0",
502− "unique": "0.6.1",
503− "unlift": "1.0.1",
504− "unordered-collections": "3.1.0",
505− "unsafe-coerce": "6.0.0",
506− "unsafe-reference": "5.0.0",
507− "untagged-to-tagged": "0.1.4",
508− "untagged-union": "1.0.0",
509− "uri": "9.0.0",
510− "uuid": "9.0.0",
511− "uuidv4": "1.0.0",
512− "validation": "6.0.0",
513− "variant": "8.0.0",
514− "variant-encodings": "2.0.0",
515− "vectorfield": "1.0.1",
516− "vectors": "2.1.0",
517− "versions": "7.0.0",
518− "visx": "0.0.2",
519− "web-clipboard": "5.0.0",
520− "web-cssom": "2.0.0",
521− "web-cssom-view": "0.1.0",
522− "web-dom": "6.0.0",
523− "web-dom-parser": "8.0.0",
524− "web-dom-xpath": "3.0.0",
525− "web-encoding": "3.0.0",
526− "web-events": "4.0.0",
527− "web-fetch": "4.0.1",
528− "web-file": "4.0.0",
529− "web-geometry": "0.1.0",
530− "web-html": "4.1.0",
531− "web-pointerevents": "2.0.0",
532− "web-proletarian": "1.0.0",
533− "web-promise": "3.2.0",
534− "web-resize-observer": "2.1.0",
535− "web-router": "1.0.0",
536− "web-socket": "4.0.0",
537− "web-storage": "5.0.0",
538− "web-streams": "4.0.0",
539− "web-touchevents": "4.0.0",
540− "web-uievents": "5.0.0",
541− "web-url": "2.0.0",
542− "web-workers": "1.1.0",
543− "web-xhr": "5.0.1",
544− "webextension-polyfill": "0.1.0",
545− "webgpu": "0.0.1",
546− "which": "2.0.0",
547− "xterm": "1.0.0",
548− "yoga-fetch": "1.0.1",
549− "yoga-json": "5.1.0",
550− "yoga-om": "0.1.0",
551− "yoga-postgres": "6.0.0",
552− "yoga-tree": "1.0.0",
553− "z3": "0.0.2",
554− "zipperarray": "2.0.0"
555− }
556− },
55755 "extra_packages": {}
55856 },
55957 "packages": {
@@ -578,6 +76,16 @@
57876 "unsafe-coerce"
57977 ]
58078 },
79+ "ansi": {
80+ "type": "registry",
81+ "version": "7.0.0",
82+ "integrity": "sha256-tOUaXJFzPSfBVAqLI8hc+Tz2M1rg16cXqyHaK/3SmFg=",
83+ "dependencies": [
84+ "foldable-traversable",
85+ "lists",
86+ "prelude"
87+ ]
88+ },
58189 "arrays": {
58290 "type": "registry",
58391 "version": "7.3.0",
@@ -599,10 +107,24 @@
599107 "unsafe-coerce"
600108 ]
601109 },
110+ "avar": {
111+ "type": "registry",
112+ "version": "5.0.1",
113+ "integrity": "sha256-8V4SxF4TIWZ9Ik1P4lPzIRUAZeFBe8w5WA2VcCEKsIk=",
114+ "dependencies": [
115+ "aff",
116+ "effect",
117+ "either",
118+ "exceptions",
119+ "functions",
120+ "maybe",
121+ "prelude"
122+ ]
123+ },
602124 "bifunctors": {
603125 "type": "registry",
604− "version": "6.0.0",
605− "integrity": "sha256-tXhmlMiyVy6mbUreVZQt/2EGEsPr0T1PjJi+YCG5J7E=",
126+ "version": "6.1.0",
127+ "integrity": "sha256-CvZRb8uI75eKIqiYntR+k05Wk6tKlMS5w8sCA2AFna0=",
606128 "dependencies": [
607129 "const",
608130 "either",
@@ -611,6 +133,29 @@
611133 "tuples"
612134 ]
613135 },
136+ "catenable-lists": {
137+ "type": "registry",
138+ "version": "7.0.0",
139+ "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=",
140+ "dependencies": [
141+ "control",
142+ "foldable-traversable",
143+ "lists",
144+ "maybe",
145+ "prelude",
146+ "tuples",
147+ "unfoldable"
148+ ]
149+ },
150+ "console": {
151+ "type": "registry",
152+ "version": "6.1.0",
153+ "integrity": "sha256-3T5PPz1mATZmV0C4GzkCkpLlCa5xG7u8ZNgnLrrrI1Q=",
154+ "dependencies": [
155+ "effect",
156+ "prelude"
157+ ]
158+ },
614159 "const": {
615160 "type": "registry",
616161 "version": "6.0.0",
@@ -715,8 +260,8 @@
715260 },
716261 "exceptions": {
717262 "type": "registry",
718− "version": "6.0.0",
719− "integrity": "sha256-z65UIKhOyVXzV8QUxtky4OiYVESNPegtuBFUdAddbTM=",
263+ "version": "6.1.0",
264+ "integrity": "sha256-nRrr+N0wk0TUFOWLR7e1n1oxcXo/X345bQUtoY9lW6A=",
720265 "dependencies": [
721266 "effect",
722267 "either",
@@ -766,23 +311,34 @@
766311 "unsafe-coerce"
767312 ]
768313 },
769− "foreign-object": {
314+ "fork": {
770315 "type": "registry",
771− "version": "4.1.0",
772− "integrity": "sha256-x/Q7r80z/vmHRKvPwhYmHP/DnJciPtsyGfRTMzayKIU=",
316+ "version": "6.0.0",
317+ "integrity": "sha256-0keBVeZZg1eCZEy/En1CohVW829gQUqKtqBNIRa93/A=",
773318 "dependencies": [
774− "arrays",
319+ "aff",
320+ "prelude",
321+ "transformers"
322+ ]
323+ },
324+ "free": {
325+ "type": "registry",
326+ "version": "7.1.0",
327+ "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=",
328+ "dependencies": [
329+ "catenable-lists",
330+ "control",
331+ "distributive",
332+ "either",
333+ "exists",
775334 "foldable-traversable",
776− "functions",
777− "gen",
778− "lists",
335+ "invariant",
336+ "lazy",
779337 "maybe",
780338 "prelude",
781− "st",
782339 "tailrec",
340+ "transformers",
783341 "tuples",
784− "typelevel-prelude",
785− "unfoldable",
786342 "unsafe-coerce"
787343 ]
788344 },
@@ -933,6 +489,23 @@
933489 "prelude"
934490 ]
935491 },
492+ "mmorph": {
493+ "type": "registry",
494+ "version": "7.0.0",
495+ "integrity": "sha256-Cfltaq+tfYjuSoSBN/b8cSQZbvZTqIRs6ebQewbIkGs=",
496+ "dependencies": [
497+ "bifunctors",
498+ "either",
499+ "free",
500+ "functors",
501+ "identity",
502+ "maybe",
503+ "newtype",
504+ "prelude",
505+ "transformers",
506+ "tuples"
507+ ]
508+ },
936509 "newtype": {
937510 "type": "registry",
938511 "version": "5.0.0",
@@ -955,6 +528,16 @@
955528 "unfoldable"
956529 ]
957530 },
531+ "now": {
532+ "type": "registry",
533+ "version": "6.0.0",
534+ "integrity": "sha256-mTyG4s8tps3XIf0jjV/lME2/QlwMzrQly2wqfSSUM3o=",
535+ "dependencies": [
536+ "datetime",
537+ "effect",
538+ "prelude"
539+ ]
540+ },
958541 "nullable": {
959542 "type": "registry",
960543 "version": "6.0.0",
@@ -1029,10 +612,31 @@
1029612 "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1030613 "dependencies": []
1031614 },
615+ "pipes": {
616+ "type": "registry",
617+ "version": "8.0.0",
618+ "integrity": "sha256-dtUNpG8BGVr7pu6mQZyAnUAftUHgNS7f0QH3Kw5UTpQ=",
619+ "dependencies": [
620+ "aff",
621+ "control",
622+ "effect",
623+ "either",
624+ "foldable-traversable",
625+ "identity",
626+ "lists",
627+ "maybe",
628+ "mmorph",
629+ "newtype",
630+ "prelude",
631+ "tailrec",
632+ "transformers",
633+ "tuples"
634+ ]
635+ },
1032636 "prelude": {
1033637 "type": "registry",
1034− "version": "6.0.1",
1035− "integrity": "sha256-qicw3g9AkjIxyVp73o1dF9BiTCMWouPk9rYe6AASBCU=",
638+ "version": "6.0.2",
639+ "integrity": "sha256-IqykrDRquGUD/LRSad7y75hZv3bUsGQ4knc8tdez8Qw=",
1036640 "dependencies": []
1037641 },
1038642 "profunctor": {
@@ -1050,16 +654,6 @@
1050654 "tuples"
1051655 ]
1052656 },
1053− "record": {
1054− "type": "registry",
1055− "version": "4.0.0",
1056− "integrity": "sha256-WQ8fqp4X1wTA70J4qUcPW4Z7DCwx6e5KJnT/EuTdWSc=",
1057− "dependencies": [
1058− "functions",
1059− "prelude",
1060− "unsafe-coerce"
1061− ]
1062− },
1063657 "refs": {
1064658 "type": "registry",
1065659 "version": "6.0.0",
@@ -1077,6 +671,40 @@
1077671 "unsafe-coerce"
1078672 ]
1079673 },
674+ "spec": {
675+ "type": "registry",
676+ "version": "7.6.1",
677+ "integrity": "sha256-5GysDOS1gVbbGX75YXUdhww3h2S246Ck0sS6yt+ig+0=",
678+ "dependencies": [
679+ "aff",
680+ "ansi",
681+ "arrays",
682+ "avar",
683+ "bifunctors",
684+ "control",
685+ "datetime",
686+ "effect",
687+ "either",
688+ "exceptions",
689+ "foldable-traversable",
690+ "fork",
691+ "identity",
692+ "integers",
693+ "lists",
694+ "maybe",
695+ "newtype",
696+ "now",
697+ "ordered-collections",
698+ "parallel",
699+ "pipes",
700+ "prelude",
701+ "refs",
702+ "strings",
703+ "tailrec",
704+ "transformers",
705+ "tuples"
706+ ]
707+ },
1080708 "st": {
1081709 "type": "registry",
1082710 "version": "6.2.0",
@@ -1129,8 +757,8 @@
1129757 },
1130758 "transformers": {
1131759 "type": "registry",
1132− "version": "6.0.0",
1133− "integrity": "sha256-Nl3yXAIeeTzP6MyrkazkK7pbgeVqUm+ynLv7U5cpJYQ=",
760+ "version": "6.1.0",
761+ "integrity": "sha256-QJmgNT/y7ljPHSsXaW4zxF/982BFiQ90KNL1g/NtEOQ=",
1134762 "dependencies": [
1135763 "control",
1136764 "distributive",
@@ -1143,6 +771,7 @@
1143771 "maybe",
1144772 "newtype",
1145773 "prelude",
774+ "st",
1146775 "tailrec",
1147776 "tuples",
1148777 "unfoldable"
@@ -1164,15 +793,6 @@
1164793 "integrity": "sha256-BBqYOSnAKmayRvR5mtZoCf4SlYs2ipatD/+5nQx73aw=",
1165794 "dependencies": []
1166795 },
1167− "typelevel-prelude": {
1168− "type": "registry",
1169− "version": "7.0.0",
1170− "integrity": "sha256-+pHi14/40mTj/+lmCWqL0T7iXIXD1+NSg/KItyyoB0A=",
1171− "dependencies": [
1172− "prelude",
1173− "type-equality"
1174− ]
1175− },
1176796 "unfoldable": {
1177797 "type": "registry",
1178798 "version": "6.0.0",
@@ -1190,6 +810,18 @@
1190810 "version": "6.0.0",
1191811 "integrity": "sha256-0L1QsaY20OILjfU5TV72d3U/tSjhmL9hJ32WMp857Lk=",
1192812 "dependencies": []
813+ },
814+ "yoga-test-docker": {
815+ "type": "registry",
816+ "version": "0.1.0",
817+ "integrity": "sha256-fTd7UZ1W4w3R7sY2OJhoiFqf6Ma6ycQM/4lq9dQnkgs=",
818+ "dependencies": [
819+ "aff",
820+ "datetime",
821+ "effect",
822+ "exceptions",
823+ "prelude"
824+ ]
1193825 }
1194826 }
1195827}
spago.yaml+12−13
@@ -1,28 +1,27 @@
1−workspace:
2− package_set:
3− registry: 51.1.0
1+workspace: {}
42
53package:
64 name: yoga-redis
75 publish:
8− version: 0.1.0
6+ version: 0.1.1
97 license: MIT
108 location:
119 githubOwner: rowtype-yoga
1210 githubRepo: purescript-yoga-redis
1311 dependencies:
14− - aff: ">=7.0.0 <8.0.0"
15− - arrays: ">=7.0.0 <8.0.0"
16− - datetime: ">=6.0.0 <7.0.0"
12+ - aff: ">=7.1.0 <8.0.0"
13+ - datetime: ">=6.1.0 <7.0.0"
1714 - effect: ">=4.0.0 <5.0.0"
18− - either: ">=6.0.0 <7.0.0"
19− - foldable-traversable: ">=6.0.0 <7.0.0"
20− - foreign-object: ">=4.0.0 <5.0.0"
2115 - js-promise: ">=1.0.0 <2.0.0"
2216 - js-promise-aff: ">=1.0.0 <2.0.0"
2317 - maybe: ">=6.0.0 <7.0.0"
2418 - newtype: ">=5.0.0 <6.0.0"
2519 - nullable: ">=6.0.0 <7.0.0"
26− - prelude: ">=6.0.0 <7.0.0"
27− - record: ">=4.0.0 <5.0.0"
28− - transformers: ">=6.0.0 <7.0.0"
20+ - prelude: ">=6.0.2 <7.0.0"
21+ test:
22+ main: Test.Redis.Main
23+ dependencies:
24+ - spec: ">=7.0.0 <8.0.0"
25+ - exceptions: ">=6.0.0 <7.0.0"
26+ - console: ">=6.0.0 <7.0.0"
27+ - yoga-test-docker: ">=0.1.0 <1.0.0"
test/Main.pursnew file+444
@@ -0,0 +1,444 @@
1+module Test.Redis.Main where
2+
3+import Prelude
4+
5+import Data.Array (length)
6+import Data.Either (Either(..))
7+import Data.Maybe (Maybe(..), isJust)
8+import Data.Time.Duration (Milliseconds(..))
9+import Effect (Effect)
10+import Effect.Aff (Aff, delay, launchAff_, try, bracket)
11+import Effect.Class (liftEffect)
12+import Effect.Console (log)
13+import Node.Process (lookupEnv)
14+import Test.Spec (Spec, around, describe, it)
15+import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
16+import Test.Spec.Reporter.Console (consoleReporter)
17+import Test.Spec.Runner (runSpec)
18+import Yoga.Test.Docker as Docker
19+import Yoga.Redis as Redis
20+
21+-- Test configuration
22+testHost :: Redis.RedisHost
23+testHost = Redis.RedisHost "localhost"
24+
25+testPort :: Redis.RedisPort
26+testPort = Redis.RedisPort 6380 -- Test port from docker-compose.test.yml
27+
28+-- Helper to create and manage Redis connection
29+withRedis :: (Redis.Redis -> Aff Unit) -> Aff Unit
30+withRedis test = do
31+ redis <- liftEffect $ Redis.createRedis { host: testHost, port: testPort }
32+ -- Redis auto-connects, so no need to call connect explicitly
33+ -- Clean up test keys before each test
34+ _ <- try $ Redis.del [ Redis.RedisKey "test:*" ] redis
35+ test redis
36+ _ <- Redis.quit redis
37+ pure unit
38+
39+-- Helper to check if value is Left
40+isLeft :: forall a b. Either a b -> Boolean
41+isLeft (Left _) = true
42+isLeft _ = false
43+
44+spec :: Spec Unit
45+spec = do
46+ describe "Yoga.Redis Integration Tests" do
47+
48+ -- Connection Management Tests
49+ around withRedis do
50+ describe "Connection Management" do
51+ it "connects to Redis successfully" \redis -> do
52+ pong <- Redis.ping redis
53+ pong `shouldEqual` "PONG"
54+
55+ it "handles ping operations" \redis -> do
56+ pong <- Redis.ping redis
57+ pong `shouldSatisfy` (_ == "PONG")
58+
59+ describe "Connection Errors" do
60+ it "handles connection failures gracefully" do
61+ result <- try do
62+ redis <- liftEffect $ Redis.createRedis
63+ { host: Redis.RedisHost "invalid-host-that-does-not-exist"
64+ , port: Redis.RedisPort 9999
65+ }
66+ _ <- Redis.connect redis
67+ Redis.ping redis
68+ result `shouldSatisfy` isLeft
69+
70+ -- String Operations Tests
71+ around withRedis do
72+ describe "String Operations" do
73+ it "sets and gets values" \redis -> do
74+ let key = Redis.RedisKey "test:string:key1"
75+ let value = Redis.RedisValue "test-value"
76+ _ <- Redis.set key value {} redis
77+ result <- Redis.get key redis
78+ result `shouldEqual` Just value
79+
80+ it "returns Nothing for non-existent keys" \redis -> do
81+ result <- Redis.get (Redis.RedisKey "test:nonexistent:key") redis
82+ result `shouldEqual` Nothing
83+
84+ it "deletes keys successfully" \redis -> do
85+ let key = Redis.RedisKey "test:string:delete"
86+ let value = Redis.RedisValue "value-to-delete"
87+ _ <- Redis.set key value {} redis
88+ count <- Redis.del [ key ] redis
89+ count `shouldEqual` 1
90+ result <- Redis.get key redis
91+ result `shouldEqual` Nothing
92+
93+ it "checks key existence" \redis -> do
94+ let key = Redis.RedisKey "test:string:exists"
95+ let value = Redis.RedisValue "exists-value"
96+ _ <- Redis.set key value {} redis
97+ existsCount <- Redis.exists [ key ] redis
98+ existsCount `shouldEqual` 1
99+
100+ _ <- Redis.del [ key ] redis
101+ notExistsCount <- Redis.exists [ key ] redis
102+ notExistsCount `shouldEqual` 0
103+
104+ it "increments and decrements counters" \redis -> do
105+ let key = Redis.RedisKey "test:string:counter"
106+
107+ -- Increment from 0 to 1
108+ val1 <- Redis.incr key redis
109+ val1 `shouldEqual` 1
110+
111+ -- Increment by 5
112+ val2 <- Redis.incrBy key 5 redis
113+ val2 `shouldEqual` 6
114+
115+ -- Decrement by 1
116+ val3 <- Redis.decr key redis
117+ val3 `shouldEqual` 5
118+
119+ -- Decrement by 3
120+ val4 <- Redis.decrBy key 3 redis
121+ val4 `shouldEqual` 2
122+
123+ -- Hash Operations Tests
124+ around withRedis do
125+ describe "Hash Operations" do
126+ it "sets and gets hash fields" \redis -> do
127+ let hashKey = Redis.RedisKey "test:hash:user1"
128+ let field1 = Redis.RedisField "name"
129+ let value1 = Redis.RedisValue "Alice"
130+
131+ _ <- Redis.hset hashKey [ { field: field1, value: value1 } ] redis
132+ result <- Redis.hget hashKey field1 redis
133+ result `shouldEqual` Just value1
134+
135+ it "gets all hash fields" \redis -> do
136+ let hashKey = Redis.RedisKey "test:hash:user2"
137+ let
138+ fields =
139+ [ { field: Redis.RedisField "name", value: Redis.RedisValue "Bob" }
140+ , { field: Redis.RedisField "email", value: Redis.RedisValue "bob@example.com" }
141+ , { field: Redis.RedisField "age", value: Redis.RedisValue "30" }
142+ ]
143+
144+ _ <- Redis.hset hashKey fields redis
145+ result <- Redis.hgetall hashKey redis
146+ length result `shouldEqual` 3
147+
148+ it "checks if hash field exists" \redis -> do
149+ let hashKey = Redis.RedisKey "test:hash:exists"
150+ let field = Redis.RedisField "field1"
151+ let value = Redis.RedisValue "value1"
152+
153+ _ <- Redis.hset hashKey [ { field, value } ] redis
154+ exists <- Redis.hexists hashKey field redis
155+ exists `shouldEqual` true
156+
157+ notExists <- Redis.hexists hashKey (Redis.RedisField "nonexistent") redis
158+ notExists `shouldEqual` false
159+
160+ it "deletes hash fields" \redis -> do
161+ let hashKey = Redis.RedisKey "test:hash:delete"
162+ let field = Redis.RedisField "temp"
163+ let value = Redis.RedisValue "temporary"
164+
165+ _ <- Redis.hset hashKey [ { field, value } ] redis
166+ count <- Redis.hdel hashKey [ field ] redis
167+ count `shouldEqual` 1
168+
169+ result <- Redis.hget hashKey field redis
170+ result `shouldEqual` Nothing
171+
172+ it "gets hash length" \redis -> do
173+ let hashKey = Redis.RedisKey "test:hash:len"
174+ let
175+ fields =
176+ [ { field: Redis.RedisField "f1", value: Redis.RedisValue "v1" }
177+ , { field: Redis.RedisField "f2", value: Redis.RedisValue "v2" }
178+ , { field: Redis.RedisField "f3", value: Redis.RedisValue "v3" }
179+ ]
180+
181+ _ <- Redis.hset hashKey fields redis
182+ len <- Redis.hlen hashKey redis
183+ len `shouldEqual` 3
184+
185+ -- List Operations Tests
186+ around withRedis do
187+ describe "List Operations" do
188+ it "pushes and pops from lists (right push, left pop)" \redis -> do
189+ let key = Redis.RedisKey "test:list:queue"
190+ let
191+ values =
192+ [ Redis.RedisValue "first"
193+ , Redis.RedisValue "second"
194+ , Redis.RedisValue "third"
195+ ]
196+
197+ len <- Redis.rpush key values redis
198+ len `shouldEqual` 3
199+
200+ popped <- Redis.lpop key redis
201+ popped `shouldEqual` Just (Redis.RedisValue "first")
202+
203+ it "pushes to left and pops from right" \redis -> do
204+ let key = Redis.RedisKey "test:list:stack"
205+ let value1 = Redis.RedisValue "bottom"
206+ let value2 = Redis.RedisValue "top"
207+
208+ _ <- Redis.lpush key [ value1 ] redis
209+ _ <- Redis.lpush key [ value2 ] redis
210+
211+ popped <- Redis.rpop key redis
212+ popped `shouldEqual` Just value1
213+
214+ it "gets list range" \redis -> do
215+ let key = Redis.RedisKey "test:list:range"
216+ let
217+ values =
218+ [ Redis.RedisValue "a"
219+ , Redis.RedisValue "b"
220+ , Redis.RedisValue "c"
221+ , Redis.RedisValue "d"
222+ ]
223+
224+ _ <- Redis.rpush key values redis
225+ result <- Redis.lrange key 0 (-1) redis
226+ result `shouldEqual` values
227+
228+ partial <- Redis.lrange key 1 2 redis
229+ length partial `shouldEqual` 2
230+
231+ it "gets list length" \redis -> do
232+ let key = Redis.RedisKey "test:list:length"
233+ let values = [ Redis.RedisValue "1", Redis.RedisValue "2", Redis.RedisValue "3" ]
234+
235+ _ <- Redis.rpush key values redis
236+ len <- Redis.llen key redis
237+ len `shouldEqual` 3
238+
239+ -- Set Operations Tests
240+ around withRedis do
241+ describe "Set Operations" do
242+ it "adds and checks members" \redis -> do
243+ let key = Redis.RedisKey "test:set:members"
244+ let member1 = Redis.RedisValue "member1"
245+ let member2 = Redis.RedisValue "member2"
246+
247+ count <- Redis.sadd key [ member1, member2 ] redis
248+ count `shouldEqual` 2
249+
250+ isMember <- Redis.sismember key member1 redis
251+ isMember `shouldEqual` true
252+
253+ isNotMember <- Redis.sismember key (Redis.RedisValue "nonexistent") redis
254+ isNotMember `shouldEqual` false
255+
256+ it "gets all set members" \redis -> do
257+ let key = Redis.RedisKey "test:set:all"
258+ let
259+ members =
260+ [ Redis.RedisValue "apple"
261+ , Redis.RedisValue "banana"
262+ , Redis.RedisValue "cherry"
263+ ]
264+
265+ _ <- Redis.sadd key members redis
266+ result <- Redis.smembers key redis
267+ length result `shouldEqual` 3
268+
269+ it "removes set members" \redis -> do
270+ let key = Redis.RedisKey "test:set:remove"
271+ let member = Redis.RedisValue "to-remove"
272+
273+ _ <- Redis.sadd key [ member ] redis
274+ count <- Redis.srem key [ member ] redis
275+ count `shouldEqual` 1
276+
277+ isMember <- Redis.sismember key member redis
278+ isMember `shouldEqual` false
279+
280+ it "gets set cardinality" \redis -> do
281+ let key = Redis.RedisKey "test:set:card"
282+ let
283+ members =
284+ [ Redis.RedisValue "1"
285+ , Redis.RedisValue "2"
286+ , Redis.RedisValue "3"
287+ , Redis.RedisValue "4"
288+ ]
289+
290+ _ <- Redis.sadd key members redis
291+ card <- Redis.scard key redis
292+ card `shouldEqual` 4
293+
294+ -- TTL and Expiration Tests
295+ around withRedis do
296+ describe "TTL and Expiration" do
297+ it "sets TTL on keys with setex" \redis -> do
298+ let key = Redis.RedisKey "test:ttl:setex"
299+ let value = Redis.RedisValue "expires-soon"
300+ let ttl = Redis.TTLSeconds 5
301+
302+ _ <- Redis.setex key ttl value redis
303+ ttlResult <- Redis.ttl key redis
304+ ttlResult `shouldSatisfy` (\t -> t > 0 && t <= 5)
305+
306+ it "sets expiry with expire command" \redis -> do
307+ let key = Redis.RedisKey "test:ttl:expire"
308+ let value = Redis.RedisValue "will-expire"
309+
310+ _ <- Redis.set key value {} redis
311+ success <- Redis.expire key (Redis.TTLSeconds 10) redis
312+ success `shouldEqual` true
313+
314+ ttlResult <- Redis.ttl key redis
315+ ttlResult `shouldSatisfy` (\t -> t > 0 && t <= 10)
316+
317+ it "expires keys after TTL (short wait test)" \redis -> do
318+ let key = Redis.RedisKey "test:ttl:wait"
319+ let value = Redis.RedisValue "expires-fast"
320+
321+ _ <- Redis.setex key (Redis.TTLSeconds 1) value redis
322+
323+ -- Check it exists initially
324+ initial <- Redis.get key redis
325+ initial `shouldSatisfy` isJust
326+
327+ -- Wait for expiration
328+ delay (Milliseconds 1200.0)
329+
330+ result <- Redis.get key redis
331+ result `shouldEqual` Nothing
332+
333+ it "returns -2 TTL for non-existent keys" \redis -> do
334+ let key = Redis.RedisKey "test:ttl:nonexistent"
335+ ttlResult <- Redis.ttl key redis
336+ ttlResult `shouldEqual` (-2)
337+
338+ it "returns -1 TTL for keys without expiration" \redis -> do
339+ let key = Redis.RedisKey "test:ttl:persistent"
340+ let value = Redis.RedisValue "no-expiry"
341+
342+ _ <- Redis.set key value {} redis
343+ ttlResult <- Redis.ttl key redis
344+ ttlResult `shouldEqual` (-1)
345+
346+ -- Sorted Set Operations Tests
347+ around withRedis do
348+ describe "Sorted Set Operations" do
349+ it "adds members with scores" \redis -> do
350+ let key = Redis.RedisKey "test:zset:leaderboard"
351+ let
352+ members =
353+ [ { score: Redis.RedisScore 100.0, value: Redis.RedisValue "player1" }
354+ , { score: Redis.RedisScore 200.0, value: Redis.RedisValue "player2" }
355+ , { score: Redis.RedisScore 150.0, value: Redis.RedisValue "player3" }
356+ ]
357+
358+ count <- Redis.zadd key members {} redis
359+ count `shouldEqual` 3
360+
361+ it "gets sorted set range with scores" \redis -> do
362+ let key = Redis.RedisKey "test:zset:range"
363+ let
364+ members =
365+ [ { score: Redis.RedisScore 1.0, value: Redis.RedisValue "a" }
366+ , { score: Redis.RedisScore 2.0, value: Redis.RedisValue "b" }
367+ , { score: Redis.RedisScore 3.0, value: Redis.RedisValue "c" }
368+ ]
369+
370+ _ <- Redis.zadd key members {} redis
371+ result <- Redis.zrange key 0 (-1) redis
372+ length result `shouldEqual` 3
373+
374+ -- Verify order (should be sorted by score)
375+ case result of
376+ [ first, _, _ ] -> first.value `shouldEqual` Redis.RedisValue "a"
377+ _ -> pure unit
378+
379+ it "gets member score" \redis -> do
380+ let key = Redis.RedisKey "test:zset:score"
381+ let member = { score: Redis.RedisScore 42.5, value: Redis.RedisValue "test" }
382+
383+ _ <- Redis.zadd key [ member ] {} redis
384+ score <- Redis.zscore key member.value redis
385+ score `shouldEqual` Just (Redis.RedisScore 42.5)
386+
387+ it "removes sorted set members" \redis -> do
388+ let key = Redis.RedisKey "test:zset:remove"
389+ let member = { score: Redis.RedisScore 10.0, value: Redis.RedisValue "remove-me" }
390+
391+ _ <- Redis.zadd key [ member ] {} redis
392+ count <- Redis.zrem key [ member.value ] redis
393+ count `shouldEqual` 1
394+
395+ score <- Redis.zscore key member.value redis
396+ score `shouldEqual` Nothing
397+
398+ it "gets sorted set cardinality" \redis -> do
399+ let key = Redis.RedisKey "test:zset:card"
400+ let
401+ members =
402+ [ { score: Redis.RedisScore 1.0, value: Redis.RedisValue "x" }
403+ , { score: Redis.RedisScore 2.0, value: Redis.RedisValue "y" }
404+ ]
405+
406+ _ <- Redis.zadd key members {} redis
407+ card <- Redis.zcard key redis
408+ card `shouldEqual` 2
409+
410+ -- Pub/Sub Tests
411+ around withRedis do
412+ describe "Pub/Sub Operations" do
413+ it "publishes messages to channel" \redis -> do
414+ let channel = Redis.RedisChannel "test:pubsub:channel"
415+ let message = Redis.RedisValue "test-message"
416+
417+ -- Publishing to a channel with no subscribers returns 0
418+ subscribers <- Redis.publish channel message redis
419+ subscribers `shouldSatisfy` (\s -> s >= 0)
420+
421+main :: Effect Unit
422+main = launchAff_ do
423+ -- Check if we should skip Docker (CI provides Redis)
424+ skipDocker <- liftEffect $ lookupEnv "SKIP_DOCKER"
425+
426+ case skipDocker of
427+ Just _ -> do
428+ -- CI mode: Redis is already running
429+ liftEffect $ log "\n🧪 Starting Redis Integration Tests (CI mode)\n"
430+ runSpec [ consoleReporter ] spec
431+ Nothing -> do
432+ -- Local mode: Start Docker Compose
433+ liftEffect $ log "\n🧪 Starting Redis Integration Tests (with Docker)\n"
434+ bracket
435+ ( do
436+ liftEffect $ log "⏳ Starting Redis and waiting for it to be ready..."
437+ Docker.startService "docker-compose.test.yml" 30
438+ liftEffect $ log "✅ Redis is ready!\n"
439+ )
440+ ( \_ -> do
441+ Docker.stopService "docker-compose.test.yml"
442+ liftEffect $ log "✅ Cleanup complete\n"
443+ )
444+ (\_ -> runSpec [ consoleReporter ] spec)