graphql-fragment_cache 0.1.7 → 1.0.4

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: 1c1bfd3dbaadd053ae1e47ac16d1cb244410adeae3424b30d58b04516447bfe6
4
- data.tar.gz: 10de0c52b16423e65db745433a61aef94c795edca3b4b47c3ef834f7118d0a5f
3
+ metadata.gz: 4a52d1b703c963cd27803c1c28ca0e8c1a66775a84ad8963f88a0e414ec7ed6c
4
+ data.tar.gz: 4739e71ae750d7fbb8c9366e17da84ad9ae921409308a1b109fc60d8add0557a
5
5
  SHA512:
6
- metadata.gz: 54ad113a7ef913df0c283c130bae045621dd99298bb1e658e6a4e00440fa29a6ac3d39f45789b49f9cc8a09ddaf34fe4af18560ebbc1c41d73e33d6e539e9b10
7
- data.tar.gz: b36091bf683d1018e47ae0fcfd5e69fedad20d4a7b20687a67315d721efa63a5b5df158cb8142ed6342b5ae77c3bd41d08a281299d16f0fcd10d74107e5db78e
6
+ metadata.gz: 889510e3937c874742624f6da651914ed98c6a996842ec57fcda8121287eead193e12b974247733b38ffc7662e542eb1a82b2fb43d18ef5a121cc41607f811fb
7
+ data.tar.gz: 4e9dbeb855f55bf37fbe14900b7c4b1b398155902759fd905a87c70da6dfd67850102be48c5b350d6b3126090ced6aaf0bb5b95a7f16a990dd8b070b589e865c
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.0.4 (2020-10-12)
6
+
7
+ - [PR#34](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/34) Avoid unneded default calculation in CacheKeyBuilder ([@DmitryTsepelev][])
8
+ - [PR#31](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/31) Do not patch Connection#wrap in graphql >= 1.10.5 ([@DmitryTsepelev][])
9
+
10
+ ## 1.0.3 (2020-08-31)
11
+
12
+ - [PR#29](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/29) Cache result JSON instead of connection objects ([@DmitryTsepelev][])
13
+
14
+ ## 1.0.2 (2020-08-19)
15
+
16
+ - [PR#28](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/28) Support #keys method for GraphQL::FragmentCache::MemoryStore instance ([@reabiliti][])
17
+
18
+ ## 1.0.1 (2020-06-17)
19
+
20
+ - [PR#25](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/25) Support fragments with aliases for CacheKeyBuilder ([@DmitryTsepelev][])
21
+
22
+ ## 1.0.0 (2020-06-13)
23
+
24
+ - [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][])
25
+
5
26
  ## 0.1.7 (2020-06-02)
6
27
 
7
28
  - [PR#23](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/23) Avoid extra queries after restoring connection from cache ([@DmitryTsepelev][])
@@ -39,3 +60,4 @@
39
60
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
40
61
  [@palkan]: https://github.com/palkan
41
62
  [@ssnickolay]: https://github.com/ssnickolay
63
+ [@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
 
@@ -275,6 +275,18 @@ class QueryType < BaseObject
275
275
  end
276
276
  ```
277
277
 
278
+ ## Limitations
279
+
280
+ Caching does not work for Union types, because of the `Lookahead` implementation: it requires the exact type to be passed to the `selection` method (you can find the [discussion](https://github.com/rmosolgo/graphql-ruby/pull/3007) here). This method is used for cache key building, and I haven't found a workaround yet ([PR in progress](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/30)). If you get `Failed to look ahead the field` error — please pass `query_cache_key` explicitly:
281
+
282
+ ```ruby
283
+ field :cached_avatar_url, String, null: false
284
+
285
+ def cached_avatar_url
286
+ cache_fragment(query_cache_key: "post_avatar_url(#{object.id})") { object.avatar_url }
287
+ end
288
+ ```
289
+
278
290
  ## Credits
279
291
 
280
292
  Based on the original [gist](https://gist.github.com/palkan/faad9f6ff1db16fcdb1c071ec50e4190) by [@palkan](https://github.com/palkan) and [@ssnickolay](https://github.com/ssnickolay).
@@ -0,0 +1,64 @@
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
+ ((!storage[key].nil?) || nil) && storage[key].then do |entry|
37
+ if entry.expired?
38
+ delete(key)
39
+ next
40
+ end
41
+ entry.value
42
+ end
43
+ end
44
+
45
+ def write(key, value, expires_in: default_expires_in, **options)
46
+ key = key.to_s
47
+ @storage[key] = Entry.new(value: value, expires_at: expires_in ? Time.now + expires_in : nil)
48
+ end
49
+
50
+ def delete(key)
51
+ key = key.to_s
52
+ storage.delete(key)
53
+ end
54
+
55
+ def clear
56
+ storage.clear
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :storage
62
+ end
63
+ end
64
+ end
@@ -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
@@ -115,11 +121,11 @@ module GraphQL
115
121
  private
116
122
 
117
123
  def schema_cache_key
118
- @options.fetch(:schema_cache_key, schema.schema_cache_key)
124
+ @options.fetch(:schema_cache_key) { schema.schema_cache_key }
119
125
  end
120
126
 
121
127
  def query_cache_key
122
- @options.fetch(:query_cache_key, "#{path_cache_key}[#{selections_cache_key}]")
128
+ @options.fetch(:query_cache_key) { "#{path_cache_key}[#{selections_cache_key}]" }
123
129
  end
124
130
 
125
131
  def selections_cache_key
@@ -6,6 +6,8 @@ require "graphql/fragment_cache/ext/context_fragments"
6
6
  require "graphql/fragment_cache/ext/graphql_cache_key"
7
7
  require "graphql/fragment_cache/object"
8
8
 
9
+ require "graphql/fragment_cache/connections/patch"
10
+
9
11
  require "graphql/fragment_cache/schema/patch"
10
12
  require "graphql/fragment_cache/schema/tracer"
11
13
  require "graphql/fragment_cache/schema/instrumentation"
@@ -26,6 +28,8 @@ module GraphQL
26
28
  schema_defn.tracer(Schema::Tracer)
27
29
  schema_defn.instrument(:query, Schema::Instrumentation)
28
30
  schema_defn.extend(Schema::Patch)
31
+
32
+ GraphQL::Pagination::Connections.prepend(Connections::Patch)
29
33
  end
30
34
 
31
35
  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
@@ -115,11 +121,11 @@ module GraphQL
115
121
  private
116
122
 
117
123
  def schema_cache_key
118
- @options.fetch(:schema_cache_key, schema.schema_cache_key)
124
+ @options.fetch(:schema_cache_key) { schema.schema_cache_key }
119
125
  end
120
126
 
121
127
  def query_cache_key
122
- @options.fetch(:query_cache_key, "#{path_cache_key}[#{selections_cache_key}]")
128
+ @options.fetch(:query_cache_key) { "#{path_cache_key}[#{selections_cache_key}]" }
123
129
  end
124
130
 
125
131
  def selections_cache_key
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module FragmentCache
5
+ module Connections
6
+ # Patches GraphQL::Pagination::Connections to support raw values
7
+ module Patch
8
+ if Gem::Dependency.new("graphql", "< 1.11.0").match?("graphql", GraphQL::VERSION)
9
+ def wrap(field, object, arguments, context, *options)
10
+ raw_value?(object) ? object : super
11
+ end
12
+ elsif Gem::Dependency.new("graphql", "< 1.11.5").match?("graphql", GraphQL::VERSION)
13
+ def wrap(field, parent, items, arguments, context, *options)
14
+ raw_value?(items) ? items : super
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def raw_value?(value)
21
+ GraphQL::Execution::Interpreter::RawValue === value
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,22 +8,22 @@ module GraphQL
8
8
  class Fragment
9
9
  attr_reader :options, :path, :context
10
10
 
11
- attr_accessor :resolved_value
12
-
13
11
  def initialize(context, **options)
14
12
  @context = context
15
13
  @options = options
16
14
  @path = interpreter_context[:current_path]
17
15
  end
18
16
 
17
+ NIL_IN_CACHE = Object.new
18
+
19
19
  def read
20
- FragmentCache.cache_store.read(cache_key)
20
+ FragmentCache.cache_store.read(cache_key).tap do |cached|
21
+ return NIL_IN_CACHE if cached.nil? && FragmentCache.cache_store.exist?(cache_key)
22
+ end
21
23
  end
22
24
 
23
25
  def persist
24
- # Connections are not available from the runtime object, so
25
- # we rely on Schema::Tracer to save it for us
26
- value = resolved_value || resolve_from_runtime
26
+ value = final_value.dig(*path)
27
27
  FragmentCache.cache_store.write(cache_key, value, **options)
28
28
  end
29
29
 
@@ -37,10 +37,6 @@ module GraphQL
37
37
  context.namespace(:interpreter)
38
38
  end
39
39
 
40
- def resolve_from_runtime
41
- final_value.dig(*path)
42
- end
43
-
44
40
  def final_value
45
41
  @final_value ||= interpreter_context[:runtime].final_value
46
42
  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|
@@ -12,8 +12,6 @@ module GraphQL
12
12
 
13
13
  NO_OBJECT = Object.new
14
14
 
15
- def_delegator :field, :connection?
16
-
17
15
  def cache_fragment(object_to_cache = NO_OBJECT, **options, &block)
18
16
  raise ArgumentError, "Block or argument must be provided" unless block_given? || object_to_cache != NO_OBJECT
19
17
 
@@ -22,28 +20,13 @@ module GraphQL
22
20
  fragment = Fragment.new(context, options)
23
21
 
24
22
  if (cached = fragment.read)
25
- return restore_cached_value(cached)
23
+ return cached == Fragment::NIL_IN_CACHE ? nil : raw_value(cached)
26
24
  end
27
25
 
28
26
  (block_given? ? block.call : object_to_cache).tap do |resolved_value|
29
27
  context.fragments << fragment
30
28
  end
31
29
  end
32
-
33
- private
34
-
35
- def restore_cached_value(cached)
36
- # If we return connection object from resolver, Interpreter stops processing it
37
- connection? ? cached : raw_value(cached)
38
- end
39
-
40
- def field
41
- interpreter_context[:current_field]
42
- end
43
-
44
- def interpreter_context
45
- @interpreter_context ||= context.namespace(:interpreter)
46
- end
47
30
  end
48
31
  end
49
32
  end
@@ -10,18 +10,15 @@ module GraphQL
10
10
  class << self
11
11
  def trace(key, data)
12
12
  yield.tap do |resolved_value|
13
- next unless connection_to_cache?(key, data)
13
+ next unless connection_field?(key, data)
14
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)
15
+ verify_connections!(data[:query].context)
19
16
  end
20
17
  end
21
18
 
22
19
  private
23
20
 
24
- def connection_to_cache?(key, data)
21
+ def connection_field?(key, data)
25
22
  key == "execute_field" && data[:field].connection?
26
23
  end
27
24
 
@@ -31,12 +28,6 @@ module GraphQL
31
28
  raise StandardError,
32
29
  "GraphQL::Pagination::Connections should be enabled for connection caching"
33
30
  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
31
  end
41
32
  end
42
33
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module FragmentCache
5
- VERSION = "0.1.7"
5
+ VERSION = "1.0.4"
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.7
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-02 00:00:00.000000000 Z
11
+ date: 2020-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.7.0
33
+ version: 0.10.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.7.0
40
+ version: 0.10.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: combustion
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,28 @@ dependencies:
128
128
  requirements:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '0.6'
131
+ version: '0.10'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '0.6'
138
+ version: '0.10'
139
+ - !ruby/object:Gem::Dependency
140
+ name: unparser
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 0.4.9
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 0.4.9
139
153
  description: Fragment cache for graphql-ruby
140
154
  email:
141
155
  - dmitry.a.tsepelev@gmail.com
@@ -148,12 +162,14 @@ files:
148
162
  - README.md
149
163
  - bin/console
150
164
  - bin/setup
165
+ - lib/.rbnext/2.3/graphql/fragment_cache/memory_store.rb
151
166
  - lib/.rbnext/2.7/graphql/fragment_cache/cache_key_builder.rb
152
167
  - lib/.rbnext/2.7/graphql/fragment_cache/ext/graphql_cache_key.rb
153
168
  - lib/graphql-fragment_cache.rb
154
169
  - lib/graphql/fragment_cache.rb
155
170
  - lib/graphql/fragment_cache/cache_key_builder.rb
156
171
  - lib/graphql/fragment_cache/cacher.rb
172
+ - lib/graphql/fragment_cache/connections/patch.rb
157
173
  - lib/graphql/fragment_cache/ext/context_fragments.rb
158
174
  - lib/graphql/fragment_cache/ext/graphql_cache_key.rb
159
175
  - lib/graphql/fragment_cache/field_extension.rb