graphql 1.5.15 → 1.6.0
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/graphql.rb +4 -19
- data/lib/graphql/analysis/analyze_query.rb +27 -2
- data/lib/graphql/analysis/query_complexity.rb +10 -11
- data/lib/graphql/argument.rb +7 -6
- data/lib/graphql/backwards_compatibility.rb +47 -0
- data/lib/graphql/compatibility/execution_specification.rb +14 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
- data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
- data/lib/graphql/directive.rb +1 -6
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution/execute.rb +174 -160
- data/lib/graphql/execution/field_result.rb +5 -1
- data/lib/graphql/execution/lazy.rb +2 -2
- data/lib/graphql/execution/lazy/resolve.rb +8 -11
- data/lib/graphql/execution/multiplex.rb +134 -0
- data/lib/graphql/execution/selection_result.rb +5 -0
- data/lib/graphql/field.rb +1 -8
- data/lib/graphql/filter.rb +53 -0
- data/lib/graphql/internal_representation/node.rb +11 -6
- data/lib/graphql/internal_representation/rewrite.rb +3 -3
- data/lib/graphql/query.rb +160 -78
- data/lib/graphql/query/arguments.rb +14 -25
- data/lib/graphql/query/arguments_cache.rb +6 -13
- data/lib/graphql/query/context.rb +28 -10
- data/lib/graphql/query/executor.rb +1 -0
- data/lib/graphql/query/literal_input.rb +10 -4
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +12 -7
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/rake_task.rb +140 -0
- data/lib/graphql/relay/array_connection.rb +29 -48
- data/lib/graphql/relay/base_connection.rb +9 -7
- data/lib/graphql/relay/mutation.rb +0 -11
- data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
- data/lib/graphql/relay/mutation/resolve.rb +7 -10
- data/lib/graphql/relay/relation_connection.rb +98 -61
- data/lib/graphql/scalar_type.rb +1 -15
- data/lib/graphql/schema.rb +90 -25
- data/lib/graphql/schema/build_from_definition.rb +22 -23
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/printer.rb +2 -1
- data/lib/graphql/schema/timeout_middleware.rb +6 -6
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/warden.rb +5 -9
- data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
- data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
- data/spec/graphql/argument_spec.rb +3 -3
- data/spec/graphql/execution/lazy_spec.rb +8 -114
- data/spec/graphql/execution/multiplex_spec.rb +131 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
- data/spec/graphql/query/arguments_spec.rb +14 -16
- data/spec/graphql/query/context_spec.rb +14 -1
- data/spec/graphql/query/literal_input_spec.rb +19 -13
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +12 -1
- data/spec/graphql/rake_task_spec.rb +57 -0
- data/spec/graphql/relay/array_connection_spec.rb +24 -3
- data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -10
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +167 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
- data/spec/graphql/schema/warden_spec.rb +80 -0
- data/spec/graphql/schema_spec.rb +26 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/lazy_helpers.rb +152 -0
- data/spec/support/star_wars/schema.rb +23 -0
- metadata +28 -3
- data/lib/graphql/schema/mask.rb +0 -55
| @@ -5,10 +5,9 @@ module GraphQL | |
| 5 5 | 
             
                #
         | 
| 6 6 | 
             
                # {Arguments} recursively wraps the input in {Arguments} instances.
         | 
| 7 7 | 
             
                class Arguments
         | 
| 8 | 
            -
                  extend  | 
| 8 | 
            +
                  extend Forwardable
         | 
| 9 9 |  | 
| 10 10 | 
             
                  def initialize(values, argument_definitions:)
         | 
| 11 | 
            -
                    @original_values = values
         | 
| 12 11 | 
             
                    @argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
         | 
| 13 12 | 
             
                      arg_defn = argument_definitions[inner_key.to_s]
         | 
| 14 13 |  | 
| @@ -22,24 +21,29 @@ module GraphQL | |
| 22 21 | 
             
                  # @param key [String, Symbol] name or index of value to access
         | 
| 23 22 | 
             
                  # @return [Object] the argument at that key
         | 
| 24 23 | 
             
                  def [](key)
         | 
| 25 | 
            -
                     | 
| 26 | 
            -
                    @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).value
         | 
| 24 | 
            +
                    @argument_values.fetch(key.to_s, NULL_ARGUMENT_VALUE).value
         | 
| 27 25 | 
             
                  end
         | 
| 28 26 |  | 
| 29 27 | 
             
                  # @param key [String, Symbol] name of value to access
         | 
| 30 28 | 
             
                  # @return [Boolean] true if the argument was present in this field
         | 
| 31 29 | 
             
                  def key?(key)
         | 
| 32 | 
            -
                     | 
| 33 | 
            -
                    @argument_values.key?(key_s)
         | 
| 30 | 
            +
                    @argument_values.key?(key.to_s)
         | 
| 34 31 | 
             
                  end
         | 
| 35 32 |  | 
| 36 | 
            -
                  # Get the  | 
| 37 | 
            -
                  # @return [Hash] the  | 
| 33 | 
            +
                  # Get the hash of all values, with stringified keys
         | 
| 34 | 
            +
                  # @return [Hash] the stringified hash
         | 
| 38 35 | 
             
                  def to_h
         | 
| 39 | 
            -
                    @ | 
| 36 | 
            +
                    @to_h ||= begin
         | 
| 37 | 
            +
                      h = {}
         | 
| 38 | 
            +
                      each_value do |arg_value|
         | 
| 39 | 
            +
                        arg_key = arg_value.definition.expose_as
         | 
| 40 | 
            +
                        h[arg_key] = unwrap_value(arg_value.value)
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
                      h
         | 
| 43 | 
            +
                    end
         | 
| 40 44 | 
             
                  end
         | 
| 41 45 |  | 
| 42 | 
            -
                  def_delegators : | 
| 46 | 
            +
                  def_delegators :to_h, :keys, :values, :each
         | 
| 43 47 |  | 
| 44 48 | 
             
                  # Access each key, value and type for the arguments in this set.
         | 
| 45 49 | 
             
                  # @yield [argument_value] The {ArgumentValue} for each argument
         | 
| @@ -101,21 +105,6 @@ module GraphQL | |
| 101 105 | 
             
                      value
         | 
| 102 106 | 
             
                    end
         | 
| 103 107 | 
             
                  end
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                  def string_key_values
         | 
| 106 | 
            -
                    @string_key_values ||= stringify_keys(to_h)
         | 
| 107 | 
            -
                  end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                  def stringify_keys(value)
         | 
| 110 | 
            -
                    case value
         | 
| 111 | 
            -
                    when Hash
         | 
| 112 | 
            -
                      value.inject({}) { |memo, (k, v)| memo[k.to_s] = stringify_keys(v); memo }
         | 
| 113 | 
            -
                    when Array
         | 
| 114 | 
            -
                      value.map { |v| stringify_keys(v) }
         | 
| 115 | 
            -
                    else
         | 
| 116 | 
            -
                      value
         | 
| 117 | 
            -
                    end
         | 
| 118 | 
            -
                  end
         | 
| 119 108 | 
             
                end
         | 
| 120 109 | 
             
              end
         | 
| 121 110 | 
             
            end
         | 
| @@ -5,21 +5,14 @@ module GraphQL | |
| 5 5 | 
             
                  # @return [Hash<InternalRepresentation::Node, GraphQL::Language::NodesDirectiveNode => Hash<GraphQL::Field, GraphQL::Directive => GraphQL::Query::Arguments>>]
         | 
| 6 6 | 
             
                  def self.build(query)
         | 
| 7 7 | 
             
                    Hash.new do |h1, irep_or_ast_node|
         | 
| 8 | 
            -
                       | 
| 8 | 
            +
                      Hash.new do |h2, definition|
         | 
| 9 9 | 
             
                        ast_node = irep_or_ast_node.is_a?(GraphQL::InternalRepresentation::Node) ? irep_or_ast_node.ast_node : irep_or_ast_node
         | 
| 10 10 | 
             
                        ast_arguments = ast_node.arguments
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                        else
         | 
| 17 | 
            -
                          GraphQL::Query::LiteralInput.from_arguments(
         | 
| 18 | 
            -
                            ast_arguments,
         | 
| 19 | 
            -
                            definition.arguments,
         | 
| 20 | 
            -
                            query.variables,
         | 
| 21 | 
            -
                          )
         | 
| 22 | 
            -
                        end
         | 
| 11 | 
            +
                        GraphQL::Query::LiteralInput.from_arguments(
         | 
| 12 | 
            +
                          ast_arguments,
         | 
| 13 | 
            +
                          definition.arguments,
         | 
| 14 | 
            +
                          query.variables,
         | 
| 15 | 
            +
                        )
         | 
| 23 16 | 
             
                      end
         | 
| 24 17 | 
             
                    end
         | 
| 25 18 | 
             
                  end
         | 
| @@ -4,6 +4,7 @@ module GraphQL | |
| 4 4 | 
             
                # Expose some query-specific info to field resolve functions.
         | 
| 5 5 | 
             
                # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
         | 
| 6 6 | 
             
                class Context
         | 
| 7 | 
            +
                  extend Forwardable
         | 
| 7 8 | 
             
                  attr_reader :execution_strategy
         | 
| 8 9 | 
             
                  # `strategy` is required by GraphQL::Batch
         | 
| 9 10 | 
             
                  alias_method :strategy, :execution_strategy
         | 
| @@ -41,24 +42,32 @@ module GraphQL | |
| 41 42 | 
             
                  def initialize(query:, values:)
         | 
| 42 43 | 
             
                    @query = query
         | 
| 43 44 | 
             
                    @schema = query.schema
         | 
| 44 | 
            -
                    @ | 
| 45 | 
            +
                    @provided_values = values || {}
         | 
| 46 | 
            +
                    # Namespaced storage, where user-provided values are in `nil` namespace:
         | 
| 47 | 
            +
                    @storage = Hash.new { |h, k| h[k] = {} }
         | 
| 48 | 
            +
                    @storage[nil] = @provided_values
         | 
| 45 49 | 
             
                    @errors = []
         | 
| 46 50 | 
             
                    @path = []
         | 
| 47 51 | 
             
                  end
         | 
| 48 52 |  | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
                   | 
| 53 | 
            +
                  def_delegators :@provided_values, :[], :[]=, :to_h, :key?, :fetch
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # @!method [](key)
         | 
| 56 | 
            +
                  #   Lookup `key` from the hash passed to {Schema#execute} as `context:`
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  # @!method []=(key, value)
         | 
| 59 | 
            +
                  #   Reassign `key` to the hash passed to {Schema#execute} as `context:`
         | 
| 53 60 |  | 
| 54 61 | 
             
                  # @return [GraphQL::Schema::Warden]
         | 
| 55 62 | 
             
                  def warden
         | 
| 56 63 | 
             
                    @warden ||= @query.warden
         | 
| 57 64 | 
             
                  end
         | 
| 58 65 |  | 
| 59 | 
            -
                  #  | 
| 60 | 
            -
                   | 
| 61 | 
            -
             | 
| 66 | 
            +
                  # Get an isolated hash for `ns`. Doesn't affect user-provided storage.
         | 
| 67 | 
            +
                  # @param ns [Object] a usage-specific namespace identifier
         | 
| 68 | 
            +
                  # @return [Hash] namespaced storage
         | 
| 69 | 
            +
                  def namespace(ns)
         | 
| 70 | 
            +
                    @storage[ns]
         | 
| 62 71 | 
             
                  end
         | 
| 63 72 |  | 
| 64 73 | 
             
                  def spawn(key:, selection:, parent_type:, field:)
         | 
| @@ -71,8 +80,14 @@ module GraphQL | |
| 71 80 | 
             
                    )
         | 
| 72 81 | 
             
                  end
         | 
| 73 82 |  | 
| 83 | 
            +
                  # Return this value to tell the runtime
         | 
| 84 | 
            +
                  # to exclude this field from the response altogether
         | 
| 85 | 
            +
                  def skip
         | 
| 86 | 
            +
                    GraphQL::Execution::Execute::SKIP
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 74 89 | 
             
                  class FieldResolutionContext
         | 
| 75 | 
            -
                    extend  | 
| 90 | 
            +
                    extend Forwardable
         | 
| 76 91 |  | 
| 77 92 | 
             
                    attr_reader :path, :selection, :field, :parent_type
         | 
| 78 93 |  | 
| @@ -84,7 +99,10 @@ module GraphQL | |
| 84 99 | 
             
                      @parent_type = parent_type
         | 
| 85 100 | 
             
                    end
         | 
| 86 101 |  | 
| 87 | 
            -
                    def_delegators :@context, | 
| 102 | 
            +
                    def_delegators :@context,
         | 
| 103 | 
            +
                      :[], :[]=, :key?, :fetch, :to_h, :namespace,
         | 
| 104 | 
            +
                      :spawn, :query, :schema, :warden, :errors,
         | 
| 105 | 
            +
                      :execution_strategy, :strategy, :skip
         | 
| 88 106 |  | 
| 89 107 | 
             
                    # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
         | 
| 90 108 | 
             
                    def ast_node
         | 
| @@ -32,7 +32,7 @@ module GraphQL | |
| 32 32 | 
             
                  end
         | 
| 33 33 |  | 
| 34 34 | 
             
                  def self.defaults_for(argument_defns)
         | 
| 35 | 
            -
                    if argument_defns.none? | 
| 35 | 
            +
                    if argument_defns.values.none?(&:default_value?)
         | 
| 36 36 | 
             
                      GraphQL::Query::Arguments::NO_ARGS
         | 
| 37 37 | 
             
                    else
         | 
| 38 38 | 
             
                      from_arguments([], argument_defns, nil)
         | 
| @@ -40,7 +40,8 @@ module GraphQL | |
| 40 40 | 
             
                  end
         | 
| 41 41 |  | 
| 42 42 | 
             
                  def self.from_arguments(ast_arguments, argument_defns, variables)
         | 
| 43 | 
            -
             | 
| 43 | 
            +
                    # Variables is nil when making .defaults_for
         | 
| 44 | 
            +
                    context = variables ? variables.context : nil
         | 
| 44 45 | 
             
                    values_hash = {}
         | 
| 45 46 | 
             
                    indexed_arguments = ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a }
         | 
| 46 47 |  | 
| @@ -56,7 +57,7 @@ module GraphQL | |
| 56 57 | 
             
                        if (!value_is_a_variable || (value_is_a_variable && variables.key?(ast_arg.value.name)))
         | 
| 57 58 |  | 
| 58 59 | 
             
                          value = coerce(arg_defn.type, ast_arg.value, variables)
         | 
| 59 | 
            -
                          value = arg_defn.prepare(value)
         | 
| 60 | 
            +
                          value = arg_defn.prepare(value, context)
         | 
| 60 61 |  | 
| 61 62 | 
             
                          if value.is_a?(GraphQL::ExecutionError)
         | 
| 62 63 | 
             
                            value.ast_node = ast_arg
         | 
| @@ -72,7 +73,12 @@ module GraphQL | |
| 72 73 | 
             
                      # a value wasn't provided from the AST,
         | 
| 73 74 | 
             
                      # then add the default value.
         | 
| 74 75 | 
             
                      if arg_defn.default_value? && !values_hash.key?(arg_name)
         | 
| 75 | 
            -
                         | 
| 76 | 
            +
                        value = arg_defn.default_value
         | 
| 77 | 
            +
                        # `context` isn't present when pre-calculating defaults
         | 
| 78 | 
            +
                        if context
         | 
| 79 | 
            +
                          value = arg_defn.prepare(value, context)
         | 
| 80 | 
            +
                        end
         | 
| 81 | 
            +
                        values_hash[arg_name] = value
         | 
| 76 82 | 
             
                      end
         | 
| 77 83 | 
             
                    end
         | 
| 78 84 |  | 
| @@ -24,7 +24,11 @@ module GraphQL | |
| 24 24 | 
             
                    def result
         | 
| 25 25 | 
             
                      result_name = irep_node.name
         | 
| 26 26 | 
             
                      raw_value = get_raw_value
         | 
| 27 | 
            -
                       | 
| 27 | 
            +
                      if raw_value == GraphQL::Execution::Execute::SKIP
         | 
| 28 | 
            +
                        {}
         | 
| 29 | 
            +
                      else
         | 
| 30 | 
            +
                        { result_name => get_finished_value(raw_value) }
         | 
| 31 | 
            +
                      end
         | 
| 28 32 | 
             
                    end
         | 
| 29 33 |  | 
| 30 34 | 
             
                    # GraphQL::Batch depends on this
         | 
| @@ -52,6 +52,11 @@ module GraphQL | |
| 52 52 | 
             
                    @internal_representation
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 |  | 
| 55 | 
            +
                  def analyzers
         | 
| 56 | 
            +
                    ensure_has_validated
         | 
| 57 | 
            +
                    @query_analyzers
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 55 60 | 
             
                  private
         | 
| 56 61 |  | 
| 57 62 | 
             
                  # If the pipeline wasn't run yet, run it.
         | 
| @@ -80,13 +85,13 @@ module GraphQL | |
| 80 85 | 
             
                      end
         | 
| 81 86 |  | 
| 82 87 | 
             
                      if @validation_errors.none?
         | 
| 83 | 
            -
                        query_analyzers = build_analyzers(@schema, @max_depth, @max_complexity)
         | 
| 84 | 
            -
                        if query_analyzers.any?
         | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
                        end
         | 
| 88 | 
            +
                        @query_analyzers = build_analyzers(@schema, @max_depth, @max_complexity)
         | 
| 89 | 
            +
                        # if query_analyzers.any?
         | 
| 90 | 
            +
                        #   analysis_results = GraphQL::Analysis.analyze_query(@query, query_analyzers)
         | 
| 91 | 
            +
                        #   @analysis_errors = analysis_results
         | 
| 92 | 
            +
                        #     .flatten # accept n-dimensional array
         | 
| 93 | 
            +
                        #     .select { |r| r.is_a?(GraphQL::AnalysisError) }
         | 
| 94 | 
            +
                        # end
         | 
| 90 95 | 
             
                      end
         | 
| 91 96 | 
             
                    end
         | 
| 92 97 |  | 
| @@ -3,7 +3,7 @@ module GraphQL | |
| 3 3 | 
             
              class Query
         | 
| 4 4 | 
             
                # Read-only access to query variables, applying default values if needed.
         | 
| 5 5 | 
             
                class Variables
         | 
| 6 | 
            -
                  extend  | 
| 6 | 
            +
                  extend Forwardable
         | 
| 7 7 |  | 
| 8 8 | 
             
                  # @return [Array<GraphQL::Query::VariableValidationError>]  Any errors encountered when parsing the provided variables and literal values
         | 
| 9 9 | 
             
                  attr_reader :errors
         | 
| @@ -0,0 +1,140 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            require "fileutils"
         | 
| 3 | 
            +
            module GraphQL
         | 
| 4 | 
            +
              # A rake task for dumping a schema as IDL or JSON.
         | 
| 5 | 
            +
              #
         | 
| 6 | 
            +
              # By default, schemas are looked up by name as constants using `schema_name:`.
         | 
| 7 | 
            +
              # You can provide a `load_schema` function to return your schema another way.
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # `load_context:`, `only:` and `except:` are supported so that
         | 
| 10 | 
            +
              # you can keep an eye on how filters affect your schema.
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              # @example Dump a Schema to .graphql + .json files
         | 
| 13 | 
            +
              #   require "graphql/rake_task"
         | 
| 14 | 
            +
              #   GraphQL::RakeTask.new(schema_name: "MySchema")
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              #   # $ rake graphql:schema:dump
         | 
| 17 | 
            +
              #   # Schema IDL dumped to ./schema.graphql
         | 
| 18 | 
            +
              #   # Schema JSON dumped to ./schema.json
         | 
| 19 | 
            +
              #
         | 
| 20 | 
            +
              # @example Invoking the task from Ruby
         | 
| 21 | 
            +
              #   require "rake"
         | 
| 22 | 
            +
              #   Rake::Task["graphql:schema:dump"].invoke
         | 
| 23 | 
            +
              class RakeTask
         | 
| 24 | 
            +
                include Rake::DSL
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                DEFAULT_OPTIONS = {
         | 
| 27 | 
            +
                  namespace: "graphql",
         | 
| 28 | 
            +
                  dependencies: nil,
         | 
| 29 | 
            +
                  schema_name: nil,
         | 
| 30 | 
            +
                  load_schema: ->(task) { Object.const_get(task.schema_name) },
         | 
| 31 | 
            +
                  load_context: ->(task) { {} },
         | 
| 32 | 
            +
                  only: nil,
         | 
| 33 | 
            +
                  except: nil,
         | 
| 34 | 
            +
                  directory: ".",
         | 
| 35 | 
            +
                  idl_outfile: "schema.graphql",
         | 
| 36 | 
            +
                  json_outfile: "schema.json",
         | 
| 37 | 
            +
                }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                # @return [String] Namespace for generated tasks
         | 
| 40 | 
            +
                attr_writer :namespace
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def rake_namespace
         | 
| 43 | 
            +
                  @namespace
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # @return [Array<String>]
         | 
| 47 | 
            +
                attr_accessor :dependencies
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                # @return [String] By default, used to find the schema as a constant.
         | 
| 50 | 
            +
                # @see {#load_schema} for loading a schema another way
         | 
| 51 | 
            +
                attr_accessor :schema_name
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # @return [<#call(task)>] A proc for loading the target GraphQL schema
         | 
| 54 | 
            +
                attr_accessor :load_schema
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # @return [<#call(task)>] A callable for loading the query context
         | 
| 57 | 
            +
                attr_accessor :load_context
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                # @return [<#call(member, ctx)>, nil] A filter for this task
         | 
| 60 | 
            +
                attr_accessor :only
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # @return [<#call(member, ctx)>, nil] A filter for this task
         | 
| 63 | 
            +
                attr_accessor :except
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                # @return [String] target for IDL task
         | 
| 66 | 
            +
                attr_accessor :idl_outfile
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # @return [String] target for JSON task
         | 
| 69 | 
            +
                attr_accessor :json_outfile
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # @return [String] directory for IDL & JSON files
         | 
| 72 | 
            +
                attr_accessor :directory
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                # Set the parameters of this task by passing keyword arguments
         | 
| 75 | 
            +
                # or assigning attributes inside the block
         | 
| 76 | 
            +
                def initialize(options = {})
         | 
| 77 | 
            +
                  default_dependencies = if Rake::Task.task_defined?("environment")
         | 
| 78 | 
            +
                    [:environment]
         | 
| 79 | 
            +
                  else
         | 
| 80 | 
            +
                    []
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  all_options = DEFAULT_OPTIONS
         | 
| 84 | 
            +
                    .merge(dependencies: default_dependencies)
         | 
| 85 | 
            +
                    .merge(options)
         | 
| 86 | 
            +
                  all_options.each do |k, v|
         | 
| 87 | 
            +
                    self.public_send("#{k}=", v)
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  if block_given?
         | 
| 91 | 
            +
                    yield(self)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  define_task
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                private
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                # Use the provided `method_name` to generate a string from the specified schema
         | 
| 100 | 
            +
                # then write it to `file`.
         | 
| 101 | 
            +
                def write_outfile(method_name, file)
         | 
| 102 | 
            +
                  schema = @load_schema.call(self)
         | 
| 103 | 
            +
                  context = @load_context.call(self)
         | 
| 104 | 
            +
                  result = schema.public_send(method_name, only: @only, except: @except, context: context)
         | 
| 105 | 
            +
                  dir = File.dirname(file)
         | 
| 106 | 
            +
                  FileUtils.mkdir_p(dir)
         | 
| 107 | 
            +
                  File.write(file, result)
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def idl_path
         | 
| 111 | 
            +
                  File.join(@directory, @idl_outfile)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def json_path
         | 
| 115 | 
            +
                  File.join(@directory, @json_outfile)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                # Use the Rake DSL to add tasks
         | 
| 119 | 
            +
                def define_task
         | 
| 120 | 
            +
                  namespace(@namespace) do
         | 
| 121 | 
            +
                    namespace("schema") do
         | 
| 122 | 
            +
                      desc("Dump the schema to IDL in #{idl_path}")
         | 
| 123 | 
            +
                      task :idl => @dependencies do
         | 
| 124 | 
            +
                        write_outfile(:to_definition, idl_path)
         | 
| 125 | 
            +
                        puts "Schema IDL dumped into #{idl_path}"
         | 
| 126 | 
            +
                      end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                      desc("Dump the schema to JSON in #{json_path}")
         | 
| 129 | 
            +
                      task :json => @dependencies do
         | 
| 130 | 
            +
                        write_outfile(:to_json, json_path)
         | 
| 131 | 
            +
                        puts "Schema JSON dumped into #{json_path}"
         | 
| 132 | 
            +
                      end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                      desc("Dump the schema to JSON and IDL")
         | 
| 135 | 
            +
                      task :dump => [:idl, :json]
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
            end
         | 
| @@ -3,78 +3,59 @@ module GraphQL | |
| 3 3 | 
             
              module Relay
         | 
| 4 4 | 
             
                class ArrayConnection < BaseConnection
         | 
| 5 5 | 
             
                  def cursor_from_node(item)
         | 
| 6 | 
            -
                    idx =  | 
| 6 | 
            +
                    idx = (after ? index_from_cursor(after) : 0) + sliced_nodes.find_index(item) + 1
         | 
| 7 7 | 
             
                    encode(idx.to_s)
         | 
| 8 8 | 
             
                  end
         | 
| 9 9 |  | 
| 10 | 
            -
                   | 
| 11 | 
            -
             | 
| 12 | 
            -
                   | 
| 10 | 
            +
                  private
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def first
         | 
| 13 | 
            +
                    return @first if defined? @first
         | 
| 13 14 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
                     | 
| 15 | 
            +
                    @first = get_limited_arg(:first)
         | 
| 16 | 
            +
                    @first = max_page_size if @first && max_page_size && @first > max_page_size
         | 
| 17 | 
            +
                    @first
         | 
| 16 18 | 
             
                  end
         | 
| 17 19 |  | 
| 18 | 
            -
                   | 
| 20 | 
            +
                  def last
         | 
| 21 | 
            +
                    return @last if defined? @last
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    @last = get_limited_arg(:last)
         | 
| 24 | 
            +
                    @last = max_page_size if @last && max_page_size && @last > max_page_size
         | 
| 25 | 
            +
                    @last
         | 
| 26 | 
            +
                  end
         | 
| 19 27 |  | 
| 20 28 | 
             
                  # apply first / last limit results
         | 
| 21 29 | 
             
                  def paged_nodes
         | 
| 22 30 | 
             
                    @paged_nodes ||= begin
         | 
| 23 31 | 
             
                      items = sliced_nodes
         | 
| 24 32 |  | 
| 25 | 
            -
                      if  | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 29 | 
            -
                       | 
| 33 | 
            +
                      items = items.first(first) if first
         | 
| 34 | 
            +
                      items = items.last(last) if last
         | 
| 35 | 
            +
                      items = items.first(max_page_size) if max_page_size && !first && !last
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      items
         | 
| 30 38 | 
             
                    end
         | 
| 31 39 | 
             
                  end
         | 
| 32 40 |  | 
| 33 41 | 
             
                  # Apply cursors to edges
         | 
| 34 42 | 
             
                  def sliced_nodes
         | 
| 35 | 
            -
                    @sliced_nodes ||=  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                  def index_from_cursor(cursor)
         | 
| 39 | 
            -
                    decode(cursor).to_i
         | 
| 40 | 
            -
                  end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  def starting_offset
         | 
| 43 | 
            -
                    @starting_offset = if before
         | 
| 44 | 
            -
                      [previous_offset, 0].max
         | 
| 45 | 
            -
                    elsif last
         | 
| 46 | 
            -
                      [nodes.count - last, 0].max
         | 
| 47 | 
            -
                    else
         | 
| 48 | 
            -
                      previous_offset
         | 
| 49 | 
            -
                    end
         | 
| 50 | 
            -
                  end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                  def previous_offset
         | 
| 53 | 
            -
                    @previous_offset ||= if after
         | 
| 54 | 
            -
                      index_from_cursor(after)
         | 
| 43 | 
            +
                    @sliced_nodes ||= if before && after
         | 
| 44 | 
            +
                      nodes[index_from_cursor(after)..index_from_cursor(before)-1] || []
         | 
| 55 45 | 
             
                    elsif before
         | 
| 56 | 
            -
                       | 
| 57 | 
            -
             | 
| 46 | 
            +
                      nodes[0..index_from_cursor(before)-2] || []
         | 
| 47 | 
            +
                    elsif after
         | 
| 48 | 
            +
                      nodes[index_from_cursor(after)..-1] || []
         | 
| 58 49 | 
             
                    else
         | 
| 59 | 
            -
                       | 
| 50 | 
            +
                      nodes
         | 
| 60 51 | 
             
                    end
         | 
| 61 52 | 
             
                  end
         | 
| 62 53 |  | 
| 63 | 
            -
                  def  | 
| 64 | 
            -
                     | 
| 65 | 
            -
                      limit_from_arguments = if first
         | 
| 66 | 
            -
                        first
         | 
| 67 | 
            -
                      else
         | 
| 68 | 
            -
                        if previous_offset < 0
         | 
| 69 | 
            -
                          previous_offset + (last ? last : 0)
         | 
| 70 | 
            -
                        else
         | 
| 71 | 
            -
                          last
         | 
| 72 | 
            -
                        end
         | 
| 73 | 
            -
                      end
         | 
| 74 | 
            -
                      [limit_from_arguments, max_page_size].compact.min
         | 
| 75 | 
            -
                    end
         | 
| 54 | 
            +
                  def index_from_cursor(cursor)
         | 
| 55 | 
            +
                    decode(cursor).to_i
         | 
| 76 56 | 
             
                  end
         | 
| 77 57 | 
             
                end
         | 
| 58 | 
            +
             | 
| 78 59 | 
             
                BaseConnection.register_connection_implementation(Array, ArrayConnection)
         | 
| 79 60 | 
             
              end
         | 
| 80 61 | 
             
            end
         |