better_auth-redis-storage 0.8.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +35 -18
- data/lib/better_auth/redis_storage/version.rb +1 -1
- data/lib/better_auth/redis_storage.rb +68 -18
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f34a3a4a6ddeac7517500d5f886fd427bc7f9473ef0ee101a1d0e00d36f9a5f5
|
|
4
|
+
data.tar.gz: e00a5743378df1e4712ae318b3787762362d3a2077f8c7163978f518a2b5fc57
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2227f0cf222b1919fa63aa2fd4492b7f0ed8085677216372924492d2f42af701503adf50f4ffe50c3ffd412578e99e64074087aa232a5a4e693ac5c7b174e99c
|
|
7
|
+
data.tar.gz: a6e673a666c903a19b1ee752d5678f7da2ab2be02380e1e4e8c7710004ff7d7349acfb96e648fbf2bee318e4c95817f4e72e1c8d79b4114be4d0490c16398cb1
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -34,11 +34,13 @@ The canonical Ruby form is also supported:
|
|
|
34
34
|
storage = BetterAuth::RedisStorage.new(client: redis)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
For upstream-shaped call sites, use `BetterAuth.
|
|
38
|
-
the camelCase class alias
|
|
37
|
+
For upstream-shaped call sites, use `BetterAuth.redisStorage(client: redis)` or
|
|
38
|
+
the camelCase class alias. The upstream `keyPrefix:` keyword is accepted
|
|
39
|
+
alongside the canonical Ruby `key_prefix:` keyword:
|
|
39
40
|
|
|
40
41
|
```ruby
|
|
41
|
-
storage = BetterAuth
|
|
42
|
+
storage = BetterAuth.redisStorage(client: redis, keyPrefix: "my-app:")
|
|
43
|
+
storage = BetterAuth::RedisStorage.redisStorage(client: redis, keyPrefix: "my-app:")
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Configuration
|
|
@@ -47,20 +49,25 @@ storage = BetterAuth::RedisStorage.redisStorage(client: redis)
|
|
|
47
49
|
storage = BetterAuth::RedisStorage.new(
|
|
48
50
|
client: redis,
|
|
49
51
|
key_prefix: "better-auth:",
|
|
50
|
-
scan_count:
|
|
52
|
+
scan_count: BetterAuth::RedisStorage::SCAN_DEFAULT_COUNT,
|
|
51
53
|
atomic_clear: false
|
|
52
54
|
)
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
`client` must respond to `get`, `set`, `setex`, `del`, and `
|
|
56
|
-
also respond to `
|
|
57
|
-
`atomic_clear:` is enabled. This matches the interfaces exposed by the
|
|
58
|
-
and `redis-namespace` gems.
|
|
57
|
+
`client` must respond to `get`, `set`, `setex`, `del`, and `scan`. It should
|
|
58
|
+
also respond to `keys` only when `scan_count: nil` is configured, and to `incr`
|
|
59
|
+
when `atomic_clear:` is enabled. This matches the interfaces exposed by the
|
|
60
|
+
`redis` and `redis-namespace` gems.
|
|
59
61
|
|
|
60
|
-
`key_prefix` defaults to `"better-auth:"`.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
sharing a Redis instance
|
|
62
|
+
`key_prefix` defaults to `"better-auth:"`. `keyPrefix:` is accepted for
|
|
63
|
+
upstream-shaped call sites. Passing `nil` falls back to the default. Any other
|
|
64
|
+
value, including the empty string, is honored verbatim. Redis databases are not
|
|
65
|
+
isolation boundaries for shared clients; applications sharing a Redis instance
|
|
66
|
+
should use distinct prefixes.
|
|
67
|
+
|
|
68
|
+
`list_keys` and `clear` escape Redis glob metacharacters in `key_prefix` before
|
|
69
|
+
matching keys, so prefixes containing characters such as `*`, `?`, `[`, `]`, or
|
|
70
|
+
`\` are treated as literal namespace bytes.
|
|
64
71
|
|
|
65
72
|
> **Warning:** Passing `key_prefix: ""` puts Better Auth keys at the root of
|
|
66
73
|
> the selected Redis logical namespace. `list_keys` and `clear` then match `*`,
|
|
@@ -68,14 +75,22 @@ sharing a Redis instance should use distinct prefixes.
|
|
|
68
75
|
> key in that Redis database. Use an application-specific prefix unless the
|
|
69
76
|
> Redis database is fully dedicated to Better Auth.
|
|
70
77
|
|
|
71
|
-
`scan_count`
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
`scan_count` defaults to `BetterAuth::RedisStorage::SCAN_DEFAULT_COUNT` and uses
|
|
79
|
+
Redis `SCAN` for `list_keys` and `clear`. Set `scan_count:` to a larger positive
|
|
80
|
+
count such as `500` or `1000` to tune scan page size:
|
|
74
81
|
|
|
75
82
|
```ruby
|
|
76
83
|
storage = BetterAuth::RedisStorage.new(client: redis, scan_count: 500)
|
|
77
84
|
```
|
|
78
85
|
|
|
86
|
+
For exact legacy upstream behavior, pass `scan_count: nil` to use blocking
|
|
87
|
+
`KEYS "#{key_prefix}*"`. This is intended only for small or dedicated Redis
|
|
88
|
+
databases because `KEYS` can block Redis while it walks the keyspace:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
storage = BetterAuth::RedisStorage.new(client: redis, scan_count: nil)
|
|
92
|
+
```
|
|
93
|
+
|
|
79
94
|
`atomic_clear` is a Ruby-only opt-in for applications that need `clear` to be
|
|
80
95
|
logically atomic under concurrent writers:
|
|
81
96
|
|
|
@@ -108,7 +123,8 @@ storage.clear
|
|
|
108
123
|
`listKeys` is available as a camelCase alias for upstream parity.
|
|
109
124
|
|
|
110
125
|
`list_keys` returns every matching logical key but Redis does not guarantee key
|
|
111
|
-
order for `KEYS` or `SCAN`.
|
|
126
|
+
order for `KEYS` or `SCAN`. The SCAN path removes duplicate cursor results while
|
|
127
|
+
preserving first-seen order. Sort the returned array in application code when a
|
|
112
128
|
stable order matters.
|
|
113
129
|
|
|
114
130
|
TTL handling for `set(key, value, ttl)`:
|
|
@@ -130,12 +146,13 @@ upstream calls `del(...keys)` even when `keys` is empty, while this Ruby gem
|
|
|
130
146
|
skips `del` to avoid Redis `ERR wrong number of arguments for 'del'`.
|
|
131
147
|
When keys do exist, `clear` deletes them in batches of
|
|
132
148
|
`BetterAuth::RedisStorage::DELETE_CHUNK_SIZE` keys per `del` call to avoid very
|
|
133
|
-
large Redis argument lists.
|
|
149
|
+
large Redis argument lists. The SCAN path collects the matched key set before
|
|
150
|
+
deleting it so cursor iteration is not affected by mutating the keyspace.
|
|
134
151
|
|
|
135
152
|
With `atomic_clear: true`, `clear` increments a generation key with Redis
|
|
136
153
|
`INCR`, making old generation keys immediately invisible to `get`, `set`,
|
|
137
154
|
`delete`, `list_keys`, and Better Auth itself. Cleanup of the old generation is
|
|
138
|
-
best-effort and uses `SCAN`
|
|
155
|
+
best-effort and uses `SCAN` by default.
|
|
139
156
|
|
|
140
157
|
Redis Cluster users should treat `list_keys` and `clear` as operationally
|
|
141
158
|
constrained helpers. This adapter does not scan every cluster node, and
|
|
@@ -4,28 +4,32 @@ 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, atomic_clear: false)
|
|
8
|
-
RedisStorage.new(client: client, key_prefix: key_prefix, scan_count: scan_count, atomic_clear: atomic_clear)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
7
|
class RedisStorage
|
|
8
|
+
UNSET = Object.new.freeze
|
|
12
9
|
DEFAULT_KEY_PREFIX = "better-auth:"
|
|
13
10
|
SCAN_DEFAULT_COUNT = 100
|
|
14
11
|
DELETE_CHUNK_SIZE = 500
|
|
15
12
|
|
|
16
13
|
attr_reader :client, :key_prefix, :scan_count, :atomic_clear
|
|
17
14
|
|
|
18
|
-
def self.build(client:, key_prefix:
|
|
19
|
-
|
|
15
|
+
def self.build(client:, key_prefix: UNSET, scan_count: UNSET, atomic_clear: false, **options)
|
|
16
|
+
key_prefix_camel = extract_key_prefix_camel!(options)
|
|
17
|
+
reject_unknown_keywords!(options)
|
|
18
|
+
new(client: client, key_prefix: key_prefix, key_prefix_camel: key_prefix_camel, scan_count: scan_count, atomic_clear: atomic_clear)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
def self.redisStorage(client:, key_prefix:
|
|
23
|
-
|
|
21
|
+
def self.redisStorage(client:, key_prefix: UNSET, scan_count: UNSET, atomic_clear: false, **options)
|
|
22
|
+
key_prefix_camel = extract_key_prefix_camel!(options)
|
|
23
|
+
reject_unknown_keywords!(options)
|
|
24
|
+
new(client: client, key_prefix: key_prefix, key_prefix_camel: key_prefix_camel, scan_count: scan_count, atomic_clear: atomic_clear)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
def initialize(client:, key_prefix:
|
|
27
|
+
def initialize(client:, key_prefix: UNSET, key_prefix_camel: UNSET, scan_count: UNSET, atomic_clear: false, **options)
|
|
28
|
+
key_prefix_camel = self.class.extract_key_prefix_camel!(options) if key_prefix_camel.equal?(UNSET)
|
|
29
|
+
self.class.reject_unknown_keywords!(options)
|
|
27
30
|
@client = client
|
|
28
|
-
@key_prefix = key_prefix
|
|
31
|
+
@key_prefix = self.class.resolve_key_prefix(key_prefix, key_prefix_camel)
|
|
32
|
+
scan_count = SCAN_DEFAULT_COUNT if scan_count.equal?(UNSET)
|
|
29
33
|
if !scan_count.nil? && !(scan_count.is_a?(Integer) && scan_count.positive?)
|
|
30
34
|
raise ArgumentError, "scan_count must be nil or a positive Integer; got #{scan_count.inspect}"
|
|
31
35
|
end
|
|
@@ -33,6 +37,28 @@ module BetterAuth
|
|
|
33
37
|
@atomic_clear = !!atomic_clear
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
def self.extract_key_prefix_camel!(options)
|
|
41
|
+
options.key?(:keyPrefix) ? options.delete(:keyPrefix) : UNSET
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.reject_unknown_keywords!(options)
|
|
45
|
+
return if options.empty?
|
|
46
|
+
|
|
47
|
+
unknown = options.keys.map(&:inspect).join(", ")
|
|
48
|
+
label = (options.length == 1) ? "keyword" : "keywords"
|
|
49
|
+
raise ArgumentError, "unknown #{label}: #{unknown}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.resolve_key_prefix(key_prefix, key_prefix_camel)
|
|
53
|
+
if !key_prefix.equal?(UNSET) && !key_prefix_camel.equal?(UNSET) && key_prefix != key_prefix_camel
|
|
54
|
+
raise ArgumentError, "key_prefix and keyPrefix cannot both be provided with different values"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
selected = key_prefix.equal?(UNSET) ? key_prefix_camel : key_prefix
|
|
58
|
+
selected = DEFAULT_KEY_PREFIX if selected.equal?(UNSET) || selected.nil?
|
|
59
|
+
selected.to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
36
62
|
def get(key)
|
|
37
63
|
client.get(prefix_key(key))
|
|
38
64
|
end
|
|
@@ -113,30 +139,34 @@ module BetterAuth
|
|
|
113
139
|
def storage_keys(prefix = storage_prefix)
|
|
114
140
|
return scan_keys(prefix) if scan_count
|
|
115
141
|
|
|
116
|
-
client.keys(
|
|
142
|
+
client.keys(match_pattern(prefix))
|
|
117
143
|
end
|
|
118
144
|
|
|
119
145
|
def scan_keys(prefix = storage_prefix)
|
|
146
|
+
seen = {}
|
|
120
147
|
matches = []
|
|
121
|
-
each_scan_batch(prefix)
|
|
148
|
+
each_scan_batch(prefix) do |keys|
|
|
149
|
+
keys.each do |key|
|
|
150
|
+
next if seen[key]
|
|
151
|
+
|
|
152
|
+
seen[key] = true
|
|
153
|
+
matches << key
|
|
154
|
+
end
|
|
155
|
+
end
|
|
122
156
|
matches
|
|
123
157
|
end
|
|
124
158
|
|
|
125
159
|
def each_scan_batch(prefix = storage_prefix)
|
|
126
160
|
cursor = "0"
|
|
127
161
|
loop do
|
|
128
|
-
cursor, keys = client.scan(cursor, match:
|
|
162
|
+
cursor, keys = client.scan(cursor, match: match_pattern(prefix), count: scan_count)
|
|
129
163
|
yield keys
|
|
130
164
|
break if cursor.to_s == "0"
|
|
131
165
|
end
|
|
132
166
|
end
|
|
133
167
|
|
|
134
168
|
def delete_matching_keys(prefix, single_key: false)
|
|
135
|
-
|
|
136
|
-
each_scan_batch(prefix) { |keys| delete_keys(keys, single_key: single_key) }
|
|
137
|
-
else
|
|
138
|
-
delete_keys(storage_keys(prefix), single_key: single_key)
|
|
139
|
-
end
|
|
169
|
+
delete_keys(storage_keys(prefix), single_key: single_key)
|
|
140
170
|
end
|
|
141
171
|
|
|
142
172
|
def delete_keys(keys, single_key: false)
|
|
@@ -149,6 +179,14 @@ module BetterAuth
|
|
|
149
179
|
end
|
|
150
180
|
end
|
|
151
181
|
|
|
182
|
+
def match_pattern(prefix)
|
|
183
|
+
"#{redis_glob_escape(prefix)}*"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def redis_glob_escape(value)
|
|
187
|
+
value.to_s.gsub(/[\\*?\[\]]/) { |character| "\\#{character}" }
|
|
188
|
+
end
|
|
189
|
+
|
|
152
190
|
def coerce_ttl(ttl)
|
|
153
191
|
numeric = case ttl
|
|
154
192
|
when nil
|
|
@@ -171,4 +209,16 @@ module BetterAuth
|
|
|
171
209
|
seconds.positive? ? seconds : nil
|
|
172
210
|
end
|
|
173
211
|
end
|
|
212
|
+
|
|
213
|
+
def self.redis_storage(client:, key_prefix: RedisStorage::UNSET, scan_count: RedisStorage::UNSET, atomic_clear: false, **options)
|
|
214
|
+
key_prefix_camel = RedisStorage.extract_key_prefix_camel!(options)
|
|
215
|
+
RedisStorage.reject_unknown_keywords!(options)
|
|
216
|
+
RedisStorage.new(client: client, key_prefix: key_prefix, key_prefix_camel: key_prefix_camel, scan_count: scan_count, atomic_clear: atomic_clear)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.redisStorage(client:, key_prefix: RedisStorage::UNSET, scan_count: RedisStorage::UNSET, atomic_clear: false, **options)
|
|
220
|
+
key_prefix_camel = RedisStorage.extract_key_prefix_camel!(options)
|
|
221
|
+
RedisStorage.reject_unknown_keywords!(options)
|
|
222
|
+
RedisStorage.new(client: client, key_prefix: key_prefix, key_prefix_camel: key_prefix_camel, scan_count: scan_count, atomic_clear: atomic_clear)
|
|
223
|
+
end
|
|
174
224
|
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -112,14 +112,14 @@ files:
|
|
|
112
112
|
- README.md
|
|
113
113
|
- lib/better_auth/redis_storage.rb
|
|
114
114
|
- lib/better_auth/redis_storage/version.rb
|
|
115
|
-
homepage: https://github.com/sebasxsala/better-auth
|
|
115
|
+
homepage: https://github.com/sebasxsala/better-auth-rb
|
|
116
116
|
licenses:
|
|
117
117
|
- MIT
|
|
118
118
|
metadata:
|
|
119
|
-
homepage_uri: https://github.com/sebasxsala/better-auth
|
|
120
|
-
source_code_uri: https://github.com/sebasxsala/better-auth
|
|
121
|
-
changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-redis-storage/CHANGELOG.md
|
|
122
|
-
bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
|
|
119
|
+
homepage_uri: https://github.com/sebasxsala/better-auth-rb
|
|
120
|
+
source_code_uri: https://github.com/sebasxsala/better-auth-rb
|
|
121
|
+
changelog_uri: https://github.com/sebasxsala/better-auth-rb/blob/main/packages/better_auth-redis-storage/CHANGELOG.md
|
|
122
|
+
bug_tracker_uri: https://github.com/sebasxsala/better-auth-rb/issues
|
|
123
123
|
rdoc_options: []
|
|
124
124
|
require_paths:
|
|
125
125
|
- lib
|