graphql 1.9.17 → 1.11.7
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/core.rb +18 -2
- data/lib/generators/graphql/install_generator.rb +27 -0
- data/lib/generators/graphql/object_generator.rb +52 -8
- 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 +14 -10
- 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 +10 -0
- data/lib/generators/graphql/templates/union.erb +3 -1
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +178 -67
- data/lib/graphql/analysis/ast/visitor.rb +3 -3
- data/lib/graphql/analysis/ast.rb +12 -11
- data/lib/graphql/argument.rb +10 -38
- data/lib/graphql/backtrace/table.rb +10 -2
- data/lib/graphql/backtrace/tracer.rb +2 -1
- data/lib/graphql/base_type.rb +4 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
- data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
- data/lib/graphql/define/assign_enum_value.rb +1 -1
- data/lib/graphql/define/assign_global_id_field.rb +2 -2
- data/lib/graphql/define/assign_object_field.rb +3 -3
- data/lib/graphql/define/defined_object_proxy.rb +3 -0
- data/lib/graphql/define/instance_definable.rb +18 -108
- data/lib/graphql/directive/deprecated_directive.rb +1 -12
- data/lib/graphql/directive.rb +8 -1
- data/lib/graphql/enum_type.rb +5 -71
- data/lib/graphql/execution/directive_checks.rb +2 -2
- data/lib/graphql/execution/errors.rb +2 -3
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/instrumentation.rb +1 -1
- data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
- data/lib/graphql/execution/interpreter/arguments.rb +51 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +79 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +25 -0
- data/lib/graphql/execution/interpreter/runtime.rb +227 -254
- data/lib/graphql/execution/interpreter.rb +34 -11
- data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
- data/lib/graphql/execution/lookahead.rb +39 -114
- data/lib/graphql/execution/multiplex.rb +14 -5
- data/lib/graphql/field.rb +14 -118
- data/lib/graphql/filter.rb +1 -1
- data/lib/graphql/function.rb +1 -30
- data/lib/graphql/input_object_type.rb +6 -24
- data/lib/graphql/integer_decoding_error.rb +17 -0
- data/lib/graphql/interface_type.rb +7 -23
- data/lib/graphql/internal_representation/scope.rb +2 -2
- data/lib/graphql/internal_representation/visit.rb +2 -2
- data/lib/graphql/introspection/base_object.rb +2 -5
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +7 -7
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +33 -9
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/schema_type.rb +4 -9
- data/lib/graphql/introspection/type_type.rb +11 -7
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/invalid_null_error.rb +18 -0
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/definition_slice.rb +21 -10
- data/lib/graphql/language/document_from_schema_definition.rb +89 -64
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +52 -91
- data/lib/graphql/language/parser.rb +719 -717
- data/lib/graphql/language/parser.y +104 -98
- data/lib/graphql/language/printer.rb +1 -1
- data/lib/graphql/language/sanitized_printer.rb +222 -0
- data/lib/graphql/language/visitor.rb +2 -2
- data/lib/graphql/language.rb +2 -1
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/non_null_type.rb +0 -10
- data/lib/graphql/object_type.rb +45 -56
- data/lib/graphql/pagination/active_record_relation_connection.rb +41 -0
- data/lib/graphql/pagination/array_connection.rb +77 -0
- data/lib/graphql/pagination/connection.rb +208 -0
- data/lib/graphql/pagination/connections.rb +145 -0
- data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
- data/lib/graphql/pagination/relation_connection.rb +185 -0
- data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
- data/lib/graphql/pagination.rb +6 -0
- data/lib/graphql/query/arguments.rb +4 -2
- data/lib/graphql/query/context.rb +36 -9
- data/lib/graphql/query/fingerprint.rb +26 -0
- data/lib/graphql/query/input_validation_result.rb +23 -6
- data/lib/graphql/query/literal_input.rb +30 -10
- data/lib/graphql/query/null_context.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +4 -1
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query/variables.rb +16 -7
- data/lib/graphql/query.rb +64 -15
- data/lib/graphql/rake_task/validate.rb +3 -0
- data/lib/graphql/rake_task.rb +9 -9
- data/lib/graphql/relay/array_connection.rb +10 -12
- data/lib/graphql/relay/base_connection.rb +23 -13
- data/lib/graphql/relay/connection_type.rb +2 -1
- data/lib/graphql/relay/edge_type.rb +1 -0
- data/lib/graphql/relay/edges_instrumentation.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -86
- data/lib/graphql/relay/node.rb +2 -2
- data/lib/graphql/relay/range_add.rb +14 -5
- data/lib/graphql/relay/relation_connection.rb +8 -10
- data/lib/graphql/scalar_type.rb +15 -59
- data/lib/graphql/schema/argument.rb +113 -11
- data/lib/graphql/schema/base_64_encoder.rb +2 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
- data/lib/graphql/schema/build_from_definition.rb +212 -190
- data/lib/graphql/schema/built_in_types.rb +5 -5
- data/lib/graphql/schema/default_type_error.rb +2 -0
- data/lib/graphql/schema/directive/deprecated.rb +18 -0
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive.rb +34 -3
- data/lib/graphql/schema/enum.rb +52 -4
- data/lib/graphql/schema/enum_value.rb +6 -1
- data/lib/graphql/schema/field/connection_extension.rb +44 -20
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +200 -129
- data/lib/graphql/schema/find_inherited_value.rb +13 -0
- data/lib/graphql/schema/finder.rb +13 -11
- data/lib/graphql/schema/input_object.rb +131 -22
- data/lib/graphql/schema/interface.rb +26 -8
- data/lib/graphql/schema/introspection_system.rb +108 -37
- data/lib/graphql/schema/late_bound_type.rb +3 -2
- data/lib/graphql/schema/list.rb +47 -0
- data/lib/graphql/schema/loader.rb +134 -96
- data/lib/graphql/schema/member/base_dsl_methods.rb +29 -12
- data/lib/graphql/schema/member/build_type.rb +19 -5
- data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
- data/lib/graphql/schema/member/has_arguments.rb +105 -5
- data/lib/graphql/schema/member/has_ast_node.rb +20 -0
- data/lib/graphql/schema/member/has_fields.rb +20 -10
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
- data/lib/graphql/schema/member/validates_input.rb +33 -0
- data/lib/graphql/schema/member.rb +6 -0
- data/lib/graphql/schema/mutation.rb +5 -1
- data/lib/graphql/schema/non_null.rb +30 -0
- data/lib/graphql/schema/object.rb +65 -12
- data/lib/graphql/schema/possible_types.rb +9 -4
- data/lib/graphql/schema/printer.rb +0 -15
- data/lib/graphql/schema/relay_classic_mutation.rb +5 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +5 -2
- data/lib/graphql/schema/resolver.rb +26 -18
- data/lib/graphql/schema/scalar.rb +27 -3
- data/lib/graphql/schema/subscription.rb +8 -18
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +21 -13
- data/lib/graphql/schema/type_membership.rb +2 -2
- data/lib/graphql/schema/union.rb +37 -3
- data/lib/graphql/schema/unique_within_type.rb +1 -2
- data/lib/graphql/schema/validation.rb +10 -2
- data/lib/graphql/schema/warden.rb +115 -29
- data/lib/graphql/schema.rb +903 -195
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +10 -6
- data/lib/graphql/static_validation/literal_validator.rb +52 -27
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +43 -83
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +17 -5
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +33 -25
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
- data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -5
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
- data/lib/graphql/static_validation/type_stack.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
- data/lib/graphql/static_validation/validator.rb +30 -8
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +89 -19
- 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 +23 -5
- data/lib/graphql/subscriptions/instrumentation.rb +10 -5
- data/lib/graphql/subscriptions/serialize.rb +22 -4
- data/lib/graphql/subscriptions/subscription_root.rb +15 -5
- data/lib/graphql/subscriptions.rb +108 -35
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
- data/lib/graphql/tracing/appoptics_tracing.rb +171 -0
- data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
- data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
- data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
- data/lib/graphql/tracing/platform_tracing.rb +53 -9
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
- data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
- data/lib/graphql/tracing/scout_tracing.rb +19 -0
- data/lib/graphql/tracing/skylight_tracing.rb +8 -0
- data/lib/graphql/tracing/statsd_tracing.rb +42 -0
- data/lib/graphql/tracing.rb +14 -34
- data/lib/graphql/types/big_int.rb +1 -1
- data/lib/graphql/types/int.rb +9 -2
- data/lib/graphql/types/iso_8601_date.rb +3 -3
- data/lib/graphql/types/iso_8601_date_time.rb +25 -10
- data/lib/graphql/types/relay/base_connection.rb +11 -7
- data/lib/graphql/types/relay/base_edge.rb +2 -1
- data/lib/graphql/types/string.rb +7 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/union_type.rb +13 -28
- data/lib/graphql/unresolved_type_error.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +31 -6
- data/readme.md +1 -1
- metadata +34 -9
- data/lib/graphql/literal_validation_error.rb +0 -6
@@ -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
|
@@ -49,10 +48,26 @@ module GraphQL
|
|
49
48
|
raise ArgumentError, "Unexpected arguments: #{arguments}, must be Hash or GraphQL::Arguments"
|
50
49
|
end
|
51
50
|
|
52
|
-
sorted_h = normalized_args.to_h
|
51
|
+
sorted_h = stringify_args(field, normalized_args.to_h)
|
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)
|
@@ -71,18 +86,21 @@ module GraphQL
|
|
71
86
|
arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
|
72
87
|
end
|
73
88
|
|
74
|
-
next_args[normalized_arg_name] = stringify_args(arg_defn
|
89
|
+
next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
|
75
90
|
end
|
76
|
-
|
91
|
+
# Make sure they're deeply sorted
|
92
|
+
next_args.sort.to_h
|
77
93
|
when Array
|
78
94
|
args.map { |a| stringify_args(arg_owner, a) }
|
95
|
+
when GraphQL::Schema::InputObject
|
96
|
+
stringify_args(arg_owner, args.to_h)
|
79
97
|
else
|
80
98
|
args
|
81
99
|
end
|
82
100
|
end
|
83
101
|
|
84
102
|
def get_arg_definition(arg_owner, arg_name)
|
85
|
-
arg_owner.arguments.find { |
|
103
|
+
arg_owner.arguments[arg_name] || arg_owner.arguments.each_value.find { |v| v.keyword.to_s == arg_name }
|
86
104
|
end
|
87
105
|
end
|
88
106
|
end
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def instrument(type, field)
|
14
|
-
if type == @schema.subscription
|
14
|
+
if type == @schema.subscription.graphql_definition
|
15
15
|
# This is a root field of `subscription`
|
16
16
|
subscribing_resolve_proc = SubscriptionRegistrationResolve.new(field.resolve_proc)
|
17
17
|
field.redefine(resolve: subscribing_resolve_proc)
|
@@ -44,7 +44,10 @@ module GraphQL
|
|
44
44
|
|
45
45
|
# Wrap the proc with subscription registration logic
|
46
46
|
def call(obj, args, ctx)
|
47
|
-
|
47
|
+
result = nil
|
48
|
+
if @inner_proc && !@inner_proc.is_a?(GraphQL::Field::Resolve::BuiltInResolve)
|
49
|
+
result = @inner_proc.call(obj, args, ctx)
|
50
|
+
end
|
48
51
|
|
49
52
|
events = ctx.namespace(:subscriptions)[:events]
|
50
53
|
|
@@ -56,10 +59,12 @@ module GraphQL
|
|
56
59
|
arguments: args,
|
57
60
|
context: ctx,
|
58
61
|
)
|
59
|
-
|
62
|
+
result
|
60
63
|
elsif ctx.irep_node.subscription_topic == ctx.query.subscription_topic
|
61
|
-
|
62
|
-
|
64
|
+
if !result.nil?
|
65
|
+
result
|
66
|
+
elsif obj.is_a?(GraphQL::Schema::Object)
|
67
|
+
# The root object is _already_ the subscription update:
|
63
68
|
obj.object
|
64
69
|
else
|
65
70
|
obj
|
@@ -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
|
|
@@ -38,17 +40,17 @@ module GraphQL
|
|
38
40
|
elsif (events = context.namespace(:subscriptions)[:events])
|
39
41
|
# This is the first execution, so gather an Event
|
40
42
|
# for the backend to register:
|
41
|
-
|
43
|
+
event = Subscriptions::Event.new(
|
42
44
|
name: field.name,
|
43
|
-
arguments: arguments,
|
45
|
+
arguments: arguments_without_field_extras(arguments: arguments),
|
44
46
|
context: context,
|
45
47
|
field: field,
|
46
48
|
)
|
47
|
-
|
49
|
+
events << event
|
48
50
|
value
|
49
51
|
elsif context.query.subscription_topic == Subscriptions::Event.serialize(
|
50
52
|
field.name,
|
51
|
-
arguments,
|
53
|
+
arguments_without_field_extras(arguments: arguments),
|
52
54
|
field,
|
53
55
|
scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
|
54
56
|
)
|
@@ -60,6 +62,14 @@ module GraphQL
|
|
60
62
|
context.skip
|
61
63
|
end
|
62
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def arguments_without_field_extras(arguments:)
|
69
|
+
arguments.dup.tap do |event_args|
|
70
|
+
field.extras.each { |k| event_args.delete(k) }
|
71
|
+
end
|
72
|
+
end
|
63
73
|
end
|
64
74
|
end
|
65
75
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "securerandom"
|
3
|
+
require "graphql/subscriptions/broadcast_analyzer"
|
3
4
|
require "graphql/subscriptions/event"
|
4
5
|
require "graphql/subscriptions/instrumentation"
|
5
6
|
require "graphql/subscriptions/serialize"
|
6
|
-
|
7
|
-
require "graphql/subscriptions/action_cable_subscriptions"
|
8
|
-
end
|
7
|
+
require "graphql/subscriptions/action_cable_subscriptions"
|
9
8
|
require "graphql/subscriptions/subscription_root"
|
9
|
+
require "graphql/subscriptions/default_subscription_resolve_extension"
|
10
10
|
|
11
11
|
module GraphQL
|
12
12
|
class Subscriptions
|
@@ -18,20 +18,36 @@ module GraphQL
|
|
18
18
|
|
19
19
|
# @see {Subscriptions#initialize} for options, concrete implementations may add options.
|
20
20
|
def self.use(defn, options = {})
|
21
|
-
schema = defn.target
|
22
|
-
|
23
|
-
schema.subscriptions
|
21
|
+
schema = defn.is_a?(Class) ? defn : defn.target
|
22
|
+
|
23
|
+
if schema.subscriptions
|
24
|
+
raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
|
25
|
+
end
|
26
|
+
|
24
27
|
instrumentation = Subscriptions::Instrumentation.new(schema: schema)
|
25
|
-
defn.instrument(:field, instrumentation)
|
26
28
|
defn.instrument(:query, instrumentation)
|
29
|
+
defn.instrument(:field, instrumentation)
|
30
|
+
options[:schema] = schema
|
31
|
+
schema.subscriptions = self.new(**options)
|
32
|
+
schema.add_subscription_extension_if_necessary
|
27
33
|
nil
|
28
34
|
end
|
29
35
|
|
30
36
|
# @param schema [Class] the GraphQL schema this manager belongs to
|
31
|
-
def initialize(schema:, **rest)
|
37
|
+
def initialize(schema:, broadcast: false, default_broadcastable: false, **rest)
|
38
|
+
if broadcast
|
39
|
+
if !schema.using_ast_analysis?
|
40
|
+
raise ArgumentError, "`broadcast: true` requires AST analysis, add `using GraphQL::Analysis::AST` to your schema or see https://graphql-ruby.org/queries/ast_analysis.html."
|
41
|
+
end
|
42
|
+
schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
|
43
|
+
end
|
44
|
+
@default_broadcastable = default_broadcastable
|
32
45
|
@schema = schema
|
33
46
|
end
|
34
47
|
|
48
|
+
# @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
|
49
|
+
attr_reader :default_broadcastable
|
50
|
+
|
35
51
|
# Fetch subscriptions matching this field + arguments pair
|
36
52
|
# And pass them off to the queue.
|
37
53
|
# @param event_name [String]
|
@@ -72,40 +88,64 @@ module GraphQL
|
|
72
88
|
# `event` was triggered on `object`, and `subscription_id` was subscribed,
|
73
89
|
# so it should be updated.
|
74
90
|
#
|
75
|
-
# Load `subscription_id`'s GraphQL data, re-evaluate the query
|
76
|
-
#
|
77
|
-
# This is where a queue may be inserted to push updates in the background.
|
91
|
+
# Load `subscription_id`'s GraphQL data, re-evaluate the query and return the result.
|
78
92
|
#
|
79
93
|
# @param subscription_id [String]
|
80
94
|
# @param event [GraphQL::Subscriptions::Event] The event which was triggered
|
81
95
|
# @param object [Object] The value for the subscription field
|
82
|
-
# @return [
|
83
|
-
def
|
96
|
+
# @return [GraphQL::Query::Result]
|
97
|
+
def execute_update(subscription_id, event, object)
|
84
98
|
# Lookup the saved data for this subscription
|
85
99
|
query_data = read_subscription(subscription_id)
|
100
|
+
if query_data.nil?
|
101
|
+
delete_subscription(subscription_id)
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
86
105
|
# Fetch the required keys from the saved data
|
87
106
|
query_string = query_data.fetch(:query_string)
|
88
107
|
variables = query_data.fetch(:variables)
|
89
108
|
context = query_data.fetch(:context)
|
90
109
|
operation_name = query_data.fetch(:operation_name)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
110
|
+
result = nil
|
111
|
+
# this will be set to `false` unless `.execute` is terminated
|
112
|
+
# with a `throw :graphql_subscription_unsubscribed`
|
113
|
+
unsubscribed = true
|
114
|
+
catch(:graphql_subscription_unsubscribed) do
|
115
|
+
catch(:graphql_no_subscription_update) do
|
116
|
+
# Re-evaluate the saved query,
|
117
|
+
# but if it terminates early with a `throw`,
|
118
|
+
# it will stay `nil`
|
119
|
+
result = @schema.execute(
|
120
|
+
query: query_string,
|
121
|
+
context: context,
|
122
|
+
subscription_topic: event.topic,
|
123
|
+
operation_name: operation_name,
|
124
|
+
variables: variables,
|
125
|
+
root_value: object,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
unsubscribed = false
|
129
|
+
end
|
130
|
+
|
131
|
+
if unsubscribed
|
132
|
+
# `unsubscribe` was called, clean up on our side
|
133
|
+
# TODO also send `{more: false}` to client?
|
134
|
+
delete_subscription(subscription_id)
|
135
|
+
end
|
136
|
+
|
137
|
+
result
|
138
|
+
end
|
139
|
+
|
140
|
+
# Run the update query for this subscription and deliver it
|
141
|
+
# @see {#execute_update}
|
142
|
+
# @see {#deliver}
|
143
|
+
# @return [void]
|
144
|
+
def execute(subscription_id, event, object)
|
145
|
+
res = execute_update(subscription_id, event, object)
|
146
|
+
if !res.nil?
|
147
|
+
deliver(subscription_id, res)
|
148
|
+
end
|
109
149
|
end
|
110
150
|
|
111
151
|
# Event `event` occurred on `object`,
|
@@ -178,6 +218,16 @@ module GraphQL
|
|
178
218
|
Schema::Member::BuildType.camelize(event_or_arg_name.to_s)
|
179
219
|
end
|
180
220
|
|
221
|
+
# @return [Boolean] if true, then a query like this one would be broadcasted
|
222
|
+
def broadcastable?(query_str, **query_options)
|
223
|
+
query = GraphQL::Query.new(@schema, query_str, **query_options)
|
224
|
+
if !query.valid?
|
225
|
+
raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
|
226
|
+
end
|
227
|
+
GraphQL::Analysis::AST.analyze_query(query, @schema.query_analyzers)
|
228
|
+
query.context.namespace(:subscriptions)[:subscription_broadcastable]
|
229
|
+
end
|
230
|
+
|
181
231
|
private
|
182
232
|
|
183
233
|
# Recursively normalize `args` as belonging to `arg_owner`:
|
@@ -188,7 +238,11 @@ module GraphQL
|
|
188
238
|
# @return [Any] normalized arguments value
|
189
239
|
def normalize_arguments(event_name, arg_owner, args)
|
190
240
|
case arg_owner
|
191
|
-
when GraphQL::Field, GraphQL::InputObjectType
|
241
|
+
when GraphQL::Field, GraphQL::InputObjectType, GraphQL::Schema::Field, Class
|
242
|
+
if arg_owner.is_a?(Class) && !arg_owner.kind.input_object?
|
243
|
+
# it's a type, but not an input object
|
244
|
+
return args
|
245
|
+
end
|
192
246
|
normalized_args = {}
|
193
247
|
missing_arg_names = []
|
194
248
|
args.each do |k, v|
|
@@ -202,16 +256,35 @@ module GraphQL
|
|
202
256
|
end
|
203
257
|
|
204
258
|
if arg_defn
|
205
|
-
|
259
|
+
if arg_defn.loads
|
260
|
+
normalized_arg_name = arg_defn.keyword.to_s
|
261
|
+
end
|
262
|
+
normalized = normalize_arguments(event_name, arg_defn.type, v)
|
263
|
+
normalized_args[normalized_arg_name] = normalized
|
206
264
|
else
|
207
265
|
# Couldn't find a matching argument definition
|
208
266
|
missing_arg_names << arg_name
|
209
267
|
end
|
210
268
|
end
|
211
269
|
|
270
|
+
# Backfill default values so that trigger arguments
|
271
|
+
# match query arguments.
|
272
|
+
arg_owner.arguments.each do |name, arg_defn|
|
273
|
+
if arg_defn.default_value? && !normalized_args.key?(arg_defn.name)
|
274
|
+
default_value = arg_defn.default_value
|
275
|
+
# We don't have an underlying "object" here, so it can't call methods.
|
276
|
+
# This is broken.
|
277
|
+
normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: GraphQL::Query::NullContext)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
212
281
|
if missing_arg_names.any?
|
213
282
|
arg_owner_name = if arg_owner.is_a?(GraphQL::Field)
|
214
283
|
"Subscription.#{arg_owner.name}"
|
284
|
+
elsif arg_owner.is_a?(GraphQL::Schema::Field)
|
285
|
+
arg_owner.path
|
286
|
+
elsif arg_owner.is_a?(Class)
|
287
|
+
arg_owner.graphql_name
|
215
288
|
else
|
216
289
|
arg_owner.to_s
|
217
290
|
end
|
@@ -219,9 +292,9 @@ module GraphQL
|
|
219
292
|
end
|
220
293
|
|
221
294
|
normalized_args
|
222
|
-
when GraphQL::ListType
|
295
|
+
when GraphQL::ListType, GraphQL::Schema::List
|
223
296
|
args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) }
|
224
|
-
when GraphQL::NonNullType
|
297
|
+
when GraphQL::NonNullType, GraphQL::Schema::NonNull
|
225
298
|
normalize_arguments(event_name, arg_owner.of_type, args)
|
226
299
|
else
|
227
300
|
args
|
@@ -8,19 +8,23 @@ module GraphQL
|
|
8
8
|
module ActiveSupportNotificationsTracing
|
9
9
|
# A cache of frequently-used keys to avoid needless string allocations
|
10
10
|
KEYS = {
|
11
|
-
"lex" => "graphql
|
12
|
-
"parse" => "graphql
|
13
|
-
"validate" => "graphql
|
14
|
-
"analyze_multiplex" => "graphql
|
15
|
-
"analyze_query" => "graphql
|
16
|
-
"execute_query" => "graphql
|
17
|
-
"execute_query_lazy" => "graphql
|
18
|
-
"execute_field" => "graphql
|
19
|
-
"execute_field_lazy" => "graphql
|
11
|
+
"lex" => "lex.graphql",
|
12
|
+
"parse" => "parse.graphql",
|
13
|
+
"validate" => "validate.graphql",
|
14
|
+
"analyze_multiplex" => "analyze_multiplex.graphql",
|
15
|
+
"analyze_query" => "analyze_query.graphql",
|
16
|
+
"execute_query" => "execute_query.graphql",
|
17
|
+
"execute_query_lazy" => "execute_query_lazy.graphql",
|
18
|
+
"execute_field" => "execute_field.graphql",
|
19
|
+
"execute_field_lazy" => "execute_field_lazy.graphql",
|
20
|
+
"authorized" => "authorized.graphql",
|
21
|
+
"authorized_lazy" => "authorized_lazy.graphql",
|
22
|
+
"resolve_type" => "resolve_type.graphql",
|
23
|
+
"resolve_type_lazy" => "resolve_type.graphql",
|
20
24
|
}
|
21
25
|
|
22
26
|
def self.trace(key, metadata)
|
23
|
-
prefixed_key = KEYS[key] || "
|
27
|
+
prefixed_key = KEYS[key] || "#{key}.graphql"
|
24
28
|
ActiveSupport::Notifications.instrument(prefixed_key, metadata) do
|
25
29
|
yield
|
26
30
|
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Tracing
|
5
|
+
|
6
|
+
# This class uses the AppopticsAPM SDK from the appoptics_apm gem to create
|
7
|
+
# traces for GraphQL.
|
8
|
+
#
|
9
|
+
# There are 4 configurations available. They can be set in the
|
10
|
+
# appoptics_apm config file or in code. Please see:
|
11
|
+
# {https://docs.appoptics.com/kb/apm_tracing/ruby/configure}
|
12
|
+
#
|
13
|
+
# AppOpticsAPM::Config[:graphql][:enabled] = true|false
|
14
|
+
# AppOpticsAPM::Config[:graphql][:transaction_name] = true|false
|
15
|
+
# AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false
|
16
|
+
# AppOpticsAPM::Config[:graphql][:remove_comments] = true|false
|
17
|
+
class AppOpticsTracing < GraphQL::Tracing::PlatformTracing
|
18
|
+
# These GraphQL events will show up as 'graphql.prep' spans
|
19
|
+
PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
|
20
|
+
# These GraphQL events will show up as 'graphql.execute' spans
|
21
|
+
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
22
|
+
|
23
|
+
# During auto-instrumentation this version of AppOpticsTracing is compared
|
24
|
+
# with the version provided in the appoptics_apm gem, so that the newer
|
25
|
+
# version of the class can be used
|
26
|
+
|
27
|
+
def self.version
|
28
|
+
Gem::Version.new('1.0.0')
|
29
|
+
end
|
30
|
+
|
31
|
+
self.platform_keys = {
|
32
|
+
'lex' => 'lex',
|
33
|
+
'parse' => 'parse',
|
34
|
+
'validate' => 'validate',
|
35
|
+
'analyze_query' => 'analyze_query',
|
36
|
+
'analyze_multiplex' => 'analyze_multiplex',
|
37
|
+
'execute_multiplex' => 'execute_multiplex',
|
38
|
+
'execute_query' => 'execute_query',
|
39
|
+
'execute_query_lazy' => 'execute_query_lazy'
|
40
|
+
}
|
41
|
+
|
42
|
+
def platform_trace(platform_key, _key, data)
|
43
|
+
return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
|
44
|
+
|
45
|
+
layer = span_name(platform_key)
|
46
|
+
kvs = metadata(data, layer)
|
47
|
+
kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key)
|
48
|
+
|
49
|
+
transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
|
50
|
+
|
51
|
+
::AppOpticsAPM::SDK.trace(layer, kvs) do
|
52
|
+
kvs.clear # we don't have to send them twice
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def platform_field_key(type, field)
|
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}"
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def gql_config
|
72
|
+
::AppOpticsAPM::Config[:graphql] ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def transaction_name(query)
|
76
|
+
return if gql_config[:transaction_name] == false ||
|
77
|
+
::AppOpticsAPM::SDK.get_transaction_name
|
78
|
+
|
79
|
+
split_query = query.strip.split(/\W+/, 3)
|
80
|
+
split_query[0] = 'query' if split_query[0].empty?
|
81
|
+
name = "graphql.#{split_query[0..1].join('.')}"
|
82
|
+
|
83
|
+
::AppOpticsAPM::SDK.set_transaction_name(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def multiplex_transaction_name(names)
|
87
|
+
return if gql_config[:transaction_name] == false ||
|
88
|
+
::AppOpticsAPM::SDK.get_transaction_name
|
89
|
+
|
90
|
+
name = "graphql.multiplex.#{names.join('.')}"
|
91
|
+
name = "#{name[0..251]}..." if name.length > 254
|
92
|
+
|
93
|
+
::AppOpticsAPM::SDK.set_transaction_name(name)
|
94
|
+
end
|
95
|
+
|
96
|
+
def span_name(key)
|
97
|
+
return 'graphql.prep' if PREP_KEYS.include?(key)
|
98
|
+
return 'graphql.execute' if EXEC_KEYS.include?(key)
|
99
|
+
|
100
|
+
key[/^graphql\./] ? key : "graphql.#{key}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
104
|
+
def metadata(data, layer)
|
105
|
+
data.keys.map do |key|
|
106
|
+
case key
|
107
|
+
when :context
|
108
|
+
graphql_context(data[key], layer)
|
109
|
+
when :query
|
110
|
+
graphql_query(data[key])
|
111
|
+
when :query_string
|
112
|
+
graphql_query_string(data[key])
|
113
|
+
when :multiplex
|
114
|
+
graphql_multiplex(data[key])
|
115
|
+
else
|
116
|
+
[key, data[key]]
|
117
|
+
end
|
118
|
+
end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
|
119
|
+
end
|
120
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
121
|
+
|
122
|
+
def graphql_context(context, layer)
|
123
|
+
context.errors && context.errors.each do |err|
|
124
|
+
AppOpticsAPM::API.log_exception(layer, err)
|
125
|
+
end
|
126
|
+
|
127
|
+
[[:Path, context.path.join('.')]]
|
128
|
+
end
|
129
|
+
|
130
|
+
def graphql_query(query)
|
131
|
+
return [] unless query
|
132
|
+
|
133
|
+
query_string = query.query_string
|
134
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
135
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
136
|
+
|
137
|
+
[[:InboundQuery, query_string],
|
138
|
+
[:Operation, query.selected_operation_name]]
|
139
|
+
end
|
140
|
+
|
141
|
+
def graphql_query_string(query_string)
|
142
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
143
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
144
|
+
|
145
|
+
[:InboundQuery, query_string]
|
146
|
+
end
|
147
|
+
|
148
|
+
def graphql_multiplex(data)
|
149
|
+
names = data.queries.map(&:operations).map(&:keys).flatten.compact
|
150
|
+
multiplex_transaction_name(names) if names.size > 1
|
151
|
+
|
152
|
+
[:Operations, names.join(', ')]
|
153
|
+
end
|
154
|
+
|
155
|
+
def sanitize(query)
|
156
|
+
return unless query
|
157
|
+
|
158
|
+
# remove arguments
|
159
|
+
query.gsub(/"[^"]*"/, '"?"') # strings
|
160
|
+
.gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
|
161
|
+
.gsub(/\[[^\]]*\]/, '[?]') # arrays
|
162
|
+
end
|
163
|
+
|
164
|
+
def remove_comments(query)
|
165
|
+
return unless query
|
166
|
+
|
167
|
+
query.gsub(/#[^\n\r]*/, '')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -23,6 +23,14 @@ module GraphQL
|
|
23
23
|
def platform_field_key(type, field)
|
24
24
|
"#{type.graphql_name}.#{field.graphql_name}.graphql"
|
25
25
|
end
|
26
|
+
|
27
|
+
def platform_authorized_key(type)
|
28
|
+
"#{type.graphql_name}.authorized.graphql"
|
29
|
+
end
|
30
|
+
|
31
|
+
def platform_resolve_type_key(type)
|
32
|
+
"#{type.graphql_name}.resolve_type.graphql"
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|
@@ -63,6 +63,14 @@ module GraphQL
|
|
63
63
|
def platform_field_key(type, field)
|
64
64
|
"#{type.graphql_name}.#{field.graphql_name}"
|
65
65
|
end
|
66
|
+
|
67
|
+
def platform_authorized_key(type)
|
68
|
+
"#{type.graphql_name}.authorized"
|
69
|
+
end
|
70
|
+
|
71
|
+
def platform_resolve_type_key(type)
|
72
|
+
"#{type.graphql_name}.resolve_type"
|
73
|
+
end
|
66
74
|
end
|
67
75
|
end
|
68
76
|
end
|