graphql 1.10.14 → 1.11.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/core.rb +8 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/enum.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +13 -9
- data/lib/generators/graphql/templates/interface.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/object.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/scalar.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/generators/graphql/templates/union.erb +2 -0
- data/lib/graphql.rb +3 -3
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +26 -26
- data/lib/graphql/execution/multiplex.rb +1 -2
- data/lib/graphql/introspection/schema_type.rb +3 -3
- data/lib/graphql/invalid_null_error.rb +18 -0
- data/lib/graphql/language/nodes.rb +1 -0
- data/lib/graphql/language/visitor.rb +2 -2
- data/lib/graphql/pagination/connection.rb +18 -13
- data/lib/graphql/pagination/connections.rb +17 -4
- data/lib/graphql/query.rb +1 -2
- data/lib/graphql/query/context.rb +20 -1
- data/lib/graphql/query/fingerprint.rb +2 -0
- data/lib/graphql/query/validation_pipeline.rb +3 -0
- data/lib/graphql/schema.rb +26 -16
- data/lib/graphql/schema/build_from_definition.rb +7 -12
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
- data/lib/graphql/schema/enum_value.rb +1 -0
- data/lib/graphql/schema/field.rb +63 -77
- data/lib/graphql/schema/field/connection_extension.rb +42 -32
- data/lib/graphql/schema/loader.rb +19 -1
- data/lib/graphql/schema/member/has_fields.rb +15 -5
- data/lib/graphql/schema/mutation.rb +4 -0
- data/lib/graphql/schema/object.rb +1 -1
- data/lib/graphql/schema/resolver.rb +20 -0
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -1
- data/lib/graphql/schema/subscription.rb +3 -13
- data/lib/graphql/schema/union.rb +29 -0
- data/lib/graphql/schema/warden.rb +2 -4
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
- data/lib/graphql/subscriptions.rb +69 -24
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
- data/lib/graphql/subscriptions/event.rb +16 -1
- data/lib/graphql/subscriptions/serialize.rb +22 -4
- data/lib/graphql/subscriptions/subscription_root.rb +3 -1
- data/lib/graphql/tracing.rb +1 -27
- data/lib/graphql/tracing/appoptics_tracing.rb +10 -2
- data/lib/graphql/tracing/platform_tracing.rb +25 -15
- data/lib/graphql/tracing/statsd_tracing.rb +42 -0
- data/lib/graphql/types/iso_8601_date_time.rb +2 -1
- data/lib/graphql/types/relay/base_connection.rb +6 -5
- data/lib/graphql/version.rb +1 -1
- metadata +5 -2
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# - No queueing system; ActiveJob should be added
|
10
10
|
# - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
|
11
|
-
# - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console.
|
11
|
+
# - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console.
|
12
12
|
#
|
13
13
|
# @example Adding ActionCableSubscriptions to your schema
|
14
14
|
# class MySchema < GraphQL::Schema
|
@@ -90,6 +90,7 @@ module GraphQL
|
|
90
90
|
# A per-process map of subscriptions to deliver.
|
91
91
|
# This is provided by Rails, so let's use it
|
92
92
|
@subscriptions = Concurrent::Map.new
|
93
|
+
@events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
|
93
94
|
@serializer = serializer
|
94
95
|
super
|
95
96
|
end
|
@@ -120,27 +121,81 @@ module GraphQL
|
|
120
121
|
channel.stream_from(stream)
|
121
122
|
@subscriptions[subscription_id] = query
|
122
123
|
events.each do |event|
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
# Setup a new listener to run all events with this topic in this process
|
125
|
+
setup_stream(channel, event)
|
126
|
+
# Add this event to the list of events to be updated
|
127
|
+
@events[event.topic][event.fingerprint] << event
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Every subscribing channel is listening here, but only one of them takes any action.
|
132
|
+
# This is so we can reuse payloads when possible, and make one payload to send to
|
133
|
+
# all subscribers.
|
134
|
+
#
|
135
|
+
# But the problem is, any channel could close at any time, so each channel has to
|
136
|
+
# be ready to take over the primary position.
|
137
|
+
#
|
138
|
+
# To make sure there's always one-and-only-one channel building payloads,
|
139
|
+
# let the listener belonging to the first event on the list be
|
140
|
+
# the one to build and publish payloads.
|
141
|
+
#
|
142
|
+
def setup_stream(channel, initial_event)
|
143
|
+
topic = initial_event.topic
|
144
|
+
channel.stream_from(EVENT_PREFIX + topic, coder: ActiveSupport::JSON) do |message|
|
145
|
+
object = @serializer.load(message)
|
146
|
+
events_by_fingerprint = @events[topic]
|
147
|
+
events_by_fingerprint.each do |_fingerprint, events|
|
148
|
+
if events.any? && events.first == initial_event
|
149
|
+
# The fingerprint has told us that this response should be shared by all subscribers,
|
150
|
+
# so just run it once, then deliver the result to every subscriber
|
151
|
+
first_event = events.first
|
152
|
+
first_subscription_id = first_event.context.fetch(:subscription_id)
|
153
|
+
result = execute_update(first_subscription_id, first_event, object)
|
154
|
+
# Having calculated the result _once_, send the same payload to all subscribers
|
155
|
+
events.each do |event|
|
156
|
+
subscription_id = event.context.fetch(:subscription_id)
|
157
|
+
deliver(subscription_id, result)
|
158
|
+
end
|
159
|
+
end
|
126
160
|
end
|
161
|
+
nil
|
127
162
|
end
|
128
163
|
end
|
129
164
|
|
130
165
|
# Return the query from "storage" (in memory)
|
131
166
|
def read_subscription(subscription_id)
|
132
167
|
query = @subscriptions[subscription_id]
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
168
|
+
if query.nil?
|
169
|
+
# This can happen when a subscription is triggered from an unsubscribed channel,
|
170
|
+
# see https://github.com/rmosolgo/graphql-ruby/issues/2478.
|
171
|
+
# (This `nil` is handled by `#execute_update`)
|
172
|
+
nil
|
173
|
+
else
|
174
|
+
{
|
175
|
+
query_string: query.query_string,
|
176
|
+
variables: query.provided_variables,
|
177
|
+
context: query.context.to_h,
|
178
|
+
operation_name: query.operation_name,
|
179
|
+
}
|
180
|
+
end
|
139
181
|
end
|
140
182
|
|
141
183
|
# The channel was closed, forget about it.
|
142
184
|
def delete_subscription(subscription_id)
|
143
|
-
@subscriptions.delete(subscription_id)
|
185
|
+
query = @subscriptions.delete(subscription_id)
|
186
|
+
# This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
|
187
|
+
# see https://github.com/rmosolgo/graphql-ruby/issues/2478
|
188
|
+
if query
|
189
|
+
events = query.context.namespace(:subscriptions)[:events]
|
190
|
+
events.each do |event|
|
191
|
+
ev_by_fingerprint = @events[event.topic]
|
192
|
+
ev_for_fingerprint = ev_by_fingerprint[event.fingerprint]
|
193
|
+
ev_for_fingerprint.delete(event)
|
194
|
+
if ev_for_fingerprint.empty?
|
195
|
+
ev_by_fingerprint.delete(event.fingerprint)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
144
199
|
end
|
145
200
|
end
|
146
201
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Subscriptions
|
5
|
+
# Detect whether the current operation:
|
6
|
+
# - Is a subscription operation
|
7
|
+
# - Is completely broadcastable
|
8
|
+
#
|
9
|
+
# Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
|
10
|
+
# @api private
|
11
|
+
# @see Subscriptions#broadcastable? for a public API
|
12
|
+
class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer
|
13
|
+
def initialize(subject)
|
14
|
+
super
|
15
|
+
@default_broadcastable = subject.schema.subscriptions.default_broadcastable
|
16
|
+
# Maybe this will get set to false while analyzing
|
17
|
+
@subscription_broadcastable = true
|
18
|
+
end
|
19
|
+
|
20
|
+
# Only analyze subscription operations
|
21
|
+
def analyze?
|
22
|
+
@query.subscription?
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_enter_field(node, parent, visitor)
|
26
|
+
if (@subscription_broadcastable == false) || visitor.skipping?
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
current_field = visitor.field_definition
|
31
|
+
apply_broadcastable(current_field)
|
32
|
+
|
33
|
+
current_type = visitor.parent_type_definition
|
34
|
+
if current_type.kind.interface?
|
35
|
+
pt = @query.possible_types(current_type)
|
36
|
+
pt.each do |object_type|
|
37
|
+
ot_field = @query.get_field(object_type, current_field.graphql_name)
|
38
|
+
if !ot_field
|
39
|
+
binding.pry
|
40
|
+
end
|
41
|
+
# Inherited fields would be exactly the same object;
|
42
|
+
# only check fields that are overrides of the inherited one
|
43
|
+
if ot_field && ot_field != current_field
|
44
|
+
apply_broadcastable(ot_field)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Assign the result to context.
|
51
|
+
# (This method is allowed to return an error, but we don't need to)
|
52
|
+
# @return [void]
|
53
|
+
def result
|
54
|
+
query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value)
|
61
|
+
def apply_broadcastable(field_defn)
|
62
|
+
current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable?
|
63
|
+
case current_field_broadcastable
|
64
|
+
when nil
|
65
|
+
# If the value wasn't set, mix in the default value:
|
66
|
+
# - If the default is false and the current value is true, make it false
|
67
|
+
# - If the default is true and the current value is true, it stays true
|
68
|
+
# - If the default is false and the current value is false, keep it false
|
69
|
+
# - If the default is true and the current value is false, keep it false
|
70
|
+
@subscription_broadcastable = @subscription_broadcastable && @default_broadcastable
|
71
|
+
when false
|
72
|
+
# One non-broadcastable field is enough to make the whole subscription non-broadcastable
|
73
|
+
@subscription_broadcastable = false
|
74
|
+
when true
|
75
|
+
# Leave `@broadcastable_query` true if it's already true,
|
76
|
+
# but don't _set_ it to true if it was set to false by something else.
|
77
|
+
# Actually, just leave it!
|
78
|
+
else
|
79
|
+
raise ArgumentError, "Unexpected `.broadcastable?` value for #{field_defn.path}: #{current_field_broadcastable}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Subscriptions
|
4
|
+
class DefaultSubscriptionResolveExtension < GraphQL::Subscriptions::SubscriptionRoot::Extension
|
5
|
+
def resolve(context:, object:, arguments:)
|
6
|
+
has_override_implementation = @field.resolver ||
|
7
|
+
object.respond_to?(@field.resolver_method)
|
8
|
+
|
9
|
+
if !has_override_implementation
|
10
|
+
if context.query.subscription_update?
|
11
|
+
object.object
|
12
|
+
else
|
13
|
+
context.skip
|
14
|
+
end
|
15
|
+
else
|
16
|
+
yield(object, arguments)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -6,7 +6,6 @@ module GraphQL
|
|
6
6
|
# - Subscribed to by `subscription { ... }`
|
7
7
|
# - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)`
|
8
8
|
#
|
9
|
-
# An array of `Event`s are passed to `store.register(query, events)`.
|
10
9
|
class Event
|
11
10
|
# @return [String] Corresponds to the Subscription root field name
|
12
11
|
attr_reader :name
|
@@ -53,6 +52,22 @@ module GraphQL
|
|
53
52
|
Serialize.dump_recursive([scope, name, sorted_h])
|
54
53
|
end
|
55
54
|
|
55
|
+
# @return [String] a logical identifier for this event. (Stable when the query is broadcastable.)
|
56
|
+
def fingerprint
|
57
|
+
@fingerprint ||= begin
|
58
|
+
# When this query has been flagged as broadcastable,
|
59
|
+
# use a generalized, stable fingerprint so that
|
60
|
+
# duplicate subscriptions can be evaluated and distributed in bulk.
|
61
|
+
# (`@topic` includes field, args, and subscription scope already.)
|
62
|
+
if @context.namespace(:subscriptions)[:subscription_broadcastable]
|
63
|
+
"#{@topic}/#{@context.query.fingerprint}"
|
64
|
+
else
|
65
|
+
# not broadcastable, build a unique ID for this event
|
66
|
+
@context.schema.subscriptions.build_id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
56
71
|
class << self
|
57
72
|
private
|
58
73
|
def stringify_args(arg_owner, args)
|
@@ -9,6 +9,9 @@ module GraphQL
|
|
9
9
|
GLOBALID_KEY = "__gid__"
|
10
10
|
SYMBOL_KEY = "__sym__"
|
11
11
|
SYMBOL_KEYS_KEY = "__sym_keys__"
|
12
|
+
TIMESTAMP_KEY = "__timestamp__"
|
13
|
+
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00'
|
14
|
+
OPEN_STRUCT_KEY = "__ostruct__"
|
12
15
|
|
13
16
|
module_function
|
14
17
|
|
@@ -55,10 +58,20 @@ module GraphQL
|
|
55
58
|
if value.is_a?(Array)
|
56
59
|
value.map{|item| load_value(item)}
|
57
60
|
elsif value.is_a?(Hash)
|
58
|
-
if value.size == 1
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
if value.size == 1
|
62
|
+
case value.keys.first # there's only 1 key
|
63
|
+
when GLOBALID_KEY
|
64
|
+
GlobalID::Locator.locate(value[GLOBALID_KEY])
|
65
|
+
when SYMBOL_KEY
|
66
|
+
value[SYMBOL_KEY].to_sym
|
67
|
+
when TIMESTAMP_KEY
|
68
|
+
timestamp_class_name, timestamp_s = value[TIMESTAMP_KEY]
|
69
|
+
timestamp_class = Object.const_get(timestamp_class_name)
|
70
|
+
timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
71
|
+
when OPEN_STRUCT_KEY
|
72
|
+
ostruct_values = load_value(value[OPEN_STRUCT_KEY])
|
73
|
+
OpenStruct.new(ostruct_values)
|
74
|
+
end
|
62
75
|
else
|
63
76
|
loaded_h = {}
|
64
77
|
sym_keys = value.fetch(SYMBOL_KEYS_KEY, [])
|
@@ -101,6 +114,11 @@ module GraphQL
|
|
101
114
|
{ SYMBOL_KEY => obj.to_s }
|
102
115
|
elsif obj.respond_to?(:to_gid_param)
|
103
116
|
{GLOBALID_KEY => obj.to_gid_param}
|
117
|
+
elsif obj.is_a?(Date) || obj.is_a?(Time)
|
118
|
+
# DateTime extends Date; for TimeWithZone, call `.utc` first.
|
119
|
+
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
120
|
+
elsif obj.is_a?(OpenStruct)
|
121
|
+
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
|
104
122
|
else
|
105
123
|
obj
|
106
124
|
end
|
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
module GraphQL
|
4
4
|
class Subscriptions
|
5
|
-
#
|
5
|
+
# @api private
|
6
|
+
# @deprecated This module is no longer needed.
|
6
7
|
module SubscriptionRoot
|
7
8
|
def self.extended(child_cls)
|
9
|
+
warn "`extend GraphQL::Subscriptions::SubscriptionRoot` is no longer required; you can remove it from your Subscription type (#{child_cls})"
|
8
10
|
child_cls.include(InstanceMethods)
|
9
11
|
end
|
10
12
|
|
data/lib/graphql/tracing.rb
CHANGED
@@ -7,6 +7,7 @@ require "graphql/tracing/data_dog_tracing"
|
|
7
7
|
require "graphql/tracing/new_relic_tracing"
|
8
8
|
require "graphql/tracing/scout_tracing"
|
9
9
|
require "graphql/tracing/skylight_tracing"
|
10
|
+
require "graphql/tracing/statsd_tracing"
|
10
11
|
require "graphql/tracing/prometheus_tracing"
|
11
12
|
|
12
13
|
if defined?(PrometheusExporter::Server)
|
@@ -16,8 +17,6 @@ end
|
|
16
17
|
module GraphQL
|
17
18
|
# Library entry point for performance metric reporting.
|
18
19
|
#
|
19
|
-
# __Warning:__ Installing/uninstalling tracers is not thread-safe. Do it during application boot only.
|
20
|
-
#
|
21
20
|
# @example Sending custom events
|
22
21
|
# query.trace("my_custom_event", { ... }) do
|
23
22
|
# # do stuff ...
|
@@ -86,31 +85,6 @@ module GraphQL
|
|
86
85
|
end
|
87
86
|
end
|
88
87
|
|
89
|
-
class << self
|
90
|
-
# Install a tracer to receive events.
|
91
|
-
# @param tracer [<#trace(key, metadata)>]
|
92
|
-
# @return [void]
|
93
|
-
# @deprecated See {Schema#tracer} or use `context: { tracers: [...] }`
|
94
|
-
def install(tracer)
|
95
|
-
warn("GraphQL::Tracing.install is deprecated, add it to the schema with `tracer(my_tracer)` instead.")
|
96
|
-
if !tracers.include?(tracer)
|
97
|
-
@tracers << tracer
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# @deprecated See {Schema#tracer} or use `context: { tracers: [...] }`
|
102
|
-
def uninstall(tracer)
|
103
|
-
@tracers.delete(tracer)
|
104
|
-
end
|
105
|
-
|
106
|
-
# @deprecated See {Schema#tracer} or use `context: { tracers: [...] }`
|
107
|
-
def tracers
|
108
|
-
@tracers ||= []
|
109
|
-
end
|
110
|
-
end
|
111
|
-
# Initialize the array
|
112
|
-
tracers
|
113
|
-
|
114
88
|
module NullTracer
|
115
89
|
module_function
|
116
90
|
def trace(k, v)
|
@@ -55,7 +55,15 @@ module GraphQL
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def platform_field_key(type, field)
|
58
|
-
"graphql.#{type.
|
58
|
+
"graphql.#{type.graphql_name}.#{field.graphql_name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def platform_authorized_key(type)
|
62
|
+
"graphql.authorized.#{type.graphql_name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def platform_resolve_type_key(type)
|
66
|
+
"graphql.resolve_type.#{type.graphql_name}"
|
59
67
|
end
|
60
68
|
|
61
69
|
private
|
@@ -107,7 +115,7 @@ module GraphQL
|
|
107
115
|
else
|
108
116
|
[key, data[key]]
|
109
117
|
end
|
110
|
-
end.flatten.each_slice(2).to_h.merge(Spec: 'graphql')
|
118
|
+
end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
|
111
119
|
end
|
112
120
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
113
121
|
|
@@ -32,17 +32,19 @@ module GraphQL
|
|
32
32
|
trace_field = true # implemented with instrumenter
|
33
33
|
else
|
34
34
|
field = data[:field]
|
35
|
-
cache = platform_key_cache(data.fetch(:query).context)
|
36
|
-
platform_key = cache.fetch(field) do
|
37
|
-
cache[field] = platform_field_key(data[:owner], field)
|
38
|
-
end
|
39
|
-
|
40
35
|
return_type = field.type.unwrap
|
41
36
|
trace_field = if return_type.kind.scalar? || return_type.kind.enum?
|
42
37
|
(field.trace.nil? && @trace_scalars) || field.trace
|
43
38
|
else
|
44
39
|
true
|
45
40
|
end
|
41
|
+
|
42
|
+
platform_key = if trace_field
|
43
|
+
context = data.fetch(:query).context
|
44
|
+
cached_platform_key(context, field) { platform_field_key(data[:owner], field) }
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
46
48
|
end
|
47
49
|
|
48
50
|
if platform_key && trace_field
|
@@ -53,20 +55,16 @@ module GraphQL
|
|
53
55
|
yield
|
54
56
|
end
|
55
57
|
when "authorized", "authorized_lazy"
|
56
|
-
cache = platform_key_cache(data.fetch(:context))
|
57
58
|
type = data.fetch(:type)
|
58
|
-
|
59
|
-
|
60
|
-
end
|
59
|
+
context = data.fetch(:context)
|
60
|
+
platform_key = cached_platform_key(context, type) { platform_authorized_key(type) }
|
61
61
|
platform_trace(platform_key, key, data) do
|
62
62
|
yield
|
63
63
|
end
|
64
64
|
when "resolve_type", "resolve_type_lazy"
|
65
|
-
cache = platform_key_cache(data.fetch(:context))
|
66
65
|
type = data.fetch(:type)
|
67
|
-
|
68
|
-
|
69
|
-
end
|
66
|
+
context = data.fetch(:context)
|
67
|
+
platform_key = cached_platform_key(context, type) { platform_resolve_type_key(type) }
|
70
68
|
platform_trace(platform_key, key, data) do
|
71
69
|
yield
|
72
70
|
end
|
@@ -119,8 +117,20 @@ module GraphQL
|
|
119
117
|
|
120
118
|
attr_reader :options
|
121
119
|
|
122
|
-
|
123
|
-
|
120
|
+
# Different kind of schema objects have different kinds of keys:
|
121
|
+
#
|
122
|
+
# - Object types: `.authorized`
|
123
|
+
# - Union/Interface types: `.resolve_type`
|
124
|
+
# - Fields: execution
|
125
|
+
#
|
126
|
+
# So, they can all share one cache.
|
127
|
+
#
|
128
|
+
# If the key isn't present, the given block is called and the result is cached for `key`.
|
129
|
+
#
|
130
|
+
# @return [String]
|
131
|
+
def cached_platform_key(ctx, key)
|
132
|
+
cache = ctx.namespace(self.class)[:platform_key_cache] ||= {}
|
133
|
+
cache.fetch(key) { cache[key] = yield }
|
124
134
|
end
|
125
135
|
end
|
126
136
|
end
|