factorix 0.6.0 → 0.7.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/exe/factorix +17 -0
  4. data/lib/factorix/api/mod_download_api.rb +10 -5
  5. data/lib/factorix/api/mod_portal_api.rb +6 -49
  6. data/lib/factorix/cache/base.rb +116 -0
  7. data/lib/factorix/cache/entry.rb +25 -0
  8. data/lib/factorix/cache/file_system.rb +137 -57
  9. data/lib/factorix/cache/redis.rb +287 -0
  10. data/lib/factorix/cache/s3.rb +388 -0
  11. data/lib/factorix/cli/commands/cache/evict.rb +17 -22
  12. data/lib/factorix/cli/commands/cache/stat.rb +57 -58
  13. data/lib/factorix/cli/commands/download_support.rb +1 -6
  14. data/lib/factorix/cli/commands/mod/download.rb +2 -3
  15. data/lib/factorix/cli/commands/mod/edit.rb +1 -4
  16. data/lib/factorix/cli/commands/mod/image/add.rb +1 -4
  17. data/lib/factorix/cli/commands/mod/image/edit.rb +1 -4
  18. data/lib/factorix/cli/commands/mod/image/list.rb +1 -4
  19. data/lib/factorix/cli/commands/mod/install.rb +2 -3
  20. data/lib/factorix/cli/commands/mod/search.rb +2 -3
  21. data/lib/factorix/cli/commands/mod/show.rb +2 -3
  22. data/lib/factorix/cli/commands/mod/sync.rb +2 -3
  23. data/lib/factorix/cli/commands/mod/update.rb +6 -39
  24. data/lib/factorix/cli/commands/mod/upload.rb +1 -4
  25. data/lib/factorix/cli/commands/portal_support.rb +27 -0
  26. data/lib/factorix/container.rb +27 -13
  27. data/lib/factorix/errors.rb +3 -0
  28. data/lib/factorix/http/cache_decorator.rb +5 -5
  29. data/lib/factorix/info_json.rb +5 -5
  30. data/lib/factorix/portal.rb +3 -2
  31. data/lib/factorix/transfer/downloader.rb +19 -11
  32. data/lib/factorix/version.rb +1 -1
  33. data/lib/factorix.rb +45 -53
  34. data/sig/factorix/api/mod_download_api.rbs +1 -2
  35. data/sig/factorix/cache/base.rbs +28 -0
  36. data/sig/factorix/cache/entry.rbs +14 -0
  37. data/sig/factorix/cache/file_system.rbs +7 -6
  38. data/sig/factorix/cache/redis.rbs +36 -0
  39. data/sig/factorix/cache/s3.rbs +38 -0
  40. data/sig/factorix/errors.rbs +3 -0
  41. data/sig/factorix/portal.rbs +1 -1
  42. metadata +25 -2
@@ -50,9 +50,9 @@ module Factorix
50
50
  # Don't cache streaming requests
51
51
  return client.get(uri, headers:, &block) if block
52
52
 
53
- key = cache.key_for(uri.to_s)
53
+ cache_key = uri.to_s
54
54
 
55
- cached_body = cache.read(key)
55
+ cached_body = cache.read(cache_key)
56
56
  if cached_body
57
57
  logger.debug("Cache hit", uri: uri.to_s)
58
58
  publish("cache.hit", url: uri.to_s)
@@ -63,9 +63,9 @@ module Factorix
63
63
  publish("cache.miss", url: uri.to_s)
64
64
 
65
65
  # Locking prevents concurrent downloads of the same resource
66
- cache.with_lock(key) do
66
+ cache.with_lock(cache_key) do
67
67
  # Double-check: another thread might have filled the cache
68
- cached_body = cache.read(key)
68
+ cached_body = cache.read(cache_key)
69
69
  if cached_body
70
70
  publish("cache.hit", url: uri.to_s)
71
71
  return CachedResponse.new(cached_body)
@@ -77,7 +77,7 @@ module Factorix
77
77
  with_temporary_file do |temp|
78
78
  temp.write(response.body)
79
79
  temp.close
80
- cache.store(key, Pathname(temp.path))
80
+ cache.store(cache_key, Pathname(temp.path))
81
81
  end
82
82
  end
83
83
 
@@ -49,19 +49,19 @@ module Factorix
49
49
  def self.from_zip(zip_path)
50
50
  cache = Container.resolve(:info_json_cache)
51
51
  logger = Container.resolve(:logger)
52
- cache_key = cache.key_for(zip_path.to_s)
52
+ cache_key = zip_path.to_s
53
53
 
54
- if (cached_json = cache.read(cache_key, encoding: Encoding::UTF_8))
54
+ if (cached_json = cache.read(cache_key))
55
55
  logger.debug("info.json cache hit", path: zip_path.to_s)
56
- return from_json(cached_json)
56
+ return from_json((+cached_json).force_encoding(Encoding::UTF_8))
57
57
  end
58
58
 
59
59
  logger.debug("info.json cache miss", path: zip_path.to_s)
60
60
 
61
61
  cache.with_lock(cache_key) do
62
- if (cached_json = cache.read(cache_key, encoding: Encoding::UTF_8))
62
+ if (cached_json = cache.read(cache_key))
63
63
  logger.debug("info.json cache hit (after lock)", path: zip_path.to_s)
64
- return from_json(cached_json)
64
+ return from_json((+cached_json).force_encoding(Encoding::UTF_8))
65
65
  end
66
66
 
67
67
  json_string = Zip::File.open(zip_path) {|zip_file|
@@ -72,12 +72,13 @@ module Factorix
72
72
  #
73
73
  # @param release [API::Release] release object containing download_url and sha1
74
74
  # @param output [Pathname] output file path
75
+ # @param handler [Object, nil] event handler for download progress (optional)
75
76
  # @return [void]
76
77
  # @raise [DigestMismatchError] if SHA1 verification fails
77
- def download_mod(release, output)
78
+ def download_mod(release, output, handler: nil)
78
79
  # Extract path from URI::HTTPS
79
80
  download_path = release.download_url.path
80
- mod_download_api.download(download_path, output, expected_sha1: release.sha1)
81
+ mod_download_api.download(download_path, output, expected_sha1: release.sha1, handler:)
81
82
  end
82
83
 
83
84
  # Upload a MOD file to the portal
@@ -53,9 +53,9 @@ module Factorix
53
53
  end
54
54
 
55
55
  logger.info("Starting download", output: output.to_s)
56
- key = cache.key_for(url.to_s)
56
+ cache_key = strip_query(url)
57
57
 
58
- case try_cache_hit(key, output, expected_sha1:)
58
+ case try_cache_hit(cache_key, output, expected_sha1:)
59
59
  when :hit
60
60
  return
61
61
  when :miss
@@ -68,14 +68,14 @@ module Factorix
68
68
  raise RuntimeError, "Unexpected cache state"
69
69
  end
70
70
 
71
- cache.with_lock(key) do
72
- return if try_cache_hit(key, output, expected_sha1:) == :hit
71
+ cache.with_lock(cache_key) do
72
+ return if try_cache_hit(cache_key, output, expected_sha1:) == :hit
73
73
 
74
74
  with_temporary_file do |temp_file|
75
75
  download_file_with_progress(url, temp_file)
76
76
  verify_sha1(temp_file, expected_sha1) if expected_sha1
77
- cache.store(key, temp_file)
78
- cache.fetch(key, output)
77
+ cache.store(cache_key, temp_file)
78
+ cache.write_to(cache_key, output)
79
79
  end
80
80
  end
81
81
  end
@@ -112,21 +112,21 @@ module Factorix
112
112
  # If the cached file exists but fails SHA1 verification, the cache entry
113
113
  # is invalidated and :corrupted is returned to trigger re-download.
114
114
  #
115
- # @param key [String] cache key
115
+ # @param cache_key [String] logical cache key (URL string)
116
116
  # @param output [Pathname] path to save the cached file
117
117
  # @param expected_sha1 [String, nil] expected SHA1 digest for verification (optional)
118
118
  # @return [Symbol] :hit if cache hit with valid SHA1, :miss if not cached, :corrupted if SHA1 mismatch
119
- private def try_cache_hit(key, output, expected_sha1:)
120
- return :miss unless cache.fetch(key, output)
119
+ private def try_cache_hit(cache_key, output, expected_sha1:)
120
+ return :miss unless cache.write_to(cache_key, output)
121
121
 
122
122
  logger.info("Cache hit", output: output.to_s)
123
123
  verify_sha1(output, expected_sha1) if expected_sha1
124
- total_size = cache.size(key)
124
+ total_size = cache.size(cache_key)
125
125
  publish("cache.hit", output: output.to_s, total_size:)
126
126
  :hit
127
127
  rescue DigestMismatchError => e
128
128
  logger.warn("Cache corrupted, invalidating", output: output.to_s, error: e.message)
129
- cache.delete(key)
129
+ cache.delete(cache_key)
130
130
  :corrupted
131
131
  end
132
132
 
@@ -144,6 +144,14 @@ module Factorix
144
144
  raise DigestMismatchError, "SHA1 mismatch: expected #{expected_sha1}, got #{actual_sha1}"
145
145
  end
146
146
 
147
+ # Strip query parameters from a URI to create a safe cache key.
148
+ # This prevents sensitive data (e.g., authentication tokens) from being
149
+ # stored in cache metadata.
150
+ #
151
+ # @param uri [URI] the URI to strip
152
+ # @return [String] URI string without query parameters
153
+ private def strip_query(uri) = uri.dup.tap {|u| u.query = nil }.to_s
154
+
147
155
  # Create a temporary file for downloading, ensuring cleanup after use.
148
156
  #
149
157
  # @yield [Pathname] the temporary file path
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Factorix
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  public_constant :VERSION
6
6
  end
data/lib/factorix.rb CHANGED
@@ -35,29 +35,65 @@ module Factorix
35
35
  end
36
36
 
37
37
  # Cache settings
38
+ # Each cache type can have its own backend with hierarchical configuration.
39
+ # Common settings (backend, ttl) apply to all backends.
40
+ # Backend-specific settings are nested under the backend name.
38
41
  setting :cache do
39
42
  # Download cache settings (for MOD files)
40
43
  setting :download do
41
- setting :dir, constructor: ->(value) { Pathname(value) }
44
+ setting :backend, default: :file_system
42
45
  setting :ttl, default: nil # nil for unlimited (MOD files are immutable)
43
- setting :max_file_size, default: nil # nil for unlimited
44
- setting :compression_threshold, default: nil # nil for no compression (binary files)
46
+ setting :file_system do
47
+ setting :max_file_size, default: nil # nil for unlimited
48
+ setting :compression_threshold, default: nil # nil for no compression (binary files)
49
+ end
50
+ setting :redis do
51
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
52
+ setting :lock_timeout, default: 30
53
+ end
54
+ setting :s3 do
55
+ setting :bucket, default: nil # required when using S3 backend
56
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
57
+ setting :lock_timeout, default: 30
58
+ end
45
59
  end
46
60
 
47
61
  # API cache settings (for API responses)
48
62
  setting :api do
49
- setting :dir, constructor: ->(value) { Pathname(value) }
63
+ setting :backend, default: :file_system
50
64
  setting :ttl, default: 3600 # 1 hour (API responses may change)
51
- setting :max_file_size, default: 10 * 1024 * 1024 # 10MiB (JSON responses)
52
- setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
65
+ setting :file_system do
66
+ setting :max_file_size, default: 10 * 1024 * 1024 # 10MiB (JSON responses)
67
+ setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
68
+ end
69
+ setting :redis do
70
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
71
+ setting :lock_timeout, default: 30
72
+ end
73
+ setting :s3 do
74
+ setting :bucket, default: nil # required when using S3 backend
75
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
76
+ setting :lock_timeout, default: 30
77
+ end
53
78
  end
54
79
 
55
80
  # info.json cache settings (for MOD metadata from ZIP files)
56
81
  setting :info_json do
57
- setting :dir, constructor: ->(value) { Pathname(value) }
82
+ setting :backend, default: :file_system
58
83
  setting :ttl, default: nil # nil for unlimited (info.json is immutable within a MOD ZIP)
59
- setting :max_file_size, default: nil # nil for unlimited (info.json is small)
60
- setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
84
+ setting :file_system do
85
+ setting :max_file_size, default: nil # nil for unlimited (info.json is small)
86
+ setting :compression_threshold, default: 0 # always compress (JSON is highly compressible)
87
+ end
88
+ setting :redis do
89
+ setting :url, default: nil # nil falls back to REDIS_URL env, then localhost:6379
90
+ setting :lock_timeout, default: 30
91
+ end
92
+ setting :s3 do
93
+ setting :bucket, default: nil # required when using S3 backend
94
+ setting :region, default: nil # nil falls back to AWS_REGION env or SDK default
95
+ setting :lock_timeout, default: 30
96
+ end
61
97
  end
62
98
  end
63
99
 
@@ -108,48 +144,4 @@ module Factorix
108
144
 
109
145
  Import = Dry::AutoInject(Container)
110
146
  public_constant :Import
111
-
112
- # Initialize cache directory defaults after Container is loaded
113
- runtime = Container.resolve(:runtime)
114
- config.cache.download.dir = runtime.factorix_cache_dir / "download"
115
- config.cache.api.dir = runtime.factorix_cache_dir / "api"
116
- config.cache.info_json.dir = runtime.factorix_cache_dir / "info_json"
117
-
118
- # @deprecated Use {Container} for DI and {Factorix} for configuration. Will be removed in v1.0.
119
- class Application
120
- # @!method [](key)
121
- # @deprecated Use {Container.[]} instead
122
- def self.[](key)
123
- warn "[factorix] Factorix::Application is deprecated, use Factorix::Container for DI"
124
- Container[key]
125
- end
126
-
127
- # @!method resolve(key)
128
- # @deprecated Use {Container.resolve} instead
129
- def self.resolve(key)
130
- warn "[factorix] Factorix::Application is deprecated, use Factorix::Container for DI"
131
- Container.resolve(key)
132
- end
133
-
134
- # @!method register(...)
135
- # @deprecated Use {Container.register} instead
136
- def self.register(...)
137
- warn "[factorix] Factorix::Application is deprecated, use Factorix::Container for DI"
138
- Container.register(...)
139
- end
140
-
141
- # @!method config
142
- # @deprecated Use {Factorix.config} instead
143
- def self.config
144
- warn "[factorix] Factorix::Application is deprecated, use Factorix.config for configuration"
145
- Factorix.config
146
- end
147
-
148
- # @!method configure(&block)
149
- # @deprecated Use {Factorix.configure} instead
150
- def self.configure(&)
151
- warn "[factorix] Factorix::Application is deprecated, use Factorix.configure for configuration"
152
- Factorix.configure(&)
153
- end
154
- end
155
147
  end
@@ -7,12 +7,11 @@ module Factorix
7
7
  class MODDownloadAPI
8
8
  BASE_URL: String
9
9
 
10
- attr_reader downloader: Transfer::Downloader
11
10
  attr_reader logger: Dry::Logger::Dispatcher
12
11
 
13
12
  def initialize: (**untyped deps) -> void
14
13
 
15
- def download: (String download_url, Pathname output, ?expected_sha1: String?) -> void
14
+ def download: (String download_url, Pathname output, ?expected_sha1: String?, ?handler: untyped?) -> void
16
15
  end
17
16
  end
18
17
  end
@@ -0,0 +1,28 @@
1
+ # RBS type signature file
2
+
3
+ module Factorix
4
+ module Cache
5
+ class Base
6
+ attr_reader ttl: Integer?
7
+
8
+ def initialize: (?ttl: Integer?) -> void
9
+
10
+ def exist?: (String key) -> bool
11
+ def read: (String key) -> String?
12
+ def store: (String key, Pathname src) -> bool
13
+ def delete: (String key) -> bool
14
+ def clear: () -> void
15
+
16
+ def with_lock: (String key) { () -> void } -> void
17
+
18
+ def age: (String key) -> Float?
19
+ def expired?: (String key) -> bool
20
+ def size: (String key) -> Integer?
21
+
22
+ def each: () -> Enumerator[[String, Entry], void]
23
+ | () { ([String, Entry]) -> void } -> void
24
+
25
+ def backend_info: () -> Hash[Symbol, untyped]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # RBS type signature file
2
+
3
+ module Factorix
4
+ module Cache
5
+ class Entry
6
+ attr_reader size: Integer
7
+ attr_reader age: Float
8
+
9
+ def expired?: () -> bool
10
+
11
+ def initialize: (size: Integer, age: Float, expired: bool) -> void
12
+ end
13
+ end
14
+ end
@@ -4,18 +4,16 @@
4
4
 
5
5
  module Factorix
6
6
  module Cache
7
- class FileSystem
7
+ class FileSystem < Base
8
8
  LOCK_FILE_LIFETIME: Integer
9
9
 
10
- def initialize: (Pathname cache_dir, ?ttl: Integer?, ?max_file_size: Integer?, ?compression_threshold: Integer?) -> void
11
-
12
- def key_for: (String url_string) -> String
10
+ def initialize: (cache_type: Symbol, ?ttl: Integer?, ?max_file_size: Integer?, ?compression_threshold: Integer?) -> void
13
11
 
14
12
  def exist?: (String key) -> bool
15
13
 
16
- def fetch: (String key, Pathname output) -> bool
14
+ def write_to: (String key, Pathname output) -> bool
17
15
 
18
- def read: (String key, ?encoding: Encoding | String) -> String?
16
+ def read: (String key) -> String?
19
17
 
20
18
  def store: (String key, Pathname src) -> bool
21
19
 
@@ -30,6 +28,9 @@ module Factorix
30
28
  def size: (String key) -> Integer?
31
29
 
32
30
  def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
33
34
  end
34
35
  end
35
36
  end
@@ -0,0 +1,36 @@
1
+ # RBS type signature file
2
+ # NOTE: Do not include private method definitions in RBS files.
3
+ # Only public interfaces should be documented here.
4
+
5
+ module Factorix
6
+ module Cache
7
+ class Redis < Base
8
+ DEFAULT_LOCK_TIMEOUT: Integer
9
+
10
+ def initialize: (?url: String?, cache_type: String | Symbol, ?lock_timeout: Integer, ?ttl: Integer?) -> void
11
+
12
+ def exist?: (String key) -> bool
13
+
14
+ def read: (String key) -> String?
15
+
16
+ def write_to: (String key, Pathname output) -> bool
17
+
18
+ def store: (String key, Pathname src) -> bool
19
+
20
+ def delete: (String key) -> bool
21
+
22
+ def clear: () -> void
23
+
24
+ def age: (String key) -> Integer?
25
+
26
+ def expired?: (String key) -> bool
27
+
28
+ def size: (String key) -> Integer?
29
+
30
+ def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # RBS type signature file
2
+ # NOTE: Do not include private method definitions in RBS files.
3
+ # Only public interfaces should be documented here.
4
+
5
+ module Factorix
6
+ module Cache
7
+ class S3 < Base
8
+ DEFAULT_LOCK_TIMEOUT: Integer
9
+
10
+ def initialize: (bucket: String, cache_type: String | Symbol, ?region: String?, ?lock_timeout: Integer, ?ttl: Integer?) -> void
11
+
12
+ def exist?: (String key) -> bool
13
+
14
+ def read: (String key) -> String?
15
+
16
+ def write_to: (String key, Pathname output) -> bool
17
+
18
+ def store: (String key, Pathname src) -> bool
19
+
20
+ def delete: (String key) -> bool
21
+
22
+ def clear: () -> void
23
+
24
+ def age: (String key) -> Integer?
25
+
26
+ def expired?: (String key) -> bool
27
+
28
+ def size: (String key) -> Integer?
29
+
30
+ def with_lock: [T] (String key) { () -> T } -> T
31
+
32
+ def each: () { (String, Entry) -> void } -> void
33
+ | () -> Enumerator[[String, Entry], void]
34
+
35
+ def backend_info: () -> Hash[Symbol, untyped]
36
+ end
37
+ end
38
+ end
@@ -49,6 +49,9 @@ module Factorix
49
49
  class HTTPServerError < HTTPError
50
50
  end
51
51
 
52
+ class LockTimeoutError < InfrastructureError
53
+ end
54
+
52
55
  class DigestMismatchError < InfrastructureError
53
56
  end
54
57
 
@@ -18,7 +18,7 @@ module Factorix
18
18
 
19
19
  def get_mod_full: (String name) -> API::MODInfo
20
20
 
21
- def download_mod: (API::Release release, Pathname output) -> void
21
+ def download_mod: (API::Release release, Pathname output, ?handler: untyped?) -> void
22
22
 
23
23
  def upload_mod: (String mod_name, Pathname file_path, **untyped metadata) -> void
24
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factorix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OZAWA Sakuro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-18 00:00:00.000000000 Z
11
+ date: 2026-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dry-inflector
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: dry-logger
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -221,7 +235,11 @@ files:
221
235
  - lib/factorix/api/release.rb
222
236
  - lib/factorix/api/tag.rb
223
237
  - lib/factorix/api_credential.rb
238
+ - lib/factorix/cache/base.rb
239
+ - lib/factorix/cache/entry.rb
224
240
  - lib/factorix/cache/file_system.rb
241
+ - lib/factorix/cache/redis.rb
242
+ - lib/factorix/cache/s3.rb
225
243
  - lib/factorix/cli.rb
226
244
  - lib/factorix/cli/commands/backup_support.rb
227
245
  - lib/factorix/cli/commands/base.rb
@@ -252,6 +270,7 @@ files:
252
270
  - lib/factorix/cli/commands/mod/update.rb
253
271
  - lib/factorix/cli/commands/mod/upload.rb
254
272
  - lib/factorix/cli/commands/path.rb
273
+ - lib/factorix/cli/commands/portal_support.rb
255
274
  - lib/factorix/cli/commands/requires_game_stopped.rb
256
275
  - lib/factorix/cli/commands/version.rb
257
276
  - lib/factorix/container.rb
@@ -322,7 +341,11 @@ files:
322
341
  - sig/factorix/api/release.rbs
323
342
  - sig/factorix/api/tag.rbs
324
343
  - sig/factorix/api_credential.rbs
344
+ - sig/factorix/cache/base.rbs
345
+ - sig/factorix/cache/entry.rbs
325
346
  - sig/factorix/cache/file_system.rbs
347
+ - sig/factorix/cache/redis.rbs
348
+ - sig/factorix/cache/s3.rbs
326
349
  - sig/factorix/cli.rbs
327
350
  - sig/factorix/cli/commands/base.rbs
328
351
  - sig/factorix/cli/commands/cache/evict.rbs