graphql-fragment_cache 0.1.5 → 1.0.2

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: 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