graphql 1.10.13 → 1.11.3

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/graphql_controller.erb +11 -9
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/directive.rb +4 -0
  5. data/lib/graphql/execution/interpreter.rb +1 -1
  6. data/lib/graphql/execution/interpreter/runtime.rb +6 -4
  7. data/lib/graphql/execution/multiplex.rb +1 -2
  8. data/lib/graphql/field.rb +4 -0
  9. data/lib/graphql/input_object_type.rb +4 -0
  10. data/lib/graphql/introspection/schema_type.rb +3 -3
  11. data/lib/graphql/invalid_null_error.rb +18 -0
  12. data/lib/graphql/language/nodes.rb +1 -0
  13. data/lib/graphql/language/visitor.rb +2 -2
  14. data/lib/graphql/pagination/connection.rb +18 -13
  15. data/lib/graphql/pagination/connections.rb +17 -4
  16. data/lib/graphql/query.rb +1 -2
  17. data/lib/graphql/schema.rb +22 -16
  18. data/lib/graphql/schema/build_from_definition.rb +7 -12
  19. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  20. data/lib/graphql/schema/enum_value.rb +1 -0
  21. data/lib/graphql/schema/field.rb +63 -77
  22. data/lib/graphql/schema/field/connection_extension.rb +42 -32
  23. data/lib/graphql/schema/loader.rb +19 -1
  24. data/lib/graphql/schema/member/has_arguments.rb +3 -1
  25. data/lib/graphql/schema/member/has_fields.rb +15 -5
  26. data/lib/graphql/schema/mutation.rb +4 -0
  27. data/lib/graphql/schema/object.rb +1 -1
  28. data/lib/graphql/schema/resolver.rb +20 -0
  29. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -1
  30. data/lib/graphql/schema/subscription.rb +1 -1
  31. data/lib/graphql/schema/union.rb +29 -0
  32. data/lib/graphql/schema/warden.rb +0 -1
  33. data/lib/graphql/static_validation/literal_validator.rb +7 -7
  34. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  35. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  36. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
  37. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  38. data/lib/graphql/subscriptions.rb +41 -8
  39. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
  40. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  41. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  42. data/lib/graphql/subscriptions/event.rb +16 -1
  43. data/lib/graphql/subscriptions/serialize.rb +22 -4
  44. data/lib/graphql/subscriptions/subscription_root.rb +3 -1
  45. data/lib/graphql/tracing.rb +1 -27
  46. data/lib/graphql/tracing/platform_tracing.rb +25 -15
  47. data/lib/graphql/tracing/statsd_tracing.rb +14 -14
  48. data/lib/graphql/version.rb +1 -1
  49. metadata +4 -2
@@ -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 && value.key?(GLOBALID_KEY)
59
- GlobalID::Locator.locate(value[GLOBALID_KEY])
60
- elsif value.size == 1 && value.key?(SYMBOL_KEY)
61
- value[SYMBOL_KEY].to_sym
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
- # Extend this module in your subscription root when using {GraphQL::Execution::Interpreter}.
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
 
@@ -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)
@@ -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
- platform_key = cache.fetch(type) do
59
- cache[type] = platform_authorized_key(type)
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
- platform_key = cache.fetch(type) do
68
- cache[type] = platform_resolve_type_key(type)
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
- def platform_key_cache(ctx)
123
- ctx.namespace(self.class)[:platform_key_cache] ||= {}
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
@@ -11,7 +11,7 @@ module GraphQL
11
11
  'analyze_multiplex' => "graphql.analyze_multiplex",
12
12
  'execute_multiplex' => "graphql.execute_multiplex",
13
13
  'execute_query' => "graphql.execute_query",
14
- 'execute_query_lazy' => "graphql.execute_query",
14
+ 'execute_query_lazy' => "graphql.execute_query_lazy",
15
15
  }
16
16
 
17
17
  # @param statsd [Object] A statsd client
@@ -20,23 +20,23 @@ module GraphQL
20
20
  super(**rest)
21
21
  end
22
22
 
23
-
24
- def platform_trace(platform_key, key, data)
25
- @statsd.time(platform_key) do
26
- yield
23
+ def platform_trace(platform_key, key, data)
24
+ @statsd.time(platform_key) do
25
+ yield
26
+ end
27
27
  end
28
- end
29
28
 
30
- def platform_field_key(type, field)
31
- "graphql.#{type.graphql_name}.#{field.graphql_name}"
32
- end
29
+ def platform_field_key(type, field)
30
+ "graphql.#{type.graphql_name}.#{field.graphql_name}"
31
+ end
33
32
 
34
- def platform_authorized_key(type)
35
- "graphql.authorized.#{type.graphql_name}"
36
- end
33
+ def platform_authorized_key(type)
34
+ "graphql.authorized.#{type.graphql_name}"
35
+ end
37
36
 
38
- def platform_resolve_type_key(type)
39
- "graphql.resolve_type.#{type.graphql_name}"
37
+ def platform_resolve_type_key(type)
38
+ "graphql.resolve_type.#{type.graphql_name}"
39
+ end
40
40
  end
41
41
  end
42
42
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.10.13"
3
+ VERSION = "1.11.3"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.13
4
+ version: 1.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-16 00:00:00.000000000 Z
11
+ date: 2020-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -681,6 +681,8 @@ files:
681
681
  - lib/graphql/string_type.rb
682
682
  - lib/graphql/subscriptions.rb
683
683
  - lib/graphql/subscriptions/action_cable_subscriptions.rb
684
+ - lib/graphql/subscriptions/broadcast_analyzer.rb
685
+ - lib/graphql/subscriptions/default_subscription_resolve_extension.rb
684
686
  - lib/graphql/subscriptions/event.rb
685
687
  - lib/graphql/subscriptions/instrumentation.rb
686
688
  - lib/graphql/subscriptions/serialize.rb