graphql-fragment_cache 0.1.5 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69226c4789fd364bcd6bf0991ebd358c39a32fee383a8ce7445418d0af46eb04
4
- data.tar.gz: d7a1901dd86a3311f60a02153422044c055ffd46b0297026ab9f434eca1667dd
3
+ metadata.gz: 515ec358aecf867ce2d2ec5ac82003d802dc6c31d6402d62593a626ac5bad81e
4
+ data.tar.gz: 8b1b3d82c0fb808a2cd826c710047635ba141895843ff7b804a301848d266ff5
5
5
  SHA512:
6
- metadata.gz: 5a22448276b2618d0f85c67aa371fa20828ac24f3f943779d748214214a85d0a55c129b31928e96b904e6e1c8734f4cf5ef32bcb2debf38b9208bfea7fefc29f
7
- data.tar.gz: 17033312f4e5c89c3a135da2c2e12b2980fc5b5582ce22b3089ae1e3b7c0e95de44ca1e19f42cb0a236b6fcd7b6ff9630bb300ee1592d9ec4cf6a759f7e82839
6
+ metadata.gz: 2c2dd089bf31f6d2491fc7d904d8ca356a2dfbc2cace258fe1e3b49c18dc59a64805a1cb96655bfb23eacc62ed027198365491759005b232354a2ee8ed41e605
7
+ data.tar.gz: 92309ef8b7ee62a5fd85007a88be932874f996d12ee08a1e81530d90fa3138dc631f90ad6a6f41c3952338001e10cefbeb1691bcea7891ba9b2f4027543e659d
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.0.2 (2020-08-19)
6
+
7
+ - [PR#28](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/28) Support #keys method for GraphQL::FragmentCache::MemoryStore instance ([@reabiliti][])
8
+
9
+ ## 1.0.1 (2020-06-17)
10
+
11
+ - [PR#25](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/25) Support fragments with aliases for CacheKeyBuilder ([@DmitryTsepelev][])
12
+
13
+ ## 1.0.0 (2020-06-13)
14
+
15
+ - [PR#24](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/24) Add nil caching. **BREAKING CHANGE**: custom cache stores must also implement `#exist?(key)` method ([@DmitryTsepelev][])
16
+
17
+ ## 0.1.7 (2020-06-02)
18
+
19
+ - [PR#23](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/23) Avoid extra queries after restoring connection from cache ([@DmitryTsepelev][])
20
+
21
+ ## 0.1.6 (2020-05-30)
22
+
23
+ - [PR#22](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/22) Properly cache entites inside collections ([@DmitryTsepelev][])
24
+
5
25
  ## 0.1.5 (2020-04-28)
6
26
 
7
27
  - [PR#19](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/19) Add connections support ([@DmitryTsepelev][])
@@ -31,3 +51,4 @@
31
51
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
32
52
  [@palkan]: https://github.com/palkan
33
53
  [@ssnickolay]: https://github.com/ssnickolay
54
+ [@reabiliti]: https://github.com/reabiliti
data/README.md CHANGED
@@ -252,7 +252,7 @@ Rails.application.configure do |config|
252
252
  end
253
253
  ```
254
254
 
255
- ⚠️ Cache store must implement `#read(key)` and `#write(key, value, **options)` methods.
255
+ ⚠️ Cache store must implement `#read(key)`, `#write(key, value, **options)` and `#exist?(key)` methods.
256
256
 
257
257
  The gem provides only in-memory store out-of-the-box (`GraphQL::FragmentCache::MemoryStore`). It's used by default.
258
258
 
@@ -76,7 +76,13 @@ module GraphQL
76
76
 
77
77
  def lookup_alias_node(nodes, name)
78
78
  return if nodes.empty?
79
+
79
80
  nodes.find do |node|
81
+ if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
82
+ node = @query.fragments[node.name]
83
+ raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
84
+ end
85
+
80
86
  return node if node.alias?(name)
81
87
  child = lookup_alias_node(node.children, name)
82
88
  return child if child
@@ -124,7 +130,12 @@ module GraphQL
124
130
 
125
131
  def selections_cache_key
126
132
  current_root =
127
- path.reduce(query.lookahead) { |lkhd, name| lkhd.selection_with_alias(name) }
133
+ path.reduce(query.lookahead) { |lkhd, field_name|
134
+ # Handle cached fields inside collections:
135
+ next lkhd if field_name.is_a?(Integer)
136
+
137
+ lkhd.selection_with_alias(field_name)
138
+ }
128
139
 
129
140
  current_root.selections.to_selections_key
130
141
  end
@@ -133,6 +144,9 @@ module GraphQL
133
144
  lookahead = query.lookahead
134
145
 
135
146
  path.map { |field_name|
147
+ # Handle cached fields inside collections:
148
+ next field_name if field_name.is_a?(Integer)
149
+
136
150
  lookahead = lookahead.selection_with_alias(field_name)
137
151
  raise "Failed to look ahead the field: #{field_name}" if lookahead.is_a?(::GraphQL::Execution::Lookahead::NullLookahead)
138
152
 
@@ -1,6 +1,6 @@
1
1
  require "ruby-next"
2
2
 
3
3
  require "ruby-next/language/setup"
4
- RubyNext::Language.setup_gem_load_path
4
+ RubyNext::Language.setup_gem_load_path(transpile: true)
5
5
 
6
6
  require "graphql/fragment_cache"
@@ -4,10 +4,11 @@ require "graphql"
4
4
 
5
5
  require "graphql/fragment_cache/ext/context_fragments"
6
6
  require "graphql/fragment_cache/ext/graphql_cache_key"
7
-
8
- require "graphql/fragment_cache/schema_patch"
9
7
  require "graphql/fragment_cache/object"
10
- require "graphql/fragment_cache/instrumentation"
8
+
9
+ require "graphql/fragment_cache/schema/patch"
10
+ require "graphql/fragment_cache/schema/tracer"
11
+ require "graphql/fragment_cache/schema/instrumentation"
11
12
 
12
13
  require "graphql/fragment_cache/memory_store"
13
14
 
@@ -22,8 +23,9 @@ module GraphQL
22
23
  def use(schema_defn, options = {})
23
24
  verify_interpreter!(schema_defn)
24
25
 
25
- schema_defn.instrument(:query, Instrumentation)
26
- schema_defn.extend(SchemaPatch)
26
+ schema_defn.tracer(Schema::Tracer)
27
+ schema_defn.instrument(:query, Schema::Instrumentation)
28
+ schema_defn.extend(Schema::Patch)
27
29
  end
28
30
 
29
31
  def cache_store=(store)
@@ -76,7 +76,13 @@ module GraphQL
76
76
 
77
77
  def lookup_alias_node(nodes, name)
78
78
  return if nodes.empty?
79
+
79
80
  nodes.find do |node|
81
+ if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
82
+ node = @query.fragments[node.name]
83
+ raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
84
+ end
85
+
80
86
  return node if node.alias?(name)
81
87
  child = lookup_alias_node(node.children, name)
82
88
  return child if child
@@ -124,7 +130,12 @@ module GraphQL
124
130
 
125
131
  def selections_cache_key
126
132
  current_root =
127
- path.reduce(query.lookahead) { |lkhd, name| lkhd.selection_with_alias(name) }
133
+ path.reduce(query.lookahead) { |lkhd, field_name|
134
+ # Handle cached fields inside collections:
135
+ next lkhd if field_name.is_a?(Integer)
136
+
137
+ lkhd.selection_with_alias(field_name)
138
+ }
128
139
 
129
140
  current_root.selections.to_selections_key
130
141
  end
@@ -133,6 +144,9 @@ module GraphQL
133
144
  lookahead = query.lookahead
134
145
 
135
146
  path.map { |field_name|
147
+ # Handle cached fields inside collections:
148
+ next field_name if field_name.is_a?(Integer)
149
+
136
150
  lookahead = lookahead.selection_with_alias(field_name)
137
151
  raise "Failed to look ahead the field: #{field_name}" if lookahead.is_a?(::GraphQL::Execution::Lookahead::NullLookahead)
138
152
 
@@ -10,9 +10,7 @@ module GraphQL
10
10
  def call(query)
11
11
  return unless query.context.fragments?
12
12
 
13
- final_value = query.context.namespace(:interpreter)[:runtime].final_value
14
-
15
- query.context.fragments.each { _1.persist(final_value) }
13
+ query.context.fragments.each(&:persist)
16
14
  end
17
15
  end
18
16
  end
@@ -6,22 +6,28 @@ module GraphQL
6
6
  module FragmentCache
7
7
  # Represents a single fragment to cache
8
8
  class Fragment
9
- attr_reader :options, :path, :context, :raw_connection
9
+ attr_reader :options, :path, :context
10
10
 
11
- attr_writer :raw_connection
11
+ attr_accessor :resolved_value
12
12
 
13
13
  def initialize(context, **options)
14
14
  @context = context
15
15
  @options = options
16
- @path = context.namespace(:interpreter)[:current_path]
16
+ @path = interpreter_context[:current_path]
17
17
  end
18
18
 
19
+ NIL_IN_CACHE = Object.new
20
+
19
21
  def read
20
- FragmentCache.cache_store.read(cache_key)
22
+ FragmentCache.cache_store.read(cache_key).tap do |cached|
23
+ return NIL_IN_CACHE if cached.nil? && FragmentCache.cache_store.exist?(cache_key)
24
+ end
21
25
  end
22
26
 
23
- def persist(final_value)
24
- value = raw_connection || resolve(final_value)
27
+ def persist
28
+ # Connections are not available from the runtime object, so
29
+ # we rely on Schema::Tracer to save it for us
30
+ value = resolved_value || resolve_from_runtime
25
31
  FragmentCache.cache_store.write(cache_key, value, **options)
26
32
  end
27
33
 
@@ -31,9 +37,17 @@ module GraphQL
31
37
  @cache_key ||= CacheKeyBuilder.call(path: path, query: context.query, **options)
32
38
  end
33
39
 
34
- def resolve(final_value)
40
+ def interpreter_context
41
+ context.namespace(:interpreter)
42
+ end
43
+
44
+ def resolve_from_runtime
35
45
  final_value.dig(*path)
36
46
  end
47
+
48
+ def final_value
49
+ @final_value ||= interpreter_context[:runtime].final_value
50
+ end
37
51
  end
38
52
  end
39
53
  end
@@ -23,6 +23,14 @@ module GraphQL
23
23
  @storage = {}
24
24
  end
25
25
 
26
+ def keys
27
+ storage.keys
28
+ end
29
+
30
+ def exist?(key)
31
+ storage.key?(key)
32
+ end
33
+
26
34
  def read(key)
27
35
  key = key.to_s
28
36
  storage[key]&.then do |entry|
@@ -6,8 +6,6 @@ module GraphQL
6
6
  module FragmentCache
7
7
  using Ext
8
8
 
9
- RawConnection = Struct.new(:items, :nodes, :paged_nodes_offset, :has_previous_page, :has_next_page)
10
-
11
9
  # Adds #cache_fragment method
12
10
  module ObjectHelpers
13
11
  extend Forwardable
@@ -24,39 +22,20 @@ module GraphQL
24
22
  fragment = Fragment.new(context, options)
25
23
 
26
24
  if (cached = fragment.read)
25
+ return nil if cached == Fragment::NIL_IN_CACHE
27
26
  return restore_cached_value(cached)
28
27
  end
29
28
 
30
29
  (block_given? ? block.call : object_to_cache).tap do |resolved_value|
31
- cache_value(resolved_value, fragment)
30
+ context.fragments << fragment
32
31
  end
33
32
  end
34
33
 
35
34
  private
36
35
 
37
36
  def restore_cached_value(cached)
38
- connection? ? restore_cached_connection(cached) : raw_value(cached)
39
- end
40
-
41
- def cache_value(resolved_value, fragment)
42
- if connection?
43
- unless context.schema.new_connections?
44
- raise StandardError,
45
- "GraphQL::Pagination::Connections should be enabled for connection caching"
46
- end
47
-
48
- connection = wrap_connection(resolved_value)
49
-
50
- fragment.raw_connection = RawConnection.new(
51
- connection.items,
52
- connection.nodes,
53
- connection.instance_variable_get(:@paged_nodes_offset),
54
- connection.has_previous_page,
55
- connection.has_next_page
56
- )
57
- end
58
-
59
- context.fragments << fragment
37
+ # If we return connection object from resolver, Interpreter stops processing it
38
+ connection? ? cached : raw_value(cached)
60
39
  end
61
40
 
62
41
  def field
@@ -66,19 +45,6 @@ module GraphQL
66
45
  def interpreter_context
67
46
  @interpreter_context ||= context.namespace(:interpreter)
68
47
  end
69
-
70
- def restore_cached_connection(raw_connection)
71
- wrap_connection(raw_connection.items).tap do |connection|
72
- connection.instance_variable_set(:@nodes, raw_connection.nodes)
73
- connection.instance_variable_set(:@paged_nodes_offset, raw_connection.paged_nodes_offset)
74
- connection.instance_variable_set(:@has_previous_page, raw_connection.has_previous_page)
75
- connection.instance_variable_set(:@has_next_page, raw_connection.has_next_page)
76
- end
77
- end
78
-
79
- def wrap_connection(object)
80
- context.schema.connections.wrap(field, object, interpreter_context[:current_arguments], context)
81
- end
82
48
  end
83
49
  end
84
50
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql/fragment_cache/cacher"
4
+
5
+ module GraphQL
6
+ module FragmentCache
7
+ module Schema
8
+ # Adds hook for saving cached values after query is resolved
9
+ module Instrumentation
10
+ module_function
11
+
12
+ def before_query(query)
13
+ end
14
+
15
+ def after_query(query)
16
+ return unless query.valid?
17
+
18
+ Cacher.call(query)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module GraphQL
6
+ module FragmentCache
7
+ module Schema
8
+ # Patches GraphQL::Schema to support fragment cache
9
+ module Patch
10
+ def schema_cache_key
11
+ @schema_cache_key ||= Digest::SHA1.hexdigest(to_definition)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ # Plugin definition
5
+ module FragmentCache
6
+ module Schema
7
+ class Tracer
8
+ using Ext
9
+
10
+ class << self
11
+ def trace(key, data)
12
+ yield.tap do |resolved_value|
13
+ next unless connection_to_cache?(key, data)
14
+
15
+ # We need to attach connection object to fragment and save it later
16
+ context = data[:query].context
17
+ verify_connections!(context)
18
+ cache_connection(resolved_value, context)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def connection_to_cache?(key, data)
25
+ key == "execute_field" && data[:field].connection?
26
+ end
27
+
28
+ def verify_connections!(context)
29
+ return if context.schema.new_connections?
30
+
31
+ raise StandardError,
32
+ "GraphQL::Pagination::Connections should be enabled for connection caching"
33
+ end
34
+
35
+ def cache_connection(resolved_value, context)
36
+ current_path = context.namespace(:interpreter)[:current_path]
37
+ fragment = context.fragments.find { |fragment| fragment.path == current_path }
38
+ fragment.resolved_value = resolved_value if fragment
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module FragmentCache
5
- VERSION = "0.1.5"
5
+ VERSION = "1.0.2"
6
6
  end
7
7
  end
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: 0.1.5
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-28 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.10.8
27
27
  - !ruby/object:Gem::Dependency
28
- name: ruby-next-core
28
+ name: ruby-next
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.5.1
33
+ version: 0.7.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.5.1
40
+ version: 0.7.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: combustion
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.6'
139
+ - !ruby/object:Gem::Dependency
140
+ name: ruby-next-parser
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 2.8.0.7
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 2.8.0.7
139
153
  description: Fragment cache for graphql-ruby
140
154
  email:
141
155
  - dmitry.a.tsepelev@gmail.com
@@ -149,7 +163,6 @@ files:
149
163
  - bin/console
150
164
  - bin/setup
151
165
  - lib/.rbnext/2.7/graphql/fragment_cache/cache_key_builder.rb
152
- - lib/.rbnext/2.7/graphql/fragment_cache/cacher.rb
153
166
  - lib/.rbnext/2.7/graphql/fragment_cache/ext/graphql_cache_key.rb
154
167
  - lib/graphql-fragment_cache.rb
155
168
  - lib/graphql/fragment_cache.rb
@@ -159,13 +172,14 @@ files:
159
172
  - lib/graphql/fragment_cache/ext/graphql_cache_key.rb
160
173
  - lib/graphql/fragment_cache/field_extension.rb
161
174
  - lib/graphql/fragment_cache/fragment.rb
162
- - lib/graphql/fragment_cache/instrumentation.rb
163
175
  - lib/graphql/fragment_cache/memory_store.rb
164
176
  - lib/graphql/fragment_cache/object.rb
165
177
  - lib/graphql/fragment_cache/object_helpers.rb
166
178
  - lib/graphql/fragment_cache/rails/cache_key_builder.rb
167
179
  - lib/graphql/fragment_cache/railtie.rb
168
- - lib/graphql/fragment_cache/schema_patch.rb
180
+ - lib/graphql/fragment_cache/schema/instrumentation.rb
181
+ - lib/graphql/fragment_cache/schema/patch.rb
182
+ - lib/graphql/fragment_cache/schema/tracer.rb
169
183
  - lib/graphql/fragment_cache/version.rb
170
184
  homepage: https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache
171
185
  licenses:
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module FragmentCache
5
- using Ext
6
-
7
- # Saves resolved fragment values to cache store
8
- module Cacher
9
- class << self
10
- def call(query)
11
- return unless query.context.fragments?
12
-
13
- final_value = query.context.namespace(:interpreter)[:runtime].final_value
14
-
15
- query.context.fragments.each { |_1| _1.persist(final_value) }
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "graphql/fragment_cache/cacher"
4
-
5
- module GraphQL
6
- module FragmentCache
7
- # Adds hook for saving cached values after query is resolved
8
- module Instrumentation
9
- module_function
10
-
11
- def before_query(query)
12
- end
13
-
14
- def after_query(query)
15
- return unless query.valid?
16
-
17
- Cacher.call(query)
18
- end
19
- end
20
- end
21
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest/sha1"
4
-
5
- module GraphQL
6
- module FragmentCache
7
- # Patches GraphQL::Schema to support fragment cache
8
- module SchemaPatch
9
- def schema_cache_key
10
- @schema_cache_key ||= Digest::SHA1.hexdigest(to_definition)
11
- end
12
- end
13
- end
14
- end