git.iko.soy/rowtype-yoga/purescript-yoga-redis-om
on main help sign in
Initial: yoga-redis-om
commit72b44c5ef559c35a08f0cff1b0e2be22593a2e72 authorMark <mark.eibes@gmail.com> dateWed 4 Feb 2026, 21:58 (8 months ago)
7 files changed+540
unifiedsplit
.github/workflows/ci.yml+9 .gitignore+3 README.md+219 package.json+18 spago.yaml+29 src/Yoga/Redis/Om.purs+209 src/Yoga/Redis/OmLayer.purs+53
.github/workflows/ci.ymlnew file+9
@@ -0,0 +1,9 @@
1+name: CI
2+on: [push, pull_request]
3+jobs:
4+ build:
5+ runs-on: ubuntu-latest
6+ steps:
7+ - uses: actions/checkout@v4
8+ - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
9+ - run: spago build
.gitignorenew file+3
@@ -0,0 +1,3 @@
1+node_modules/
2+.spago/
3+output*/
README.mdnew file+219
@@ -0,0 +1,219 @@
1+# yoga-redis-om
2+
3+Om-wrapped Redis operations for PureScript using the Yoga Om effect system.
4+
5+## Overview
6+
7+This package provides Om-wrapped versions of Redis operations from [yoga-redis](../yoga-redis), allowing you to use Redis within the Om effect system without manually threading the Redis client through your code.
8+
9+## Installation
10+
11+```bash
12+spago install yoga-redis-om yoga-om-core yoga-om-layer
13+```
14+
15+## Usage
16+
17+### Basic Example
18+
19+```purescript
20+import Yoga.Redis.Om as Redis
21+import Yoga.Redis.OmLayer as RedisLayer
22+import Yoga.Om (runOm)
23+
24+main = launchAff_ do
25+ let config =
26+ { host: "localhost"
27+ , port: 6379
28+ , password: Nothing
29+ , db: Nothing
30+ , keyPrefix: Nothing
31+ , connectTimeout: Nothing
32+ }
33+
34+ runOm (RedisLayer.live config) do
35+ -- All operations use the implicit Redis client from the environment
36+ Redis.set "mykey" "myvalue"
37+ value <- Redis.get "mykey"
38+ log $ "Retrieved: " <> show value
39+```
40+
41+### With Om Layers
42+
43+```purescript
44+import Yoga.Om ((:>), type (:>))
45+import Yoga.Redis.Om as Redis
46+import Yoga.Redis.OmLayer (RedisEnv)
47+
48+-- Your application needs Redis
49+type AppEnv = RedisEnv :> ()
50+
51+myApp :: forall r. Has RedisEnv r => Om r Unit
52+myApp = do
53+ Redis.set "counter" "0"
54+ Redis.incr "counter"
55+ count <- Redis.get "counter"
56+ log $ "Count: " <> show count
57+
58+main = launchAff_ do
59+ runOm (RedisLayer.live config) myApp
60+```
61+
62+### Hash Operations
63+
64+```purescript
65+import Yoga.Redis.Om as Redis
66+
67+saveUser :: forall r. Has RedisEnv r => Om r Unit
68+saveUser = do
69+ Redis.hset "user:1" "name" "Alice"
70+ Redis.hset "user:1" "email" "alice@example.com"
71+ Redis.hset "user:1" "age" "30"
72+
73+ user <- Redis.hgetall "user:1"
74+ log $ "User data: " <> show user
75+```
76+
77+### List Operations
78+
79+```purescript
80+import Yoga.Redis.Om as Redis
81+
82+manageQueue :: forall r. Has RedisEnv r => Om r Unit
83+manageQueue = do
84+ -- Add items to queue
85+ Redis.lpush "tasks" ["task1", "task2", "task3"]
86+
87+ -- Get queue length
88+ length <- Redis.llen "tasks"
89+ log $ "Queue length: " <> show length
90+
91+ -- Process items
92+ task <- Redis.rpop "tasks"
93+ case task of
94+ Just t -> log $ "Processing: " <> t
95+ Nothing -> log "Queue is empty"
96+```
97+
98+## API Reference
99+
100+All operations from [yoga-redis](../yoga-redis) are available with Om wrappers. The key difference is that you don't need to pass the Redis client explicitly - it's provided by the Om environment.
101+
102+### String Operations
103+
104+- `get` - Get value by key
105+- `set` - Set key to value
106+- `setex` - Set key with expiry
107+- `del` - Delete key(s)
108+- `exists` - Check if key exists
109+- `incr` / `incrBy` - Increment
110+- `decr` / `decrBy` - Decrement
111+
112+### Hash Operations
113+
114+- `hget` - Get field from hash
115+- `hset` - Set field in hash
116+- `hgetall` - Get all fields
117+- `hdel` - Delete field
118+- `hexists` - Check field exists
119+- `hkeys` - Get all keys
120+- `hlen` - Get number of fields
121+
122+### List Operations
123+
124+- `lpush` / `rpush` - Push to list
125+- `lpop` / `rpop` - Pop from list
126+- `lrange` - Get range
127+- `llen` - Get length
128+
129+### Set Operations
130+
131+- `sadd` - Add members
132+- `srem` - Remove members
133+- `smembers` - Get all members
134+- `sismember` - Check membership
135+- `scard` - Get size
136+
137+### Sorted Set Operations
138+
139+- `zadd` - Add with score
140+- `zrem` - Remove members
141+- `zrange` - Get range
142+- `zcard` - Get size
143+- `zscore` - Get score
144+
145+### Other
146+
147+- `ping` - Test connection
148+- `publish` - Publish to channel
149+- `expire` - Set expiry
150+- `ttl` - Get time to live
151+
152+## Layer Management
153+
154+### Creating a Redis Layer
155+
156+```purescript
157+import Yoga.Redis.OmLayer as RedisLayer
158+
159+-- Simple configuration
160+let config = { url: "redis://localhost:6379" }
161+let layer = RedisLayer.live config
162+
163+-- Detailed configuration
164+let config =
165+ { host: "localhost"
166+ , port: 6379
167+ , password: Just "secret"
168+ , db: Just 0
169+ , keyPrefix: Just "myapp:"
170+ , connectTimeout: Just (Milliseconds 10000.0)
171+ }
172+let layer = RedisLayer.live config
173+```
174+
175+### Type-Safe Environment Requirements
176+
177+The Om system ensures at compile time that your Redis layer is provided:
178+
179+```purescript
180+-- This will compile
181+runOm (RedisLayer.live config) Redis.ping
182+
183+-- This will NOT compile (missing Redis layer)
184+runOm emptyLayer Redis.ping
185+```
186+
187+## Advantages Over Raw Bindings
188+
189+1. **No Client Threading** - Redis client is implicit in the environment
190+2. **Composable** - Easily combine with other Om layers
191+3. **Type-Safe** - Compiler ensures required layers are provided
192+4. **Testable** - Easy to mock Redis layer for testing
193+5. **Clean Code** - Less boilerplate, more readable
194+
195+## Testing
196+
197+You can create mock Redis layers for testing:
198+
199+```purescript
200+import Yoga.Redis.OmLayer as RedisLayer
201+
202+mockRedisLayer :: Layer (RedisEnv :> ()) (RedisEnv :> ())
203+mockRedisLayer = -- implement mock behavior
204+
205+spec = describe "MyApp" do
206+ it "works with Redis" do
207+ result <- runOm mockRedisLayer myApp
208+ result `shouldEqual` expected
209+```
210+
211+## Related Packages
212+
213+- [yoga-redis](../yoga-redis) - Raw Redis bindings
214+- [yoga-om-core](https://github.com/rowtype-yoga/purescript-yoga-om) - Om effect system core
215+- [yoga-om-layer](https://github.com/rowtype-yoga/purescript-yoga-om) - Om layer management
216+
217+## License
218+
219+MIT
package.jsonnew file+18
@@ -0,0 +1,18 @@
1+{
2+ "name": "purescript-yoga-redis-om",
3+ "version": "0.1.0",
4+ "description": "Om-wrapped Redis operations for PureScript",
5+ "license": "MIT",
6+ "repository": {
7+ "type": "git",
8+ "url": "https://github.com/rowtype-yoga/purescript-yoga-backend-stack.git",
9+ "directory": "packages/yoga-redis-om"
10+ },
11+ "keywords": [
12+ "purescript",
13+ "redis",
14+ "om",
15+ "effect-system",
16+ "yoga"
17+ ]
18+}
spago.yamlnew file+29
@@ -0,0 +1,29 @@
1+workspace:
2+ package_set:
3+ registry: 51.1.0
4+
5+package:
6+ name: yoga-redis-om
7+ publish:
8+ version: 0.1.0
9+ license: MIT
10+ location:
11+ githubOwner: rowtype-yoga
12+ githubRepo: purescript-yoga-redis-om
13+ dependencies:
14+ - yoga-redis: "*"
15+ - yoga-om-core: ">=1.0.0 <2.0.0"
16+ - yoga-om-layer: ">=1.0.0 <2.0.0"
17+ - aff: ">=7.0.0 <8.0.0"
18+ - arrays: ">=7.0.0 <8.0.0"
19+ - datetime: ">=6.0.0 <7.0.0"
20+ - effect: ">=4.0.0 <5.0.0"
21+ - either: ">=6.0.0 <7.0.0"
22+ - foldable-traversable: ">=6.0.0 <7.0.0"
23+ - foreign-object: ">=4.0.0 <5.0.0"
24+ - maybe: ">=6.0.0 <7.0.0"
25+ - newtype: ">=5.0.0 <6.0.0"
26+ - nullable: ">=6.0.0 <7.0.0"
27+ - prelude: ">=6.0.0 <7.0.0"
28+ - record: ">=4.0.0 <5.0.0"
29+ - transformers: ">=6.0.0 <7.0.0"
src/Yoga/Redis/Om.pursnew file+209
@@ -0,0 +1,209 @@
1+module Yoga.Redis.Om
2+ ( -- * Om-friendly operations
3+ ping
4+ , get
5+ , set
6+ , setex
7+ , del
8+ , exists
9+ , expire
10+ , ttl
11+ , incr
12+ , incrBy
13+ , decr
14+ , decrBy
15+ -- * Hash operations
16+ , hget
17+ , hset
18+ , hgetall
19+ , hdel
20+ , hexists
21+ , hkeys
22+ , hlen
23+ -- * List operations
24+ , lpush
25+ , rpush
26+ , lpop
27+ , rpop
28+ , lrange
29+ , llen
30+ -- * Set operations
31+ , sadd
32+ , srem
33+ , smembers
34+ , sismember
35+ , scard
36+ -- * Sorted set operations
37+ , zadd
38+ , zrem
39+ , zrange
40+ , zcard
41+ , zscore
42+ -- * Pub/Sub
43+ , publish
44+ -- * Re-exports from base module
45+ , module Yoga.Redis
46+ ) where
47+
48+import Prelude
49+
50+import Data.Maybe (Maybe)
51+import Effect.Aff.Class (liftAff)
52+import Prim.Row (class Union)
53+import Yoga.Redis as R
54+import Yoga.Redis (Redis, RedisKey, RedisValue, RedisField, RedisChannel, RedisScore, TTLSeconds, TTLMilliseconds, ZAddMember, ZRangeMember)
55+import Yoga.Om (Om)
56+
57+-- | Om-friendly ping
58+ping :: forall ctx err. R.Redis -> Om ctx err String
59+ping redis = liftAff $ R.ping redis
60+
61+-- | Om-friendly get
62+get :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Maybe R.RedisValue)
63+get key redis = liftAff $ R.get key redis
64+
65+-- | Om-friendly set
66+set :: forall ctx err opts opts_. Union opts opts_ R.SetOptionsImpl => R.RedisKey -> R.RedisValue -> { | opts } -> R.Redis -> Om ctx err Unit
67+set key value opts redis = liftAff $ R.set key value opts redis
68+
69+-- | Om-friendly setex
70+setex :: forall ctx err. R.RedisKey -> R.TTLSeconds -> R.RedisValue -> R.Redis -> Om ctx err Unit
71+setex key ttlVal value redis = liftAff $ R.setex key ttlVal value redis
72+
73+-- | Om-friendly del
74+del :: forall ctx err. Array R.RedisKey -> R.Redis -> Om ctx err Int
75+del keys redis = liftAff $ R.del keys redis
76+
77+-- | Om-friendly exists
78+exists :: forall ctx err. Array R.RedisKey -> R.Redis -> Om ctx err Int
79+exists keys redis = liftAff $ R.exists keys redis
80+
81+-- | Om-friendly expire
82+expire :: forall ctx err. R.RedisKey -> R.TTLSeconds -> R.Redis -> Om ctx err Boolean
83+expire key ttlVal redis = liftAff $ R.expire key ttlVal redis
84+
85+-- | Om-friendly ttl
86+ttl :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
87+ttl key redis = liftAff $ R.ttl key redis
88+
89+-- | Om-friendly incr
90+incr :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
91+incr key redis = liftAff $ R.incr key redis
92+
93+-- | Om-friendly incrBy
94+incrBy :: forall ctx err. R.RedisKey -> Int -> R.Redis -> Om ctx err Int
95+incrBy key increment redis = liftAff $ R.incrBy key increment redis
96+
97+-- | Om-friendly decr
98+decr :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
99+decr key redis = liftAff $ R.decr key redis
100+
101+-- | Om-friendly decrBy
102+decrBy :: forall ctx err. R.RedisKey -> Int -> R.Redis -> Om ctx err Int
103+decrBy key decrement redis = liftAff $ R.decrBy key decrement redis
104+
105+-- Hash operations
106+
107+-- | Om-friendly hget
108+hget :: forall ctx err. R.RedisKey -> R.RedisField -> R.Redis -> Om ctx err (Maybe R.RedisValue)
109+hget key field redis = liftAff $ R.hget key field redis
110+
111+-- | Om-friendly hset
112+hset :: forall ctx err. R.RedisKey -> Array { field :: R.RedisField, value :: R.RedisValue } -> R.Redis -> Om ctx err Int
113+hset key fieldValues redis = liftAff $ R.hset key fieldValues redis
114+
115+-- | Om-friendly hgetall
116+hgetall :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Array { field :: R.RedisField, value :: R.RedisValue })
117+hgetall key redis = liftAff $ R.hgetall key redis
118+
119+-- | Om-friendly hdel
120+hdel :: forall ctx err. R.RedisKey -> Array R.RedisField -> R.Redis -> Om ctx err Int
121+hdel key fields redis = liftAff $ R.hdel key fields redis
122+
123+-- | Om-friendly hexists
124+hexists :: forall ctx err. R.RedisKey -> R.RedisField -> R.Redis -> Om ctx err Boolean
125+hexists key field redis = liftAff $ R.hexists key field redis
126+
127+-- | Om-friendly hkeys
128+hkeys :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Array R.RedisField)
129+hkeys key redis = liftAff $ R.hkeys key redis
130+
131+-- | Om-friendly hlen
132+hlen :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
133+hlen key redis = liftAff $ R.hlen key redis
134+
135+-- List operations
136+
137+-- | Om-friendly lpush
138+lpush :: forall ctx err. R.RedisKey -> Array R.RedisValue -> R.Redis -> Om ctx err Int
139+lpush key values redis = liftAff $ R.lpush key values redis
140+
141+-- | Om-friendly rpush
142+rpush :: forall ctx err. R.RedisKey -> Array R.RedisValue -> R.Redis -> Om ctx err Int
143+rpush key values redis = liftAff $ R.rpush key values redis
144+
145+-- | Om-friendly lpop
146+lpop :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Maybe R.RedisValue)
147+lpop key redis = liftAff $ R.lpop key redis
148+
149+-- | Om-friendly rpop
150+rpop :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Maybe R.RedisValue)
151+rpop key redis = liftAff $ R.rpop key redis
152+
153+-- | Om-friendly lrange
154+lrange :: forall ctx err. R.RedisKey -> Int -> Int -> R.Redis -> Om ctx err (Array R.RedisValue)
155+lrange key start stop redis = liftAff $ R.lrange key start stop redis
156+
157+-- | Om-friendly llen
158+llen :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
159+llen key redis = liftAff $ R.llen key redis
160+
161+-- Set operations
162+
163+-- | Om-friendly sadd
164+sadd :: forall ctx err. R.RedisKey -> Array R.RedisValue -> R.Redis -> Om ctx err Int
165+sadd key members redis = liftAff $ R.sadd key members redis
166+
167+-- | Om-friendly srem
168+srem :: forall ctx err. R.RedisKey -> Array R.RedisValue -> R.Redis -> Om ctx err Int
169+srem key members redis = liftAff $ R.srem key members redis
170+
171+-- | Om-friendly smembers
172+smembers :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err (Array R.RedisValue)
173+smembers key redis = liftAff $ R.smembers key redis
174+
175+-- | Om-friendly sismember
176+sismember :: forall ctx err. R.RedisKey -> R.RedisValue -> R.Redis -> Om ctx err Boolean
177+sismember key member redis = liftAff $ R.sismember key member redis
178+
179+-- | Om-friendly scard
180+scard :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
181+scard key redis = liftAff $ R.scard key redis
182+
183+-- Sorted set operations
184+
185+-- | Om-friendly zadd
186+zadd :: forall ctx err opts opts_. Union opts opts_ R.ZAddOptionsImpl => R.RedisKey -> Array R.ZAddMember -> { | opts } -> R.Redis -> Om ctx err Int
187+zadd key members opts redis = liftAff $ R.zadd key members opts redis
188+
189+-- | Om-friendly zrem
190+zrem :: forall ctx err. R.RedisKey -> Array R.RedisValue -> R.Redis -> Om ctx err Int
191+zrem key members redis = liftAff $ R.zrem key members redis
192+
193+-- | Om-friendly zrange
194+zrange :: forall ctx err. R.RedisKey -> Int -> Int -> R.Redis -> Om ctx err (Array R.ZRangeMember)
195+zrange key start stop redis = liftAff $ R.zrange key start stop redis
196+
197+-- | Om-friendly zcard
198+zcard :: forall ctx err. R.RedisKey -> R.Redis -> Om ctx err Int
199+zcard key redis = liftAff $ R.zcard key redis
200+
201+-- | Om-friendly zscore
202+zscore :: forall ctx err. R.RedisKey -> R.RedisValue -> R.Redis -> Om ctx err (Maybe R.RedisScore)
203+zscore key member redis = liftAff $ R.zscore key member redis
204+
205+-- Pub/Sub operations
206+
207+-- | Om-friendly publish
208+publish :: forall ctx err. R.RedisChannel -> R.RedisValue -> R.Redis -> Om ctx err Int
209+publish channel message redis = liftAff $ R.publish channel message redis
src/Yoga/Redis/OmLayer.pursnew file+53
@@ -0,0 +1,53 @@
1+module Yoga.Redis.OmLayer
2+ ( RedisConfig
3+ , RedisL
4+ , redisLayer
5+ , redisLayer'
6+ ) where
7+
8+import Prelude
9+
10+import Effect.Aff.Class (liftAff)
11+import Effect.Class (liftEffect)
12+import Effect.Console as Console
13+import Yoga.Redis (Redis)
14+import Yoga.Redis as R
15+import Yoga.Om as Om
16+import Yoga.Om.Layer (OmLayer, makeLayer)
17+
18+-- | Redis configuration
19+type RedisConfig =
20+ { host :: R.RedisHost
21+ , port :: R.RedisPort
22+ }
23+
24+-- | Row type for Redis service
25+type RedisL r = (redis :: Redis | r)
26+
27+-- | Create a Redis layer that provides Redis connection as a service
28+-- | Requires RedisConfig in context
29+redisLayer :: forall r. OmLayer (redisConfig :: RedisConfig | r) (RedisL ()) ()
30+redisLayer = makeLayer do
31+ { redisConfig } <- Om.ask
32+ redis <- liftEffect $ R.createRedis redisConfig
33+ pong <- liftAff $ R.ping redis
34+ liftEffect $ Console.log $
35+ "⚡ Redis connected: " <> show redisConfig.host <> ":" <> show redisConfig.port
36+ liftEffect $ Console.log $
37+ " Status: " <> pong
38+ pure { redis }
39+
40+-- | Create a Redis layer with inline config
41+-- | Useful when you don't need config from context
42+redisLayer'
43+ :: forall r
44+ . RedisConfig
45+ -> OmLayer r (RedisL ()) ()
46+redisLayer' config = makeLayer do
47+ redis <- liftEffect $ R.createRedis config
48+ pong <- liftAff $ R.ping redis
49+ liftEffect $ Console.log $
50+ "⚡ Redis connected: " <> show config.host <> ":" <> show config.port
51+ liftEffect $ Console.log $
52+ " Status: " <> pong
53+ pure { redis }