better_auth-redis-storage 0.1.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fec3e6f91d2678257084d87d27d230b401dde8dc9c4d852e47d5a0bc5c910de8
4
- data.tar.gz: 5eee5736114ba877ba5e7308ba5da7d400a08686b9b46d01a98e57fda4bb24cc
3
+ metadata.gz: 93c01dd5ff213cf3095641e36d70bd2b4e0d80ce7d344688ed830e01d4ad43e6
4
+ data.tar.gz: 9e416e99e8c8d4df05e71c5cf1af743f43e50d99009beefb502280c49c42e726
5
5
  SHA512:
6
- metadata.gz: 3890b8e529be3471125825d684da70a0280988c6c554683bfbabba6cc26612ca0d3508377ae49592c323101b2f52feb363807e996236b2c2a76640a949253226
7
- data.tar.gz: bf77b96b0f29ad9c86a4ae11fc00c92a6a6a9f28aa8e02d233c4ae38a39285a63bd126b90c1b41dadc4490f993fdc88f1d0829e8b5af8fa27cff157d5f8e56fe
6
+ metadata.gz: dd78bfd531a72e64fb05a338261f37daaedaf5b3c32cb262b77b92bfdb6b611194e1d64b583f18c1922520affe52f6ab9f247de1613348be13b6b52a7234df41
7
+ data.tar.gz: 2d54b9ffe8517dd5e4729c3ef6a010b60f57015b9b05f52a3e7fab281b56037ee544e0936dca420249d4e3aeff84721524639e01cccc30fa4913184158f4496a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0 - 2026-04-29
6
+
7
+ - Add `BetterAuth.redis_storage` and `BetterAuth::RedisStorage.redisStorage` builders for upstream-shaped Redis storage configuration.
8
+ - Add optional `scan_count:` support to use Redis `SCAN` instead of upstream-compatible `KEYS`.
9
+ - Split real Redis coverage into a `REDIS_INTEGRATION=1` integration suite and expand secondary-storage compatibility tests.
10
+
3
11
  ## 0.1.0
4
12
 
5
13
  - Initial Redis secondary storage package for Better Auth Ruby.
6
-
data/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Redis secondary storage package for Better Auth Ruby.
4
4
 
5
+ This gem tracks the server-side behavior of upstream `@better-auth/redis-storage`
6
+ pinned at Better Auth `v1.6.9`. The Ruby gem versions independently from the
7
+ upstream npm package; `BetterAuth::RedisStorage::VERSION` is the Ruby gem
8
+ version.
9
+
5
10
  ## Installation
6
11
 
7
12
  Add the gem and require the package before configuring auth:
@@ -19,13 +24,114 @@ redis = Redis.new(url: ENV.fetch("REDIS_URL"))
19
24
  auth = BetterAuth.auth(
20
25
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
21
26
  database: :memory,
22
- secondary_storage: BetterAuth::RedisStorage.new(client: redis)
27
+ secondary_storage: BetterAuth.redis_storage(client: redis)
28
+ )
29
+ ```
30
+
31
+ The canonical Ruby form is also supported:
32
+
33
+ ```ruby
34
+ storage = BetterAuth::RedisStorage.new(client: redis)
35
+ ```
36
+
37
+ For upstream-shaped call sites, use `BetterAuth.redis_storage(client: redis)` or
38
+ the camelCase class alias:
39
+
40
+ ```ruby
41
+ storage = BetterAuth::RedisStorage.redisStorage(client: redis)
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ ```ruby
47
+ storage = BetterAuth::RedisStorage.new(
48
+ client: redis,
49
+ key_prefix: "better-auth:",
50
+ scan_count: nil
51
+ )
52
+ ```
53
+
54
+ `client` must respond to `get`, `set`, `setex`, `del`, and `keys`. It should
55
+ also respond to `scan` when `scan_count:` is configured. This matches the
56
+ interfaces exposed by the `redis` and `redis-namespace` gems.
57
+
58
+ `key_prefix` defaults to `"better-auth:"`. Passing `nil` falls back to the
59
+ default. Any other value, including the empty string, is honored verbatim.
60
+ Redis databases are not isolation boundaries for shared clients; applications
61
+ sharing a Redis instance should use distinct prefixes.
62
+
63
+ `scan_count` is a Ruby-only opt-in for large Redis databases. By default the gem
64
+ uses `KEYS "#{key_prefix}*"` to match upstream exactly. Set `scan_count:` to a
65
+ positive count such as `100`, `500`, or `1000` to use `SCAN` instead:
66
+
67
+ ```ruby
68
+ storage = BetterAuth::RedisStorage.new(client: redis, scan_count: 500)
69
+ ```
70
+
71
+ ## Behavior
72
+
73
+ The storage object implements the Better Auth secondary storage contract:
74
+
75
+ ```ruby
76
+ storage.get(key)
77
+ storage.set(key, value, ttl = nil)
78
+ storage.delete(key)
79
+ storage.list_keys
80
+ storage.clear
81
+ ```
82
+
83
+ `listKeys` is available as a camelCase alias for upstream parity.
84
+
85
+ TTL handling for `set(key, value, ttl)`:
86
+
87
+ | TTL value | Redis command |
88
+ | --- | --- |
89
+ | `nil`, non-numeric strings, `0`, negative numbers | `set(prefixed_key, value)` |
90
+ | Positive `Integer` | `setex(prefixed_key, ttl, value)` |
91
+ | Positive `Float` | `setex(prefixed_key, ttl.to_i, value)` |
92
+ | Positive numeric `String` | `setex(prefixed_key, ttl.to_i, value)` |
93
+
94
+ `set`, `delete`, and `clear` return `nil`, mirroring upstream's `Promise<void>`
95
+ contract in Ruby form. Tests and applications should assert stored values via
96
+ `get` rather than relying on truthy return values.
97
+
98
+ `clear` intentionally differs from upstream when there are no matching keys:
99
+ upstream calls `del(...keys)` even when `keys` is empty, while this Ruby gem
100
+ skips `del` to avoid Redis `ERR wrong number of arguments for 'del'`.
101
+
102
+ ## Better Auth Usage
103
+
104
+ `secondary_storage` is used by Better Auth for session payload storage,
105
+ active-session indexes, verification values, and rate limiting when
106
+ `rate_limit: { storage: "secondary-storage" }` is configured.
107
+
108
+ ```ruby
109
+ auth = BetterAuth.auth(
110
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
111
+ database: :memory,
112
+ secondary_storage: BetterAuth.redis_storage(client: redis),
113
+ rate_limit: { storage: "secondary-storage", enabled: true }
23
114
  )
24
115
  ```
25
116
 
26
- ## Notes
117
+ Custom secondary storage backends should implement:
27
118
 
28
- This package depends on the official `redis` gem. Keeping Redis storage outside `better_auth` avoids installing Redis client dependencies for applications that do not use secondary storage.
119
+ - `get(key)`
120
+ - `set(key, value, ttl = nil)`
121
+ - `delete(key)`
122
+ - Optional: `list_keys` or `listKeys`
123
+ - Optional: `clear`
29
124
 
30
- `secondary_storage` is used by Better Auth for session payload storage, active-session indexes, and rate limiting when `rate_limit: { storage: "secondary-storage" }` is configured.
125
+ ## Testing
31
126
 
127
+ The normal unit suite skips real Redis unless explicitly enabled:
128
+
129
+ ```bash
130
+ bundle exec rake test
131
+ ```
132
+
133
+ Run the Redis integration suite with:
134
+
135
+ ```bash
136
+ REDIS_INTEGRATION=1 REDIS_URL=redis://localhost:6379/15 bundle exec rake test:integration
137
+ ```
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BetterAuth
4
4
  class RedisStorage
5
- VERSION = "0.1.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -4,18 +4,28 @@ require "better_auth"
4
4
  require_relative "redis_storage/version"
5
5
 
6
6
  module BetterAuth
7
+ def self.redis_storage(client:, key_prefix: RedisStorage::DEFAULT_KEY_PREFIX, scan_count: nil)
8
+ RedisStorage.new(client: client, key_prefix: key_prefix, scan_count: scan_count)
9
+ end
10
+
7
11
  class RedisStorage
8
12
  DEFAULT_KEY_PREFIX = "better-auth:"
13
+ SCAN_DEFAULT_COUNT = 100
9
14
 
10
- attr_reader :client, :key_prefix
15
+ attr_reader :client, :key_prefix, :scan_count
16
+
17
+ def self.build(client:, key_prefix: DEFAULT_KEY_PREFIX, scan_count: nil)
18
+ new(client: client, key_prefix: key_prefix, scan_count: scan_count)
19
+ end
11
20
 
12
- def self.build(client:, key_prefix: DEFAULT_KEY_PREFIX)
13
- new(client: client, key_prefix: key_prefix)
21
+ def self.redisStorage(client:, key_prefix: DEFAULT_KEY_PREFIX, scan_count: nil)
22
+ new(client: client, key_prefix: key_prefix, scan_count: scan_count)
14
23
  end
15
24
 
16
- def initialize(client:, key_prefix: DEFAULT_KEY_PREFIX)
25
+ def initialize(client:, key_prefix: DEFAULT_KEY_PREFIX, scan_count: nil)
17
26
  @client = client
18
- @key_prefix = key_prefix.to_s
27
+ @key_prefix = key_prefix.nil? ? DEFAULT_KEY_PREFIX : key_prefix.to_s
28
+ @scan_count = scan_count
19
29
  end
20
30
 
21
31
  def get(key)
@@ -24,24 +34,30 @@ module BetterAuth
24
34
 
25
35
  def set(key, value, ttl = nil)
26
36
  prefixed_key = prefix_key(key)
27
- if ttl&.to_i&.positive?
28
- client.setex(prefixed_key, ttl.to_i, value)
37
+ coerced_ttl = coerce_ttl(ttl)
38
+ if coerced_ttl
39
+ client.setex(prefixed_key, coerced_ttl, value)
29
40
  else
30
41
  client.set(prefixed_key, value)
31
42
  end
43
+ nil
32
44
  end
33
45
 
34
46
  def delete(key)
35
47
  client.del(prefix_key(key))
48
+ nil
36
49
  end
37
50
 
38
51
  def list_keys
39
- client.keys("#{key_prefix}*").map { |key| unprefix_key(key) }
52
+ storage_keys.map { |key| unprefix_key(key) }
40
53
  end
41
54
 
42
55
  def clear
43
- keys = client.keys("#{key_prefix}*")
56
+ keys = storage_keys
57
+ # Upstream calls del(...keys) unconditionally; Ruby keeps this guard to
58
+ # avoid Redis ERR wrong number of arguments when no prefixed keys exist.
44
59
  client.del(*keys) unless keys.empty?
60
+ nil
45
61
  end
46
62
 
47
63
  alias_method :listKeys, :list_keys
@@ -55,5 +71,37 @@ module BetterAuth
55
71
  def unprefix_key(key)
56
72
  key.sub(/\A#{Regexp.escape(key_prefix)}/, "")
57
73
  end
74
+
75
+ def storage_keys
76
+ return scan_keys if scan_count
77
+
78
+ client.keys("#{key_prefix}*")
79
+ end
80
+
81
+ def scan_keys
82
+ cursor = "0"
83
+ matches = []
84
+ loop do
85
+ cursor, keys = client.scan(cursor, match: "#{key_prefix}*", count: scan_count)
86
+ matches.concat(keys)
87
+ break if cursor.to_s == "0"
88
+ end
89
+ matches
90
+ end
91
+
92
+ def coerce_ttl(ttl)
93
+ numeric = case ttl
94
+ when nil
95
+ nil
96
+ when Integer
97
+ ttl
98
+ when Float
99
+ ttl.to_i
100
+ when String
101
+ Integer(ttl, exception: false)
102
+ end
103
+
104
+ numeric&.positive? ? numeric : nil
105
+ end
58
106
  end
59
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_auth-redis-storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Sala