mudis 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1586fbcdac3ae237f88954090971beca8be02f3892f36c25363403d2ac80f34f
4
+ data.tar.gz: b12179ea11d6f51a3049d3eaa4428648fc088ecb6ade607bbe832227ceb64678
5
+ SHA512:
6
+ metadata.gz: bff8cda9a3f627f6c94b3f40b737365603ab51fd6edd8e845f058bed517ccfa86a4ad61465fb4b807540525b779586cfbbe77eccd8a16ab59afc69aa2f7cd43a
7
+ data.tar.gz: 544c25fee1a7346e8c0725ee5e83d5764f3028d76a261666ecdc0d3291c2bccf5a52cf07713d480750cb149e12c52201a2da2a6de0faa8f191ea45b7ba81d47b
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ ![mudis_signet](design/mudis.png "Mudis")
2
+
3
+ **Mudis** is a fast, thread-safe, in-memory, sharded LRU (Least Recently Used) cache for Ruby applications. Inspired by Redis, it provides value serialization, optional compression, per-key expiry, and metric tracking in a lightweight, dependency-free package that lives inside your Ruby process.
4
+
5
+ It’s ideal for scenarios where performance and process-local caching are critical, and where a full Redis setup is overkill or otherwise not possible.
6
+
7
+ ---
8
+
9
+ ## Design
10
+
11
+ #### Write - Read - Eviction
12
+
13
+ ![mudis_flow](design/mudis_flow.png "Write - Read - Eviction")
14
+
15
+ #### Cache Key Lifecycle
16
+
17
+ ![mudis_lru](design/mudis_lru.png "Mudis Cache Key Lifecycle")
18
+
19
+ ---
20
+
21
+ ## Features
22
+
23
+ - **Thread-safe**: Uses per-bucket mutexes for high concurrency.
24
+ - **Sharded**: Buckets data across multiple internal stores to minimize lock contention.
25
+ - **LRU Eviction**: Automatically evicts least recently used items as memory fills up.
26
+ - **Expiry Support**: Optional TTL per key with background cleanup thread.
27
+ - **Compression**: Optional Zlib compression for large values.
28
+ - **Metrics**: Tracks hits, misses, and evictions.
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ Add this line to your Gemfile:
35
+
36
+ ```ruby
37
+ gem 'mudis'
38
+ ```
39
+
40
+ Or install it manually:
41
+
42
+ ```bash
43
+ gem install mudis
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Configuration (Rails)
49
+
50
+ In your Rails app, create an initializer:
51
+
52
+ ```ruby
53
+ # config/initializers/mudis.rb
54
+
55
+ Mudis.serializer = JSON # or Marshal | Oj
56
+ Mudis.compress = true # Compress values using Zlib
57
+ Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
58
+ Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
59
+
60
+ at_exit do
61
+ Mudis.stop_expiry_thread
62
+ end
63
+ ```
64
+
65
+ > If your `lib/` folder isn't eager loaded, explicitly `require 'mudis'` in this file.
66
+
67
+ ---
68
+
69
+ ## Basic Usage
70
+
71
+ ```ruby
72
+ require 'mudis'
73
+
74
+ # Write a value with optional TTL
75
+ Mudis.write('user:123', { name: 'Alice' }, expires_in: 600)
76
+
77
+ # Read it back
78
+ Mudis.read('user:123') # => { "name" => "Alice" }
79
+
80
+ # Check if it exists
81
+ Mudis.exists?('user:123') # => true
82
+
83
+ # Atomically update
84
+ Mudis.update('user:123') { |data| data.merge(age: 30) }
85
+
86
+ # Delete a key
87
+ Mudis.delete('user:123')
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Rails Service Integration
93
+
94
+ For simplified or transient use in a controller, you can wrap your cache logic in a reusable thin class:
95
+
96
+ ```ruby
97
+ class MudisService
98
+ attr_reader :cache_key
99
+
100
+ def initialize(cache_key)
101
+ @cache_key = cache_key
102
+ end
103
+
104
+ def write(data, expires_in: nil)
105
+ Mudis.write(cache_key, data, expires_in: expires_in)
106
+ end
107
+
108
+ def read(default: nil)
109
+ Mudis.read(cache_key) || default
110
+ end
111
+
112
+ def update
113
+ Mudis.update(cache_key) { |current| yield(current) }
114
+ end
115
+
116
+ def delete
117
+ Mudis.delete(cache_key)
118
+ end
119
+
120
+ def exists?
121
+ Mudis.exists?(cache_key)
122
+ end
123
+ end
124
+ ```
125
+
126
+ Use it like:
127
+
128
+ ```ruby
129
+ cache = MudisCacheService.new("user:#{current_user.id}")
130
+ cache.write({ preferences: "dark" }, expires_in: 3600)
131
+ cache.read # => { "preferences" => "dark" }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Metrics
137
+
138
+ Track cache effectiveness and performance:
139
+
140
+ ```ruby
141
+ Mudis.metrics
142
+ # => { hits: 15, misses: 5, evictions: 3 }
143
+ ```
144
+
145
+ Optionally, return these metrics from a controller for remote analysis and monitoring if using rails.
146
+
147
+ ```ruby
148
+ class MudisController < ApplicationController
149
+
150
+ def metrics
151
+ render json: {
152
+ mudis_metrics: Mudis.metrics,
153
+ memory_used_bytes: Mudis.current_memory_bytes,
154
+ memory_max_bytes: Mudis.max_memory_bytes,
155
+ keys: Mudis.all_keys.size
156
+ }
157
+ end
158
+
159
+ end
160
+
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Advanced Configuration
166
+
167
+ | Setting | Description | Default |
168
+ |--------------------------|---------------------------------------------|--------------------|
169
+ | `Mudis.serializer` | JSON, Marshal, or Oj | `JSON` |
170
+ | `Mudis.compress` | Enable Zlib compression | `false` |
171
+ | `Mudis.max_value_bytes` | Max allowed size in bytes for a value | `nil` (no limit) |
172
+ | `Mudis.buckets` | Number of cache shards (via ENV var) | `32` |
173
+ | `start_expiry_thread` | Background TTL cleanup loop (every N sec) | Disabled by default|
174
+
175
+ To customize the number of buckets, set the `MUDIS_BUCKETS` environment variable.
176
+
177
+ ---
178
+
179
+ ## Graceful Shutdown
180
+
181
+ Don’t forget to stop the expiry thread when your app exits:
182
+
183
+ ```ruby
184
+ at_exit { Mudis.stop_expiry_thread }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Known Limitations
190
+
191
+ - Data is **process-local** and **non-persistent**.
192
+ - Not suitable for cross-process or cross-language use.
193
+ - Keys are globally scoped (no namespacing by default).
194
+ - Compression introduces CPU overhead.
195
+
196
+ ---
197
+
198
+ ## Roadmap
199
+
200
+ - [ ] Namespaced cache keys
201
+ - [ ] Stats per bucket
202
+ - [ ] Optional max memory cap per bucket
203
+ - [ ] Built-in fetch/read-or-write DSL
204
+
205
+ ---
206
+
207
+ ## License
208
+
209
+ MIT License © kiebor81
210
+
211
+ ---
212
+
213
+ ## Contributing
214
+
215
+ PRs are welcome! To get started:
216
+
217
+ ```bash
218
+ git clone https://github.com/kiebor81/mudis
219
+ cd mudis
220
+ bundle install
221
+
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Contact
227
+
228
+ For issues, suggestions, or feedback, please open a GitHub issue
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ MUDIS_VERSION = "0.1.0"
data/lib/mudis.rb ADDED
@@ -0,0 +1,273 @@
1
+ # lib/mudis.rb
2
+ require 'json'
3
+ require 'thread'
4
+ require 'zlib'
5
+
6
+ # Mudis is a thread-safe, in-memory, sharded, LRU cache with optional compression and expiry.
7
+ # It is designed for high concurrency and performance within a Ruby application.
8
+ class Mudis
9
+ # --- Global Configuration and State ---
10
+
11
+ @serializer = JSON # Default serializer (can be changed to Marshal or Oj)
12
+ @compress = false # Whether to compress values with Zlib
13
+ @metrics = { hits: 0, misses: 0, evictions: 0 } # Metrics tracking read/write behavior
14
+ @metrics_mutex = Mutex.new # Mutex for synchronizing access to metrics
15
+ @max_value_bytes = nil # Optional size cap per value
16
+ @stop_expiry = false # Signal for stopping expiry thread
17
+
18
+ class << self
19
+ attr_accessor :serializer, :compress, :max_value_bytes
20
+
21
+ # Returns a snapshot of metrics (thread-safe)
22
+ def metrics
23
+ @metrics_mutex.synchronize { @metrics.dup }
24
+ end
25
+ end
26
+
27
+ # Node structure for the LRU doubly-linked list
28
+ class LRUNode
29
+ attr_accessor :key, :prev, :next
30
+ def initialize(key)
31
+ @key = key
32
+ @prev = nil
33
+ @next = nil
34
+ end
35
+ end
36
+
37
+ # Number of cache buckets (shards). Default: 32
38
+ def self.buckets
39
+ @buckets ||= (ENV['MUDIS_BUCKETS']&.to_i || 32)
40
+ end
41
+
42
+ # --- Internal Structures ---
43
+
44
+ @stores = Array.new(buckets) { {} } # Array of hash buckets for storage
45
+ @mutexes = Array.new(buckets) { Mutex.new } # Per-bucket mutexes
46
+ @lru_heads = Array.new(buckets) { nil } # Head node for each LRU list
47
+ @lru_tails = Array.new(buckets) { nil } # Tail node for each LRU list
48
+ @lru_nodes = Array.new(buckets) { {} } # Map of key => LRU node
49
+ @current_bytes = Array.new(buckets, 0) # Memory usage per bucket
50
+ @max_bytes = 1_073_741_824 # 1 GB global max cache size
51
+ @threshold_bytes = (@max_bytes * 0.9).to_i # Eviction threshold at 90%
52
+ @expiry_thread = nil # Background thread for expiry cleanup
53
+
54
+ class << self
55
+ # Starts a thread that periodically removes expired entries
56
+ def start_expiry_thread(interval: 60)
57
+ return if @expiry_thread&.alive?
58
+
59
+ @stop_expiry = false
60
+ @expiry_thread = Thread.new do
61
+ loop do
62
+ break if @stop_expiry
63
+ sleep interval
64
+ cleanup_expired!
65
+ end
66
+ end
67
+ end
68
+
69
+ # Signals and joins the expiry thread
70
+ def stop_expiry_thread
71
+ @stop_expiry = true
72
+ @expiry_thread&.join
73
+ @expiry_thread = nil
74
+ end
75
+
76
+ # Computes which bucket a key belongs to
77
+ def bucket_index(key)
78
+ key.hash % buckets
79
+ end
80
+
81
+ # Checks if a key exists and is not expired
82
+ def exists?(key)
83
+ !!read(key)
84
+ end
85
+
86
+ # Reads and returns the value for a key, updating LRU and metrics
87
+ def read(key)
88
+ raw_entry = nil
89
+ idx = bucket_index(key)
90
+ mutex = @mutexes[idx]
91
+
92
+ mutex.synchronize do
93
+ raw_entry = @stores[idx][key]
94
+ if raw_entry && raw_entry[:expires_at] && Time.now > raw_entry[:expires_at]
95
+ evict_key(idx, key)
96
+ raw_entry = nil
97
+ end
98
+
99
+ metric(:hits) if raw_entry
100
+ metric(:misses) unless raw_entry
101
+ end
102
+
103
+ return nil unless raw_entry
104
+
105
+ value = decompress_and_deserialize(raw_entry[:value])
106
+ promote_lru(idx, key)
107
+ value
108
+ end
109
+
110
+ # Writes a value to the cache with optional expiry and LRU tracking
111
+ def write(key, value, expires_in: nil)
112
+ raw = serializer.dump(value)
113
+ raw = Zlib::Deflate.deflate(raw) if compress
114
+ size = key.bytesize + raw.bytesize
115
+ return if max_value_bytes && raw.bytesize > max_value_bytes
116
+
117
+ idx = bucket_index(key)
118
+ mutex = @mutexes[idx]
119
+ store = @stores[idx]
120
+
121
+ mutex.synchronize do
122
+ evict_key(idx, key) if store[key]
123
+
124
+ while @current_bytes[idx] + size > (@threshold_bytes / buckets) && @lru_tails[idx]
125
+ evict_key(idx, @lru_tails[idx].key)
126
+ metric(:evictions)
127
+ end
128
+
129
+ store[key] = {
130
+ value: raw,
131
+ expires_at: expires_in ? Time.now + expires_in : nil,
132
+ created_at: Time.now
133
+ }
134
+
135
+ insert_lru(idx, key)
136
+ @current_bytes[idx] += size
137
+ end
138
+ end
139
+
140
+ # Atomically updates the value for a key using a block
141
+ def update(key)
142
+ idx = bucket_index(key)
143
+ mutex = @mutexes[idx]
144
+ store = @stores[idx]
145
+
146
+ raw_entry = nil
147
+ mutex.synchronize do
148
+ raw_entry = store[key]
149
+ return nil unless raw_entry
150
+ end
151
+
152
+ value = decompress_and_deserialize(raw_entry[:value])
153
+ new_value = yield(value)
154
+ new_raw = serializer.dump(new_value)
155
+ new_raw = Zlib::Deflate.deflate(new_raw) if compress
156
+
157
+ mutex.synchronize do
158
+ old_size = key.bytesize + raw_entry[:value].bytesize
159
+ new_size = key.bytesize + new_raw.bytesize
160
+ store[key][:value] = new_raw
161
+ @current_bytes[idx] += (new_size - old_size)
162
+ promote_lru(idx, key)
163
+ end
164
+ end
165
+
166
+ # Deletes a key from the cache
167
+ def delete(key)
168
+ idx = bucket_index(key)
169
+ mutex = @mutexes[idx]
170
+
171
+ mutex.synchronize do
172
+ evict_key(idx, key)
173
+ end
174
+ end
175
+
176
+ # Removes expired keys across all buckets
177
+ def cleanup_expired!
178
+ now = Time.now
179
+ buckets.times do |idx|
180
+ mutex = @mutexes[idx]
181
+ store = @stores[idx]
182
+ mutex.synchronize do
183
+ store.keys.each do |key|
184
+ if store[key][:expires_at] && now > store[key][:expires_at]
185
+ evict_key(idx, key)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ # Returns an array of all cache keys
193
+ def all_keys
194
+ keys = []
195
+ buckets.times do |idx|
196
+ mutex = @mutexes[idx]
197
+ store = @stores[idx]
198
+ mutex.synchronize { keys.concat(store.keys) }
199
+ end
200
+ keys
201
+ end
202
+
203
+ # Returns total memory used across all buckets
204
+ def current_memory_bytes
205
+ @current_bytes.sum
206
+ end
207
+
208
+ # Returns configured maximum memory allowed
209
+ def max_memory_bytes
210
+ @max_bytes
211
+ end
212
+
213
+ private
214
+
215
+ # Decompresses and deserializes a raw value
216
+ def decompress_and_deserialize(raw)
217
+ val = compress ? Zlib::Inflate.inflate(raw) : raw
218
+ serializer.load(val)
219
+ end
220
+
221
+ # Thread-safe metric increment
222
+ def metric(name)
223
+ @metrics_mutex.synchronize { @metrics[name] += 1 }
224
+ end
225
+
226
+ # Removes a key from storage and LRU
227
+ def evict_key(idx, key)
228
+ store = @stores[idx]
229
+ entry = store.delete(key)
230
+ return unless entry
231
+
232
+ @current_bytes[idx] -= (key.bytesize + entry[:value].bytesize)
233
+
234
+ node = @lru_nodes[idx].delete(key)
235
+ remove_node(idx, node) if node
236
+ end
237
+
238
+ # Inserts a key at the head of the LRU list
239
+ def insert_lru(idx, key)
240
+ node = LRUNode.new(key)
241
+ node.next = @lru_heads[idx]
242
+ @lru_heads[idx].prev = node if @lru_heads[idx]
243
+ @lru_heads[idx] = node
244
+ @lru_tails[idx] ||= node
245
+ @lru_nodes[idx][key] = node
246
+ end
247
+
248
+ # Promotes a key to the front of the LRU list
249
+ def promote_lru(idx, key)
250
+ node = @lru_nodes[idx][key]
251
+ return unless node && @lru_heads[idx] != node
252
+
253
+ remove_node(idx, node)
254
+ insert_lru(idx, key)
255
+ end
256
+
257
+ # Removes a node from the LRU list
258
+ def remove_node(idx, node)
259
+ if node.prev
260
+ node.prev.next = node.next
261
+ else
262
+ @lru_heads[idx] = node.next
263
+ end
264
+
265
+ if node.next
266
+ node.next.prev = node.prev
267
+ else
268
+ @lru_tails[idx] = node.prev
269
+ end
270
+ end
271
+ end
272
+ end
273
+
data/sig/mudis.rbs ADDED
@@ -0,0 +1,28 @@
1
+ # sig/mudis.rbs
2
+
3
+ class Mudis
4
+ # Configuration
5
+ class << self
6
+ attr_accessor serializer : Object
7
+ attr_accessor compress : bool
8
+ attr_accessor max_value_bytes : Integer?
9
+ end
10
+
11
+ # Lifecycle
12
+ def self.start_expiry_thread: (?interval: Integer) -> void
13
+ def self.stop_expiry_thread: () -> void
14
+
15
+ # Core operations
16
+ def self.write: (String, untyped, ?expires_in: Integer) -> void
17
+ def self.read: (String) -> untyped?
18
+ def self.update: (String) { (untyped) -> untyped } -> void
19
+ def self.delete: (String) -> void
20
+ def self.exists?: (String) -> bool
21
+
22
+ # Introspection & management
23
+ def self.metrics: () -> Hash[Symbol, Integer]
24
+ def self.cleanup_expired!: () -> void
25
+ def self.all_keys: () -> Array[String]
26
+ def self.current_memory_bytes: () -> Integer
27
+ def self.max_memory_bytes: () -> Integer
28
+ end
@@ -0,0 +1,120 @@
1
+ # spec/mudis_spec.rb
2
+ require_relative "spec_helper"
3
+
4
+ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
5
+ before(:each) do
6
+ Mudis.stop_expiry_thread
7
+ Mudis.instance_variable_set(:@buckets, nil)
8
+ Mudis.instance_variable_set(:@stores, Array.new(Mudis.buckets) { {} })
9
+ Mudis.instance_variable_set(:@mutexes, Array.new(Mudis.buckets) { Mutex.new })
10
+ Mudis.instance_variable_set(:@lru_heads, Array.new(Mudis.buckets) { nil })
11
+ Mudis.instance_variable_set(:@lru_tails, Array.new(Mudis.buckets) { nil })
12
+ Mudis.instance_variable_set(:@lru_nodes, Array.new(Mudis.buckets) { {} })
13
+ Mudis.instance_variable_set(:@current_bytes, Array.new(Mudis.buckets, 0))
14
+ Mudis.instance_variable_set(:@metrics, { hits: 0, misses: 0, evictions: 0 })
15
+ Mudis.serializer = JSON
16
+ Mudis.compress = false
17
+ Mudis.max_value_bytes = nil
18
+ end
19
+
20
+ describe ".write and .read" do
21
+ it "writes and reads a value" do
22
+ Mudis.write("foo", { bar: "baz" })
23
+ result = Mudis.read("foo")
24
+ expect(result).to eq({ "bar" => "baz" })
25
+ end
26
+
27
+ it "returns nil for non-existent keys" do
28
+ expect(Mudis.read("nope")).to be_nil
29
+ end
30
+ end
31
+
32
+ describe ".exists?" do
33
+ it "returns true if key exists" do
34
+ Mudis.write("check", [1, 2, 3])
35
+ expect(Mudis.exists?("check")).to be true
36
+ end
37
+
38
+ it "returns false if key does not exist" do
39
+ expect(Mudis.exists?("missing")).to be false
40
+ end
41
+ end
42
+
43
+ describe ".delete" do
44
+ it "deletes a key" do
45
+ Mudis.write("temp", 42)
46
+ Mudis.delete("temp")
47
+ expect(Mudis.read("temp")).to be_nil
48
+ end
49
+ end
50
+
51
+ describe ".update" do
52
+ it "updates a cached value" do
53
+ Mudis.write("counter", 5)
54
+ Mudis.update("counter") { |v| v + 1 }
55
+ expect(Mudis.read("counter")).to eq(6)
56
+ end
57
+ end
58
+
59
+ describe "expiry handling" do
60
+ it "expires values after specified time" do
61
+ Mudis.write("short_lived", "gone soon", expires_in: 1)
62
+ sleep 2
63
+ expect(Mudis.read("short_lived")).to be_nil
64
+ end
65
+ end
66
+
67
+ describe ".metrics" do
68
+ it "tracks hits and misses" do
69
+ Mudis.write("hit_me", "value")
70
+ Mudis.read("hit_me") # hit
71
+ Mudis.read("miss_me") # miss
72
+ metrics = Mudis.metrics
73
+ expect(metrics[:hits]).to eq(1)
74
+ expect(metrics[:misses]).to eq(1)
75
+ end
76
+ end
77
+
78
+ describe "LRU eviction" do
79
+ it "evicts old entries when size limit is reached" do
80
+ Mudis.stop_expiry_thread
81
+
82
+ # Force one bucket
83
+ Mudis.instance_variable_set(:@buckets, 1)
84
+ Mudis.instance_variable_set(:@stores, [{}])
85
+ Mudis.instance_variable_set(:@mutexes, [Mutex.new])
86
+ Mudis.instance_variable_set(:@lru_heads, [nil])
87
+ Mudis.instance_variable_set(:@lru_tails, [nil])
88
+ Mudis.instance_variable_set(:@lru_nodes, [{}])
89
+ Mudis.instance_variable_set(:@current_bytes, [0])
90
+
91
+ # Set very small threshold
92
+ Mudis.instance_variable_set(:@threshold_bytes, 60)
93
+ Mudis.max_value_bytes = 100
94
+
95
+ big_val1 = "a" * 50
96
+ big_val2 = "b" * 50
97
+
98
+ Mudis.write("a", big_val1)
99
+ Mudis.write("b", big_val2)
100
+
101
+ expect(Mudis.read("a")).to be_nil
102
+ expect(Mudis.read("b")).not_to be_nil
103
+ end
104
+ end
105
+
106
+ describe ".all_keys" do
107
+ it "lists all stored keys" do
108
+ Mudis.write("k1", 1)
109
+ Mudis.write("k2", 2)
110
+ expect(Mudis.all_keys).to include("k1", "k2")
111
+ end
112
+ end
113
+
114
+ describe ".current_memory_bytes" do
115
+ it "returns a non-zero byte count after writes" do
116
+ Mudis.write("size_test", "a" * 100)
117
+ expect(Mudis.current_memory_bytes).to be > 0
118
+ end
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mudis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kiebor81
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-07-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Thread-safe, bucketed, in-process cache for Ruby apps. Drop-in replacement
28
+ for Kredis in some scenarios.
29
+ email:
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files:
33
+ - sig/mudis.rbs
34
+ files:
35
+ - README.md
36
+ - lib/mudis.rb
37
+ - lib/mudis/version.rb
38
+ - sig/mudis.rbs
39
+ - spec/mudis_spec.rb
40
+ homepage: https://github.com/kiebor81/mudis
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.5.17
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: A fast in-memory Ruby LRU cache with compression and expiry.
63
+ test_files:
64
+ - spec/mudis_spec.rb