lib/graphql/subscriptions.rb in graphql-1.10.14 vs lib/graphql/subscriptions.rb in graphql-1.11.0
- old
+ new
@@ -1,14 +1,16 @@
# frozen_string_literal: true
require "securerandom"
+require "graphql/subscriptions/broadcast_analyzer"
require "graphql/subscriptions/event"
require "graphql/subscriptions/instrumentation"
require "graphql/subscriptions/serialize"
if defined?(ActionCable)
require "graphql/subscriptions/action_cable_subscriptions"
end
require "graphql/subscriptions/subscription_root"
+require "graphql/subscriptions/default_subscription_resolve_extension"
module GraphQL
class Subscriptions
# Raised when either:
# - the triggered `event_name` doesn't match a field in the schema; or
@@ -27,18 +29,29 @@
instrumentation = Subscriptions::Instrumentation.new(schema: schema)
defn.instrument(:query, instrumentation)
defn.instrument(:field, instrumentation)
options[:schema] = schema
schema.subscriptions = self.new(**options)
+ schema.add_subscription_extension_if_necessary
nil
end
# @param schema [Class] the GraphQL schema this manager belongs to
- def initialize(schema:, **rest)
+ def initialize(schema:, broadcast: false, default_broadcastable: false, **rest)
+ if broadcast
+ if !schema.using_ast_analysis?
+ 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."
+ end
+ schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
+ end
+ @default_broadcastable = default_broadcastable
@schema = schema
end
+ # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
+ attr_reader :default_broadcastable
+
# Fetch subscriptions matching this field + arguments pair
# And pass them off to the queue.
# @param event_name [String]
# @param args [Hash<String, Symbol => Object]
# @param object [Object]
@@ -75,19 +88,17 @@
end
# `event` was triggered on `object`, and `subscription_id` was subscribed,
# so it should be updated.
#
- # Load `subscription_id`'s GraphQL data, re-evaluate the query, and deliver the result.
+ # Load `subscription_id`'s GraphQL data, re-evaluate the query and return the result.
#
- # This is where a queue may be inserted to push updates in the background.
- #
# @param subscription_id [String]
# @param event [GraphQL::Subscriptions::Event] The event which was triggered
# @param object [Object] The value for the subscription field
- # @return [void]
- def execute(subscription_id, event, object)
+ # @return [GraphQL::Query::Result]
+ def execute_update(subscription_id, event, object)
# Lookup the saved data for this subscription
query_data = read_subscription(subscription_id)
if query_data.nil?
# Jump down to the `delete_subscription` call
raise GraphQL::Schema::Subscription::UnsubscribedError
@@ -96,27 +107,39 @@
query_string = query_data.fetch(:query_string)
variables = query_data.fetch(:variables)
context = query_data.fetch(:context)
operation_name = query_data.fetch(:operation_name)
# Re-evaluate the saved query
- result = @schema.execute(
+ @schema.execute(
query: query_string,
context: context,
subscription_topic: event.topic,
operation_name: operation_name,
variables: variables,
root_value: object,
)
- deliver(subscription_id, result)
rescue GraphQL::Schema::Subscription::NoUpdateError
# This update was skipped in user code; do nothing.
+ nil
rescue GraphQL::Schema::Subscription::UnsubscribedError
# `unsubscribe` was called, clean up on our side
# TODO also send `{more: false}` to client?
delete_subscription(subscription_id)
+ nil
end
+ # Run the update query for this subscription and deliver it
+ # @see {#execute_update}
+ # @see {#deliver}
+ # @return [void]
+ def execute(subscription_id, event, object)
+ res = execute_update(subscription_id, event, object)
+ if !res.nil?
+ deliver(subscription_id, res)
+ end
+ end
+
# Event `event` occurred on `object`,
# Update all subscribers.
# @param event [Subscriptions::Event]
# @param object [Object]
# @return [void]
@@ -181,9 +204,19 @@
#
# @param event_or_arg_name [String, Symbol]
# @return [String]
def normalize_name(event_or_arg_name)
Schema::Member::BuildType.camelize(event_or_arg_name.to_s)
+ end
+
+ # @return [Boolean] if true, then a query like this one would be broadcasted
+ def broadcastable?(query_str, **query_options)
+ query = GraphQL::Query.new(@schema, query_str, **query_options)
+ if !query.valid?
+ raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
+ end
+ GraphQL::Analysis::AST.analyze_query(query, @schema.query_analyzers)
+ query.context.namespace(:subscriptions)[:subscription_broadcastable]
end
private
# Recursively normalize `args` as belonging to `arg_owner`: