graphql 1.10.14 → 1.11.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 +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
|