graphql-fragment_cache 0.1.7 → 1.0.4

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