graphql-fragment_cache 1.20.5 → 1.21.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 +6 -0
- data/README.md +53 -1
- data/lib/.rbnext/2.3/graphql/fragment_cache/cache_key_builder.rb +50 -48
- data/lib/graphql/fragment_cache/cache_key_builder.rb +0 -67
- data/lib/graphql/fragment_cache/fragment.rb +23 -6
- data/lib/graphql/fragment_cache/graphql_ruby_version.rb +0 -14
- data/lib/graphql/fragment_cache/memory_store.rb +0 -4
- data/lib/graphql/fragment_cache/railtie.rb +3 -1
- data/lib/graphql/fragment_cache/schema/lazy_cache_resolver.rb +11 -0
- data/lib/graphql/fragment_cache/version.rb +1 -1
- data/lib/graphql/fragment_cache.rb +2 -16
- data/lib/graphql-fragment_cache.rb +0 -5
- metadata +10 -40
- data/lib/.rbnext/2.7/graphql/fragment_cache/cache_key_builder.rb +0 -219
- data/lib/.rbnext/2.7/graphql/fragment_cache/ext/graphql_cache_key.rb +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a13fb7ec2904be7d61b8f2ff020cc131d794f77cda87f9ea0cca5a71850055d8
|
4
|
+
data.tar.gz: 4c04a9f8aed82998a96c28334b6fc0a3dc06de61d8c0d9e6eb7287dc8b378076
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4347bed71acb7657604b0ede1bccf075fffc00429697dc6c05cd26fa169ed19e58325f542a94b964f2129b4b25c3656c74d4e567440cfd848f5514e3ed1dd9c0
|
7
|
+
data.tar.gz: 42f0f52fe8d48d386c1bbc4a931eb3ff08d63164f2fd18f10d77830f044df496c27702f2af383117f685ae3eda1df7717f948d978ba42cb5fa812931a827147d
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.21.0 (2025-02-01)
|
6
|
+
|
7
|
+
- [PR#130](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/130) Dataloader support ([@DmitryTsepelev][])
|
8
|
+
- [PR#125](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/125) Introduce cache lookup instrumentation hook ([@danielhartnell][])
|
9
|
+
|
5
10
|
## 1.20.5 (2024-11-02)
|
6
11
|
|
7
12
|
- [PR#120](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/120) Fix warning on ActiveSupport::Cache.format_version ([@Drowze][])
|
@@ -211,3 +216,4 @@
|
|
211
216
|
[@diegofigueroa]: https://github.com/diegofigueroa
|
212
217
|
[@noma4i]: https://github.com/noma4i
|
213
218
|
[@Drowze]: https://github.com/Drowze
|
219
|
+
[@danielhartnell]: https://github.com/danielhartnell
|
data/README.md
CHANGED
@@ -381,6 +381,34 @@ class QueryType < BaseObject
|
|
381
381
|
end
|
382
382
|
```
|
383
383
|
|
384
|
+
## Dataloader
|
385
|
+
|
386
|
+
If you are using [Dataloader](https://graphql-ruby.org/dataloader/overview.html), you will need to let the gem know using `dataloader: true`:
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
class PostType < BaseObject
|
390
|
+
field :author, User, null: false
|
391
|
+
|
392
|
+
def author
|
393
|
+
cache_fragment(dataloader: true) do
|
394
|
+
dataloader.with(AuthorDataloaderSource).load(object.id)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# or
|
400
|
+
|
401
|
+
class PostType < BaseObject
|
402
|
+
field :author, User, null: false, cache_fragment: {dataloader: true}
|
403
|
+
|
404
|
+
def author
|
405
|
+
dataloader.with(AuthorDataloaderSource).load(object.id)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
```
|
409
|
+
|
410
|
+
The problem is that I didn't find a way to detect that dataloader (and, therefore, Fiber) is used, and the block is forced to resolve, causing the N+1 inside the Dataloader Source class.
|
411
|
+
|
384
412
|
## How to use `#cache_fragment` in extensions (and other places where context is not available)
|
385
413
|
|
386
414
|
If you want to call `#cache_fragment` from places other that fields or resolvers, you'll need to pass `context` explicitly and turn on `raw_value` support. For instance, let's take a look at this extension:
|
@@ -446,6 +474,30 @@ Cache processing can be disabled if needed. For example:
|
|
446
474
|
GraphQL::FragmentCache.enabled = false if Rails.env.test?
|
447
475
|
```
|
448
476
|
|
477
|
+
## Cache lookup monitoring
|
478
|
+
|
479
|
+
It may be useful to capture cache lookup events. When monitoring is enabled, the `cache_key`, `operation_name`, `path` and a boolean indicating a cache hit or miss will be sent to a `cache_lookup_event` method. This method can be implemented in your application to handle the event.
|
480
|
+
|
481
|
+
Example handler defined in a Rails initializer:
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
module GraphQL
|
485
|
+
module FragmentCache
|
486
|
+
class Fragment
|
487
|
+
def self.cache_lookup_event(**args)
|
488
|
+
# Monitoring such as incrementing a cache hit counter metric
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
```
|
494
|
+
|
495
|
+
Like managing caching itself, monitoring can be enabled if needed. It is disabled by default. For example:
|
496
|
+
|
497
|
+
```ruby
|
498
|
+
GraphQL::FragmentCache.monitoring_enabled = true
|
499
|
+
```
|
500
|
+
|
449
501
|
## Limitations
|
450
502
|
|
451
503
|
1. `Schema#execute`, [graphql-batch](https://github.com/Shopify/graphql-batch) and _graphql-ruby-fragment_cache_ do not [play well](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/issues/45) together. The problem appears when `cache_fragment` is _inside_ the `.then` block:
|
@@ -477,7 +529,7 @@ end
|
|
477
529
|
field :cached_avatar_url, String, null: false
|
478
530
|
|
479
531
|
def cached_avatar_url
|
480
|
-
cache_fragment(
|
532
|
+
cache_fragment(path_cache_key: "post_avatar_url(#{object.id})") { object.avatar_url }
|
481
533
|
end
|
482
534
|
```
|
483
535
|
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
def traverse_argument(argument)
|
15
15
|
return argument unless argument.is_a?(GraphQL::Schema::InputObject)
|
16
16
|
|
17
|
-
"{#{argument.map { |
|
17
|
+
"{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
|
18
18
|
end
|
19
19
|
|
20
20
|
def to_selections_key
|
@@ -26,7 +26,7 @@ module GraphQL
|
|
26
26
|
field_name = "#{field_alias}:#{field_name}" unless field_alias.empty?
|
27
27
|
|
28
28
|
unless val.arguments.empty?
|
29
|
-
args = val.arguments.map { |
|
29
|
+
args = val.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
|
30
30
|
field_name += "(#{args})"
|
31
31
|
end
|
32
32
|
|
@@ -53,64 +53,66 @@ module GraphQL
|
|
53
53
|
alias_selection(name, **kwargs)
|
54
54
|
end
|
55
55
|
|
56
|
-
|
57
|
-
|
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)
|
58
59
|
|
59
|
-
|
60
|
-
|
60
|
+
alias_node = lookup_alias_node(ast_nodes, name)
|
61
|
+
return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
|
61
62
|
|
62
|
-
|
63
|
+
next_field_name = alias_node.name
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
83
85
|
end
|
84
86
|
end
|
85
|
-
end
|
86
87
|
|
87
|
-
|
88
|
-
|
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
|
89
93
|
else
|
90
94
|
::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
|
91
95
|
end
|
92
|
-
|
93
|
-
::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
|
94
|
-
end
|
95
|
-
end
|
96
|
+
end
|
96
97
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
def alias_selections
|
99
|
+
return @alias_selections if defined?(@alias_selections)
|
100
|
+
@alias_selections ||= {}
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
-
|
103
|
+
def lookup_alias_node(nodes, name)
|
104
|
+
return if nodes.empty?
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
112
|
+
return node if node.alias?(name)
|
113
|
+
child = lookup_alias_node(node.children, name)
|
114
|
+
return child if child
|
115
|
+
end
|
114
116
|
end
|
115
117
|
end
|
116
118
|
end
|
@@ -195,7 +197,7 @@ module GraphQL
|
|
195
197
|
|
196
198
|
next lookahead.field.name if lookahead.arguments.empty?
|
197
199
|
|
198
|
-
args = lookahead.arguments.map { |
|
200
|
+
args = lookahead.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
|
199
201
|
"#{lookahead.field.name}(#{args})"
|
200
202
|
}.join("/")
|
201
203
|
end
|
@@ -204,7 +206,7 @@ module GraphQL
|
|
204
206
|
def traverse_argument(argument)
|
205
207
|
return argument unless argument.is_a?(GraphQL::Schema::InputObject)
|
206
208
|
|
207
|
-
"{#{argument.map { |
|
209
|
+
"{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
|
208
210
|
end
|
209
211
|
|
210
212
|
def object_cache_key
|
@@ -3,8 +3,6 @@
|
|
3
3
|
require "json"
|
4
4
|
require "digest"
|
5
5
|
|
6
|
-
using RubyNext
|
7
|
-
|
8
6
|
module GraphQL
|
9
7
|
module FragmentCache
|
10
8
|
using Ext
|
@@ -52,76 +50,11 @@ module GraphQL
|
|
52
50
|
return selection(name, **kwargs) if selects?(name, **kwargs)
|
53
51
|
alias_selection(name, **kwargs)
|
54
52
|
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
53
|
end
|
119
54
|
})
|
120
55
|
|
121
56
|
# Builds cache key for fragment
|
122
57
|
class CacheKeyBuilder
|
123
|
-
using RubyNext
|
124
|
-
|
125
58
|
class << self
|
126
59
|
def call(**options)
|
127
60
|
new(**options).build
|
@@ -16,12 +16,10 @@ module GraphQL
|
|
16
16
|
return fragments.map { |f| [f, f.read] }.to_h
|
17
17
|
end
|
18
18
|
|
19
|
-
fragments_to_cache_keys = fragments
|
20
|
-
.map { |f| [f, f.cache_key] }.to_h
|
19
|
+
fragments_to_cache_keys = fragments.map { |f| [f, f.cache_key] }.to_h
|
21
20
|
|
22
21
|
# Filter out all the cache_keys for fragments with renew_cache: true in their context
|
23
|
-
cache_keys = fragments_to_cache_keys
|
24
|
-
.reject { |k, _v| k.context[:renew_cache] == true }.values
|
22
|
+
cache_keys = fragments_to_cache_keys.reject { |k, _v| k.context[:renew_cache] == true }.values
|
25
23
|
|
26
24
|
# If there are cache_keys look up values with read_multi otherwise return an empty hash
|
27
25
|
cache_keys_to_values = if cache_keys.empty?
|
@@ -30,9 +28,23 @@ module GraphQL
|
|
30
28
|
FragmentCache.cache_store.read_multi(*cache_keys)
|
31
29
|
end
|
32
30
|
|
31
|
+
if GraphQL::FragmentCache.monitoring_enabled
|
32
|
+
begin
|
33
|
+
fragments.map do |fragment|
|
34
|
+
cache_lookup_event(
|
35
|
+
cache_key: fragment.cache_key,
|
36
|
+
operation_name: fragment.context.query.operation_name,
|
37
|
+
path: fragment.path,
|
38
|
+
cache_hit: cache_keys_to_values.key?(fragment.cache_key)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
# Allow cache_lookup_event to fail when we do not have all of the requested attributes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
33
46
|
# Fragmenst without values or with renew_cache: true in their context will have nil values like the read method
|
34
|
-
fragments_to_cache_keys
|
35
|
-
.map { |fragment, cache_key| [fragment, cache_keys_to_values[cache_key]] }.to_h
|
47
|
+
fragments_to_cache_keys.map { |fragment, cache_key| [fragment, cache_keys_to_values[cache_key]] }.to_h
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
@@ -87,6 +99,11 @@ module GraphQL
|
|
87
99
|
def final_value
|
88
100
|
@final_value ||= context.query.result["data"]
|
89
101
|
end
|
102
|
+
|
103
|
+
def cache_lookup_event(**args)
|
104
|
+
# This method can be implemented in your application
|
105
|
+
# This provides a mechanism to monitor cache hits for a fragment
|
106
|
+
end
|
90
107
|
end
|
91
108
|
end
|
92
109
|
end
|
@@ -1,24 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
using RubyNext
|
4
|
-
|
5
3
|
module GraphQL
|
6
4
|
module FragmentCache
|
7
5
|
module GraphRubyVersion
|
8
6
|
module_function
|
9
7
|
|
10
|
-
def before_2_0?
|
11
|
-
check_graphql_version "< 2.0.0"
|
12
|
-
end
|
13
|
-
|
14
|
-
def after_2_0_13?
|
15
|
-
check_graphql_version "> 2.0.13"
|
16
|
-
end
|
17
|
-
|
18
|
-
def before_2_1_4?
|
19
|
-
check_graphql_version "< 2.1.4"
|
20
|
-
end
|
21
|
-
|
22
8
|
def after_2_2_5?
|
23
9
|
check_graphql_version "> 2.2.5"
|
24
10
|
end
|
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
using RubyNext
|
4
|
-
|
5
3
|
module GraphQL
|
6
4
|
module FragmentCache
|
7
5
|
# Memory adapter for storing cached fragments
|
8
6
|
class MemoryStore
|
9
|
-
using RubyNext
|
10
|
-
|
11
7
|
class Entry < Struct.new(:value, :expires_at, keyword_init: true)
|
12
8
|
def expired?
|
13
9
|
expires_at && expires_at < Time.now
|
@@ -26,7 +26,9 @@ module GraphQL
|
|
26
26
|
|
27
27
|
if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
|
28
28
|
initializer "graphql-fragment_cache" do
|
29
|
-
config.graphql_fragment_cache.store = if Rails.version.to_f >=
|
29
|
+
config.graphql_fragment_cache.store = if Rails.version.to_f >= 8.0
|
30
|
+
[:null_store]
|
31
|
+
elsif Rails.version.to_f >= 7.0
|
30
32
|
[:null_store, serializer: :marshal_7_0]
|
31
33
|
else
|
32
34
|
:null_store
|
@@ -16,6 +16,8 @@ module GraphQL
|
|
16
16
|
@block = block
|
17
17
|
|
18
18
|
@lazy_state[:pending_fragments] << @fragment
|
19
|
+
|
20
|
+
ensure_dataloader_resulution! if @fragment.options[:dataloader]
|
19
21
|
end
|
20
22
|
|
21
23
|
def resolve
|
@@ -35,6 +37,15 @@ module GraphQL
|
|
35
37
|
@query_ctx.fragments << @fragment
|
36
38
|
end
|
37
39
|
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def ensure_dataloader_resulution!
|
44
|
+
return if FragmentCache.cache_store.exist?(@fragment.cache_key)
|
45
|
+
|
46
|
+
@object_to_cache = @block.call
|
47
|
+
@block = nil
|
48
|
+
end
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
@@ -25,14 +25,13 @@ module GraphQL
|
|
25
25
|
class << self
|
26
26
|
attr_reader :cache_store
|
27
27
|
attr_accessor :enabled
|
28
|
+
attr_accessor :monitoring_enabled
|
28
29
|
attr_accessor :namespace
|
29
30
|
attr_accessor :default_options
|
30
31
|
|
31
32
|
attr_accessor :skip_cache_when_query_has_errors
|
32
33
|
|
33
34
|
def use(schema_defn, options = {})
|
34
|
-
verify_interpreter_and_analysis!(schema_defn)
|
35
|
-
|
36
35
|
if GraphRubyVersion.after_2_2_5?
|
37
36
|
schema_defn.trace_with(Schema::Instrumentation::Tracer)
|
38
37
|
else
|
@@ -69,24 +68,11 @@ module GraphQL
|
|
69
68
|
def check_graphql_version(predicate)
|
70
69
|
Gem::Dependency.new("graphql", predicate).match?("graphql", GraphQL::VERSION)
|
71
70
|
end
|
72
|
-
|
73
|
-
def verify_interpreter_and_analysis!(schema_defn)
|
74
|
-
if GraphRubyVersion.before_2_0?
|
75
|
-
unless schema_defn.interpreter?
|
76
|
-
raise StandardError,
|
77
|
-
"GraphQL::Execution::Interpreter should be enabled for fragment caching"
|
78
|
-
end
|
79
|
-
|
80
|
-
unless schema_defn.analysis_engine == GraphQL::Analysis::AST
|
81
|
-
raise StandardError,
|
82
|
-
"GraphQL::Analysis::AST should be enabled for fragment caching"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
71
|
end
|
87
72
|
|
88
73
|
self.cache_store = MemoryStore.new
|
89
74
|
self.enabled = true
|
75
|
+
self.monitoring_enabled = false
|
90
76
|
self.namespace = "graphql"
|
91
77
|
self.default_options = {}
|
92
78
|
self.skip_cache_when_query_has_errors = false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-fragment_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -16,28 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.1.4
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: ruby-next
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 0.15.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 0.15.0
|
26
|
+
version: 2.1.4
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: combustion
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,16 +56,16 @@ dependencies:
|
|
70
56
|
name: sqlite3
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
|
-
- - "
|
59
|
+
- - ">="
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
61
|
+
version: '0'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
|
-
- - "
|
66
|
+
- - ">="
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
68
|
+
version: '0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: rake
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,20 +108,6 @@ dependencies:
|
|
122
108
|
- - ">="
|
123
109
|
- !ruby/object:Gem::Version
|
124
110
|
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: ruby-next
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0.10'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0.10'
|
139
111
|
- !ruby/object:Gem::Dependency
|
140
112
|
name: unparser
|
141
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -193,8 +165,6 @@ files:
|
|
193
165
|
- lib/.rbnext/2.3/graphql/fragment_cache/cache_key_builder.rb
|
194
166
|
- lib/.rbnext/2.3/graphql/fragment_cache/memory_store.rb
|
195
167
|
- lib/.rbnext/2.5/graphql/fragment_cache/cacher.rb
|
196
|
-
- lib/.rbnext/2.7/graphql/fragment_cache/cache_key_builder.rb
|
197
|
-
- lib/.rbnext/2.7/graphql/fragment_cache/ext/graphql_cache_key.rb
|
198
168
|
- lib/graphql-fragment_cache.rb
|
199
169
|
- lib/graphql/fragment_cache.rb
|
200
170
|
- lib/graphql/fragment_cache/cache_key_builder.rb
|
@@ -230,14 +200,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
230
200
|
requirements:
|
231
201
|
- - ">="
|
232
202
|
- !ruby/object:Gem::Version
|
233
|
-
version: '3.
|
203
|
+
version: '3.1'
|
234
204
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
235
205
|
requirements:
|
236
206
|
- - ">="
|
237
207
|
- !ruby/object:Gem::Version
|
238
208
|
version: '0'
|
239
209
|
requirements: []
|
240
|
-
rubygems_version: 3.
|
210
|
+
rubygems_version: 3.5.22
|
241
211
|
signing_key:
|
242
212
|
specification_version: 4
|
243
213
|
summary: Fragment cache for graphql-ruby
|
@@ -1,219 +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 { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.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 { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.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
|
-
def alias_selection(name, selected_type: @selected_type, arguments: nil)
|
57
|
-
return alias_selections[name] if alias_selections.key?(name)
|
58
|
-
|
59
|
-
alias_node = lookup_alias_node(ast_nodes, name)
|
60
|
-
return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
|
61
|
-
|
62
|
-
next_field_name = alias_node.name
|
63
|
-
|
64
|
-
# From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
|
65
|
-
next_field_defn =
|
66
|
-
if GraphQL::FragmentCache.graphql_ruby_before_2_0?
|
67
|
-
get_class_based_field(selected_type, next_field_name)
|
68
|
-
else
|
69
|
-
@query.get_field(selected_type, next_field_name)
|
70
|
-
end
|
71
|
-
|
72
|
-
alias_selections[name] =
|
73
|
-
if next_field_defn
|
74
|
-
next_nodes = []
|
75
|
-
arguments = @query.arguments_for(alias_node, next_field_defn)
|
76
|
-
arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
|
77
|
-
@ast_nodes.each do |ast_node|
|
78
|
-
ast_node.selections.each do |selection|
|
79
|
-
if GraphQL::FragmentCache.graphql_ruby_after_2_0_13? && GraphQL::FragmentCache.graphql_ruby_before_2_1_4?
|
80
|
-
find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
|
81
|
-
else
|
82
|
-
find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
if next_nodes.any?
|
88
|
-
::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
|
89
|
-
else
|
90
|
-
::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
|
91
|
-
end
|
92
|
-
else
|
93
|
-
::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def alias_selections
|
98
|
-
return @alias_selections if defined?(@alias_selections)
|
99
|
-
@alias_selections ||= {}
|
100
|
-
end
|
101
|
-
|
102
|
-
def lookup_alias_node(nodes, name)
|
103
|
-
return if nodes.empty?
|
104
|
-
|
105
|
-
nodes.find do |node|
|
106
|
-
if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
|
107
|
-
node = @query.fragments[node.name]
|
108
|
-
raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
|
109
|
-
end
|
110
|
-
|
111
|
-
return node if node.alias?(name)
|
112
|
-
child = lookup_alias_node(node.children, name)
|
113
|
-
return child if child
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
})
|
118
|
-
|
119
|
-
# Builds cache key for fragment
|
120
|
-
class CacheKeyBuilder
|
121
|
-
using RubyNext
|
122
|
-
|
123
|
-
class << self
|
124
|
-
def call(**options)
|
125
|
-
new(**options).build
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
attr_reader :query, :path, :object, :schema
|
130
|
-
|
131
|
-
def initialize(query:, path:, object: nil, **options)
|
132
|
-
@object = object
|
133
|
-
@query = query
|
134
|
-
@schema = query.schema
|
135
|
-
@path = path
|
136
|
-
@options = options
|
137
|
-
end
|
138
|
-
|
139
|
-
def build
|
140
|
-
key_parts = [
|
141
|
-
GraphQL::FragmentCache.namespace,
|
142
|
-
simple_path_cache_key,
|
143
|
-
implicit_cache_key,
|
144
|
-
object_cache_key
|
145
|
-
]
|
146
|
-
|
147
|
-
key_parts
|
148
|
-
.compact
|
149
|
-
.map { |key_part| key_part.tr("/", "-") }
|
150
|
-
.join("/")
|
151
|
-
end
|
152
|
-
|
153
|
-
private
|
154
|
-
|
155
|
-
def implicit_cache_key
|
156
|
-
Digest::SHA1.hexdigest("#{schema_cache_key}/#{query_cache_key}")
|
157
|
-
end
|
158
|
-
|
159
|
-
def schema_cache_key
|
160
|
-
@options.fetch(:schema_cache_key) { schema.schema_cache_key }
|
161
|
-
end
|
162
|
-
|
163
|
-
def query_cache_key
|
164
|
-
@options.fetch(:query_cache_key) { "#{path_cache_key}[#{selections_cache_key}]" }
|
165
|
-
end
|
166
|
-
|
167
|
-
def selections_cache_key
|
168
|
-
current_root =
|
169
|
-
path.reduce(query.lookahead) { |lkhd, field_name|
|
170
|
-
# Handle cached fields inside collections:
|
171
|
-
next lkhd if field_name.is_a?(Integer)
|
172
|
-
|
173
|
-
lkhd.selection_with_alias(field_name)
|
174
|
-
}
|
175
|
-
|
176
|
-
current_root.selections.to_selections_key
|
177
|
-
end
|
178
|
-
|
179
|
-
def simple_path_cache_key
|
180
|
-
return if path_cache_key.nil?
|
181
|
-
|
182
|
-
path_cache_key.split("(").first
|
183
|
-
end
|
184
|
-
|
185
|
-
def path_cache_key
|
186
|
-
@path_cache_key ||= @options.fetch(:path_cache_key) do
|
187
|
-
lookahead = query.lookahead
|
188
|
-
|
189
|
-
path.map { |field_name|
|
190
|
-
# Handle cached fields inside collections:
|
191
|
-
next field_name if field_name.is_a?(Integer)
|
192
|
-
|
193
|
-
lookahead = lookahead.selection_with_alias(field_name)
|
194
|
-
raise "Failed to look ahead the field: #{field_name}" if lookahead.is_a?(::GraphQL::Execution::Lookahead::NullLookahead)
|
195
|
-
|
196
|
-
next lookahead.field.name if lookahead.arguments.empty?
|
197
|
-
|
198
|
-
args = lookahead.arguments.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")
|
199
|
-
"#{lookahead.field.name}(#{args})"
|
200
|
-
}.join("/")
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def traverse_argument(argument)
|
205
|
-
return argument unless argument.is_a?(GraphQL::Schema::InputObject)
|
206
|
-
|
207
|
-
"{#{argument.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")}}"
|
208
|
-
end
|
209
|
-
|
210
|
-
def object_cache_key
|
211
|
-
@options[:object_cache_key] || object_key(object)
|
212
|
-
end
|
213
|
-
|
214
|
-
def object_key(obj)
|
215
|
-
obj&._graphql_cache_key
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module FragmentCache
|
5
|
-
module Ext
|
6
|
-
# Adds #_graphql_cache_key method to Object,
|
7
|
-
# which just call #graphql_cache_key or #cache_key.
|
8
|
-
#
|
9
|
-
# For other core classes returns string representation.
|
10
|
-
#
|
11
|
-
# Raises ArgumentError otherwise.
|
12
|
-
#
|
13
|
-
# We use a refinement to avoid case/if statements for type checking
|
14
|
-
refine Object do
|
15
|
-
def _graphql_cache_key
|
16
|
-
return graphql_cache_key if respond_to?(:graphql_cache_key)
|
17
|
-
return cache_key if respond_to?(:cache_key)
|
18
|
-
return to_a._graphql_cache_key if respond_to?(:to_a)
|
19
|
-
|
20
|
-
to_s
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
refine Array do
|
25
|
-
def _graphql_cache_key
|
26
|
-
map { |_1| _1._graphql_cache_key }.join("/")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
refine NilClass do
|
31
|
-
def _graphql_cache_key
|
32
|
-
""
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
refine TrueClass do
|
37
|
-
def _graphql_cache_key
|
38
|
-
"t"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
refine FalseClass do
|
43
|
-
def _graphql_cache_key
|
44
|
-
"f"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
refine String do
|
49
|
-
def _graphql_cache_key
|
50
|
-
self
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
refine Symbol do
|
55
|
-
def _graphql_cache_key
|
56
|
-
to_s
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
if RUBY_PLATFORM.match?(/java/i)
|
61
|
-
refine Integer do
|
62
|
-
def _graphql_cache_key
|
63
|
-
to_s
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
refine Float do
|
68
|
-
def _graphql_cache_key
|
69
|
-
to_s
|
70
|
-
end
|
71
|
-
end
|
72
|
-
else
|
73
|
-
refine Numeric do
|
74
|
-
def _graphql_cache_key
|
75
|
-
to_s
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
refine Time do
|
81
|
-
def _graphql_cache_key
|
82
|
-
to_s
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
refine Module do
|
87
|
-
def _graphql_cache_key
|
88
|
-
name
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|