graphql-fragment_cache 1.22.0 → 1.22.3

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: c2f7385deb55dd824ed24ca60059bba372ad645bd2d1a183dee49089b2788648
4
- data.tar.gz: 991e976fc7d2a6451e7362e6d899829511d4979544aaf2f01cff25b210782f47
3
+ metadata.gz: 076cc28fcf1a65116b294f3df5422e74a097e00f7568f5265143f6468c5bce35
4
+ data.tar.gz: e20103b9efc1672e5f0df675ba54eedcc6a2c1a0528227f6eb846fa5a61e41b4
5
5
  SHA512:
6
- metadata.gz: 07ac9a5d1ba73da5561a7c433499486986ccbc9b70c07ea2aaf22b4781d331cd16ca8e9e05005cea2031d5955a188cb5d96431d04439ae229ad97441d913627c
7
- data.tar.gz: b9fb29e2a1d1a3bf24808b6186802d4c093764d11a5b80b07e6db8cb97c9454dbff6b878bd5c3767288ff551ee560573ce8ec3834fb01375a3a24aee312bad1c
6
+ metadata.gz: bcb941215e44b94fce0f8dee4dfdd6899ecb1f17f017959ac4c98c95ab9e74f6b609ac5a414ca35c80e853eef0aa69cb919b387f08ac7a9a4079c81e772c5087
7
+ data.tar.gz: e10156565aaccda651550b94d27c60d217483ce371e17b325342eb6996801cb952bd05320da1e2bc98a80eb24eb6b3cff82e323280e55cf5acbe569fb26efabc
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.22.3 (2026-11-07)
6
+
7
+ - [PR#144](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/144) Fix SystemStackError when cache_fragment is used inside a mutation
8
+ ([@LRFalk01][])
9
+ - [PR#99](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/99) Fix for race condition when reading from cache
10
+ ([@gsdean][])
11
+
12
+ ## 1.22.2 (2025-06-07)
13
+
14
+ - [PR#137](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/137) Remove new_connections? check for gql > 2.3.10 in Tracer ([@DmitryTsepelev][])
15
+
5
16
  ## 1.22.0 (2025-02-20)
6
17
 
7
18
  - [PR#134](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/134) Add possibility to include and exclude arguments from generated cache key ([@mgruner][])
@@ -222,3 +233,5 @@
222
233
  [@Drowze]: https://github.com/Drowze
223
234
  [@danielhartnell]: https://github.com/danielhartnell
224
235
  [@mgruner]: https://github.com/mgruner
236
+ [@gsdean]: https://github.com/gsdean
237
+ [@LRFalk01]: https://github.com/LRFalk01
data/README.md CHANGED
@@ -9,7 +9,10 @@ class PostType < BaseObject
9
9
  end
10
10
  ```
11
11
 
12
- You can support my open–source work [here](https://boosty.to/dmitry_tsepelev).
12
+ ## Professional Support
13
+
14
+ Need help with your GraphQL API architecture, schema design, or performance?
15
+ I'm available for consulting — [get in touch](https://dmitrytsepelev.dev/consulting).
13
16
 
14
17
  ## Getting started
15
18
 
@@ -46,9 +46,9 @@ module GraphQL
46
46
  end
47
47
 
48
48
  refine ::GraphQL::Execution::Lookahead do
49
- def selection_with_alias(name, **kwargs)
50
- return selection(name, **kwargs) if selects?(name, **kwargs)
51
- alias_selection(name, **kwargs)
49
+ def selection_with_alias(name, **)
50
+ return selection(name, **) if selects?(name, **)
51
+ alias_selection(name, **)
52
52
  end
53
53
  end
54
54
  })
@@ -56,8 +56,8 @@ module GraphQL
56
56
  # Builds cache key for fragment
57
57
  class CacheKeyBuilder
58
58
  class << self
59
- def call(**options)
60
- new(**options).build
59
+ def call(**)
60
+ new(**).build
61
61
  end
62
62
  end
63
63
 
@@ -87,6 +87,8 @@ module GraphQL
87
87
  end
88
88
 
89
89
  def value_from_cache
90
+ return nil unless FragmentCache.cache_store.exist?(cache_key)
91
+
90
92
  FragmentCache.cache_store.read(cache_key).tap do |cached|
91
93
  return NIL_IN_CACHE if cached.nil? && FragmentCache.cache_store.exist?(cache_key)
92
94
  end
@@ -9,6 +9,10 @@ module GraphQL
9
9
  check_graphql_version "> 2.2.5"
10
10
  end
11
11
 
12
+ def before_2_3_11?
13
+ check_graphql_version "< 2.3.11"
14
+ end
15
+
12
16
  def check_graphql_version(predicate)
13
17
  Gem::Dependency.new("graphql", predicate).match?("graphql", GraphQL::VERSION)
14
18
  end
@@ -25,13 +25,6 @@ module GraphQL
25
25
 
26
26
  # Instrumentations were deprecated in 2.2.5, this is a module to migrate to new interface
27
27
  module Tracer
28
- def execute_query(query:)
29
- result = super
30
- ensure
31
- GraphQL::FragmentCache::Schema::Instrumentation.after_query(query)
32
- result
33
- end
34
-
35
28
  def execute_multiplex(multiplex:)
36
29
  result = super
37
30
  multiplex.queries.each do |query|
@@ -12,7 +12,7 @@ module GraphQL
12
12
  yield.tap do |resolved_value|
13
13
  next unless connection_field?(key, data)
14
14
 
15
- verify_connections!(data[:query].context)
15
+ verify_connections!(data[:query].context) if GraphRubyVersion.before_2_3_11?
16
16
  end
17
17
  end
18
18
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module FragmentCache
5
- VERSION = "1.22.0"
5
+ VERSION = "1.22.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-fragment_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.0
4
+ version: 1.22.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-02-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: graphql
@@ -52,6 +51,20 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: benchmark
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
55
68
  - !ruby/object:Gem::Dependency
56
69
  name: sqlite3
57
70
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +163,20 @@ dependencies:
150
163
  - - ">="
151
164
  - !ruby/object:Gem::Version
152
165
  version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: ostruct
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
153
180
  description: Fragment cache for graphql-ruby
154
181
  email:
155
182
  - dmitry.a.tsepelev@gmail.com
@@ -162,9 +189,6 @@ files:
162
189
  - README.md
163
190
  - bin/console
164
191
  - bin/setup
165
- - lib/.rbnext/2.3/graphql/fragment_cache/cache_key_builder.rb
166
- - lib/.rbnext/2.3/graphql/fragment_cache/memory_store.rb
167
- - lib/.rbnext/2.5/graphql/fragment_cache/cacher.rb
168
192
  - lib/graphql-fragment_cache.rb
169
193
  - lib/graphql/fragment_cache.rb
170
194
  - lib/graphql/fragment_cache/cache_key_builder.rb
@@ -192,7 +216,6 @@ metadata:
192
216
  homepage_uri: https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache
193
217
  source_code_uri: https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache
194
218
  changelog_uri: https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/CHANGELOG.md
195
- post_install_message:
196
219
  rdoc_options: []
197
220
  require_paths:
198
221
  - lib
@@ -207,8 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
230
  - !ruby/object:Gem::Version
208
231
  version: '0'
209
232
  requirements: []
210
- rubygems_version: 3.5.22
211
- signing_key:
233
+ rubygems_version: 4.0.3
212
234
  specification_version: 4
213
235
  summary: Fragment cache for graphql-ruby
214
236
  test_files: []
@@ -1,221 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "digest"
5
-
6
- using RubyNext
7
-
8
- module GraphQL
9
- module FragmentCache
10
- using Ext
11
-
12
- using(Module.new {
13
- refine Array do
14
- def traverse_argument(argument)
15
- return argument unless argument.is_a?(GraphQL::Schema::InputObject)
16
-
17
- "{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
18
- end
19
-
20
- def to_selections_key
21
- map { |val|
22
- children = val.selections.empty? ? "" : "[#{val.selections.to_selections_key}]"
23
-
24
- field_name = val.field.name
25
- field_alias = val.ast_nodes.map(&:alias).join
26
- field_name = "#{field_alias}:#{field_name}" unless field_alias.empty?
27
-
28
- unless val.arguments.empty?
29
- args = val.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
30
- field_name += "(#{args})"
31
- end
32
-
33
- "#{field_name}#{children}"
34
- }.join(".")
35
- end
36
- end
37
-
38
- refine ::GraphQL::Language::Nodes::AbstractNode do
39
- def alias?(_)
40
- false
41
- end
42
- end
43
-
44
- refine ::GraphQL::Language::Nodes::Field do
45
- def alias?(val)
46
- self.alias == val
47
- end
48
- end
49
-
50
- refine ::GraphQL::Execution::Lookahead do
51
- def selection_with_alias(name, **kwargs)
52
- return selection(name, **kwargs) if selects?(name, **kwargs)
53
- alias_selection(name, **kwargs)
54
- end
55
-
56
- if GraphRubyVersion.before_2_1_4?
57
- def alias_selection(name, selected_type: @selected_type, arguments: nil)
58
- return alias_selections[name] if alias_selections.key?(name)
59
-
60
- alias_node = lookup_alias_node(ast_nodes, name)
61
- return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
62
-
63
- next_field_name = alias_node.name
64
-
65
- # From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
66
- next_field_defn =
67
- if GraphRubyVersion.before_2_0?
68
- get_class_based_field(selected_type, next_field_name)
69
- else
70
- @query.get_field(selected_type, next_field_name)
71
- end
72
-
73
- alias_selections[name] =
74
- if next_field_defn
75
- next_nodes = []
76
- arguments = @query.arguments_for(alias_node, next_field_defn)
77
- arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
78
- @ast_nodes.each do |ast_node|
79
- ast_node.selections.each do |selection|
80
- if GraphRubyVersion.after_2_0_13? && GraphRubyVersion.before_2_1_4?
81
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
82
- else
83
- find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
84
- end
85
- end
86
- end
87
-
88
- if next_nodes.any?
89
- ::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
90
- else
91
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
92
- end
93
- else
94
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
95
- end
96
- end
97
-
98
- def alias_selections
99
- return @alias_selections if defined?(@alias_selections)
100
- @alias_selections ||= {}
101
- end
102
-
103
- def lookup_alias_node(nodes, name)
104
- return if nodes.empty?
105
-
106
- nodes.find do |node|
107
- if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
108
- node = @query.fragments[node.name]
109
- raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
110
- end
111
-
112
- return node if node.alias?(name)
113
- child = lookup_alias_node(node.children, name)
114
- return child if child
115
- end
116
- end
117
- end
118
- end
119
- })
120
-
121
- # Builds cache key for fragment
122
- class CacheKeyBuilder
123
- using RubyNext
124
-
125
- class << self
126
- def call(**options)
127
- new(**options).build
128
- end
129
- end
130
-
131
- attr_reader :query, :path, :object, :schema
132
-
133
- def initialize(query:, path:, object: nil, **options)
134
- @object = object
135
- @query = query
136
- @schema = query.schema
137
- @path = path
138
- @options = options
139
- end
140
-
141
- def build
142
- key_parts = [
143
- GraphQL::FragmentCache.namespace,
144
- simple_path_cache_key,
145
- implicit_cache_key,
146
- object_cache_key
147
- ]
148
-
149
- key_parts
150
- .compact
151
- .map { |key_part| key_part.tr("/", "-") }
152
- .join("/")
153
- end
154
-
155
- private
156
-
157
- def implicit_cache_key
158
- Digest::SHA1.hexdigest("#{schema_cache_key}/#{query_cache_key}")
159
- end
160
-
161
- def schema_cache_key
162
- @options.fetch(:schema_cache_key) { schema.schema_cache_key }
163
- end
164
-
165
- def query_cache_key
166
- @options.fetch(:query_cache_key) { "#{path_cache_key}[#{selections_cache_key}]" }
167
- end
168
-
169
- def selections_cache_key
170
- current_root =
171
- path.reduce(query.lookahead) { |lkhd, field_name|
172
- # Handle cached fields inside collections:
173
- next lkhd if field_name.is_a?(Integer)
174
-
175
- lkhd.selection_with_alias(field_name)
176
- }
177
-
178
- current_root.selections.to_selections_key
179
- end
180
-
181
- def simple_path_cache_key
182
- return if path_cache_key.nil?
183
-
184
- path_cache_key.split("(").first
185
- end
186
-
187
- def path_cache_key
188
- @path_cache_key ||= @options.fetch(:path_cache_key) do
189
- lookahead = query.lookahead
190
-
191
- path.map { |field_name|
192
- # Handle cached fields inside collections:
193
- next field_name if field_name.is_a?(Integer)
194
-
195
- lookahead = lookahead.selection_with_alias(field_name)
196
- raise "Failed to look ahead the field: #{field_name}" if lookahead.is_a?(::GraphQL::Execution::Lookahead::NullLookahead)
197
-
198
- next lookahead.field.name if lookahead.arguments.empty?
199
-
200
- args = lookahead.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
201
- "#{lookahead.field.name}(#{args})"
202
- }.join("/")
203
- end
204
- end
205
-
206
- def traverse_argument(argument)
207
- return argument unless argument.is_a?(GraphQL::Schema::InputObject)
208
-
209
- "{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
210
- end
211
-
212
- def object_cache_key
213
- @options[:object_cache_key] || object_key(object)
214
- end
215
-
216
- def object_key(obj)
217
- ((((__safe_lvar__ = obj) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__._graphql_cache_key)
218
- end
219
- end
220
- end
221
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- using RubyNext
4
-
5
- module GraphQL
6
- module FragmentCache
7
- # Memory adapter for storing cached fragments
8
- class MemoryStore
9
- using RubyNext
10
-
11
- class Entry < Struct.new(:value, :expires_at, keyword_init: true)
12
- def expired?
13
- expires_at && expires_at < Time.now
14
- end
15
- end
16
-
17
- attr_reader :default_expires_in
18
-
19
- def initialize(expires_in: nil, **other)
20
- raise ArgumentError, "Unsupported options: #{other.keys.join(",")}" unless other.empty?
21
-
22
- @default_expires_in = expires_in
23
- @storage = {}
24
- end
25
-
26
- def keys
27
- storage.keys
28
- end
29
-
30
- def exist?(key)
31
- storage.key?(key)
32
- end
33
-
34
- def read(key)
35
- key = key.to_s
36
- ((((__safe_lvar__ = storage[key]) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.then { |entry|
37
- if entry.expired?
38
- delete(key)
39
- next
40
- end
41
- entry.value
42
- })
43
- end
44
-
45
- def write(key, value, options = {})
46
- expires_in = options[:expires_in] || default_expires_in
47
- key = key.to_s
48
- @storage[key] = Entry.new(value: value, expires_at: expires_in ? Time.now + expires_in : nil)
49
- end
50
-
51
- def delete(key)
52
- key = key.to_s
53
- storage.delete(key)
54
- end
55
-
56
- def clear
57
- storage.clear
58
- end
59
-
60
- private
61
-
62
- attr_reader :storage
63
- end
64
- end
65
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module FragmentCache
5
- using Ext
6
-
7
- class WriteError < StandardError
8
- attr_reader :key, :value, :original_error
9
-
10
- def initialize(original_error, key, value)
11
- @original_error = original_error
12
- @key = key
13
- @value = value
14
-
15
- super(original_error.message)
16
- end
17
- end
18
-
19
- class WriteMultiError < StandardError
20
- attr_reader :values, :original_error
21
-
22
- def initialize(original_error, values)
23
- @original_error = original_error
24
- @values = values
25
-
26
- super(original_error.message)
27
- end
28
- end
29
-
30
- # Saves resolved fragment values to cache store
31
- module Cacher
32
- class << self
33
- def call(query)
34
- return unless query.context.fragments?
35
-
36
- if FragmentCache.cache_store.respond_to?(:write_multi)
37
- batched_persist(query)
38
- else
39
- persist(query)
40
- end
41
- end
42
-
43
- private
44
-
45
- def batched_persist(query)
46
- select_valid_fragments(query).group_by(&:options).each do |options, group|
47
- hash = group.map { |fragment| [fragment.cache_key, fragment.value] }.to_h
48
-
49
- begin
50
- FragmentCache.cache_store.write_multi(hash, options)
51
- rescue => e
52
- raise WriteMultiError.new(e, hash)
53
- end
54
- end
55
- end
56
-
57
- def persist(query)
58
- select_valid_fragments(query).each do |fragment|
59
- begin;FragmentCache.cache_store.write(fragment.cache_key, fragment.value, fragment.options)
60
- rescue => e
61
- raise WriteError.new(e, fragment.cache_key, fragment.value), e;end
62
- end
63
- end
64
-
65
- def select_valid_fragments(query)
66
- query.context.fragments.select(&:with_final_value?)
67
- end
68
- end
69
- end
70
- end
71
- end